pax_global_header00006660000000000000000000000064147643463520014530gustar00rootroot0000000000000052 comment=efbbdae81c7a18fc13424a7aae60c52c312f38c5 graphql-ruby-2.2.17/000077500000000000000000000000001476434635200142365ustar00rootroot00000000000000graphql-ruby-2.2.17/.codeclimate.yml000066400000000000000000000004151476434635200173100ustar00rootroot00000000000000version: "2" exclude_patterns: - "spec/" - "lib/graphql/language/lexer.rb" - "lib/graphql/language/parser.rb" # plugin - duplication - is failing on these - "spec/graphql/language/printer_spec.rb" - "lib/graphql/compatibility/query_parser_specification.rb" graphql-ruby-2.2.17/.gitattributes000066400000000000000000000000641476434635200171310ustar00rootroot00000000000000*.snap linguist-generated *.lock linguist-generated graphql-ruby-2.2.17/.github/000077500000000000000000000000001476434635200155765ustar00rootroot00000000000000graphql-ruby-2.2.17/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476434635200177615ustar00rootroot00000000000000graphql-ruby-2.2.17/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000026511476434635200224570ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve graphql-ruby title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Versions** `graphql` version: `rails` (or other framework): other applicable versions (`graphql-batch`, etc) **GraphQL schema** Include relevant types and fields (in Ruby is best, in GraphQL IDL is ok). Any custom extensions, etc? ```ruby class Product < GraphQL::Schema::Object field :id, ID, hash_key: :id # … end class ApplicationSchema < GraphQL::Schema query QueryType # … end ``` **GraphQL query** Example GraphQL query and response (if query execution is involved) ```graphql query { products { id title } } ``` ```json { "data": { "products": […] } } ``` **Steps to reproduce** Steps to reproduce the behavior **Expected behavior** A clear and concise description of what you expected to happen. **Actual behavior** What specifically went wrong? Place full backtrace here (if a Ruby exception is involved):
Click to view exception backtrace ``` Something went wrong 2.6.0/gems/graphql-1.9.17/lib/graphql/subscriptions/instrumentation.rb:34:in `after_query' … don't hesitate to include all the rows here: they will be collapsed ```
**Additional context** Add any other context about the problem here. With these details, we can efficiently hunt down the bug! graphql-ruby-2.2.17/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011271476434635200235070ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. graphql-ruby-2.2.17/.github/contributing.md000066400000000000000000000026511476434635200206330ustar00rootroot00000000000000# Contributing Thanks for getting involved! I hope the information below will help you contribute to graphql-ruby. ## Issues When reporting a bug, please include these details when applicable: - `graphql` version and other applicable versions (Rails, `graphql-batch`, etc) - Definitions of schema or relevant types and fields (in Ruby is best, in GraphQL IDL is ok) - Example GraphQL query and response (if query execution is involved) - Full backtrace (if a Ruby exception is involved) With these details, we can efficiently hunt down the bug! ## Code It's important for code to fit in with design and maintenance goals of the project. For this reason, consider an issue to discuss new features or large refactors. That way we can work together to find suitable solution! ## Legal By submitting a Pull Request, you disavow any rights or claims to any changes submitted to `graphql-ruby` and assign the copyright of those changes to Robert Mosolgo, the author and maintainer of `graphql-ruby`. If you cannot or don't want to reassign those rights (your employment contract for your employer may not allow this), don't submit a PR. Instead, open an issue so that someone else can give it a try. In short, contributing code means that the code belongs to the maintainer. That's generally what you want, since the burden of upkeep, support and distribution falls on the maintainer anyways. I hope this doesn't prohibit you from contributing! graphql-ruby-2.2.17/.github/dependabot.yml000066400000000000000000000001661476434635200204310ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" graphql-ruby-2.2.17/.github/workflows/000077500000000000000000000000001476434635200176335ustar00rootroot00000000000000graphql-ruby-2.2.17/.github/workflows/apidocs.yaml000066400000000000000000000030551476434635200221440ustar00rootroot00000000000000name: Publish API docs on: # For some reason, `on: release: ...` didn't work with `nektos/act` push: # Sequence of patterns matched against refs/tags tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 # for re-running on old tags workflow_dispatch: inputs: publish_version: required: true type: string permissions: {} jobs: build: permissions: contents: write # for git push (s0/git-publish-subdir-action) name: Publish API Docs runs-on: ubuntu-latest steps: - name: Checkout release tag uses: actions/checkout@v4 with: ref: ${{ env.GITHUB_REF }} - name: Checkout GitHub pages branch uses: actions/checkout@v4 with: path: gh-pages ref: gh-pages - uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' bundler-cache: true - name: Build API docs run: | bundle exec rake site:fetch_latest apidocs:gen_version["${{ inputs.publish_version || env.GITHUB_REF }}"] - name: Commit changes as last committer run: | git config --global user.name rmosolgo git config --global user.email rdmosolgo@github.com git status bundle exec rake site:commit_changes git status - name: Deploy to GitHub pages via gh-pages branch uses: s0/git-publish-subdir-action@master env: REPO: self BRANCH: gh-pages FOLDER: gh-pages GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} graphql-ruby-2.2.17/.github/workflows/ci.yaml000066400000000000000000000072341476434635200211200ustar00rootroot00000000000000name: CI Suite on: - pull_request jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake rubocop system_tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake compile - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true env: BUNDLE_GEMFILE: ./spec/dummy/Gemfile - run: bundle exec rails test:system working-directory: ./spec/dummy # Some coverage goals of these tests: # - Test once without Rails at all # - Test postgres, to make sure that the ActiveRecord # stuff works on that (as well as the default sqlite) # - Test mongoid -- and several versions, since they're quite different # - Run the JS unit tests once # - Test each major version of Rails we support # - Test the min/max minor Ruby version we support (and others?) test: strategy: fail-fast: false matrix: include: - gemfile: Gemfile ruby: 3.2 # Rails 6.1 is tested with Postgresql below - gemfile: gemfiles/rails_7.0.gemfile ruby: 3.1 - gemfile: gemfiles/rails_master.gemfile ruby: 3.1 - gemfile: gemfiles/rails_7.0.gemfile ruby: 3.2 - gemfile: gemfiles/rails_7.1.gemfile ruby: 3.3 - gemfile: gemfiles/rails_master.gemfile ruby: 3.3 runs-on: ubuntu-latest steps: - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true bundler: ${{ matrix.bundler || 'default' }} - run: bundle exec rake compile - run: bundle exec rake test javascript_test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: '21' - run: npm ci working-directory: ./javascript_client - run: npm test working-directory: ./javascript_client postgres_test: runs-on: ubuntu-latest services: postgres: image: postgres:latest env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - 5432:5432 # needed because the postgres container does not provide a healthcheck options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - run: echo BUNDLE_GEMFILE='gemfiles/rails_6.1_postgresql.gemfile' > $GITHUB_ENV - run: echo DATABASE='POSTGRESQL' > $GITHUB_ENV - run: echo PGPASSWORD='postgres' > $GITHUB_ENV - run: echo GRAPHQL_CPARSER=1 > $GITHUB_ENV - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake compile test mongodb_test: strategy: fail-fast: false matrix: gemfile: - gemfiles/mongoid_6.gemfile - gemfiles/mongoid_7.gemfile runs-on: ubuntu-latest services: mongodb: image: mongo ports: - 27017:27017 steps: - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake compile test graphql-ruby-2.2.17/.github/workflows/website.yaml000066400000000000000000000027141476434635200221650ustar00rootroot00000000000000name: Publish Website on: # For some reason, `on: release: ...` didn't work with `nektos/act` push: # Sequence of patterns matched against refs/tags tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 workflow_dispatch: permissions: {} jobs: build: permissions: contents: write # for git push (s0/git-publish-subdir-action) name: Publish Website runs-on: ubuntu-latest steps: - name: Checkout master uses: actions/checkout@v4 - name: Checkout GitHub pages branch uses: actions/checkout@v4 with: path: gh-pages ref: gh-pages - uses: ruby/setup-ruby@v1 with: ruby-version: '3.1' bundler-cache: true - name: Build HTML, reindex env: ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} run: | bundle exec rake site:fetch_latest site:build_doc site:update_search_index site:clean_html site:build_html - name: Commit changes as last committer run: | git config --global user.name "$(git log --format="%aN" -n 1)" git config --global user.email "$(git log --format="%aE" -n 1)" bundle exec rake site:commit_changes - name: Deploy to GitHub pages via gh-pages branch uses: s0/git-publish-subdir-action@master env: REPO: self BRANCH: gh-pages FOLDER: gh-pages GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} graphql-ruby-2.2.17/.gitignore000066400000000000000000000012771476434635200162350ustar00rootroot00000000000000.ruby-version doc/ .yardoc/ guides/yardoc/ pkg/ Gemfile.lock gemfiles/*.lock # Test database *.db .bundle/ vendor/ .idea/ _site .sass-cache .jekyll-metadata .jekyll-cache gh-pages/ tmp/* __*.db node_modules/ yarn.lock OperationStoreClient.js spec/integration/tmp .vscode/ *.swp *.swo .DS_Store # These are generated for distribution, but shouldn't be # versioned with the typescript source (which is in javascript_client/src): javascript_client/__tests__ javascript_client/subscriptions javascript_client/sync javascript_client/cli* javascript_client/index* # Don't commit compiled extension files: *.bundle *.so # Ragel generates Ruby type hints which is great, but I'm not ready to support them *.ri graphql-ruby-2.2.17/.rubocop.yml000066400000000000000000000051031476434635200165070ustar00rootroot00000000000000require: - ./cop/development/none_without_block_cop - ./cop/development/no_eval_cop - ./cop/development/no_focus_cop - ./lib/graphql/rubocop/graphql/default_null_true - ./lib/graphql/rubocop/graphql/default_required_true - ./cop/development/context_is_passed_cop AllCops: DisabledByDefault: true SuggestExtensions: false TargetRubyVersion: 2.4 Exclude: - 'lib/graphql/language/lexer.rb' - 'lib/graphql/language/parser.rb' - 'gemfiles/**/*' - 'tmp/**/*' - 'vendor/**/*' - 'spec/integration/tmp/**/*' - 'spec/fixtures/cop/*.rb' Development/ContextIsPassedCop: Exclude: - 'spec/**/*' - 'cop/**/*' # Legacy-related: - 'lib/graphql/*_type.rb' - 'lib/graphql/define/**/*.rb' - 'lib/graphql/relay/**/*' - 'lib/graphql/function.rb' - 'lib/graphql/directive.rb' - 'lib/graphql/field.rb' - 'lib/graphql/schema/traversal.rb' - 'lib/graphql/schema/possible_types.rb' - 'lib/graphql/schema/validation.rb' - 'lib/graphql/compatibility/**/*' - 'lib/graphql/static_validation/literal_validator.rb' - 'lib/graphql/static_validation/rules/**/*.rb' - 'lib/graphql/internal_representation/**/*.rb' # AST-related: - 'lib/graphql/schema/build_from_definition.rb' - 'lib/graphql/language/printer.rb' - 'lib/graphql/language/nodes.rb' # Build-time, not runtime: - 'lib/graphql/schema/addition.rb' - 'lib/graphql/schema/introspection_system.rb' # Methods from generators - 'lib/generators/graphql/type_generator.rb' Development/NoneWithoutBlockCop: Include: - "lib/**/*" - "spec/**/*" Development/NoEvalCop: Include: - "lib/**/*" Development/NoFocusCop: Include: - "spec/**/*" # def ... # end Layout/DefEndAlignment: EnforcedStyleAlignWith: def # value = if # # ... # end Layout/EndAlignment: EnforcedStyleAlignWith: variable Lint/UselessAssignment: Enabled: true Lint/DuplicateMethods: Enabled: true Metrics/ParameterLists: Max: 7 CountKeywordArgs: false Style/ClassAndModuleChildren: EnforcedStyle: nested Layout/EmptyLineBetweenDefs: AllowAdjacentOneLineDefs: true NumberOfEmptyLines: [0, 1, 2] Style/FrozenStringLiteralComment: Enabled: true Layout/IndentationWidth: Width: 2 Style/LambdaCall: EnforcedStyle: call Layout/LeadingCommentSpace: Enabled: true Naming/MethodName: EnforcedStyle: snake_case Style/WordArray: EnforcedStyle: brackets # ->(...) { ... } Layout/SpaceInLambdaLiteral: Enabled: true # Default is "require_no_space" GraphQL/DefaultNullTrue: Enabled: true GraphQL/DefaultRequiredTrue: Enabled: true graphql-ruby-2.2.17/.yardopts000066400000000000000000000001621476434635200161030ustar00rootroot00000000000000--no-private --markup=markdown --readme=readme.md --title='GraphQL Ruby API Documentation' 'lib/**/*.rb' - '*.md' graphql-ruby-2.2.17/CHANGELOG-enterprise.md000066400000000000000000000065531476434635200202360ustar00rootroot00000000000000# graphql-enterprise ### Breaking Changes ### Deprecations ### New Features ### Bug Fix # 1.3.4 (18 Mar 2024) - ObjectCache: use new `trace_with` API for instrumentation # 1.3.3 (30 Jan 2024) - ObjectCache: fix compatibility with `run_graphql_field` test helper #4816 # 1.3.2 (15 Jan 2024) ### Bug Fix - Limiters: Migrate to new `trace_with` instrumentation API, requires GraphQL-Ruby 2.0.18+ # 1.3.1 (12 June 2023) ### Bug Fix - Add missing `require "graphql"` #4511 # 1.3.0 (29 May 2023) ### New Features - Changesets: Add `added_in: ...` and `removed_in: ...` for inline definition changes # 1.2.0 (10 February 2023) ### New Features - Support the `redis-client` gem as `redis:` (requires graphql-pro 1.24.0+) # 1.1.14 (3 November 2022) ### New Features - Limiters: Support `dashboard_charts: false` to disable built-in instrumentation - Limiters: Support `assign_as:` to use a different accessor method for storing limiter instances on schema classes (add a corresponding `class << self; attr_accessor ...; end` to the schema class to use it) - Limiters: Support `context_key:` to put runtime info in a different key in query context - Runtime Limiter: Add `window_ms:` to runtime info # 1.1.13 (21 October 2022) ### Bug Fix - Limiter: handle missing fields in MutationLimiter # 1.1.12 (18 October 2022) ### New Features - Limiters: add MutationLimiter ### Bug Fix - ObjectCache: Update Redis calls to support redis-rb 5.0 # 1.1.11 (25 August 2022) ### Bug Fix - ObjectCache: also update `delete` to handle more than 1000 objects in Lua # 1.1.10 (19 August 2022) ### Bug Fix - ObjectCache: read and write objects 1000-at-a-time to avoid overloading Lua scripts in Redis # 1.1.9 (3 August 2022) ### New Features - ObjectCache: Add a message to context when a type or field causes a query to be treated as "private" ### Bug Fix - ObjectCache: skip the query analyzer when `context[:skip_object_cache]` is present # 1.1.8 (1 August 2022) ### New Features - ObjectCache: Add `ObjectType.cache_dependencies_for(object, context)` to customize dependencies for an object ### Bug Fix - ObjectCache: Fix to make `context[:object_cache][:objects]` a Set # 1.1.7 (28 July 2022) ### Bug Fix - ObjectCache: remove needless `resolve_type` calls # 1.1.6 (28 July 2022) ### Bug Fix - ObjectCache: persist the type names of cached objects, pass them to `Schema.resolve_type` when validating cached responses. # 1.1.5 (22 July 2022) ### New Features - ObjectCache: add `cache_introspection: { ttl: ... }` for setting an expiration (in seconds) on introspection fields. # 1.1.4 (19 March 2022) ### Bug Fix - ObjectCache: don't create a cache fingerprint if the query is found to be uncacheable during analysis. # 1.1.3 (3 March 2022) ### Bug Fix - Changesets: Return an empty set when a schema doesn't use changesets #3972 # 1.1.2 (1 March 2022) ### New Features - Changesets: Add introspection methods `Schema.changesets` and `Changeset.changes` # 1.1.1 (14 February 2021) ### Bug Fix - Changesets: don't require `context.schema` for plain-Ruby calls to introspection methods #3929 # 1.1.0 (24 November 2021) ### New Features - Changesets: Add `GraphQL::Enterprise::Changeset` # 1.0.1 (9 November 2021) ### Bug Fix - Object Cache: properly handle invalid queries #3703 # 1.0.0 (13 October 2021) ### New Features - Rate limiters: first release - Object cache: first release graphql-ruby-2.2.17/CHANGELOG-pro.md000066400000000000000000000743271476434635200166620ustar00rootroot00000000000000# graphql-pro ### Breaking Changes ### Deprecations ### New Features # 1.26.5 (1 Mar 2024) - OperationStore::AddOperationBatch: remove rescue for StatementInvalid inside transaction # 1.26.4 (27 Feb 2024) - RelationConnection: Don't quote table names that weren't quoted in original SQL, fixes #4508 (comment) # 1.26.3 (19 Feb 2024) - OperationStore: fix `sync` endpoint for Rack 3+ #4829 - Improve error message handling on Rails 7.1 # 1.26.2 (30 Jan 2024) - `@defer` / `@stream`: Write delimiters at the end of each patch so that clients respond to payloads more quickly. (Previously, delimiters were added at the start of each patch, so clients had to wait for the _next_ patch before they knew the current one was complete.) # 1.26.1 (23 Jan 2024) - Pundit integration: improve error message when a `Scope` class is missing # 1.26.0 (19 Jan 2024) ### Breaking Changes - Pundit integration: when the integration encounters an Array, it tries to find a configured policy class. If it can't, it raises an error. Previously, the integration silently permitted all items in the array; this default has been changed. See #4726 for more discussion of this change. If you encounter this error: - add `scope: false` to any fields that return arrays to get the previous behavior (no authorization applied to the array; each item authorized on its own) - Or, apply [scoping](https://graphql-ruby.org/authorization/scoping.html) by manually configuring a `pundit_policy_class` in the field's return type, then adding a `class Scope ...` inside that policy class. See the Pundit docs for the scope class API: https://github.com/varvet/pundit#scopes. If you want to continue passing _all_ arrays through without scoping (for example, if you know they've already been authorized another way, or if you're OK with them being authorized one-at-a-time later), you can implement this in your base `Scope` class, for example: ```ruby class BasePolicy class Scope def initialize(user, items) @user = user @items = items end def resolve if items.is_a?(Array) items else raise "Implement #{self.class}#resolve to filter these items: #{items.inspect}" end end end # Pass this scope class along to subclasses: def self.inherited(child_class) child_class.const_set(:Scope, Class.new(BasePolicy::Scope)) super end end ``` Alternatively, you could implement `def self.scope_items(items, context)` to skip arrays, for example: ```ruby module SkipScopingOnArrays def scope_items(items, context) if items.is_a?(Array) items # return these as-is else super end end end # Then, in type definitions which should skip scoping on arrays: extend SkipScopingOnArrays ``` # 1.25.2 (29 Dec 2023) ### New Features - Subscriptions: send `more: false` when the server calls `unsubscribe` # 1.25.1 (21 Dec 2023) ### Bug Fix - Ably subscriptions: update webhook handler for `presence.message` events # 1.25.0 (7 Dec 2023) ### Bug Fix - OperationStore: `.dup` the given `context` to avoid leaking state between queries when indexing - Subscriptions: use the schema or query logger to output debug messages # 1.24.15 (17 Nov 2023) ### Bug Fix - OperationStore: don't sort directives when normalizing, properly retain directives on Operation and Fragment definitions #4703 # 1.24.14 (16 Nov 2023) ### Bug Fix - OperationStore: also pass `context:` for ActiveRecord backend batches # 1.24.13 (13 Nov 2023) ### New Features - OperationStore: accept `context:` for `AddOperationBatch.call` #4697 # 1.24.12 (13 Nov 2023) ### New Features - OperationStore: accept `context:` to `Validate.validate` #4697 ### Bug Fix - OperationStore: don't rescue application-raised `KeyError`s #4699 # 1.24.11 (8 Nov 2023) ### Bug Fix - OperationStore: fix compatibility with 1.12.x #4696 # 1.24.10 (2 Nov 2023) ### Bug Fix - Improve compatibility with GraphQL-Ruby 1.12.x # 1.24.9 (4 Oct 2023) ### Bug Fix - OperationStore: Preserve variable default values of `false` when normalizing queries # 1.24.8 (29 Aug 2023) ### Bug Fix - OperationStore: search for operation during `Query#initialize` to avoid races with other instrumentation. Add `use ... trace: true` to get the old behavior. # 1.24.7 (16 June 2023) ### Bug Fix - Stable relation connections: quote table names and column names in `WHERE` clauses #4508 # 1.24.6 (24 May 2023) ### New Features - Defer: Add `incremental: true` for new proposed wire format, add example for working with GraphQL-Batch #4477 # 1.24.5 (24 May 2023) ### Bug Fix - Stable relation connection: Quote table names and column names in selects and orders #4485 # 1.24.4 (18 April 2023) ### Bug Fix - `@defer`: update `context[:current_path]` usage to fix `path:` on deferred errors # 1.24.3 (14 April 2023) ### Bug Fix - `OperationStore`: fix when used with Changesets (or other ways of defining arguments with the same name) #4440 # 1.24.2 (20 Mar 2023) ### Bug Fix - Remove debug output, oops # 1.24.1 (20 Mar 2023) ### Bug Fix - Fix `OperationStore` with new module-based execution traces (#4389) # 1.24.0 (10 Feb 2023) ### New Features - Support the `redis-client` gem as `redis:` # 1.23.9 (2 Feb 2023) ### Bug Fix - Dashboard: Support Ruby 3.2.0 # 1.23.8 (27 Jan 2023) ### New Features - OperationStore: Support `Changeset-Version` header for syncing with changesets #4304 # 1.23.7 (25 Jan 2023) ### Bug Fix - Stable Relation Connections: Fix handling of Postgres JSON accesses # 1.23.6 ### New Features - Subscriptions: accept `connection_pool:` instead of `redis:` for use with the `connection_pool` gem ### Bug Fix - Stable connections: rescue `ActiveRecord::StatementInvalid` when loading nodes and return a client-facing error instead # 1.23.5 (29 December 2022) ### New Features - Ably subscriptions: Also listen for `presence.leave` webhooks to clean up subscriptions more quickly # 1.23.4 (20 December 2022) ### Bug Fix - Dashboard: nicely render subscriptions that are not found or cleaned up by `read_subscription_failed_error` # 1.23.3 (19 December 2022) ### New Features - Add `GraphQL::Pro::Subscriptions#read_subscription_failed_error` for handling errors that are raised when reloading queries from storage # 1.23.2 (18 October 2022) ### New Features - Add dashboard component for Enterprise mutation limiter # 1.23.1 (25 August 2022) ### Bug Fix - Redis: update redis usage to be forward-compatible with redis 5.x #4167 # 1.23.0 (2 August 2022) ### New Features - Stable connections: support SQL queries that sort by `IS NOT NULL` #4153 # 1.22.3 (26 July 2022) ### Bug Fix - Stable connections: handle `edges {...}` when an invalid cursor is given #4148 # 1.22.2 (20 April 2022) ### Bug Fix - Use `deprecated_accepts_definitions` to stop warnings when loading this gem on 1.13.x # 1.22.1 (22 March 2022) ### Bug Fix - Pusher subscriptions: don't try to send empty trigger batches to Pusher # 1.22.0 (19 March 2022) ### New Features - Pusher subscriptions: it now sends updates in groups of 10 by default, pass `use ..., batch_size: 1` to revert to the previous behavior. - OperationStore: when using ActiveRecord for storage, it now batches updates to `last_used_at` every 5 seconds. Pass `use ..., update_last_used_at_every: 0` to update that column synchronously, instead, as before. # 1.21.6 (16 March 2022) ### Bug Fix - OperationStore: Fix no method error in Redis pipeline usage # 1.21.5 (7 March 2022) ### Bug Fix - Postgres stable connection: support more complex aliased selects #3976 # 1.21.4 (15 February 2022) ### Bug Fix - Encoders: don't extend `DeprecatedDefine` if it's not present (graphql-ruby < 1.12) # 1.21.3 (9 February 2022) ### New Features - Future-proof for GraphQL-Ruby 2.0 # 1.21.2 (27 January 2022) ### New Features - Dashboard, Routes: support lazy-loading the schema with `Routes::Lazy` #3868 - OperationStore: Update deprecated usage of `@redis.pipelined` to address warning # 1.21.1 (20 January 2022) ### Bug Fix - Stream, Defer: Include `hasNext: true|false` in patches # 1.21.0 (20 January 2022) ### New Features - Stream: Add `@stream` directive for evaluating list items one-at-a-time # 1.20.4 (4 December 2021) ### Bug Fix - Stable connections: Fix using startCursor / endCursor without nodes #3752 # 1.20.3 (27 November 2021) ### Bug Fix - Stable Connections: Properly handle cursors containing invalid JSON #3735 # 1.20.2 (15 November 2021) ### New Features - Operation Store sync: ActiveRecord backend performance improvements: when syncing operations, only validate newly-added operations, reduce allocations when normalizing incoming query strings # 1.20.1 (8 November 2021) ### Bug Fix - Operation Store sync: fix when operations are re-synced with new aliases # 1.20.0 (5 November 2021) ### New Features - Operation Store: Use Rails `insert_all` for better performance when adding new operations # 1.19.2 (26 October 2021) ### New Features - Pundit and CanCan integrations: Add `ResolverIntegration` modules for plain resolvers #3392 ### Bug Fix - OperationStore Redis backend: pipeline updates to last_used_at values #3672 # 1.19.1 (15 October 2021) ### Bug Fix - OperationStore: fix a stack overflow error on GraphQL 1.9 #3653 # 1.19.0 (13 October 2021) ### New Features - Dashboard: add a component for GraphQL-Enterprise rate limiters # 1.18.3 (1 Sept 2021) ### Breaking Changes - Stable cursors: raise an error on unrecognized orderings instead of ignoring them #3605 ### Bug Fix - Stable cursors: Handle `Arel::Attributes::Attribute` and `Arel::SqlLiteral` #3605 # 1.18.2 (16 August 2021) ### Bug Fix - Stable connections: nicely handle incoming cursors with too many sort values #3581 # 1.18.1 (20 July 2021) ### Bug Fix - Stable connections: improve handling of `SELECT` with `CASE` #3558 - Defer: fix to support runtime wrapper objects in graphql-ruby 1.12.13 # 1.18.0 (31 May 2021) ### New Features - Ably subscriptions: send `quickAck: true` for a faster response from Ably ### Bug Fix - Defer: support dataloader inside deferred blocks # 1.17.15 (29 Apr 2021) ### New Features - Defer: support `label:` argument which is returned in the patch for that deferral #3454 # 1.17.14 (14 Apr 2021) - Dashboard: fix stack error when OperationStore isn't configured on a class-based schema # 1.17.13 (14 Apr 2021) - Stable Connections: When using aliases and GROUP BY, replace the alias when building a HAVING condition. - Pundit integration: Add a `use_owner_role(true)` configuration option # 1.17.12 (3 Apr 2021) ### Bug Fix - Stable Connections: Re-select aliased fields that are referenced by ORDER BY. #3421 # 1.17.11 (12 Mar 2021) ### Bug Fix - Pundit integration: properly halt when `unauthorized_by_pundit` returns errors-as-data after a mutation argument fails authorization #3384 # 1.17.10 (11 Mar 2021) ### Bug Fix - Pundit, CanCan integrations: properly call configured auth hooks for arguments that are lists and input objects # 1.17.9 (3 Mar 2021) ### Bug Fix - Fix OperationStore assignment on GraphQL-Ruby 1.9 # 1.17.8 (23 Feb 2021) ### New Features - Subscriptions: change the default `cleanup_delay_s:` to 5 seconds (use `cleanup_delay_s: 0` to get the old behavior) ### Bug Fix - Subscriptions: Handle unsubscribe race condition #3357 # 1.17.7 (19 Feb 2021) ### New Features - CanCan integration: support `can_can_subject:` config for overriding the use of `object` as the CanCan subject #3350 ### Bug Fixes - Subscriptions: Support `Redis::Namespace` without deprecation warnings for `script load` #3347 # 1.17.6 (18 Feb 2021) ### New Features - Stable connections: implement `range_add_edge` to leverage GraphQL-Ruby 1.12.5's improved RangeAdd #2184 ### Bug Fix - Defer: Update to work with Dataloader # 1.17.5 (13 Feb 2021) ### Bug Fix - Subscriptions: Use `MULTI` instead of Lua for some operations - Subscriptions: Use `EVAL_SHA` for duplicate scripts to reduce network overhead #3285 - Subscriptions: Don't use `redis.call`, which is unsupported in the `redis-namespace` gem #3322 # 1.17.4 (4 Feb 2021) ## Bug Fix - Stable Relation Connection: Don't emit `OR ... IS NULL` for columns that are known to be `null: false` (this improves index utilization) ## 1.17.3 (2 Feb 2021) ### New Features - Pusher subscriptions: `context[:compress_pusher_payload] = true` will cause the payload to be gzipped before being sent to Pusher ## 1.17.2 (30 Jan 2021) ### Bug Fix - Subscriptions: don't generate keys inside Lua scripts (for redis-namespace compatibility, and probably better support for Redis cluster) #3307 ## 1.17.1 (25 Jan 2021) ### New Features - OperationStore: add `OperationStore::AddOperationBatch.call` for adding data directly - Subscriptions: use Lua scripts for more efficient Redis access ## 1.17.0 (20 Jan 2021) ### New Features - Updates for 1.12.0 compatibility ### Bug Fix - OperationStore: improve performance by batching reads and writes during updates ## 1.16.2 (21 Dec 2020) ### New Features - Subscriptions: Add `stale_ttl_s:` and `cleanup_delay_s:` to customize persistence in Redis #3252 ## 1.16.1 (3 Dec 2020) ### Bug Fix - Fix duplicate calls to `Argument#authorized?` in CanCan and Pundit integrations #3242 ## 1.16.0 (10 Nov 2020) ### New Features - Ably Subscriptions: `cipher_base:` sets up end-to-end encryption ## 1.15.7 (29 Sept 2020) ### Bug Fix - Encoder: fix Ruby 2.7 warning #3161 - Stable connections: Handle `ARRAY[...]` selections and cursors on Postgres #3166 - Pundit: properly lookup policies for list inputs #3146 ## 1.15.6 (17 Sept 2020) ### Bug Fix - Stable Connections: Use method access to get `.cursor_#{idx}` values instead of `.attributes[:cursor_#{idx}]`, fixes #3149 ## 1.15.5 ### New Features - Stable Connections: use `.to_sql` to handle orderings that use complex Arel expressions (#3109) ## 1.15.4 (28 July 2020) ### New Features - Pundit: add `pundit_policy_class_for(object, context)` and `pundit_role_for(object, context)` for custom runtime lookups ## 1.15.3 (17 July 2020) ### Bug Fix - Subscriptions: don't send empty updates when subscriptions return `:no_update` ## 1.15.2 (16 July 2020) ### New Features - OperationStore: improve handling of archived operations in index views ## 1.15.1 (16 July 2020) (Oops, bad release!) ## 1.15.0 (15 July 2020) - OperationStore: Store & display `last_used_at` for operation store clients and operations. To upgrade, add the column to your ActiveRecord table: ```ruby add_column :graphql_client_operations, :last_used_at, :datetime ``` (It works out-of-the-box with the Redis backend.) You can opt out of this feature by adding `use GraphQL::Pro::OperationStore, ... default_touch_last_used_at: false` to your schema setup. - OperationStore: Add archive/unarchive workflow for operations. To upgrade, add the column to your table: ```ruby add_column :graphql_client_operations, :is_archived, :boolean, index: true ``` (It works out-of-the-box with the Redis backend.) - OperationStore: Fix indexing of enum values ## 1.14.1 (29 June 2020) - CanCan: Accept `can_can_attribute:` configuration, which is passed as the third input to `.can?(...)` ## 1.14.0 (13 June 2020) ### New Features - Add PubnubSubscriptions - Update subscription implementations to support `broadcast: true` when available ### Bug Fix - More Ruby 2.7 warning fixes ## 1.13.6 (8 June 2020) ### Bug Fix - Return the proper `pageInfo` values when it's requested before `edges` or `nodes` (#2972) ## 1.13.5 (11 May 2020) ### Bug Fix - Fix some warnings on Ruby 2.7 ## 1.13.4 (17 Apr 2020) ### Bug Fix - StableRelationConnection: properly return `hasNextPage: true` when `before` and `max_page_size` are used. ## 1.13.3 (2 Apr 2020) ### New Features - `GraphQL::Pro::OperationStore::Migration` can be used to copy persisted operations from one backend to another (eg, ActiveRecord to Redis). See the source file, `lib/graphql/pro/operation_store/migration.rb` for docs. ## 1.13.2 (28 Mar 2020) ### Deprecations - `GraphQL::Pro::Subscriptions` is deprecated; use `GraphQL::Pro::PusherSubscriptions` instead which works the same, but better (see below). This new name avoids confusion with the later-added `AblySubscriptions`. ### New Features - `GraphQL::Pro::PusherSubscriptions` replaces `GraphQL::Pro::Subscriptions` and adds orphaned record cleanup. (No more dangling records in Redis.) ## 1.13.1 (12 Mar 2020) - Use `nonce: true` when working with cursors in new stable connections ## 1.13.0 (10 Feb 2020) ### New Features - OperationStore supports a `redis:` backend - OperationStore supports an arbitrary `backend_class:` for persistence operations ### Bug Fix - Use a loop when clearing Redis subscription state to avoid large stack traces #2701 - Handle empty subscription keys when publishing updates #2061 ## 1.12.2 (22 Jan 2020) ### Bug Fix - Improve backwards compat with OperationStore (Improve adding `.tracer`, use `.graphql_name` when indexing) ## 1.12.1 (20 Jan 2020) ### Bug Fix - Fix OperationStore on class-based schemas with query instrumenters that use the query string ## 1.12.0 (20 Jan 2020) ### Deprecations - `GraphQL::Pro::Monitoring` is deprecated; see Tracing for a replacement: https://graphql-ruby.org/queries/tracing.html - `GraphQL::Pro::Repository` is deprecated; see OperationStore for a replacement: https://graphql-ruby.org/operation_store/overview.html ### New Features - New stable connection support based on GraphQL-Ruby 1.10's new pagination implementation. New classes provide better handling of `NULL` values in order-by columns and they can be applied on a field-by-field basis(`GraphQL::Pro::SqliteStableRelationConnection`, `GraphQL::Pro::MySQLStableRelationConnection`, `GraphQL::Pro::PostgresStableRelationConnection`). ### Bug Fix - Add the Access query analyzer to class-based schemas ## 1.11.0 (10 Oct 2019) ### New Features - Forwards-compatibility for graphql 1.10.0 - Support 1.10.0.pre1's input object argument `loads:` authorization ## 1.10.8 (8 Oct 2019) ### Bug Fix - Continue authorizing input object arguments - Use millisecond-aware string format for datetimes in cursors ## 1.10.7 (22 Jul 2019) ### Bug Fix - Support multiple subscriptions in one document ## 1.10.6 (27 Jun 2019) ### New Features - Support custom `#can_can_ability` methods on query context for CanCanIntegration - Support custom `#pundit_user` method on query context for PunditIntegration ### Bug Fix - Fix off-by-one error when paginating backwards from the last item in a stable relation connection ## 1.10.5 (11 May 2019) ### New Features - Include expected HMAC digest in OperationStore debug output ## 1.10.4 (26 Mar 2019) ### Bug Fix - Include content-length and content-type headers in OperationStore JSON responses ## 1.10.3 (13 Mar 2019) ### Bug Fix - Support stable connections ordered by Arel SQL literals ## 1.10.2 (11 Mar 2019) ### Bug Fix - Support stable connections on realized views (which don't have primary keys) ## 1.10.1 (8 Mar 2019) ### Bug Fix - Pundit integration: support `pundit_policy_class` String names when scoping connections ## 1.10.0 (5 Mar 2019) ### New Features - Add `GraphQL::Pro::Defer`, implementing `@defer` for streaming responses ## 1.9.13 (4 Mar 2019) ### Bug Fix - Pundit integration: correctly authorize fields when Query root is nil ## 1.9.12 (22 Feb 2019) ### Bug Fix - Pundit integration: use overriden `pundit_policy_class` for scoping and mutation authorization ## 1.9.11 (20 Feb 2019) ### Bug Fix - Pundit integration: Fields use the owner's configured `pundit_policy_class` if there is one - Pundit integration: avoid conflicts with `#initialize` for schema classes that don't need it ## 1.9.10 (19 Feb 2019) ### Bug Fix - Support inheritance with `pundit_policy_class(...)` ## 1.9.9 (13 Feb 2019) ### New Features - Support `pundit_policy_class(...)` and `pundit_policy_class:` to manually specify a class or class name. ## 1.9.8 (30 Jan 2019) ### New Features - Inject `context` into policy lookup hooks instead of just the user ## 1.9.7 (30 Jan 2019) ### New Features - Extract `pundit_policy` and `scope_by_pundit_policy` hooks for user override ## 1.9.6 (18 Jan 2019) ### Bug Fix - Properly render subscription context in dashboard ## 1.9.5 (14 Jan 2019) ## Bug Fix - Don't pass arrays to Pundit scopes (fixes https://github.com/rmosolgo/graphql-ruby/issues/2008) ## 1.9.4 (11 Jan 2019) ## Bug Fix - Prepare for future compat with graphql-ruby 1.9 ## 1.9.3 (3 Dec 2018) ### Bug Fix - Include table name when adding a default order-by-id to ActiveRecord Relations - Raise if a required cursor attribute is missing - Improve `rake routes` output for operation store endpoint - Support already-parsed queries in subscription RedisStorage ## 1.9.2 (2 Nov 2018) ### Bug Fix - Derp, remove the dummy app's `.log` files from the gem bundle - Fix ordering bug when a SQL function call doesn't have an explicit order ## 1.9.1 (1 Nov 2018) ### Bug Fix - Fix Pusher reference in AblySubscriptions ## 1.9.0 (27 Oct 2018) ### New Features - Add `GraphQL::Pro::AblySubscriptions` for GraphQL subscriptions over Ably.io transport ## 1.8.2 (22 Oct 2018) ### Bug Fix - Support `NULLS LAST` in stable cursors ## 1.8.1 (16 Oct 2018) ### Bug Fix - Improve operation store models to work when `config.active_record.primary_key_prefix_type` is set ## 1.8.0 (11 Oct 2018) ### New Features - Support Rails 3.2 with OperationStore - Use `.select` to filter items in CanCanIntegration ### Bug Fix - Properly send an _ability_ and the configured `can_can_action` to `.accessible_by` - Use a string (not integer) for `Content-Length` header in the dashboard ## 1.7.13 (2 Oct 2018) ### Breaking Change - `PunditIntegration`: instead of raising `MutationAuthorizationFailed` when an argument fails authorization, it will send a `GraphQL::UnauthorizedError` to your `Schema.unauthorized_object` hook. (This is what all other authorization failures do.) To retain the previous behavior, in your base mutation, add: ```ruby def unauthorized_by_pundit(owner, value) # Raise a runtime error to halt query execution raise "#{value} failed #{owner}'s auth check" end ``` Otherwise, customize the handling of this behavior with `Schema.unauthorized_object`. ### Bug Fix - Auth: mutation arguments which have authorization constraints but _don't_ load an object from the database will have _mutation instance_ passed to the auth check, not the input value. ## 1.7.12 (29 Aug 2018) ### New Features - Add `GraphQL::Pro::CanCanIntegration` which leverages GraphQL-Ruby's built-in auth ## 1.7.11 (21 Aug 2018) ### Bug Fix - `PunditIntegration`: Don't try to authorize loaded objects when they're `nil` ## 1.7.10 (10 Aug 2018) ### New Features - Update `PunditIntegration` for arguments, unions, interfaces and mutations ## 1.7.9 (9 Aug 2018) ### New Features - Add a new `PunditIntegration` which leverages the built-in authorization methods ## 1.7.8 (10 July 2018) ### Bug Fix - Authorization: fix scoping lists of abstract type when there's no `#scope` method on the strategy ## 1.7.7 (10 May 2018) ### Bug Fix - Fix ordering of authorization field instrumenter (put it at the end, not the beginning of the list) ## 1.7.6 (2 May 2018) ### New Features - Authorization: Add `view`/`access`/`authorize` methods to `GraphQL::Schema::Mutation` ## 1.7.5 (19 Apr 2018) ### New Features - Authorization: when a `fallback:` configuration is given, apply it to each field which doesn't have a configuration of its own or from its return type. _Don't_ apply that configuration at schema level (it's applied to each otherwise uncovered field instead). ## 1.7.4 (16 Apr 2018) ### New Features - Support Mongoid::Criteria in authorization scoping ## 1.7.3 (12 Apr 2018) ### Bug Fix - Fix authorization code for when `ActiveRecord` is not defined ## 1.7.2 (10 Apr 2018) ### Bug Fix - Use a more permissive regexp (`/^\s*((?:[a-z._]+)\(.*\))\s*(asc|desc)?\s*$/im`) to parse SQL functions ## 1.7.1 (4 Apr 2018) ### Bug Fix - Fix route helpers to support class-based schemas ## 1.7.0 (25 Mar 2018) ### New Features - Support `1.8-pre` versions of GraphQL-Ruby ### Bug Fix - Fix OperationStore when other query instrumenters need `.query_string` ## 1.6.5 (7 Feb 2018) ### Bug Fix - Support `LEAST(...)` in stable cursors ## 1.6.4 (7 Feb 2018) ### Bug Fix - Support `CASE ... END` in stable cursors ## 1.6.3 (26 Jan 2018) ### Bug Fix - Support `FIELD(...)` in stable cursors ## 1.6.2 (13 Jan 2018) ### Bug Fix - Improve detection of `OperationStore` for the dashboard - Serve `Content-Type` and `Content-Length` headers with dashboard pages - Better `Dashboard#inspect` for Rails routes output - Use a string to apply order-by-primary-key for better Rails 3 support ## 1.6.1 (22 Nov 2017) ### New Features - Support `composite_primary_keys` gem ## 1.6.0 (13 Nov 2017) ### Breaking Changes - `GraphQL::Pro::UI` renamed to `GraphQL::Pro::Dashboard` ### Deprecations - Routing method `.ui` was renamed to `.dashboard` ### New Features - Added `GraphQL::Pro::Subscriptions` - Added subscriptions component to Dashboard ## 1.5.9 (10 Oct 2017) ### Bug Fix - Don't crash when scoping lists of abstract types with Pundit ## 1.5.8 (2 Oct 2017) ### New Features - Use `authorize(:pundit, namespace: )` to lookup policies in a namespace instead of the global namespace. ### Bug Fix - Introspection data is allowed through `fallback:` `authorize:` and `access:` filters. (It can be hidden with a `view:` filter.) ## 1.5.7 (20 Sept 2017) ### Bug Fix - Properly return `nil` when a list of authorized objects returns `nil` ## 1.5.6 (19 Sept 2017) ### New Features - Add `authorization(..., operation_store:)` option for authorizing operation store requests ## 1.5.5 (18 Sept 2017) ### New Features - Support `ConnectionType.bidrectional_pagination?` in stable RelationConnection ## 1.5.4 (18 Sept 2017) ### Bug Fix - Fix load issue when Rails is not present ## 1.5.3 (4 Sept 2017) ### Bug Fix - Fix OperationStore views on PostgresQL - Fix stable cursors when joined tables have the same column names __Note:__ This is implemented by adding extra fields to the `SELECT` clause with aliases like `cursor_#{idx}`, so you'll notice this in your SQL logs. ## 1.5.2 (4 Aug 2017) ### Bug Fix - Bump `graphql` dependency to `1.6` ## 1.5.1 (2 Aug 2017) ### New Features - Routing extensions moved to `using GraphQL::Pro::Routes` ### Deprecations - Deprecate `using GraphQL::Pro`, move extensions to `GraphQL::Pro::Routes` ## 1.5.0 (31 Jul 2017) ### New Features - Add `GraphQL::Pro::OperationStore` for persisted queries with Rails ## 1.4.8 (14 Jul 2017) ### Bug Fix - Update `authorization` to use type-level `resolve_type` hooks ## 1.4.7 (13 Jul 2017) ### Bug Fix - Update authorization instrumentation for `graphql >= 1.6.5` ## 1.4.6 (6 Jul 2017) ### Bug Fix - Fix typo in RelationConnection source ## 1.4.5 (6 Jul 2017) ### Bug Fix - Correctly fall back to offset-based cursors with `before:` argument ## 1.4.4 (15 Jun 2017) ### New Features - Add `Schema#unauthorized_object(obj, ctx)` hook for failed runtime checks ### Bug Fix - Prevent usage of `parent_role:` with `view:` or `access:` (since parent role requires a runtime check) - Fix versioned, encrypted cursors with 16-byte legacy cursors ## 1.4.3 (13 Jun 2017) ### New Features - `OrderedRelationConnection` supports ordering by joined fields ### Bug Fix - Update auth plugin for new Relay instrumenters - `Pro::Encoder` supports `encoder(...)` as documented ## 1.4.2 (2 May 2017) ### Bug Fix - Fix compatibility of `RelationConnection` and `RangeAdd` helper ## 1.4.1 (19 Apr 2017) ### New Features - Add `:datadog` monitoring ## 1.4.0 (19 Apr 2017) ### New Features - `ActiveRecord::Relation`s can be scoped by Pundit `Scope`s, CanCan `accessible_by`, or custom strategy's `#scope(gate, relation)` methods - Default authorization configuration can be provided with `authorization(..., fallback: { ... })` - Authorization's `:current_user` key can be customized with `authorization(..., current_user: ...)` ## 1.3.0 (7 Mar 2017) ### New Features - Serve static, persisted queries with `GraphQL::Pro::Repository` ## 1.2.3 (2 May 2017) ### Bug Fix - Fix compatibility of `RelationConnection` and `RangeAdd` helper ## 1.2.2 (6 Mar 2017) ### Bug Fix - Raise `GraphQL::Pro::RelationConnection::InvalidRelationError` when a grouped, unordered relation is returned from a field. (This relation can't be stably paginated.) ## 1.2.1 (3 Mar 2017) ### New Features - Formally support ActiveRecord `>= 4.1.0` ### Bug Fix - Support grouped relations in `GraphQL::Pro::RelationConnection` ## 1.2.0 (1 Mar 2017) ### New Features - Authorize fields based on their parent object, for example: ```ruby AccountType = GraphQL::ObjectType.define do name "Account" # This field is visible to all users: field :name, types.String # This is only visible when the current user is an `:owner` # of this account field :account_balance, types.Int, authorize: { parent_role: :owner } end ``` ## 1.1.1 (22 Feb 2017) ### Bug Fixes - Fix monitoring when `Query#selected_operation` is nil ## 1.1.0 (9 Feb 2017) ### New Features - Add AppSignal monitoring platform - Add type- and field-level opting in and opting out of monitoring - Add `monitor_scalars: false` to skip monitoring on scalars ### Bug Fixes - Fix `OrderedRelationConnection` when neither `first` nor `last` are provided (use `max_page_size` or don't limit) ## 1.0.4 (23 Jan 2017) ### Bug Fixes - `OrderedRelationConnection` exposes more metadata methods: `parent`, `field`, `arguments`, `max_page_size`, `first`, `after`, `last`, `before` ## 1.0.3 (23 Jan 2017) ### Bug Fixes - When an authorization check fails on a non-null field, propagate the null and add a response to the errors key (as if the field had returned null). It previously leaked the internal symbol `__graphql_pro_access_not_allowed__`. - Apply a custom Pundit policy even when the value isn't `nil`. (It previously fell back to `Pundit.policy`, skipping a `pundit_policy_name` configuration.) ## 1.0.2 ### Bug Fixes - `OrderedRelationConnection` exposes the underlying relation as `#nodes` (like `RelationConnection` does), supporting custom connection fields. ## 1.0.1 ### New Features - CanCan integration now supports a custom `Ability` class with the `ability_class:` option: ```ruby authorize :cancan, ability_class: CustomAbility ``` ## 1.0.0 - `GraphQL::Pro` released graphql-ruby-2.2.17/CHANGELOG-relay.md000066400000000000000000000066771476434635200172010ustar00rootroot00000000000000# graphql-relay ### Breaking Changes ### Deprecations ### New Features ### Bug Fix ## 0.12.0 (21 Jul 2016) ### Breaking Changes - Don't cache a global node identification config #51 To migrate, assign your node identification helper to the schema: ```ruby NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define { ... } MySchema.node_identification = NodeIdentification ``` ### New Features - Support lazy definition blocks from graphql-ruby 0.17 - Add `startCursor` and `endCursor` to `PageInfo` #60 ### Bug Fix - Support `field:` keyword for connection helper #58 ## 0.11.2 (6 Jul 2016) ### New Features - Include description for built-in objects #55 ## 0.11.1 (24 Jun 2016) ### Bug Fix - Correctly pass parent object to Connections #53 ## 0.11.0 (19 Jun 2016) ### Breaking Changes - `BaseType.define_connection` no longer caches the result to use as the default `BaseType.connection_type`. Now, store the result of `.define_connection` in a variable and pass that variable into the schema: ```ruby # Capture the returned type: SomethingCustomConnectionType = SomethingType.define_connection { ... } DifferentThingType = GraphQL::ObjectType.define do # And pass it to the connection helper: connection :somethings, SomethingCustomConnectionType end ``` ### New Features - Support for custom edge types / classes #50 - Support for multiple connection classes #50 ## 0.10.0 (31 May 2016) ### New Feature - Support `graphql` 0.14.0 #47 ### Bug Fix - Use strings as argument names, not symbols #47 ## 0.9.5 ### New Feature - Root `id` field may have a description #43 ## 0.9.4 (29 Apr 2016) ### Bug Fix - Fix Node interface to support GraphQL 0.13.0+ ## 0.9.2 (29 Apr 2016) ### Bug Fix - Fix Node interface when type_from_object returns nil ## 0.9.1 (6 Apr 2016) ### Bug Fix - Respond to connection fields without any pagination arguments - Limit by `max_page_size` even when no arguments are present ## 0.9.0 (30 Mar 2016) ### Breaking change - Remove the `order` argument from connection fields. This isn't part of the spec and shouldn't have been there in the first place! You can implement this behavior with a custom argument, for example: ```ruby field :cities, CityType.connection_type do argument :order, types.String, default_value: "name" resolve ->(obj, args, ctx) { obj.order(args[:order]) } end ``` ### Bug Fix - Include the MIT license in the project's source ## 0.8.1 (22 Mar 2016) ### Bug Fix - Accept description for Mutations ## 0.8.0 (20 Mar 2016) ### New Feature - Accept configs for `to_global_id` and `from_global_id` - Support `graphql` 0.12+ ## 0.7.1 (29 Feb 2016) ### Bug Fix - Limit the `count(*)` when testing next page with ActiveRecord #28 ## 0.7.0 (20 Feb 2016) ### New Feature - `max_page_size` option for connections - Support ActiveSupport 5.0.0.beta2 ## 0.6.2 (11 Feb 2016) ### Bug Fix - Correctly cast values from connection cursors #21 - Use class _name_ instead of class _object_ when finding a connection implementation (to support Rails autoloading) #16 ## 0.6.1 (14 Dec 2015) ### Bug Fix - Stringify `id` when passed into `to_global_id` ## 0.6.0 (11 Dec 2015) ### Breaking Change - `GlobalNodeIdentification#object_from_id(id, ctx)` now accepts context as the second argument #9 ## 0.5.1 (11 Dec 2015) ### Feature - Allow custom UUID join string #15 ### Bug Fix - Remove implicit ActiveSupport dependency #14 graphql-ruby-2.2.17/CHANGELOG.md000066400000000000000000004564261476434635200160700ustar00rootroot00000000000000# Changelog [Versioning guidelines](https://graphql-ruby.org/development.html#versioning) ### Breaking changes ### Deprecations ### New features ### Bug fixes # 2.2.17 (12 Mar 2025) - Security: fix CVE-2025-27407 # 2.2.16 (17 Jul 2024) - Fix path on errors raised in Dataloader Sources #5026 # 2.2.15 (20 May 2024) ### Bug fixes - Directives: correctly handle runtime directives in deeply nested fragments #4962 # 2.2.14 (18 Mar 2024) ### Bug fixes - Parser: properly handle stray hyphens in query strings #4879 # 2.2.13 (11 Mar 2024) ### Bug fixes - Tracing: when a new base `:default` trace class is added, merge already-configured trace modules into it #4875 # 2.2.12 (6 Mar 2024) ### Deprecations - `Schema.{query|mutation|subscription}_execution_strategy` methods are deprecated without replacement #4867 ### Breaking Changes - Connections: Revert changes to `hasNextPage` returning `false` when no `first` is given (previously changed in 2.2.6) #4866 ### Bug fixes - Complexity: handle unauthorized argument errors better #4868 - Pass `context` when fetching argument for `loads: ...` #4870 # 2.2.11 (27 Feb 2024) ### New features - Sentry: support transaction names in tracing #4853 ### Bug fixes - Tracing: handle unknown trace modes at runtime #4856 # 2.2.10 (20 Feb 2024) ### New features - Parser: support directives on variable definitions #4847 ### Bug fixes - Fix compatibility with Ruby 3.4 #4846 - Tracing: Fix applying default options to non-default modes #4849, #4850 # 2.2.9 (15 Feb 2024) ### New features - Complexity: Treat custom Connection fields as metadata (like `totalCount`), not as if they were evaluated for each item in the list #4842 - Subscriptions: Serialize `ActiveRecord::Relation`s given to `.trigger` #4840 ### Bug fixes - Complexity: apply configured `complexity ...` to connection fields #4841 - Authorization: properly handle Resolver arguments that return `false` for `#authorized?` #4839 # 2.2.8 (7 Feb 2024) ### New features - Responses have `"errors"` before `"data"`, as recommended by the GraphQL spec #4823 ### Bug fixes - Sentry: fix integration with other trace modules #4830 - Sentry: fix when child span is `nil` (test environments) #4828 - Remove needless Base64 backport #4820 - Fix module arrangement to support RDoc #4819 # 2.2.7 (29 Jan 2024) ### Deprecations - Deprecate returning `.resolve` dataloader requests (use `.load` instead) #4807 - Deprecate `error_bubbling(true)`, no replacement. Please open an issue if you need this option. #4813 ### Bug fixes - Remove unused `racc` dependency #4814 - Fix `backtrace: true` when used with `@defer` and batch-loaded lists #4815 - Accept input objects when required arguments aren't provided but have default values #4811 # 2.2.6 (25 Jan 2024) ### Deprecations - `instrument(:query | :multiplex, ...)` was deprecated, use a `trace_with` module instead. #4771 - Legacy `PlatformTracing` classes are deprecated, use a `PlatformTrace` module instead #4779 ### New features - `FieldUsage` analyzer: returns a `used_deprecated_enum_values: ...` array in its result Hash #4805 - `validate_timeout` applies to query analysis as well as static validation #4800 - `SentryTrace` is added for instrumenting with Sentry #4775 ### Bug fixes - `FieldUsage` analyzer: properly find deprecated arguments in non-null input objects #4805 - DataDog: replace usage of `span_type` setter with `span` setter #4776 - Fix coercion error handing with given `null` values #4799 - Raise a better error when variables are defined with non-input types #4791 - Fix `hasNextPage` when `max_page_size` is set #4780 # 2.2.5 (10 Jan 2024) ### Bug fixes - Parser: fix enum values named `type` #4772 - GraphQL::Deprecation: remove this unused helper module #4769 # 2.2.4 (3 Jan 2024) ### Bug fixes - AsyncDataloader: don't resolve fields with event loop #4757 - Parser: properly parse some fields and args named after keywords #4759 - Performance: use `all?` to check classes directly #4760 # 2.2.3 (28 Dec 2023) ### Bug fixes - AsyncDataloader: avoid leftover `suspended` Fibers #4754 - Generators: fix path and constant name of BaseResolver #4755 # 2.2.2 (27 Dec 2023) ### Bug fixes - Dataloader: remove `Fiber#transfer` support because Ruby's control flow is unpredictable (#4748, #4752, #4743) - Parser: fix handling of single-token document - QueryComplexity: improve performance # 2.2.1 (20 Dec 2023) ### Bug fixes - `AsyncDataloader`: re-raise errors from fields and sources #4736 - Parser: fix parsing directives on interfaces in SDL #4738 # 2.2.0 (18 Dec 2023) ### Breaking changes - `loads:` now requires a schema's `self.resolve_type` method to be implemented so that loaded objects can be verified to be of the expected type #4678 - Tracing: the new Ruby-based parser doesn't emit a "lex" event. (`graphql/c_parser` still does.) ### New features - `GraphQL::Dataloader::AsyncDataloader`: a Dataloader class that uses the `async` gem to run I/O from fields and Dataloader sources in parallel #4727 - Parser: use a heavily-optimized lexer and a hand-written parser for better performance #4718 - `run_graphql_field`: a helper method for running fields in tests #4732 # 2.1.10 (27 Dec 2023) - Dataloader: remove Fiber#transfer support because of unpredictable Ruby control flow #4753 # 2.1.9 (21 Dec 2023) ### Bug fixes - Dataloader: fix some fiber scheduling bugs #4744 # 2.1.8 (18 Dec 2023) ### New features - Rails generators: generate a base resolver class by default #4513 - Dataloader: add some support for transfer-based Fiber schedulers, simplify algorithm #4625 #4729 - `prepare`: check for the named method on the argument owner, too #4717 # 2.1.7 (4 Dec 2023) ### New features - Make `NullContext` inherit from `Context`, to make typechecking easier #4709 - Accept a custom `Schema.query_class` to use for executing queries #4679 ### Bug fixes - Default `reauthorize_scoped_objects` to false #4720 - Fix `subscriptions.trigger` with custom enum values #4713 - Fix `backtrace: true` with GraphQL-Pro `@defer` #4708 - Omit `to_h` from Input Object validation error message #4701 - When trimming whitespace from block strings, remove first and last lines that contain only whitespace #4704 # 2.1.6 (2 Nov 2023) ### Breaking Changes - The parser cache is now opt-in. Add `config.graphql.parser_cache = true` to your Rails environment setup to enable it. #4648 ### New features - New `ISO8601Duration` scalar #4688 ### Bug fixes - Trace: fix custom trace mode inheritance #4693 # 2.1.5 (25 Oct 2023) ### Bug fixes - Logger: Fix `Schema.default_logger` when Rails is present but doesn't have a logger #4686 # 2.1.4 (24 Oct 2023) ### New features - Add `Query#logger` #4674 - Lookahead: Add `with_alias:` option #2912 - Improve support for `load_application_object_failed` #4667 ### Bug fixes - Execution: Fix runtime loop in some cases with fragments #4684 - Fix `Connection#initialize` outside of execution #4675 - Fix ParseError in `Subscriptions#trigger` #4673 - Mongo: don't load all records in hasNextPage #4671 - Interfaces: fix `definition_methods` when interfaces implement other interfaces #4670 - Migrate `NullContext` to use the built-in Singleton module #4669 - Speed up type lookup #4664 - Fix `ScopeExtension#after_resolve` outside of execution #4685 - Speed up `one_of?` checks #4680 # 2.1.3 (12 Oct 2023) ### Bug fixes - Tracing: fix legacy tracers added to `GraphQL::Schema` #4663 - Add `racc` as a dependency because it's not included by default in Ruby 3.3 #4661 - Connections: don't add automatic connection behaviors for types named "Connection" #4668 # 2.1.2 (11 Oct 2023) ### New features - Depth: accept `count_introspection_fields: false` #4658 - Dataloader: add `get_fiber_variables` and `set_fiber_variables` #4593 - Trace: Add `Schema.default_trace_mode` #4642 ### Bug fixes - Fix merging results after calling directives #4639 #4660 - Visibility: don't reveal implementors of hidden abstract types #4589 - Bump required Ruby version to 2.7 since numbered block arguments are used #4659 - `hash_key:`: use the configured hash key when the underlying Hash has a default value Proc #4656 # 2.1.1 (2 Oct 2023) ### New features - Mutations: `HasSingleInput` provides Relay Classic-like `input: ...` argument behavior #4581 - Add `@specifiedBy` default directive #4633 - Analysis: support `visit?` hook to skip visit but still return a value - Add `context.scoped` for a long-lived reference to the current scoped context #4605 ### Bug fixes - Sanitized printer: Correctly print enum variable defaults #4652 - Schema printer: use `extend schema` when the schema has directives #4647 - Performance: pass runtime state through interpreter code #4621 - Performance: add `StaticVisitor` for faster AST visits #4645 - Performance: faster field lookup #4626 - Improve generator templates #4627 - Dataloader: clear cache between root mutation fields #4617 - Performance: Improve argument checks #4622 - Remove unused legacy connection code #4606 # 2.1.0 (30 Aug 2023) ### Breaking changes - Visitor: legacy-style proc-based visitors are no longer supported #4577 #4583 - Deprecated `GraphQL::Filter` is removed #4325 - Language::Printer has been re-written to append to a buffer; custom printers will need to be updated #4394 ### New features - Authorization: Items in a list can skip object-level `.authorized?` checks if the type is configured with `reauthorize_scoped_objects(false)` #3994 - Subscriptions: `unsubscribe(...)` accepts a value to be used to return a result along with unsubscribing #4283 - Language::Printer is much faster #4394 # 2.0.27 (30 Aug 2023) ### New features - Validators: Support `%{value}` in custom messages #4601 ### Bug fixes - Resolvers: Support `return false, nil` from `ready?` and `authorized?` #4585 - Enums: properly load directives from Schema IDL #4596 - Language: faster scanner #4576 - Language: support fields and arguments named `"null"` #4586 - Language: fix block string quote unescaping #4580 - Generator: use generated node type in Relay-related fields #4598 # 2.0.26 (8 Aug 2023) ### Bug fixes - Datadog Tracing: fix LocalJumpError #4579 # 2.0.25 (7 Aug 2023) ### New features - Tracing: add trace modes #4571 - Dataloader: add `Source#result_key_for` for customizing cache keys in sources #4569 ### Bug fixes - Tracing: Support multiple tracing platforms at once #4543 # 2.0.24 (27 Jun 2023) ### New features - `Schema::Object.wrap` can be used to customize how objects are (or aren't) wrapped by `GraphQL::Schema::Object` instances at runtime #4524 - `Query`: accept a `static_validator:` option in `#initialize` to use instead of the default validation configuration. ### Bug fixes - Performance: Reduce memory usage when adding types to a schema #4533 - Performance, `Dataloader`: when loading specific keys, only run dataloader until those specific keys are resolved #4519 # 2.0.23 (19 Jun 2023) ### New features - Printer: print extensions in SDL #4516 - Trace: accept trace instances during query execution #4497 - AlwaysVisible: Make a way to bypass type visibility #4442, #4491 ### Bug fixes - Tests: fix assertion for Ruby 3.3.0-dev #4515 - Performance: improve fragment possible type lookup #4506 - Docs: document Timeout can handle floats #4505 - Performance: use a dedicated object for field extension state #4401 - Backtrace: fix `backtrace: true` with other trace modules #4505 - Handle `context.warden` being nil #4503 - Dev: disable Minitest::Reporters for RubyMin #4494 - Trace: fix compatibility with inheritance #4487 - Context: fix NullContext compatibility with fetch, dig and key? #4483 # 2.0.22 (17 May 2023) ### New features - Warden: manually instantiating doesn't require a `filter` instance #4462 ### Bug fixes - Enum: fix procs for enum values #4474 - Lexer: force UTF-8 encoding #4467 - Trace: inherit superclass `trace_options` #4470 - Dataloader: properly run mutations in sequence #4461 - NotificationsTrace: Add `execute_multiplex.graphql` event #4460 - Fix `Context#dig` when called with one key #4458 - Performance: Use a plain hash for selection sets at runtime #4453 - Performance: Memoize current trace #4450, #4452 - Performance: Pass is_non_null to runtime check #4449 - Performance: Use `compare_by_identity` on some runtime caches - Properly support nested queries (fix `Thread.current` clash) #4445 # 2.0.21 (11 April 2023) ### Deprecations - Deprecate `GraphQL::Filter` (use `visible?` methods instead) #4424 ### New features - PrometheusTracing: support histograms #4418 ### Bug fixes - Backtrace: improve compatibility with `trace_with` #4437 - Consolidate internally-used empty value constants #4434 - Fix some warnings #4422 - Performance: improve runtime speed #4436 #4433 #4428 #4430 #4427 #4399 - Validation: fix inline fragment selection on scalar #4429 - `@oneOf`: print definition in the SDL when it's used - SDL: load schema directives when they're used - Appsignal tracing: Fix `resolve_type` definition # 2.0.20 (30 March 2023) ### Bug fixes - `.resolve_type`: fix returning `[Type, false]` from resolve_type #4412 - Parsing: improve usage of `GraphQL.default_parser` #4411 - AppsignalTrace: implement missing methods #4390 - Runtime: Fix `current_depth` method in some lazy lists #4386 - Performance: improve `Object` object shape #4365 - Tracing: return execution errors raised from field resolution to `execute_field` hooks #4398 # 2.0.19 (14 March 2023) ### Bug fixes - Scoped context: fix `context.scoped_context.current_path` #4376 - Tracing: fix `tracer` inheritance in Schema classes #4379 - Timeout: fix `Timeout` plugin when other tracers are used #4383 - Performance: use Arrays instead of `GraphQL::Language::Token`s when scanning #4366 # 2.0.18 (9 March 2023) ### Breaking Changes - Tracing: `"execute_field"` events on fields defined on interface types will now receive the _interface_ type as `data[:owner]` instead of the current object type. To get the old behavior, use `data[:object].class` instead. #4292 ### New features - Add `TypeKind#leaf?` #4352 ### Bug fixes - Tracing: use the interface type as `data[:owner]` instead of the object type #4292 - Performance: improve Shape compatibility of `GraphQL::Schema::Field` #4360 - Performance: improve Shape compatibility of `GraphQL::Schema::Warden` #4361 - Performance: rewrite the token scanner in plain Ruby #4369 - Performance: make `deprecation_reason` faster #4356 - Performance: improve lazy value resolution in execution #4333 - Performance: create `current_path` only when the application needs it #4342 - Performance: add `GraphQL::Tracing::Trace` as a lower-overhead tracing API #4344 - Connections: fix `hasNextPage` for already-loaded ActiveRecord Relations #4349 # 2.0.17.2 (29 March 2023) ### Bug fixes - Unions and Interfaces: support returning `[type_module, false]` from `resolve_type` #4413 # 2.0.17.1 (27 March 2023) ### Bug fixes - Tracing: restore behavior returning execution errors raised during field resolution #4402 # 2.0.17 (14 February 2023) ### Breaking changes - Enums: require at least one value in a definition #4278 ### New features - Enums: support `nil` as a Ruby value #4311 ### Bug fixes - Don't re-encode ASCII strings as UTF-8 #4319, #4343 - Fix `handle_or_reraise` with arguments validation #4341 - Performance: Remove error handling from `Lazy#value` (unused) #4335 - Performance: Use codegen instead of dynamic dispatch in `Language::Visitor` and `Analysis::AST::Visitor` #4338 - Performance: reduce indirection in `#introspection?` and `#graphql_name` #4327 - Clean up thread-based state after running queries #4329 - JSON types: don't pass raw NullValue AST nodes to `coerce_input` #4324, #4320 - Performance: reduce `.is_a?` calls at runtime #4318 - Performance: cache interface type memberships #4311 - Performance: eagerly define some type instance variables for Shape friendliness #4300 #4295 #4297 - Performance: reduce argument overhead, don't scope introspection by default, reduce duplicate call to Field#type #4317 - Fix anonymous `eval` usage #4288 - Authorization: fix field auth fail call after lazy #4289 - Subscriptions: fix `loads:`/`as:` # 2.0.16 (19 December 2022) ### Breaking changes - `Union`: Only accept Object types in `possible_types` (previously, other types were also accepted, but this was against the spec) #4269 ### New features - Rake: support introspection query options in the `RakeTask` #4247 - Subscriptions: Merge `.trigger(... context: { ... })` into the query context when running updates #4242 ### Bug fixes - Make BaseEdge and subclasses return true for `.default_relay?` #4272 - Validation: return a proper error for duplicate-named fragments when used indirectly #4268 - Don't re-apply `scope_items` to `nodes { ... }` or `edges { ... }` arrays #4263 - Fix `Concurrent::Map` initialization to prevent race conditions - Speed up scoped context lookup #4245 - Support overriding built-in context keys #4239 - Context: properly `dig` into `:current_arguments` #4249 # 2.0.15 (22 October 2022) ### New features - SDL: support extensions on the schema itself #4203 - SDL: recognize `.graphqls` files in `.from_definition` #4204 - Schema: add a reader method of `TypeMembership#options` #4209 ### Bug fixes - Node Behaviors: call the id-from-object hook with the type definition, not the type instance #4233 - RelayClassicMutation: add a period to the generated description of the payload type #4229 - Dataloader: make scoped context work with Dataloader #4220 - SDL: fix parsing repeatable directives #4218 - Lookahead: reduce more allocations in `.selects?` #4212 - Introspection Query: strip blank lines from generated query strings #4208 - Enums: Add error handling to result coercion #4206 - Lookahead: add `selected_type:` to `.selects?` #4194 - Lookahead: fix `.selects?` on unions #4193 - Fields: use field-local `connection:` config over resolver config #4191 # 2.0.14 (8 September 2022) ### New features - Input Objects: support `one_of` for input objects that allow exactly one argument #4184 - Dataloader: add `source.merge({ ... })` for adding objects to dataloader source caches #4186 - Validation: generate new schemas with a suggested `validate_max_errors` of 100 #4179 ### Bug fixes - Lookahead: improve performance when field names are given as symbols #4189 - Runtime: simplify some internal code #4183 - Datadog tracing: remove deprecated options #4159 # 2.0.13 (12 August 2022) ### New features - Fields: add configuration methods for `default_value` and `prepare` #4156 - Static validation: merge directive errors when they're on the same location or directive ### Bug fixes - Subscriptions: properly use the given `.trigger(... context: )` for determining subscription root field visibility #4160 - Fix fields that use `hash_key:` and have a falsy value for that key #4132 - Variable validation: respect `validate_max_error` limit - Performance: use `Array#+` to add objects during execution #4142 # 2.0.12 (19 July 2022) ### New features - Support returning `[Type, nil]` from `resolve_type` #4130 ### Bug fixes - SDL: Don't print empty braces for input objects with no arguments #4138 - Arguments: always call `prepare` before loading objects based on ID (`loads:`) #4128 - Don't support re-assigning `Query#validate=` after validation has run #4127 # 2.0.11 (20 June 2022) ### New features - Support full unicode range #4090 ### Bug fixes - Subscriptions: support overriding subscriptions in subclasses #4108 - Schema: support types with duplicate names and cyclical references #4107 - Connections: don't exceed application-applied `LIMIT` with `max_page_size` #4104 - Field: add `Field#relay_nodes_field` config reader #4103 - Remove partial `opentelementry` implementation, oops #4086 - Remove unused method `Lazy.resolve` # 2.0.10 (20 June 2022) Oops, this version was accidentally released to RubyGems as "2.10.0". I yanked it. See 2.0.11 instead. # 2.0.9 (31 May 2022) ### New features - Connections: use `Schema.default_page_size`, `Field#default_page_size`, or `Resolver.default_page_size` when one of them is available and no `first` or `last` is given #4081 - Tracing: Add `OpenTelementryTracing` #4077 ### Bug fixes - Field usage analyzer: don't crash on null input objects #4078 - Complexity: properly handle `ExecutionError`s raised in `prepare:` hooks #4079 # 2.0.8 (24 May 2022) ### New Features - Fields: return `fallback_value:` when method or hash key field resolution fails #4069 - Support `hash_key:` lookups on Hash-like objects #4072 - Datadog tracing: support `prepare_span` hook for adding custom tags #4067 ### Bug fixes - Fields: When `hash_key:` is given, populate `#method_str` based on it #4072 - Errors: rescue errors raised when calling `.each` on list values #4052 - Date type: continue accepting dates without hyphens #4061 - Parser: properly parse empty type definitions #4046 # 2.0.7 (25 April 2022) ### New Features - Subscriptions: support `validate_update: false` to disable validation when running subscription updates #4039 - Expose duplicated name on `DuplicateNamesError` #4022 ### Bug Fixes - Datadog: improve tracer #4038 - `hash_key:` try stringified hash key when resolving fields (this restores previous behavior) #4043 - Printer: Don't print empty field set when types have no fields (`{\n}`) #4042 - Dataloader: improve handoff between lazy resolution and dataloader resolution #4036 - Remove unused `Lazy::Resolve` module from legacy execution code #4035 # 2.0.6 (14 April 2022) ### Bug fixes - Dataloader: make multiplexes use custom dataloaders #4026 - ISO8601Date: properly accept `nil` as input #4025 - Mutation: fix error message when `ready?` returns an invalid result #4029 - ISO8601 scalars: add `specified_by_url` configs #4014 - Array connection: don't return all items when `before` is the first cursor #4012 - Introspection: fix typo `specifiedByUrl` -> `specifiedByURL` - Fields: fix `hash_key` to take priority over method lookup #4015 # 2.0.5 (28 March 2022) ### Bug Fixes - Resolvers: fix inheriting arguments when parent classes aren't hooked up directly to the schema #4006 # 2.0.4 (21 March 2022) ### Bug fixes - Fields: make sure `null:` config overrides a default from a resolver #4000 # 2.0.3 (21 March 2022) ### Bug fixes - Fields: make sure field configs override resolver defaults #3975 - Fix `Field#scoped?` when the field uses a resolver #3990 - Allow schema members to have multiple of `repeatable` directives #3986 - Remove some legacy code #3979 #9995 - SDL: fix indirect interface implementation when loading a schema #3982 - Datadog tracing: Support ddtrace 1.0 #3978 - Fix `Node` implementation when connection types include built-in behavior modules #3967 - Small stack trace size reduction #3957 # 2.0.2 (1 March 2022) ### New features - Reduce schema memory footprint #3959 ### Bug fixes - Mutation: Correctly use a configured `type(...)` #3965 - Interfaces: De-duplicate indirectly implemented interfaces #3932 - Remove an unnecessary require #3961 # 2.0.1 (21 February 2022) ### Breaking changes - Resolvers: refactored so that, instead of _copying_ configurations to `field ...` instances, `GraphQL::Schema::Field`s reference their provided `resolver: ...`, `mutation: ...`, or `subscription: ...` classes for many properties. This _shouldn't_ break anything -- all of graphql-ruby's own tests passed just fine -- but it's mentioned here in case you notice anything out-of-sorts in your own application #3916 - Remove deprecated field options `field:`, `function:`, and `resolve:` (these were already no-ops, but they were overlooked in 2.0.0) #3917 ### Bug fixes - Scoped context: fix usage with dataloader #3950 - Subscriptions: support multiple definitions for subscription root fields with `.trigger` #3897 #3935 - Improve some error messages #3920 #3923 - Clean up scalar validation code #3982 # 2.0.0 (9 February 2022) ### Breaking Changes - __None, ideally.__ If you have an application that ran without warnings on v1.13, you should be able to update to 2.0.0 without a hitch. If this isn't the case, please [open an issue](https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md&title=[2.0%20update]%20describe%20your%20problem) and let me know what happened! I plan to maintain 1.13 for a while in order to ensure a smooth transition. - But, many legacy code components were removed, so if there are any more references to those, there will be name errors! See #3729 for a list of removed components. # 1.13.19 (2 February 2023) ### Bug fixes - Performance: don't re-encode schema member names #4323 - Performance: fix a duplicate field.type call #4316 - Performance: use `scope: false` for introspection types #4315 - Performance: improve argument coercion and validation #4312 - Performance: improve interface type membership lookup #4309 # 1.13.18 (10 January 2023) ### New Features - `hash_key:`: perform `[...]` lookups even when the underlying object isn't a Hash #4286 # 1.13.17 (17 November 2022) ### Bug fixes - Handle ExecutionErrors from prepare hooks when calculating complexity #4248 # 1.13.16 (31 August 2022) ### New Features - Make variable validation respect `validate_max_errors` #4178 # 1.13.15 (30 June 2022) ### Bug fixes - Remove partial OpenTelementry tracing #4086 - Properly use `Query#validate` to skip static validation #3881 # 1.13.14 (20 June 2022) ### New Features - Add `Field#relay_nodes_field` reader #4103 - Datadog: detect tracing module #4100 # 1.13.13 (31 May 2022) ### New features - Datadog: update tracer for ddtrace 1.0 #4038 - Datadog: Add `#prepare_span` hook for custom tags #4067 - Tracing: Add `OpenTelementry` tracing #4077 # 1.13.12 (14 April 2022) - Pass `context[:dataloader]` to multiplex context #4026 - Add a deprecation warning to `.accepts_definitions` #4002 # 1.13.11 (21 March 2022) ### Deprecations - `RangeAdd` warns when `context:` isn't provided (it's required in GraphQL-Ruby 2.0) #3996 # 1.13.10 ### Breaking changes - `id` fields: #3914 Previously, when a field was created with `global_id_field`, it would pass a _legacy-style_ type definition (an instance of `GraphQL::ObjectType`) to `Schema.id_from_object(...)`. Now, it passes a class-based definition instead. If your `id_from_object(...)` method was using any methods from those legacy definitions, they should be migrated. (Most notably, uses of `type.name` should be migrated to `type.graphql_name`.) ### Deprecations - Connections: deprecation warnings were added to configuration methods `.bidirectional_pagination = ...` and `.default_nodes_field = ...`. These two configurations don't apply to the new pagination implementation, so they can be removed. #3918 # 1.13.9 (9 February 2022) ### Breaking changes - Authorization: #3903 In graphql-ruby v1.12.17-1.13.8, when input objects used `prepare: -> { ... }` , the returned values were not authorized at all. However, this release goes back to the behavior from 1.12.16 and before, where a returned `Hash` is validated just like an input object that didn't have a `prepare:` hook. To get the previous behavior, you can implement `def self.authorized?` in the input object you want to skip authorization in: ```ruby class Types::BaseInputObject < GraphQL::Schema::InputObject def self.authorized?(obj, value, ctx) if value.is_a?(self) super else true # graphql-ruby skipped auth in this case for v1.12.17-v1.13.8 end end end ``` ### Bug fixes - Support re-setting `query.validate = ...` after a query is initialized #3881 - Handle validation errors in connection complexity calculations #3906 - Input Objects: try to authorize values when `prepare:` returns a Hash (this was default < v1.12.16) #3903 - SDL: fix when a type has two directives # 1.13.8 (1 February 2022) ### Bug fixes - Introspection query: hide newly-supported fields behind arguments, maintain backwards-compatible INTROSPECTION_QUERY #3877 # 1.13.7 (28 January 2022) ### New Features - Arguments: `replace_null_with_default: true` replaces incoming `null`s with the configured `default_value:` #3871 - Arguments: support `dig: [key1, key2, ...]` for nested hash key access #3856 - Generators: support more Postgresql field types #3577 - Generators: support downcased generator argument types #3577 - Generators: add an input type generator #3577 - Generators: support namespaces in generators #3577 ### Bug Fixes - Field: better error for nil `owner` #3870 - ISO8601DateTime: don't accept inputs with partial time parts #3862 - SDL: fix for base connection classes that implement interfaces #3859 - Cops: find `required: true` on `f.argument` calls (with explicit receiver) #3858 - Analysis: handle undefined or hidden fields with `nil` in `visitor.field_definition` #3857 # 1.13.6 (20 January 2022) ### New features - Introspection: support `__Schema.description`, `__Directive.isRepeatable`, `__Type.specifiedByUrl`, and `__DirectiveLocation.VARIABLE_DEFINITION` #3854 - Directives: Call `Directive.resolve_each` for list items #3853 - Dataloader: Run each list item in its own fiber (to support batching across list items) #3841 ### Bug fixes - RelationConnection: Preserve `OFFSET` when it's already set on the relation #3846 - `Types::ISO8601Date`: Accept default values as Ruby date objects #3563 # 1.13.5 (13 January 2022) ### New features - Directives: support `repeatable` directives #3837 - Tracing: use `context[:fallback_transaction_name]` when operations aren't named #3778 ### Bug fixes - Performance: improve performance of queries with directives #3835 - Fix crash on undefined constant `NodeField` #3832 - Fix crash on partially-required `ActiveSupport` #3829 # 1.13.4 (7 January 2022) ### Bug fixes - Connections: Fix regression in 1.13.3 on unbounded Relation connections #3822 # 1.13.3 (6 January 2022) ### Deprecations - `GraphQL::Relay::NodeField` and `GraphQL::Relay::NodesField` are deprecated; use `GraphQL::Types::Relay::HasNodesField` or `GraphQL::Types::Relay::HasNodeField` instead. (The underlying field instances require a reference to their owner type, but `NodeField` and `NodesField` can't do that, since they're shared instances) #3791 ### New features - Arguments: support `required: :nullable` to make an argument required to be _present_, even if it's `null` #3784 - Connections: When paginating an AR::Relation, use already-loaded results if possible #3790 - Tracing: Support DRY::Notifications #3776 - Improve the error when a Ruby method doesn't support the defined GraphQL arguments #3785 - Input Objects: call `.authorized?` on them at runtime #3786 - Field extensions: add `extras(...)` for extension-related extras with automatic cleanup #3787 ### Bug fixes - Validation: accept nullable variable types for arguments with default values #3819 - Validation: raise a better error when a schema receives a `query { ... }` but has no query root #3815 - Improve the error message when `Schema.get_field` can't make sense of the arguments #3815 - Subscriptions: losslessly serialize Rails 7 TimeWithZone #3774 - Field Usage analyzer: handle errors from `prepare:` hooks #3794 - Schema from definition: fix default values with camelized arguments #3780 # 1.13.2 (15 December 2021) ### Bug fixes - Authorization: only authorize arguments _once_, after they've been loaded with `loads:` #3782 - Execution: always provide an `Interpreter::Arguments` instance as `context[:current_arguments]` #3783 # 1.13.1 (13 December 2021) ### Deprecations - `.to_graphql` and `.graphql_definition` are deprecated and will be removed in GraphQL-Ruby 2.0. All features using those legacy definitions are already removed and all behaviors should have been ported to class-based definitions. So, you should be able to remove those calls entirely. Please open an issue if you have trouble with it! #3750 #3765 ### New features - `context.response_extensions[...] = ...` adds key-value pairs to the `"extensions" => {...}` hash in the final response #3770 - Connections: `node_type` and `edge_type` accept `field_options:` to pass custom options to generated fields #3756 - Field extensions: Support `default_argument ...` configuration for adding arguments if the field doesn't already have them #3751 ### Bug fixes - fix `rails destroy graphql:install` #3739 - ActionCable subscriptions: close channel when unsubscribing from server #3737 - Mutations: call `.authorized?` on arguments from `input_object_class`, `input_type`, too #3738 - Prevent blank strings with `validates: { length: ... }, allow_blank: false` #3747 - Lexer: return mutable strings when strings are empty #3741 - Errors: don't send execution errors to schema-defined handlers from inside lazies #3749 - Complexity: don't multiple `edges` and `nodes` fields by page size #3758 - Performance: fix validation performance degradation from 1.12.20 #3762 # 1.13.0 (24 November 2021) Since this version, GraphQL-Ruby is tested on Ruby 2.4+ and Rails 4+ only. ### Breaking changes - ActionCable Subscriptions: No update is delivered if all subscriptions return `NO_UPDATE` #3713 - Subscription classes: If a subscription has a `scope ...` configuration, then a `scope:` option is required in `.trigger(...)`. Use `scope ..., optional: true` to get the old behavior. #3692 - Arguments whose default values are used aren't checked for authorization #3665 - Complexity: Connection fields have a default complexity implementation based on `first`/`last`/`max_page_size` #3609 - Arguments: if arguments are configured to return `false` for `.visible?(context)`, their default values won't be applied ### New features - Visibility: A schema may contain multiple members with the same name. For each name, GraphQL-Ruby will use the one that returns true for `.visible?(context)` for each query (and raise an error if multiple objects with the same name are visible). #3651 #3716 #3725 - Dataloader: `nonblocking: true` will make GraphQL::Dataloader use `Fiber.scheduler` to run fields and load data with sources, supporting non-blocking IO. #3482 - `null: true` and `required: true` are now default. GraphQL-Ruby includes some RuboCop cops, `GraphQL/DefaultNullTrue` and `GraphQL/DefaultRequiredTrue`, which identify and remove those needless configurations. #3612 - Interfaces may `implement ...` other interfaces #3613 ### Bug fixes - Enum `value(...)` and Input Object `argument(...)` methods return the defined object #3727 - When a field returns an array of mixed errors and values, the result will contain `nil` where there were errors in the list #3656 # 1.12.24 (4 February 2022) ### Bug fixes - SDL: fix parsing schemas where types have multiple directives #3886 # 1.12.23 (20 December 2021) ### Bug fixes - FieldUsage analyzer: handle arguments that raise an error during `prepare:` #3795 # 1.12.22 (8 December 2021) ### Bug fixes - Static validation: fix regression and improve performance of fields_will_merge validation #3761 # 1.12.21 (23 November 2021) ### Bug fixes - Validators: Fix `format:`/`allow_blank: true` to correctly accept a blank string #3726 - Generators: generate a correct `Schema.type_error` hook #3722 # 1.12.20 (17 November 2021) ### New Features - Static validation: improve error messages when fields won't merge #3698 - Generators: improve id_from_object and type_error suggested implementations #3710 - Connections: make the new connections module fall back to old connections #3704 ### Bug fixes - Dataloader: re-enqueue sources when one call to `yield` didn't satisfy their pending requests #3707 - Subscriptions: Fix when JSON-typed arguments are used #3705 # 1.12.19 (5 November 2021) ### New Features - Argument validation: Make `allow_null` and `allow_blank` work standalone #3671 - Add field and path info to Encoding errors #3697 - Add `Resolver#unauthorized_object` for handling loaded but unauthorized objects #3689 ### Bug fixes - Properly hook up `Schema.validate_max_errors` at runtime #3691 # 1.12.18 (2 November 2021) ### New features - Subscriptions: Add `NO_UPDATE` constant for skipping subscription updates #3664 - Validation: Add `Schema.validate_max_errors(integer)` for halting validation when it reaches a certain number #3683 - Call `self.load_...` methods on Input objects for loading arguments #3682 - Use `import_methods` in Refinements when available #3674 - `AppsignalTracing`: Add `set_action_name` #3659 ### Bug fixes - Authorize objects returned from custom `def load_...` methods #3682 - Fix `context[:current_field]` when argument `prepare:` hooks raise an error #3666 - Raise a helpful error when a Resolver doesn't have a configured `type(...)` #3679 - Better error message when subscription clients are using ActionCable #3668 - Dataloader: Fix dataloading of input object arguments #3666 - Subscriptions: Fix parsing time zones #3667 - Subscriptions: Fix parsing with non-null arguments #3620 - Authorization: Call `schema.unauthorized_field` for unauthorized resolvers - Fix when literal `null` is used as a value for a list argument #3660 # 1.12.17 (15 October 2021) ### New features - Support `extras: [:parent]` #3645 - Support ranges in `NumericalityValidator` #3635 - Add some Dataloader methods for testing #3335 ### Bug fixes - Support input object arguments called `context` #3654 - Support single-item default values for list arguments #3652 - Ensure query strings are strings before running a query #3628 - Fix empty hash kwargs for Ruby 3 #3610 - Fix wrongly detecting Ipnut objects in authorization #3606 # 1.12.16 (31 August 2021) ### New features - Connections: automatically support Mongoid 7.3 #3599 - Support `def self.topic_for` in Subscription classes for server-filtered streams #3597 - When a list item or object field has an invalid null, stop executing that list or ### Bug fixes - Perf: don't refine String when unnecessary #3593 - BigInt: always parse as base 10 #3586 - Errors: only return one error when a node in a non-null connection has an invalid null #3601 # 1.12.15 (23 August 2021) ### New Features - Subscriptions: add support for multi-tenant setups when deserializing context #3574 - Analyzers: also track deprecated arguments #3549 # 1.12.14 (22 July 2021) ### Bug fixes - SDL: support directive arguments referencing overridden built-in scalars #3564 - Use `"_"` as the name for `field :_, ...` fields #3560 - Support `sanitized_printer(...)` in the schema definition for `Query#sanitized_query_string` - `GraphQL::Backtrace`: fix multiplex support # 1.12.13 (20 June 2021) ### Breaking changes - Add a trailing newline to the `Schema.to_definition` output string #3541 ### Bug fixes - Properly handled list results in GraphQL::Backtrace #3540 - Return plain `Hash`es and `Array`s from queries instead of customized subclasses #3533 - Fix errors raised from non-null fields #3537 - Resolver: don't pass frozen array of extensions when none were configured #3515 - Configure the right `owner` for `node` and `nodes` fields #3509 - Improve error message for invalid enum value #3507 - Properly halt on lazily-returned `context.skip`s #3514 - Fix: call overridden `to_h` methods on InputObject classes #3539 - Halt execution when a runtime directive argument raises a `GraphQL::ExecutionError` #3542 # 1.12.12 (31 May 2021) ### Bug fixes - Directives on inline fragments and fragment spreads receive `.resolve(...)` calls #3499 # 1.12.11 (28 May 2021) ### Bug fixes - Validate argument default values when adding them to the schema #3496 - Resolvers inherit extensions from superclasses #3500 - Greatly reduce runtime overhead #3494, #3505 - Remove hidden directives from introspection #3488 # 1.12.10 (18 May 2021) ### New features - Use `GlobalID::Locator.locate_many` for arrays of global Ids #3481 - Support runtime directives (call `.resolve`) on `QUERY` #3474 ### Bug fixes - Don't override Resolver `#load_*` methods when they're inherited #3486 - Fix validation of runtime directive arguments that have input objects #3485 - Add a final newline to rake task output - Don't add connection arguments to fields loaded from introspection responses #3470 - Fix `rescue_from` on loading arguments #3471 # 1.12.9 (7 May 2021) ### New features - Overriding `.authorized_new(...)` to call `.new(...)` effectively skips object authorization #3446 - Dataloader copies Fiber-local values from `Thread.current[...]` when initializing new Fibers #3461 ### Bug fixes - Fix introspection of default value input objects #3456 - Add `StandardError => ...` condition to the generated GraphqlController #3460 - Fix `Dataloader::Source` on Ruby 3 with keyword arguments - Respect directive visibility at runtime #3450 - ActionCable subscriptions: only deserialize the broadcast payload once #3443 - Don't re-add `graphiql-rails` when `generate graphql:install` is run twice #3441 - Allow differing selections on mutually exclusive interfaces #3063 - Respect `max_page_size: nil` override in fields #3438 # 1.12.8 (12 Apr 2021) ### Bug fixes - Fix loading single-key hashes in Subscriptions #3428 - Fix looking up `rescue_from` handlers inherited from parent schema classes #3431 # 1.12.7 (7 Apr 2021) ### Breaking changes - `Execution::Errors` (which implements `rescue_from`) was refactored so that, when an error matches more than one registered handler, it picks the _most specific_ handler instead of the _first match_ in the underlying Hash. This might "break" your code if your application registered a handler for a parent class and a child class, but expects instances of the child class to be handled by the handler for the parent class. (This seems very unlikely -- I consider the change to be a "breaking fix.") #3404 ### New features - Errors: pick the most specific error handlers (instead of an order-dependent selection) #3404 - Add `node_nullable(...)` connection configuration options #3389 - Add `has_nodes_field(true|false)` connection configuration option #3388 - Store more metadata in argument-related static validation errors #3406 ### Bug fixes - Fix connection nullability settings to properly handle `false` #3386 - Fix returning `RawValue`s as part of a list #3403 - Fix introspection for deprecated directive arguments #3416 - Optimize `has_next_page` for ActiveRecord::Relation connections #3414 - Tracing: consistent event sequencing when queries are executed with `Query#result` #3408 # 1.12.6 (11 March 2021) ### Breaking changes - Static validation: previously, variables passed as arguments to input objects were not properly type-checked. #3370 fixed type checking in this case, but may case existing (invalid) queries to break. ### New features - Connection types: support `edges_nullable(false)` and `edge_nullable(false)` for non-null fields #3376 - Connections: add `.arguments` reader to new `Pagination::Connection` classes #3360 ### Bug fixes - Relation connection: Remove extra `COUNT` query from some scenarios #3373 - Add a Bootsnap-style parsing cache when Bootsnap is detected #3156 - Fix input validation for input object variables #3370 # 1.12.5 (18 February 2021) ### New features - Resolvers: support `max_page_size` config #3338 - RangeAdd: call `range_add_edge` (if supported) to improve stable connection support #3341 ### Bug fixes - Backtrace: fix new tracer when analyzing multiplex without executing it #3342 - Dataloader: pass along `throw`s #3333 - Skip possible_types filtering for non-interface types #3336 - Improve debugging message for ListResultFailedError #3339 # 1.12.4 (8 February 2021) ### Bug fixes - Allow prepended modules to add fields #3325 - Fix ConnectionExtension when another extension short-circuits `resolve` #3326 - Backtrace: Fix GraphQL::Backtrace with static validation (used by graphql-client) #3324 - Dataloader: Fix yield from root fiber when accessing arguments from analyzers. Fix arguments sometimes containing unresolved `Execution::Lazy`s #3320 - Dataloader: properly pass raised errors to `handle_error` handlers #3319 - Fix NameError in validation error #3303 - Dataloader: properly batch when parent fields were not batched #3312 # 1.12.3 (27 January 2021) ### Bug fixes - Fix constant names for legacy scalar types # 1.12.2 (26 January 2021) ### New features - `GraphQL::Deprecation.warn` is used for GraphQL-Ruby 2.0 deprecation warnings (and calls through to `ActiveSupport::Deprecation.warn` if it's available) #3292 # 1.12.1 (25 January 2021) ### Bug fixes - `GraphQL::Dataloader`: properly support selections with multiple fields #3297 # 1.12.0 (20 January 2021) ### Breaking changes - `GraphQL::Schema` defaults to `GraphQL::Execution::Interpreter`, `GraphQL::Analysis::AST`, `GraphQL::Pagination::Connections`, and `GraphQL::Execution::Errors`. (#3145) To get the previous (deprecated) behaviors: ```ruby # Revert to deprecated execution behaviors: use GraphQL::Execution::Execute use GraphQL::Analysis # Disable the new connection implementation: self.connections = nil ``` - `GraphQL::Execution::Interpreter::Arguments` instances are frozen (#3138). (Usually, GraphQL code doesn't interact with these objects, but they're used some places under the hood.) ### Deprecations - Many, many legacy classes and methods were deprecated. #3275 Deprecation errors include links to migration documentation. For a full list, see: https://github.com/rmosolgo/graphql-ruby/issues/3056 ### New features - Rails-like argument validations (#3207) - Fiber-based `GraphQL::Dataloader` for batch-loading data #3264 - Connection and edge behaviors are available as mixins #3071 - Schema definition supports schema directives #3224 ### Bug fixes # 1.11.10 (5 Nov 2021) ### Bug fixes - Properly hook up `Schema.max_validation_errors` at query runtime #3690 # 1.11.9 (1 Nov 2021) ### New Features - `Schema.max_validation_errors(val)` limits the number of errors that can be added during static validation #3675 # 1.11.8 (12 Feb 2021) ### Bug fixes - Improve performance of `Schema.possible_types(t)` for object types #3172 # 1.11.7 (18 January 2021) ### Breaking changes - Incoming integer values are properly bound (as per the spec) #3206 To continue receiving out-of-bound integer values, add this to your schema's `def self.type_error(err, ctx)` hook: ```ruby def self.type_error(err, ctx) if err.is_a?(GraphQL::IntegerDecodingError) return err.integer_value # return it anyways, since this is how graphql-ruby used to work end # ... end ``` ### New features - Support Ruby 3.0 #3278 - Add validation timeout option #3234 - Support Prometheus custom_labels in GraphQLCollector #3215 ### Bug fixes - Handle `GraphQL::UnauthorizedError` in interpreter in from arguments #3276 - Set description for auto-generated `input:` argument #3141 - Improve performance of fields will merge validation #3228 - Use `Float` graphql type for ActiveRecord decimal columns #3246 - Add some custom methods to ArrayConnection #3238 - Fix generated fields for types ending Connection #3223 - Improve runtime performance #3217 - Improve argument handling when extensions shortcut the defined resolve #3212 - Bind scalar ints as per the spec #3206 - Validate that input object names are unique #3205 ## 1.11.6 (29 October 2020) ### Breaking changes FieldExtension: pass extended values instead of originals to `after_resolve` #3168 ### Deprecations ### New features - Accept additional options in `global_id_field` macro #3196 ### Bug fixes - Use `graphql_name` in `UnauthorizedError` default message (fixes #3174) #3176 - Improve error handling for base 64 decoding (in `UniqueWithinType`) #3179 - Fix `.valid_isolated_input?` on parsed schemas (fixes #3181) #3182 - Fix fields nullability in subscriptions documentation #3194 - Update `RangeAdd` to use new connections when available #3195 ## 1.11.5 (30 September 2020) ### New features - SanitizedPrinter: accept `inline_variables: false` option and add `#redact_argument_value?` and `#redacted_argument_value` hooks #3167 - GraphQL::Schema::Timeoout#max_seconds(query) can provide a per-query timeout duration #3167 - Implement Interpreter::Arguments#fetch - Assign `current_{path,field,arguments,object}` in `query.context` #3139. The values at these keys change while the query is running. - ActionCableSubscriptions: accept `use(..., namespace: "...")` for running multiple schemas in the same application #3076 - Add `deprecation_reason:` to arguments #3015 ### Bug fixes - SanitizedPrinter: Fix lists and JSON scalars #3171 - Improve retained memory in Schema.from_definition #3153 - Make it easier to cache schema parsing #3153 - Make sure deprecated arguments aren't required #3137 - Use `.empty?` instead of `.length.zero?` in lexer #3134 - Return a proper error when a stack error happens #3129 - Assert valid input types on arguments #3120 - Improve Validator#validate performance #3125 - Don't wrap `RawValue` in ConnectionExtension #3122 - Fix interface possible types visibility #3124 ## 1.11.4 (24 August 2020) ### Breaking changes ### New features - Add `node_nullable` option for `edge_type` #3083 - Use module namespacing for template generators #3098 ### Bug fixes - Rescue `SystemStackError`s during validation #3107 - Add `require 'digest/sha2'` for fingerprint #3103 - Optimize `GraphQL::Query::Context#dig` #3090 - Check if new connections before calling method on it (fixes #3059) #3100 - Thread field owner type through interpreter runtime (fixes #3086) #3099 - Check for visible interfaces on the type in warden #3096 - Update `AppOpticsTracing` with latest changes in `PlatformTracing` #3097 - Use throw instead of raise to halt subscriptions early #3084 - Optimize `GraphQL::Query::Context#fetch` #3081 ## 1.11.3 (13 August 2020) ### Breaking changes - Reverted the `required` and `default_value` argument behaviour change in 1.11.2 since it was not spec compliant #3066 ### New features - Improve resolver method conflict warning #3069, #3062 - Store arguments on `Mutation` instances after they're loaded #3073 ### Bug fixes - Fix connection wrappers on lazy lists #3070 ## 1.11.2 (1 August 2020) ### Breaking changes - Previously, GraphQL-Ruby allowed _both_ `default_value: ...` and `required: true` in argument definitions. However, this definition doesn't make sense -- a default value is never used for a `required: true` argument. This configuration now raises an error. Remove the `default_value:` to get rid of the error. #3011 ### New features - Support Date, Time and OpenStruct in Subscription::Serialize #3057 ### Bug fixes - Speed up `DELETE_NODE` check #3053 - Reject invalid enum values during definition #3055 - Fix `.trigger` from unsubscribed ActionCable channel #3051 - Fix error message from VariablesAreUsedAndDefined for anonymous queries #3050 - Fix renaming variable identifiers in AST visitor #3045 - Reject `default_value: ...` used with `required: true` during definition #3011 - Use the configured `edge_class:` with new connections #3036 - Don't call visible for unused arguments #3030, #3031 - Properly load directives from introspection results #3021 - Reject interfaces as members of unions #3024 - Load deprecation reason from introspection results #3014 - Fix arguments caching when extension modify arguments #3009 ## 1.11.1 (17 June 2020) ### New Features - Add `StatsdTracing` #2996 ### Bug Fixes - Raise the proper `InvalidNullError` when a mutation field returns an invalid `nil` #2997 ## 1.11.0 (13 June 2020) ### Breaking changes - Global tracers are removed (deprecated since 1.7.4) #2936 - Fields defined in camel case (`field :doStuff`) will not line up to methods that are underscore case (`def do_stuff`). Instead, the given symbol is used _verbatim_. #2938 To work around this: - Change the name of the method to match the field (eg, `def doStuff`) - Change the name of the field to match the method (eg, `field :do_stuff`, let graphql-ruby camelize it for you) - Or, add `resolver_method: :do_stuff` to explicitly map the field to a method on the object type definition You can probably find instances of this in your application with a regexp like `/field :[a-z]+[A-Z]/`, and review them. ### New features - `extend SubscriptionRoot` is no longer necessary #2770 - Add `broadcast: true` option to subscriptions #2959 - Add `Edge#parent` to new connection classes #2961 ### Bug fixes - Use the field name as configured for hash key or method name #2906 ## 1.10.12 (13 June 2020) ### Bug fixes - Fix compatibility of `YYYY-mm-dd` with `Types::ISO8601DateTime` #2989 - Remove unused ivar in InputObject #2987 ## 1.9.21 (12 June 2020) ### Bug fixes - Fix `extras:` on subscription fields #2983 ## 1.10.11 (11 June 2020) ### New features - Scout tracer adds transaction name to traces #2969 - `resolve_type` can optionally return a resolved object #2976 - DateTime scalar returns a `Time` for better timezone handling #2973 - Interpreter memory improvements #2980, #2978 - Support lazy values from field-level authorization hooks #2977 - Object generator infers fields from model classes #2954 - Add type-specific runtime errors #2957 ### Bug fixes - Fix for error when using `extras:` with subscription fields #2984 - Improve Schema.error_handler inheritance #2975 - Add raw_value to conflict warning list #2958 - Arguments#each_value yields ArgumentValues #2956 ## 1.10.10 (20 May 2020) ### Bug Fixes - Fix lazy `loads:` with list arguments #2949 - Show object fields even when inherited ones are hidden #2950 - Use `reverse_each` in instrumenters #2945 - Fix underscored names in introspection loader #2941 - Fix array input to Date/DateTime types #2927 - Fix method conflict warnings on schema loader #2934 - Fix some Ruby 2.7 warnings #2925 ## 1.9.20 (20 May 2020) ### Bug fixes - Fix `default_value: {}` on Ruby 2.7 ## 1.10.9 (4 May 2020) ### New features - Add `Interpreter::Arguments#dig` #2912 ## 1.10.8 (27 April 2020) ### Breaking changes - With the interpreter, `Query#arguments_for` returns `Interpreter::Arguments` instances instead of plain hashes. (They should work mostly the same, though.) #2881 ### New features - `Schema::Field#introspection?` returns true for built-in introspection-related fields ### Bug fixes - Fix Ruby 2.7 warning on `Schema.to_json` #2905 - Pass `&block` to nested method calls to reduce stack depths #2900 - Fix lazy `loads:` with list arguments #2894 - Fix `loads:` on nested input object #2895 - Rescue base64 encoding errors in the encoder #2896 ## 1.10.7 (16 April 2020) ### Breaking changes - `Schema.from_introspection(...)` builds class-based schemas #2876 ### New features - `Date` and `DateTime` types also accept well-formatted strings #2848 - `Schema.from_introspection(...)` builds class-based schemas #2876 - `Schema#to_definition` now dumps all directives that were part of the original IDL, if the schema was parsed with `.from_definition` #2879 ### Bug fixes - Fix memory leak in legacy runtime #2884 - Fix interface inheritance in legacy runtime #2882 - Fix description on `List` and `NonNull` types (for introspection) #2875 - Fix over-rescue of NoMethodError when building list responses #2887 ## 1.10.6 (6 April 2020) ### New features - Add options to `implements(...)` and inteface type visibility #2791 - Add `Query#fingerprint` for logging #2859 - Add `--playground` option to install generator #2839 - Support lazy-loaded objects from input object `loads:` #2834 ### Bug fixes - Fix `Language::Nodes` equality: move `eql?` to `==` #2861 - Make rake task properly detect rails `environment` task #2862 - Fix `nil` override for `max_page_size` #2843 - Fix `pageInfo` methods when they're called before `nodes` #2845 - Make the default development error match a normal GraphQL error #2825 - Fix `loads:` with `require: false` #2833 - Fix typeerror for `BigInt` given `nil` #2827 ## 1.10.5 (12 March 2020) ### New features - Add `#field_complexity` hook to `AST::QueryComplexity` analyzer #2807 ### Bug fixes - Pass `nonce: true` when encoding cursors #2821 - Ignore empty-string cursors #2821 - Properly pass along `Analysis::AST` to schema instances #2820 - Support filtering unreachable types in schemas from IDL #2816 - Use `Query#arguments_for` for lookahead arguments #2811 - Fix pagination bug on old connections #2799 - Support new connection system on old runtime #2798 - Add details to raise CoercionErrors #2796 ## 1.10.4 (3 March 2020) ### Breaking changes - When an argument is defined with a symbol (`argument :my_arg, ...`), that symbol is used _verbatim_ to build Ruby keyword arguments. Previously it was converted to underscore-case, but this autotransform was confusing and wrong in some cases. You may have to change the symbol in your `argument(...)` configuration if you were depending on that underscorization. #2792 - Schemas from `.from_definition` previously had half-way connection support. It's now completely removed, so you have to add connection wrappers manually. See #2782 for migration notes. ### New features - Add `Appoptics` tracing #2789 - Add `Query#sanitized_query_string` #2785 - Improved duplicate type error message #2777 ### Bug fixes - Fix arguments ending in numbers, so they're injected with the same name that they're configured with #2792 - Improve `Query#arguments_for` with interpreter #2781 - Fix visitor replacement of variable definitions #2752 - Remove half-broken connection handling from `Schema.from_definition` #2782 ## 1.10.3 (17 Feb 2020) ### New features - Support `loads:` with plain field arguments #2720 - Support `raw_value(...)` to halt execution with a certain value #2699 - `.read_subscription` can return `nil` to bypass executing a subscription #2741 ### Bug fixes - Connection wrappers are properly inherited #2750 - `prepare(...)` is properly applied to default values in subscription fields #2748 - Code tidying for RSpec warnings #2741 - Include new analysis module when generating a schema #2734 - Include directive argument types in printed schemas #2733 - Use `module_parent_name` in Rails #2713 - Fix overriding default scalars in build_from_definition #2722 - Fix some non-null errors in lists #2651 ## 1.10.2 (31 Jan 2020) ### Bug fixes - Properly wrap nested input objects in instances #2710 ## 1.10.1 (28 Jan 2020) ### Bug fixes - Include Interface-level `orphan_types` when building a schema #2705 - Properly re-enter selections in complexity analyzer #2595 - Fix input objects with null values #2690 - Fix default values of `{}` in `.define`-based schemas #2703 - Fix field extension presence check #2689 - Make new relation connections more efficient #2697 - Don't include fields `@skip(if: true)` or `@include(if: false)` in lookahead #2700 ## 1.9.19 (28 Jan 2020) ### Bug Fixes - Fix argument default value of `{}` with Ruby 2.7 argument handling #2704 ## 1.10.0 (20 Jan 2020) ### Breaking Changes - Class-based schemas using the new interpreter will now use _definition classes_ at runtime. #2363 (Previously, `.to_graphql` methods were used to generate singletons which were used at runtime.) This means: - Methods that used to receive types at runtime will now receive classes instead of those singletons. - `.name` will now call `Class#name`, which will give the class name. Use `.graphql_name` to get the name of a GraphQL type. (Fields, arguments and directives have `.graphql_name` too, so you can use it everywhere.) - Some methods that return hashes are slow because they merge hashes according to class inheritance, for example `MySchema.types` and `MyObjectType.fields`. Instead: - If you only need one item out of the Hash, use `.get_type(type_name)` or `.get_field(field_name)` instead. Those methods find a match without performing Hash merges. - If you need the whole Hash, get a cached value from `context.warden` (an instance of `GraphQL::Schema::Warden`) at runtime. Those values reflect the types and fields which are permitted for the current query, and they're cached for life of the query. Check the API docs to see methods on the `warden`. - Class-based schemas using the interpreter _must_ add `use GraphQL::Analysis::AST` to their schema (and update their custom analyzers, see https://graphql-ruby.org/queries/ast_analysis.html) #2363 - ActiveSupport::Notifications events are correctly named in event.library format #2562 - Field and Argument `#authorized?` methods now accept _three_ arguments (instead of 2). They now accept `(obj, args, ctx)`, where `args` is the arguments (for a field) or the argument value (for an argument). #2520 - Double-null `!!` is disallowed by the parser #2397 - (Non-interpreter only) The return value of subscription fields is passed along to execute the subscription. Return `nil` to get the previous behavior. #2536 - `Schema.from_definition` builds a _class-based schema_ from the definition string #2178 - Only integers are accepted for `Int` type #2404 - Custom scalars now call `.coerce_input` on all input values - previously this call was skipped for `null` values. ### Deprecations - `.define` is deprecated; class-based schema definitions should be used instead. If you're having trouble or you can't find information about an upgrade path, please open an issue on GitHub! ### New Features - Add tracing events for `.authorized?` and `.resolve_type` calls #2660 - `Schema.from_definition` accepts `using:` for installing plugins (equivalent to `use ...` in class-based schemas) #2307 - Add `$` to variable names in error messages #2531 - Add invalid value to argument error message #2531 - Input object arguments with `loads:` get the loaded object in their `authorized?` hook, as `arg` in `authorized?(obj, args, ctx)`. #2536 - `GraphQL::Pagination` auto-pagination system #2143 - `Schema.from_definition` builds a _class-based schema_ from the definition string #2178 ### Bug Fixes - Fix warnings on Ruby 2.7 #2668 - Fix Ruby keyword list to support Ruby 2.7 #2640 - Reduce memory of class-based schema #2636 - Improve runtime performance of interpreter #2630 - Big numbers (ie, greater than Ruby's `Infinity`) no longer :boom: when being reserialized #2320 - Fix `hasNextPage`/`hasPrevious` page when max_page_size limits the items returned #2608 - Return parse errors for empty documents and empty argument lists #2344 - Properly serialize `defaultValue` of input objects containing enum values #2439 - Don't crash when a query contains `!!`. #2397 - Resolver `loads:` assign the value to argument `@loads` #2364 - Only integers are accepted for `Int` type #2404 ## 1.9.18 (15 Jan 2020) ### New features - Support disabling `__type` or `__schema` individually #2657 - Support Ruby 2.7, and turn on CI for it :tada: #2665 ### Bug fixes - Fix Ruby 2.7 warnings #2653 #2669 - Properly build camelized names for directive classes #2666 - Use schema-defined context class for SDL generation #2656 - Apply visibility checks when generating SDL #2637 ## 1.9.17 (17 Dec 2019) ### New features - Scoped context for propagating values to child fields #2634 - Add `type_membership_class` with possible_type visibility #2391 ### Bug fixes - Don't return unreachable types in introspection response #2596 - Wrap more of execution with error handling #2632 - Fix InputObject `.prepare` for the interpreter #2624 - Fix Ruby keyword list to support Ruby 2.7 #2640 - Fix performance of urlsafe_encode64 backport #2643 ## 1.9.16 (2 Dec 2019) ### Breaking changes - `GraphQL::Schema::Resolver#initialize` accepts a new keyword argument, `field:`. If you have overriden this method, you'll have to add that keyword to your argument list (and pass it along to `super`.) #2605 ### Deprecations - `SkylightTracing` is disabled; the Skylight agent contains its own GraphQL support. See Skylight's docs for migration. #2601 ### New features ### Bug fixes - Fix multiplex max_depth calculation #2613 - Use monotonic time in TimeoutMiddleware #2622 - Use underscored names in Mutation generator #2617 - Fix lookahead when added to mutations in their `field(...)` definitions #2605 - Handle returned lists of errors from Mutations #2567 - Fix lexer error on block strings containing only newlines #2598 - Fix mutation generator to reference the new base class #2580 - Use the right camelization configuration when generating subscription topics #2552 ## 1.9.15 (30 Oct 2019) ### New features - Improve parser performance #2572 - Add `def prepare` API for input objects #1869 - Support `extensions` config in Resolver classes #2570 - Support custom `.connection_extension` in field classes #2561 - Warn when a field name is a Ruby keyword #2559 - Improve performance for ActiveRecord connection #2547 ### Bug fixes - Fix errantly generated `def resolve_field` method in `BaseField` #2578 - Comment out the `null_session` handling in the generated controller, for better compat with Rails API mode #2557 - Fix validation error with duplicate, self-referencing fragment #2577 - Revert the `.authorized?` behavior of InputObjects to handle cyclical references. See 1.10.0.pre1 for a better behavior. #2576 - Replace `NotImplementedError` (which is meant for operating system APIs) with `GraphQL::RequiredImplementationMissingError` #2543 ## 1.9.14 (14 Oct 2019) ### New features - Add `null_session` CSRF handing in `install` generator #2524 - Correctly report InputObjects without arguments and Objects without fields as invalid #2539 #2462 ### Bug fixes - Fix argument incompatibility #2541 - Add a `require` for `Types::ISO8691Date` #2528 - Fix errors re-raised after lazy fields #2525 ## 1.9.13 (8 Oct 2019) ### Breaking changes - Enum values were (erroneously) accepted as ID or String values, but they aren't anymore. #2505 ### New features - Add `Query#executed?` #2486 - Add `Types::ISO8601Date` #2471 ### Bug fixes - Don't accept Enums as IDs or Strings #2505 - Call `.authorized?` hooks on arguments that belong to input objects #2519 - Fix backslash parsing edge case #2510 - Improve performance #2504 #2498 - Properly stringify keys in error extensions #2508 - Fix `extras:` handling in RelayClassicMutation #2484 - Use `Types::BaseField` in scaffold #2470 ## 1.9.12 (9 Sept 2019) ### Breaking Changes - AST Analyzers follow fragments spreads as if they were inline fragments. #2463 ### New Features - `use GraphQL::Execution::Errors` provides error handling for the new interpreter. #2458 ### Bug Fixes - Fix false positive on enum value validation #2454 ## 1.9.11 (29 Aug 2019) ### Breaking Changes - Introspection fields are now considered for query depth validations, so you'll need at least `max_depth: 13` to run the introspection query #2437 ### New features - Add `extras` setter to `GraphQL::Schema::Field` #2450 - Add extensions in `CoercionError` #2431 ### Bug fixes - Make `extensions` kwarg on field on more flexible for extensions with options #2443 - Fix list validation error handling #2441 - Include introspective fields in query depth calculations #2437 - Correct the example for using 'a class method to generate fields' #2435 - Enable multiple execution errors for Fields defined to return a list #2433 ## 1.9.10 (20 Aug 2019) ### New features - Support required arguments with default values #2416 ### Bug fixes - Properly disable `max_complexity` and `max_depth` when `nil` is passed #2409 - Fix printing class-based schemas #2406 - Improve field method naming conflict check #2420 ## 1.9.9 (30 July 2019) ### New features - Memoize generated strings in `.to_query_string` #2400 - Memoize generated strings in platform tracing #2401 ### Bug fixes - Support class-based subscription type in `.define`-based schema #2403 ## 1.9.8 (24 July 2019) ### New features - Schema classes pass their configuration to subclasses #2384 - Improve memory consumption of lexer and complexity validator #2389 - The `install` generator creates a BaseArgument #2379 - When a field name conflicts with a built-in method name, give a warning #2376 ### Bug fixes - When a resolver argument uses `loads:`, the argument definition will preserve the type in `.loads` #2365 - When an required argument is hidden, it won't add a validation error #2393 - Fix handling of invalid UTF-8 #2372, #2377 - Empty block strings are parsed correctly #2381 - For resolvers, only authorize arguments once #2378 ## 1.9.7 (25 June 2019) ### Breaking changes - `Analysis::AST::Visitor#argument_definition` no longer returns the _previous_ argument definition. Instead, it returns the _current_ argument definition and `#previous_argument_definition` returns the previous one. You might have to replace calls to `.argument_definition` with `.previous_argument_definition` for compatibility. #2226 ### New features - Accept a `subscription_scope` configuration in Subscription classes #2297 - Add a `disable_introspection_entry_points` configuration in Schema classes #2327 - Add `Analysis::AST::Visitor#argument_definition` which returns the _current_ argument definition, `#previous_argument_definition` returns the _previous_ one #2226 - Run CI on Ruby 2.6 #2328 - Autogenerate base field class #2216 - Add timeout support with interpreter #2220 ### Bug fixes - Fix Stack overflow when calling `.to_json` on input objects #2343 - Fix off-by-one error with hasNextPage and ArrayConnections #2349 - Fix GraphQL-Pro operation store compatibility #2350 - Fix class-based transformer when multiple mutations are in one file #2309 - Use `default_graphql_name` for Edge classes #2224 - Support nested `loads:` with input objects #2323 - Support `max_complexity` with multiplex & AST analysis #2306 ## 1.9.6 (23 May 2019) ### Bug fixes - Backport `String#-@` for Ruby 2.2 support #2305 ## 1.9.5 (22 May 2019) ### New features - Support `rescue_from` returning `GraphQL::ExecutionError` #2140 - Accept `context:` in `Schema.validate` #2256 - Include `query:` in interpreter tracing for `execute_field` and `execute_field_lazy` #2236 - Add `Types::JSON` #2227 - Add `null:` option to `BaseEdge.node_type` #2249 ### Bug fixes - Fix Ruby 2.2 compatibility #2302 - Distinguish aliased selections in lookahead #2266 - Properly show list enum default values in introspection #2263 - Performance improvements: #2289, #2244, #2258, #2257, #2240 - Don't recursively unwrap inputs for RelayClassicMutation #2236 - Fix `Schema::Field#scoped?` when no return type #2255 - Properly forward more authorization errors #2165 - Raise `ParseError` for `.parse(nil)` #2238 ## 1.9.4 (5 Apr 2019) ### Breaking Changes - `GraphQL::Schema::Resolver::LoadApplicationObjectFailedError` was renamed to `GraphQL::LoadApplicationObjectFailedError`. (This will only break if you're referencing the class by name and running Ruby 2.5+) #2080 ### New features - Add `Types::BigInt` #2150 - Add auto-loading arguments support in Input Object types #2080 - Add analytics tag to Datadog tracing #2154 ### Bug fixes - Fix `Query#execute` when no explicit query string is passed in #2142 - Fix when a root type returns nil because unauthorized #2144 - Fix tracing `node` by threading `owner:` through field tracing #2156 - Fix interpreter handling of exceptions raised during argument preparation #2198 - Fix ActionCableLink when there are errors but no data #2176 - Provide empty hash as default option for field resolvers #2189 - Prevent argument names from overwriting Arguments methods #2171 - Include array indices in error paths #2162 - Handle non-node arrays in AST visitor #2161 ## 1.9.3 (20 Feb 2019) ### Bug fixes - Fix `Schema::Subscription` when it has no arguments #2135 - Don't try to scope `nil`, just skip scoping altogether #2134 - Fix when a root `.authorized?` returns `false` and there's no `root_value` #2136 - Fix platform tracing with interpreter & introspection #2137 - Support root Subscription types with name other than `Subscription` #2102 - Fix nested list-type input object nullability validation #2123 ## 1.9.2 (15 Feb 2019) ### Bug fixes - Properly support connection fields with resolve procs #2115 ## 1.9.1 (14 Feb 2019) ### Bug fixes - Properly pass errors to Resolver `load_application_object_failed` methods #2110 ## 1.9.0 (13 Feb 2019) ### Breaking Changes - AST nodes are immutable. To modify a parsed GraphQL query, see `GraphQL::Language::Visitor` for its mutation API, which builds a new AST with the specified mutations applied. #1338, #1740 - Cursors use urlsafe Base64. This won't break your clients (it's backwards-compatible), but it might break your tests, so it's listed here. #1698 - Add `field(..., resolver_method:)` for when GraphQL-Ruby should call a method _other than_ the one whose name matches the field name (#1961). This means that if you're using `method:` to call a different method _on the Schema::Object subclass_, you should update that configuration to `resolver_method:`. (`method:` is still used to call a different method on the _underlying application object_.) - `Int` type now applies boundaries as [described in the spec](https://facebook.github.io/graphql/June2018/#sec-Int) #2101. To preserve the previous, unbounded behavior, handle the error in your schema's `.type_error(err, ctx)` hook, for example: ```ruby class MySchema < GraphQL::Schema def self.type_error(err, ctx) if err.is_a?(GraphQL::IntegerEncodingError) # Preserve the previous unbounded behavior # by returning the out-of-bounds value err.integer_value else super end end end ``` - `field(...)` configurations don't create implicit method definitions (#1961). If one resolver method depended on the implicitly-created method from another field, you'll have to refactor that call or manually add a `def ...` for that field. - Calling `super` in a field method doesn't work anymore (#1961) - Error `"problems"` are now in `"extensions" : { "problems": ... }` #2077 - Change schema default to `error_bubbling false` #2069 ### New Features - Add class-based subscriptions with `GraphQL::Schema::Subscription` #1930 - Add `GraphQL::Execution::Interpreter` (#1394) and `GraphQL::Analysis::AST` (#1824) which together cut GraphQL overhead by half (time and memory) - Add `Schema.unauthorized_field(err)` for when `Field#authorized?` checks fail (#1994) - Add class-based custom directives for the interpreter (#2055) - Add `Schema::FieldExtension` for customizing field execution with class-based fields #1795 - Add `Query#lookahead` for root-level selection info #1931 - Validation errors have `"extensions": { ... }` which includes metadata about that error #1970 ### Bug fixes - Fix list-type arguments passed with a single value #2085 - Support `false` as an Enum value #2050 - Support `hash_key:` fields when the key isn't a valid Ruby method name #2016 ## 1.8.15 (13 Feb 2019) ### Bug fixes - Fix unwrapping inputobject types when turning arguments to hashes #2094 - Support lazy objects from `.resolve_type` hooks #2108 ## 1.8.14 (9 Feb 2019) ### Bug Fixes - Fix single-item list inputs that aren't passed as lists #2095 ## 1.8.13 (4 Jan 2019) ### Bug fixes - Fix regression in block string parsing #2032 ## 1.8.12 (3 Jan 2019) ### Breaking changes - When an input object's argument has a validation error, that error is reported on the _argument_ instead of its parent input object. #2013 ### New features - Add `error_bubbling false` Schema configuration for nicer validation of compound inputs #2013 - Print descriptions as block strings in SDL #2011 - Improve string-to-constant resolution #1810 - Add `Query::Context#to_hash` for splatting #1955 - Add `#dig` to `Schema::InputObject` and `Query::Arguments` #1968 - Add `.*_execution_strategy` methods to class-based schemas #1914 - Accept multiple errors when adding `.rescue_from` handlers #1991 ### Bug fixes - Fix scalar tracing in NewRelic and Skylight #1954 - Fix lexer for multiple block strings #1937 - Add `unscope(:order)` when counting relations #1911 - Improve build-from-definition error message #1998 - Fix regression in legacy compat #2000 ## 1.8.11 (16 Oct 2018) ### New features - `extras: [:lookahead]` injects a `GraphQL::Execution::Lookahead` ### Bug fixes - Fix type printing in Printer #1902 - Rescue `GraphQL::ExecutionError` in `.before_query` hooks #1898 - Properly load default values that are lists of input objects from the IDL #1874 ## 1.8.10 (21 Sep 2018) ### Bug fixes - When using `loads:` with a nullable mutation input field, allow `null` values to be provided. #1851 - When an invalid Base64 encoded cursor is provided, raise a `GraphQL::ExecutionError` instead of `ArgumentError`. #1855 - Fix an issue with `extras: [:path]` would use the field's `path` instead of the `context`. #1859 ### New features - Add scalar type generator `rails g graphql:scalar` #1847 - Add `#dig` method to `Query::Context` #1861 ## 1.8.9 (13 Sep 2018) ### Breaking changes - When `field ... ` is called with a block and the block has one argument, the field is yielded, but `self` inside the block is _not_ changed to the field. #1843 ### New features - `extras: [...]` can inject values from the field instance #1808 - Add `ISO8601DateTime.time_precision` for customization #1845 - Fix input objects with default values of enum #1827 - `Schema.sync_lazy(value)` hook for intercepting lazy-resolved objects #1784 ### Bug fixes - When a field block is provided with an arity of `1`, yield the field #1843 ## 1.8.8 (27 Aug 2018) ### Bug fixes - When using `RelayClassicMutation`, `client_mutation_id` will no longer be passed to `authorized?` method #1771 - Fix issue in schema upgrader script which would cause `.to_non_null_type` calls in type definition to be ignored #1783 - Ensure enum values respond to `graphql_name` #1792 - Fix infinite resolution bug that could occur when an exception not inheriting from `StandardError` is thrown #1804 ### New features - Add `#path` method to schema members #1766 - Add `as:` argument to allow overriding the name of the argument when using `loads:` #1773 - Add support for list of IDs when using `loads:` in an argument definition #1797 ## 1.8.7 (9 Aug 2018) ### Breaking changes - Some mutation authorization hooks added in 1.8.5 were changed, see #1736 and #1737. Roughly: - `before_prepare` was changed to `#ready?` - `validate_*` hooks were replaced with a single `#authorized?` method ### Bug fixes - Argument default values include nested default values #1728 - Clean up duplciate method defs #1739 ### New features - Built-in support for Mongoid 5, 6, 7 #1754 - Mutation `#ready?` and `#authorized?` may halt flow and/or return data #1736, #1737 - Add `.scope_items(items, ctx)` hook for filtering lists - Add `#default_graphql_name` for overriding default logic #1729 - Add `#add_argument` for building schemas #1732 - Cursors are decoded using `urlsafe_decode64` to future-proof for urlsafe cursors #1748 ## 1.8.6 (31 July 2018) ### Breaking changes - Only allow Objects to implement actual Interfaces #1715. Use `include` instead for plain Ruby modules. - Revert extending interface methods onto Objects #1716. If you were taking advantage of this feature, you can create a plain Ruby module with the functionality and include it in both the interface and object. ### Deprecations ### New features - Support string descriptions (from June 2018 GraphQL spec) #1725 - Add some accessors to Schema members #1722 - Yield argument for definition block with arity of one #1714 - Yield field for definition blocks with arity of one #1712 - Support grouping by "endpoint" with skylight instrumentation #1663 - Validation: Don't traverse irep if no handlers are registered #1696 - Add `nodes_field` option to `edge_type` to hide nodes field #1693 - Add `GraphQL::Types::ISO8601DateTime` to documentation #1694 - Conditional Analyzers #1690 - Improve error messages in `ActionCableSubscriptions` #1675 - Add Prometheus tracing #1672 - Add `map` to `InputObject` #1669 ### Bug fixes - Improve the mutation generator #1718 - Fix method inheritance for interfaces #1709 - Fix Interface inheritance chain #1686 - Fix require in `tracing.rb` #1685 - Remove delegate for `FieldResolutionContext#schema` #1682 - Remove duplicated `object_class` method #1667 ## 1.8.5 (10 July 2018) ### Breaking changes - GraphQL validation errors now include `"filename"` if the parsed document had a `filename` #1618 ### Deprecations - `TypeKind#resolves?` is deprecated in favor of `TypeKind#abstract?` #1619 ### New features - Add Mutation loading/authorization system #1609 - Interface `definition_methods` are inherited by object type classes #1635 - include `"filename"` in GraphQL errors if the parsed document has a filename #1618 - Add `Schema::InputObject#empty?` #1651 - require `ISO8601DateTime` by default #1660 - Support `extend` in the parser #1620 - Improve generator to have nicer error handling in development ### Bug fixes - Fix `@skip`/`@include` with default value of `false` #1617 - Fix lists of abstract types with promises #1613 - Don't check the type of `nil` when it's in a list #1610 - Fix NoMethodError when `variables: nil` is passed to `execute(...)` #1661 - Objects returned from `Schema.unauthorized_objects` are properly wrapped by their type proxies #1662 ## 1.8.4 (21 June 2018) ### New features - Add class-based definitions for Relay types #1568 - Add a built-in auth system #1494 ### Bug fixes - Properly rescue coercion errors in variable values #1602 ## 1.8.3 (14 June 2018) ### New features - Add an ISO 8601 DateTime scalar: `Types::ISO8601DateTime`. #1566 - Use classes under the hood for built-in scalars. These are now accessible via `Types::` namespace. #1565 - Add `possible_types` helpers to abstract types #1580 ### Bug fixes - Fix `Language::Visitor` when visiting `InputObjectTypeDefinition` nodes to include child `Directive` nodes. #1584 - Fix an issue preventing proper subclassing of `TimeoutMiddleware`. #1579 - Fix `graphql:interface` generator such that it generates working code. #1577 - Update the description of auto-generated `before` and `after` arguments to better describe their input type. #1572 - Add `Language::Nodes::DirectiveLocation` AST node to represent directive locations in directive definitions. #1564 ## 1.8.2 (6 June 2018) ### Breaking changes - `Schema::InputObject#to_h` recursively transforms hashes to underscorized, symbolized keys. #1555 ### New features - Generators create class-based types #1562 - `Schema::InputObject#to_h` returns a underscorized, symbolized hash #1555 ### Bug fixes - Support `default_mask` in class-based schemas #1563 - Fix null propagation for list types #1558 - Validate unique arguments in queries #1557 - Fix `RelayClassicMutation`s with no arguments #1543 ## 1.8.1 (1 June 2018) ### Breaking changes - When filtering items out of a schema, Unions will now be hidden if their possible types are all hidden or if all fields returning it are hidden. #1515 ### New features - `GraphQL::ExecutionError.new` accepts an `extensions:` option which will be merged into the `"extensions"` key in that error's JSON #1552 ### Bug fixes - When filtering items out of a schema, Unions will now be hidden if their possible types are all hidden or if all fields returning it are hidden. #1515 - Require that fields returning interfaces have selections made on them #1551 - Correctly mark introspection types and fields as `introspection?` #1535 - Remove unused introspection objects #1534 - use `object`/`context` in the upgrader instead of `@object`/`@context` #1529 - (Development) Don't require mongodb for non-mongo tests #1548 - Track position of union member nodes in the parser #1541 ## 1.8.0 (17 May 2018) `1.8.0` has been in prerelease for 6 months. See the prerelease changelog for change-by-change details. Here's a high-level changelog, followed by a detailed list of changes since the last prerelease. ### High-level changes #### Breaking Changes - GraphQL-Ruby is not tested on Ruby 2.1. #1070 Because Ruby 2.1 doesn't garbage collect Symbols, it's possible that GraphQL-Ruby will introduce a OOM vulnerability where unique symbols are dynamically created, for example, turning user input into Symbols. No instances of this are known in GraphQL-Ruby ... yet! - `GraphQL::Delegate`, a duplicate of Ruby's `Forwardable`, was removed. Use `Forwardable` instead, and update your Ruby if you're on `2.4.0`, due to a performance regression in `Forwardable` in that version. - `MySchema.subscriptions.trigger` asserts that its inputs are valid arguments #1400. So if you were previously passing invalid options there, you'll get an error. Remove those options. #### New Features - A new class-based API for schema definition. The old API is completely supported, but the new one is much nicer to use. If you migrate, some schema extensions may require a bit of extra work. - Built-in support for Mongoid-backed Relay connections - `.execute(variables: ...)` and `subscriptions.trigger` both accept Symbol-keyed hashes - Lots of other small things around SDL parsing, tracing, runtime ... everything. Read the details below for a full list. #### Bug Fixes - Many, many bug fixes. See the detailed list if you're curious about specific bugs. ### Changes since `1.8.0.pre11`: #### Breaking Changes - `GraphQL::Schema::Field#initialize`'s signature changed to accept keywords and a block only. `type:`, `description:` and `name:` were moved to keywords. See `Field.from_options` for how the `field(...)` helper's arguments are merged to go to `Field.new`. #1508 #### New Features - `Schema::Resolver` is a replacement for `GraphQL::Function` #1472 - Fix subscriptions with class-based schema #1478 - `Tracing::NewRelicTracing` accepts `set_transaction_name:` to use the GraphQL operation name as the NewRelic transaction name #1430 #### Bug fixes - Backported `accepts_definition`s are inherited #1514 - Fix Schema generator's `resolve_type` method #1481 - Fix constant assignment warnings with interfaces including multiple other interfaces #1465 - InputObject types loaded from SDL have the proper AST node assigned to them #1512 ## 1.8.0.pre11 (3 May 2018) ### Breaking changes - `Schema::Mutation.resolve_mutation` was moved to an instance method; see changes to `Schema::RelayClassicMutation` in #1469 for an example refactor - `GraphQL::Delegate` was removed, use Ruby's `Forwardable` instead (warning: bad performance on Ruby 2.4.0) - `GraphQL::Schema::Interface` is a module, not a class #1372. To refactor, use a base module instead of a base class: ```ruby module BaseInterface include GraphQL::Schema::Interface end ``` And include that in your interface types: ```ruby module Reservable include BaseInterface field :reservations, ... end ``` In object types, no change is required; use `implements` as before: ```ruby class EventVenue < BaseObject implements Reservable end ``` ### New features - `GraphQL::Schema::Interface` is a module - Support `prepare:` and `as:` argument options #1469 - First-class support for Mongoid connections #1452 - More type inspection helpers for class-based types #1446 - Field methods may call `super` to get the default behavior #1437 - `variables:` accepts symbol keys #1401 - Reprint any directives which were parsed from SDL #1417 - Support custom JSON scalars #1398 - Subscription `trigger` accepts symbol, underscored arguments and validates their presence #1400 - Mutations accept a `null(true | false)` setting to affect field nullability #1406 - `RescueMiddleware` uses inheritance to match errors #1393 - Resolvers may return a list of errors #1231 ### Bug fixes - Better error for anonymous class names #1459 - Input Objects correctly inherit arguments #1432 - Fix `.subscriptions` for class-based Schemas #1391 ## 1.8.0.pre10 (4 Apr 2018) ### New features - Add `Schema::Mutation` and `Schema::RelayClassicMutation` base classes #1360 ### Bug fixes - Fix using anonymous classes for field types #1358 ## 1.8.0.pre9 (19 Mar 2018) - New version number. (I needed this because I messed up build tooling for 1.8.0.pre8). ## 1.8.0.pre8 (19 Mar 2018) ### New Features - Backport `accepts_definition` for configurations #1357 - Add `#owner` method to Schema objects - Add `Interface.orphan_types` config for orphan types #1346 - Add `extras: :execution_errors` for `add_error` #1313 - Accept a block to `Schema::Argument#initialize` #1356 ### Bug Fixes - Support `cursor_encoder` #1357 - Don't double-count lazy/eager field time in Tracing #1321 - Fix camelization to support single leading underscore #1315 - Fix `.resolve_type` for Union and Interface classes #1342 - Apply kwargs before block in `Argument.from_dsl` #1350 ## 1.8.0.pre7 (27 Feb 2018) ### New features - Upgrader improvements #1305 - Support `global_id_field` for interfaces #1299 - Add `camelize: false` #1300 - Add readers for `context`, `object` and `arguments` #1283 - Replace `Schema.method_missing` with explicit whitelist #1265 ## 1.8.0.pre6 (1 Feb 2018) ### New features - Custom enum value classes #1264 ### Bug fixes - Properly print SDL type directives #1255 ## 1.8.0.pre5 (1 Feb 2018) ### New features - Upgrade argument access with the upgrader #1251 - Add `Schema#find(str)` for finding schema members by name #1232 ### Bug fixes - Fix `Schema.max_complexity` #1246 - Support cyclical connections/edges #1253 ## 1.8.0.pre4 (18 Jan 2018) ### Breaking changes - `Type.fields`, `Field.arguments`, `Enum.values` and `InputObject.arguments` return a Hash instead of an Array #1222 ### New features - By default, fields try hash keys which match their name, as either a symbol or a string #1225 - `field do ... end` instance_evals on the Field instance, not a FieldProxy #1227 - `[T, null: true]` creates lists with nullable items #1229 - Upgrader improvements #1223 ### Bug fixes - Don't require `parser` unless the upgrader is run #1218 ## 1.8.0.pre3 (12 Jan 2018) ### New Features - Custom `Context` classes for class-based schemas #1161 - Custom introspection for class-based schemas #1170 - Improvements to upgrader tasks and internals #1151, #1178, #1212 - Allow description inside field blocks #1175 ## 1.8.0.pre2 (29 Nov 2017) ### New Features - Add `rake graphql:upgrade[app/graphql]` for automatic upgrade #1110 - Automatically camelize field names and argument names #1143, #1126 - Improved error message when defining `name` instead of `graphql_name` #1104 ### Bug fixes - Fix list wrapping when value is `nil` #1117 - Fix ArgumentError typo #1098 ## 1.8.0.pre1 (14 Nov 2017) ### Breaking changes - Stop official support for Ruby 2.1 #1070 ### New features - Add class-based schema definition API #1037 ## 1.7.14 (4 Apr 2018) ### New features - Support new IDL spec for `&` for interfaces #1304 - Schema members built from IDL have an `#ast_node` #1367 ### Bug fixes - Fix paging backwards with `hasNextPage` #1319 - Add hint for `orphan_types` in error message #1380 - Use an empty hash for `result` when a query has unhandled errors #1382 ## 1.7.13 (28 Feb 2018) ### Bug fixes - `Schema#as_json` returns a hash, not a `GraphQL::Query::Result` #1288 ## 1.7.12 (13 Feb 2018) ### Bug fixes - `typed_children` should always return a Hash #1278 ## 1.7.11 (13 Feb 2018) ### Bug fixes - Fix compatibility of `irep_node.typed_children` on leaf nodes #1277 ## 1.7.10 (13 Feb 2018) ### Breaking Changes - Empty selections (`{ }`) are invalid in the GraphQL spec, but were previously allowed by graphql-ruby. They now return a parse error. #1268 ### Bug fixes - Fix error when inline fragments are spread on scalars #1268 - Fix printing SDL when types have interfaces and directives #1255 ## 1.7.9 (1 Feb 2018) ## New Features - Support block string inputs #1219 ## Bug fixes - Fix deprecation regression in schema printer #1250 - Fix resource names in DataDog tracing #1208 - Fix passing `context` to multiplex in `Query#result` #1200 ## 1.7.8 (11 Jan 2018) ### New features - Refactor `Schema::Printer` to use `Language::Printer` #1159 - Add `ArgumentValue#default_used?` and `Arguments#default_used?` #1152 ### Bug fixes - Fix Scout Tracing #1187 - Call `#inspect` for `EnumType::UnresolvedValueError` #1179 - Parse empty field sets in IDL parser #1145 ## 1.7.7 (29 Nov 2017) ### New features - `Schema#to_document` returns a `Language::Nodes::Document` #1134 - Add `trace_scalars` and `trace: true|false` to monitoring #1103 - Add `Tracing::DataDogPlatform` monitoring #1129 - Support namespaces in `rails g graphql:function` and `:loader` #1127 - Support `serializer:` option for `ActionCableSubscriptions` #1085 ### Bug fixes - Properly count the column after a closing quote #1136 - Fix default value input objects in `Schema.from_definition` #1135 - Fix `rails destroy graphql:mutation` #1119 - Avoid unneeded query in RelationConnection with Sequel #1101 - Improve & document instrumentation stack behavior #1101 ## 1.7.6 (13 Nov 2017) ### Bug fixes - Serialize symbols in with `GraphQL::Subscriptions::Serialize` #1084 ## 1.7.5 (7 Nov 2017) ### Breaking changes - Rename `Backtrace::InspectResult#inspect` to `#inspect_result` #1022 ### New features - Improved website search with Algolia #934 - Support customized generator directory #1047 - Recursively serialize `GlobalID`-compliant objects in Arrays and hashes #1030 - Add `Subscriptions#build_id` helper #1046 - Add `#non_null?` and `#list?` helper methods to type objects #1054 ### Bug fixes - Fix infinite loop in query instrumentation when error is raised #1074 - Don't try to trace error when it's not raised during execution - Improve validation of query variable definitions #1073 - Fix Scout tracing module load order #1064 ## 1.7.4 (9 Oct 2017) ### Deprecations - `GraphQL::Tracing.install` is deprecated, use schema-local or query-local tracers instead #996 ### New features - Add monitoring plugins for AppSignal, New Relic, Scout and Skylight #994, #1013 - Custom coercion errors for custom scalars #988 - Extra `options` for `GraphQL::ExecutionError` #1002 - Use `GlobalID` for subscription serialization when available #1004 - Schema- and query-local, threadsafe tracers #996 ### Bug fixes - Accept symbol-keyed arguments to `.trigger` #1009 ## 1.7.3 (20 Sept 2017) ### Bug fixes - Fix arguments on `Query.__type` field #978 - Fix `Relay::Edge` objects in `Backtrace` tables #975 ## 1.7.2 (20 Sept 2017) ### Bug fixes - Correctly skip connections that return `ctx.skip` #972 ## 1.7.1 (18 Sept 2017) ### Bug fixes - Properly release changes from 1.7.0 ## 1.7.0 (18 Sept 2017) ### Breaking changes - `GraphQL::Result` is the returned from GraphQL execution. #898 `Schema#execute` and `Query#result` both return a `GraphQL::Result`. It implements Hash-like methods to preserve compatibility. ### New features - `puts ctx.backtrace` prints out a GraphQL backtrace table #946 - `GraphQL::Backtrace.enable` wraps unhandled errors with GraphQL backtraces #946 - `GraphQL::Relay::ConnectionType.bidrectional_pagination = true` turns on _true_ bi-directional pagination checks for `hasNextPage`/`hasPreviousPage` fields. This will become the default behavior in a future version. #960 - Field arguments may be accessed as methods on the `args` object. This is an alternative to `#[]` syntax which provides did-you-mean behavior instead of returning `nil` on a typo. #924 For example: ```ruby # using hash syntax: args[:limit] # => 10 args[:limittt] # => nil # using method syntax: args.limit # => 10 args.limittt # => NoMethodError ``` The old syntax is _not_ deprecated. - Improvements to schema filters #919 - If a type is not referenced by anything, it's hidden - If a type is an abstract type, but has no visible members, it's hidden - `GraphQL::Argument.define` builds re-usable arguments #948 - `GraphQL::Subscriptions` provides hooks for subscription platforms #672 - `GraphQL::Subscriptions::ActionCableSubscriptions` implements subscriptions over ActionCable #672 - More runtime values are accessble from a `ctx` object #923 : - `ctx.parent` returns the `ctx` from the parent field - `ctx.object` returns the current `obj` for that field - `ctx.value` returns the resolved GraphQL value for that field These can be used together, for example, `ctx.parent.object` to get the parent object. - `GraphQL::Tracing` provides more hooks into gem internals for performance monitoring #917 - `GraphQL::Result` provides access to the original `query` and `context` after executing a query #898 ### Bug fixes - Prevent passing _both_ query string and parsed document to `Schema#execute` #957 - Prevent invalid names for types #947 ## 1.6.8 (8 Sept 2017) ### Breaking changes - Validate against EnumType value names to match `/^[_a-zA-Z][_a-zA-Z0-9]*$/` #915 ### New features - Use stdlib `forwardable` when it's not Ruby 2.4.0 #926 - Improve `UnresolvedTypeError` message #928 - Add a default field to the Rails generated mutation type #922 ### Bug fixes - Find types via directive arguments when traversing the schema #944 - Assign `#connection?` when building a schema from IDL #941 - Initialize `@edge_class` to `nil` #942 - Disallow invalid enum values #915 - Disallow doubly-nested non-null types #916 - Fix `Query#selected_operation_name` when no selections are present #899 - Fix needless `COUNT` query for `hasNextPage` #906 - Fix negative offset with `last` argument #907 - Fix line/col for `ArgumentsAreDefined` validation #890 - Fix Sequel error when limit is `0` #892 ## 1.6.7 (11 Aug 2017) ### New features - Add `GraphQL.parse_file` and `AbstractNode#filename` #873 - Support `.graphql` filepaths with `Schema.from_definition` #872 ### Bug fixes - Fix variable usage inside non-null list #888 - Fix unqualified usage of ActiveRecord::Relation #885 - Fix `FieldsWillMerge` handling of equivalent input objects - Fix to call `prepare:` on nested input types ## 1.6.6 (14 Jul 2017) ### New features - Validate `graphql-pro` downloads with `rake graphql:pro:validate[$VERSION]` #846 ### Bug fixes - Remove usage of Rails-only `Array.wrap` #840 - Fix `RelationConnection` to count properly when relation contains an alias #838 - Print name of Enum type when a duplicate value is added #843 ## 1.6.5 (13 Jul 2017) ### Breaking changes - `Schema#types[](type_name)` returns `nil` when there's no type named `type_name` (it used to raise `RuntimeError`). To get an error for missing types, use `.fetch` instead, for example: ```ruby # Old way: MySchema.types[type_name] # => may raise RuntimeError # New way: MySchema.types.fetch(type_name) # => may raise KeyError ``` - Schema build steps happen in one pass instead of two passes #819 . This means that `instrument(:field)` hooks may not access `Schema#types`, `Schema#possible_types` or `Schema#get_field`, since the underlying data hasn't been prepared yet. There's not really a clear upgrade path here. It's a bit of a mess. If you're affected by this, feel free to open an issue and we'll try to find something that works! ### Deprecations - `Schema#resolve_type` is now called with `(abstract_type, obj, ctx)` instead of `(obj, ctx)` #834 . To update, add an unused parameter to the beginning of your `resolve_type` hook: ```ruby MySchema = GraphQL::Schema.define do # Old way: resolve_type ->(obj, ctx) { ... } # New way: resolve_type ->(type, obj, ctx) { ... } end ``` ### New features - `rails g graphql:mutation` will add Mutation boilerplate if it wasn't added already #812 - `InterfaceType` and `UnionType` both accept `resolve_type ->(obj, ctx) { ... }` functions for type-specific resolution. This function takes precedence over `Schema#resolve_type` #829 #834 - `Schema#resolve_type` is called with three arguments, `(abstract_type, obj, ctx)`, so you can distinguish object type based on interface or union. - `Query#operation_name=` may be assigned during query instrumentation #833 - `query.context.add_error(err)` may be used to add query-level errors #833 ### Bug fixes - `argument(...)` DSL accepts custom keywords #809 - Use single-query `max_complexity` overrides #812 - Return a client error when `InputObjectType` receives an array as input #803 - Properly handle raised errors in `prepare` functions #805 - Fix using `as` and `prepare` in `argument do ... end` blocks #817 - When types are added to the schema with `instrument(:field, ...)`, make sure they're in `Schema#types` #819 - Raise an error when duplicate `EnumValue` is created #831 - Properly resolve all query levels breadth-first when using `lazy_resolve` #835 - Fix tests to run on PostgresQL; Run CI on PostgresQL #814 - When no query string is present, return a client error instead of raising `ArgumentError` #833 - Properly validate lists containing variables #824 ## 1.6.4 (20 Jun 2017) ### New features - `Schema.to_definition` sorts fields and arguments alphabetically #775 - `validate: false` skips static validations in query execution #790 ### Bug fixes - `graphql:install` adds `operation_name: params[:operationName]` #786 - `graphql:install` skips `graphiql-rails` for API-only apps #772 - `SerialExecution` calls `.is_a?(Skip)` to avoid user-defined `#==` methods #794 - `prepare:` functions which return `ExecutionError` are properly handled when default values are present #801 ## 1.6.3 (7 Jun 2017) ### Bug fixes - Run multiplex instrumentation when running a single query with a legacy execution strategy #766 - Check _each_ strategy when looking for overridden execution strategy #765 - Correctly wrap `Method`s with BackwardsCompatibility #763 - Various performance improvements #764 - Don't call `#==(other)` on user-provided objects (use `.is_a?` instead) #761 - Support lazy object from custom connection `#edge_nodes` #762 - If a lazy field returns an invalid null, stop evaluating its siblings #767 ## 1.6.2 (2 Jun 2017) ### New features - `Schema.define { default_max_page_size(...) }` provides a Connection `max_page_size` when no other is provided #752 - `Schema#get_field(type, field)` accepts a string type name #756 - `Schema.define { rescue_from(...) }` accepts multiple error classes for the handler #758 ### Bug fixes - Use `*_execution_strategy` when executing a single query (doesn't support `Schema#multiplex`) #755 - Fix NameError when `ActiveRecord` isn't loaded #747 - Fix `Query#mutation?` etc to support lazily-loaded AST #754 ## 1.6.1 (28 May 2017) ### New Features - `Query#selected_operation_name` returns the operation to execute, even if it was inferred (not provided as `operation_name:`) #746 ### Bug fixes - Return `nil` from `Query#operation_name` if no `operation_name:` was provided #746 ## 1.6.0 (27 May 2017) ### Breaking changes - `InternalRepresentation::Node#return_type` will now return the wrapping type. Use `return_type.unwrap` to access the old value #704 - `instrument(:query, ...)` instrumenters are applied as a stack instead of a queue #735. If you depend on queue-based behavior, move your `before_query` and `after_query` hooks to separate instrumenters. - In a `Relay::Mutation`, Raising or returning a `GraphQL::Execution` will nullify the mutation field, not the field's children. #731 - `args.to_h` returns a slightly different hash #714 - keys are always `String`s - if an argument is aliased with `as:`, the alias is used as the key - `InternalRepresentation::Node#return_type` includes the original "wrapper" types (non-null or list types), call `.unwrap` to get the inner type #20 ```ruby # before irep_node.return_type # after irep_node.return_type.unwrap ``` ### Deprecations - Argument `prepare` functions which take one argument are deprecated #730 ```ruby # before argument :id, !types.ID, prepare: ->(val) { ... } # after argument :id, !types.ID, prepare: ->(val, ctx) { ... } ``` ### New features - `Schema#multiplex(queries)` runs multiple queries concurrently #691 - `GraphQL::RakeTask` supports dumping the schema to IDL or JSON #687 - Improved support for `Schema.from_definition` #699 : - Custom scalars are supported with `coerce_input` and `coerce_result` functions - `resolve_type` function will be used for abstract types - Default resolve behavior is to check `obj` for a method and call it with 0, 1, or 2 arguments. - `ctx.skip` may be returned from field resolve functions to exclude the field from the response entirely #688 - `instrument(:field, ..., after_built_ins: true)` to apply field instrumentation after Relay wrappers #740 - Argument `prepare` functions are invoked with `(val, ctx)` (previously, it was only `(val)`) #730 - `args.to_h` returns stringified, aliased arguments #714 - `ctx.namespace(:my_namespace)` provides namespaced key-value storage #689 - `GraphQL::Query` can be initialized without a query_string; it can be added after initialization #710 - Improved filter support #713 - `Schema.execute(only:, except:)` accept a callable _or_ an array of callables (multiple filters) - Filters can be added to a query via `Query#merge_filters(only:, except:)`. You can add a filter to every query by merging it in during query instrumentation. ### Bug fixes - Correctly apply cursors and `max_page_size` in `Relay::RelationConnection` and `Relay::ArrayConnection` #728 - Nullify a mutation field when it raises or returns an error #731 ## 1.5.14 (27 May 2017) ### New features - `UniqueWithinType` Relay ID generator supports `-` in the ID #742 - `assign_metadata_key` assigns `true` when the definition method is called without arguments #724 - Improved lexer performance #737 ### Bug fixes - Assign proper `parent` when a `connection` resolve returns a promise #736 ## 1.5.13 (11 May 2017) - Fix raising `ExecutionError` inside mutation resolve functions (it nullifies the field) #722 ## 1.5.12 (9 May 2017) - Fix returning `nil` from connection resolve functions (now they become `null`) #719 - Fix duplicate AST nodes when merging fragments #721 ## 1.5.11 (8 May 2017) ### New features - `Schema.from_definition` accepts a `parser:` option (to work around lack of schema parser in `graphql-libgraphqlparser`) #712 - `Query#internal_representation` exposes an `InternalRepresentation::Document` #701 - Update generator usage of `graphql-batch` #697 ### Bug fixes - Handle fragments with the same name as operations #706 - Fix type generator: ensure type name is camelized #718 - Fix `Query#operation_name` to return the operation name #707 - Fix pretty-print of non-null & list types #705 - Fix single input objects passed to list-type arguments #716 ## 1.5.10 (25 Apr 2017) ### New features - Support Rails 5.1 #693 - Fall back to `String#encode` for non-UTF-8/non-ASCII strings #676 ### Bug Fixes - Correctly apply `Relay::Mutation`'s `return_field ... property:` argument #692 - Handle Rails 5.1's `ActionController::Parameters` #693 ## 1.5.9 (19 Apr 2017) ### Bug Fixes - Include instrumentation-related changes in introspection result #681 ## 1.5.8 (18 Apr 2017) ### New features - Use Relay PageInfo descriptions from graphql-js #673 ### Bug Fixes - Allow fields with different arguments when fragments are included within inline fragments of non-overlapping types #680 - Run `lazy_resolve` instrumentation for `connection` fields #679 ## 1.5.7 (14 Apr 2017) ### Bug fixes - `InternalRepresentation::Node#definition` returns `nil` instead of raising NoMethodError for operation fields #675 - `Field#function` is properly populated for fields derived from `GraphQL::Function`s #674 ## 1.5.6 (9 Apr 2017) ## Breaking Changes - Returned strings which aren't encoded as UTF-8 or ASCII will raise `GraphQL::StringEncodingError` instead of becoming `nil` #661 To preserve the previous behavior, Implement `Schema#type_error` to return `nil` for this error, eg: ```ruby GraphQL::Schema.define do type_error ->(err, ctx) { case err # ... when GraphQL::StringEncodingError nil end } ``` - `coerce_non_null_input` and `validate_non_null_input` are private #667 ## Deprecations - One-argument `coerce_input` and `coerce_result` functions for custom scalars are deprecated. #667 Those functions now accept a second argument, `ctx`. ```ruby # From ->(val) { val.to_i } # To: ->(val, ctx) { val.to_i } ``` - Calling `coerce_result`, `coerce_input`, `valid_input?` or `validate_input` without a `ctx` is deprecated. #667 Use `coerce_isolated_result` `coerce_isolated_input`, `valid_isolated_input?`, `validate_input` to explicitly bypass `ctx`. ## New Features - Include `#types` in `GraphQL::Function` #654 - Accept `prepare:` function for arguments #646 - Scalar coerce functions receive `ctx` #667 ## Bug Fixes - Properly apply default values of `false` #658 - Fix application of argument options in `GraphQL::Relay::Mutation` #660 - Support concurrent-ruby `>1.0.0` #663 - Only raise schema validation errors on `#execute` to avoid messing with Rails constant loading #665 ## 1.5.5 (31 Mar 2017) ### Bug Fixes - Improve threadsafety of `lazy_resolve` cache, use `Concurrent::Map` if it's available #631 - Properly handle unexpeced input objects #638 - Handle errors during definition by preseriving the definition #632 - Fix `nil` input for nullable list types #637, #639 - Handle invalid schema IDL with a validation error #647 - Properly serialize input object default values #635 - Fix `as:` on mutation `input_field` #650 - Fix null propagation for `nil` members of non-null list types #649 ## 1.5.4 (22 Mar 2017) ### Breaking Changes - Stop supporting deprecated one-argument schema masks #616 ### Bug Fixes - Return a client error for unknown variable types when default value is provided or when directives are present #627 - Fix validation performance regression on nested abstract fragment conditions #622, #624 - Put back `InternalRepresentation::Node#parent` and fix it for fragment fields #621 - Ensure enum names are strings #619 ## 1.5.3 (20 Mar 2017) ### Bug Fixes - Fix infinite loop triggered by user input. #620 This query would cause an infinite loop: ```graphql query { ...frag } fragment frag on Query { __typename } fragment frag on Query { ...frag } ``` - Validate fragment name uniqueness #618 ## 1.5.2 (16 Mar 2017) ### Breaking Changes - Parse errors are no longer raised to the application. #607 Instead, they're returned to the client in the `"errors"` key. To preserve the previous behavior, you can implement `Schema#parse_error` to raise the error: ```ruby MySchema = GraphQL::Schema.define do # ... parse_error ->(err, ctx) { raise(err) } end ``` ### New Features - Add `graphq:enum` generator #611 - Parse errors are returned to the client instead of raised #607 ### Bug Fixes - Handle negative cursor pagination args as `0` #612 - Properly handle returned `GraphQL::ExecutionError`s from connection resolves #610 - Properly handle invalid nulls in lazy scalar fields #609 - Properly handle invalid input objects passed to enum arguments #604 - Fix introspection response of enum default values #605 - Allow `Schema.from_definition` default resolver hashes to have defaults #608 ## 1.5.1 (12 Mar 2017) ### Bug fixes - Fix rewrite performance regressions from 1.5.0 #599 - Remove unused `GraphQL::Execution::Lazy` initialization API #597 ## 1.5.0 (10 Mar 2017), yanked ### Breaking changes - _Only_ UTF-8-encoded strings will be returned by `String` fields. Strings with other encodings (or objects whose `#to_s` method returns a string with a different encoding) will return `nil` instead of that string. #517 To opt into the _previous_ behavior, you can modify `GraphQL::STRING_TYPE`: ```ruby # app/graphql/my_schema.rb # Restore previous string behavior: GraphQL::STRING_TYPE.coerce_result = ->(value) { value.to_s } MySchema = GraphQL::Schema.define { ... } ``` - Substantial changes to the internal query representation (#512, #536). Query analyzers may notice some changes: - Nodes skipped by directives are not visited - Nodes are always on object types, so `Node#owner_type` always returns an object type. (Interfaces and Unions are replaced with concrete object types which are valid in the current scope.) See [changes to `Analysis::QueryComplexity`](https://github.com/rmosolgo/graphql-ruby/compare/v1.4.5...v1.5.0#diff-8ff2cdf0fec46dfaab02363664d0d201) for an example migration. Here are some other specific changes: - Nodes are tracked on object types only, not interface or union types - Deprecated, buggy `Node#children` and `Node#path` were removed - Buggy `#included` was removed - Nodes excluded by directives are entirely absent from the rewritten tree - Internal `InternalRepresentation::Selection` was removed (no longer needed) - `Node#spreads` was replaced by `Node#ast_spreads` which returns a Set ### New features - `Schema#validate` returns a list of errors for a query string #513 - `implements ...` adds interfaces to object types _without_ inherit-by-default #548, #574 - `GraphQL::Relay::RangeAdd` for implementing `RANGE_ADD` mutations #587 - `use ...` definition method for plugins #565 - Rails generators #521, #580 - `GraphQL::Function` for reusable resolve behavior with arguments & return type #545 - Support for Ruby 2.4 #475 - Relay `node` & `nodes` field can be extended with a custom block #552 - Performance improvements: - Resolve fragments only once when validating #504 - Reuse `Arguments` objects #500 - Skip needless `FieldResult`s #482 - Remove overhead from `ensure_defined` #483 - Benchmark & Profile tasks for gem maintenance #520, #579 - Fetch `has_next_page` while fetching items in `RelationConnection` #556 - Merge selections on concrete object types ahead of time #512 - Support runnable schemas with `Schema.from_definition` #567, #584 ### Bug fixes - Support different arguments on non-overlapping typed fragments #512 - Don't include children of `@skip`ped nodes when parallel branches are not skipped #536 - Fix offset in ArrayConnection when it's larger than the array #571 - Add missing `frozen_string_literal` comments #589 ## 1.4.5 (6 Mar 2017) ### Bug Fixes - When an operation name is provided but no such operation is present, return an error (instead of executing the first operation) #563 - Require unique operation names #563 - Require selections on root type #563 - If a non-null field returns `null`, don't resolve any more sibling fields. #575 ## 1.4.4 (17 Feb 2017) ### New features - `Relay::Node.field` and `Relay::Node.plural_field` accept a custom `resolve:` argument #550 - `Relay::BaseConnection#context` provides access to the query context #537 - Allow re-assigning `Field#name` #541 - Support `return_interfaces` on `Relay::Mutation`s #533 - `BaseType#to_definition` stringifies the type to IDL #539 - `argument ... as:` can be used to alias an argument inside the resolve function #542 ### Bug fixes - Fix negative offset from cursors on PostgresQL #510 - Fix circular dependency issue on `.connection_type`s #535 - Better error when `Relay::Mutation.resolve` doesn't return a Hash ## 1.4.3 (8 Feb 2017) ### New features - `GraphQL::Relay::Node.plural_field` finds multiple nodes by UUID #525 ### Bug fixes - Properly handle errors from lazy mutation results #528 - Encode all parsed strings as UTF-8 #516 - Improve error messages #501 #519 ## 1.4.2 (23 Jan 2017) ### Bug fixes - Absent variables aren't present in `args` (_again_!) #494 - Ensure definitions were executed when accessing `Field#resolve_proc` #502 (This could have caused errors when multiple instrumenters modified the same field in the schema.) ## 1.4.1 (16 Jan 2017) ### Bug fixes - Absent variables aren't present in `args` #479 - Fix grouped ActiveRecord relation with `last` only #476 - `Schema#default_mask` & query `only:`/`except:` are combined, not overriden #485 - Root types can be hidden with dynamic filters #480 ## 1.4.0 (8 Jan 2017) ### Breaking changes ### Deprecations - One-argument schema filters are deprecated. Schema filters are now called with _two_ arguments, `(member, ctx)`. #463 To update, add a second argument to your schema filter. - The arity of middleware `#call` methods has changed. Instead of `next_middleware` being the last argument, it is passed as a block. To update, call `yield` to continue the middleware chain or use `&next_middleware` to capture `next_middleware` into a local variable. ```ruby # Previous: def call(*args, next_middleware) next_middleware.call end # Current def call(*args) yield end # Or def call(*args, &next_middleware) next_middleware.call end ``` ### New features - You can add a `nodes` field directly to a connection. #451 That way you can say `{ friends { nodes } }` instead of `{ freinds { edges { node } } }`. Either pass `nodes_field: true` when defining a custom connection type, for example: ```ruby FriendsConnectionType = FriendType.define_connection(nodes_field: true) ``` Or, set `GraphQL::Relay::ConnectionType.default_nodes_field = true` before defining your schema, for example: ```ruby GraphQL::Relay::ConnectionType.default_nodes_field = true MySchema = GraphQL::Schema.define { ... } ``` - Middleware performance was dramatically improved by reducing object allocations. #462 `next_middleware` is now passed as a block. In general, [`yield` is faster than calling a captured block](https://github.com/JuanitoFatas/fast-ruby#proccall-and-block-arguments-vs-yieldcode). - Improve error messages for wrongly-typed variable values #423 - Cache the value of `resolve_type` per object per query #462 - Pass `ctx` to schema filters #463 - Accept whitelist schema filters as `only:` #463 - Add `Schema#to_definition` which accepts `only:/except:` to filter the schema when printing #463 - Add `Schema#default_mask` as a default `except:` filter #463 - Add reflection methods to types #473 - `#introspection?` marks built-in introspection types - `#default_scalar?` marks built-in scalars - `#default_relay?` marks built-in Relay types - `#default_directive?` marks built-in directives ### Bug fixes - Fix ArrayConnection: gracefully handle out-of-bounds cursors #452 - Fix ArrayConnection & RelationConnection: properly handle `last` without `before` #362 ## 1.3.0 (8 Dec 2016) ### Deprecations - As per the spec, `__` prefix is reserved for built-in names only. This is currently deprecated and will be invalid in a future version. #427, #450 ### New features - `Schema#lazy_resolve` allows you to define handlers for a second pass of resolution #386 - `Field#lazy_resolve` can be instrumented to track lazy resolution #429 - `Schema#type_error` allows you to handle `InvalidNullError`s and `UnresolvedTypeErrors` in your own way #416 - `Schema#cursor_encoder` can be specified for transforming cursors from built-in Connection implementations #345 - Schema members `#dup` correctly: they shallowly copy their state into new instances #444 - `Query#provided_variables` is now public #430 ### Bug fixes - Schemas created from JSON or strings with custom scalars can validate queries (although they still can't check if inputs are valid for those custom scalars) #445 - Always use `quirks_mode: true` when serializing values (to support non-stdlib `JSON`s) #449 - Calling `#redefine` on a Schema member copies state outside of previous `#define` blocks (uses `#dup`) #444 ## 1.2.6 (1 Dec 2016) ### Bug fixes - Preserve connection behaviors after `redefine` #421 - Implement `respond_to_missing?` on `DefinedObjectProxy` (which is `self` inside `.define { ... }`) #414 ## 1.2.5 (22 Nov 2016) ### Breaking changes - `Visitor` received some breaking changes, though these are largely-private APIs (#401): - Global visitor hooks (`Visitor#enter` and `Visitor#leave`) have been removed - Returning `SKIP` from a visitor hook no longer skips sibling nodes ### New features - `Schema#instrument` may be called outside of `Schema.define` #399 - Validation: assert that directives on a node are unique #409 - `instrument(:query)` hooks are executed even if the query raises an error #412 ### Bug fixes - `Mutation#input_fields` should trigger lazy definition #392 - `ObjectType#connection` doesn't modify the provided `GraphQL::Field` #411 - `Mutation#resolve` may return a `GraphQL::ExecutionError` #405 - `Arguments` can handle nullable arguments passed as `nil` #410 ## 1.2.4 (14 Nov 2016) ### Bug fixes - For invalid enum values, print the enum name in the error message (not a Ruby object dump) #403 - Improve detection of invalid UTF-8 escapes #394 ## 1.2.3 (14 Nov 2016) ### Bug fixes - `Lexer` previous token should be a local variable, not a method attribute #396 - `Arguments` should wrap values according to their type, not their value #398 ## 1.2.2 (7 Nov 2016) ### New features - `Schema.execute` raises an error if `variables:` is a string ### Bug fixes - Dynamic fields `__schema`, `__type` and `__typename` are properly validated #391 ## 1.2.1 (7 Nov 2016) ### Bug fixes - Implement `Query::Context#strategy` and `FieldResolutionContext#strategy` to support GraphQL::Batch #382 ## 1.2.0 (7 Nov 2016) ### Breaking changes - A breaking change from 1.1.0 was reverted: two-character `"\\u"` _is_ longer treated as the Unicode escape character #372 - Due to the execution bug described below, the internal representation of a query has changed. Although `Node` responds to the same methods, tree is built differently and query analyzers visit it differently. #373, #379 The difference is in cases like this: ```graphql outer { ... on A { inner1 { inner2 } } ... on B { inner1 { inner3 } } } ``` Previously, visits would be: - `outer`, which has one child: - `inner1`, which has two definitions (one on `A`, another on `B`), then visit its two `children`: - `inner2` which has one definition (on the return type of `inner1`) - `inner3` which has one definition (on the return type of `inner1`) This can be wrong for some cases. For example, if `A` and `B` are mutually exclusive (both object types, or union types with no shared members), then `inner2` and `inner3` will never be executed together. Now, the visit goes like this: - `outer` which has two entries in `typed_children`, one on `A` and another on `B`. Visit each `typed_chidren` branch: - `inner1`, then its one `typed_children` branch: - `inner2` - `inner1`, then its one `typed_children` branch: - `inner3` As you can see, we visit `inner1` twice, once for each type condition. `inner2` and `inner3` are no longer visited as siblings. Instead they're visited as ... cousins? (They share a grandparent, not a parent.) Although `Node#children` is still present, it may not contain all children actually resolved at runtime, since multiple `typed_children` branches could apply to the same runtime type (eg, two branches on interface types can apply to the same object type). To track all children, you have to do some bookkeeping during visitation, see `QueryComplexity` for an example. You can see PR #373 for how built-in analyzers were changed to reflect this. ### Deprecations - `InternalRepresentation::Node#children` and `InternalRepresentation::Node#definitions` are deprecated due to the bug described below and the breaking change described above. Instead, use `InternalRepresentation::Node#typed_children` and `InternalRepresentation::Node#defininition`. #373 ### New features - `null` support for the whole library: as a query literal, variable value, and argument default value. To check for the presence of a nullable, use `Arguments#key?` #369 - `GraphQL::Schema::UniqueWithinType.default_id_separator` may be assigned to a custom value #381 - `Context#add_error(err)` may be used to add a `GraphQL::ExecutionError` to the response's `"errors"` key (and the resolve function can still return a value) #367 - The third argument of `resolve` is now a `FieldResolutionContext`, which behaves just like a `Query::Context`, except that it is not modified during query execution. This means you can capture a reference to that context and access some field-level details after the fact: `#path`, `#ast_node`, `#irep_node`. (Other methods are delegated to the underlying `Query::Context`) #379 - `TimeoutMiddleware`'s second argument is a _proxied_ query object: it's `#context` method returns the `FieldResolutionContext` (see above) for the timed-out field. Other methods are delegated to the underlying `Query` #379 ### Bug fixes - Fix deep selection merging on divergently-typed fragments. #370, #373, #379 Previously, nested selections on different fragments were not distinguished. Consider a case like this: ```graphql ... on A { inner1 { inner2 } } ... on B { inner1 { inner3 } } ``` Previously, an object of type `A` would resolve `inner1`, then the result would receive _both_ `inner2` and `inner3`. The same was true for an object of type `B`. Now, those are properly distinguished. An object of type `A` resolves `inner1`, then its result receives `inner2`. An object of type `B` receives `inner1`, then `inner3`. ## 1.1.0 (1 Nov 2016) ### Breaking changes - Two-character `"\\u"` is no longer treated as the Unicode escape character, only the Unicode escape character `"\u"` is treated that way. (This behavior was a bug, the migration path is to use the Unicode escape character.) #366 - `GraphQL::Language::ParserTests` was removed, use `GraphQL::Compatibility` instead. #366 - Non-null arguments can't be defined with default values, because those values would never be used #361 ### New features - `Schema.from_definition(definition_string)` builds a `GraphQL::Schema` out of a schema definition. #346 - Schema members (types, fields, arguments, enum values) can be hidden on a per-query basis with the `except:` option #300 - `GraphQL::Compatibility` contains `.build_suite` functions for testing user-provided parsers and execution strategies with GraphQL internals. #366 - Schema members respond to `#redefine { ... }` for making shallow copies with extended definitions. #357 - `Schema#instrument` provides an avenue for observing query and field resolution with no overhead. - Some `SerialExecution` objects were converted to functions, resulting in a modest performance improvement for query resolution. ### Bug fixes - `NonNullType` and `ListType` have no name (`nil`), as per the spec #355 - Non-null arguments can't be defined with default values, because those values would never be used #361 ## 1.0.0 (25 Oct 2016) ### Breaking changes - `validate: false` option removed from `Schema.execute` (it didn't work anyways) #338 - Some deprecated methods were removed: #349 - `BaseConnection#object` was removed, use `BaseConnection#nodes` - `BaseConnection.connection_for_items` was removed, use `BaseConnection#connection_for_nodes` - Two-argument resolve functions for `Relay::Mutation`s are not supported, use three arguments instead: `(root_obj, input, ctx)` - `Schema.new` no longer accepts initialization options, use `Schema.define` instead - `GraphQL::ObjectType::UnresolvedTypeError` was removed, use `GraphQL::UnresolvedTypeError` instead - Fragment type conditions should be parsed as `TypeName` nodes, not strings. (Users of `graphql-libgraphqlparser` should update to `1.0.0` of that gem.) #342 ### New Features - Set `ast_node` and `irep_node` on query context before sending it to middleware #348 - Enum values can be extended with `.define` #341 ### Bug Fixes - Use `RelationConnection` for Rails 3 relations (which also extend `Array`) #343 - Fix schema printout when arguments have comments #335 ## 0.19.4 (18 Oct 2016) ### Breaking changes - `Relay::BaseConnection#order` was removed (it always returned `nil`) #313 - In the IDL, Interface names & Union members are parsed as `TypeName` nodes instead of Strings #322 ### New features - Print and parse descriptions in the IDL #305 - Schema roots from IDL are omitted when their names match convention #320 - Don't add `rescue_middleware` to a schema if it's not using `rescue_from` #328 - `Query::Arguments#each_value` yields `Query::Argument::ArgumentValue` instances which contain key, value and argument definition #331 ### Bug fixes - Use `JSON.generate(val, quirks_mode: true)` for compatibility with other JSON implementations #316 - Improvements for compatibility with 1.9.3 branch #315 #314 #313 - Raise a descriptive error when calculating a `cursor` for a node which isn't present in the connection's members #327 ## 0.19.3 (13 Oct 2016) ### Breaking Changes - `GraphQL::Query::Arguments.new` requires `argument_definitions:` of type `{String => GraphQL::Argument }` #304 ### Deprecations - `Relay::Mutation#resolve` has a new signature. #301 Previously, it was called with two arguments: ```ruby resolve ->(inputs, ctx) { ... } ``` Now, it's called with three inputs: ```ruby resolve ->(obj, inputs, ctx) { ... } ``` `obj` is the value of `root_value:` given to `Schema#execute`, as with other root-level fields. Two-argument resolvers are still supported, but they are deprecated and will be removed in a future version. ### New features - `Relay::Mutation` accepts a user-defined `return_type` #310 - `Relay::Mutation#resolve` receives the `root_value` passed to `Schema#execute` #301 - Derived `Relay` objects have descriptions #303 ### Bug fixes - Introspection query is 7 levels deep instead of 3 #308 - Unknown variable types cause validation errors, not runtime errors #310 - `Query::Arguments` doesn't wrap hashes from parsed scalars (fix for user-defined "JSONScalar") #304 ## 0.19.2 (6 Oct 2016) ### New features - If a list entry has a `GraphQL::ExecutionError`, replace the entry with `nil` and return the error #295 ### Bug fixes - Support graphql-batch rescuing `InvalidNullError`s #296 - Schema printer prints Enum names, not Ruby values for enums #297 ## 0.19.1 (4 Oct 2016) ### Breaking changes - Previously-deprecated `InterfaceType#resolve_type` hook has been removed, use `Schema#resolve_type` instead #290 ### New features - Eager-load schemas at definition time, validating types & schema-level hooks #289 - `InvalidNullError`s contain the type & field name that returned null #293 - If an object is resolved with `Schema#resolve_type` and the resulting type is not a member of the expected possible types, raise an error #291 ### Bug fixes - Allow `directive` as field or argument name #288 ## 0.19.0 (30 Sep 2016) ### Breaking changes - `GraphQL::Relay::GlobalNodeIdentification` was removed. Its features were moved to `GraphQL::Schema` or `GraphQL::Relay::Node`. The new hooks support more robust & flexible global IDs. #243 - Relay's `"Node"` interface and `node(id: "...")` field were both moved to `GraphQL::Relay::Node`. To use them in your schema, call `.field` and `.interface`. For example: ```ruby # Adding a Relay-compliant `node` field: field :node, GraphQL::Relay::Node.field ``` ```ruby # This object type implements Relay's `Node` interface: interfaces [GraphQL::Relay::Node.interface] ``` - UUID hooks were renamed and moved to `GraphQL::Schema`. You should define `id_from_object` and `object_from_id` in your `Schema.define { ... }` block. For example: ```ruby MySchema = GraphQL::Schema.define do # Fetch an object by UUID object_from_id ->(id, ctx) { MyApp::RelayLookup.find(id) } # Generate a UUID for this object id_from_object ->(obj, type_defn, ctx) { MyApp::RelayLookup.to_id(obj) } end ``` - The new hooks have no default implementation. To use the previous default, use `GraphQL::Schema::UniqueWithinType`, for example: ```ruby MySchema = GraphQL::Schema.define do object_from_id ->(id, ctx) { # Break the id into its parts: type_name, object_id = GraphQL::Schema::UniqueWithinType.decode(id) # Fetch the identified object # ... } id_from_object ->(obj, type_defn, ctx) { # Provide the type name & the object's `id`: GraphQL::Schema::UniqueWithinType.encode(type_defn.name, obj.id) } end ``` If you were using a custom `id_separator`, it's now accepted as an input to `UniqueWithinType`'s methods, as `separator:`. For example: ```ruby # use "---" as a ID separator GraphQL::Schema::UniqueWithinType.encode(type_name, object_id, separator: "---") GraphQL::Schema::UniqueWithinType.decode(relay_id, separator: "---") ``` - `type_from_object` was previously deprecated and has been replaced by `Schema#resolve_type`. You should define this hook in your schema to return a type definition for a given object: ```ruby MySchema = GraphQL::Schema.define do # ... resolve_type ->(obj, ctx) { # based on `obj` and `ctx`, # figure out which GraphQL type to use # and return the type } end ``` - `Schema#node_identification` has been removed. - `Argument` default values have been changed to be consistent with `InputObjectType` default values. #267 Previously, arguments expected GraphQL values as `default_value`s. Now, they expect application values. (`InputObjectType`s always worked this way.) Consider an enum like this one, where custom values are provided: ```ruby PowerStateEnum = GraphQL::EnumType.define do name "PowerState" value("ON", value: 1) value("OFF", value: 0) end ``` __Previously__, enum _names_ were provided as default values, for example: ```ruby field :setPowerState, PowerStateEnum do # Previously, the string name went here: argument :newValue, default_value: "ON" end ``` __Now__, enum _values_ are provided as default values, for example: ```ruby field :setPowerState, PowerStateEnum do # Now, use the application value as `default_value`: argument :newValue, default_value: 1 end ``` Note that if you __don't have custom values__, then there's no change, because the name and value are the same. Here are types that are affected by this change: - Custom scalars (previously, the `default_value` was a string, now it should be the application value, eg `Date` or `BigDecimal`) - Enums with custom `value:`s (previously, the `default_value` was the name, now it's the value) If you can't replace `default_value`s, you can also use a type's `#coerce_input` method to translate a GraphQL value into an application value. For example: ```ruby # Using a custom scalar, "Date" # PREVIOUSLY, provide a string: argument :starts_on, DateType, default_value: "2016-01-01" # NOW, transform the string into a Date: argument :starts_on, DateType, default_value: DateType.coerce_input("2016-01-01") ``` ### New features - Support `@deprecated` in the Schema language #275 - Support `directive` definitions in the Schema language #280 - Use the same introspection field descriptions as `graphql-js` #284 ### Bug fixes - Operation name is no longer present in execution error `"path"` values #276 - Default values are correctly dumped & reloaded in the Schema language #267 ## 0.18.15 (20 Sep 2016) ### Breaking changes - Validation errors no longer have a `"path"` key in their JSON. It was renamed to `"fields"` #264 - `@skip` and `@include` over multiple selections are handled according to the spec: if the same field is selected multiple times and _one or more_ of them would be included, the field will be present in the response. Previously, if _one or more_ of them would be skipped, it was absent from the response. #256 ### New features - Execution errors include a `"path"` key which points to the field in the response where the error occurred. #259 - Parsing directives from the Schema language is now supported #273 ### Bug fixes - `@skip` and `@include` over multiple selections are now handled according to the spec #256 ## 0.18.14 (20 Sep 2016) ### Breaking changes - Directives are no longer considered as "conflicts" in query validation. This is in conformity with the spec, but a change for graphql-ruby #263 ### Features - Query analyzers may emit errors by raising `GraphQL::AnalysisError`s during `#call` or returning a single error or an array of errors from `#final_value` #262 ### Bug fixes - Merge fields even when `@skip` / `@include` are not identical #263 - Fix possible infinite loop in `FieldsWillMerge` validation #261 ## 0.18.13 (19 Sep 2016) ### Bug fixes - Find infinite loops in nested contexts, too #258 ## 0.18.12 (19 Sep 2016) ### New features - `GraphQL::Analysis::FieldUsage` can be used to check for deprecated fields in the query analysis phase #245 ### Bug fixes - If a schema receives a query on `mutation` or `subscription` but that root doesn't exist, return a validation error #254 - `Query::Arguments#to_h` only includes keys that were provided in the query or have a default value #251 ## 0.18.11 (11 Sep 2016) ### New features - `GraphQL::Language::Nodes::Document#slice(operation_name)` finds that operation and its dependencies and puts them in a new `Document` #241 ### Bug fixes - Validation errors for non-existent fields have the location of the field usage, not the parent field #247 - Properly `require "forwardable"` #242 - Remove `ALLOWED_CONSTANTS` for boolean input, use a plain comparison #240 ## 0.18.10 (9 Sep 2016) ### New features - Assign `#mutation` on objects which are derived from a `Relay::Mutation` #239 ## 0.18.9 (6 Sep 2016) ### Bug fixes - fix backward compatibility for `type_from_object` #238 ## 0.18.8 (6 Sep 2016) ### New features - AST nodes now respond to `#eql?(other)` to test value equality #231 ### Bug fixes - The `connection` helper no longer adds a duplicate field #235 ## 0.18.7 (6 Sep 2016) ### New features - Support parsing nameless fragments (but not executing them) #232 ### Bug fixes - Allow `__type(name: "Whatever")` to return null, as per the spec #233 - Include a Relay mutation's description with a mutation field #225 ## 0.18.6 (29 Aug 2016) ### New features - ` GraphQL::Schema::Loader.load(schema_json)` turns an introspection result into a `GraphQL::Schema` #207 - `.define` accepts plural definitions for: object fields, interface fields field arguments, enum values #222 ## 0.18.5 (27 Aug 2016) ### Deprecations - `Schema.new` is deprecated; use `Schema.define` instead. Before: ```ruby schema = GraphQL::Schema.new( query: QueryType, mutation: MutationType, max_complexity: 100, types: [ExtraType, OtherType] ) schema.node_identification = MyGlobalID schema.rescue_from(ActiveRecord::RecordNotFound) { |err| "..." } ``` After: ```ruby schema = GraphQL::Schema.define do query QueryType mutation MutationType max_complexity 100 node_identification MyGlobalID rescue_from(ActiveRecord::RecordNotFound) { |err| "..." } # Types was renamed to `orphan_types` to avoid conflict with the `types` helper orphan_types [ExtraType, OtherType] end ``` This unifies the disparate methods of configuring a schema and provides new, more flexible design space. It also adds `#metadata` to schemas for user-defined storage. - `UnionType#resolve_type`, `InterfaceType#resolve_type`, and `GlobalNodeIdentification#type_from_object` are deprecated, unify them into `Schema#resolve_type` instead. Before: ```ruby GraphQL::Relay::GlobalNodeIdentification.define do type_from_object ->(obj) { ... } end GraphQL::InterfaceType.define do resolve_type ->(obj, ctx) { ... } end ``` After: ```ruby GraphQL::Schema.define do resolve_type ->(obj, ctx) { ... } end ``` This simplifies type inference and prevents unexpected behavior when different parts of the schema resolve types differently. ### New features - Include expected type in Argument errors #221 - Define schemas with `Schema.define` #208 - Define a global object-to-type function with `Schema#resolve_type` #216 ### Bug fixes ## 0.18.4 (25 Aug 2016) ### New features - `InvalidNullError`s expose a proper `#message` #217 ### Bug fixes - Return an empty result for queries with no operations #219 ## 0.18.3 (22 Aug 2016) ### Bug fixes - `Connection.new(:field)` is optional, not required #215 - 0.18.2 introduced a more restrictive approach to resolving interfaces & unions; revert that approach #212 ## 0.18.2 (17 Aug 2016) ### New features - Connection objects expose the `GraphQL::Field` that created them via `Connection#field` #206 ## 0.18.1 (7 Aug 2016) ### Deprecations - Unify `Relay` naming around `nodes` as the items of a connection: - `Relay::BaseConnection.connection_for_nodes` replaces `Relay::BaseConnection.connection_for_items` - `Relay::BaseConnection#nodes` replaces `Relay::BaseConnection#object` ### New features - Connection fields' `.resolve_proc` is an instance of `Relay::ConnectionResolve` #204 - Types, fields and arguments can store arbitrary values in their `metadata` hashes #203 ## 0.18.0 (4 Aug 2016) ### Breaking changes - `graphql-relay` has been merged with `graphql`, you should remove `graphql-relay` from your gemfile. #195 ### Deprecations ### New features - `GraphQL.parse` can turn schema definitions into a `GraphQL::Language::Nodes::Document`. The document can be stringified again with `Document#to_query_string` #191 - Validation errors include a `path` to the part of the query where the error was found #198 - `.define` also accepts keywords for each helper method, eg `GraphQL::ObjectType.define(name: "PostType", ...)` ### Bug fixes - `global_id_field`s have default complexity of 1, not `nil` - Relay `pageInfo` is correct for connections limited by `max_page_size` - Rescue invalid variable errors & missing operation name errors during query analysis ## 0.17.2 (26 Jul 2016) ### Bug fixes - Correctly spread fragments when nested inside other fragments #194 ## 0.17.1 (26 Jul 2016) ### Bug fixes - Fix `InternalRepresentation::Node#inspect` ## 0.17.0 (21 Jul 2016) ### Breaking changes - `InternalRepresentation::Node` API changes: - `#definition_name` returns the field name on field nodes (while `#name` may have an alias) - `#definitions` returns `{type => field}` pairs for possible fields on this node - `#definition` is gone, it is equivalent to `node.definitions.values.first` - `#on_types` is gone, it is equivalent to `node.definitions.keys` ### New features - Accept `hash_key:` field option - Call `.define { }` block lazily, so `-> { }` is not needed for circular references #182 ### Bug fixes - Support `on` as an Enum value - If the same field is requested on multiple types, choose the maximum complexity among them (not the first) ## 0.16.1 (20 Jul 2016) ### Bug fixes - Fix merging fragments on Union types (see #190, broken from #180) ## 0.16.0 (14 Jul 2016) ### Breaking changes & deprecations - I don't _know_ that this breaks anything, but `GraphQL::Query::SerialExecution` now iterates over a tree of `GraphQL::InternalRepresentation::Node`s instead of an AST (`GraphQL::Language::Nodes::Document`). ### New features - Query context keys can be assigned with `Context#[]=` #178 - Cancel further field resolution with `TimeoutMiddleware` #179 - Add `GraphQL::InternalRepresentation` for normalizing queries from AST #180 - Analyze the query before running it #180 - Assign complexity cost to fields, enforce max complexity before running it #180 - Log max complexity or max depth with `MaxComplexity` or `MaxDepth` analyzers #180 - Query context exposes `#irep_node`, the internal representation of the current node #180 ### Bug fixes - Non-null errors are propagated to the next nullable field, all the way up to `data` #174 ## 0.15.3 (28 Jun 2016) ### New features - `EnumValue`s can receive their properties after instantiation #171 ## 0.15.2 (16 Jun 2016) ### New features - Support lazy type arguments in Object's `interfaces` and Union's `possible_types` #169 ### Bug fixes - Support single-member Unions, as per the spec #170 ## 0.15.1 (15 Jun 2016) ### Bug fixes - Whitelist operation types in `lexer.rb` ## 0.15.0 (11 Jun 2016) ### Breaking changes & deprecations - Remove `debug:` option, propagate all errors. #161 ## 0.14.1 (11 Jun 2016) ### Breaking changes & deprecations - `debug:` is deprecated (#165). Propagating errors (`debug: true`) will become the default behavior. You can get a similar implementation of error gobbling with `CatchallMiddleware`. Add it to your schema: ```ruby MySchema.middleware << GraphQL::Schema::CatchallMiddleware ``` ### New features ### Bug fixes - Restore previous introspection fields on DirectiveType as deprecated #164 - Apply coercion to input default values #162 - Proper Enum behavior when a value isn't found ## 0.14.0 (31 May 2016) ### Breaking changes & deprecations ### New features - `GraphQL::Language::Nodes::Document#to_query_string` will re-serialize a query AST #151 - Accept `root_value:` when running a query #157 - Accept a `GraphQL::Language::Nodes::Document` to `Query.new` (this allows you to cache parsed queries on the server) #152 ### Bug fixes - Improved parse error messages #149 - Improved build-time validation #150 - Raise a meaningful error when a Union or Interface can't be resolved during query execution #155 ## 0.13.0 (29 Apr 2016) ### Breaking changes & deprecations - "Dangling" object types are not loaded into the schema. The must be passed in `GraphQL::Schema.new(types: [...])`. (This was deprecated in 0.12.1) ### New features - Update directive introspection to new spec #121 - Improved schema validation errors #113 - 20x faster parsing #119 - Support inline fragments without type condition #123 - Support multiple schemas composed of the same types #142 - Accept argument `description` and `default_value` in the block #138 - Middlewares can send _new_ arguments to subsequent middlewares #129 ### Bug fixes - Don't leak details of internal errors #120 - Default query `context` to `{}` #133 - Fixed list nullability validation #131 - Ensure field names are strings #128 - Fix `@skip` and `@include` implementation #124 - Interface membership is not shared between schemas #142 ## 0.12.1 (26 Apr 2016) ### Breaking changes & deprecations - __Connecting object types to the schema _only_ via interfaces is deprecated.__ It will be unsupported in the next version of `graphql`. Sometimes, object type is only connected to the Query (or Mutation) root by being a member of an interface. In these cases, bugs happen, especially with Rails development mode. (And sometimes, the bugs don't appear until you deploy to a production environment!) So, in a case like this: ```ruby HatInterface = GraphQL::ObjectType.define do # ... end FezType = GraphQL::ObjectType.define do # ... interfaces [HatInterface] end QueryType = GraphQL::ObjectType.define do field :randomHat, HatInterface # ... end ``` `FezType` can only be discovered by `QueryType` _through_ `HatInterface`. If `fez_type.rb` hasn't been loaded by Rails, `HatInterface.possible_types` will be empty! Now, `FezType` must be passed to the schema explicitly: ```ruby Schema.new( # ... types: [FezType] ) ``` Since the type is passed directly to the schema, it will be loaded right away! ### New features ### Bug fixes ## 0.12.0 (20 Mar 2016) ### Breaking changes & deprecations - `GraphQL::DefinitionConfig` was replaced by `GraphQL::Define` #116 - Many scalar types are more picky about which inputs they allow (#115). To get the previous behavior, add this to your program: ```ruby # Previous coerce behavior for scalars: GraphQL::BOOLEAN_TYPE.coerce = ->(value) { !!value } GraphQL::ID_TYPE.coerce = ->(value) { value.to_s } GraphQL::STRING_TYPE.coerce = ->(value) { value.to_s } # INT_TYPE and FLOAT_TYPE were unchanged ``` - `GraphQL::Field`s can't be renamed because `#resolve` may depend on that name. (This was only a problem if you pass the _same_ `GraphQL::Field` instance to `field ... field:` definitions.) - `GraphQL::Query::DEFAULT_RESOLVE` was removed. `GraphQL::Field#resolve` handles that behavior. ### New features - Can override `max_depth:` from `Schema#execute` - Base `GraphQL::Error` for all graphql-related errors ### Bug fixes - Include `""` for String default values (so it's encoded as a GraphQL string literal) ## 0.11.1 (6 Mar 2016) ### New features - Schema `max_depth:` option #110 - Improved validation errors for input objects #104 - Interfaces provide field implementations to object types #108 ## 0.11.0 (28 Feb 2016) ### Breaking changes & deprecations - `GraphQL::Query::BaseExecution` was removed, you should probably extend `SerialExecution` instead #96 - `GraphQL::Language::Nodes` members no longer raise if they don't get inputs during `initialize` #92 - `GraphQL.parse` no longer accepts `as:` for parsing partial queries. #92 ### New features - `Field#property` & `Field#property=` can be used to access & modify the method that will be sent to the underlying object when resolving a field #88 - When defining a field, you can pass a string for as `type`. It will be looked up in the global namespace. - `Query::Arguments#to_h` unwraps `Arguments` objects recursively - If you raise `GraphQL::ExecutionError` during field resolution, it will be rescued and the message will be added to the response's `errors` key. #93 - Raise an error when non-null fields are `nil` #94 ### Bug fixes - Accept Rails params as input objects - Don't get a runtime error when input contains unknown key #100 ## 0.10.9 (15 Jan 2016) ### Bug fixes - Handle re-assignment of `ObjectType#interfaces` #84 - Fix merging queries on interface-typed fields #85 ## 0.10.8 (14 Jan 2016) ### Bug fixes - Fix transform of nested lists #79 - Fix parse & transform of escaped characters #83 ## 0.10.7 (22 Dec 2015) ### New features - Support Rubinius ### Bug fixes - Coerce values into one-item lists for ListTypes ## 0.10.6 (20 Dec 2015) ### Bug fixes - Remove leftover `puts`es ## 0.10.5 (19 Dec 2015) ### Bug fixes - Accept enum value description in definition #71 - Correctly parse empty input objects #75 - Correctly parse arguments preceded by newline - Find undefined input object keys during static validation ## 0.10.4 (24 Nov 2015) ### New features - Add `Arguments#to_h` #66 ### Bug fixes - Accept argument description in definition - Correctly parse empty lists ## 0.10.3 (11 Nov 2015) ### New features - Support root-level `subscription` type ### Bug fixes - Require Set for Schema::Printer ## 0.10.2 (10 Nov 2015) ### Bug fixes - Handle blank strings in queries - Raise if a field is configured without a proper type #61 ## 0.10.1 (22 Oct 2015) ### Bug fixes - Properly merge fields on fragments within fragments - Properly delegate enumerable-ish methods on `Arguments` #56 - Fix & refactor literal coersion & validation #53 ## 0.10.0 (17 Oct 2015) ### New features - Scalars can have distinct `coerce_input` and `coerce_result` methods #48 - Operations don't require a name #54 ### Bug fixes - Big refactors and fixes to variables and arguments: - Correctly apply argument default values - Correctly apply variable default values - Raise at execution-time if non-null variables are missing - Incoming values are coerced to their proper types before execution ## 0.9.5 (1 Oct 2015) ### New features - Add `Schema#middleware` to wrap field access - Add `RescueMiddleware` to handle errors during field execution - Add `Schema::Printer` for printing the schema definition #45 ### Bug fixes ## 0.9.4 (22 Sept 2015) ### New features - Fields can return `GraphQL::ExecutionError`s to add errors to the response ### Bug fixes - Fix resolution of union types in some queries #41 ## 0.9.3 (15 Sept 2015) ### New features - Add `Schema#execute` shorthand for running queries - Merge identical fields in fragments so they're only resolved once #34 - An error during parsing raises `GraphQL::ParseError` #33 ### Bug fixes - Find nested input types in `TypeReducer` #35 - Find variable usages inside fragments during static validation ## 0.9.2, 0.9.1 (10 Sept 2015) ### Bug fixes - remove Celluloid dependency ## 0.9.0 (10 Sept 2015) ### Breaking changes & deprecations - remove `GraphQL::Query::ParallelExecution` (use [`graphql-parallel`](https://github.com/rmosolgo/graphql-parallel)) ## 0.8.1 (10 Sept 2015) ### Breaking changes & deprecations - `GraphQL::Query::ParallelExecution` has been extracted to [`graphql-parallel`](https://github.com/rmosolgo/graphql-parallel) ## 0.8.0 (4 Sept 2015) ### New features - Async field resolution with `context.async { ... }` - Access AST node during resolve with `context.ast_node` ### Bug fixes - Fix for validating arguments returning up too soon - Raise if you try to define 2 types with the same name - Raise if you try to get a type by name but it doesn't exist ## 0.7.1 (27 Aug 2015) ### Bug fixes - Merge nested results from different fragments instead of using the latest one only ## 0.7.0 (26 Aug 2015) ### Breaking changes & deprecations - Query keyword argument `params:` was removed, use `variables:` instead. ### Bug fixes - `@skip` has precedence over `@include` - Handle when `DEFAULT_RESOVE` returns nil ## 0.6.2 (20 Aug 2015) ### Bug fixes - Fix whitespace parsing in input objects ## 0.6.1 (16 Aug 2015) ### New features - Parse UTF-8 characters & escaped characters ### Bug fixes - Properly parse empty strings - Fix argument / variable compatibility validation ## 0.6.0 (14 Aug 2015) ### Breaking changes & deprecations - Deprecate `params` option to `Query#new` in favor of `variables` - Deprecated `.new { |obj, types, fields, args| }` API was removed (use `.define`) ### New features - `Query#new` accepts `operation_name` argument - `InterfaceType` and `UnionType` accept `resolve_type` configs ### Bug fixes - Gracefully handle blank-string & whitespace-only queries - Handle lists in variable definitions and arguments - Handle non-null input types ## 0.5.0 (12 Aug 2015) ### Breaking changes & deprecations - Deprecate definition API that yielded a bunch of helpers #18 ### New features - Add new definition API #18 graphql-ruby-2.2.17/CNAME000066400000000000000000000000211476434635200147750ustar00rootroot00000000000000graphql-ruby.org graphql-ruby-2.2.17/Gemfile000066400000000000000000000011341476434635200155300ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" gemspec gem 'bootsnap' # required by the Rails apps generated in tests gem 'stackprof', platform: :ruby gem 'pry' gem 'pry-stack_explorer', platform: :ruby gem 'pry-byebug' # For Ruby 3.2 compat: gem "yard", github: "lsegal/yard", ref: "b51bf26" if RUBY_VERSION >= "3.0" gem "libev_scheduler" gem "evt" end if RUBY_VERSION >= "3.1.1" gem "async", "~>2.0" end # Required for running `jekyll algolia ...` (via `rake site:update_search_index`) group :jekyll_plugins do gem 'jekyll-algolia', '~> 1.0' gem 'jekyll-redirect-from' end graphql-ruby-2.2.17/MIT-LICENSE000066400000000000000000000020361476434635200156730ustar00rootroot00000000000000Copyright 2015 Robert Mosolgo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. graphql-ruby-2.2.17/Rakefile000066400000000000000000000131361476434635200157070ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_helper" Bundler::GemHelper.install_tasks require "rake/testtask" require_relative "guides/_tasks/site" require_relative "lib/graphql/rake_task/validate" require 'rake/extensiontask' Rake::TestTask.new do |t| t.libs << "spec" << "lib" << "graphql-c_parser/lib" exclude_integrations = [] ['Mongoid', 'Rails'].each do |integration| begin Object.const_get(integration) rescue NameError exclude_integrations << integration.downcase end end t.test_files = Dir['spec/**/*_spec.rb'].reject do |f| next unless f.start_with?("spec/integration/") excluded = exclude_integrations.any? do |integration| f.start_with?("spec/integration/#{integration}/") end puts "+ #{f}" unless excluded excluded end t.warning = false end require 'rubocop/rake_task' RuboCop::RakeTask.new default_tasks = [:test, :rubocop] if ENV["SYSTEM_TESTS"] task(default: ["test:system"] + default_tasks) else task(default: default_tasks) end def assert_dependency_version(dep_name, required_version, check_script) version = `#{check_script}` if !version.include?(required_version) raise <<-ERR build_parser requires #{dep_name} version "#{required_version}", but found: $ #{check_script} > #{version} To fix this issue: - Update #{dep_name} to the required version - Update the assertion in `Rakefile` to match the current version ERR end end namespace :bench do def prepare_benchmark $LOAD_PATH << "./lib" << "./spec/support" require_relative("./benchmark/run.rb") end desc "Benchmark parsing" task :parse do prepare_benchmark GraphQLBenchmark.run("parse") end desc "Benchmark lexical analysis" task :scan do prepare_benchmark GraphQLBenchmark.run("scan") end desc "Benchmark the introspection query" task :query do prepare_benchmark GraphQLBenchmark.run("query") end desc "Benchmark validation of several queries" task :validate do prepare_benchmark GraphQLBenchmark.run("validate") end desc "Profile a validation" task :validate_memory do prepare_benchmark GraphQLBenchmark.validate_memory end desc "Generate a profile of the introspection query" task :profile do prepare_benchmark GraphQLBenchmark.profile end desc "Run benchmarks on a very large result" task :profile_large_result do prepare_benchmark GraphQLBenchmark.profile_large_result end desc "Run benchmarks on a small result" task :profile_small_result do prepare_benchmark GraphQLBenchmark.profile_small_result end desc "Run introspection on a small schema" task :profile_small_introspection do prepare_benchmark GraphQLBenchmark.profile_small_introspection end desc "Dump schema to SDL" task :profile_to_definition do prepare_benchmark GraphQLBenchmark.profile_to_definition end desc "Compare GraphQL-Batch and GraphQL-Dataloader" task :profile_batch_loaders do prepare_benchmark GraphQLBenchmark.profile_batch_loaders end desc "Run benchmarks on schema creation" task :profile_boot do prepare_benchmark GraphQLBenchmark.profile_boot end desc "Check the memory footprint of a large schema" task :profile_schema_memory_footprint do prepare_benchmark GraphQLBenchmark.profile_schema_memory_footprint end desc "Check the depth of the stacktrace during execution" task :profile_stack_depth do prepare_benchmark GraphQLBenchmark.profile_stack_depth end desc "Run a very big introspection query" task :profile_large_introspection do prepare_benchmark GraphQLBenchmark.profile_large_introspection end task :profile_small_query_on_large_schema do prepare_benchmark GraphQLBenchmark.profile_small_query_on_large_schema end desc "Run analysis on a big query" task :profile_large_analysis do prepare_benchmark GraphQLBenchmark.profile_large_analysis end desc "Run analysis on parsing" task :profile_parse do prepare_benchmark GraphQLBenchmark.profile_parse end end namespace :test do desc "Run system tests for ActionCable subscriptions" task :system do success = Dir.chdir("spec/dummy") do system("bundle install") system("bundle exec bin/rails test:system") end success || abort end task js: "js:test" end namespace :js do client_dir = "./javascript_client" desc "Run the tests for javascript_client" task :test do success = Dir.chdir(client_dir) do system("yarn run test") end success || abort end desc "Install JS dependencies" task :install do Dir.chdir(client_dir) do system("yarn install") end end desc "Compile TypeScript to JavaScript" task :build do Dir.chdir(client_dir) do system("yarn tsc") end end task all: [:install, :build, :test] end task :build_c_lexer do assert_dependency_version("Ragel", "7.0.4", "ragel -v") `ragel -F1 graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl` end Rake::ExtensionTask.new("graphql_c_parser_ext") do |t| t.ext_dir = 'graphql-c_parser/ext/graphql_c_parser_ext' t.lib_dir = "graphql-c_parser/lib/graphql" end task :build_yacc_parser do assert_dependency_version("Bison", "3.8", "yacc --version") `yacc graphql-c_parser/ext/graphql_c_parser_ext/parser.y -o graphql-c_parser/ext/graphql_c_parser_ext/parser.c -Wyacc` end task :move_binary do # For some reason my local env doesn't respect the `lib_dir` configured above `mv graphql-c_parser/lib/*.bundle graphql-c_parser/lib/graphql` end desc "Build the C Extension" task build_ext: [:build_c_lexer, :build_yacc_parser, "compile:graphql_c_parser_ext", :move_binary] graphql-ruby-2.2.17/benchmark/000077500000000000000000000000001476434635200161705ustar00rootroot00000000000000graphql-ruby-2.2.17/benchmark/abstract_fragments.graphql000066400000000000000000000006741476434635200234300ustar00rootroot00000000000000query AbstractFragments { node(id: "1") { ...Frag1 } } fragment Frag1 on Commentable { id __typename ...Frag2 } fragment Frag2 on Commentable { id __typename ...Frag3 } fragment Frag3 on Commentable { id __typename ...Frag4 } fragment Frag4 on Commentable { id __typename ...Frag5 } fragment Frag5 on Commentable { id __typename ...Frag6 } fragment Frag6 on Commentable { comments { body } } graphql-ruby-2.2.17/benchmark/abstract_fragments_2.graphql000066400000000000000000000012301476434635200236360ustar00rootroot00000000000000query AbstractFragments { node(id: "1") { ...Frag1 } } fragment Frag1 on Commentable { id __typename ...Frag9 ...Frag2 } fragment Frag2 on Commentable { id __typename ...Frag9 ...Frag3 } fragment Frag3 on Commentable { id __typename ...Frag9 ...Frag4 } fragment Frag4 on Commentable { id __typename ...Frag9 ...Frag5 } fragment Frag5 on Commentable { id __typename ...Frag9 ...Frag6 } fragment Frag6 on Commentable { ...Frag7 ...Frag9 name id comments { ...Frag8 ...Frag7 id } } fragment Frag7 on Node { id } fragment Frag8 on Comment { body } fragment Frag9 on Named { name } graphql-ruby-2.2.17/benchmark/batch_loading.rb000066400000000000000000000064321476434635200213000ustar00rootroot00000000000000# frozen_string_literal: true module BatchLoading class GraphQLBatchSchema < GraphQL::Schema DATA = [ { id: "1", name: "Bulls", player_ids: ["2", "3"] }, { id: "2", name: "Michael Jordan", team_id: "1" }, { id: "3", name: "Scottie Pippin", team_id: "1" }, { id: "4", name: "Braves", player_ids: ["5", "6"] }, { id: "5", name: "Chipper Jones", team_id: "4" }, { id: "6", name: "Tom Glavine", team_id: "4" }, ] class DataLoader < GraphQL::Batch::Loader def initialize(column: :id) @column = column end def perform(keys) keys.each do |key| record = DATA.find { |d| d[@column] == key } fulfill(key, record) end end end class Team < GraphQL::Schema::Object field :name, String, null: false field :players, "[BatchLoading::GraphQLBatchSchema::Player]", null: false def players DataLoader.load_many(object[:player_ids]) end end class Player < GraphQL::Schema::Object field :name, String, null: false field :team, Team, null: false def team DataLoader.load(object[:team_id]) end end class Query < GraphQL::Schema::Object field :team, Team do argument :name, String end def team(name:) DataLoader.for(column: :name).load(name) end end query(Query) use GraphQL::Batch end class GraphQLDataloaderSchema < GraphQL::Schema class DataSource < GraphQL::Dataloader::Source def initialize(options = {column: :id}) @column = options[:column] end def fetch(keys) keys.map { |key| d = GraphQLBatchSchema::DATA.find { |d| d[@column] == key } # p [key, @column, d] d } end end class Team < GraphQL::Schema::Object field :name, String, null: false field :players, "[BatchLoading::GraphQLDataloaderSchema::Player]", null: false def players dataloader.with(DataSource).load_all(object[:player_ids]) end end class Player < GraphQL::Schema::Object field :name, String, null: false field :team, Team, null: false def team dataloader.with(DataSource).load(object[:team_id]) end end class Query < GraphQL::Schema::Object field :team, Team do argument :name, String end def team(name:) dataloader.with(DataSource, column: :name).load(name) end end query(Query) use GraphQL::Dataloader end class GraphQLNoBatchingSchema < GraphQL::Schema DATA = GraphQLBatchSchema::DATA class Team < GraphQL::Schema::Object field :name, String, null: false field :players, "[BatchLoading::GraphQLNoBatchingSchema::Player]", null: false def players object[:player_ids].map { |id| DATA.find { |d| d[:id] == id } } end end class Player < GraphQL::Schema::Object field :name, String, null: false field :team, Team, null: false def team DATA.find { |d| d[:id] == object[:team_id] } end end class Query < GraphQL::Schema::Object field :team, Team do argument :name, String end def team(name:) DATA.find { |d| d[:name] == name } end end query(Query) end end graphql-ruby-2.2.17/benchmark/big_query.graphql000066400000000000000000000155611476434635200215460ustar00rootroot00000000000000query Anc_inbox_layout($first_0:Int!,$first_5:Int!,$first_6:Int!,$first_a:Int!,$first_c:Int!,$order_by_1:ReportOrderInput!,$substate_2:ReportStateEnum!,$pre_submission_review_states_3:[ReportPreSubmissionReviewStateEnum]!,$size_4:ProfilePictureSizes!,$size_9:ProfilePictureSizes!,$not_types_7:[ActivityTypes]!,$order_by_8:ActivityOrderInput!,$order_by_b:TeamOrderInput!) { query { id, ...FE } } fragment F0 on User { username, _profile_picturePkPpF:profile_picture(size:$size_4), impact, reputation, signal, id } fragment F1 on Report { reporter { username, _profile_picturePkPpF:profile_picture(size:$size_4), impact, reputation, signal, _duplicate_users2Nhzxe:duplicate_users(first:$first_5) { pageInfo { hasNextPage, hasPreviousPage }, total_count, edges { node { id, ...F0 }, cursor } }, id }, id } fragment F2 on Report { id } fragment F3 on Node { id, __typename } fragment F4 on ReportActivityInterface { automated_response, genius_execution_id, report { team { handle, id }, id }, __typename, ...F3 } fragment F5 on Team { url, internet_bug_bounty, _profile_pictureihzmG:profile_picture(size:$size_9), name, id } fragment F6 on User { username, url, _profile_pictureihzmG:profile_picture(size:$size_9), id } fragment F7 on ActivitiesBugDuplicate { original_report_id, id } fragment F8 on ActivitiesReferenceIdAdded { reference, reference_url, id } fragment F9 on ActivitiesCveIdAdded { cve_ids, id } fragment Fa on ActivitiesAgreedOnGoingPublic { first_to_agree, id } fragment Fb on ActivitiesBugCloned { original_report_id, id } fragment Fc on ActivitiesUserAssignedToBug { assigned_user { url, username, id }, id } fragment Fd on ActivitiesGroupAssignedToBug { assigned_group { name, id }, id } fragment Fe on ActivitiesExternalUserInvited { email, id } fragment Ff on ActivitiesExternalUserInvitationCancelled { email, id } fragment Fg on ActivitiesExternalUserRemoved { removed_user { id }, id } fragment Fh on ActivitiesUserBannedFromProgram { removed_user { id }, id } fragment Fi on ActivitiesBountyAwarded { bounty_amount, bounty_currency, bonus_amount, report { reporter { username, url, id }, id }, id } fragment Fj on ActivitiesBountySuggested { bounty_amount, bounty_currency, bonus_amount, id } fragment Fk on ActivitiesBugResolved { report { reporter { username, url, id }, id }, id } fragment Fl on ActivitiesSwagAwarded { report { reporter { username, url, id }, id }, id } fragment Fm on ActivitiesChangedScope { old_scope { asset_identifier, id }, new_scope { asset_identifier, id }, id } fragment Fn on ActivityInterface { _id, internal, i_can_edit, __typename, message, markdown_message, created_at, updated_at, actor { __typename, ...F5, ...F6, ...F3 }, attachments { _id, file_name, content_type, expiring_url, id }, ...F7, ...F8, ...F9, ...Fa, ...Fb, ...Fc, ...Fd, ...Fe, ...Ff, ...Fg, ...Fh, ...Fi, ...Fj, ...Fk, ...Fl, ...Fm, ...F3 } fragment Fo on User { username, url, __typename, id } fragment Fp on TeamMemberGroup { name, __typename, id } fragment Fq on Report { _id, url, title, state, substate, created_at, assignee { __typename, ...Fo, ...Fp, ...F3 }, cloned_from { _id, id }, reporter { username, url, id }, team { _id, url, handle, name, twitter_handle, website, about, offers_bounties, id }, id } fragment Fr on Report { state, stage, disclosed_at, cve_ids, singular_disclosure_disabled, disclosed_at, bug_reporter_agreed_on_going_public_at, team_member_agreed_on_going_public_at, comments_closed, mediation_requested_at, vulnerability_information, vulnerability_information_html, reporter { disabled, username, url, _profile_picture2g6hJa:profile_picture(size:$size_4), id }, weakness { id, name }, original_report { id, url }, attachments { _id, file_name, expiring_url, content_type, id }, allow_singular_disclosure_at, allow_singular_disclosure_after, singular_disclosure_allowed, severity { rating, score, author_type, id }, structured_scope { _id, asset_type, asset_identifier, max_severity, id }, _activities4z6spP:activities(first:$first_6,not_types:$not_types_7,order_by:$order_by_8) { edges { node { __typename, ...F4, ...Fn, ...F3 }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id, ...Fq } fragment Fs on Report { id, ...Fr } fragment Ft on Report { title, id } fragment Fu on Report { _id, pre_submission_review_state, i_can_anc_review, reporter { username, id }, team { handle, id }, id, ...F2 } fragment Fv on Report { team { policy_html, id }, structured_scope { asset_identifier, asset_type, instruction, id }, id } fragment Fw on Report { weakness { name, id }, id } fragment Fx on Report { severity { rating, score, id }, id } fragment Fy on Report { latest_activity_at, created_at, id, ...Fq } fragment Fz on Query { me { username, _teamsWbVmT:teams(order_by:$order_by_b,first:$first_c) { edges { node { name, handle, id }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id }, id } fragment FA on Query { _reports1t04lE:reports(page:$first_0,first:$first_a,limit:$first_a,order_by:$order_by_1,substate:$substate_2,pre_submission_review_states:$pre_submission_review_states_3) { total_count, edges { node { _id, id, ...Fy }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id, ...Fz } fragment FB on Query { id, ...Fz } fragment FC on Query { id } fragment FD on Query { me { username, _profile_pictureihzmG:profile_picture(size:$size_9), id }, id, ...FC } fragment FE on Query { _reports3QQXft:reports(first:$first_0,order_by:$order_by_1,substate:$substate_2,pre_submission_review_states:$pre_submission_review_states_3) { edges { node { id, ...F1, ...F2, ...Fs, ...Ft, ...Fu, ...Fv, ...Fw, ...Fx }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id, ...FA, ...FB, ...FD } graphql-ruby-2.2.17/benchmark/big_schema.graphql000066400000000000000000003372231476434635200216430ustar00rootroot00000000000000# Autogenerated input type of AcceptInvitation input AcceptInvitationInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! } # Autogenerated return type of AcceptInvitation type AcceptInvitationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String me: User } # Autogenerated input type of AcknowledgeProgramHealthAcknowledgementMutation input AcknowledgeProgramHealthAcknowledgementMutationInput { # A unique identifier for the client performing the mutation. clientMutationId: String program_health_acknowledgement_id: ID! } # Autogenerated return type of AcknowledgeProgramHealthAcknowledgementMutation type AcknowledgeProgramHealthAcknowledgementMutationPayload { acknowledged_program_health_acknowledgement_id: String! # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # A Activities::AgreedOnGoingPublic activity for a report type ActivitiesAgreedOnGoingPublic implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! first_to_agree: Boolean! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BountyAwarded activity for a report type ActivitiesBountyAwarded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean bonus_amount: String bounty_amount: String bounty_currency: String created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BountySuggested activity for a report type ActivitiesBountySuggested implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean bonus_amount: String bounty_amount: String bounty_currency: String created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugCloned activity for a report type ActivitiesBugCloned implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String original_report: Report original_report_id: Int @deprecated(reason: "Deprecated in favor of .original_report") report: Report updated_at: String! } # A Activities::BugDuplicate activity for a report type ActivitiesBugDuplicate implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String original_report: Report original_report_id: Int @deprecated(reason: "Deprecated in favor of .original_report") report: Report updated_at: String! } # A Activities::BugFiled activity for a report type ActivitiesBugFiled implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugInformative activity for a report type ActivitiesBugInformative implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugNeedsMoreInfo activity for a report type ActivitiesBugNeedsMoreInfo implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugNew activity for a report type ActivitiesBugNew implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugNotApplicable activity for a report type ActivitiesBugNotApplicable implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugReopened activity for a report type ActivitiesBugReopened implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugResolved activity for a report type ActivitiesBugResolved implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugSpam activity for a report type ActivitiesBugSpam implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugTriaged activity for a report type ActivitiesBugTriaged implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ChangedScope activity for a report type ActivitiesChangedScope implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String new_scope: StructuredScope old_scope: StructuredScope report: Report updated_at: String! } # A Activities::Comment activity for a report type ActivitiesComment implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::CommentsClosed activity for a report type ActivitiesCommentsClosed implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::CveIdAdded activity for a report type ActivitiesCveIdAdded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! cve_ids: [String] genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalAdvisoryAdded activity for a report type ActivitiesExternalAdvisoryAdded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserInvitationCancelled activity for a report type ActivitiesExternalUserInvitationCancelled implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! email: String genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserInvited activity for a report type ActivitiesExternalUserInvited implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! email: String genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserJoined activity for a report type ActivitiesExternalUserJoined implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! duplicate_report: Report duplicate_report_id: Int @deprecated(reason: "Deprecated in favor of .duplicate_report") genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserRemoved activity for a report type ActivitiesExternalUserRemoved implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String removed_user: User! report: Report updated_at: String! } # A Activities::GroupAssignedToBug activity for a report type ActivitiesGroupAssignedToBug implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! assigned_group: TeamMemberGroup attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") group: TeamMemberGroup @deprecated(reason: "deprecated in favor of assigned group") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::HackerRequestedMediation activity for a report type ActivitiesHackerRequestedMediation implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ManuallyDisclosed activity for a report type ActivitiesManuallyDisclosed implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::MediationRequested activity for a report type ActivitiesMediationRequested implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # An Activities::NobodyAssignedToBug activity for a report type ActivitiesNobodyAssignedToBug implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::NotEligibleForBounty activity for a report type ActivitiesNotEligibleForBounty implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReassignedToTeam activity for a report type ActivitiesReassignedToTeam implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReferenceIdAdded activity for a report type ActivitiesReferenceIdAdded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String reference: String reference_url: String report: Report updated_at: String! } # A Activities::ReportBecamePublic activity for a report type ActivitiesReportBecamePublic implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportCollaboratorInvited activity for a report type ActivitiesReportCollaboratorInvited implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportCollaboratorJoined activity for a report type ActivitiesReportCollaboratorJoined implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportSeverityUpdated activity for a report type ActivitiesReportSeverityUpdated implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportTitleUpdated activity for a report type ActivitiesReportTitleUpdated implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String new_title: String! old_title: String! report: Report updated_at: String! } # A Activities::ReportVulnerabilityTypesUpdated activity for a report type ActivitiesReportVulnerabilityTypesUpdated implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String new_weakness: Weakness old_weakness: Weakness report: Report updated_at: String! } # A Activities::SwagAwarded activity for a report type ActivitiesSwagAwarded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report swag: Swag! updated_at: String! } # A Activities::TeamPublished activity for a team type ActivitiesTeamPublished implements ActivityInterface, Node { _id: ID! actor: ActorUnion! attachments: [Attachment] created_at: String! i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String updated_at: String! } # A Activities::UserAssignedToBug activity for a report type ActivitiesUserAssignedToBug implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! assigned_user: User! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::UserBannedFromProgram activity for a report type ActivitiesUserBannedFromProgram implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String removed_user: User! report: Report updated_at: String! } # A Activities::UserJoined activity for a user type ActivitiesUserJoined implements ActivityInterface, Node { _id: ID! actor: ActorUnion! attachments: [Attachment] created_at: String! i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String updated_at: String! } # The connection type for ActivityUnion. type ActivityConnection { # A list of edges. edges: [ActivityUnionEdge] max_updated_at: String # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # A interface for the common fields on an HackerOne Activity interface ActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] created_at: String! i_can_edit: Boolean! internal: Boolean markdown_message: String message: String updated_at: String! } # Fields on which a collection of activities can be ordered enum ActivityOrderField { created_at updated_at } input ActivityOrderInput { direction: OrderDirection! field: ActivityOrderField! } # Possible types for an activity enum ActivityTypes { AgreedOnGoingPublic BountyAwarded BountySuggested BugCloned BugDuplicate BugFiled BugInformative BugNeedsMoreInfo BugNew BugNotApplicable BugReopened BugResolved BugSpam BugTriaged ChangedScope Comment CommentsClosed CveIdAdded ExternalAdvisoryAdded ExternalUserInvitationCancelled ExternalUserInvited ExternalUserJoined ExternalUserRemoved GroupAssignedToBug HackerRequestedMediation ManuallyDisclosed MediationRequested NobodyAssignedToBug NotEligibleForBounty ReassignedToTeam ReferenceIdAdded ReportBecamePublic ReportCollaboratorInvited ReportCollaboratorJoined ReportSeverityUpdated ReportTitleUpdated ReportVulnerabilityTypesUpdated SwagAwarded TeamPublished UserAssignedToBug UserBannedFromProgram UserJoined } # Activities can be of multiple types union ActivityUnion = ActivitiesAgreedOnGoingPublic | ActivitiesBountyAwarded | ActivitiesBountySuggested | ActivitiesBugCloned | ActivitiesBugDuplicate | ActivitiesBugFiled | ActivitiesBugInformative | ActivitiesBugNeedsMoreInfo | ActivitiesBugNew | ActivitiesBugNotApplicable | ActivitiesBugReopened | ActivitiesBugResolved | ActivitiesBugSpam | ActivitiesBugTriaged | ActivitiesChangedScope | ActivitiesComment | ActivitiesCommentsClosed | ActivitiesCveIdAdded | ActivitiesExternalAdvisoryAdded | ActivitiesExternalUserInvitationCancelled | ActivitiesExternalUserInvited | ActivitiesExternalUserJoined | ActivitiesExternalUserRemoved | ActivitiesGroupAssignedToBug | ActivitiesHackerRequestedMediation | ActivitiesManuallyDisclosed | ActivitiesMediationRequested | ActivitiesNobodyAssignedToBug | ActivitiesNotEligibleForBounty | ActivitiesReassignedToTeam | ActivitiesReferenceIdAdded | ActivitiesReportBecamePublic | ActivitiesReportCollaboratorInvited | ActivitiesReportCollaboratorJoined | ActivitiesReportSeverityUpdated | ActivitiesReportTitleUpdated | ActivitiesReportVulnerabilityTypesUpdated | ActivitiesSwagAwarded | ActivitiesTeamPublished | ActivitiesUserAssignedToBug | ActivitiesUserBannedFromProgram | ActivitiesUserJoined # The connection type for ActivityUnion. type ActivityUnionConnection { # A list of edges. edges: [ActivityUnionEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type ActivityUnionEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: ActivityUnion } # The actor of an activity can be multiple types union ActorUnion = Team | User # A HackerOne user's address used for submitting swag type Address implements Node { _id: ID! city: String! country: String! created_at: String! id: ID! name: String! phone_number: String postal_code: String! state: String! street: String! tshirt_size: TshirtSizeEnum @deprecated(reason: "Query tshirt size on User instead") } # Autogenerated input type of ArchiveStructuredScope input ArchiveStructuredScopeInput { # A unique identifier for the client performing the mutation. clientMutationId: String structured_scope_id: ID! } # Autogenerated return type of ArchiveStructuredScope type ArchiveStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String query: Query structured_scope: StructuredScope } # Report can be assigned to either a user or a team member group union AssigneeUnion = TeamMemberGroup | User # A HackerOne attachment for a report type Attachment implements Node { _id: ID! content_type: String! created_at: String! expiring_url: String! file_name: String! file_size: Int! id: ID! } # Types of authentication methods for users enum AuthenticationServiceEnum { database saml token } # A HackerOne badge type Badge implements Node { _id: ID! description: String! id: ID! image_path: String! name: String! } # Represents a badge earned by a user type BadgesUsers implements Node { _id: ID! badge: Badge! created_at: String! id: ID! user: User! } # The connection type for BadgesUsers. type BadgesUsersConnection { # A list of edges. edges: [BadgesUsersEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type BadgesUsersEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: BadgesUsers } # Resources for setting up the Bank Transfer payment method type BankTransferReference implements Node { beneficiary_required_details(currency: String!, bank_account_country: String!, beneficiary_country: String!): BeneficiaryRequiredDetail countries: [Country] currencies: [Currency] id: ID! } # A specification of information needed to create a bank transfer payment preference type BeneficiaryRequiredDetail implements Node { bank_account_country: String! beneficiary_country: String! beneficiary_required_details( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BeneficiaryRequiredDetailsConnection currency: String! id: ID! } input BeneficiaryRequiredDetailInput { field: String! value: String! } # A specification of the possibilities for creating a bank transfer payout preference type BeneficiaryRequiredDetails implements Node { beneficiary_entity_type: String! beneficiary_required_fields( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BeneficiaryRequiredFieldConnection description: String! fee: String! id: ID! payment_type: String! } # The connection type for BeneficiaryRequiredDetails. type BeneficiaryRequiredDetailsConnection { # A list of edges. edges: [BeneficiaryRequiredDetailsEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type BeneficiaryRequiredDetailsEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: BeneficiaryRequiredDetails } # A specification of the format of a field used to create a bank transfer payout preference type BeneficiaryRequiredField implements Node { description: String! field: String! id: ID! regex: String! } # The connection type for BeneficiaryRequiredField. type BeneficiaryRequiredFieldConnection { # A list of edges. edges: [BeneficiaryRequiredFieldEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type BeneficiaryRequiredFieldEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: BeneficiaryRequiredField } # A HackerOne bounty for a report type Bounty implements Node { _id: ID! amount: String! awarded_amount: String! awarded_bonus_amount: String! awarded_currency: String! bonus_amount: String! created_at: String! id: ID! report: Report! status: BountyStatusEnum! } # The connection type for Bounty. type BountyConnection { # A list of edges. edges: [BountyEdge] # Information to aid in pagination. pageInfo: PageInfo! total_amount: Float! total_count: Int! } # An edge in a connection. type BountyEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Bounty } # Status which reflect the state of a bounty enum BountyStatusEnum { cancelled confirmed failed failed_ofac_check needs_payout_method needs_tax_form no_mileage_account no_status no_tax_form ofac_reject pending pending_ofac_check rejected sent } # A subset of weaknesses that share a common characteristic type Cluster implements Node { created_at: String! description: String id: ID! name: String! updated_at: String! weaknesses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: WeaknessOrder = {direction: ASC, field: name} search: String = null team_handle: String = null team_weakness_state: [TeamWeaknessStates] = [enabled, disabled, hidden] ): ClusterWeaknessConnection } # The connection type for Cluster. type ClusterConnection { # A list of edges. edges: [ClusterEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ClusterEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Cluster } input ClusterOrder { direction: OrderDirection! field: ClusterOrderField! } # Fields on which a collection of Cluster can be ordered enum ClusterOrderField { BROWSING_FRIENDLY } # The connection type for Weakness. type ClusterWeaknessConnection { # A list of edges. edges: [ClusterWeaknessEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ClusterWeaknessEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Weakness team_weakness(team_handle: String!): TeamWeakness } # A Coinbase Payout Preference type CoinbasePayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean email: String id: ID! } # A common response type CommonResponse implements Node { # The primary key from the database _id: ID! created_at: String! id: ID! message: String! team: Team! title: String! updated_at: String! } # The connection type for CommonResponse. type CommonResponseConnection { # A list of edges. edges: [CommonResponseEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type CommonResponseEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: CommonResponse } input CommonResponseOrder { direction: OrderDirection! field: CommonResponseOrderField! } # Fields on which a collection of common responses can be ordered enum CommonResponseOrderField { title } # Will only return values for valid SeverityRatingEnum and null. scalar CountBySeverity # A country as specified in ISO 3166 type Country implements Node { alpha2: String! currency_code: String id: ID! name: String! } # Autogenerated input type of CreateActivityComment input CreateActivityCommentInput { # A unique identifier for the client performing the mutation. clientMutationId: String internal: Boolean! message: String! report_id: ID! } # Autogenerated return type of CreateActivityComment type CreateActivityCommentPayload { activities: ActivityUnionConnection activity: ActivitiesComment # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash new_activity_edge: ActivityUnionEdge report: Report } # Autogenerated input type of CreateBounty input CreateBountyInput { amount: Float bonus_amount: Float # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of CreateBounty type CreateBountyPayload { bounty: Bounty! # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated input type of CreateBountySuggestion input CreateBountySuggestionInput { amount: Float bonus_amount: Float # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of CreateBountySuggestion type CreateBountySuggestionPayload { activity: ActivitiesBountySuggested! # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated input type of CreateCoinbasePayoutPreference input CreateCoinbasePayoutPreferenceInput { # A unique identifier for the client performing the mutation. clientMutationId: String coinbase_email: String! default_method: Boolean! } # Autogenerated return type of CreateCoinbasePayoutPreference type CreateCoinbasePayoutPreferencePayload { # A unique identifier for the client performing the mutation. clientMutationId: String me: User } # Autogenerated input type of CreateCurrencycloudBankTransferPayoutPreference input CreateCurrencycloudBankTransferPayoutPreferenceInput { bank_account_country: String! bank_account_holder_name: String! beneficiary_entity_type: CurrencycloudBankTransferEntityType! beneficiary_required_details: [BeneficiaryRequiredDetailInput]! # A unique identifier for the client performing the mutation. clientMutationId: String currency: String! default_method: Boolean! payment_type: CurrencycloudBankTransferPaymentType! } # Autogenerated return type of CreateCurrencycloudBankTransferPayoutPreference type CreateCurrencycloudBankTransferPayoutPreferencePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of CreateJiraOauthUrl input CreateJiraOauthUrlInput { # A unique identifier for the client performing the mutation. clientMutationId: String site: String! team_id: ID! } # Autogenerated return type of CreateJiraOauthUrl type CreateJiraOauthUrlPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team url: String } # Autogenerated input type of CreateJiraWebhookToken input CreateJiraWebhookTokenInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of CreateJiraWebhookToken type CreateJiraWebhookTokenPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team! webhook_url: String! } # Autogenerated input type of CreateMailingAddress input CreateMailingAddressInput { city: String! # A unique identifier for the client performing the mutation. clientMutationId: String country: String! name: String! phone_number: String! postal_code: String! state: String! street: String! } # Autogenerated return type of CreateMailingAddress type CreateMailingAddressPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of CreateOrUpdateHackeroneToJiraEventsConfiguration input CreateOrUpdateHackeroneToJiraEventsConfigurationInput { assignee_changes: Boolean # A unique identifier for the client performing the mutation. clientMutationId: String comments: Boolean public_disclosures: Boolean rewards: Boolean state_changes: Boolean team_id: ID! } # Autogenerated return type of CreateOrUpdateHackeroneToJiraEventsConfiguration type CreateOrUpdateHackeroneToJiraEventsConfigurationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team! } # Autogenerated input type of CreateOrUpdateJiraIntegration input CreateOrUpdateJiraIntegrationInput { assignee: String base_url: String! # A unique identifier for the client performing the mutation. clientMutationId: String custom: String description: String! generate_webhook_in_jira_if_does_not_exist: Boolean issue_type: Int! labels: String pid: Int! summary: String! team_id: ID! } # Autogenerated return type of CreateOrUpdateJiraIntegration type CreateOrUpdateJiraIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of CreatePaypalPreference input CreatePaypalPreferenceInput { # A unique identifier for the client performing the mutation. clientMutationId: String default_method: Boolean! paypal_email: String! } # Autogenerated return type of CreatePaypalPreference type CreatePaypalPreferencePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of CreateSlackPipeline input CreateSlackPipelineInput { channel: String! # A unique identifier for the client performing the mutation. clientMutationId: String descriptive_label: String notification_report_agreed_on_going_public: Boolean! notification_report_assignee_changed: Boolean! notification_report_became_public: Boolean! notification_report_bounty_paid: Boolean! notification_report_bounty_suggested: Boolean! notification_report_bug_closed_as_spam: Boolean! notification_report_bug_duplicate: Boolean! notification_report_bug_informative: Boolean! notification_report_bug_needs_more_info: Boolean! notification_report_bug_new: Boolean! notification_report_bug_not_applicable: Boolean! notification_report_closed_as_resolved: Boolean! notification_report_comments_closed: Boolean! notification_report_created: Boolean! notification_report_internal_comment_added: Boolean! notification_report_manually_disclosed: Boolean! notification_report_not_eligible_for_bounty: Boolean! notification_report_public_comment_added: Boolean! notification_report_reopened: Boolean! notification_report_swag_awarded: Boolean! notification_report_triaged: Boolean! team_id: ID! } # Autogenerated return type of CreateSlackPipeline type CreateSlackPipelinePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash new_slack_pipeline_edge: SlackPipelineEdge slack_pipelines_connection: SlackPipelineConnection team: Team! } # Autogenerated input type of CreateStructuredScope input CreateStructuredScopeInput { asset_identifier: String! asset_type: StructuredScopedAssetTypeEnum availability_requirement: String # A unique identifier for the client performing the mutation. clientMutationId: String confidentiality_requirement: String eligible_for_bounty: Boolean eligible_for_submission: Boolean instruction: String integrity_requirement: String reference: String team_id: ID! } # Autogenerated return type of CreateStructuredScope type CreateStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of CreateSurveyAnswer input CreateSurveyAnswerInput { # A unique identifier for the client performing the mutation. clientMutationId: String feedback: String invitation_token: String structured_response_ids: [ID]! } # Autogenerated return type of CreateSurveyAnswer type CreateSurveyAnswerPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of CreateSwag input CreateSwagInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of CreateSwag type CreateSwagPayload { # A unique identifier for the client performing the mutation. clientMutationId: String swag: Swag! } # Autogenerated input type of CreateTaxForm input CreateTaxFormInput { # A unique identifier for the client performing the mutation. clientMutationId: String type: TaxFormTypeEnum! } # Autogenerated return type of CreateTaxForm type CreateTaxFormPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of CreateUserBountiesReport input CreateUserBountiesReportInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of CreateUserBountiesReport type CreateUserBountiesReportPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of CreateUserLufthansaAccount input CreateUserLufthansaAccountInput { # A unique identifier for the client performing the mutation. clientMutationId: String first_name: String! last_name: String! number: String! } # Autogenerated return type of CreateUserLufthansaAccount type CreateUserLufthansaAccountPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # A currency as defined by ISO 4217 type Currency implements Node { code: String! id: ID! name: String! } # Possible currencies codes for bounties enum CurrencyCode { USD XLA } # Different entity types for currencycloud payout preferences enum CurrencycloudBankTransferEntityType { company individual } # Different payment types for currencycloud payout preferences enum CurrencycloudBankTransferPaymentType { priority regular } # A CurrencyCloud Bank Transfer Payout Preference type CurrencycloudBankTransferPayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean id: ID! name: String } # Autogenerated input type of DeleteBiDirectionalJiraIntegration input DeleteBiDirectionalJiraIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of DeleteBiDirectionalJiraIntegration type DeleteBiDirectionalJiraIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeleteJiraWebhook input DeleteJiraWebhookInput { # A unique identifier for the client performing the mutation. clientMutationId: String jira_webhook_id: ID! } # Autogenerated return type of DeleteJiraWebhook type DeleteJiraWebhookPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeletePhabricatorIntegration input DeletePhabricatorIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of DeletePhabricatorIntegration type DeletePhabricatorIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeleteSlackPipeline input DeleteSlackPipelineInput { # A unique identifier for the client performing the mutation. clientMutationId: String slack_pipeline_id: String! } # Autogenerated return type of DeleteSlackPipeline type DeleteSlackPipelinePayload { # A unique identifier for the client performing the mutation. clientMutationId: String deleted_slack_pipeline_id: String! team: Team } # Autogenerated input type of DeleteTeamMember input DeleteTeamMemberInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_member_id: ID! } # Autogenerated return type of DeleteTeamMember type DeleteTeamMemberPayload { # A unique identifier for the client performing the mutation. clientMutationId: String deletedMembershipId: ID errors: Hash me: User } # Autogenerated input type of DeleteTeamSlackIntegration input DeleteTeamSlackIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String slack_integration_id: ID! } # Autogenerated return type of DeleteTeamSlackIntegration type DeleteTeamSlackIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeleteUserLufthansaAccount input DeleteUserLufthansaAccountInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of DeleteUserLufthansaAccount type DeleteUserLufthansaAccountPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of DisableTwoFactorAuthentication input DisableTwoFactorAuthenticationInput { # A unique identifier for the client performing the mutation. clientMutationId: String password: String! totp_code: String! } # Autogenerated return type of DisableTwoFactorAuthentication type DisableTwoFactorAuthenticationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of DismissProgramHealthAcknowledgementMutation input DismissProgramHealthAcknowledgementMutationInput { # A unique identifier for the client performing the mutation. clientMutationId: String program_health_acknowledgement_id: ID! } # Autogenerated return type of DismissProgramHealthAcknowledgementMutation type DismissProgramHealthAcknowledgementMutationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String dismissed_program_health_acknowledgement_id: String! errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of EnableTwoFactorAuthentication input EnableTwoFactorAuthenticationInput { backup_codes: [String]! # A unique identifier for the client performing the mutation. clientMutationId: String password: String! signature: String! totp_code: String! totp_secret: String! } # Autogenerated return type of EnableTwoFactorAuthentication type EnableTwoFactorAuthenticationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of EnableUser input EnableUserInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of EnableUser type EnableUserPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of EnrollForProgram input EnrollForProgramInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of EnrollForProgram type EnrollForProgramPayload { # A unique identifier for the client performing the mutation. clientMutationId: String deleted_recommended_program_edge: TeamEdge query: Query! teams_connection: TeamConnection } # An error type Error implements Node { field: String id: ID! message: String! type: ErrorTypeEnum! } # Types of errors that can occur enum ErrorTypeEnum { ARGUMENT AUTHORIZATION } # An External Program type ExternalProgram implements Node { # The primary key from the database _id: ID! about: String handle: String! # ID of the object. id: ID! name: String! profile_picture(size: ProfilePictureSizes!): String! } # Autogenerated input type of GenerateMfaOtp input GenerateMfaOtpInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of GenerateMfaOtp type GenerateMfaOtpPayload { backup_codes: [String] # A unique identifier for the client performing the mutation. clientMutationId: String me: User qrcode: Hash signature: String totp_secret: String } # Autogenerated input type of GenerateTaxFormUrl input GenerateTaxFormUrlInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of GenerateTaxFormUrl type GenerateTaxFormUrlPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # A HackeronePayroll Payout Preference type HackeronePayrollPayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean email: String id: ID! } # Configuration for the events sent from HackerOne to JIRA type HackeroneToJiraEventsConfiguration implements Node { assignee_changes: Boolean! comments: Boolean! id: ID! public_disclosures: Boolean! rewards: Boolean! state_changes: Boolean! team: Team! } scalar Hash # An interface for the common fields on a HackerOne Invitation interface InvitationInterface { _id: ID! } # User invitation preference type enum InvitationPreferenceTypeEnum { always bounty_only never } # Invitations can be of multiple types union InvitationUnion = InvitationsSoftLaunch # The connection type for InvitationUnion. type InvitationUnionConnection { # A list of edges. edges: [InvitationUnionEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type InvitationUnionEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: InvitationUnion } # An invitation to join a private team as a hacker type InvitationsSoftLaunch implements InvitationInterface, Node { _id: ID! expires_at: String id: ID! markdown_message: String message: String team: Team! token: String } # A JIRA integration for a team type JiraIntegration implements Node { assignee: String base_url: String! created_at: String! custom: String description: String! id: ID! issue_type: Int! labels: String pid: Int! summary: String! team: Team! updated_at: String! } # A JIRA Oauth for a team type JiraOauth implements Node { # Assignables for a project assignables(project_id: Int!): [Hash] configured: Boolean! created_at: String! id: ID! issue_types: [Hash] projects: [Hash] site: String team: Team! updated_at: String! } # A JIRA webhook for a team type JiraWebhook implements Node { created_at: String! id: ID! last_event_received_at: String last_token_issued_at: String process_assignee_change: Boolean! process_comment_add: Boolean! process_priority_change: Boolean! process_resolution_change: Boolean! process_status_change: Boolean! team: Team! updated_at: String! } # Autogenerated input type of LeavePrivateProgram input LeavePrivateProgramInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! } # Autogenerated return type of LeavePrivateProgram type LeavePrivateProgramPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of LockReport input LockReportInput { # A unique identifier for the client performing the mutation. clientMutationId: String report_id: ID! } # Autogenerated return type of LockReport type LockReportPayload { activities: ActivityUnionConnection activity: ActivitiesComment # A unique identifier for the client performing the mutation. clientMutationId: String new_activity_edge: ActivityUnionEdge report: Report } # Settings for a user's Lufthansa Account type LufthansaAccount implements Node { created_at: String! first_name: String! id: ID! last_name: String! number: String! updated_at: String! } # Autogenerated input type of MarkReportAsNeedsMoreInfo input MarkReportAsNeedsMoreInfoInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String! report_id: ID! } # Autogenerated return type of MarkReportAsNeedsMoreInfo type MarkReportAsNeedsMoreInfoPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] query: Query report: Report report_id: ID! was_successful: Boolean! } # Autogenerated input type of MarkReportAsNoise input MarkReportAsNoiseInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String! report_id: ID! } # Autogenerated return type of MarkReportAsNoise type MarkReportAsNoisePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] query: Query report: Report was_successful: Boolean! } # Autogenerated input type of MarkReportAsSignal input MarkReportAsSignalInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of MarkReportAsSignal type MarkReportAsSignalPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] query: Query report: Report was_successful: Boolean! } type Mutation { acceptInvitation(input: AcceptInvitationInput!): AcceptInvitationPayload acknowledgeProgramHealthAcknowledgement(input: AcknowledgeProgramHealthAcknowledgementMutationInput!): AcknowledgeProgramHealthAcknowledgementMutationPayload archiveStructuredScope(input: ArchiveStructuredScopeInput!): ArchiveStructuredScopePayload createActivityComment(input: CreateActivityCommentInput!): CreateActivityCommentPayload createBounty(input: CreateBountyInput!): CreateBountyPayload createBountySuggestion(input: CreateBountySuggestionInput!): CreateBountySuggestionPayload createCoinbasePayoutPreference(input: CreateCoinbasePayoutPreferenceInput!): CreateCoinbasePayoutPreferencePayload createCurrencycloudBankTransferPayoutPreference(input: CreateCurrencycloudBankTransferPayoutPreferenceInput!): CreateCurrencycloudBankTransferPayoutPreferencePayload createJiraOauthUrl(input: CreateJiraOauthUrlInput!): CreateJiraOauthUrlPayload createJiraWebhookToken(input: CreateJiraWebhookTokenInput!): CreateJiraWebhookTokenPayload createMailingAddress(input: CreateMailingAddressInput!): CreateMailingAddressPayload createOrUpdateHackeroneToJiraEventsConfiguration(input: CreateOrUpdateHackeroneToJiraEventsConfigurationInput!): CreateOrUpdateHackeroneToJiraEventsConfigurationPayload createOrUpdateJiraIntegration(input: CreateOrUpdateJiraIntegrationInput!): CreateOrUpdateJiraIntegrationPayload createPaypalPreference(input: CreatePaypalPreferenceInput!): CreatePaypalPreferencePayload createSlackPipeline(input: CreateSlackPipelineInput!): CreateSlackPipelinePayload createStructuredScope(input: CreateStructuredScopeInput!): CreateStructuredScopePayload createSurveyAnswer(input: CreateSurveyAnswerInput!): CreateSurveyAnswerPayload createSwag(input: CreateSwagInput!): CreateSwagPayload createTaxForm(input: CreateTaxFormInput!): CreateTaxFormPayload createUserBountiesReport(input: CreateUserBountiesReportInput!): CreateUserBountiesReportPayload createUserLufthansaAccount(input: CreateUserLufthansaAccountInput!): CreateUserLufthansaAccountPayload deleteBiDirectionalJiraIntegration(input: DeleteBiDirectionalJiraIntegrationInput!): DeleteBiDirectionalJiraIntegrationPayload deleteJiraWebhook(input: DeleteJiraWebhookInput!): DeleteJiraWebhookPayload deletePhabricatorIntegration(input: DeletePhabricatorIntegrationInput!): DeletePhabricatorIntegrationPayload deleteSlackPipeline(input: DeleteSlackPipelineInput!): DeleteSlackPipelinePayload deleteTeamMember(input: DeleteTeamMemberInput!): DeleteTeamMemberPayload deleteTeamSlackIntegration(input: DeleteTeamSlackIntegrationInput!): DeleteTeamSlackIntegrationPayload deleteUserLufthansaAccount(input: DeleteUserLufthansaAccountInput!): DeleteUserLufthansaAccountPayload disableTwoFactorAuthentication(input: DisableTwoFactorAuthenticationInput!): DisableTwoFactorAuthenticationPayload dismissProgramHealthAcknowledgement(input: DismissProgramHealthAcknowledgementMutationInput!): DismissProgramHealthAcknowledgementMutationPayload enableTwoFactorAuthentication(input: EnableTwoFactorAuthenticationInput!): EnableTwoFactorAuthenticationPayload enableUser(input: EnableUserInput!): EnableUserPayload enrollForProgram(input: EnrollForProgramInput!): EnrollForProgramPayload generateMfaOtp(input: GenerateMfaOtpInput!): GenerateMfaOtpPayload generateTaxFormUrl(input: GenerateTaxFormUrlInput!): GenerateTaxFormUrlPayload leavePrivateProgram(input: LeavePrivateProgramInput!): LeavePrivateProgramPayload lockReport(input: LockReportInput!): LockReportPayload markReportAsNeedsMoreInfo(input: MarkReportAsNeedsMoreInfoInput!): MarkReportAsNeedsMoreInfoPayload markReportAsNoise(input: MarkReportAsNoiseInput!): MarkReportAsNoisePayload markReportAsSignal(input: MarkReportAsSignalInput!): MarkReportAsSignalPayload rejectInvitation(input: RejectInvitationInput!): RejectInvitationPayload updateAutomaticInvites(input: UpdateAutomaticInvitesInput!): UpdateAutomaticInvitesPayload updateBackupCodes(input: UpdateBackupCodesInput!): UpdateBackupCodesPayload updateInvitationPreferences(input: UpdateInvitationPreferencesInput!): UpdateInvitationPreferencesPayload updateJiraWebhook(input: UpdateJiraWebhookInput!): UpdateJiraWebhookPayload updateMe(input: UpdateMeInput!): UpdateMePayload updatePhabricatorIntegration(input: UpdatePhabricatorIntegrationInput!): UpdatePhabricatorIntegrationPayload updateReportStateToNeedsMoreInfo(input: UpdateReportStateToNeedsMoreInfoInput!): UpdateReportStateToNeedsMoreInfoPayload updateReportStructuredScope(input: UpdateReportStructuredScopeInput!): UpdateReportStructuredScopePayload updateSlackPipeline(input: UpdateSlackPipelineInput!): UpdateSlackPipelinePayload updateSlackUser(input: UpdateSlackUserInput!): UpdateSlackUserPayload updateStructuredScope(input: UpdateStructuredScopeInput!): UpdateStructuredScopePayload updateTeamFancySlackIntegration(input: UpdateTeamFancySlackIntegrationInput!): UpdateTeamFancySlackIntegrationPayload updateTeamMemberVisibility(input: UpdateTeamMemberVisibilityInput!): UpdateTeamMemberVisibilityPayload updateTeamSubmissionState(input: UpdateTeamSubmissionStateInput!): UpdateTeamSubmissionStatePayload updateTeamSubscription(input: UpdateTeamSubscriptionInput!): UpdateTeamSubscriptionPayload updateTeamWeakness(input: UpdateTeamWeaknessInput!): UpdateTeamWeaknessPayload updateUserEmail(input: UpdateUserEmailInput!): UpdateUserEmailPayload updateUserLufthansaAccount(input: UpdateUserLufthansaAccountInput!): UpdateUserLufthansaAccountPayload updateUserPassword(input: UpdateUserPasswordInput!): UpdateUserPasswordPayload } # An object with an ID. interface Node { # ID of the object. id: ID! } # Possible directions for sorting a collection enum OrderDirection { ASC DESC } # Information about pagination in a connection. type PageInfo { # When paginating forwards, the cursor to continue. endCursor: String # When paginating forwards, are there more items? hasNextPage: Boolean! # When paginating backwards, are there more items? hasPreviousPage: Boolean! # When paginating backwards, the cursor to continue. startCursor: String } # The connection type for User. type ParticipantConnection { # A list of edges. edges: [ParticipantWithReputationEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! year_range: [Int] } # An edge in a connection. type ParticipantWithReputationEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: User # The participant's rank within the team rank: Int # The participant's reputation within the team reputation: Int } # A interface for the common fields on an Payout Preference interface PayoutPreferenceInterface { _id: ID! default: Boolean id: ID! } # A user can have payout preferences for different payment services union PayoutPreferenceUnion = CoinbasePayoutPreferenceType | CurrencycloudBankTransferPayoutPreferenceType | HackeronePayrollPayoutPreferenceType | PaypalPayoutPreferenceType # A Paypal Payout Preference type PaypalPayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean email: String id: ID! } # All available permissions enum PermissionEnum { program_management } # A Phabricator integration for a team type PhabricatorIntegration implements Node { base_url: String created_at: String! description: String id: ID! process_h1_comment_added: Boolean! process_h1_status_change: Boolean! process_phabricator_comment_added: Boolean! process_phabricator_status_change: Boolean! project_tags: String team: Team! title: String updated_at: String! } # Different possible profile picture sizes enum ProfilePictureSizes { # 110x110 large # 82x82 medium # 62x62 small # 260x260 xtralarge } # A program_health_acknowledgement for a team_member type ProgramHealthAcknowledgement implements Node { acknowledged: Boolean created_at: String dismissed: Boolean id: ID! reason: ProgramHealthAcknowledgementReasonEnum team_member: TeamMember! } # The connection type for ProgramHealthAcknowledgement. type ProgramHealthAcknowledgementConnection { # A list of edges. edges: [ProgramHealthAcknowledgementEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ProgramHealthAcknowledgementEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: ProgramHealthAcknowledgement } # reason which reflect the state of a program health acknowledgement enum ProgramHealthAcknowledgementReasonEnum { failed ok paused review } # Root entity of the Hackerone Schema type Query implements Node { bank_transfer_reference: BankTransferReference clusters( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: ClusterOrder = {direction: ASC, field: BROWSING_FRIENDLY} ): ClusterConnection external_program(handle: String!): ExternalProgram id: ID! me: User # Fetches an object given its ID. node( # ID of the object. id: ID! ): Node query: Query! report(id: Int!): Report @deprecated(reason: "Query for a Report node at the root level is not recommended.") reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String database_id: Int = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this limit: Int = null order_by: ReportOrderInput = {direction: DESC, field: id} # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this page: Int = null pre_submission_review_states: [ReportPreSubmissionReviewStateEnum] = null substate: ReportStateEnum without_scope: Boolean = null ): ReportConnection resource(url: URI): ResourceInterface session: Session severity_calculator: SeverityCalculator surveys( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String category: SurveyCategoryEnum # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SurveyConnectionType team(handle: String!): Team @deprecated(reason: "Query for a Team node at the root level is not recommended. Ref T12456") teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String enrollable: Boolean # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamOrderInput permissions: [PermissionEnum] = [] ): TeamConnection user(username: String!): User @deprecated(reason: "Query for a User node at the root level is not recommended. Ref T12456") users( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): UserConnection } # Autogenerated input type of RejectInvitation input RejectInvitationInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! } # Autogenerated return type of RejectInvitation type RejectInvitationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String me: User } # A HackerOne report type Report implements Node, ResourceInterface { _id: ID! activities( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int not_types: [ActivityTypes] order_by: ActivityOrderInput types: [ActivityTypes] # A timestamp encoded as a string that. When provided, only activities with a # updated_at greater than this value will be resolved. Example: # activities(updated_at_after: "2017-11-30 13:37:12 UTC") updated_at_after: String ): ActivityConnection allow_singular_disclosure_after: String allow_singular_disclosure_at: String assignee: AssigneeUnion attachments: [Attachment] bounties: [Bounty] bounty_awarded_at: String bug_reporter_agreed_on_going_public_at: String cloned_from: Report closed_at: String comments_closed: Boolean created_at: String! cve_ids: [String] disclosed_at: String first_program_activity_at: String i_can_anc_review: Boolean id: ID! latest_activity_at: String latest_public_activity_at: String latest_public_activity_of_reporter_at: String latest_public_activity_of_team_at: String mediation_requested_at: String original_report: Report pre_submission_review_state: String reference: ID reference_link: String reporter: User severity: Severity singular_disclosure_allowed: Boolean singular_disclosure_disabled: Boolean stage: String stale: Boolean state: String structured_scope: StructuredScope substate: String! suggested_bounty: String summaries: [Summary] swag: [Swag] swag_awarded_at: String team: Team team_member_agreed_on_going_public_at: String title: String triaged_at: String # A pre-submission trigger that notified the hacker before submission. This # field is only present for reports filed after February 14, 2016. triggered_pre_submission_trigger: Trigger url: URI! vulnerability_information: String vulnerability_information_html: String weakness: Weakness } # A interface for the common fields on an HackerOne Report Activity interface ReportActivityInterface { attachments: [Attachment] automated_response: Boolean genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") report: Report } # The connection type for Report. type ReportConnection { # Groups and counts reports by the severity rating count_by_severity: [CountBySeverity]! # A list of edges. edges: [ReportEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ReportEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Report } # Filters which can be used to query reports enum ReportFilterEnum { assigned assigned_to_me bounty_awarded going_public_team going_public_user hacker_requested_mediation ineligible_for_bounty needs_first_program_response no_bounty_awarded no_swag_awarded not_public public reporter_is_active stale swag_awarded unassigned } # Fields on which a collection of reports can be ordered enum ReportOrderField { created_at id latest_activity_at } input ReportOrderInput { direction: OrderDirection! field: ReportOrderField! } # Pre submission review states a report can be in enum ReportPreSubmissionReviewStateEnum { pre_submission_accepted pre_submission_needs_more_info pre_submission_pending pre_submission_rejected } # States a report can be in enum ReportStateEnum { closed duplicate informative needs_more_info new not_applicable open pre_submission resolved spam triaged } # The connection type for User. type ReporterConnection { # A list of edges. edges: [UserEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # Represents a type that can be retrieved by a URL. interface ResourceInterface { url: URI! } # A HackerOne session type Session implements Node { csrf_token: String! @deprecated(reason: "This token is used to support legacy operations in HackerOne's frontend") id: ID! } # A HackerOne severity for a report type Severity implements Node { _id: ID! attack_complexity: SeverityAttackComplexityEnum attack_vector: SeverityAttackVectorEnum author_type: SeverityAuthorEnum availability: SeverityAvailabilityEnum confidentiality: SeverityConfidentialityEnum created_at: String id: ID! integrity: SeverityIntegrityEnum privileges_required: SeverityPrivilegesRequiredEnum rating: SeverityRatingEnum scope: SeverityScopeEnum score: Float user_id: Int user_interaction: SeverityUserInteractionEnum } # Severity attack complexity enum SeverityAttackComplexityEnum { high low } # Severity attack vector enum SeverityAttackVectorEnum { adjacent local network physical } # Severity author enum SeverityAuthorEnum { Team User } # Severity availability enum SeverityAvailabilityEnum { high low none } # Calculate CVSS Severity score and rating type SeverityCalculator implements Node { calculated_max_severity(availability_requirement: SeveritySecurityRequirementEnum, confidentiality_requirement: SeveritySecurityRequirementEnum, integrity_requirement: SeveritySecurityRequirementEnum): SeverityRatingEnum id: ID! } # Severity confidentiality enum SeverityConfidentialityEnum { high low none } # Severity integrity enum SeverityIntegrityEnum { high low none } # Severity privileges required enum SeverityPrivilegesRequiredEnum { high low none } # Severity rating enum SeverityRatingEnum { critical high low medium none } # Severity scope enum SeverityScopeEnum { changed unchanged } # Severity security requirement rating enum SeveritySecurityRequirementEnum { high low medium none } # Severity user interaction enum SeverityUserInteractionEnum { none required } # SLA types enum SlaTypeEnum { first_program_response report_triage } # Slack channel type SlackChannel implements Node { # ID of the object. id: ID! name: String! } # A Slack integration for a team type SlackIntegration implements Node { channel: String @deprecated(reason: "this field is not used in our new Slack integration") channels: [SlackChannel] id: ID! should_fetch_slack_channels: Boolean! should_fetch_slack_users: Boolean! team: Team! team_url: String! users: [SlackUser] } # A Slack Pipeline Configuration for notifications type SlackPipeline implements Node, ResourceInterface { # The primary key from the database _id: ID! channel: String! descriptive_label: String id: ID! notification_report_agreed_on_going_public: Boolean! notification_report_assignee_changed: Boolean! notification_report_became_public: Boolean! notification_report_bounty_paid: Boolean! notification_report_bounty_suggested: Boolean! notification_report_bug_closed_as_spam: Boolean! notification_report_bug_duplicate: Boolean! notification_report_bug_informative: Boolean! notification_report_bug_needs_more_info: Boolean! notification_report_bug_new: Boolean! notification_report_bug_not_applicable: Boolean! notification_report_closed_as_resolved: Boolean! notification_report_comments_closed: Boolean! notification_report_created: Boolean! notification_report_internal_comment_added: Boolean! notification_report_manually_disclosed: Boolean! notification_report_not_eligible_for_bounty: Boolean! notification_report_public_comment_added: Boolean! notification_report_reopened: Boolean! notification_report_swag_awarded: Boolean! notification_report_triaged: Boolean! team: Team! updated_at: String! url: URI! } # The connection type for SlackPipeline. type SlackPipelineConnection { # A list of edges. edges: [SlackPipelineEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type SlackPipelineEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: SlackPipeline } # Slack user type SlackUser implements Node { # The id provided by Slack _id: String! avatar_small: String! deleted: Boolean! email: String! # ID of the object. id: ID! name: String! real_name: String } # A static participant for a team type StaticParticipant implements Node { _id: ID! avatar(size: ProfilePictureSizes!): String! bio: String external_url: String id: ID! name: String! year: String } # The connection type for StaticParticipant. type StaticParticipantConnection { # A list of edges. edges: [StaticParticipantEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type StaticParticipantEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: StaticParticipant } # A defined scope of a HackerOne program type StructuredScope implements Node, ResourceInterface { _id: ID! archived_at: String asset_identifier: String! asset_type: StructuredScopedAssetTypeEnum availability_requirement: SeveritySecurityRequirementEnum confidentiality_requirement: SeveritySecurityRequirementEnum created_at: String! eligible_for_bounty: Boolean eligible_for_submission: Boolean id: ID! instruction: String integrity_requirement: SeveritySecurityRequirementEnum max_severity: SeverityRatingEnum reference: String rendered_instruction: String reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ReportConnection structured_scope_versions( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): StructuredScopeVersionConnection team: Team updated_at: String url: URI! } # An edge in a connection. type StructuredScopeEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: StructuredScope } # A versioned log of a scope of a HackerOne program type StructuredScopeVersion implements Node, ResourceInterface { _id: ID! archived_at: String created_at: String! id: ID! instruction: String max_severity: String team: Team url: URI! } # The connection type for StructuredScopeVersion. type StructuredScopeVersionConnection { # A list of edges. edges: [StructuredScopeVersionEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type StructuredScopeVersionEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: StructuredScopeVersion } # Structured Scope asset type enum enum StructuredScopedAssetTypeEnum { APPLE_STORE_APP_ID CIDR DOWNLOADABLE_EXECUTABLES GOOGLE_PLAY_APP_ID HARDWARE OTHER OTHER_APK OTHER_IPA SOURCE_CODE TESTFLIGHT URL WINDOWS_APP_STORE_APP_ID } # The connection type for StructuredScope. type StructuredScopesConnection { # A list of edges. edges: [StructuredScopeEdge] max_updated_at: String # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # Submission states enum SubmissionStateEnum { disabled open paused } # Team subscription action enum enum SubscriptionActionEnum { subscribe_to_all unsubscribe_from_all } # A HackerOne summary for a report type Summary implements Node { _id: ID! category: String! @deprecated(reason: "The implementation of this field contains hard to reason about polymorphism") content: String! created_at: String! id: ID! updated_at: String! user: User! } # A HackerOne survey type Survey implements Node, ResourceInterface { category: String! id: ID! question: String! structured_responses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SurveyStructuredResponseConnectionType url: URI! } # Survey categories enum SurveyCategoryEnum { invitation_rejection } # The connection type for Survey. type SurveyConnectionType { # A list of edges. edges: [SurveyEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type SurveyEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Survey } # Prepared survey response reasons type SurveyStructuredResponse implements Node { _id: ID! enabled: Boolean! helper_text: String id: ID! reason: String! survey: Survey } # The connection type for SurveyStructuredResponse. type SurveyStructuredResponseConnectionType { # A list of edges. edges: [SurveyStructuredResponseEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type SurveyStructuredResponseEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: SurveyStructuredResponse } # A HackerOne swag awarded for a report type Swag implements Node { _id: ID! address: Address @deprecated(reason: "This field is deprecated. The preferred way to access this data is using swag.user.address.") created_at: String id: ID! report: Report sent: Boolean! team: Team user: User } # The connection type for Swag. type SwagConnection { # A list of edges. edges: [SwagEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type SwagEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Swag } # A tax form for a user type TaxForm implements Node { created_at: String! hello_sign_client_id: String id: ID! signed_at: String status: TaxFormStateEnum! type: TaxFormTypeEnum! url: String } # Status of a tax form enum TaxFormStateEnum { expired needs_review rejected requested unavailable valid } # Type of a tax form enum TaxFormTypeEnum { W8BEN W8BENE W9 W9Corporate } # A HackerOne team type Team implements Node, ResourceInterface { # The primary key from the database _id: ID! about: String abuse: Boolean activities( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int not_types: [ActivityTypes] order_by: ActivityOrderInput types: [ActivityTypes] # A timestamp encoded as a string that. When provided, only activities with a # updated_at greater than this value will be resolved. Example: # activities(updated_at_after: "2017-11-30 13:37:12 UTC") updated_at_after: String ): ActivityConnection automatic_invites: Boolean base_bounty: Int bug_count: Int child_teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamConnection common_responses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: CommonResponseOrder = {direction: ASC, field: title} ): CommonResponseConnection created_at: String currency: String fancy_slack_integration: Boolean fancy_slack_integration_enabled: Boolean! first_response_time: Float hackerone_to_jira_events_configuration: HackeroneToJiraEventsConfiguration handle: String! has_avatar: Boolean has_policy: Boolean i_can_create_jira_webhook: Boolean i_can_destroy_jira_webhook: Boolean! i_can_view_base_bounty: Boolean i_can_view_jira_integration: Boolean i_can_view_jira_webhook: Boolean i_can_view_phabricator_integration: Boolean i_can_view_program_health: Boolean! i_can_view_reports_resolved: Boolean i_can_view_weaknesses: Boolean i_cannot_create_jira_webhook_reasons: [TeamCannotCreateJiraWebhookReasons]! id: ID! inbox_views( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamInboxViewOrder = {direction: ASC, field: position} visible: Boolean ): TeamInboxViewConnection internet_bug_bounty: Boolean jira_integration: JiraIntegration jira_oauth: JiraOauth jira_phase_3_enabled: Boolean! jira_webhook: JiraWebhook maximum_number_of_team_mediation_requests: Float name: String! new_staleness_threshold: Int offers_bounties: Boolean offers_swag: Boolean open_soft_launch_invitations( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): InvitationUnionConnection @deprecated(reason: "This should be a generic invitation connection.\n Used interim until generic invitation type is defined") participants( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int limit: Int = null year: Int = null ): ParticipantConnection phabricator_integration: PhabricatorIntegration policy_html: String profile_picture(size: ProfilePictureSizes!): String! report_submission_form_intro: String report_template: String reporters( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ReporterConnection reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String database_id: Int = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this limit: Int = null order_by: ReportOrderInput = {direction: DESC, field: id} # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this page: Int = null pre_submission_review_states: [ReportPreSubmissionReviewStateEnum] = null state: ReportStateEnum substate: ReportStateEnum violates_minimum_sla: SlaTypeEnum without_scope: Boolean = null ): ReportConnection review_requested_at: String sla_failed_count: Int sla_missed_count: Int slack_integration: SlackIntegration slack_pipelines( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SlackPipelineConnection state: TeamState! static_participants( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): StaticParticipantConnection structured_scopes( # Returns the elements in the list that come after the specified cursor. after: String archived: Boolean = null asset_type: String = null # Returns the elements in the list that come before the specified cursor. before: String eligible_for_submission: Boolean = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int search: String = null ): StructuredScopesConnection submission_state: SubmissionStateEnum! target_signal: Float team_member_groups( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamMemberGroupConnection team_members( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamMemberOrder = {direction: ASC, field: username} ): TeamMemberConnection triage_active: Boolean triage_time: Float triaged_staleness_threshold: Int twitter_handle: String updated_at: String url: URI! weaknesses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String cluster_id: ID = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: WeaknessOrder = {direction: ASC, field: name} search: String = null team_weakness_state: [TeamWeaknessStates] = [enabled, disabled, hidden] ): TeamWeaknessConnection website: String whitelisted_hackers( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ReporterConnection } # Different reasons why a team cannot create a jira webhook enum TeamCannotCreateJiraWebhookReasons { CANNOT_VIEW FEATURE_GATED PROGRAM_PERMISSION_REQUIRED } # The connection type for Team. type TeamConnection { # A list of edges. edges: [TeamEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Team } # A team report filter preset type TeamInboxView implements Node { _id: ID! assigned_to_group_ids: [Int!]! assigned_to_user_ids: [Int!]! built_in: Boolean! created_at: String! filters: [ReportFilterEnum!]! hackathons: [Int!]! id: ID! key: String! name: String! position: Int! reporter_ids: [Int!]! severities: [String!]! substates: [ReportStateEnum!]! team: Team! text_query: String! updated_at: String! visible: Boolean! } # The connection type for TeamInboxView. type TeamInboxViewConnection { # A list of edges. edges: [TeamInboxViewEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamInboxViewEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: TeamInboxView } input TeamInboxViewOrder { direction: OrderDirection! field: TeamInboxViewOrderField! } # Fields on which a collection of team inbox views can be ordered enum TeamInboxViewOrderField { position } # A HackerOne team member type TeamMember implements Node { # The primary key from the database _id: ID! auto_subscribe: Boolean concealed: Boolean created_at: String! i_can_leave_team: Boolean! id: ID! permissions: [String]! slack_user_id: String team: Team! user: User! } # The connection type for TeamMember. type TeamMemberConnection { # A list of edges. edges: [TeamMemberEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamMemberEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: TeamMember } # A HackerOne team member group type TeamMemberGroup implements Node { _id: ID! created_at: String # ID of the object. id: ID! name: String! permissions: [String]! } # The connection type for TeamMemberGroup. type TeamMemberGroupConnection { # A list of edges. edges: [TeamMemberGroupEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type TeamMemberGroupEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: TeamMemberGroup } input TeamMemberOrder { direction: OrderDirection! field: TeamMemberOrderField! } # Fields on which a collection of team members can be ordered enum TeamMemberOrderField { username } # Fields on which a collection of Teams can be ordered enum TeamOrderField { missing_pressure name } input TeamOrderInput { direction: OrderDirection! field: TeamOrderField! } # Different possible team states enum TeamState { da_mode inactive public_mode sandboxed soft_launched } # Team configuration of a weakness type TeamWeakness implements Node { id: ID! instruction: String state: TeamWeaknessStates team: Team! weakness: Weakness! } # The connection type for Weakness. type TeamWeaknessConnection { # A list of edges. edges: [TeamWeaknessEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamWeaknessEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Weakness team_weakness: TeamWeakness } # Possible states of how a weakness can be configured for a team enum TeamWeaknessStates { disabled enabled hidden } # A HackerOne trigger type Trigger implements Node { _id: ID! id: ID! title: String! } # Tshirt size enum TshirtSizeEnum { M_Large M_Medium M_Small M_XLarge M_XXLarge W_Large W_Medium W_Small W_XLarge W_XXLarge } # Represents a RFC compliant URI string. It is often used to fetch an object. scalar URI # Autogenerated input type of UpdateAutomaticInvites input UpdateAutomaticInvitesInput { # A unique identifier for the client performing the mutation. clientMutationId: String enabled: Boolean! handle: String! } # Autogenerated return type of UpdateAutomaticInvites type UpdateAutomaticInvitesPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of UpdateBackupCodes input UpdateBackupCodesInput { backup_codes: [String]! # A unique identifier for the client performing the mutation. clientMutationId: String password: String! signature: String! totp_code: String! totp_secret: String! } # Autogenerated return type of UpdateBackupCodes type UpdateBackupCodesPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of UpdateInvitationPreferences input UpdateInvitationPreferencesInput { # A unique identifier for the client performing the mutation. clientMutationId: String invitation_preference: InvitationPreferenceTypeEnum! } # Autogenerated return type of UpdateInvitationPreferences type UpdateInvitationPreferencesPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of UpdateJiraWebhook input UpdateJiraWebhookInput { # A unique identifier for the client performing the mutation. clientMutationId: String jira_webhook_id: ID! process_assignee_change: Boolean process_comment_add: Boolean process_priority_change: Boolean process_resolution_change: Boolean process_status_change: Boolean } # Autogenerated return type of UpdateJiraWebhook type UpdateJiraWebhookPayload { # A unique identifier for the client performing the mutation. clientMutationId: String jira_webhook: JiraWebhook! } # Autogenerated input type of UpdateMe input UpdateMeInput { # A unique identifier for the client performing the mutation. clientMutationId: String tshirt_size: String! } # Autogenerated return type of UpdateMe type UpdateMePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of UpdatePhabricatorIntegration input UpdatePhabricatorIntegrationInput { api_token: String base_url: String # A unique identifier for the client performing the mutation. clientMutationId: String description: String process_h1_comment_added: Boolean process_h1_status_change: Boolean process_phabricator_comment_added: Boolean process_phabricator_status_change: Boolean team_id: ID! title: String } # Autogenerated return type of UpdatePhabricatorIntegration type UpdatePhabricatorIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of UpdateReportStateToNeedsMoreInfo input UpdateReportStateToNeedsMoreInfoInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String! report_id: ID! } # Autogenerated return type of UpdateReportStateToNeedsMoreInfo type UpdateReportStateToNeedsMoreInfoPayload { # A unique identifier for the client performing the mutation. clientMutationId: String report: Report } # Autogenerated input type of UpdateReportStructuredScope input UpdateReportStructuredScopeInput { # A unique identifier for the client performing the mutation. clientMutationId: String report_id: ID! structured_scope_id: ID } # Autogenerated return type of UpdateReportStructuredScope type UpdateReportStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String report: Report } # Autogenerated input type of UpdateSlackPipeline input UpdateSlackPipelineInput { channel: String! # A unique identifier for the client performing the mutation. clientMutationId: String descriptive_label: String notification_report_agreed_on_going_public: Boolean! notification_report_assignee_changed: Boolean! notification_report_became_public: Boolean! notification_report_bounty_paid: Boolean! notification_report_bounty_suggested: Boolean! notification_report_bug_closed_as_spam: Boolean! notification_report_bug_duplicate: Boolean! notification_report_bug_informative: Boolean! notification_report_bug_needs_more_info: Boolean! notification_report_bug_new: Boolean! notification_report_bug_not_applicable: Boolean! notification_report_closed_as_resolved: Boolean! notification_report_comments_closed: Boolean! notification_report_created: Boolean! notification_report_internal_comment_added: Boolean! notification_report_manually_disclosed: Boolean! notification_report_not_eligible_for_bounty: Boolean! notification_report_public_comment_added: Boolean! notification_report_reopened: Boolean! notification_report_swag_awarded: Boolean! notification_report_triaged: Boolean! slack_pipeline_id: ID! } # Autogenerated return type of UpdateSlackPipeline type UpdateSlackPipelinePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash slack_pipeline: SlackPipeline! } # Autogenerated input type of UpdateSlackUser input UpdateSlackUserInput { # A unique identifier for the client performing the mutation. clientMutationId: String slack_user_id: String! team_member_id: ID! } # Autogenerated return type of UpdateSlackUser type UpdateSlackUserPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team_member: TeamMember! } # Autogenerated input type of UpdateStructuredScope input UpdateStructuredScopeInput { asset_identifier: String availability_requirement: String # A unique identifier for the client performing the mutation. clientMutationId: String confidentiality_requirement: String eligible_for_bounty: Boolean eligible_for_submission: Boolean instruction: String integrity_requirement: String reference: String structured_scope_id: ID! } # Autogenerated return type of UpdateStructuredScope type UpdateStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash query: Query structured_scope: StructuredScope } # Autogenerated input type of UpdateTeamFancySlackIntegration input UpdateTeamFancySlackIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String fancy_slack_integration: Boolean! team_id: ID! } # Autogenerated return type of UpdateTeamFancySlackIntegration type UpdateTeamFancySlackIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of UpdateTeamMemberVisibility input UpdateTeamMemberVisibilityInput { # A unique identifier for the client performing the mutation. clientMutationId: String concealed: Boolean! team_member_id: ID! } # Autogenerated return type of UpdateTeamMemberVisibility type UpdateTeamMemberVisibilityPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team_member: TeamMember } # Autogenerated input type of UpdateTeamSubmissionState input UpdateTeamSubmissionStateInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! submission_state: SubmissionStateEnum! } # Autogenerated return type of UpdateTeamSubmissionState type UpdateTeamSubmissionStatePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of UpdateTeamSubscription input UpdateTeamSubscriptionInput { action: SubscriptionActionEnum auto_subscribe: Boolean! # A unique identifier for the client performing the mutation. clientMutationId: String team_member_id: ID! } # Autogenerated return type of UpdateTeamSubscription type UpdateTeamSubscriptionPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team_member: TeamMember } # Autogenerated input type of UpdateTeamWeakness input UpdateTeamWeaknessInput { # A unique identifier for the client performing the mutation. clientMutationId: String instruction: String state: TeamWeaknessStates! team_weakness_id: ID! } # Autogenerated return type of UpdateTeamWeakness type UpdateTeamWeaknessPayload { # A unique identifier for the client performing the mutation. clientMutationId: String query: Query team_weakness: TeamWeakness } # Autogenerated input type of UpdateUserEmail input UpdateUserEmailInput { # A unique identifier for the client performing the mutation. clientMutationId: String email: String! password: String! } # Autogenerated return type of UpdateUserEmail type UpdateUserEmailPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of UpdateUserLufthansaAccount input UpdateUserLufthansaAccountInput { # A unique identifier for the client performing the mutation. clientMutationId: String first_name: String! last_name: String! number: String! } # Autogenerated return type of UpdateUserLufthansaAccount type UpdateUserLufthansaAccountPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of UpdateUserPassword input UpdateUserPasswordInput { # A unique identifier for the client performing the mutation. clientMutationId: String current_password: String! password: String! password_confirmation: String! } # Autogenerated return type of UpdateUserPassword type UpdateUserPasswordPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # A HackerOne user type User implements Node, ResourceInterface { _id: ID! address: Address authentication_service: AuthenticationServiceEnum! badges( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BadgesUsersConnection bio: String bounties( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String currency: CurrencyCode = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BountyConnection created_at: String! demo_hacker: Boolean! disabled: Boolean! duplicate_users( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): UserConnection email: String i_can_update_username: Boolean id: ID! impact: Float impact_percentile: Float invitation_preference: InvitationPreferenceTypeEnum location: String lufthansa_account: LufthansaAccount member_of_verified_team: Boolean membership(team_handle: String!): TeamMember memberships( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamMemberConnection name: String next_update_username_date: String otp_backup_codes: [String] payout_preferences: [PayoutPreferenceUnion] profile_picture(size: ProfilePictureSizes!): String! profile_pictures: Hash! @deprecated(reason: "Returns all the possible profile pictures instead of just the one you want use .profile_picture instead.") program_health_acknowledgements( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ProgramHealthAcknowledgementConnection rank: Int remaining_reports(team_handle: String!): Int reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String database_id: Int = null # Returns the first _n_ elements from the list. first: Int handle: String # Returns the last _n_ elements from the list. last: Int # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this limit: Int = null order_by: ReportOrderInput = {direction: DESC, field: id} # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this page: Int = null pre_submission_review_states: [ReportPreSubmissionReviewStateEnum] = null substate: ReportStateEnum without_scope: Boolean = null ): ReportConnection reputation: Int signal: Float signal_percentile: Float soft_launch_invitation(team_handle: String!): InvitationsSoftLaunch swag( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SwagConnection tax_form: TaxForm teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String enrollable: Boolean # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamOrderInput permissions: [PermissionEnum] = [] ): TeamConnection totp_enabled: Boolean totp_supported: Boolean triage_user: Boolean tshirt_size: TshirtSizeEnum unconfirmed_email: String url: URI! username: String! website: String whitelisted_teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamConnection } # The connection type for User. type UserConnection { # A list of edges. edges: [UserEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type UserEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: User } # The type of vulnerability on a HackerOne report type Weakness implements Node { _id: ID! clusters( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ClusterConnection created_at: String! description: String external_id: String id: ID! name: String! updated_at: String! } input WeaknessOrder { direction: OrderDirection! field: WeaknessOrderField! } # Fields on which a collection of weaknesses can be ordered enum WeaknessOrderField { name } graphql-ruby-2.2.17/benchmark/run.rb000066400000000000000000000457111476434635200173310ustar00rootroot00000000000000# frozen_string_literal: true require "graphql" require "jazz" require "benchmark/ips" require "stackprof" require "memory_profiler" require "graphql/batch" module GraphQLBenchmark QUERY_STRING = GraphQL::Introspection::INTROSPECTION_QUERY DOCUMENT = GraphQL.parse(QUERY_STRING) SCHEMA = Jazz::Schema BENCHMARK_PATH = File.expand_path("../", __FILE__) CARD_SCHEMA = GraphQL::Schema.from_definition(File.read(File.join(BENCHMARK_PATH, "schema.graphql"))) ABSTRACT_FRAGMENTS = GraphQL.parse(File.read(File.join(BENCHMARK_PATH, "abstract_fragments.graphql"))) ABSTRACT_FRAGMENTS_2_QUERY_STRING = File.read(File.join(BENCHMARK_PATH, "abstract_fragments_2.graphql")) ABSTRACT_FRAGMENTS_2 = GraphQL.parse(ABSTRACT_FRAGMENTS_2_QUERY_STRING) BIG_SCHEMA = GraphQL::Schema.from_definition(File.join(BENCHMARK_PATH, "big_schema.graphql")) BIG_QUERY_STRING = File.read(File.join(BENCHMARK_PATH, "big_query.graphql")) BIG_QUERY = GraphQL.parse(BIG_QUERY_STRING) FIELDS_WILL_MERGE_SCHEMA = GraphQL::Schema.from_definition("type Query { hello: String }") FIELDS_WILL_MERGE_QUERY = GraphQL.parse("{ #{Array.new(5000, "hello").join(" ")} }") module_function def self.run(task) Benchmark.ips do |x| case task when "query" x.report("query") { SCHEMA.execute(document: DOCUMENT) } when "validate" x.report("validate - introspection ") { CARD_SCHEMA.validate(DOCUMENT) } x.report("validate - abstract fragments") { CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS) } x.report("validate - abstract fragments 2") { CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS_2) } x.report("validate - big query") { BIG_SCHEMA.validate(BIG_QUERY) } x.report("validate - fields will merge") { FIELDS_WILL_MERGE_SCHEMA.validate(FIELDS_WILL_MERGE_QUERY) } when "scan" require "graphql/c_parser" x.report("scan c - introspection") { GraphQL.scan_with_c(QUERY_STRING) } x.report("scan - introspection") { GraphQL.scan_with_ruby(QUERY_STRING) } x.report("scan c - fragments") { GraphQL.scan_with_c(ABSTRACT_FRAGMENTS_2_QUERY_STRING) } x.report("scan - fragments") { GraphQL.scan_with_ruby(ABSTRACT_FRAGMENTS_2_QUERY_STRING) } x.report("scan c - big query") { GraphQL.scan_with_c(BIG_QUERY_STRING) } x.report("scan - big query") { GraphQL.scan_with_ruby(BIG_QUERY_STRING) } when "parse" # Uncomment this to use the C parser: # require "graphql/c_parser" x.report("parse - introspection") { GraphQL.parse(QUERY_STRING) } x.report("parse - fragments") { GraphQL.parse(ABSTRACT_FRAGMENTS_2_QUERY_STRING) } x.report("parse - big query") { GraphQL.parse(BIG_QUERY_STRING) } else raise("Unexpected task #{task}") end end end def self.profile_parse # To profile the C parser instead: # require "graphql/c_parser" report = MemoryProfiler.report do GraphQL.parse(BIG_QUERY_STRING) GraphQL.parse(QUERY_STRING) GraphQL.parse(ABSTRACT_FRAGMENTS_2_QUERY_STRING) end report.pretty_print end def self.validate_memory FIELDS_WILL_MERGE_SCHEMA.validate(FIELDS_WILL_MERGE_QUERY) report = MemoryProfiler.report do FIELDS_WILL_MERGE_SCHEMA.validate(FIELDS_WILL_MERGE_QUERY) nil end report.pretty_print end def self.profile # Warm up any caches: SCHEMA.execute(document: DOCUMENT) # CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS) res = nil result = StackProf.run(mode: :wall) do # CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS) res = SCHEMA.execute(document: DOCUMENT) end StackProf::Report.new(result).print_text end def self.build_large_schema Class.new(GraphQL::Schema) do query_t = Class.new(GraphQL::Schema::Object) do graphql_name("Query") int_ts = 5.times.map do |i| int_t = Module.new do include GraphQL::Schema::Interface graphql_name "Interface#{i}" 5.times do |n2| field :"field#{n2}", String do argument :arg, String end end end field :"int_field_#{i}", int_t int_t end obj_ts = 100.times.map do |n| input_obj_t = Class.new(GraphQL::Schema::InputObject) do graphql_name("Input#{n}") argument :arg, String end obj_t = Class.new(GraphQL::Schema::Object) do graphql_name("Object#{n}") implements(*int_ts) 20.times do |n2| field :"field#{n2}", String do argument :input, input_obj_t end end field :self_field, self field :int_0_field, int_ts[0] end field :"rootfield#{n}", obj_t obj_t end 10.times do |n| union_t = Class.new(GraphQL::Schema::Union) do graphql_name "Union#{n}" possible_types(*obj_ts.sample(10)) end field :"unionfield#{n}", union_t end end query(query_t) end end def self.profile_boot Benchmark.ips do |x| x.config(time: 10) x.report("Booting large schema") { build_large_schema } end result = StackProf.run(mode: :wall, interval: 1) do build_large_schema end StackProf::Report.new(result).print_text retained_schema = nil report = MemoryProfiler.report do retained_schema = build_large_schema end report.pretty_print end SILLY_LARGE_SCHEMA = build_large_schema def self.profile_small_query_on_large_schema schema = Class.new(SILLY_LARGE_SCHEMA) Benchmark.ips do |x| x.report("Run small query") { schema.execute("{ __typename }") } end result = StackProf.run(mode: :wall, interval: 1) do schema.execute("{ __typename }") end StackProf::Report.new(result).print_text StackProf.run(mode: :wall, out: "tmp/small_query.dump", interval: 1) do schema.execute("{ __typename }") end report = MemoryProfiler.report do schema.execute("{ __typename }") end puts "\n\n" report.pretty_print end def self.profile_large_introspection schema = SILLY_LARGE_SCHEMA Benchmark.ips do |x| x.config(time: 10) x.report("Run large introspection") { schema.to_json } end result = StackProf.run(mode: :wall) do schema.to_json end StackProf::Report.new(result).print_text report = MemoryProfiler.report do schema.to_json end puts "\n\n" report.pretty_print end def self.profile_large_analysis query_str = "query {\n".dup 5.times do |n| query_str << " intField#{n} { " 20.times do |o| query_str << "...Obj#{o}Fields " end query_str << "}\n" end query_str << "}" 20.times do |o| query_str << "fragment Obj#{o}Fields on Object#{o} { " 20.times do |f| query_str << " field#{f}(arg: \"a\")\n" end query_str << " selfField { selfField { selfField { __typename } } }\n" # query_str << " int0Field { ...Int0Fields }" query_str << "}\n" end # query_str << "fragment Int0Fields on Interface0 { __typename }" query = GraphQL::Query.new(SILLY_LARGE_SCHEMA, query_str) analyzers = [ GraphQL::Analysis::AST::FieldUsage, GraphQL::Analysis::AST::QueryDepth, GraphQL::Analysis::AST::QueryComplexity ] Benchmark.ips do |x| x.report("Running introspection") { GraphQL::Analysis::AST.analyze_query(query, analyzers) } end StackProf.run(mode: :wall, out: "last-stackprof.dump", interval: 1) do GraphQL::Analysis::AST.analyze_query(query, analyzers) end result = StackProf.run(mode: :wall, interval: 1) do GraphQL::Analysis::AST.analyze_query(query, analyzers) end StackProf::Report.new(result).print_text report = MemoryProfiler.report do GraphQL::Analysis::AST.analyze_query(query, analyzers) end puts "\n\n" report.pretty_print end # Adapted from https://github.com/rmosolgo/graphql-ruby/issues/861 def self.profile_large_result schema = ProfileLargeResult::Schema document = ProfileLargeResult::ALL_FIELDS Benchmark.ips do |x| x.config(time: 10) x.report("Querying for #{ProfileLargeResult::DATA.size} objects") { schema.execute(document: document) } end result = StackProf.run(mode: :wall, interval: 1) do schema.execute(document: document) end StackProf::Report.new(result).print_text report = MemoryProfiler.report do schema.execute(document: document) end report.pretty_print end def self.profile_small_result schema = ProfileLargeResult::Schema document = GraphQL.parse <<-GRAPHQL query { foos(first: 5) { __typename id int1 int2 string1 string2 foos(first: 5) { __typename string1 string2 foo { __typename int1 } } } } GRAPHQL Benchmark.ips do |x| x.config(time: 10) x.report("Querying for #{ProfileLargeResult::DATA.size} objects") { schema.execute(document: document) } end StackProf.run(mode: :wall, interval: 1, out: "tmp/small.dump") do schema.execute(document: document) end result = StackProf.run(mode: :wall, interval: 1) do schema.execute(document: document) end StackProf::Report.new(result).print_text report = MemoryProfiler.report do schema.execute(document: document) end report.pretty_print end def self.profile_small_introspection schema = ProfileLargeResult::Schema document = GraphQL.parse(GraphQL::Introspection::INTROSPECTION_QUERY) Benchmark.ips do |x| x.config(time: 5) x.report("Introspection") { schema.execute(document: document) } end result = StackProf.run(mode: :wall, interval: 1) do schema.execute(document: document) end StackProf::Report.new(result).print_text report = MemoryProfiler.report do schema.execute(document: document) end report.pretty_print end module ProfileLargeResult def self.eager_or_proc(value) ENV["EAGER"] ? value : -> { value } end DATA_SIZE = 1000 DATA = DATA_SIZE.times.map { eager_or_proc({ id: SecureRandom.uuid, int1: SecureRandom.random_number(100000), int2: SecureRandom.random_number(100000), string1: eager_or_proc(SecureRandom.base64), string2: SecureRandom.base64, boolean1: SecureRandom.random_number(1) == 0, boolean2: SecureRandom.random_number(1) == 0, int_array: eager_or_proc(10.times.map { eager_or_proc(SecureRandom.random_number(100000)) } ), string_array: 10.times.map { SecureRandom.base64 }, boolean_array: 10.times.map { SecureRandom.random_number(1) == 0 }, }) } module Bar include GraphQL::Schema::Interface field :string_array, [String], null: false end module Baz include GraphQL::Schema::Interface implements Bar field :int_array, [Integer], null: false field :boolean_array, [Boolean], null: false end class ExampleExtension < GraphQL::Schema::FieldExtension end class FooType < GraphQL::Schema::Object implements Baz field :id, ID, null: false, extensions: [ExampleExtension] field :int1, Integer, null: false, extensions: [ExampleExtension] field :int2, Integer, null: false, extensions: [ExampleExtension] field :string1, String, null: false do argument :arg1, String, required: false argument :arg2, String, required: false argument :arg3, String, required: false argument :arg4, String, required: false end field :string2, String, null: false do argument :arg1, String, required: false argument :arg2, String, required: false argument :arg3, String, required: false argument :arg4, String, required: false end field :boolean1, Boolean, null: false do argument :arg1, String, required: false argument :arg2, String, required: false argument :arg3, String, required: false argument :arg4, String, required: false end field :boolean2, Boolean, null: false do argument :arg1, String, required: false argument :arg2, String, required: false argument :arg3, String, required: false argument :arg4, String, required: false end field :foos, [FooType], null: false, description: "Return a list of Foo objects" do argument :first, Integer, default_value: DATA_SIZE end def foos(first:) DATA.first(first) end field :foo, FooType def foo DATA.sample end end class QueryType < GraphQL::Schema::Object description "Query root of the system" field :foos, [FooType], null: false, description: "Return a list of Foo objects" do argument :first, Integer, default_value: DATA_SIZE end def foos(first:) DATA.first(first) end end class Schema < GraphQL::Schema query QueryType # use GraphQL::Dataloader lazy_resolve Proc, :call end ALL_FIELDS = GraphQL.parse <<-GRAPHQL query($skip: Boolean = false) { foos { id @skip(if: $skip) int1 int2 string1 string2 boolean1 boolean2 stringArray intArray booleanArray } } GRAPHQL end def self.profile_to_definition require_relative "./batch_loading" schema = ProfileLargeResult::Schema schema.to_definition Benchmark.ips do |x| x.report("to_definition") { schema.to_definition } end result = StackProf.run(mode: :wall, interval: 1) do schema.to_definition end StackProf::Report.new(result).print_text report = MemoryProfiler.report do schema.to_definition end report.pretty_print end def self.profile_batch_loaders require_relative "./batch_loading" include BatchLoading document = GraphQL.parse <<-GRAPHQL { braves: team(name: "Braves") { ...TeamFields } bulls: team(name: "Bulls") { ...TeamFields } } fragment TeamFields on Team { players { team { players { team { name } } } } } GRAPHQL batch_result = GraphQLBatchSchema.execute(document: document).to_h dataloader_result = GraphQLDataloaderSchema.execute(document: document).to_h no_batch_result = GraphQLNoBatchingSchema.execute(document: document).to_h results = [batch_result, dataloader_result, no_batch_result].uniq if results.size > 1 puts "Batch result:" pp batch_result puts "Dataloader result:" pp dataloader_result puts "No-batch result:" pp no_batch_result raise "Got different results -- fix implementation before benchmarking." end Benchmark.ips do |x| x.report("GraphQL::Batch") { GraphQLBatchSchema.execute(document: document) } x.report("GraphQL::Dataloader") { GraphQLDataloaderSchema.execute(document: document) } x.report("No Batching") { GraphQLNoBatchingSchema.execute(document: document) } x.compare! end puts "========== GraphQL-Batch Memory ==============" report = MemoryProfiler.report do GraphQLBatchSchema.execute(document: document) end report.pretty_print puts "========== Dataloader Memory =================" report = MemoryProfiler.report do GraphQLDataloaderSchema.execute(document: document) end report.pretty_print puts "========== No Batch Memory ==============" report = MemoryProfiler.report do GraphQLNoBatchingSchema.execute(document: document) end report.pretty_print end def self.profile_schema_memory_footprint schema = nil report = MemoryProfiler.report do query_type = Class.new(GraphQL::Schema::Object) do graphql_name "Query" 100.times do |i| type = Class.new(GraphQL::Schema::Object) do graphql_name "Object#{i}" field :f, Integer end field "f#{i}", type end end thing_type = Class.new(GraphQL::Schema::Object) do graphql_name "Thing" field :name, String end mutation_type = Class.new(GraphQL::Schema::Object) do graphql_name "Mutation" 100.times do |i| mutation_class = Class.new(GraphQL::Schema::RelayClassicMutation) do graphql_name "Do#{i}" argument :id, "ID" field :thing, thing_type field :things, thing_type.connection_type end field "f#{i}", mutation: mutation_class end end schema = Class.new(GraphQL::Schema) do query(query_type) mutation(mutation_type) end end report.pretty_print end class StackDepthSchema < GraphQL::Schema class Thing < GraphQL::Schema::Object field :thing, self do argument :lazy, Boolean, default_value: false end def thing(lazy:) if lazy -> { :something } else :something end end field :stack_trace_depth, Integer do argument :lazy, Boolean, default_value: false end def stack_trace_depth(lazy:) get_depth = -> { graphql_caller = caller.select { |c| c.include?("graphql") } graphql_caller.size } if lazy get_depth else get_depth.call end end end class Query < GraphQL::Schema::Object field :thing, Thing def thing :something end end query(Query) lazy_resolve(Proc, :call) end def self.profile_stack_depth query_str = <<-GRAPHQL query($lazyThing: Boolean!, $lazyStackTrace: Boolean!) { thing { thing(lazy: $lazyThing) { thing(lazy: $lazyThing) { thing(lazy: $lazyThing) { thing(lazy: $lazyThing) { stackTraceDepth(lazy: $lazyStackTrace) } } } } } } GRAPHQL eager_res = StackDepthSchema.execute(query_str, variables: { lazyThing: false, lazyStackTrace: false }) lazy_res = StackDepthSchema.execute(query_str, variables: { lazyThing: true, lazyStackTrace: false }) very_lazy_res = StackDepthSchema.execute(query_str, variables: { lazyThing: true, lazyStackTrace: true }) get_depth = ->(result) { result["data"]["thing"]["thing"]["thing"]["thing"]["thing"]["stackTraceDepth"] } puts <<~RESULT Result Depth --------------------- Eager #{get_depth.call(eager_res)} Lazy #{get_depth.call(lazy_res)} Very Lazy #{get_depth.call(very_lazy_res)} RESULT end end graphql-ruby-2.2.17/benchmark/schema.graphql000066400000000000000000000033701476434635200210130ustar00rootroot00000000000000# A big schema for testing type Query { node(id: ID!): Node } interface Node { id: ID! } interface Node2 { id: ID } interface Commentable { id: ID! comments: [Comment!]! } interface Named { name: String! } type Comment implements Node { author: Player body: String! id: ID! } type Card implements Node, Commentable, Node2, Named { name: String! converted_mana_cost: Int! mana_cost: String! colors: [Color!]! power: Int toughness: Int rules_text: String! id: ID! comments: [Comment!]! } type Printing implements Node, Commentable, Node2 { card: Card! expansion: Expansion! rarity: Rarity! artist: Artist! id: ID! comments: [Comment!]! } type Expansion implements Node, Commentable, Named { name: String! code: String! printings: [Printing!]! block: Block! id: ID! comments: [Comment!]! } type Block implements Node, Commentable, Named { name: String! expansions: [Expansion!]! id: ID! comments: [Comment!]! } # Eg shard, guild, clan type Watermark implements Node, Commentable, Named { name: String! cards: [Card!]! colors: [Color!]! id: ID! comments: [Comment!]! } type Artist implements Node, Commentable, Named { name: String! printings: [Printing!]! id: ID! comments: [Comment!]! } type Player implements Node, Commentable, Named { name: String! decks: [Deck!]! id: ID! comments: [Comment!]! } type Deck implements Node, Commentable, Named { name: String! colors: [Color!]! slots: [Slot!]! id: ID! comments: [Comment!]! } type Slot implements Node, Commentable { deck: Deck! card: Card! id: ID! comments: [Comment!]! } enum Color { WHITE BLUE BLACK RED GREEN COLORLESS } enum Rarity { COMMON UNCOMMON RARE MYTHIC_RARE TIMESHIFTED } graphql-ruby-2.2.17/cop/000077500000000000000000000000001476434635200150175ustar00rootroot00000000000000graphql-ruby-2.2.17/cop/development/000077500000000000000000000000001476434635200173415ustar00rootroot00000000000000graphql-ruby-2.2.17/cop/development/context_is_passed_cop.rb000066400000000000000000000030371476434635200242500ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop' module Cop module Development class ContextIsPassedCop < RuboCop::Cop::Cop MSG = <<-MSG This method also accepts `context` as an argument. Pass it so that the returned value will reflect the current query, or use another method that isn't context-dependent. MSG # These are already context-aware or else not query-related def_node_matcher :likely_query_specific_receiver?, " { (send _ {:ast_node :query :context :warden :ctx :query_ctx :query_context}) (lvar {:ast_node :query :context :warden :ctx :query_ctx :query_context}) (ivar {:@query :@context :@warden}) (send _ {:introspection_system}) } " def_node_matcher :method_doesnt_receive_second_context_argument?, <<-MATCHER (send _ {:get_field :get_argument :get_type} _) MATCHER def_node_matcher :method_doesnt_receive_first_context_argument?, <<-MATCHER (send _ {:fields :arguments :types :enum_values}) MATCHER def_node_matcher :is_enum_values_call_without_arguments?, " (send (send _ {:enum :enum_type (ivar {:@enum :@enum_type})}) {:values}) " def on_send(node) if ( method_doesnt_receive_second_context_argument?(node) || method_doesnt_receive_first_context_argument?(node) || is_enum_values_call_without_arguments?(node) ) && !likely_query_specific_receiver?(node.to_a[0]) add_offense(node) end end end end end graphql-ruby-2.2.17/cop/development/no_eval_cop.rb000066400000000000000000000011701476434635200221510ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop' module Cop module Development class NoEvalCop < RuboCop::Cop::Base MSG_TEMPLATE = "Don't use `%{eval_method_name}` which accepts strings and may result evaluating unexpected code. Use `%{exec_method_name}` instead, and pass a block." def on_send(node) case node.method_name when :module_eval, :class_eval, :instance_eval message = MSG_TEMPLATE % { eval_method_name: node.method_name, exec_method_name: node.method_name.to_s.sub("eval", "exec").to_sym } add_offense node, message: message end end end end end graphql-ruby-2.2.17/cop/development/no_focus_cop.rb000066400000000000000000000007421476434635200223450ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop' module Cop module Development # Make sure no tests are focused, from https://github.com/rubocop-hq/rubocop/issues/3773#issuecomment-420662102 class NoFocusCop < RuboCop::Cop::Cop MSG = 'Remove `focus` from tests.' def_node_matcher :focused?, <<-MATCHER (send nil? :focus) MATCHER def on_send(node) return unless focused?(node) add_offense node end end end end graphql-ruby-2.2.17/cop/development/none_without_block_cop.rb000066400000000000000000000017511476434635200244270ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop' module Cop module Development # A custom Rubocop rule to catch uses of `.none?` without a block. # # @see https://github.com/rmosolgo/graphql-ruby/pull/2090 class NoneWithoutBlockCop < RuboCop::Cop::Cop MSG = <<-MD Instead of `.none?` without a block: - Use `.empty?` to check for an empty collection (faster) - Add a block to explicitly check for `false` (more clear) Run `-a` to replace this with `.empty?`. MD def on_block(node) # Since this method was called with a block, it can't be # a case of `.none?` without a block ignore_node(node.send_node) end def on_send(node) if !ignored_node?(node) && node.method_name == :none? && node.arguments.size == 0 add_offense(node) end end def autocorrect(node) lambda do |corrector| corrector.replace(node.location.selector, "empty?") end end end end end graphql-ruby-2.2.17/gemfiles/000077500000000000000000000000001476434635200160315ustar00rootroot00000000000000graphql-ruby-2.2.17/gemfiles/mongoid_6.gemfile000066400000000000000000000004331476434635200212440ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "mongoid", "~> 6.4.1" if RUBY_VERSION >= "3.0" gem "libev_scheduler" gem "evt" end gemspec path: "../" graphql-ruby-2.2.17/gemfiles/mongoid_7.gemfile000066400000000000000000000004311476434635200212430ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "mongoid", "~> 7.0" if RUBY_VERSION >= "3.0" gem "libev_scheduler" gem "evt" end gemspec path: "../" graphql-ruby-2.2.17/gemfiles/rails_6.1.gemfile000066400000000000000000000006361476434635200210660ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 6.1.0", require: "rails/all" gem "sqlite3", "~> 1.4", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" if RUBY_VERSION >= "3.0" gem "libev_scheduler" gem "evt" end gemspec path: "../" graphql-ruby-2.2.17/gemfiles/rails_6.1_postgresql.gemfile000066400000000000000000000004251476434635200233450ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 6.1.0", require: "rails/all" gem "pg", platform: :ruby gem "sequel" gemspec path: "../" graphql-ruby-2.2.17/gemfiles/rails_7.0.gemfile000066400000000000000000000005631476434635200210650ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 7.0.0", require: "rails/all" gem "sqlite3", "~> 1.4", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gem "evt" gem "async" gemspec path: "../" graphql-ruby-2.2.17/gemfiles/rails_7.1.gemfile000066400000000000000000000006151476434635200210640ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 7.1.0", require: "rails/all" gem "sqlite3", "~> 1.4", platform: :ruby gem "pg", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gem "evt" gem "async" gemspec path: "../" graphql-ruby-2.2.17/gemfiles/rails_master.gemfile000066400000000000000000000010061476434635200220450ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", github: "rails/rails", require: "rails/all", ref: "main" gem 'sqlite3', "~> 1.4", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gem "evt" if RUBY_ENGINE == "ruby" # This doesn't work on truffle-ruby becuase there's no `IO::READABLE` gem "libev_scheduler" end gem "async" gemspec path: "../" graphql-ruby-2.2.17/graphql-c_parser/000077500000000000000000000000001476434635200174705ustar00rootroot00000000000000graphql-ruby-2.2.17/graphql-c_parser/CHANGELOG.md000066400000000000000000000010621476434635200213000ustar00rootroot00000000000000# GraphQL::CParser ## 1.0.8 - Support directives on variable definitions, requires `graphql` 2.2.10+ #4847 ## 1.0.5 - Properly parse integers with leading zeros as Integers, not Floats #4556 ## 1.0.4 - Use UTF-8 encoding for static strings #4526 ## 1.0.3 - Raise a `ParseError` on bad Unicode escapes (like the Ruby parser) #4514 - Force UTF-8 encoding (like the Ruby parser) #4467 ## 1.0.2 - Remove `.y` and `.rl` files to avoid triggering build tasks during install ## 1.0.1 - Fix gem files (to include `ext`) ## 1.0.0 - Release GraphQL::CParser graphql-ruby-2.2.17/graphql-c_parser/Rakefile000066400000000000000000000004301476434635200211320ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_helper" # use a custom tag to avoid conflicting with GraphQL-Ruby tags in the same git repo class CustomGemHelper < Bundler::GemHelper def version_tag "graphql-c_parser-v#{version}" end end CustomGemHelper.install_tasks graphql-ruby-2.2.17/graphql-c_parser/ext/000077500000000000000000000000001476434635200202705ustar00rootroot00000000000000graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/000077500000000000000000000000001476434635200244645ustar00rootroot00000000000000graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/extconf.rb000066400000000000000000000001351476434635200264560ustar00rootroot00000000000000# frozen_string_literal: true require 'mkmf' create_makefile 'graphql/graphql_c_parser_ext' graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/graphql_c_parser_ext.c000066400000000000000000000014351476434635200310270ustar00rootroot00000000000000#include "graphql_c_parser_ext.h" VALUE GraphQL_CParser_Lexer_tokenize_with_c(VALUE self, VALUE query_string) { return tokenize(query_string); } VALUE GraphQL_CParser_Parser_c_parse(VALUE self) { yyparse(self, rb_ivar_get(self, rb_intern("@filename"))); return Qnil; } void Init_graphql_c_parser_ext() { VALUE GraphQL = rb_define_module("GraphQL"); VALUE CParser = rb_define_module_under(GraphQL, "CParser"); VALUE Lexer = rb_define_module_under(CParser, "Lexer"); rb_define_singleton_method(Lexer, "tokenize_with_c", GraphQL_CParser_Lexer_tokenize_with_c, 1); setup_static_token_variables(); VALUE Parser = rb_define_class_under(CParser, "Parser", rb_cObject); rb_define_method(Parser, "c_parse", GraphQL_CParser_Parser_c_parse, 0); initialize_node_class_variables(); } graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/graphql_c_parser_ext.h000066400000000000000000000002161476434635200310300ustar00rootroot00000000000000#ifndef Graphql_ext_h #define Graphql_ext_h #include #include "lexer.h" #include "parser.h" void Init_graphql_c_parser_ext(); #endif graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/lexer.c000066400000000000000000001475501476434635200257630ustar00rootroot00000000000000#line 1 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" #line 102 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" #line 8 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" static const char _graphql_c_lexer_trans_keys[] = { 4, 22, 4, 43, 14, 47, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 49, 4, 22, 4, 4, 4, 4, 4, 22, 4, 4, 4, 4, 14, 15, 14, 15, 10, 15, 12, 12, 0, 49, 0, 0, 4, 22, 4, 4, 4, 4, 4, 4, 4, 22, 4, 4, 4, 4, 1, 1, 14, 15, 12, 12, 10, 29, 14, 15, 12, 15, 12, 12, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 0 }; static const signed char _graphql_c_lexer_char_class[] = { 0, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, 4, 5, 6, 2, 7, 2, 8, 9, 2, 10, 0, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 2, 2, 17, 2, 2, 18, 19, 19, 19, 19, 20, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 22, 23, 2, 24, 2, 25, 26, 27, 28, 29, 30, 31, 32, 33, 19, 19, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 19, 45, 46, 19, 47, 48, 49, 0 }; static const short _graphql_c_lexer_index_offsets[] = { 0, 19, 59, 93, 126, 159, 192, 225, 258, 291, 324, 360, 379, 380, 381, 400, 401, 402, 404, 406, 412, 413, 463, 464, 483, 484, 485, 486, 505, 506, 507, 508, 510, 511, 531, 533, 537, 538, 571, 604, 637, 670, 703, 736, 769, 802, 835, 868, 901, 934, 967, 1000, 1033, 1066, 1099, 1132, 1165, 1198, 1231, 1264, 1297, 1330, 1363, 1396, 1429, 1462, 1495, 1528, 1561, 1594, 1627, 1660, 1693, 1726, 1759, 1792, 1825, 1858, 1891, 1924, 1957, 1990, 2023, 2056, 2089, 2122, 2155, 2188, 2221, 2254, 2287, 2320, 2353, 2386, 2419, 2452, 2485, 2518, 2551, 2584, 2617, 2650, 2683, 2716, 2749, 2782, 2815, 2848, 2881, 2914, 2947, 2980, 3013, 3046, 3079, 3112, 3145, 3178, 3211, 3244, 3277, 3310, 3343, 3376, 3409, 3442, 3475, 3508, 3541, 3574, 3607, 3640, 0 }; static const short _graphql_c_lexer_indices[] = { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 4, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 0, 0, 0, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 0, 0, 0, 10, 10, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 0, 0, 0, 11, 11, 0, 0, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 0, 0, 0, 12, 12, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 12, 12, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 1, 15, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 16, 17, 18, 19, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 16, 20, 21, 23, 23, 25, 25, 26, 26, 24, 24, 25, 25, 27, 30, 31, 29, 32, 33, 34, 35, 36, 37, 38, 29, 39, 40, 29, 41, 42, 43, 44, 45, 46, 46, 47, 29, 48, 46, 46, 46, 46, 49, 50, 51, 46, 46, 52, 46, 53, 54, 55, 46, 56, 57, 58, 59, 60, 46, 46, 46, 61, 62, 63, 30, 66, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 14, 69, 70, 71, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 16, 72, 18, 73, 41, 42, 75, 26, 26, 76, 76, 23, 23, 76, 76, 76, 76, 77, 76, 76, 76, 76, 76, 76, 76, 76, 77, 25, 25, 75, 74, 42, 42, 78, 46, 46, 13, 13, 13, 46, 46, 13, 13, 13, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 80, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 81, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 82, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 83, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 84, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 85, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 86, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 87, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 88, 46, 46, 46, 46, 46, 46, 46, 46, 89, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 90, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 91, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 92, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 93, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 94, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 95, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 96, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 97, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 98, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 99, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 100, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 101, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 102, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 103, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 104, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 105, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 106, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 107, 108, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 109, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 110, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 111, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 112, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 113, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 114, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 115, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 116, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 117, 46, 46, 46, 118, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 119, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 120, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 121, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 122, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 123, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 124, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 125, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 126, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 127, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 128, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 129, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 130, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 131, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 132, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 133, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 134, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 135, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 136, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 137, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 138, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 139, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 140, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 141, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 142, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 143, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 144, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 145, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 146, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 147, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 148, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 149, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 150, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 151, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 152, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 153, 46, 46, 46, 46, 46, 46, 154, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 155, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 156, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 157, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 158, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 159, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 160, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 161, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 162, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 163, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 164, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 165, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 166, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 167, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 168, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 169, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 170, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 171, 46, 46, 46, 46, 46, 172, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 173, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 174, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 175, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 176, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 177, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 178, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 179, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 79, 79, 79, 46, 46, 79, 79, 79, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 180, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 0 }; static const signed char _graphql_c_lexer_index_defaults[] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 22, 24, 24, 0, 29, 64, 1, 67, 68, 68, 14, 14, 14, 34, 65, 74, 76, 76, 74, 65, 13, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 0 }; static const short _graphql_c_lexer_cond_targs[] = { 21, 0, 21, 1, 2, 3, 6, 4, 5, 7, 8, 9, 10, 21, 11, 12, 14, 13, 25, 15, 16, 27, 21, 33, 21, 34, 18, 21, 21, 21, 22, 21, 21, 23, 30, 21, 21, 21, 21, 31, 36, 32, 35, 21, 21, 21, 37, 21, 21, 38, 46, 53, 63, 81, 88, 91, 92, 96, 105, 123, 128, 21, 21, 21, 21, 21, 24, 21, 21, 26, 21, 28, 29, 21, 21, 17, 21, 19, 20, 21, 39, 40, 41, 42, 43, 44, 45, 37, 47, 49, 48, 37, 50, 51, 52, 37, 54, 57, 55, 56, 37, 58, 59, 60, 61, 62, 37, 64, 72, 65, 66, 67, 68, 69, 70, 71, 37, 73, 75, 74, 37, 76, 77, 78, 79, 80, 37, 82, 83, 84, 85, 86, 87, 37, 89, 90, 37, 37, 93, 94, 95, 37, 97, 98, 99, 100, 101, 102, 103, 104, 37, 106, 113, 107, 110, 108, 109, 37, 111, 112, 37, 114, 115, 116, 117, 118, 119, 120, 121, 122, 37, 124, 126, 125, 37, 127, 37, 129, 130, 131, 37, 0 }; static const signed char _graphql_c_lexer_cond_actions[] = { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7, 0, 0, 8, 0, 11, 0, 12, 13, 6, 0, 14, 15, 16, 17, 0, 6, 6, 6, 18, 19, 20, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 25, 26, 27, 28, 29, 30, 31, 0, 32, 4, 4, 33, 34, 0, 35, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 38, 0, 0, 0, 39, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 43, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 45, 0, 0, 46, 47, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 50, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 53, 0, 54, 0, 0, 0, 55, 0 }; static const signed char _graphql_c_lexer_to_state_actions[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const signed char _graphql_c_lexer_from_state_actions[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const signed char _graphql_c_lexer_eof_trans[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 14, 14, 14, 14, 14, 23, 25, 25, 1, 29, 65, 66, 68, 69, 69, 69, 69, 69, 74, 66, 75, 77, 77, 75, 66, 14, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 0 }; static const int graphql_c_lexer_start = 21; static const int graphql_c_lexer_first_final = 21; static const int graphql_c_lexer_error = -1; static const int graphql_c_lexer_en_main = 21; #line 104 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" #include #define INIT_STATIC_TOKEN_VARIABLE(token_name) \ static VALUE GraphQLTokenString##token_name; INIT_STATIC_TOKEN_VARIABLE(ON) INIT_STATIC_TOKEN_VARIABLE(FRAGMENT) INIT_STATIC_TOKEN_VARIABLE(QUERY) INIT_STATIC_TOKEN_VARIABLE(MUTATION) INIT_STATIC_TOKEN_VARIABLE(SUBSCRIPTION) INIT_STATIC_TOKEN_VARIABLE(REPEATABLE) INIT_STATIC_TOKEN_VARIABLE(RCURLY) INIT_STATIC_TOKEN_VARIABLE(LCURLY) INIT_STATIC_TOKEN_VARIABLE(RBRACKET) INIT_STATIC_TOKEN_VARIABLE(LBRACKET) INIT_STATIC_TOKEN_VARIABLE(RPAREN) INIT_STATIC_TOKEN_VARIABLE(LPAREN) INIT_STATIC_TOKEN_VARIABLE(COLON) INIT_STATIC_TOKEN_VARIABLE(VAR_SIGN) INIT_STATIC_TOKEN_VARIABLE(DIR_SIGN) INIT_STATIC_TOKEN_VARIABLE(ELLIPSIS) INIT_STATIC_TOKEN_VARIABLE(EQUALS) INIT_STATIC_TOKEN_VARIABLE(BANG) INIT_STATIC_TOKEN_VARIABLE(PIPE) INIT_STATIC_TOKEN_VARIABLE(AMP) INIT_STATIC_TOKEN_VARIABLE(SCHEMA) INIT_STATIC_TOKEN_VARIABLE(SCALAR) INIT_STATIC_TOKEN_VARIABLE(EXTEND) INIT_STATIC_TOKEN_VARIABLE(IMPLEMENTS) INIT_STATIC_TOKEN_VARIABLE(INTERFACE) INIT_STATIC_TOKEN_VARIABLE(UNION) INIT_STATIC_TOKEN_VARIABLE(ENUM) INIT_STATIC_TOKEN_VARIABLE(DIRECTIVE) INIT_STATIC_TOKEN_VARIABLE(INPUT) static VALUE GraphQL_type_str; static VALUE GraphQL_true_str; static VALUE GraphQL_false_str; static VALUE GraphQL_null_str; typedef enum TokenType { AMP, BANG, COLON, DIRECTIVE, DIR_SIGN, ENUM, ELLIPSIS, EQUALS, EXTEND, FALSE_LITERAL, FLOAT, FRAGMENT, IDENTIFIER, INPUT, IMPLEMENTS, INT, INTERFACE, LBRACKET, LCURLY, LPAREN, MUTATION, NULL_LITERAL, ON, PIPE, QUERY, RBRACKET, RCURLY, REPEATABLE, RPAREN, SCALAR, SCHEMA, STRING, SUBSCRIPTION, TRUE_LITERAL, TYPE_LITERAL, UNION, VAR_SIGN, BLOCK_STRING, QUOTED_STRING, UNKNOWN_CHAR, COMMENT, BAD_UNICODE_ESCAPE } TokenType; typedef struct Meta { int line; int col; char *query_cstr; char *pe; VALUE tokens; VALUE previous_token; } Meta; #define STATIC_VALUE_TOKEN(token_type, content_str) \ case token_type: \ token_sym = ID2SYM(rb_intern(#token_type)); \ token_content = GraphQLTokenString##token_type; \ break; #define DYNAMIC_VALUE_TOKEN(token_type) \ case token_type: \ token_sym = ID2SYM(rb_intern(#token_type)); \ token_content = rb_utf8_str_new(ts, te - ts); \ break; void emit(TokenType tt, char *ts, char *te, Meta *meta) { int quotes_length = 0; // set by string tokens below int line_incr = 0; VALUE token_sym = Qnil; VALUE token_content = Qnil; switch(tt) { STATIC_VALUE_TOKEN(ON, "on") STATIC_VALUE_TOKEN(FRAGMENT, "fragment") STATIC_VALUE_TOKEN(QUERY, "query") STATIC_VALUE_TOKEN(MUTATION, "mutation") STATIC_VALUE_TOKEN(SUBSCRIPTION, "subscription") STATIC_VALUE_TOKEN(REPEATABLE, "repeatable") STATIC_VALUE_TOKEN(RCURLY, "}") STATIC_VALUE_TOKEN(LCURLY, "{") STATIC_VALUE_TOKEN(RBRACKET, "]") STATIC_VALUE_TOKEN(LBRACKET, "[") STATIC_VALUE_TOKEN(RPAREN, ")") STATIC_VALUE_TOKEN(LPAREN, "(") STATIC_VALUE_TOKEN(COLON, ":") STATIC_VALUE_TOKEN(VAR_SIGN, "$") STATIC_VALUE_TOKEN(DIR_SIGN, "@") STATIC_VALUE_TOKEN(ELLIPSIS, "...") STATIC_VALUE_TOKEN(EQUALS, "=") STATIC_VALUE_TOKEN(BANG, "!") STATIC_VALUE_TOKEN(PIPE, "|") STATIC_VALUE_TOKEN(AMP, "&") STATIC_VALUE_TOKEN(SCHEMA, "schema") STATIC_VALUE_TOKEN(SCALAR, "scalar") STATIC_VALUE_TOKEN(EXTEND, "extend") STATIC_VALUE_TOKEN(IMPLEMENTS, "implements") STATIC_VALUE_TOKEN(INTERFACE, "interface") STATIC_VALUE_TOKEN(UNION, "union") STATIC_VALUE_TOKEN(ENUM, "enum") STATIC_VALUE_TOKEN(DIRECTIVE, "directive") STATIC_VALUE_TOKEN(INPUT, "input") // For these, the enum name doesn't match the symbol name: case TYPE_LITERAL: token_sym = ID2SYM(rb_intern("TYPE")); token_content = GraphQL_type_str; break; case TRUE_LITERAL: token_sym = ID2SYM(rb_intern("TRUE")); token_content = GraphQL_true_str; break; case FALSE_LITERAL: token_sym = ID2SYM(rb_intern("FALSE")); token_content = GraphQL_false_str; break; case NULL_LITERAL: token_sym = ID2SYM(rb_intern("NULL")); token_content = GraphQL_null_str; break; DYNAMIC_VALUE_TOKEN(IDENTIFIER) DYNAMIC_VALUE_TOKEN(INT) DYNAMIC_VALUE_TOKEN(FLOAT) DYNAMIC_VALUE_TOKEN(COMMENT) case UNKNOWN_CHAR: if (ts[0] == '\0') { return; } else { token_content = rb_utf8_str_new(ts, te - ts); token_sym = ID2SYM(rb_intern("UNKNOWN_CHAR")); break; } case QUOTED_STRING: quotes_length = 1; token_content = rb_utf8_str_new(ts + quotes_length, (te - ts - (2 * quotes_length))); token_sym = ID2SYM(rb_intern("STRING")); break; case BLOCK_STRING: token_sym = ID2SYM(rb_intern("STRING")); quotes_length = 3; token_content = rb_utf8_str_new(ts + quotes_length, (te - ts - (2 * quotes_length))); line_incr = FIX2INT(rb_funcall(token_content, rb_intern("count"), 1, rb_utf8_str_new_cstr("\n"))); break; case STRING: // This is used only by the parser, this is never reached break; } if (token_sym != Qnil) { if (tt == BLOCK_STRING || tt == QUOTED_STRING) { VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mGraphQLLanguage = rb_const_get_at(mGraphQL, rb_intern("Language")); VALUE mGraphQLLanguageLexer = rb_const_get_at(mGraphQLLanguage, rb_intern("Lexer")); VALUE valid_string_pattern = rb_const_get_at(mGraphQLLanguageLexer, rb_intern("VALID_STRING")); if (tt == BLOCK_STRING) { VALUE mGraphQLLanguageBlockString = rb_const_get_at(mGraphQLLanguage, rb_intern("BlockString")); token_content = rb_funcall(mGraphQLLanguageBlockString, rb_intern("trim_whitespace"), 1, token_content); } // The parser doesn't distinguish between these, // Maybe updated below if it's invalid UTF-8 tt = STRING; if ( RB_TEST(rb_funcall(token_content, rb_intern("valid_encoding?"), 0)) && RB_TEST(rb_funcall(token_content, rb_intern("match?"), 1, valid_string_pattern)) ) { rb_funcall(mGraphQLLanguageLexer, rb_intern("replace_escaped_characters_in_place"), 1, token_content); if (!RB_TEST(rb_funcall(token_content, rb_intern("valid_encoding?"), 0))) { token_sym = ID2SYM(rb_intern("BAD_UNICODE_ESCAPE")); tt = BAD_UNICODE_ESCAPE; } } else { token_sym = ID2SYM(rb_intern("BAD_UNICODE_ESCAPE")); tt = BAD_UNICODE_ESCAPE; } } VALUE token = rb_ary_new_from_args(6, token_sym, rb_int2inum(meta->line), rb_int2inum(meta->col), token_content, meta->previous_token, INT2FIX(200 + (int)tt) ); // COMMENTs are retained as `previous_token` but aren't pushed to the normal token list if (tt != COMMENT) { rb_ary_push(meta->tokens, token); } meta->previous_token = token; } // Bump the column counter for the next token meta->col += te - ts; meta->line += line_incr; } VALUE tokenize(VALUE query_rbstr) { int cs = 0; int act = 0; char *p = StringValueCStr(query_rbstr); char *pe = p + strlen(p); char *eof = pe; char *ts = 0; char *te = 0; VALUE tokens = rb_ary_new(); struct Meta meta_s = {1, 1, p, pe, tokens, Qnil}; Meta *meta = &meta_s; #line 938 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { cs = (int)graphql_c_lexer_start; ts = 0; te = 0; act = 0; } #line 354 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" #line 949 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { unsigned int _trans = 0; const char * _keys; const short * _inds; int _ic; _resume: {} if ( p == pe && p != eof ) goto _out; switch ( _graphql_c_lexer_from_state_actions[cs] ) { case 10: { { #line 1 "NONE" {ts = p;}} #line 964 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } } if ( p == eof ) { if ( _graphql_c_lexer_eof_trans[cs] > 0 ) { _trans = (unsigned int)_graphql_c_lexer_eof_trans[cs] - 1; } } else { _keys = ( _graphql_c_lexer_trans_keys + ((cs<<1))); _inds = ( _graphql_c_lexer_indices + (_graphql_c_lexer_index_offsets[cs])); if ( ( (*( p))) <= 125 && ( (*( p))) >= 9 ) { _ic = (int)_graphql_c_lexer_char_class[(int)( (*( p))) - 9]; if ( _ic <= (int)(*( _keys+1)) && _ic >= (int)(*( _keys)) ) _trans = (unsigned int)(*( _inds + (int)( _ic - (int)(*( _keys)) ) )); else _trans = (unsigned int)_graphql_c_lexer_index_defaults[cs]; } else { _trans = (unsigned int)_graphql_c_lexer_index_defaults[cs]; } } cs = (int)_graphql_c_lexer_cond_targs[_trans]; if ( _graphql_c_lexer_cond_actions[_trans] != 0 ) { switch ( _graphql_c_lexer_cond_actions[_trans] ) { case 6: { { #line 1 "NONE" {te = p+1;}} #line 1002 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 26: { { #line 75 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 75 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(RCURLY, ts, te, meta); } }} #line 1015 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 24: { { #line 76 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 76 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(LCURLY, ts, te, meta); } }} #line 1028 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 17: { { #line 77 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 77 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(RPAREN, ts, te, meta); } }} #line 1041 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 16: { { #line 78 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 78 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(LPAREN, ts, te, meta); } }} #line 1054 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 23: { { #line 79 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 79 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(RBRACKET, ts, te, meta); } }} #line 1067 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 22: { { #line 80 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 80 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(LBRACKET, ts, te, meta); } }} #line 1080 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 18: { { #line 81 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 81 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(COLON, ts, te, meta); } }} #line 1093 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 32: { { #line 82 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 82 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(BLOCK_STRING, ts, te, meta); } }} #line 1106 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 2: { { #line 83 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 83 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(QUOTED_STRING, ts, te, meta); } }} #line 1119 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 14: { { #line 84 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 84 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(VAR_SIGN, ts, te, meta); } }} #line 1132 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 20: { { #line 85 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 85 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(DIR_SIGN, ts, te, meta); } }} #line 1145 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 8: { { #line 86 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 86 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(ELLIPSIS, ts, te, meta); } }} #line 1158 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 19: { { #line 87 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 87 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(EQUALS, ts, te, meta); } }} #line 1171 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 13: { { #line 88 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 88 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(BANG, ts, te, meta); } }} #line 1184 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 25: { { #line 89 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 89 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(PIPE, ts, te, meta); } }} #line 1197 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 15: { { #line 90 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 90 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(AMP, ts, te, meta); } }} #line 1210 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 12: { { #line 93 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 93 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" meta->line += 1; meta->col = 1; } }} #line 1226 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 11: { { #line 100 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p+1;{ #line 100 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(UNKNOWN_CHAR, ts, te, meta); } }} #line 1239 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 34: { { #line 54 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 54 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(INT, ts, te, meta); } }} #line 1252 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 35: { { #line 55 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 55 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(FLOAT, ts, te, meta); } }} #line 1265 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 31: { { #line 82 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 82 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(BLOCK_STRING, ts, te, meta); } }} #line 1278 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 30: { { #line 83 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 83 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(QUOTED_STRING, ts, te, meta); } }} #line 1291 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 36: { { #line 91 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 91 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(IDENTIFIER, ts, te, meta); } }} #line 1304 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 33: { { #line 92 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 92 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(COMMENT, ts, te, meta); } }} #line 1317 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 27: { { #line 98 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 98 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" meta->col += te - ts; } }} #line 1330 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 28: { { #line 100 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {te = p;p = p - 1;{ #line 100 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(UNKNOWN_CHAR, ts, te, meta); } }} #line 1343 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 5: { { #line 54 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {p = ((te))-1; { #line 54 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(INT, ts, te, meta); } }} #line 1357 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 7: { { #line 55 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {p = ((te))-1; { #line 55 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(FLOAT, ts, te, meta); } }} #line 1371 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 1: { { #line 100 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {p = ((te))-1; { #line 100 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(UNKNOWN_CHAR, ts, te, meta); } }} #line 1385 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 3: { { #line 1 "NONE" {switch( act ) { case 3: { p = ((te))-1; { #line 56 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(ON, ts, te, meta); } break; } case 4: { p = ((te))-1; { #line 57 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(FRAGMENT, ts, te, meta); } break; } case 5: { p = ((te))-1; { #line 58 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(TRUE_LITERAL, ts, te, meta); } break; } case 6: { p = ((te))-1; { #line 59 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(FALSE_LITERAL, ts, te, meta); } break; } case 7: { p = ((te))-1; { #line 60 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(NULL_LITERAL, ts, te, meta); } break; } case 8: { p = ((te))-1; { #line 61 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(QUERY, ts, te, meta); } break; } case 9: { p = ((te))-1; { #line 62 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(MUTATION, ts, te, meta); } break; } case 10: { p = ((te))-1; { #line 63 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(SUBSCRIPTION, ts, te, meta); } break; } case 11: { p = ((te))-1; { #line 64 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(SCHEMA, ts, te, meta); } break; } case 12: { p = ((te))-1; { #line 65 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(SCALAR, ts, te, meta); } break; } case 13: { p = ((te))-1; { #line 66 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(TYPE_LITERAL, ts, te, meta); } break; } case 14: { p = ((te))-1; { #line 67 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(EXTEND, ts, te, meta); } break; } case 15: { p = ((te))-1; { #line 68 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(IMPLEMENTS, ts, te, meta); } break; } case 16: { p = ((te))-1; { #line 69 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(INTERFACE, ts, te, meta); } break; } case 17: { p = ((te))-1; { #line 70 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(UNION, ts, te, meta); } break; } case 18: { p = ((te))-1; { #line 71 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(ENUM, ts, te, meta); } break; } case 19: { p = ((te))-1; { #line 72 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(INPUT, ts, te, meta); } break; } case 20: { p = ((te))-1; { #line 73 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(DIRECTIVE, ts, te, meta); } break; } case 21: { p = ((te))-1; { #line 74 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(REPEATABLE, ts, te, meta); } break; } case 29: { p = ((te))-1; { #line 82 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(BLOCK_STRING, ts, te, meta); } break; } case 30: { p = ((te))-1; { #line 83 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(QUOTED_STRING, ts, te, meta); } break; } case 38: { p = ((te))-1; { #line 91 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" emit(IDENTIFIER, ts, te, meta); } break; } }} } #line 1551 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 47: { { #line 1 "NONE" {te = p+1;}} #line 1561 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 56 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 3;}} #line 1567 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 41: { { #line 1 "NONE" {te = p+1;}} #line 1577 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 57 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 4;}} #line 1583 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 53: { { #line 1 "NONE" {te = p+1;}} #line 1593 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 58 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 5;}} #line 1599 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 40: { { #line 1 "NONE" {te = p+1;}} #line 1609 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 59 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 6;}} #line 1615 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 46: { { #line 1 "NONE" {te = p+1;}} #line 1625 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 60 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 7;}} #line 1631 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 48: { { #line 1 "NONE" {te = p+1;}} #line 1641 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 61 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 8;}} #line 1647 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 45: { { #line 1 "NONE" {te = p+1;}} #line 1657 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 62 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 9;}} #line 1663 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 52: { { #line 1 "NONE" {te = p+1;}} #line 1673 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 63 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 10;}} #line 1679 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 51: { { #line 1 "NONE" {te = p+1;}} #line 1689 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 64 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 11;}} #line 1695 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 50: { { #line 1 "NONE" {te = p+1;}} #line 1705 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 65 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 12;}} #line 1711 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 54: { { #line 1 "NONE" {te = p+1;}} #line 1721 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 66 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 13;}} #line 1727 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 39: { { #line 1 "NONE" {te = p+1;}} #line 1737 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 67 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 14;}} #line 1743 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 42: { { #line 1 "NONE" {te = p+1;}} #line 1753 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 68 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 15;}} #line 1759 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 44: { { #line 1 "NONE" {te = p+1;}} #line 1769 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 69 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 16;}} #line 1775 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 55: { { #line 1 "NONE" {te = p+1;}} #line 1785 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 70 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 17;}} #line 1791 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 38: { { #line 1 "NONE" {te = p+1;}} #line 1801 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 71 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 18;}} #line 1807 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 43: { { #line 1 "NONE" {te = p+1;}} #line 1817 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 72 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 19;}} #line 1823 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 37: { { #line 1 "NONE" {te = p+1;}} #line 1833 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 73 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 20;}} #line 1839 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 49: { { #line 1 "NONE" {te = p+1;}} #line 1849 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 74 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 21;}} #line 1855 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 4: { { #line 1 "NONE" {te = p+1;}} #line 1865 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 82 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 29;}} #line 1871 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 29: { { #line 1 "NONE" {te = p+1;}} #line 1881 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 83 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 30;}} #line 1887 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } case 21: { { #line 1 "NONE" {te = p+1;}} #line 1897 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" { #line 91 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" {act = 38;}} #line 1903 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } } } if ( p == eof ) { if ( cs >= 21 ) goto _out; } else { switch ( _graphql_c_lexer_to_state_actions[cs] ) { case 9: { { #line 1 "NONE" {ts = 0;}} #line 1923 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.c" break; } } p += 1; goto _resume; } _out: {} } #line 355 "graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl" return tokens; } #define SETUP_STATIC_TOKEN_VARIABLE(token_name, token_content) \ GraphQLTokenString##token_name = rb_utf8_str_new_cstr(token_content); \ rb_funcall(GraphQLTokenString##token_name, rb_intern("-@"), 0); \ rb_global_variable(&GraphQLTokenString##token_name); \ #define SETUP_STATIC_STRING(var_name, str_content) \ var_name = rb_utf8_str_new_cstr(str_content); \ rb_global_variable(&var_name); \ rb_str_freeze(var_name); \ void setup_static_token_variables() { SETUP_STATIC_TOKEN_VARIABLE(ON, "on") SETUP_STATIC_TOKEN_VARIABLE(FRAGMENT, "fragment") SETUP_STATIC_TOKEN_VARIABLE(QUERY, "query") SETUP_STATIC_TOKEN_VARIABLE(MUTATION, "mutation") SETUP_STATIC_TOKEN_VARIABLE(SUBSCRIPTION, "subscription") SETUP_STATIC_TOKEN_VARIABLE(REPEATABLE, "repeatable") SETUP_STATIC_TOKEN_VARIABLE(RCURLY, "}") SETUP_STATIC_TOKEN_VARIABLE(LCURLY, "{") SETUP_STATIC_TOKEN_VARIABLE(RBRACKET, "]") SETUP_STATIC_TOKEN_VARIABLE(LBRACKET, "[") SETUP_STATIC_TOKEN_VARIABLE(RPAREN, ")") SETUP_STATIC_TOKEN_VARIABLE(LPAREN, "(") SETUP_STATIC_TOKEN_VARIABLE(COLON, ":") SETUP_STATIC_TOKEN_VARIABLE(VAR_SIGN, "$") SETUP_STATIC_TOKEN_VARIABLE(DIR_SIGN, "@") SETUP_STATIC_TOKEN_VARIABLE(ELLIPSIS, "...") SETUP_STATIC_TOKEN_VARIABLE(EQUALS, "=") SETUP_STATIC_TOKEN_VARIABLE(BANG, "!") SETUP_STATIC_TOKEN_VARIABLE(PIPE, "|") SETUP_STATIC_TOKEN_VARIABLE(AMP, "&") SETUP_STATIC_TOKEN_VARIABLE(SCHEMA, "schema") SETUP_STATIC_TOKEN_VARIABLE(SCALAR, "scalar") SETUP_STATIC_TOKEN_VARIABLE(EXTEND, "extend") SETUP_STATIC_TOKEN_VARIABLE(IMPLEMENTS, "implements") SETUP_STATIC_TOKEN_VARIABLE(INTERFACE, "interface") SETUP_STATIC_TOKEN_VARIABLE(UNION, "union") SETUP_STATIC_TOKEN_VARIABLE(ENUM, "enum") SETUP_STATIC_TOKEN_VARIABLE(DIRECTIVE, "directive") SETUP_STATIC_TOKEN_VARIABLE(INPUT, "input") SETUP_STATIC_STRING(GraphQL_type_str, "type") SETUP_STATIC_STRING(GraphQL_true_str, "true") SETUP_STATIC_STRING(GraphQL_false_str, "false") SETUP_STATIC_STRING(GraphQL_null_str, "null") } graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/lexer.h000066400000000000000000000002211476434635200257470ustar00rootroot00000000000000#ifndef Graphql_lexer_h #define Graphql_lexer_h #include VALUE tokenize(VALUE query_rbstr); void setup_static_token_variables(); #endif graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/lexer.rl000066400000000000000000000321761476434635200261530ustar00rootroot00000000000000%%{ machine graphql_c_lexer; IDENTIFIER = [_A-Za-z][_0-9A-Za-z]*; NEWLINE = [\c\r\n]; BLANK = [, \t]+; COMMENT = '#' [^\n\r]*; INT = '-'? ('0'|[1-9][0-9]*); FLOAT = INT ('.'[0-9]+) (('e' | 'E')?('+' | '-')?[0-9]+)?; ON = 'on'; FRAGMENT = 'fragment'; TRUE_LITERAL = 'true'; FALSE_LITERAL = 'false'; NULL_LITERAL = 'null'; QUERY = 'query'; MUTATION = 'mutation'; SUBSCRIPTION = 'subscription'; SCHEMA = 'schema'; SCALAR = 'scalar'; TYPE_LITERAL = 'type'; EXTEND = 'extend'; IMPLEMENTS = 'implements'; INTERFACE = 'interface'; UNION = 'union'; ENUM = 'enum'; INPUT = 'input'; DIRECTIVE = 'directive'; REPEATABLE = 'repeatable'; LCURLY = '{'; RCURLY = '}'; LPAREN = '('; RPAREN = ')'; LBRACKET = '['; RBRACKET = ']'; COLON = ':'; # Could limit to hex here, but “bad unicode escape” on 0XXF is probably a # more helpful error than “unknown char” UNICODE_ESCAPE = "\\u" ([0-9A-Za-z]{4} | LCURLY [0-9A-Za-z]{4,} RCURLY); VAR_SIGN = '$'; DIR_SIGN = '@'; ELLIPSIS = '...'; EQUALS = '='; BANG = '!'; PIPE = '|'; AMP = '&'; QUOTED_STRING = ('"' ((('\\"' | ^'"') - "\\") | UNICODE_ESCAPE | '\\' [\\/bfnrt])* '"'); # catch-all for anything else. must be at the bottom for precedence. UNKNOWN_CHAR = /./; BLOCK_STRING = ('"""' ('\\"""' | ^'"' | '"'{1,2} ^'"')* '"'{0,2} '"""'); main := |* INT => { emit(INT, ts, te, meta); }; FLOAT => { emit(FLOAT, ts, te, meta); }; ON => { emit(ON, ts, te, meta); }; FRAGMENT => { emit(FRAGMENT, ts, te, meta); }; TRUE_LITERAL => { emit(TRUE_LITERAL, ts, te, meta); }; FALSE_LITERAL => { emit(FALSE_LITERAL, ts, te, meta); }; NULL_LITERAL => { emit(NULL_LITERAL, ts, te, meta); }; QUERY => { emit(QUERY, ts, te, meta); }; MUTATION => { emit(MUTATION, ts, te, meta); }; SUBSCRIPTION => { emit(SUBSCRIPTION, ts, te, meta); }; SCHEMA => { emit(SCHEMA, ts, te, meta); }; SCALAR => { emit(SCALAR, ts, te, meta); }; TYPE_LITERAL => { emit(TYPE_LITERAL, ts, te, meta); }; EXTEND => { emit(EXTEND, ts, te, meta); }; IMPLEMENTS => { emit(IMPLEMENTS, ts, te, meta); }; INTERFACE => { emit(INTERFACE, ts, te, meta); }; UNION => { emit(UNION, ts, te, meta); }; ENUM => { emit(ENUM, ts, te, meta); }; INPUT => { emit(INPUT, ts, te, meta); }; DIRECTIVE => { emit(DIRECTIVE, ts, te, meta); }; REPEATABLE => { emit(REPEATABLE, ts, te, meta); }; RCURLY => { emit(RCURLY, ts, te, meta); }; LCURLY => { emit(LCURLY, ts, te, meta); }; RPAREN => { emit(RPAREN, ts, te, meta); }; LPAREN => { emit(LPAREN, ts, te, meta); }; RBRACKET => { emit(RBRACKET, ts, te, meta); }; LBRACKET => { emit(LBRACKET, ts, te, meta); }; COLON => { emit(COLON, ts, te, meta); }; BLOCK_STRING => { emit(BLOCK_STRING, ts, te, meta); }; QUOTED_STRING => { emit(QUOTED_STRING, ts, te, meta); }; VAR_SIGN => { emit(VAR_SIGN, ts, te, meta); }; DIR_SIGN => { emit(DIR_SIGN, ts, te, meta); }; ELLIPSIS => { emit(ELLIPSIS, ts, te, meta); }; EQUALS => { emit(EQUALS, ts, te, meta); }; BANG => { emit(BANG, ts, te, meta); }; PIPE => { emit(PIPE, ts, te, meta); }; AMP => { emit(AMP, ts, te, meta); }; IDENTIFIER => { emit(IDENTIFIER, ts, te, meta); }; COMMENT => { emit(COMMENT, ts, te, meta); }; NEWLINE => { meta->line += 1; meta->col = 1; }; BLANK => { meta->col += te - ts; }; UNKNOWN_CHAR => { emit(UNKNOWN_CHAR, ts, te, meta); }; *|; }%% %% write data; #include #define INIT_STATIC_TOKEN_VARIABLE(token_name) \ static VALUE GraphQLTokenString##token_name; INIT_STATIC_TOKEN_VARIABLE(ON) INIT_STATIC_TOKEN_VARIABLE(FRAGMENT) INIT_STATIC_TOKEN_VARIABLE(QUERY) INIT_STATIC_TOKEN_VARIABLE(MUTATION) INIT_STATIC_TOKEN_VARIABLE(SUBSCRIPTION) INIT_STATIC_TOKEN_VARIABLE(REPEATABLE) INIT_STATIC_TOKEN_VARIABLE(RCURLY) INIT_STATIC_TOKEN_VARIABLE(LCURLY) INIT_STATIC_TOKEN_VARIABLE(RBRACKET) INIT_STATIC_TOKEN_VARIABLE(LBRACKET) INIT_STATIC_TOKEN_VARIABLE(RPAREN) INIT_STATIC_TOKEN_VARIABLE(LPAREN) INIT_STATIC_TOKEN_VARIABLE(COLON) INIT_STATIC_TOKEN_VARIABLE(VAR_SIGN) INIT_STATIC_TOKEN_VARIABLE(DIR_SIGN) INIT_STATIC_TOKEN_VARIABLE(ELLIPSIS) INIT_STATIC_TOKEN_VARIABLE(EQUALS) INIT_STATIC_TOKEN_VARIABLE(BANG) INIT_STATIC_TOKEN_VARIABLE(PIPE) INIT_STATIC_TOKEN_VARIABLE(AMP) INIT_STATIC_TOKEN_VARIABLE(SCHEMA) INIT_STATIC_TOKEN_VARIABLE(SCALAR) INIT_STATIC_TOKEN_VARIABLE(EXTEND) INIT_STATIC_TOKEN_VARIABLE(IMPLEMENTS) INIT_STATIC_TOKEN_VARIABLE(INTERFACE) INIT_STATIC_TOKEN_VARIABLE(UNION) INIT_STATIC_TOKEN_VARIABLE(ENUM) INIT_STATIC_TOKEN_VARIABLE(DIRECTIVE) INIT_STATIC_TOKEN_VARIABLE(INPUT) static VALUE GraphQL_type_str; static VALUE GraphQL_true_str; static VALUE GraphQL_false_str; static VALUE GraphQL_null_str; typedef enum TokenType { AMP, BANG, COLON, DIRECTIVE, DIR_SIGN, ENUM, ELLIPSIS, EQUALS, EXTEND, FALSE_LITERAL, FLOAT, FRAGMENT, IDENTIFIER, INPUT, IMPLEMENTS, INT, INTERFACE, LBRACKET, LCURLY, LPAREN, MUTATION, NULL_LITERAL, ON, PIPE, QUERY, RBRACKET, RCURLY, REPEATABLE, RPAREN, SCALAR, SCHEMA, STRING, SUBSCRIPTION, TRUE_LITERAL, TYPE_LITERAL, UNION, VAR_SIGN, BLOCK_STRING, QUOTED_STRING, UNKNOWN_CHAR, COMMENT, BAD_UNICODE_ESCAPE } TokenType; typedef struct Meta { int line; int col; char *query_cstr; char *pe; VALUE tokens; VALUE previous_token; } Meta; #define STATIC_VALUE_TOKEN(token_type, content_str) \ case token_type: \ token_sym = ID2SYM(rb_intern(#token_type)); \ token_content = GraphQLTokenString##token_type; \ break; #define DYNAMIC_VALUE_TOKEN(token_type) \ case token_type: \ token_sym = ID2SYM(rb_intern(#token_type)); \ token_content = rb_utf8_str_new(ts, te - ts); \ break; void emit(TokenType tt, char *ts, char *te, Meta *meta) { int quotes_length = 0; // set by string tokens below int line_incr = 0; VALUE token_sym = Qnil; VALUE token_content = Qnil; switch(tt) { STATIC_VALUE_TOKEN(ON, "on") STATIC_VALUE_TOKEN(FRAGMENT, "fragment") STATIC_VALUE_TOKEN(QUERY, "query") STATIC_VALUE_TOKEN(MUTATION, "mutation") STATIC_VALUE_TOKEN(SUBSCRIPTION, "subscription") STATIC_VALUE_TOKEN(REPEATABLE, "repeatable") STATIC_VALUE_TOKEN(RCURLY, "}") STATIC_VALUE_TOKEN(LCURLY, "{") STATIC_VALUE_TOKEN(RBRACKET, "]") STATIC_VALUE_TOKEN(LBRACKET, "[") STATIC_VALUE_TOKEN(RPAREN, ")") STATIC_VALUE_TOKEN(LPAREN, "(") STATIC_VALUE_TOKEN(COLON, ":") STATIC_VALUE_TOKEN(VAR_SIGN, "$") STATIC_VALUE_TOKEN(DIR_SIGN, "@") STATIC_VALUE_TOKEN(ELLIPSIS, "...") STATIC_VALUE_TOKEN(EQUALS, "=") STATIC_VALUE_TOKEN(BANG, "!") STATIC_VALUE_TOKEN(PIPE, "|") STATIC_VALUE_TOKEN(AMP, "&") STATIC_VALUE_TOKEN(SCHEMA, "schema") STATIC_VALUE_TOKEN(SCALAR, "scalar") STATIC_VALUE_TOKEN(EXTEND, "extend") STATIC_VALUE_TOKEN(IMPLEMENTS, "implements") STATIC_VALUE_TOKEN(INTERFACE, "interface") STATIC_VALUE_TOKEN(UNION, "union") STATIC_VALUE_TOKEN(ENUM, "enum") STATIC_VALUE_TOKEN(DIRECTIVE, "directive") STATIC_VALUE_TOKEN(INPUT, "input") // For these, the enum name doesn't match the symbol name: case TYPE_LITERAL: token_sym = ID2SYM(rb_intern("TYPE")); token_content = GraphQL_type_str; break; case TRUE_LITERAL: token_sym = ID2SYM(rb_intern("TRUE")); token_content = GraphQL_true_str; break; case FALSE_LITERAL: token_sym = ID2SYM(rb_intern("FALSE")); token_content = GraphQL_false_str; break; case NULL_LITERAL: token_sym = ID2SYM(rb_intern("NULL")); token_content = GraphQL_null_str; break; DYNAMIC_VALUE_TOKEN(IDENTIFIER) DYNAMIC_VALUE_TOKEN(INT) DYNAMIC_VALUE_TOKEN(FLOAT) DYNAMIC_VALUE_TOKEN(COMMENT) case UNKNOWN_CHAR: if (ts[0] == '\0') { return; } else { token_content = rb_utf8_str_new(ts, te - ts); token_sym = ID2SYM(rb_intern("UNKNOWN_CHAR")); break; } case QUOTED_STRING: quotes_length = 1; token_content = rb_utf8_str_new(ts + quotes_length, (te - ts - (2 * quotes_length))); token_sym = ID2SYM(rb_intern("STRING")); break; case BLOCK_STRING: token_sym = ID2SYM(rb_intern("STRING")); quotes_length = 3; token_content = rb_utf8_str_new(ts + quotes_length, (te - ts - (2 * quotes_length))); line_incr = FIX2INT(rb_funcall(token_content, rb_intern("count"), 1, rb_utf8_str_new_cstr("\n"))); break; case STRING: // This is used only by the parser, this is never reached break; } if (token_sym != Qnil) { if (tt == BLOCK_STRING || tt == QUOTED_STRING) { VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mGraphQLLanguage = rb_const_get_at(mGraphQL, rb_intern("Language")); VALUE mGraphQLLanguageLexer = rb_const_get_at(mGraphQLLanguage, rb_intern("Lexer")); VALUE valid_string_pattern = rb_const_get_at(mGraphQLLanguageLexer, rb_intern("VALID_STRING")); if (tt == BLOCK_STRING) { VALUE mGraphQLLanguageBlockString = rb_const_get_at(mGraphQLLanguage, rb_intern("BlockString")); token_content = rb_funcall(mGraphQLLanguageBlockString, rb_intern("trim_whitespace"), 1, token_content); } // The parser doesn't distinguish between these, // Maybe updated below if it's invalid UTF-8 tt = STRING; if ( RB_TEST(rb_funcall(token_content, rb_intern("valid_encoding?"), 0)) && RB_TEST(rb_funcall(token_content, rb_intern("match?"), 1, valid_string_pattern)) ) { rb_funcall(mGraphQLLanguageLexer, rb_intern("replace_escaped_characters_in_place"), 1, token_content); if (!RB_TEST(rb_funcall(token_content, rb_intern("valid_encoding?"), 0))) { token_sym = ID2SYM(rb_intern("BAD_UNICODE_ESCAPE")); tt = BAD_UNICODE_ESCAPE; } } else { token_sym = ID2SYM(rb_intern("BAD_UNICODE_ESCAPE")); tt = BAD_UNICODE_ESCAPE; } } VALUE token = rb_ary_new_from_args(6, token_sym, rb_int2inum(meta->line), rb_int2inum(meta->col), token_content, meta->previous_token, INT2FIX(200 + (int)tt) ); // COMMENTs are retained as `previous_token` but aren't pushed to the normal token list if (tt != COMMENT) { rb_ary_push(meta->tokens, token); } meta->previous_token = token; } // Bump the column counter for the next token meta->col += te - ts; meta->line += line_incr; } VALUE tokenize(VALUE query_rbstr) { int cs = 0; int act = 0; char *p = StringValueCStr(query_rbstr); char *pe = p + strlen(p); char *eof = pe; char *ts = 0; char *te = 0; VALUE tokens = rb_ary_new(); struct Meta meta_s = {1, 1, p, pe, tokens, Qnil}; Meta *meta = &meta_s; %% write init; %% write exec; return tokens; } #define SETUP_STATIC_TOKEN_VARIABLE(token_name, token_content) \ GraphQLTokenString##token_name = rb_utf8_str_new_cstr(token_content); \ rb_funcall(GraphQLTokenString##token_name, rb_intern("-@"), 0); \ rb_global_variable(&GraphQLTokenString##token_name); \ #define SETUP_STATIC_STRING(var_name, str_content) \ var_name = rb_utf8_str_new_cstr(str_content); \ rb_global_variable(&var_name); \ rb_str_freeze(var_name); \ void setup_static_token_variables() { SETUP_STATIC_TOKEN_VARIABLE(ON, "on") SETUP_STATIC_TOKEN_VARIABLE(FRAGMENT, "fragment") SETUP_STATIC_TOKEN_VARIABLE(QUERY, "query") SETUP_STATIC_TOKEN_VARIABLE(MUTATION, "mutation") SETUP_STATIC_TOKEN_VARIABLE(SUBSCRIPTION, "subscription") SETUP_STATIC_TOKEN_VARIABLE(REPEATABLE, "repeatable") SETUP_STATIC_TOKEN_VARIABLE(RCURLY, "}") SETUP_STATIC_TOKEN_VARIABLE(LCURLY, "{") SETUP_STATIC_TOKEN_VARIABLE(RBRACKET, "]") SETUP_STATIC_TOKEN_VARIABLE(LBRACKET, "[") SETUP_STATIC_TOKEN_VARIABLE(RPAREN, ")") SETUP_STATIC_TOKEN_VARIABLE(LPAREN, "(") SETUP_STATIC_TOKEN_VARIABLE(COLON, ":") SETUP_STATIC_TOKEN_VARIABLE(VAR_SIGN, "$") SETUP_STATIC_TOKEN_VARIABLE(DIR_SIGN, "@") SETUP_STATIC_TOKEN_VARIABLE(ELLIPSIS, "...") SETUP_STATIC_TOKEN_VARIABLE(EQUALS, "=") SETUP_STATIC_TOKEN_VARIABLE(BANG, "!") SETUP_STATIC_TOKEN_VARIABLE(PIPE, "|") SETUP_STATIC_TOKEN_VARIABLE(AMP, "&") SETUP_STATIC_TOKEN_VARIABLE(SCHEMA, "schema") SETUP_STATIC_TOKEN_VARIABLE(SCALAR, "scalar") SETUP_STATIC_TOKEN_VARIABLE(EXTEND, "extend") SETUP_STATIC_TOKEN_VARIABLE(IMPLEMENTS, "implements") SETUP_STATIC_TOKEN_VARIABLE(INTERFACE, "interface") SETUP_STATIC_TOKEN_VARIABLE(UNION, "union") SETUP_STATIC_TOKEN_VARIABLE(ENUM, "enum") SETUP_STATIC_TOKEN_VARIABLE(DIRECTIVE, "directive") SETUP_STATIC_TOKEN_VARIABLE(INPUT, "input") SETUP_STATIC_STRING(GraphQL_type_str, "type") SETUP_STATIC_STRING(GraphQL_true_str, "true") SETUP_STATIC_STRING(GraphQL_false_str, "false") SETUP_STATIC_STRING(GraphQL_null_str, "null") } graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/parser.c000066400000000000000000004105641476434635200261360ustar00rootroot00000000000000/* A Bison parser, made by GNU Bison 3.8.2. */ /* Bison implementation for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, especially those whose name start with YY_ or yy_. They are private implementation details that can be changed or removed. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Identify Bison output, and Bison version. */ #define YYBISON 30802 /* Bison version string. */ #define YYBISON_VERSION "3.8.2" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 2 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* First part of user prologue. */ #line 5 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" // C Declarations #include #define YYSTYPE VALUE int yylex(YYSTYPE *, VALUE, VALUE); void yyerror(VALUE, VALUE, const char*); static VALUE GraphQL_Language_Nodes_NONE; static VALUE r_string_query; #define MAKE_AST_NODE(node_class_name, nargs, ...) rb_funcall(GraphQL_Language_Nodes_##node_class_name, rb_intern("from_a"), nargs + 1, filename,__VA_ARGS__) #define SETUP_NODE_CLASS_VARIABLE(node_class_name) static VALUE GraphQL_Language_Nodes_##node_class_name; SETUP_NODE_CLASS_VARIABLE(Argument) SETUP_NODE_CLASS_VARIABLE(Directive) SETUP_NODE_CLASS_VARIABLE(Document) SETUP_NODE_CLASS_VARIABLE(Enum) SETUP_NODE_CLASS_VARIABLE(Field) SETUP_NODE_CLASS_VARIABLE(FragmentDefinition) SETUP_NODE_CLASS_VARIABLE(FragmentSpread) SETUP_NODE_CLASS_VARIABLE(InlineFragment) SETUP_NODE_CLASS_VARIABLE(InputObject) SETUP_NODE_CLASS_VARIABLE(ListType) SETUP_NODE_CLASS_VARIABLE(NonNullType) SETUP_NODE_CLASS_VARIABLE(NullValue) SETUP_NODE_CLASS_VARIABLE(OperationDefinition) SETUP_NODE_CLASS_VARIABLE(TypeName) SETUP_NODE_CLASS_VARIABLE(VariableDefinition) SETUP_NODE_CLASS_VARIABLE(VariableIdentifier) SETUP_NODE_CLASS_VARIABLE(ScalarTypeDefinition) SETUP_NODE_CLASS_VARIABLE(ObjectTypeDefinition) SETUP_NODE_CLASS_VARIABLE(InterfaceTypeDefinition) SETUP_NODE_CLASS_VARIABLE(UnionTypeDefinition) SETUP_NODE_CLASS_VARIABLE(EnumTypeDefinition) SETUP_NODE_CLASS_VARIABLE(InputObjectTypeDefinition) SETUP_NODE_CLASS_VARIABLE(EnumValueDefinition) SETUP_NODE_CLASS_VARIABLE(DirectiveDefinition) SETUP_NODE_CLASS_VARIABLE(DirectiveLocation) SETUP_NODE_CLASS_VARIABLE(FieldDefinition) SETUP_NODE_CLASS_VARIABLE(InputValueDefinition) SETUP_NODE_CLASS_VARIABLE(SchemaDefinition) SETUP_NODE_CLASS_VARIABLE(ScalarTypeExtension) SETUP_NODE_CLASS_VARIABLE(ObjectTypeExtension) SETUP_NODE_CLASS_VARIABLE(InterfaceTypeExtension) SETUP_NODE_CLASS_VARIABLE(UnionTypeExtension) SETUP_NODE_CLASS_VARIABLE(EnumTypeExtension) SETUP_NODE_CLASS_VARIABLE(InputObjectTypeExtension) SETUP_NODE_CLASS_VARIABLE(SchemaExtension) #line 124 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" # ifndef YY_CAST # ifdef __cplusplus # define YY_CAST(Type, Val) static_cast (Val) # define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) # else # define YY_CAST(Type, Val) ((Type) (Val)) # define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) # endif # endif # ifndef YY_NULLPTR # if defined __cplusplus # if 201103L <= __cplusplus # define YY_NULLPTR nullptr # else # define YY_NULLPTR 0 # endif # else # define YY_NULLPTR ((void*)0) # endif # endif /* Debug traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif #if YYDEBUG extern int yydebug; #endif /* Token kinds. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE enum yytokentype { YYEMPTY = -2, YYEOF = 0, /* "end of file" */ YYerror = 256, /* error */ YYUNDEF = 257, /* "invalid token" */ AMP = 200, /* AMP */ BANG = 201, /* BANG */ COLON = 202, /* COLON */ DIRECTIVE = 203, /* DIRECTIVE */ DIR_SIGN = 204, /* DIR_SIGN */ ENUM = 205, /* ENUM */ ELLIPSIS = 206, /* ELLIPSIS */ EQUALS = 207, /* EQUALS */ EXTEND = 208, /* EXTEND */ FALSE_LITERAL = 209, /* FALSE_LITERAL */ FLOAT = 210, /* FLOAT */ FRAGMENT = 211, /* FRAGMENT */ IDENTIFIER = 212, /* IDENTIFIER */ INPUT = 213, /* INPUT */ IMPLEMENTS = 214, /* IMPLEMENTS */ INT = 215, /* INT */ INTERFACE = 216, /* INTERFACE */ LBRACKET = 217, /* LBRACKET */ LCURLY = 218, /* LCURLY */ LPAREN = 219, /* LPAREN */ MUTATION = 220, /* MUTATION */ NULL_LITERAL = 221, /* NULL_LITERAL */ ON = 222, /* ON */ PIPE = 223, /* PIPE */ QUERY = 224, /* QUERY */ RBRACKET = 225, /* RBRACKET */ RCURLY = 226, /* RCURLY */ REPEATABLE = 227, /* REPEATABLE */ RPAREN = 228, /* RPAREN */ SCALAR = 229, /* SCALAR */ SCHEMA = 230, /* SCHEMA */ STRING = 231, /* STRING */ SUBSCRIPTION = 232, /* SUBSCRIPTION */ TRUE_LITERAL = 233, /* TRUE_LITERAL */ TYPE_LITERAL = 234, /* TYPE_LITERAL */ UNION = 235, /* UNION */ VAR_SIGN = 236 /* VAR_SIGN */ }; typedef enum yytokentype yytoken_kind_t; #endif /* Token kinds. */ #define YYEMPTY -2 #define YYEOF 0 #define YYerror 256 #define YYUNDEF 257 #define AMP 200 #define BANG 201 #define COLON 202 #define DIRECTIVE 203 #define DIR_SIGN 204 #define ENUM 205 #define ELLIPSIS 206 #define EQUALS 207 #define EXTEND 208 #define FALSE_LITERAL 209 #define FLOAT 210 #define FRAGMENT 211 #define IDENTIFIER 212 #define INPUT 213 #define IMPLEMENTS 214 #define INT 215 #define INTERFACE 216 #define LBRACKET 217 #define LCURLY 218 #define LPAREN 219 #define MUTATION 220 #define NULL_LITERAL 221 #define ON 222 #define PIPE 223 #define QUERY 224 #define RBRACKET 225 #define RCURLY 226 #define REPEATABLE 227 #define RPAREN 228 #define SCALAR 229 #define SCHEMA 230 #define STRING 231 #define SUBSCRIPTION 232 #define TRUE_LITERAL 233 #define TYPE_LITERAL 234 #define UNION 235 #define VAR_SIGN 236 /* Value type. */ #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef int YYSTYPE; # define YYSTYPE_IS_TRIVIAL 1 # define YYSTYPE_IS_DECLARED 1 #endif int yyparse (VALUE parser, VALUE filename); /* Symbol kind. */ enum yysymbol_kind_t { YYSYMBOL_YYEMPTY = -2, YYSYMBOL_YYEOF = 0, /* "end of file" */ YYSYMBOL_YYerror = 1, /* error */ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ YYSYMBOL_AMP = 3, /* AMP */ YYSYMBOL_BANG = 4, /* BANG */ YYSYMBOL_COLON = 5, /* COLON */ YYSYMBOL_DIRECTIVE = 6, /* DIRECTIVE */ YYSYMBOL_DIR_SIGN = 7, /* DIR_SIGN */ YYSYMBOL_ENUM = 8, /* ENUM */ YYSYMBOL_ELLIPSIS = 9, /* ELLIPSIS */ YYSYMBOL_EQUALS = 10, /* EQUALS */ YYSYMBOL_EXTEND = 11, /* EXTEND */ YYSYMBOL_FALSE_LITERAL = 12, /* FALSE_LITERAL */ YYSYMBOL_FLOAT = 13, /* FLOAT */ YYSYMBOL_FRAGMENT = 14, /* FRAGMENT */ YYSYMBOL_IDENTIFIER = 15, /* IDENTIFIER */ YYSYMBOL_INPUT = 16, /* INPUT */ YYSYMBOL_IMPLEMENTS = 17, /* IMPLEMENTS */ YYSYMBOL_INT = 18, /* INT */ YYSYMBOL_INTERFACE = 19, /* INTERFACE */ YYSYMBOL_LBRACKET = 20, /* LBRACKET */ YYSYMBOL_LCURLY = 21, /* LCURLY */ YYSYMBOL_LPAREN = 22, /* LPAREN */ YYSYMBOL_MUTATION = 23, /* MUTATION */ YYSYMBOL_NULL_LITERAL = 24, /* NULL_LITERAL */ YYSYMBOL_ON = 25, /* ON */ YYSYMBOL_PIPE = 26, /* PIPE */ YYSYMBOL_QUERY = 27, /* QUERY */ YYSYMBOL_RBRACKET = 28, /* RBRACKET */ YYSYMBOL_RCURLY = 29, /* RCURLY */ YYSYMBOL_REPEATABLE = 30, /* REPEATABLE */ YYSYMBOL_RPAREN = 31, /* RPAREN */ YYSYMBOL_SCALAR = 32, /* SCALAR */ YYSYMBOL_SCHEMA = 33, /* SCHEMA */ YYSYMBOL_STRING = 34, /* STRING */ YYSYMBOL_SUBSCRIPTION = 35, /* SUBSCRIPTION */ YYSYMBOL_TRUE_LITERAL = 36, /* TRUE_LITERAL */ YYSYMBOL_TYPE_LITERAL = 37, /* TYPE_LITERAL */ YYSYMBOL_UNION = 38, /* UNION */ YYSYMBOL_VAR_SIGN = 39, /* VAR_SIGN */ YYSYMBOL_YYACCEPT = 40, /* $accept */ YYSYMBOL_start = 41, /* start */ YYSYMBOL_document = 42, /* document */ YYSYMBOL_definitions_list = 43, /* definitions_list */ YYSYMBOL_definition = 44, /* definition */ YYSYMBOL_executable_definition = 45, /* executable_definition */ YYSYMBOL_operation_definition = 46, /* operation_definition */ YYSYMBOL_operation_type = 47, /* operation_type */ YYSYMBOL_operation_name_opt = 48, /* operation_name_opt */ YYSYMBOL_variable_definitions_opt = 49, /* variable_definitions_opt */ YYSYMBOL_variable_definitions_list = 50, /* variable_definitions_list */ YYSYMBOL_variable_definition = 51, /* variable_definition */ YYSYMBOL_default_value_opt = 52, /* default_value_opt */ YYSYMBOL_selection_list = 53, /* selection_list */ YYSYMBOL_selection = 54, /* selection */ YYSYMBOL_selection_set = 55, /* selection_set */ YYSYMBOL_selection_set_opt = 56, /* selection_set_opt */ YYSYMBOL_field = 57, /* field */ YYSYMBOL_arguments_opt = 58, /* arguments_opt */ YYSYMBOL_arguments_list = 59, /* arguments_list */ YYSYMBOL_argument = 60, /* argument */ YYSYMBOL_literal_value = 61, /* literal_value */ YYSYMBOL_input_value = 62, /* input_value */ YYSYMBOL_null_value = 63, /* null_value */ YYSYMBOL_variable = 64, /* variable */ YYSYMBOL_list_value = 65, /* list_value */ YYSYMBOL_list_value_list = 66, /* list_value_list */ YYSYMBOL_enum_name = 67, /* enum_name */ YYSYMBOL_enum_value = 68, /* enum_value */ YYSYMBOL_object_value = 69, /* object_value */ YYSYMBOL_object_value_list_opt = 70, /* object_value_list_opt */ YYSYMBOL_object_value_list = 71, /* object_value_list */ YYSYMBOL_object_value_field = 72, /* object_value_field */ YYSYMBOL_object_literal_value = 73, /* object_literal_value */ YYSYMBOL_object_literal_value_list_opt = 74, /* object_literal_value_list_opt */ YYSYMBOL_object_literal_value_list = 75, /* object_literal_value_list */ YYSYMBOL_object_literal_value_field = 76, /* object_literal_value_field */ YYSYMBOL_directives_list_opt = 77, /* directives_list_opt */ YYSYMBOL_directives_list = 78, /* directives_list */ YYSYMBOL_directive = 79, /* directive */ YYSYMBOL_name = 80, /* name */ YYSYMBOL_schema_keyword = 81, /* schema_keyword */ YYSYMBOL_name_without_on = 82, /* name_without_on */ YYSYMBOL_fragment_spread = 83, /* fragment_spread */ YYSYMBOL_inline_fragment = 84, /* inline_fragment */ YYSYMBOL_fragment_definition = 85, /* fragment_definition */ YYSYMBOL_fragment_name_opt = 86, /* fragment_name_opt */ YYSYMBOL_type = 87, /* type */ YYSYMBOL_nullable_type = 88, /* nullable_type */ YYSYMBOL_type_system_definition = 89, /* type_system_definition */ YYSYMBOL_schema_definition = 90, /* schema_definition */ YYSYMBOL_operation_type_definition_list_opt = 91, /* operation_type_definition_list_opt */ YYSYMBOL_operation_type_definition_list = 92, /* operation_type_definition_list */ YYSYMBOL_operation_type_definition = 93, /* operation_type_definition */ YYSYMBOL_type_definition = 94, /* type_definition */ YYSYMBOL_description = 95, /* description */ YYSYMBOL_description_opt = 96, /* description_opt */ YYSYMBOL_scalar_type_definition = 97, /* scalar_type_definition */ YYSYMBOL_object_type_definition = 98, /* object_type_definition */ YYSYMBOL_implements_opt = 99, /* implements_opt */ YYSYMBOL_interfaces_list = 100, /* interfaces_list */ YYSYMBOL_legacy_interfaces_list = 101, /* legacy_interfaces_list */ YYSYMBOL_input_value_definition = 102, /* input_value_definition */ YYSYMBOL_input_value_definition_list = 103, /* input_value_definition_list */ YYSYMBOL_arguments_definitions_opt = 104, /* arguments_definitions_opt */ YYSYMBOL_field_definition = 105, /* field_definition */ YYSYMBOL_field_definition_list_opt = 106, /* field_definition_list_opt */ YYSYMBOL_field_definition_list = 107, /* field_definition_list */ YYSYMBOL_interface_type_definition = 108, /* interface_type_definition */ YYSYMBOL_union_members = 109, /* union_members */ YYSYMBOL_union_type_definition = 110, /* union_type_definition */ YYSYMBOL_enum_type_definition = 111, /* enum_type_definition */ YYSYMBOL_enum_value_definition = 112, /* enum_value_definition */ YYSYMBOL_enum_value_definitions = 113, /* enum_value_definitions */ YYSYMBOL_input_object_type_definition = 114, /* input_object_type_definition */ YYSYMBOL_directive_definition = 115, /* directive_definition */ YYSYMBOL_directive_repeatable_opt = 116, /* directive_repeatable_opt */ YYSYMBOL_directive_locations = 117, /* directive_locations */ YYSYMBOL_type_system_extension = 118, /* type_system_extension */ YYSYMBOL_schema_extension = 119, /* schema_extension */ YYSYMBOL_type_extension = 120, /* type_extension */ YYSYMBOL_scalar_type_extension = 121, /* scalar_type_extension */ YYSYMBOL_object_type_extension = 122, /* object_type_extension */ YYSYMBOL_interface_type_extension = 123, /* interface_type_extension */ YYSYMBOL_union_type_extension = 124, /* union_type_extension */ YYSYMBOL_enum_type_extension = 125, /* enum_type_extension */ YYSYMBOL_input_object_type_extension = 126 /* input_object_type_extension */ }; typedef enum yysymbol_kind_t yysymbol_kind_t; #ifdef short # undef short #endif /* On compilers that do not define __PTRDIFF_MAX__ etc., make sure and (if available) are included so that the code can choose integer types of a good width. */ #ifndef __PTRDIFF_MAX__ # include /* INFRINGES ON USER NAME SPACE */ # if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YY_STDINT_H # endif #endif /* Narrow types that promote to a signed type and that can represent a signed or unsigned integer of at least N bits. In tables they can save space and decrease cache pressure. Promoting to a signed type helps avoid bugs in integer arithmetic. */ #ifdef __INT_LEAST8_MAX__ typedef __INT_LEAST8_TYPE__ yytype_int8; #elif defined YY_STDINT_H typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif #ifdef __INT_LEAST16_MAX__ typedef __INT_LEAST16_TYPE__ yytype_int16; #elif defined YY_STDINT_H typedef int_least16_t yytype_int16; #else typedef short yytype_int16; #endif /* Work around bug in HP-UX 11.23, which defines these macros incorrectly for preprocessor constants. This workaround can likely be removed in 2023, as HPE has promised support for HP-UX 11.23 (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of . */ #ifdef __hpux # undef UINT_LEAST8_MAX # undef UINT_LEAST16_MAX # define UINT_LEAST8_MAX 255 # define UINT_LEAST16_MAX 65535 #endif #if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ typedef __UINT_LEAST8_TYPE__ yytype_uint8; #elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ && UINT_LEAST8_MAX <= INT_MAX) typedef uint_least8_t yytype_uint8; #elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX typedef unsigned char yytype_uint8; #else typedef short yytype_uint8; #endif #if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ typedef __UINT_LEAST16_TYPE__ yytype_uint16; #elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ && UINT_LEAST16_MAX <= INT_MAX) typedef uint_least16_t yytype_uint16; #elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX typedef unsigned short yytype_uint16; #else typedef int yytype_uint16; #endif #ifndef YYPTRDIFF_T # if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ # define YYPTRDIFF_T __PTRDIFF_TYPE__ # define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ # elif defined PTRDIFF_MAX # ifndef ptrdiff_t # include /* INFRINGES ON USER NAME SPACE */ # endif # define YYPTRDIFF_T ptrdiff_t # define YYPTRDIFF_MAXIMUM PTRDIFF_MAX # else # define YYPTRDIFF_T long # define YYPTRDIFF_MAXIMUM LONG_MAX # endif #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif #define YYSIZE_MAXIMUM \ YY_CAST (YYPTRDIFF_T, \ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ ? YYPTRDIFF_MAXIMUM \ : YY_CAST (YYSIZE_T, -1))) #define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) /* Stored state numbers (used for stacks). */ typedef yytype_int16 yy_state_t; /* State numbers in computations. */ typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif #ifndef YY_ATTRIBUTE_PURE # if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) # else # define YY_ATTRIBUTE_PURE # endif #endif #ifndef YY_ATTRIBUTE_UNUSED # if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else # define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YY_USE(E) ((void) (E)) #else # define YY_USE(E) /* empty */ #endif /* Suppress an incorrect diagnostic about yylval being uninitialized. */ #if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ # if __GNUC__ * 100 + __GNUC_MINOR__ < 407 # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") # else # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # endif # define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif #if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ # define YY_IGNORE_USELESS_CAST_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") # define YY_IGNORE_USELESS_CAST_END \ _Pragma ("GCC diagnostic pop") #endif #ifndef YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_END #endif #define YY_ASSERT(E) ((void) (0 && (E))) #if 1 /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ /* Use EXIT_SUCCESS as a witness for stdlib.h. */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's 'empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* 1 */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif # endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 79 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 808 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 40 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 87 /* YYNRULES -- Number of rules. */ #define YYNRULES 182 /* YYNSTATES -- Number of states. */ #define YYNSTATES 311 /* YYMAXUTOK -- Last valid token kind. */ #define YYMAXUTOK 257 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ (0 <= (YYX) && (YYX) <= YYMAXUTOK \ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ : YYSYMBOL_YYUNDEF) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ static const yytype_int8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2 }; #if YYDEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { 0, 103, 103, 105, 119, 120, 123, 124, 125, 128, 129, 132, 143, 154, 167, 168, 169, 172, 173, 176, 177, 180, 181, 184, 196, 197, 200, 201, 204, 205, 206, 209, 212, 213, 216, 227, 240, 241, 244, 245, 248, 258, 259, 260, 261, 262, 263, 264, 265, 266, 269, 270, 271, 273, 281, 290, 291, 294, 295, 298, 299, 300, 301, 303, 312, 321, 322, 325, 326, 329, 340, 349, 350, 353, 354, 357, 368, 369, 372, 373, 375, 385, 386, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 403, 404, 405, 406, 407, 408, 412, 422, 431, 442, 454, 455, 458, 459, 462, 469, 478, 479, 480, 483, 496, 497, 500, 504, 509, 514, 515, 516, 517, 518, 519, 521, 524, 525, 528, 540, 554, 555, 556, 557, 560, 568, 574, 582, 587, 601, 602, 605, 606, 609, 623, 624, 627, 628, 629, 632, 646, 654, 659, 672, 685, 697, 698, 701, 714, 728, 729, 732, 733, 737, 738, 741, 752, 764, 765, 766, 767, 768, 769, 771, 781, 793, 805, 814, 825, 834, 845, 854 }; #endif /** Accessing symbol of state STATE. */ #define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) #if 1 /* The user-facing name of the symbol whose (internal) number is YYSYMBOL. No bounds checking. */ static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; static const char * yysymbol_name (yysymbol_kind_t yysymbol) { static const char *const yy_sname[] = { "end of file", "error", "invalid token", "AMP", "BANG", "COLON", "DIRECTIVE", "DIR_SIGN", "ENUM", "ELLIPSIS", "EQUALS", "EXTEND", "FALSE_LITERAL", "FLOAT", "FRAGMENT", "IDENTIFIER", "INPUT", "IMPLEMENTS", "INT", "INTERFACE", "LBRACKET", "LCURLY", "LPAREN", "MUTATION", "NULL_LITERAL", "ON", "PIPE", "QUERY", "RBRACKET", "RCURLY", "REPEATABLE", "RPAREN", "SCALAR", "SCHEMA", "STRING", "SUBSCRIPTION", "TRUE_LITERAL", "TYPE_LITERAL", "UNION", "VAR_SIGN", "$accept", "start", "document", "definitions_list", "definition", "executable_definition", "operation_definition", "operation_type", "operation_name_opt", "variable_definitions_opt", "variable_definitions_list", "variable_definition", "default_value_opt", "selection_list", "selection", "selection_set", "selection_set_opt", "field", "arguments_opt", "arguments_list", "argument", "literal_value", "input_value", "null_value", "variable", "list_value", "list_value_list", "enum_name", "enum_value", "object_value", "object_value_list_opt", "object_value_list", "object_value_field", "object_literal_value", "object_literal_value_list_opt", "object_literal_value_list", "object_literal_value_field", "directives_list_opt", "directives_list", "directive", "name", "schema_keyword", "name_without_on", "fragment_spread", "inline_fragment", "fragment_definition", "fragment_name_opt", "type", "nullable_type", "type_system_definition", "schema_definition", "operation_type_definition_list_opt", "operation_type_definition_list", "operation_type_definition", "type_definition", "description", "description_opt", "scalar_type_definition", "object_type_definition", "implements_opt", "interfaces_list", "legacy_interfaces_list", "input_value_definition", "input_value_definition_list", "arguments_definitions_opt", "field_definition", "field_definition_list_opt", "field_definition_list", "interface_type_definition", "union_members", "union_type_definition", "enum_type_definition", "enum_value_definition", "enum_value_definitions", "input_object_type_definition", "directive_definition", "directive_repeatable_opt", "directive_locations", "type_system_extension", "schema_extension", "type_extension", "scalar_type_extension", "object_type_extension", "interface_type_extension", "union_type_extension", "enum_type_extension", "input_object_type_extension", YY_NULLPTR }; return yy_sname[yysymbol]; } #endif #define YYPACT_NINF (-259) #define yypact_value_is_default(Yyn) \ ((Yyn) == YYPACT_NINF) #define YYTABLE_NINF (-148) #define yytable_value_is_error(Yyn) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ static const yytype_int16 yypact[] = { 232, 14, 737, 473, -259, -259, 7, -259, -259, 23, -259, 202, -259, -259, -259, 704, -259, -259, -259, -259, -259, 144, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, 704, 704, 704, 704, 7, 704, 704, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, 19, 572, -259, -259, 506, -259, -259, 12, -259, -259, -259, 704, 41, 7, -259, -259, -259, 38, -259, 59, 704, 704, 704, 704, 704, 704, 7, 7, 62, 7, 73, 6, 62, 7, 605, 605, 75, 7, -259, -259, 704, 704, 7, 58, 70, -259, -259, 65, 7, 704, 7, 7, 62, 7, 62, 7, 77, 6, 91, 6, 304, 7, 7, 70, 7, 104, 122, 605, -259, 7, 114, 7, 638, -259, -259, 58, 671, -259, 117, 75, -259, 118, 80, -259, 704, -10, -259, 75, 106, 109, 113, 7, -259, 7, 133, 110, 110, 704, 260, 151, 704, 126, 183, 126, 704, 128, 75, -259, 75, 539, 7, -259, -259, 406, -259, -259, 704, -259, -259, 159, -259, -259, -259, 110, 136, 110, 110, 126, 126, 704, 770, -259, -18, 704, -259, 24, -259, 151, 704, -259, 39, -259, -259, -259, -259, 142, -259, -259, -259, -259, 75, -259, -259, -259, -259, 338, 704, -259, -259, -259, -259, 704, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, 605, 107, -259, 146, 66, 79, -259, -259, 142, 7, -259, -259, 167, -259, -259, -259, 704, -259, 82, 704, -259, -259, -259, 372, 149, 704, -259, 150, 704, -259, 168, -259, 170, -259, 704, -259, -259, -259, 605, 106, -259, -259, -259, -259, -259, -259, -259, 178, -259, -259, 179, 406, 440, 7, -259, 161, 170, 180, 406, 440, -259, -259, 704, -259, -259, 704, 7, 605, -259, -259, -259, 7, -259 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ static const yytype_uint8 yydefact[] = { 127, 0, 105, 0, 15, 14, 76, 126, 16, 0, 2, 127, 4, 6, 9, 17, 10, 7, 111, 112, 128, 0, 120, 121, 122, 123, 124, 125, 113, 8, 164, 165, 168, 169, 170, 171, 172, 173, 0, 0, 0, 0, 76, 0, 0, 91, 89, 92, 97, 93, 95, 90, 86, 87, 98, 94, 84, 83, 96, 85, 88, 99, 100, 106, 0, 76, 82, 13, 0, 26, 28, 36, 81, 29, 30, 0, 115, 77, 78, 1, 5, 19, 18, 0, 0, 0, 0, 0, 0, 0, 76, 76, 131, 0, 0, 167, 131, 76, 0, 0, 0, 76, 12, 27, 0, 0, 76, 36, 0, 114, 79, 0, 76, 0, 76, 76, 131, 76, 131, 76, 0, 180, 0, 182, 0, 76, 174, 0, 76, 0, 178, 0, 109, 76, 107, 76, 0, 103, 101, 36, 0, 38, 0, 32, 80, 0, 0, 117, 0, 0, 21, 0, 142, 0, 0, 76, 129, 76, 0, 127, 127, 0, 135, 133, 134, 145, 0, 145, 0, 0, 0, 108, 0, 0, 76, 37, 39, 0, 33, 35, 0, 116, 118, 0, 20, 22, 11, 127, 160, 127, 127, 145, 145, 0, 0, 156, 127, 0, 140, 127, 135, 132, 0, 138, 127, 176, 166, 175, 151, 177, 110, 104, 102, 31, 32, 45, 41, 59, 42, 0, 65, 53, 60, 43, 44, 0, 61, 50, 40, 46, 51, 48, 63, 47, 52, 49, 62, 119, 0, 127, 161, 0, 127, 127, 150, 130, 153, 76, 179, 157, 0, 181, 141, 136, 0, 148, 127, 0, 34, 55, 57, 0, 0, 66, 67, 0, 72, 73, 0, 54, 24, 143, 0, 154, 158, 155, 0, 142, 146, 149, 152, 56, 58, 64, 68, 0, 70, 74, 0, 0, 0, 76, 162, 159, 24, 0, 0, 0, 50, 69, 71, 25, 23, 0, 76, 0, 75, 163, 139, 76, 144 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { -259, -259, -259, -259, 177, -259, -259, 9, -259, -259, -259, 43, -101, 60, -67, -95, -17, -259, -97, -259, 61, -258, -174, -259, -259, -259, -259, 4, -259, -259, -259, -259, -63, -259, -259, -259, -61, 34, -36, -52, -3, -170, 2, -259, -259, -259, -259, -81, -259, -259, -259, -259, 87, -138, -259, -259, 15, -259, -259, -26, 46, -259, -180, -48, -68, -41, -103, -259, -259, 27, -259, -259, -194, 32, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259, -259 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int16 yydefgoto[] = { 0, 9, 10, 11, 12, 13, 14, 61, 81, 112, 149, 150, 291, 68, 69, 178, 179, 70, 106, 140, 141, 227, 299, 229, 230, 231, 261, 232, 233, 234, 262, 263, 264, 235, 265, 266, 267, 76, 77, 78, 132, 62, 72, 73, 74, 16, 64, 133, 134, 17, 18, 109, 146, 147, 19, 20, 197, 22, 23, 125, 163, 164, 198, 199, 188, 255, 205, 256, 24, 209, 25, 26, 195, 196, 27, 28, 241, 293, 29, 30, 31, 32, 33, 34, 35, 36, 37 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_int16 yytable[] = { 71, 103, 249, 228, 63, 137, 95, 236, 182, 15, 144, 248, 82, 75, 75, 21, 7, 104, 135, 252, 15, 184, 38, 79, 236, 110, 21, -77, 182, 148, 39, 298, 301, 40, 105, 90, 91, 92, 93, 306, 96, 97, 174, 110, 98, 260, 41, 42, 249, 236, 169, 43, 44, 251, 121, 123, 186, 126, 7, 252, 111, 130, 108, 252, 207, 71, 113, 101, -147, 110, 128, 110, 107, 7, 110, 211, 94, 212, 110, 124, 105, 114, 115, 116, 117, 118, 119, 282, 244, 245, 155, 236, 157, 4, 127, 273, 136, 5, 159, 100, 7, 139, 142, 4, 148, 8, 103, 5, 274, 181, 152, 278, 160, 7, 168, 8, 7, 145, 171, 236, 236, 162, 177, 180, 120, 122, 236, 236, 187, 75, 189, 129, -77, 71, 190, 138, 145, 142, 271, 239, 143, 7, 243, 193, 7, 183, 151, 204, 153, 154, 83, 156, 84, 158, 202, 145, 210, 270, 200, 165, 85, 203, 167, 86, 238, 208, 240, 170, 257, 172, 71, 272, 276, 289, 194, 145, 87, 237, 283, 286, 290, 88, 89, 296, 297, 305, 226, 303, 80, 191, 208, 192, 185, 304, 250, 294, 173, 258, 247, 253, 284, 176, -3, 226, 194, 287, 4, 201, 214, 295, 5, 194, 206, 1, 166, 279, 2, 268, 8, 254, 246, 242, 269, 3, 309, 4, 0, 0, 226, 5, 0, 0, 0, 0, 0, 6, 7, 8, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 277, 0, 3, 280, 4, 0, 194, 0, 5, 285, 0, 0, 288, 0, 6, 7, 8, 0, 292, 226, 254, -137, 0, 0, -137, 0, -137, 0, 0, 0, 275, 0, 0, -137, -137, 0, 0, 0, 0, -137, 0, 0, 0, 0, 0, -137, 288, 226, 226, 307, 0, 0, 0, 0, 226, 226, 161, 0, 0, 45, 0, 46, 0, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 302, 0, 4, 54, 66, 0, 5, 0, 0, 55, 0, 56, 57, 308, 8, 58, 59, 60, 310, 45, 0, 46, 0, 0, 47, 215, 216, 49, 217, 51, 52, 218, 53, 219, 220, 0, 4, 221, 222, 0, 5, 259, 0, 55, 0, 56, 57, 223, 8, 224, 59, 60, 225, 45, 0, 46, 0, 0, 47, 215, 216, 49, 217, 51, 52, 218, 53, 219, 220, 0, 4, 221, 222, 0, 5, 281, 0, 55, 0, 56, 57, 223, 8, 224, 59, 60, 225, 45, 0, 46, 0, 0, 47, 215, 216, 49, 217, 51, 52, 218, 53, 219, 220, 0, 4, 221, 222, 0, 5, 0, 0, 55, 0, 56, 57, 223, 8, 224, 59, 60, 225, 45, 0, 46, 0, 0, 47, 215, 216, 49, 217, 51, 52, 218, 53, 219, 300, 0, 4, 221, 222, 0, 5, 0, 0, 55, 0, 56, 57, 223, 8, 224, 59, 60, 45, 0, 46, 65, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 66, 0, 5, 0, 67, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 65, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 66, 0, 5, 0, 102, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 65, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 66, 0, 5, 0, 213, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 75, 46, 0, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 99, 0, 5, 0, 0, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 0, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 131, 0, 0, 4, 54, 66, 0, 5, 0, 0, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 65, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 66, 0, 5, 0, 0, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 0, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 66, 0, 5, 0, 0, 55, 175, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 0, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 66, 0, 5, 0, 0, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 0, 0, 47, 48, 0, 49, 50, 51, 52, 0, 53, 0, 0, 0, 4, 54, 0, 0, 5, 0, 0, 55, 0, 56, 57, 0, 8, 58, 59, 60, 45, 0, 46, 0, 0, 47, 0, 0, 49, 217, 51, 52, 0, 53, 0, 0, 0, 4, 0, 222, 0, 5, 0, 0, 55, 0, 56, 57, 0, 8, 0, 59, 60 }; static const yytype_int16 yycheck[] = { 3, 68, 196, 177, 2, 100, 42, 177, 146, 0, 107, 29, 15, 7, 7, 0, 34, 5, 99, 199, 11, 31, 8, 0, 194, 77, 11, 21, 166, 39, 16, 289, 290, 19, 22, 38, 39, 40, 41, 297, 43, 44, 139, 95, 25, 219, 32, 33, 242, 219, 131, 37, 38, 29, 90, 91, 151, 93, 34, 239, 22, 97, 21, 243, 167, 68, 7, 65, 29, 121, 96, 123, 75, 34, 126, 170, 42, 172, 130, 17, 22, 84, 85, 86, 87, 88, 89, 261, 191, 192, 116, 261, 118, 23, 21, 29, 21, 27, 21, 65, 34, 104, 105, 23, 39, 35, 173, 27, 29, 29, 113, 29, 21, 34, 10, 35, 34, 108, 4, 289, 290, 124, 5, 5, 90, 91, 296, 297, 22, 7, 21, 97, 10, 136, 21, 101, 127, 140, 31, 187, 106, 34, 190, 10, 34, 148, 112, 21, 114, 115, 6, 117, 8, 119, 3, 146, 28, 238, 161, 125, 16, 164, 128, 19, 5, 168, 30, 133, 26, 135, 173, 25, 5, 5, 159, 166, 32, 180, 29, 29, 10, 37, 38, 5, 5, 5, 177, 26, 11, 155, 193, 157, 149, 294, 197, 276, 136, 214, 194, 202, 263, 140, 0, 194, 189, 266, 23, 161, 174, 277, 27, 196, 29, 11, 127, 256, 14, 220, 35, 204, 193, 189, 225, 21, 305, 23, -1, -1, 219, 27, -1, -1, -1, -1, -1, 33, 34, 35, -1, -1, -1, -1, -1, 11, -1, -1, 14, -1, -1, -1, -1, 254, -1, 21, 257, 23, -1, 242, -1, 27, 263, -1, -1, 266, -1, 33, 34, 35, -1, 272, 261, 256, 12, -1, -1, 15, -1, 17, -1, -1, -1, 247, -1, -1, 24, 25, -1, -1, -1, -1, 30, -1, -1, -1, -1, -1, 36, 300, 289, 290, 303, -1, -1, -1, -1, 296, 297, 3, -1, -1, 6, -1, 8, -1, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, 291, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, 304, 35, 36, 37, 38, 309, 6, -1, 8, -1, -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, -1, 23, 24, 25, -1, 27, 28, -1, 30, -1, 32, 33, 34, 35, 36, 37, 38, 39, 6, -1, 8, -1, -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, -1, 23, 24, 25, -1, 27, 28, -1, 30, -1, 32, 33, 34, 35, 36, 37, 38, 39, 6, -1, 8, -1, -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, 34, 35, 36, 37, 38, 39, 6, -1, 8, -1, -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, 34, 35, 36, 37, 38, 6, -1, 8, 9, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, 29, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, 9, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, 29, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, 9, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, 29, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, 7, 8, -1, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, -1, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, 20, -1, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, 9, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, -1, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, -1, 30, 31, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, -1, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, 25, -1, 27, -1, -1, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, -1, -1, 11, 12, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, 24, -1, -1, 27, -1, -1, 30, -1, 32, 33, -1, 35, 36, 37, 38, 6, -1, 8, -1, -1, 11, -1, -1, 14, 15, 16, 17, -1, 19, -1, -1, -1, 23, -1, 25, -1, 27, -1, -1, 30, -1, 32, 33, -1, 35, -1, 37, 38 }; /* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of state STATE-NUM. */ static const yytype_int8 yystos[] = { 0, 11, 14, 21, 23, 27, 33, 34, 35, 41, 42, 43, 44, 45, 46, 47, 85, 89, 90, 94, 95, 96, 97, 98, 108, 110, 111, 114, 115, 118, 119, 120, 121, 122, 123, 124, 125, 126, 8, 16, 19, 32, 33, 37, 38, 6, 8, 11, 12, 14, 15, 16, 17, 19, 24, 30, 32, 33, 36, 37, 38, 47, 81, 82, 86, 9, 25, 29, 53, 54, 57, 80, 82, 83, 84, 7, 77, 78, 79, 0, 44, 48, 80, 6, 8, 16, 19, 32, 37, 38, 80, 80, 80, 80, 77, 78, 80, 80, 25, 25, 77, 82, 29, 54, 5, 22, 58, 80, 21, 91, 79, 22, 49, 7, 80, 80, 80, 80, 80, 80, 77, 78, 77, 78, 17, 99, 78, 21, 99, 77, 78, 20, 80, 87, 88, 87, 21, 55, 77, 80, 59, 60, 80, 77, 58, 47, 92, 93, 39, 50, 51, 77, 80, 77, 77, 99, 77, 99, 77, 21, 21, 3, 80, 100, 101, 77, 92, 77, 10, 87, 77, 4, 77, 53, 58, 31, 60, 5, 55, 56, 5, 29, 93, 80, 31, 51, 55, 22, 104, 21, 21, 77, 77, 10, 96, 112, 113, 96, 102, 103, 80, 100, 3, 80, 21, 106, 29, 106, 80, 109, 28, 55, 55, 29, 77, 12, 13, 15, 18, 20, 21, 24, 25, 34, 36, 39, 47, 61, 62, 63, 64, 65, 67, 68, 69, 73, 81, 80, 5, 103, 30, 116, 113, 103, 106, 106, 109, 67, 29, 112, 80, 29, 102, 80, 96, 105, 107, 26, 56, 28, 62, 66, 70, 71, 72, 74, 75, 76, 80, 80, 87, 31, 25, 29, 29, 77, 5, 80, 29, 105, 80, 28, 62, 29, 72, 80, 29, 76, 80, 5, 10, 52, 80, 117, 87, 104, 5, 5, 61, 62, 21, 61, 77, 26, 52, 5, 61, 80, 77, 87, 77 }; /* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ static const yytype_int8 yyr1[] = { 0, 40, 41, 42, 43, 43, 44, 44, 44, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 49, 49, 50, 50, 51, 52, 52, 53, 53, 54, 54, 54, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 61, 61, 61, 61, 61, 61, 61, 61, 61, 62, 62, 62, 63, 64, 65, 65, 66, 66, 67, 67, 67, 67, 68, 69, 70, 70, 71, 71, 72, 73, 74, 74, 75, 75, 76, 77, 77, 78, 78, 79, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 83, 84, 84, 85, 86, 86, 87, 87, 88, 88, 89, 89, 89, 90, 91, 91, 92, 92, 93, 94, 94, 94, 94, 94, 94, 95, 96, 96, 97, 98, 99, 99, 99, 99, 100, 100, 101, 101, 102, 103, 103, 104, 104, 105, 106, 106, 107, 107, 107, 108, 109, 109, 110, 111, 112, 113, 113, 114, 115, 116, 116, 117, 117, 118, 118, 119, 119, 120, 120, 120, 120, 120, 120, 121, 122, 123, 124, 124, 125, 125, 126, 126 }; /* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ static const yytype_int8 yyr2[] = { 0, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 5, 3, 2, 1, 1, 1, 0, 1, 0, 3, 1, 2, 6, 0, 2, 1, 2, 1, 1, 1, 3, 0, 1, 6, 4, 0, 3, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 1, 2, 1, 1, 1, 1, 1, 3, 0, 1, 1, 2, 3, 3, 0, 1, 1, 2, 3, 0, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 3, 6, 0, 1, 1, 2, 1, 3, 1, 1, 1, 3, 0, 3, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 6, 0, 3, 2, 2, 1, 3, 1, 2, 6, 1, 2, 0, 3, 6, 0, 3, 0, 1, 2, 6, 1, 3, 6, 7, 3, 1, 2, 7, 8, 0, 1, 1, 3, 1, 1, 6, 3, 1, 1, 1, 1, 1, 1, 4, 6, 6, 6, 4, 7, 4, 7, 4 }; enum { YYENOMEM = -2 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab #define YYNOMEM goto yyexhaustedlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY) \ { \ yychar = (Token); \ yylval = (Value); \ YYPOPSTACK (yylen); \ yystate = *yyssp; \ goto yybackup; \ } \ else \ { \ yyerror (parser, filename, YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (0) /* Backward compatibility with an undocumented macro. Use YYerror or YYUNDEF. */ #define YYERRCODE YYUNDEF /* Enable debugging if requested. */ #if YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (0) # define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Kind, Value, parser, filename); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ static void yy_symbol_value_print (FILE *yyo, yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, VALUE parser, VALUE filename) { FILE *yyoutput = yyo; YY_USE (yyoutput); YY_USE (parser); YY_USE (filename); if (!yyvaluep) return; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ static void yy_symbol_print (FILE *yyo, yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, VALUE parser, VALUE filename) { YYFPRINTF (yyo, "%s %s (", yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); yy_symbol_value_print (yyo, yykind, yyvaluep, parser, filename); YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, int yyrule, VALUE parser, VALUE filename) { int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), &yyvsp[(yyi + 1) - (yynrhs)], parser, filename); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyssp, yyvsp, Rule, parser, filename); \ } while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ # define YYDPRINTF(Args) ((void) 0) # define YY_SYMBOL_PRINT(Title, Kind, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif /* Context of a parse error. */ typedef struct { yy_state_t *yyssp; yysymbol_kind_t yytoken; } yypcontext_t; /* Put in YYARG at most YYARGN of the expected tokens given the current YYCTX, and return the number of tokens stored in YYARG. If YYARG is null, return the number of expected tokens (guaranteed to be less than YYNTOKENS). Return YYENOMEM on memory exhaustion. Return 0 if there are more than YYARGN expected tokens, yet fill YYARG up to YYARGN. */ static int yypcontext_expected_tokens (const yypcontext_t *yyctx, yysymbol_kind_t yyarg[], int yyargn) { /* Actual size of YYARG. */ int yycount = 0; int yyn = yypact[+*yyctx->yyssp]; if (!yypact_value_is_default (yyn)) { /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for this state because they are default actions. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yyx; for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYSYMBOL_YYerror && !yytable_value_is_error (yytable[yyx + yyn])) { if (!yyarg) ++yycount; else if (yycount == yyargn) return 0; else yyarg[yycount++] = YY_CAST (yysymbol_kind_t, yyx); } } if (yyarg && yycount == 0 && 0 < yyargn) yyarg[0] = YYSYMBOL_YYEMPTY; return yycount; } #ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) # else /* Return the length of YYSTR. */ static YYPTRDIFF_T yystrlen (const char *yystr) { YYPTRDIFF_T yylen; for (yylen = 0; yystr[yylen]; yylen++) continue; return yylen; } # endif #endif #ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ static char * yystpcpy (char *yydest, const char *yysrc) { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif #endif static int yy_syntax_error_arguments (const yypcontext_t *yyctx, yysymbol_kind_t yyarg[], int yyargn) { /* Actual size of YYARG. */ int yycount = 0; /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected tokens because there are none. - The only way there can be no lookahead present (in yychar) is if this state is a consistent state with a default action. Thus, detecting the absence of a lookahead is sufficient to determine that there is no unexpected or expected token to report. In that case, just report a simple "syntax error". - Don't assume there isn't a lookahead just because this state is a consistent state with a default action. There might have been a previous inconsistent state, consistent state with a non-default action, or user semantic action that manipulated yychar. - Of course, the expected token list depends on states to have correct lookahead information, and it depends on the parser not to perform extra reductions after fetching a lookahead from the scanner and before detecting a syntax error. Thus, state merging (from LALR or IELR) and default reductions corrupt the expected token list. However, the list is correct for canonical LR with one exception: it will still contain any token that will not be accepted due to an error action in a later state. */ if (yyctx->yytoken != YYSYMBOL_YYEMPTY) { int yyn; if (yyarg) yyarg[yycount] = yyctx->yytoken; ++yycount; yyn = yypcontext_expected_tokens (yyctx, yyarg ? yyarg + 1 : yyarg, yyargn - 1); if (yyn == YYENOMEM) return YYENOMEM; else yycount += yyn; } return yycount; } /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message about the unexpected token YYTOKEN for the state stack whose top is YYSSP. Return 0 if *YYMSG was successfully written. Return -1 if *YYMSG is not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return YYENOMEM if the required number of bytes is too large to store. */ static int yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, const yypcontext_t *yyctx) { enum { YYARGS_MAX = 5 }; /* Internationalized format string. */ const char *yyformat = YY_NULLPTR; /* Arguments of yyformat: reported tokens (one for the "unexpected", one per "expected"). */ yysymbol_kind_t yyarg[YYARGS_MAX]; /* Cumulated lengths of YYARG. */ YYPTRDIFF_T yysize = 0; /* Actual size of YYARG. */ int yycount = yy_syntax_error_arguments (yyctx, yyarg, YYARGS_MAX); if (yycount == YYENOMEM) return YYENOMEM; switch (yycount) { #define YYCASE_(N, S) \ case N: \ yyformat = S; \ break default: /* Avoid compiler warnings. */ YYCASE_(0, YY_("syntax error")); YYCASE_(1, YY_("syntax error, unexpected %s")); YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); #undef YYCASE_ } /* Compute error message size. Don't count the "%s"s, but reserve room for the terminator. */ yysize = yystrlen (yyformat) - 2 * yycount + 1; { int yyi; for (yyi = 0; yyi < yycount; ++yyi) { YYPTRDIFF_T yysize1 = yysize + yystrlen (yysymbol_name (yyarg[yyi])); if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return YYENOMEM; } } if (*yymsg_alloc < yysize) { *yymsg_alloc = 2 * yysize; if (! (yysize <= *yymsg_alloc && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; return -1; } /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ { char *yyp = *yymsg; int yyi = 0; while ((*yyp = *yyformat) != '\0') if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) { yyp = yystpcpy (yyp, yysymbol_name (yyarg[yyi++])); yyformat += 2; } else { ++yyp; ++yyformat; } } return 0; } /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void yydestruct (const char *yymsg, yysymbol_kind_t yykind, YYSTYPE *yyvaluep, VALUE parser, VALUE filename) { YY_USE (yyvaluep); YY_USE (parser); YY_USE (filename); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*----------. | yyparse. | `----------*/ int yyparse (VALUE parser, VALUE filename) { /* Lookahead token kind. */ int yychar; /* The semantic value of the lookahead symbol. */ /* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ YY_INITIAL_VALUE (static YYSTYPE yyval_default;) YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Number of syntax errors so far. */ int yynerrs = 0; yy_state_fast_t yystate = 0; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus = 0; /* Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* Their size. */ YYPTRDIFF_T yystacksize = YYINITDEPTH; /* The state stack: array, bottom, top. */ yy_state_t yyssa[YYINITDEPTH]; yy_state_t *yyss = yyssa; yy_state_t *yyssp = yyss; /* The semantic value stack: array, bottom, top. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs = yyvsa; YYSTYPE *yyvsp = yyvs; int yyn; /* The return value of yyparse. */ int yyresult; /* Lookahead symbol kind. */ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; YYDPRINTF ((stderr, "Starting parse\n")); yychar = YYEMPTY; /* Cause a token to be read. */ goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; /*--------------------------------------------------------------------. | yysetstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: YYDPRINTF ((stderr, "Entering state %d\n", yystate)); YY_ASSERT (0 <= yystate && yystate < YYNSTATES); YY_IGNORE_USELESS_CAST_BEGIN *yyssp = YY_CAST (yy_state_t, yystate); YY_IGNORE_USELESS_CAST_END YY_STACK_PRINT (yyss, yyssp); if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE YYNOMEM; #else { /* Get the current used size of the three stacks, in elements. */ YYPTRDIFF_T yysize = yyssp - yyss + 1; # if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * YYSIZEOF (*yyssp), &yyvs1, yysize * YYSIZEOF (*yyvsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; } # else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) YYNOMEM; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yy_state_t *yyss1 = yyss; union yyalloc *yyptr = YY_CAST (union yyalloc *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) YYNOMEM; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; YY_IGNORE_USELESS_CAST_BEGIN YYDPRINTF ((stderr, "Stack size increased to %ld\n", YY_CAST (long, yystacksize))); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token\n")); yychar = yylex (&yylval, parser, filename); } if (yychar <= YYEOF) { yychar = YYEOF; yytoken = YYSYMBOL_YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else if (yychar == YYerror) { /* The scanner already issued an error message, process directly to error recovery. But do not keep the error token as lookahead, it is too special and may lead us to an endless loop in error recovery. */ yychar = YYUNDEF; yytoken = YYSYMBOL_YYerror; goto yyerrlab1; } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yytable_value_is_error (yyn)) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Discard the shifted token. */ yychar = YYEMPTY; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; YY_REDUCE_PRINT (yyn); switch (yyn) { case 2: /* start: document */ #line 103 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ivar_set(parser, rb_intern("@result"), yyvsp[0]); } #line 1922 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 3: /* document: definitions_list */ #line 105 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { VALUE position_source = rb_ary_entry(yyvsp[0], 0); VALUE line, col; if (RB_TEST(position_source)) { line = rb_funcall(position_source, rb_intern("line"), 0); col = rb_funcall(position_source, rb_intern("col"), 0); } else { line = INT2FIX(1); col = INT2FIX(1); } yyval = MAKE_AST_NODE(Document, 3, line, col, yyvsp[0]); } #line 1939 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 4: /* definitions_list: definition */ #line 119 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 1945 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 5: /* definitions_list: definitions_list definition */ #line 120 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 1951 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 11: /* operation_definition: operation_type operation_name_opt variable_definitions_opt directives_list_opt selection_set */ #line 132 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(OperationDefinition, 7, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), rb_ary_entry(yyvsp[-4], 3), (RB_TEST(yyvsp[-3]) ? rb_ary_entry(yyvsp[-3], 3) : Qnil), yyvsp[-2], yyvsp[-1], yyvsp[0] ); } #line 1967 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 12: /* operation_definition: LCURLY selection_list RCURLY */ #line 143 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(OperationDefinition, 7, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), r_string_query, Qnil, GraphQL_Language_Nodes_NONE, GraphQL_Language_Nodes_NONE, yyvsp[-1] ); } #line 1983 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 13: /* operation_definition: LCURLY RCURLY */ #line 154 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(OperationDefinition, 7, rb_ary_entry(yyvsp[-1], 1), rb_ary_entry(yyvsp[-1], 2), r_string_query, Qnil, GraphQL_Language_Nodes_NONE, GraphQL_Language_Nodes_NONE, GraphQL_Language_Nodes_NONE ); } #line 1999 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 17: /* operation_name_opt: %empty */ #line 172 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qnil; } #line 2005 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 19: /* variable_definitions_opt: %empty */ #line 176 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2011 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 20: /* variable_definitions_opt: LPAREN variable_definitions_list RPAREN */ #line 177 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2017 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 21: /* variable_definitions_list: variable_definition */ #line 180 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2023 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 22: /* variable_definitions_list: variable_definitions_list variable_definition */ #line 181 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2029 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 23: /* variable_definition: VAR_SIGN name COLON type default_value_opt directives_list_opt */ #line 184 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(VariableDefinition, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-4], 3), yyvsp[-2], yyvsp[-1], yyvsp[0] ); } #line 2044 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 24: /* default_value_opt: %empty */ #line 196 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qnil; } #line 2050 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 25: /* default_value_opt: EQUALS literal_value */ #line 197 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[0]; } #line 2056 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 26: /* selection_list: selection */ #line 200 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2062 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 27: /* selection_list: selection_list selection */ #line 201 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2068 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 31: /* selection_set: LCURLY selection_list RCURLY */ #line 209 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2074 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 32: /* selection_set_opt: %empty */ #line 212 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new(); } #line 2080 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 34: /* field: name COLON name arguments_opt directives_list_opt selection_set_opt */ #line 216 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Field, 7, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-5], 3), // alias rb_ary_entry(yyvsp[-3], 3), // name yyvsp[-2], // args yyvsp[-1], // directives yyvsp[0] // subselections ); } #line 2096 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 35: /* field: name arguments_opt directives_list_opt selection_set_opt */ #line 227 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Field, 7, rb_ary_entry(yyvsp[-3], 1), rb_ary_entry(yyvsp[-3], 2), Qnil, // alias rb_ary_entry(yyvsp[-3], 3), // name yyvsp[-2], // args yyvsp[-1], // directives yyvsp[0] // subselections ); } #line 2112 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 36: /* arguments_opt: %empty */ #line 240 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2118 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 37: /* arguments_opt: LPAREN arguments_list RPAREN */ #line 241 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2124 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 38: /* arguments_list: argument */ #line 244 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2130 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 39: /* arguments_list: arguments_list argument */ #line 245 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2136 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 40: /* argument: name COLON input_value */ #line 248 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Argument, 4, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), rb_ary_entry(yyvsp[-2], 3), yyvsp[0] ); } #line 2149 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 41: /* literal_value: FLOAT */ #line 258 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_funcall(rb_ary_entry(yyvsp[0], 3), rb_intern("to_f"), 0); } #line 2155 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 42: /* literal_value: INT */ #line 259 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_funcall(rb_ary_entry(yyvsp[0], 3), rb_intern("to_i"), 0); } #line 2161 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 43: /* literal_value: STRING */ #line 260 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_entry(yyvsp[0], 3); } #line 2167 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 44: /* literal_value: TRUE_LITERAL */ #line 261 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qtrue; } #line 2173 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 45: /* literal_value: FALSE_LITERAL */ #line 262 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qfalse; } #line 2179 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 53: /* null_value: NULL_LITERAL */ #line 273 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(NullValue, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3) ); } #line 2191 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 54: /* variable: VAR_SIGN name */ #line 281 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(VariableIdentifier, 3, rb_ary_entry(yyvsp[-1], 1), rb_ary_entry(yyvsp[-1], 2), rb_ary_entry(yyvsp[0], 3) ); } #line 2203 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 55: /* list_value: LBRACKET RBRACKET */ #line 290 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2209 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 56: /* list_value: LBRACKET list_value_list RBRACKET */ #line 291 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2215 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 57: /* list_value_list: input_value */ #line 294 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2221 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 58: /* list_value_list: list_value_list input_value */ #line 295 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2227 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 63: /* enum_value: enum_name */ #line 303 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Enum, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3) ); } #line 2239 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 64: /* object_value: LCURLY object_value_list_opt RCURLY */ #line 312 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InputObject, 3, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), yyvsp[-1] ); } #line 2251 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 65: /* object_value_list_opt: %empty */ #line 321 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2257 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 67: /* object_value_list: object_value_field */ #line 325 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2263 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 68: /* object_value_list: object_value_list object_value_field */ #line 326 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2269 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 69: /* object_value_field: name COLON input_value */ #line 329 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Argument, 4, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), rb_ary_entry(yyvsp[-2], 3), yyvsp[0] ); } #line 2282 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 70: /* object_literal_value: LCURLY object_literal_value_list_opt RCURLY */ #line 340 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InputObject, 3, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), yyvsp[-1] ); } #line 2294 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 71: /* object_literal_value_list_opt: %empty */ #line 349 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2300 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 73: /* object_literal_value_list: object_literal_value_field */ #line 353 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2306 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 74: /* object_literal_value_list: object_literal_value_list object_literal_value_field */ #line 354 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2312 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 75: /* object_literal_value_field: name COLON literal_value */ #line 357 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Argument, 4, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), rb_ary_entry(yyvsp[-2], 3), yyvsp[0] ); } #line 2325 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 76: /* directives_list_opt: %empty */ #line 368 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2331 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 78: /* directives_list: directive */ #line 372 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2337 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 79: /* directives_list: directives_list directive */ #line 373 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2343 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 80: /* directive: DIR_SIGN name arguments_opt */ #line 375 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(Directive, 4, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), rb_ary_entry(yyvsp[-1], 3), yyvsp[0] ); } #line 2356 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 101: /* fragment_spread: ELLIPSIS name_without_on directives_list_opt */ #line 412 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(FragmentSpread, 4, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), rb_ary_entry(yyvsp[-1], 3), yyvsp[0] ); } #line 2369 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 102: /* inline_fragment: ELLIPSIS ON type directives_list_opt selection_set */ #line 422 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InlineFragment, 5, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), yyvsp[-2], yyvsp[-1], yyvsp[0] ); } #line 2383 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 103: /* inline_fragment: ELLIPSIS directives_list_opt selection_set */ #line 431 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InlineFragment, 5, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), Qnil, yyvsp[-1], yyvsp[0] ); } #line 2397 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 104: /* fragment_definition: FRAGMENT fragment_name_opt ON type directives_list_opt selection_set */ #line 442 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(FragmentDefinition, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), yyvsp[-4], yyvsp[-2], yyvsp[-1], yyvsp[0] ); } #line 2412 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 105: /* fragment_name_opt: %empty */ #line 454 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qnil; } #line 2418 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 106: /* fragment_name_opt: name_without_on */ #line 455 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_entry(yyvsp[0], 3); } #line 2424 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 108: /* type: nullable_type BANG */ #line 459 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(NonNullType, 3, rb_funcall(yyvsp[-1], rb_intern("line"), 0), rb_funcall(yyvsp[-1], rb_intern("col"), 0), yyvsp[-1]); } #line 2430 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 109: /* nullable_type: name */ #line 462 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3) ); } #line 2442 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 110: /* nullable_type: LBRACKET type RBRACKET */ #line 469 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(ListType, 3, rb_funcall(yyvsp[-1], rb_intern("line"), 0), rb_funcall(yyvsp[-1], rb_intern("col"), 0), yyvsp[-1] ); } #line 2454 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 114: /* schema_definition: SCHEMA directives_list_opt operation_type_definition_list_opt */ #line 483 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(SchemaDefinition, 6, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), // TODO use static strings: rb_hash_aref(yyvsp[0], rb_str_new_cstr("query")), rb_hash_aref(yyvsp[0], rb_str_new_cstr("mutation")), rb_hash_aref(yyvsp[0], rb_str_new_cstr("subscription")), yyvsp[-1] ); } #line 2470 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 115: /* operation_type_definition_list_opt: %empty */ #line 496 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_hash_new(); } #line 2476 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 116: /* operation_type_definition_list_opt: LCURLY operation_type_definition_list RCURLY */ #line 497 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2482 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 117: /* operation_type_definition_list: operation_type_definition */ #line 500 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_hash_new(); rb_hash_aset(yyval, rb_ary_entry(yyvsp[0], 0), rb_ary_entry(yyvsp[0], 1)); } #line 2491 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 118: /* operation_type_definition_list: operation_type_definition_list operation_type_definition */ #line 504 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_hash_aset(yyval, rb_ary_entry(yyvsp[0], 0), rb_ary_entry(yyvsp[0], 1)); } #line 2499 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 119: /* operation_type_definition: operation_type COLON name */ #line 509 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(2, rb_ary_entry(yyvsp[-2], 3), rb_ary_entry(yyvsp[0], 3)); } #line 2507 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 127: /* description_opt: %empty */ #line 524 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qnil; } #line 2513 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 129: /* scalar_type_definition: description_opt SCALAR name directives_list_opt */ #line 528 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(ScalarTypeDefinition, 5, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), rb_ary_entry(yyvsp[-1], 3), // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-3]) ? rb_ary_entry(yyvsp[-3], 3) : Qnil), yyvsp[0] ); } #line 2528 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 130: /* object_type_definition: description_opt TYPE_LITERAL name implements_opt directives_list_opt field_definition_list_opt */ #line 540 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(ObjectTypeDefinition, 7, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), rb_ary_entry(yyvsp[-3], 3), yyvsp[-2], // implements // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-5]) ? rb_ary_entry(yyvsp[-5], 3) : Qnil), yyvsp[-1], yyvsp[0] ); } #line 2545 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 131: /* implements_opt: %empty */ #line 554 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2551 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 132: /* implements_opt: IMPLEMENTS AMP interfaces_list */ #line 555 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[0]; } #line 2557 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 133: /* implements_opt: IMPLEMENTS interfaces_list */ #line 556 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[0]; } #line 2563 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 134: /* implements_opt: IMPLEMENTS legacy_interfaces_list */ #line 557 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[0]; } #line 2569 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 135: /* interfaces_list: name */ #line 560 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { VALUE new_name = MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3) ); yyval = rb_ary_new_from_args(1, new_name); } #line 2582 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 136: /* interfaces_list: interfaces_list AMP name */ #line 568 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { VALUE new_name = MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3)); rb_ary_push(yyval, new_name); } #line 2591 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 137: /* legacy_interfaces_list: name */ #line 574 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { VALUE new_name = MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3) ); yyval = rb_ary_new_from_args(1, new_name); } #line 2604 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 138: /* legacy_interfaces_list: legacy_interfaces_list name */ #line 582 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3))); } #line 2612 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 139: /* input_value_definition: description_opt name COLON type default_value_opt directives_list_opt */ #line 587 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InputValueDefinition, 7, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), rb_ary_entry(yyvsp[-4], 3), yyvsp[-2], yyvsp[-1], // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-5]) ? rb_ary_entry(yyvsp[-5], 3) : Qnil), yyvsp[0] ); } #line 2629 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 140: /* input_value_definition_list: input_value_definition */ #line 601 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2635 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 141: /* input_value_definition_list: input_value_definition_list input_value_definition */ #line 602 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2641 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 142: /* arguments_definitions_opt: %empty */ #line 605 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2647 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 143: /* arguments_definitions_opt: LPAREN input_value_definition_list RPAREN */ #line 606 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2653 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 144: /* field_definition: description_opt name arguments_definitions_opt COLON type directives_list_opt */ #line 609 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(FieldDefinition, 7, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), rb_ary_entry(yyvsp[-4], 3), yyvsp[-1], // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-5]) ? rb_ary_entry(yyvsp[-5], 3) : Qnil), yyvsp[-3], yyvsp[0] ); } #line 2670 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 145: /* field_definition_list_opt: %empty */ #line 623 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2676 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 146: /* field_definition_list_opt: LCURLY field_definition_list RCURLY */ #line 624 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = yyvsp[-1]; } #line 2682 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 147: /* field_definition_list: %empty */ #line 627 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = GraphQL_Language_Nodes_NONE; } #line 2688 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 148: /* field_definition_list: field_definition */ #line 628 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2694 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 149: /* field_definition_list: field_definition_list field_definition */ #line 629 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2700 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 150: /* interface_type_definition: description_opt INTERFACE name implements_opt directives_list_opt field_definition_list_opt */ #line 632 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InterfaceTypeDefinition, 7, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), rb_ary_entry(yyvsp[-3], 3), // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-5]) ? rb_ary_entry(yyvsp[-5], 3) : Qnil), yyvsp[-2], yyvsp[-1], yyvsp[0] ); } #line 2717 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 151: /* union_members: name */ #line 646 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { VALUE new_member = MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3) ); yyval = rb_ary_new_from_args(1, new_member); } #line 2730 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 152: /* union_members: union_members PIPE name */ #line 654 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, MAKE_AST_NODE(TypeName, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3))); } #line 2738 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 153: /* union_type_definition: description_opt UNION name directives_list_opt EQUALS union_members */ #line 659 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(UnionTypeDefinition, 6, rb_ary_entry(yyvsp[-4], 1), rb_ary_entry(yyvsp[-4], 2), rb_ary_entry(yyvsp[-3], 3), yyvsp[0], // types // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-5]) ? rb_ary_entry(yyvsp[-5], 3) : Qnil), yyvsp[-2] ); } #line 2754 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 154: /* enum_type_definition: description_opt ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY */ #line 672 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(EnumTypeDefinition, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-4], 3), // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-6]) ? rb_ary_entry(yyvsp[-6], 3) : Qnil), yyvsp[-3], yyvsp[-1] ); } #line 2770 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 155: /* enum_value_definition: description_opt enum_name directives_list_opt */ #line 685 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(EnumValueDefinition, 5, rb_ary_entry(yyvsp[-1], 1), rb_ary_entry(yyvsp[-1], 2), rb_ary_entry(yyvsp[-1], 3), // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-2]) ? rb_ary_entry(yyvsp[-2], 3) : Qnil), yyvsp[0] ); } #line 2785 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 156: /* enum_value_definitions: enum_value_definition */ #line 697 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, yyvsp[0]); } #line 2791 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 157: /* enum_value_definitions: enum_value_definitions enum_value_definition */ #line 698 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, yyvsp[0]); } #line 2797 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 158: /* input_object_type_definition: description_opt INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY */ #line 701 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InputObjectTypeDefinition, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-4], 3), // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-6]) ? rb_ary_entry(yyvsp[-6], 3) : Qnil), yyvsp[-3], yyvsp[-1] ); } #line 2813 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 159: /* directive_definition: description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt directive_repeatable_opt ON directive_locations */ #line 714 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(DirectiveDefinition, 7, rb_ary_entry(yyvsp[-6], 1), rb_ary_entry(yyvsp[-6], 2), rb_ary_entry(yyvsp[-4], 3), (RB_TEST(yyvsp[-2]) ? Qtrue : Qfalse), // repeatable // TODO see get_description for reading a description from comments (RB_TEST(yyvsp[-7]) ? rb_ary_entry(yyvsp[-7], 3) : Qnil), yyvsp[-3], yyvsp[0] ); } #line 2830 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 160: /* directive_repeatable_opt: %empty */ #line 728 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qnil; } #line 2836 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 161: /* directive_repeatable_opt: REPEATABLE */ #line 729 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = Qtrue; } #line 2842 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 162: /* directive_locations: name */ #line 732 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = rb_ary_new_from_args(1, MAKE_AST_NODE(DirectiveLocation, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3))); } #line 2848 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 163: /* directive_locations: directive_locations PIPE name */ #line 733 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { rb_ary_push(yyval, MAKE_AST_NODE(DirectiveLocation, 3, rb_ary_entry(yyvsp[0], 1), rb_ary_entry(yyvsp[0], 2), rb_ary_entry(yyvsp[0], 3))); } #line 2854 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 166: /* schema_extension: EXTEND SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY */ #line 741 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(SchemaExtension, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), // TODO use static strings: rb_hash_aref(yyvsp[-1], rb_str_new_cstr("query")), rb_hash_aref(yyvsp[-1], rb_str_new_cstr("mutation")), rb_hash_aref(yyvsp[-1], rb_str_new_cstr("subscription")), yyvsp[-3] ); } #line 2870 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 167: /* schema_extension: EXTEND SCHEMA directives_list */ #line 752 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(SchemaExtension, 6, rb_ary_entry(yyvsp[-2], 1), rb_ary_entry(yyvsp[-2], 2), Qnil, Qnil, Qnil, yyvsp[0] ); } #line 2885 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 174: /* scalar_type_extension: EXTEND SCALAR name directives_list */ #line 771 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(ScalarTypeExtension, 4, rb_ary_entry(yyvsp[-3], 1), rb_ary_entry(yyvsp[-3], 2), rb_ary_entry(yyvsp[-1], 3), yyvsp[0] ); } #line 2898 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 175: /* object_type_extension: EXTEND TYPE_LITERAL name implements_opt directives_list_opt field_definition_list_opt */ #line 781 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(ObjectTypeExtension, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-3], 3), yyvsp[-2], // implements yyvsp[-1], yyvsp[0] ); } #line 2913 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 176: /* interface_type_extension: EXTEND INTERFACE name implements_opt directives_list_opt field_definition_list_opt */ #line 793 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InterfaceTypeExtension, 6, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-3], 3), yyvsp[-2], yyvsp[-1], yyvsp[0] ); } #line 2928 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 177: /* union_type_extension: EXTEND UNION name directives_list_opt EQUALS union_members */ #line 805 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(UnionTypeExtension, 5, rb_ary_entry(yyvsp[-5], 1), rb_ary_entry(yyvsp[-5], 2), rb_ary_entry(yyvsp[-3], 3), yyvsp[0], // types yyvsp[-2] ); } #line 2942 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 178: /* union_type_extension: EXTEND UNION name directives_list */ #line 814 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(UnionTypeExtension, 5, rb_ary_entry(yyvsp[-3], 1), rb_ary_entry(yyvsp[-3], 2), rb_ary_entry(yyvsp[-1], 3), GraphQL_Language_Nodes_NONE, // types yyvsp[0] ); } #line 2956 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 179: /* enum_type_extension: EXTEND ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY */ #line 825 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(EnumTypeExtension, 5, rb_ary_entry(yyvsp[-6], 1), rb_ary_entry(yyvsp[-6], 2), rb_ary_entry(yyvsp[-4], 3), yyvsp[-3], yyvsp[-1] ); } #line 2970 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 180: /* enum_type_extension: EXTEND ENUM name directives_list */ #line 834 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(EnumTypeExtension, 5, rb_ary_entry(yyvsp[-3], 1), rb_ary_entry(yyvsp[-3], 2), rb_ary_entry(yyvsp[-1], 3), yyvsp[0], GraphQL_Language_Nodes_NONE ); } #line 2984 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 181: /* input_object_type_extension: EXTEND INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY */ #line 845 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InputObjectTypeExtension, 5, rb_ary_entry(yyvsp[-6], 1), rb_ary_entry(yyvsp[-6], 2), rb_ary_entry(yyvsp[-4], 3), yyvsp[-3], yyvsp[-1] ); } #line 2998 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; case 182: /* input_object_type_extension: EXTEND INPUT name directives_list */ #line 854 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" { yyval = MAKE_AST_NODE(InputObjectTypeExtension, 5, rb_ary_entry(yyvsp[-3], 1), rb_ary_entry(yyvsp[-3], 2), rb_ary_entry(yyvsp[-1], 3), yyvsp[0], GraphQL_Language_Nodes_NONE ); } #line 3012 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" break; #line 3016 "graphql-c_parser/ext/graphql_c_parser_ext/parser.c" default: break; } /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, but that translation would be missed if the semantic action invokes YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an incorrect destructor might then be invoked immediately. In the case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; *++yyvsp = yyval; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ { const int yylhs = yyr1[yyn] - YYNTOKENS; const int yyi = yypgoto[yylhs] + *yyssp; yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp ? yytable[yyi] : yydefgoto[yylhs]); } goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; { yypcontext_t yyctx = {yyssp, yytoken}; char const *yymsgp = YY_("syntax error"); int yysyntax_error_status; yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); if (yysyntax_error_status == 0) yymsgp = yymsg; else if (yysyntax_error_status == -1) { if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = YY_CAST (char *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); if (yymsg) { yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); yymsgp = yymsg; } else { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; yysyntax_error_status = YYENOMEM; } } yyerror (parser, filename, yymsgp); if (yysyntax_error_status == YYENOMEM) YYNOMEM; } } if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval, parser, filename); yychar = YYEMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (0) YYERROR; ++yynerrs; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ /* Pop stack until we find a state that shifts the error token. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { yyn += YYSYMBOL_YYerror; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yydestruct ("Error: popping", YY_ACCESSING_SYMBOL (yystate), yyvsp, parser, filename); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturnlab; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturnlab; /*-----------------------------------------------------------. | yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. | `-----------------------------------------------------------*/ yyexhaustedlab: yyerror (parser, filename, YY_("memory exhausted")); yyresult = 2; goto yyreturnlab; /*----------------------------------------------------------. | yyreturnlab -- parsing is finished, clean up and return. | `----------------------------------------------------------*/ yyreturnlab: if (yychar != YYEMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = YYTRANSLATE (yychar); yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval, parser, filename); } /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, parser, filename); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); return yyresult; } #line 864 "graphql-c_parser/ext/graphql_c_parser_ext/parser.y" // Custom functions int yylex (YYSTYPE *lvalp, VALUE parser, VALUE filename) { VALUE next_token_idx_rb_int = rb_ivar_get(parser, rb_intern("@next_token_index")); int next_token_idx = FIX2INT(next_token_idx_rb_int); VALUE tokens = rb_ivar_get(parser, rb_intern("@tokens")); VALUE next_token = rb_ary_entry(tokens, next_token_idx); if (!RB_TEST(next_token)) { return YYEOF; } rb_ivar_set(parser, rb_intern("@next_token_index"), INT2FIX(next_token_idx + 1)); VALUE token_type_rb_int = rb_ary_entry(next_token, 5); int next_token_type = FIX2INT(token_type_rb_int); if (next_token_type == 241) { // BAD_UNICODE_ESCAPE VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mCParser = rb_const_get_at(mGraphQL, rb_intern("CParser")); VALUE bad_unicode_error = rb_funcall( mCParser, rb_intern("prepare_bad_unicode_error"), 1, parser ); rb_exc_raise(bad_unicode_error); } *lvalp = next_token; return next_token_type; } void yyerror(VALUE parser, VALUE filename, const char *msg) { VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mCParser = rb_const_get_at(mGraphQL, rb_intern("CParser")); VALUE rb_message = rb_str_new_cstr(msg); VALUE exception = rb_funcall( mCParser, rb_intern("prepare_parse_error"), 2, rb_message, parser ); rb_exc_raise(exception); } #define INITIALIZE_NODE_CLASS_VARIABLE(node_class_name) \ rb_global_variable(&GraphQL_Language_Nodes_##node_class_name); \ GraphQL_Language_Nodes_##node_class_name = rb_const_get_at(mGraphQLLanguageNodes, rb_intern(#node_class_name)); void initialize_node_class_variables() { VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mGraphQLLanguage = rb_const_get_at(mGraphQL, rb_intern("Language")); VALUE mGraphQLLanguageNodes = rb_const_get_at(mGraphQLLanguage, rb_intern("Nodes")); rb_global_variable(&GraphQL_Language_Nodes_NONE); GraphQL_Language_Nodes_NONE = rb_ary_new(); rb_ary_freeze(GraphQL_Language_Nodes_NONE); rb_global_variable(&r_string_query); r_string_query = rb_str_new_cstr("query"); rb_str_freeze(r_string_query); INITIALIZE_NODE_CLASS_VARIABLE(Argument) INITIALIZE_NODE_CLASS_VARIABLE(Directive) INITIALIZE_NODE_CLASS_VARIABLE(Document) INITIALIZE_NODE_CLASS_VARIABLE(Enum) INITIALIZE_NODE_CLASS_VARIABLE(Field) INITIALIZE_NODE_CLASS_VARIABLE(FragmentDefinition) INITIALIZE_NODE_CLASS_VARIABLE(FragmentSpread) INITIALIZE_NODE_CLASS_VARIABLE(InlineFragment) INITIALIZE_NODE_CLASS_VARIABLE(InputObject) INITIALIZE_NODE_CLASS_VARIABLE(ListType) INITIALIZE_NODE_CLASS_VARIABLE(NonNullType) INITIALIZE_NODE_CLASS_VARIABLE(NullValue) INITIALIZE_NODE_CLASS_VARIABLE(OperationDefinition) INITIALIZE_NODE_CLASS_VARIABLE(TypeName) INITIALIZE_NODE_CLASS_VARIABLE(VariableDefinition) INITIALIZE_NODE_CLASS_VARIABLE(VariableIdentifier) INITIALIZE_NODE_CLASS_VARIABLE(ScalarTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(ObjectTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(InterfaceTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(UnionTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(EnumTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(InputObjectTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(EnumValueDefinition) INITIALIZE_NODE_CLASS_VARIABLE(DirectiveDefinition) INITIALIZE_NODE_CLASS_VARIABLE(DirectiveLocation) INITIALIZE_NODE_CLASS_VARIABLE(FieldDefinition) INITIALIZE_NODE_CLASS_VARIABLE(InputValueDefinition) INITIALIZE_NODE_CLASS_VARIABLE(SchemaDefinition) INITIALIZE_NODE_CLASS_VARIABLE(ScalarTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(ObjectTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(InterfaceTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(UnionTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(EnumTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(InputObjectTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(SchemaExtension) } graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/parser.h000066400000000000000000000002141476434635200261260ustar00rootroot00000000000000#ifndef Graphql_parser_h #define Graphql_parser_h int yyparse(VALUE parser, VALUE filename); void initialize_node_class_variables(); #endif graphql-ruby-2.2.17/graphql-c_parser/ext/graphql_c_parser_ext/parser.y000066400000000000000000000700561476434635200261620ustar00rootroot00000000000000%require "3.8" %define api.pure full %define parse.error detailed %{ // C Declarations #include #define YYSTYPE VALUE int yylex(YYSTYPE *, VALUE, VALUE); void yyerror(VALUE, VALUE, const char*); static VALUE GraphQL_Language_Nodes_NONE; static VALUE r_string_query; #define MAKE_AST_NODE(node_class_name, nargs, ...) rb_funcall(GraphQL_Language_Nodes_##node_class_name, rb_intern("from_a"), nargs + 1, filename,__VA_ARGS__) #define SETUP_NODE_CLASS_VARIABLE(node_class_name) static VALUE GraphQL_Language_Nodes_##node_class_name; SETUP_NODE_CLASS_VARIABLE(Argument) SETUP_NODE_CLASS_VARIABLE(Directive) SETUP_NODE_CLASS_VARIABLE(Document) SETUP_NODE_CLASS_VARIABLE(Enum) SETUP_NODE_CLASS_VARIABLE(Field) SETUP_NODE_CLASS_VARIABLE(FragmentDefinition) SETUP_NODE_CLASS_VARIABLE(FragmentSpread) SETUP_NODE_CLASS_VARIABLE(InlineFragment) SETUP_NODE_CLASS_VARIABLE(InputObject) SETUP_NODE_CLASS_VARIABLE(ListType) SETUP_NODE_CLASS_VARIABLE(NonNullType) SETUP_NODE_CLASS_VARIABLE(NullValue) SETUP_NODE_CLASS_VARIABLE(OperationDefinition) SETUP_NODE_CLASS_VARIABLE(TypeName) SETUP_NODE_CLASS_VARIABLE(VariableDefinition) SETUP_NODE_CLASS_VARIABLE(VariableIdentifier) SETUP_NODE_CLASS_VARIABLE(ScalarTypeDefinition) SETUP_NODE_CLASS_VARIABLE(ObjectTypeDefinition) SETUP_NODE_CLASS_VARIABLE(InterfaceTypeDefinition) SETUP_NODE_CLASS_VARIABLE(UnionTypeDefinition) SETUP_NODE_CLASS_VARIABLE(EnumTypeDefinition) SETUP_NODE_CLASS_VARIABLE(InputObjectTypeDefinition) SETUP_NODE_CLASS_VARIABLE(EnumValueDefinition) SETUP_NODE_CLASS_VARIABLE(DirectiveDefinition) SETUP_NODE_CLASS_VARIABLE(DirectiveLocation) SETUP_NODE_CLASS_VARIABLE(FieldDefinition) SETUP_NODE_CLASS_VARIABLE(InputValueDefinition) SETUP_NODE_CLASS_VARIABLE(SchemaDefinition) SETUP_NODE_CLASS_VARIABLE(ScalarTypeExtension) SETUP_NODE_CLASS_VARIABLE(ObjectTypeExtension) SETUP_NODE_CLASS_VARIABLE(InterfaceTypeExtension) SETUP_NODE_CLASS_VARIABLE(UnionTypeExtension) SETUP_NODE_CLASS_VARIABLE(EnumTypeExtension) SETUP_NODE_CLASS_VARIABLE(InputObjectTypeExtension) SETUP_NODE_CLASS_VARIABLE(SchemaExtension) %} %param {VALUE parser} %param {VALUE filename} // YACC Declarations %token AMP 200 %token BANG 201 %token COLON 202 %token DIRECTIVE 203 %token DIR_SIGN 204 %token ENUM 205 %token ELLIPSIS 206 %token EQUALS 207 %token EXTEND 208 %token FALSE_LITERAL 209 %token FLOAT 210 %token FRAGMENT 211 %token IDENTIFIER 212 %token INPUT 213 %token IMPLEMENTS 214 %token INT 215 %token INTERFACE 216 %token LBRACKET 217 %token LCURLY 218 %token LPAREN 219 %token MUTATION 220 %token NULL_LITERAL 221 %token ON 222 %token PIPE 223 %token QUERY 224 %token RBRACKET 225 %token RCURLY 226 %token REPEATABLE 227 %token RPAREN 228 %token SCALAR 229 %token SCHEMA 230 %token STRING 231 %token SUBSCRIPTION 232 %token TRUE_LITERAL 233 %token TYPE_LITERAL 234 %token UNION 235 %token VAR_SIGN 236 %% // YACC Rules start: document { rb_ivar_set(parser, rb_intern("@result"), $1); } document: definitions_list { VALUE position_source = rb_ary_entry($1, 0); VALUE line, col; if (RB_TEST(position_source)) { line = rb_funcall(position_source, rb_intern("line"), 0); col = rb_funcall(position_source, rb_intern("col"), 0); } else { line = INT2FIX(1); col = INT2FIX(1); } $$ = MAKE_AST_NODE(Document, 3, line, col, $1); } definitions_list: definition { $$ = rb_ary_new_from_args(1, $1); } | definitions_list definition { rb_ary_push($$, $2); } definition: executable_definition | type_system_definition | type_system_extension executable_definition: operation_definition | fragment_definition operation_definition: operation_type operation_name_opt variable_definitions_opt directives_list_opt selection_set { $$ = MAKE_AST_NODE(OperationDefinition, 7, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3), (RB_TEST($2) ? rb_ary_entry($2, 3) : Qnil), $3, $4, $5 ); } | LCURLY selection_list RCURLY { $$ = MAKE_AST_NODE(OperationDefinition, 7, rb_ary_entry($1, 1), rb_ary_entry($1, 2), r_string_query, Qnil, GraphQL_Language_Nodes_NONE, GraphQL_Language_Nodes_NONE, $2 ); } | LCURLY RCURLY { $$ = MAKE_AST_NODE(OperationDefinition, 7, rb_ary_entry($1, 1), rb_ary_entry($1, 2), r_string_query, Qnil, GraphQL_Language_Nodes_NONE, GraphQL_Language_Nodes_NONE, GraphQL_Language_Nodes_NONE ); } operation_type: QUERY | MUTATION | SUBSCRIPTION operation_name_opt: /* none */ { $$ = Qnil; } | name variable_definitions_opt: /* none */ { $$ = GraphQL_Language_Nodes_NONE; } | LPAREN variable_definitions_list RPAREN { $$ = $2; } variable_definitions_list: variable_definition { $$ = rb_ary_new_from_args(1, $1); } | variable_definitions_list variable_definition { rb_ary_push($$, $2); } variable_definition: VAR_SIGN name COLON type default_value_opt directives_list_opt { $$ = MAKE_AST_NODE(VariableDefinition, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($2, 3), $4, $5, $6 ); } default_value_opt: /* none */ { $$ = Qnil; } | EQUALS literal_value { $$ = $2; } selection_list: selection { $$ = rb_ary_new_from_args(1, $1); } | selection_list selection { rb_ary_push($$, $2); } selection: field | fragment_spread | inline_fragment selection_set: LCURLY selection_list RCURLY { $$ = $2; } selection_set_opt: /* none */ { $$ = rb_ary_new(); } | selection_set field: name COLON name arguments_opt directives_list_opt selection_set_opt { $$ = MAKE_AST_NODE(Field, 7, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3), // alias rb_ary_entry($3, 3), // name $4, // args $5, // directives $6 // subselections ); } | name arguments_opt directives_list_opt selection_set_opt { $$ = MAKE_AST_NODE(Field, 7, rb_ary_entry($1, 1), rb_ary_entry($1, 2), Qnil, // alias rb_ary_entry($1, 3), // name $2, // args $3, // directives $4 // subselections ); } arguments_opt: /* none */ { $$ = GraphQL_Language_Nodes_NONE; } | LPAREN arguments_list RPAREN { $$ = $2; } arguments_list: argument { $$ = rb_ary_new_from_args(1, $1); } | arguments_list argument { rb_ary_push($$, $2); } argument: name COLON input_value { $$ = MAKE_AST_NODE(Argument, 4, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3), $3 ); } literal_value: FLOAT { $$ = rb_funcall(rb_ary_entry($1, 3), rb_intern("to_f"), 0); } | INT { $$ = rb_funcall(rb_ary_entry($1, 3), rb_intern("to_i"), 0); } | STRING { $$ = rb_ary_entry($1, 3); } | TRUE_LITERAL { $$ = Qtrue; } | FALSE_LITERAL { $$ = Qfalse; } | null_value | enum_value | list_value | object_literal_value input_value: literal_value | variable | object_value null_value: NULL_LITERAL { $$ = MAKE_AST_NODE(NullValue, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3) ); } variable: VAR_SIGN name { $$ = MAKE_AST_NODE(VariableIdentifier, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($2, 3) ); } list_value: LBRACKET RBRACKET { $$ = GraphQL_Language_Nodes_NONE; } | LBRACKET list_value_list RBRACKET { $$ = $2; } list_value_list: input_value { $$ = rb_ary_new_from_args(1, $1); } | list_value_list input_value { rb_ary_push($$, $2); } enum_name: /* any identifier, but not "true", "false" or "null" */ IDENTIFIER | ON | operation_type | schema_keyword enum_value: enum_name { $$ = MAKE_AST_NODE(Enum, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3) ); } object_value: LCURLY object_value_list_opt RCURLY { $$ = MAKE_AST_NODE(InputObject, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), $2 ); } object_value_list_opt: /* nothing */ { $$ = GraphQL_Language_Nodes_NONE; } | object_value_list object_value_list: object_value_field { $$ = rb_ary_new_from_args(1, $1); } | object_value_list object_value_field { rb_ary_push($$, $2); } object_value_field: name COLON input_value { $$ = MAKE_AST_NODE(Argument, 4, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3), $3 ); } /* like the previous, but with literals only: */ object_literal_value: LCURLY object_literal_value_list_opt RCURLY { $$ = MAKE_AST_NODE(InputObject, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), $2 ); } object_literal_value_list_opt: /* nothing */ { $$ = GraphQL_Language_Nodes_NONE; } | object_literal_value_list object_literal_value_list: object_literal_value_field { $$ = rb_ary_new_from_args(1, $1); } | object_literal_value_list object_literal_value_field { rb_ary_push($$, $2); } object_literal_value_field: name COLON literal_value { $$ = MAKE_AST_NODE(Argument, 4, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3), $3 ); } directives_list_opt: /* none */ { $$ = GraphQL_Language_Nodes_NONE; } | directives_list directives_list: directive { $$ = rb_ary_new_from_args(1, $1); } | directives_list directive { rb_ary_push($$, $2); } directive: DIR_SIGN name arguments_opt { $$ = MAKE_AST_NODE(Directive, 4, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($2, 3), $3 ); } name: name_without_on | ON schema_keyword: SCHEMA | SCALAR | TYPE_LITERAL | IMPLEMENTS | INTERFACE | UNION | ENUM | INPUT | DIRECTIVE | EXTEND | FRAGMENT | REPEATABLE name_without_on: IDENTIFIER | TRUE_LITERAL | FALSE_LITERAL | NULL_LITERAL | operation_type | schema_keyword fragment_spread: ELLIPSIS name_without_on directives_list_opt { $$ = MAKE_AST_NODE(FragmentSpread, 4, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($2, 3), $3 ); } inline_fragment: ELLIPSIS ON type directives_list_opt selection_set { $$ = MAKE_AST_NODE(InlineFragment, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), $3, $4, $5 ); } | ELLIPSIS directives_list_opt selection_set { $$ = MAKE_AST_NODE(InlineFragment, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), Qnil, $2, $3 ); } fragment_definition: FRAGMENT fragment_name_opt ON type directives_list_opt selection_set { $$ = MAKE_AST_NODE(FragmentDefinition, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), $2, $4, $5, $6 ); } fragment_name_opt: /* none */ { $$ = Qnil; } | name_without_on { $$ = rb_ary_entry($1, 3); } type: nullable_type | nullable_type BANG { $$ = MAKE_AST_NODE(NonNullType, 3, rb_funcall($1, rb_intern("line"), 0), rb_funcall($1, rb_intern("col"), 0), $1); } nullable_type: name { $$ = MAKE_AST_NODE(TypeName, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3) ); } | LBRACKET type RBRACKET { $$ = MAKE_AST_NODE(ListType, 3, rb_funcall($2, rb_intern("line"), 0), rb_funcall($2, rb_intern("col"), 0), $2 ); } type_system_definition: schema_definition | type_definition | directive_definition schema_definition: SCHEMA directives_list_opt operation_type_definition_list_opt { $$ = MAKE_AST_NODE(SchemaDefinition, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), // TODO use static strings: rb_hash_aref($3, rb_str_new_cstr("query")), rb_hash_aref($3, rb_str_new_cstr("mutation")), rb_hash_aref($3, rb_str_new_cstr("subscription")), $2 ); } operation_type_definition_list_opt: /* none */ { $$ = rb_hash_new(); } | LCURLY operation_type_definition_list RCURLY { $$ = $2; } operation_type_definition_list: operation_type_definition { $$ = rb_hash_new(); rb_hash_aset($$, rb_ary_entry($1, 0), rb_ary_entry($1, 1)); } | operation_type_definition_list operation_type_definition { rb_hash_aset($$, rb_ary_entry($2, 0), rb_ary_entry($2, 1)); } operation_type_definition: operation_type COLON name { $$ = rb_ary_new_from_args(2, rb_ary_entry($1, 3), rb_ary_entry($3, 3)); } type_definition: scalar_type_definition | object_type_definition | interface_type_definition | union_type_definition | enum_type_definition | input_object_type_definition description: STRING description_opt: /* none */ { $$ = Qnil; } | description scalar_type_definition: description_opt SCALAR name directives_list_opt { $$ = MAKE_AST_NODE(ScalarTypeDefinition, 5, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($3, 3), // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $4 ); } object_type_definition: description_opt TYPE_LITERAL name implements_opt directives_list_opt field_definition_list_opt { $$ = MAKE_AST_NODE(ObjectTypeDefinition, 7, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($3, 3), $4, // implements // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $5, $6 ); } implements_opt: /* none */ { $$ = GraphQL_Language_Nodes_NONE; } | IMPLEMENTS AMP interfaces_list { $$ = $3; } | IMPLEMENTS interfaces_list { $$ = $2; } | IMPLEMENTS legacy_interfaces_list { $$ = $2; } interfaces_list: name { VALUE new_name = MAKE_AST_NODE(TypeName, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3) ); $$ = rb_ary_new_from_args(1, new_name); } | interfaces_list AMP name { VALUE new_name = MAKE_AST_NODE(TypeName, 3, rb_ary_entry($3, 1), rb_ary_entry($3, 2), rb_ary_entry($3, 3)); rb_ary_push($$, new_name); } legacy_interfaces_list: name { VALUE new_name = MAKE_AST_NODE(TypeName, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3) ); $$ = rb_ary_new_from_args(1, new_name); } | legacy_interfaces_list name { rb_ary_push($$, MAKE_AST_NODE(TypeName, 3, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($2, 3))); } input_value_definition: description_opt name COLON type default_value_opt directives_list_opt { $$ = MAKE_AST_NODE(InputValueDefinition, 7, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($2, 3), $4, $5, // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $6 ); } input_value_definition_list: input_value_definition { $$ = rb_ary_new_from_args(1, $1); } | input_value_definition_list input_value_definition { rb_ary_push($$, $2); } arguments_definitions_opt: /* none */ { $$ = GraphQL_Language_Nodes_NONE; } | LPAREN input_value_definition_list RPAREN { $$ = $2; } field_definition: description_opt name arguments_definitions_opt COLON type directives_list_opt { $$ = MAKE_AST_NODE(FieldDefinition, 7, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($2, 3), $5, // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $3, $6 ); } field_definition_list_opt: /* none */ { $$ = GraphQL_Language_Nodes_NONE; } | LCURLY field_definition_list RCURLY { $$ = $2; } field_definition_list: /* none - this is not actually valid but graphql-ruby used to print this */ { $$ = GraphQL_Language_Nodes_NONE; } | field_definition { $$ = rb_ary_new_from_args(1, $1); } | field_definition_list field_definition { rb_ary_push($$, $2); } interface_type_definition: description_opt INTERFACE name implements_opt directives_list_opt field_definition_list_opt { $$ = MAKE_AST_NODE(InterfaceTypeDefinition, 7, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($3, 3), // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $4, $5, $6 ); } union_members: name { VALUE new_member = MAKE_AST_NODE(TypeName, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3) ); $$ = rb_ary_new_from_args(1, new_member); } | union_members PIPE name { rb_ary_push($$, MAKE_AST_NODE(TypeName, 3, rb_ary_entry($3, 1), rb_ary_entry($3, 2), rb_ary_entry($3, 3))); } union_type_definition: description_opt UNION name directives_list_opt EQUALS union_members { $$ = MAKE_AST_NODE(UnionTypeDefinition, 6, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($3, 3), $6, // types // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $4 ); } enum_type_definition: description_opt ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY { $$ = MAKE_AST_NODE(EnumTypeDefinition, 6, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($3, 3), // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $4, $6 ); } enum_value_definition: description_opt enum_name directives_list_opt { $$ = MAKE_AST_NODE(EnumValueDefinition, 5, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($2, 3), // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $3 ); } enum_value_definitions: enum_value_definition { $$ = rb_ary_new_from_args(1, $1); } | enum_value_definitions enum_value_definition { rb_ary_push($$, $2); } input_object_type_definition: description_opt INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY { $$ = MAKE_AST_NODE(InputObjectTypeDefinition, 6, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($3, 3), // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $4, $6 ); } directive_definition: description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt directive_repeatable_opt ON directive_locations { $$ = MAKE_AST_NODE(DirectiveDefinition, 7, rb_ary_entry($2, 1), rb_ary_entry($2, 2), rb_ary_entry($4, 3), (RB_TEST($6) ? Qtrue : Qfalse), // repeatable // TODO see get_description for reading a description from comments (RB_TEST($1) ? rb_ary_entry($1, 3) : Qnil), $5, $8 ); } directive_repeatable_opt: /* nothing */ { $$ = Qnil; } | REPEATABLE { $$ = Qtrue; } directive_locations: name { $$ = rb_ary_new_from_args(1, MAKE_AST_NODE(DirectiveLocation, 3, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($1, 3))); } | directive_locations PIPE name { rb_ary_push($$, MAKE_AST_NODE(DirectiveLocation, 3, rb_ary_entry($3, 1), rb_ary_entry($3, 2), rb_ary_entry($3, 3))); } type_system_extension: schema_extension | type_extension schema_extension: EXTEND SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY { $$ = MAKE_AST_NODE(SchemaExtension, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), // TODO use static strings: rb_hash_aref($5, rb_str_new_cstr("query")), rb_hash_aref($5, rb_str_new_cstr("mutation")), rb_hash_aref($5, rb_str_new_cstr("subscription")), $3 ); } | EXTEND SCHEMA directives_list { $$ = MAKE_AST_NODE(SchemaExtension, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), Qnil, Qnil, Qnil, $3 ); } type_extension: scalar_type_extension | object_type_extension | interface_type_extension | union_type_extension | enum_type_extension | input_object_type_extension scalar_type_extension: EXTEND SCALAR name directives_list { $$ = MAKE_AST_NODE(ScalarTypeExtension, 4, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4 ); } object_type_extension: EXTEND TYPE_LITERAL name implements_opt directives_list_opt field_definition_list_opt { $$ = MAKE_AST_NODE(ObjectTypeExtension, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4, // implements $5, $6 ); } interface_type_extension: EXTEND INTERFACE name implements_opt directives_list_opt field_definition_list_opt { $$ = MAKE_AST_NODE(InterfaceTypeExtension, 6, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4, $5, $6 ); } union_type_extension: EXTEND UNION name directives_list_opt EQUALS union_members { $$ = MAKE_AST_NODE(UnionTypeExtension, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $6, // types $4 ); } | EXTEND UNION name directives_list { $$ = MAKE_AST_NODE(UnionTypeExtension, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), GraphQL_Language_Nodes_NONE, // types $4 ); } enum_type_extension: EXTEND ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY { $$ = MAKE_AST_NODE(EnumTypeExtension, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4, $6 ); } | EXTEND ENUM name directives_list { $$ = MAKE_AST_NODE(EnumTypeExtension, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4, GraphQL_Language_Nodes_NONE ); } input_object_type_extension: EXTEND INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY { $$ = MAKE_AST_NODE(InputObjectTypeExtension, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4, $6 ); } | EXTEND INPUT name directives_list { $$ = MAKE_AST_NODE(InputObjectTypeExtension, 5, rb_ary_entry($1, 1), rb_ary_entry($1, 2), rb_ary_entry($3, 3), $4, GraphQL_Language_Nodes_NONE ); } %% // Custom functions int yylex (YYSTYPE *lvalp, VALUE parser, VALUE filename) { VALUE next_token_idx_rb_int = rb_ivar_get(parser, rb_intern("@next_token_index")); int next_token_idx = FIX2INT(next_token_idx_rb_int); VALUE tokens = rb_ivar_get(parser, rb_intern("@tokens")); VALUE next_token = rb_ary_entry(tokens, next_token_idx); if (!RB_TEST(next_token)) { return YYEOF; } rb_ivar_set(parser, rb_intern("@next_token_index"), INT2FIX(next_token_idx + 1)); VALUE token_type_rb_int = rb_ary_entry(next_token, 5); int next_token_type = FIX2INT(token_type_rb_int); if (next_token_type == 241) { // BAD_UNICODE_ESCAPE VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mCParser = rb_const_get_at(mGraphQL, rb_intern("CParser")); VALUE bad_unicode_error = rb_funcall( mCParser, rb_intern("prepare_bad_unicode_error"), 1, parser ); rb_exc_raise(bad_unicode_error); } *lvalp = next_token; return next_token_type; } void yyerror(VALUE parser, VALUE filename, const char *msg) { VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mCParser = rb_const_get_at(mGraphQL, rb_intern("CParser")); VALUE rb_message = rb_str_new_cstr(msg); VALUE exception = rb_funcall( mCParser, rb_intern("prepare_parse_error"), 2, rb_message, parser ); rb_exc_raise(exception); } #define INITIALIZE_NODE_CLASS_VARIABLE(node_class_name) \ rb_global_variable(&GraphQL_Language_Nodes_##node_class_name); \ GraphQL_Language_Nodes_##node_class_name = rb_const_get_at(mGraphQLLanguageNodes, rb_intern(#node_class_name)); void initialize_node_class_variables() { VALUE mGraphQL = rb_const_get_at(rb_cObject, rb_intern("GraphQL")); VALUE mGraphQLLanguage = rb_const_get_at(mGraphQL, rb_intern("Language")); VALUE mGraphQLLanguageNodes = rb_const_get_at(mGraphQLLanguage, rb_intern("Nodes")); rb_global_variable(&GraphQL_Language_Nodes_NONE); GraphQL_Language_Nodes_NONE = rb_ary_new(); rb_ary_freeze(GraphQL_Language_Nodes_NONE); rb_global_variable(&r_string_query); r_string_query = rb_str_new_cstr("query"); rb_str_freeze(r_string_query); INITIALIZE_NODE_CLASS_VARIABLE(Argument) INITIALIZE_NODE_CLASS_VARIABLE(Directive) INITIALIZE_NODE_CLASS_VARIABLE(Document) INITIALIZE_NODE_CLASS_VARIABLE(Enum) INITIALIZE_NODE_CLASS_VARIABLE(Field) INITIALIZE_NODE_CLASS_VARIABLE(FragmentDefinition) INITIALIZE_NODE_CLASS_VARIABLE(FragmentSpread) INITIALIZE_NODE_CLASS_VARIABLE(InlineFragment) INITIALIZE_NODE_CLASS_VARIABLE(InputObject) INITIALIZE_NODE_CLASS_VARIABLE(ListType) INITIALIZE_NODE_CLASS_VARIABLE(NonNullType) INITIALIZE_NODE_CLASS_VARIABLE(NullValue) INITIALIZE_NODE_CLASS_VARIABLE(OperationDefinition) INITIALIZE_NODE_CLASS_VARIABLE(TypeName) INITIALIZE_NODE_CLASS_VARIABLE(VariableDefinition) INITIALIZE_NODE_CLASS_VARIABLE(VariableIdentifier) INITIALIZE_NODE_CLASS_VARIABLE(ScalarTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(ObjectTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(InterfaceTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(UnionTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(EnumTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(InputObjectTypeDefinition) INITIALIZE_NODE_CLASS_VARIABLE(EnumValueDefinition) INITIALIZE_NODE_CLASS_VARIABLE(DirectiveDefinition) INITIALIZE_NODE_CLASS_VARIABLE(DirectiveLocation) INITIALIZE_NODE_CLASS_VARIABLE(FieldDefinition) INITIALIZE_NODE_CLASS_VARIABLE(InputValueDefinition) INITIALIZE_NODE_CLASS_VARIABLE(SchemaDefinition) INITIALIZE_NODE_CLASS_VARIABLE(ScalarTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(ObjectTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(InterfaceTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(UnionTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(EnumTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(InputObjectTypeExtension) INITIALIZE_NODE_CLASS_VARIABLE(SchemaExtension) } graphql-ruby-2.2.17/graphql-c_parser/graphql-c_parser.gemspec000066400000000000000000000021071476434635200242670ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.push File.expand_path("../lib", __FILE__) require "graphql/c_parser/version" require "date" Gem::Specification.new do |s| s.name = "graphql-c_parser" s.version = GraphQL::CParser::VERSION s.date = Date.today.to_s s.summary = "A parser for GraphQL, implemented as a C extension" s.homepage = "https://github.com/rmosolgo/graphql-ruby" s.authors = ["Robert Mosolgo"] s.email = ["rdmosolgo@gmail.com"] s.license = "MIT" s.required_ruby_version = ">= 2.4.0" s.metadata = { "homepage_uri" => "https://graphql-ruby.org", "changelog_uri" => "https://github.com/rmosolgo/graphql-ruby/blob/master/graphql-c_parser/CHANGELOG.md", "source_code_uri" => "https://github.com/rmosolgo/graphql-ruby", "bug_tracker_uri" => "https://github.com/rmosolgo/graphql-ruby/issues", "mailing_list_uri" => "https://buttondown.email/graphql-ruby", } s.files = Dir["{lib,ext}/**/*.{rb,h,c}"] s.extensions << "ext/graphql_c_parser_ext/extconf.rb" s.add_dependency "graphql", ">= 2.2.10" end graphql-ruby-2.2.17/graphql-c_parser/lib/000077500000000000000000000000001476434635200202365ustar00rootroot00000000000000graphql-ruby-2.2.17/graphql-c_parser/lib/graphql-c_parser.rb000066400000000000000000000000711476434635200240130ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/c_parser" graphql-ruby-2.2.17/graphql-c_parser/lib/graphql/000077500000000000000000000000001476434635200216745ustar00rootroot00000000000000graphql-ruby-2.2.17/graphql-c_parser/lib/graphql/c_parser.rb000066400000000000000000000071221476434635200240210ustar00rootroot00000000000000# frozen_string_literal: true require "graphql" require "graphql/c_parser/version" require "graphql/graphql_c_parser_ext" module GraphQL module CParser def self.parse(query_str, filename: nil, trace: GraphQL::Tracing::NullTrace) parser = Parser.new(query_str, filename, trace) parser.result end def self.parse_file(filename) contents = File.read(filename) parse(contents, filename: filename) end def self.prepare_parse_error(message, parser) if message.start_with?("memory exhausted") return GraphQL::ParseError.new("This query is too large to execute.", nil, nil, parser.query_string, filename: parser.filename) end token = parser.tokens[parser.next_token_index - 1] if token # There might not be a token if it's a comments-only string line = token[1] col = token[2] if line && col location_str = " at [#{line}, #{col}]" if !message.include?(location_str) message += location_str end end if !message.include?("end of file") message.sub!(/, unexpected ([a-zA-Z ]+)(,| at)/, ", unexpected \\1 (#{token[3].inspect})\\2") end end GraphQL::ParseError.new(message, line, col, parser.query_string, filename: parser.filename) end def self.prepare_bad_unicode_error(parser) token = parser.tokens[parser.next_token_index - 1] line = token[1] col = token[2] GraphQL::ParseError.new( "Parse error on bad Unicode escape sequence: #{token[3].inspect} (error) at [#{line}, #{col}]", line, col, parser.query_string, filename: parser.filename ) end module Lexer def self.tokenize(graphql_string) if !(graphql_string.encoding == Encoding::UTF_8 || graphql_string.ascii_only?) graphql_string = graphql_string.dup.force_encoding(Encoding::UTF_8) end if !graphql_string.valid_encoding? return [ [ :BAD_UNICODE_ESCAPE, 1, 1, graphql_string, nil, # prev token 241 # BAD_UNICODE_ESCAPE in lexer.rl ] ] end tokenize_with_c(graphql_string) end end class Parser def initialize(query_string, filename, trace) if query_string.nil? raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) end @query_string = query_string @filename = filename @tokens = nil @next_token_index = 0 @result = nil @trace = trace end def result if @result.nil? @tokens = @trace.lex(query_string: @query_string) do GraphQL::CParser::Lexer.tokenize(@query_string) end @trace.parse(query_string: @query_string) do c_parse @result end end @result end attr_reader :tokens, :next_token_index, :query_string, :filename end end def self.scan_with_c(graphql_string) GraphQL::CParser::Lexer.tokenize(graphql_string) end def self.parse_with_c(string, filename: nil, trace: GraphQL::Tracing::NullTrace) if string.nil? raise GraphQL::ParseError.new("No query string was present", nil, nil, string) end document = GraphQL::CParser.parse(string, filename: filename, trace: trace) if document.definitions.size == 0 raise GraphQL::ParseError.new("Unexpected end of document", 1, 1, string) end document end self.default_parser = GraphQL::CParser end graphql-ruby-2.2.17/graphql-c_parser/lib/graphql/c_parser/000077500000000000000000000000001476434635200234725ustar00rootroot00000000000000graphql-ruby-2.2.17/graphql-c_parser/lib/graphql/c_parser/version.rb000066400000000000000000000001371476434635200255050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module CParser VERSION = "1.0.8" end end graphql-ruby-2.2.17/graphql-ruby.png000066400000000000000000000103401476434635200173570ustar00rootroot00000000000000PNG  IHDRgOۢsBIT|d pHYsaa?itEXtSoftwarewww.inkscape.org<]IDATxkTՕ֣i@^VuKv!Hdia,A2fEdYc|A,i߈(N&jpdfT59Uz=hn=.tϚszw9)f6I2"&{R6ZkL~")J=MC*O͋Y) v)VކT(>ރXPX_4\yMwD˳qYh 脯PZtW/Z{ˡrl ܑG$zi Ҿm#cCt3/؛MkRlɟP۸3RlQik<&^@juJ; p&FK@ 0㿺dyC!fP+kf|]Ʒh#J16XYzQ6Xkl!ӬO2 ŋ4F]qII[5-z(vQ!$dx+ .Tm YsiAhiR7?wZ0@$'ކI YPiv$*,)IbC_L_?brGKĊ;%g]iil~ 1 Cb. /V]]ўWH%TN$"i 2֏HfƦ`SJ { ]Pg5Я\ič7M9OEO5D|KM̋)^ RASQpIJL{9^fsw&Q.Pd(PZg>IfyxυLi>-^!'6^E@J  z|op:×W?<iٲ3R_a5l%fؿu_˞VM,bPe]ibbi.Iq׮.Пfur?._pPDeoR#(69S9\wS_ݶ#[&m_)2~]>5u?4\| ߀oQ(6p$fy0^ZCCoRNg+ǹ@/õpf_hN:=^?.iĽ Wv Hy`SngߟKvn$h?ygx;^Εz7+߽?zfR^$CR:"G@zSܦac{ꊄ <_I(T,Ćj,e*Z_pi&hi/vw۪igtF$dҠs">;&Nw겅~xXŠ jCX;/leXnLLvU  /WN^bX&,诳v"ƌy1KA_LoX^pŽPLT~n 4 ^EJuB8cZ^i:,yhGN;Mвbx}/}.GlBJ ?DڜHΰ^0EDpUE,ٽ5/z6ۉ\FUK[ԓr+.FeJ/;> zfljA X݂Ph— wh}rZj~"*w7 Hm?O/ZO5i9/ z}+{Z4:CTBXS 0*\^ԚZpM9h I`s V˜c)*R(CzGEfyF!& `Qb@a[/K.L5MIq >D^Y f2Bϴ#̤v#Vl8+0%l̄BDpu? I++W] ݅|,R1PJգ|=-ٟWg}I13! G.3btEQ'1D%j&@ki,eYhK菠O*48J ^֘EԒHm;Q~kùH}ٴai- ӺŬxUc6HobPP[[x ӣWzS찕aVDac$Dy@BUcŪQgۧ dF۟VN@X]ԦBUeL(_Cʑ@t)@&/9mj}JѵDžM5b:n'_ 4FB9pM>eMWu$>5i=8̳T C4@tý׀^+ٝ<W^Lb, 1z~Z(K 6 BeMO% ٛzVh R0jd^#6Eo:~uÜf|C61*\ ,ştEC 4D/X%(QӰ.R|$fvttHV(p[*ZY7yӌoPdjK3R)V qUB 1:߸g5wK*A%SuBuwmކ{ ${k8%ow @>1dġ79Jx!?'nቮ"FDo()l^>語hCmU'bn&493鬫z|!}ij*U\1_>[Y7yqCgOe*\WT #xHr3vnGΝdr 2i|UE\_*1e7x1.7EB/V҇JU{-`%W4^Ttyż+[ԛ+M pA5޳6Se-߭e0( *F G Ö9E͘3m %$}`%NC=w [~䨥Ce*rz/^e(H^8mG9hah-;N[1#bb,qu^_ [HRF&2xN3+|%僤®4=Wz"MTa#u}%-T߰nP|)rVq`g)A9kV`j~pĺ;>A2 zʛhG#tLȁ &^IYYlv"3ʺCǺ#|S7 :\8f!(u_sqͱFB1WĸtCOqIHs#}ǚ8@AGƯT$;3fIB:?7hᘓ=[1#8 ]mu|< 1pלX__'*IuU.TʳP@%WՁK+APnb@gbЗbú.)*ܻãW4_,ĚvutWAL\U'c E ȊЀ[cb@?9iz^;:6D>ʜq@gx@,AAGnj/LP̋z]Ҩݕ)& } !rH#Ϣ(gMd@~!v/L $}}(ʳMe໵VN %["|C 1(1(qs2~ BK& Jr K@ѿ6P0(Фc.}@"̎-9͟K59ݜ3YؒgB6pk}0eQ>F:q}+ r4IENDB`graphql-ruby-2.2.17/graphql-ruby.svg000066400000000000000000000024041476434635200173740ustar00rootroot00000000000000 graphql-ruby-2.2.17/graphql.gemspec000066400000000000000000000035521476434635200172460ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.push File.expand_path("../lib", __FILE__) require "graphql/version" require "date" Gem::Specification.new do |s| s.name = "graphql" s.version = GraphQL::VERSION s.date = Date.today.to_s s.summary = "A GraphQL language and runtime for Ruby" s.description = "A plain-Ruby implementation of GraphQL." s.homepage = "https://github.com/rmosolgo/graphql-ruby" s.authors = ["Robert Mosolgo"] s.email = ["rdmosolgo@gmail.com"] s.license = "MIT" s.required_ruby_version = ">= 2.7.0" s.metadata = { "homepage_uri" => "https://graphql-ruby.org", "changelog_uri" => "https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md", "source_code_uri" => "https://github.com/rmosolgo/graphql-ruby", "bug_tracker_uri" => "https://github.com/rmosolgo/graphql-ruby/issues", "mailing_list_uri" => "https://buttondown.email/graphql-ruby", "rubygems_mfa_required" => "true", } s.files = Dir["{lib}/**/*", "MIT-LICENSE", "readme.md", ".yardopts"] s.add_runtime_dependency "base64" s.add_development_dependency "benchmark-ips" s.add_development_dependency "concurrent-ruby", "~>1.0" s.add_development_dependency "graphql-batch" s.add_development_dependency "memory_profiler" s.add_development_dependency "minitest" s.add_development_dependency "minitest-focus" s.add_development_dependency "minitest-reporters" s.add_development_dependency "rake" s.add_development_dependency 'rake-compiler' s.add_development_dependency "rubocop", "1.12" # for Ruby 2.4 enforcement # website stuff s.add_development_dependency "jekyll" s.add_development_dependency "yard" s.add_development_dependency "jekyll-algolia" s.add_development_dependency "jekyll-redirect-from" s.add_development_dependency "m", "~> 1.5.0" s.add_development_dependency "webrick" end graphql-ruby-2.2.17/guides/000077500000000000000000000000001476434635200155165ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/CNAME000066400000000000000000000000211476434635200162550ustar00rootroot00000000000000graphql-ruby.org graphql-ruby-2.2.17/guides/_config.yml000066400000000000000000000012331476434635200176440ustar00rootroot00000000000000title: GraphQL Ruby baseurl: "" url: "https://graphql-ruby.org" exclude: - .gitignore keep_files: ["api-doc", ".git"] # Build settings markdown: kramdown highlighter: rouge kramdown: auto_ids: true hard_wrap: false input: GFM defaults: - scope: path: "" values: layout: "default" fullwidth: true algolia: application_id: '8VO8708WUV' index_name: 'prod_graphql_ruby' settings: searchableAttributes: - section - title - headings - content customRanking: - desc(title) - desc(headings) - desc(content) plugins: - jekyll-algolia - jekyll-redirect-from graphql-ruby-2.2.17/guides/_layouts/000077500000000000000000000000001476434635200173555ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/_layouts/default.html000066400000000000000000000063541476434635200216770ustar00rootroot00000000000000 {% if page.section contains "GraphQL" %} {{ page.section }} - {{ page.title }} {% else %} GraphQL - {{ page.title }} {% endif %}
{{ content }}
graphql-ruby-2.2.17/guides/_layouts/doc_stub.html000066400000000000000000000002521476434635200220440ustar00rootroot00000000000000 {{ content }} graphql-ruby-2.2.17/guides/_layouts/guide.html000066400000000000000000000055141476434635200213450ustar00rootroot00000000000000--- layout: default --- {% if page.experimental %}

⚠ Experimental ⚠

This feature may get big changes in future releases. Check the changelog for update notes.

{% endif %} {% if page.pro %}

⚡️ Pro Feature ⚡️ This feature is bundled with GraphQL-Pro.

{% endif %} {% if page.enterprise %}

🌟 Enterprise Feature 🌟 This feature is bundled with GraphQL-Enterprise.

{% endif %}

{{ page.title }}

{% table_of_contents %}
{{ content }}
graphql-ruby-2.2.17/guides/_plugins/000077500000000000000000000000001476434635200173365ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/_plugins/api_doc.rb000066400000000000000000000157041476434635200212700ustar00rootroot00000000000000# frozen_string_literal: true require_relative "../../lib/graphql/version" require "kramdown" module GraphQLSite API_DOC_ROOT = "/api-doc/#{GraphQL::VERSION}/" module APIDoc def api_doc(input) if !input.start_with?("GraphQL") ruby_ident = "GraphQL::#{input}" else ruby_ident = input end doc_path = ruby_ident .gsub("::", "/") # namespaces .sub(/#(.+)$/, "#\\1-instance_method") # instance methods .sub(/\.(.+)$/, "#\\1-class_method") # class methods %|#{input}| end def link_to_img(img_path, img_title) full_img_path = "#{@context.registers[:site].baseurl}#{img_path}" <<-HTML #{img_title} HTML end end class APIDocRoot < Liquid::Tag def render(context) API_DOC_ROOT end end class CalloutBlock < Liquid::Block def initialize(tag_name, callout_class, tokens) super @callout_class = callout_class.strip end def render(context) raw_text = super site = context.registers[:site] converter = site.find_converter_instance(::Jekyll::Converters::Markdown) rendered_text = converter.convert(raw_text) heading = case @callout_class when "warning" "⚠ Heads up!" else raise ArgumentError, "Unhandled callout class: #{@callout_class.inspect}" end %|

#{heading}

#{rendered_text}
| end end class OpenAnIssue < Liquid::Tag def initialize(tag_name, issue_info, tokens) title, body = issue_info.split(",") # remove whitespace and quotes if value is present @title = strip_arg(title) @body = strip_arg(body) end def render(context) %|open an issue| end private def strip_arg(text) text && text.strip[1..-2] end end # Build a URL relative to `site.baseurl`, # asserting that the page exists. class InternalLink < Liquid::Tag GUIDES_ROOT = "guides/" def initialize(tag_name, guide_info, tokens) text, path = guide_info.split(",") # remove whitespace and quotes if value is present @text = strip_arg(text) @path = strip_arg(path) if @path && @path.start_with?("/") @path = @path[1..-1] end if !exist?(@path) raise "Internal link failed, couldn't find file for: #{path}" end end def render(context) <<-HTML.chomp #{@text} HTML end private def strip_arg(text) text && text.strip[1..-2] end POSSIBLE_EXTENSIONS = [".html", ".md"] def exist?(path) filepath = GUIDES_ROOT + path.split("#").first filepath = filepath.sub(".html", "") POSSIBLE_EXTENSIONS.any? { |ext| File.exist?(filepath + ext) } end end class TableOfContents < Liquid::Tag def render(context) headers = context["page"]["content"].scan(/^##+[^\n]+$/m) section_count = 0 current_table = header_table = [nil] prev_depth = nil headers.each do |h| header_hashes = h.match(/^#+/)[0] depth = header_hashes.size if depth == 2 section_count += 1 end text = h.gsub(/^#+ /, "") target = text.downcase .gsub(/[^a-z0-9_]+/, "-") .sub(/-$/, "") .sub(/^-/, "") rendered_text = Kramdown::Document.new(text, auto_ids: false) .to_html .sub("

", "") .sub("

", "") # remove wrapping added by kramdown if prev_depth if prev_depth > depth # outdent current_table = current_table[0] elsif prev_depth < depth # indent new_table = [current_table] current_table[-1][-1] = new_table current_table = new_table else # same depth end end current_table << [rendered_text, target, []] prev_depth = depth end table_html = "".dup render_table_into_html(table_html, header_table) html = <<~HTML

Contents

#{table_html}
HTML if section_count == 0 if headers.any? full_path = "guides/#{context["page"]["path"]}" warn("No sections identified for #{full_path} -- make sure it's using `## ...` for section headings.") end "" else html end end private def render_table_into_html(html_str, table) html_str << "
    " table.each_with_index do |entry, idx| if idx == 0 next # parent reference end rendered_text, target, child_table = *entry html_str << "
  1. " html_str << "#{rendered_text}" if child_table.any? render_table_into_html(html_str, child_table) end html_str << "
  2. " end html_str << "
" end end end Liquid::Template.register_filter(GraphQLSite::APIDoc) Liquid::Template.register_tag("api_doc_root", GraphQLSite::APIDocRoot) Liquid::Template.register_tag("open_an_issue", GraphQLSite::OpenAnIssue) Liquid::Template.register_tag("internal_link", GraphQLSite::InternalLink) Liquid::Template.register_tag("table_of_contents", GraphQLSite::TableOfContents) Liquid::Template.register_tag('callout', GraphQLSite::CalloutBlock) Jekyll::Hooks.register :site, :pre_render do |site| section_pages = Hash.new { |h, k| h[k] = [] } section_names = [] site.pages.each do |page| this_section = page.data["section"] if this_section this_section_pages = section_pages[this_section] this_section_pages << page this_section_pages.sort_by! { |page| page.data["index"] || 100 } page.data["section_pages"] = this_section_pages section_names << this_section end end section_names.compact! section_names.uniq! all_sections = [] section_names.each do |section_name| all_sections << { "name" => section_name, "overview_page" => section_pages[section_name].first, } end sorted_section_names = site.pages.find { |p| p.data["title"] == "Guides Index" }.data["sections"].map { |s| s["name"] } all_sections.sort_by! { |s| sorted_section_names.index(s["name"]) } site.data["all_sections"] = all_sections end module Jekyll module Algolia module Hooks def self.before_indexing_each(record, node, context) record = record.dup record.delete(:section_pages) record end end end end graphql-ruby-2.2.17/guides/_sass/000077500000000000000000000000001476434635200166265ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/_sass/reset.scss000066400000000000000000000021021476434635200206400ustar00rootroot00000000000000/* https://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } graphql-ruby-2.2.17/guides/_tasks/000077500000000000000000000000001476434635200170025ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/_tasks/site.rb000066400000000000000000000137371476434635200203060ustar00rootroot00000000000000# frozen_string_literal: true require "yard" require "webrick" namespace :apidocs do desc "Fetch a gem version from RubyGems, build the docs" task :gen_version, [:version] do |t, args| # GITHUB_REF comes from GitHub Actions version = args[:version] || ENV["GITHUB_REF"] || raise("A version is required") puts "Building docs for #{version}" # GitHub Actions gives the full tag name if version.start_with?("refs/tags/") version = version[10..-1] end if version.start_with?("v") version = version[1..-1] end Dir.mktmpdir do puts "Fetching graphql-#{version}" system("gem fetch graphql --version=#{version}") system("gem unpack graphql-#{version}.gem") system("rm graphql-#{version}.gem") Dir.chdir("graphql-#{version}") do # Copy it into gh-pages for publishing # and locally for previewing push_dest = File.expand_path("../gh-pages/api-doc/#{version}") local_dest = File.expand_path("../guides/_site/api-doc/#{version}") puts "Creating directories: #{push_dest.inspect}, #{local_dest.inspect}" FileUtils.mkdir_p(push_dest) FileUtils.mkdir_p(local_dest) system("yardoc") puts "Copying from #{Dir.pwd}/doc to #{push_dest}" copy_entry "doc", push_dest puts "Copying from #{Dir.pwd}/doc to #{local_dest}" copy_entry "doc", local_dest end end puts "Successfully generated docs for #{version}" end end namespace :site do desc "View the documentation site locally" task serve: [] do # if you need api docs, add `:build_doc` to the list of dependencies require "jekyll" options = { "source" => File.expand_path("guides"), "destination" => File.expand_path("guides/_site"), "watch" => true, "serving" => true } # Generate the site in server mode. puts "Running Jekyll..." Jekyll::Commands::Build.process(options) Jekyll::Commands::Serve.process(options) end desc "Get the gh-pages branch locally, make sure it's up-to-date" task :fetch_latest do # Ensure the gh-pages dir exists so we can generate into it. puts "Checking for gh-pages dir..." unless File.exist?("./gh-pages") puts "Creating gh-pages dir..." sh "git clone git@github.com:rmosolgo/graphql-ruby gh-pages" end # Ensure latest gh-pages branch history. Dir.chdir("gh-pages") do sh "git checkout gh-pages" sh "git pull origin gh-pages" end end desc "Remove all generated HTML (making space to re-generate)" task :clean_html do # Proceed to purge all files in case we removed a file in this release. puts "Cleaning gh-pages directory..." purge_exclude = [ 'gh-pages/.', 'gh-pages/..', 'gh-pages/.git', 'gh-pages/.gitignore', 'gh-pages/api-doc', ] FileList["gh-pages/{*,.*}"].exclude(*purge_exclude).each do |path| sh "rm -rf #{path}" end end desc "Build guides/ into gh-pages/ with Jekyll" task :build_html do # Copy site to gh-pages dir. puts "Building site into gh-pages branch..." ENV['JEKYLL_ENV'] = 'production' require "jekyll" Jekyll::Commands::Build.process({ "source" => File.expand_path("guides"), "destination" => File.expand_path("gh-pages"), "sass" => { "style" => "compressed" } }) File.write('gh-pages/.nojekyll', "Prevent GitHub from running Jekyll") end desc "Commit new docs" task :commit_changes do puts "Committing and pushing to GitHub Pages..." sha = `git rev-parse HEAD`.strip Dir.chdir('gh-pages') do system "git status" system "git add ." system "git status" system "git commit --allow-empty -m 'Updating to #{sha}.'" end end desc "Push docs to gh-pages branch" task :push_commit do Dir.chdir('gh-pages') do sh "git push origin gh-pages" end end desc "Commit the local site to the gh-pages branch and publish to GitHub Pages" task publish: [:build_doc, :update_search_index, :fetch_latest, :clean_html, :build_html, :commit_changes, :push_commit] YARD::Rake::YardocTask.new(:prepare_yardoc) task build_doc: :prepare_yardoc do require_relative "../../lib/graphql/version" def to_rubydoc_url(path) "/api-doc/#{GraphQL::VERSION}/" + path .gsub("::", "/") # namespaces .sub(/#(.+)$/, "#\\1-instance_method") # instance methods .sub(/\.(.+)$/, "#\\1-class_method") # class methods end DOC_TEMPLATE = <<-PAGE --- layout: doc_stub search: true title: %{title} url: %{url} rubydoc_url: %{url} doc_stub: true --- %{documentation} PAGE puts "Preparing YARD docs @ v#{GraphQL::VERSION} for search index..." registry = YARD::Registry.load!(".yardoc") files_target = "guides/yardoc" FileUtils.rm_rf(files_target) FileUtils.mkdir_p(files_target) # Get docs for all classes and modules docs = registry.all(:class, :module) docs.each do |code_object| begin # Skip private classes and modules if code_object.visibility == :private next end rubydoc_url = to_rubydoc_url(code_object.path) page_content = DOC_TEMPLATE % { title: code_object.path, url: rubydoc_url, documentation: code_object.format.gsub(/-{2,}/, " ").gsub(/^\s+/, ""), } filename = code_object.path.gsub(/\W+/, "_") filepath = "guides/yardoc/#{filename}.md" File.write(filepath, page_content) rescue StandardError => err puts "Error on: #{code_object.path}" puts err puts err.backtrace end end puts "Wrote #{docs.size} YARD docs to #{files_target}." end desc "Update the Algolia search index used for graphql-ruby.org" task :update_search_index do if !ENV["ALGOLIA_API_KEY"] warn("Can't update search index without ALGOLIA_API_KEY; Search will be out-of-date.") else system("bundle exec jekyll algolia push --source=./guides") end end end graphql-ruby-2.2.17/guides/authorization/000077500000000000000000000000001476434635200204165ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/authorization/authorization.md000066400000000000000000000113031476434635200236360ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Authorization desc: During execution, check if the current user has permission to access retrieved objects. index: 3 --- While a query is running, you can check each object to see whether the current user is authorized to interact with that object. If the user is _not_ authorized, you can handle the case with an error. ## Adding Authorization Checks Schema members have `authorized?` methods which will be called during execution: - Type classes have `.authorized?(object, context)` class methods - Fields have `#authorized?(object, args, context)` instance methods - Arguments have `#authorized?(object, arg_value, context)` instance methods - Mutations and Resolvers have `.authorized?(object, context)` class methods and `#authorized?(args)` instance methods These methods are called with: - `object`: the object from your application which was returned from a field - `args`/`arg_value`: The arguments for a field, or the value of an argument - `context`: the query context, based on the hash passed as `context:` #### Object Authorization When you implement this method to return `false`, the query will be halted, for example: ```ruby class Types::Friendship < Types::BaseObject # You can only see the details on a `Friendship` # if you're one of the people involved in it. def self.authorized?(object, context) super && (object.to_friend == context[:viewer] || object.from_friend == context[:viewer]) end end ``` (Always call `super` to get the default checks, too.) Now, whenever an object of type `Friendship` is going to be returned to the client, it will first go through the `.authorized?` method. If that method returns false, the field will get `nil` instead of the original object, and you may handle that case with an error (see below). #### Field Authorization Field `#authorized?` methods are called before resolving a field, for example: ```ruby class Types::BaseField < GraphQL::Schema::Field # Pass `field ..., require_admin: true` to reject non-admin users from a given field def initialize(*args, require_admin: false, **kwargs, &block) @require_admin = require_admin super(*args, **kwargs, &block) end def authorized?(obj, args, ctx) # if `require_admin:` was given, then require the current user to be an admin super && (@require_admin ? ctx[:viewer]&.admin? : true) end end ``` For this to work, the base field class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-fields" %}. #### Argument Authorization Argument `#authorized?` hooks are called before resolving the field that the argument belongs to. For example: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument def initialize(*args, require_logged_in: false, **kwargs, &block) @require_logged_in = require_logged_in super(*args, **kwargs, &block) end def authorized?(obj, arg_value, ctx) super && if @require_logged_in ctx[:viewer].present? else true end end end ``` For this to work, the base argument class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-arguments" %}. ## Mutation Authorization See mutations/mutation_authorization.html#can-this-user-perform-this-action {% internal_link "Mutation Authorization", "/mutations/mutation_authorization.html#can-this-user-perform-this-action" %}) in the Mutation Guides. ## Handling Unauthorized Objects By default, GraphQL-Ruby silently replaces unauthorized objects with `nil`, as if they didn't exist. You can customize this behavior by implementing {{ "Schema.unauthorized_object" | api_doc }} in your schema class, for example: ```ruby class MySchema < GraphQL::Schema # Override this hook to handle cases when `authorized?` returns false for an object: def self.unauthorized_object(error) # Add a top-level error to the response instead of returning nil: raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions" end end ``` Now, the custom hook will be called instead of the default one. If `.unauthorized_object` returns a non-`nil` object (and doesn't `raise` an error), then that object will be used in place of the unauthorized object. A similar hook is available for unauthorized fields: ```ruby class MySchema < GraphQL::Schema # Override this hook to handle cases when `authorized?` returns false for a field: def self.unauthorized_field(error) # Add a top-level error to the response instead of returning nil: raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions" end end ``` graphql-ruby-2.2.17/guides/authorization/can_can_integration.md000066400000000000000000000301261476434635200247270ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: CanCan Integration desc: Hook up GraphQL to CanCan abilities index: 4 pro: true --- [GraphQL::Pro](https://graphql.pro) includes an integration for powering GraphQL authorization with [CanCan](https://github.com/CanCanCommunity/cancancan). __Why bother?__ You _could_ put your authorization code in your GraphQL types themselves, but writing a separate authorization layer gives you a few advantages: - Since the authorization code isn't embedded in GraphQL, you can use the same logic in non-GraphQL (or legacy) parts of the app. - The authorization logic can be tested in isolation, so your end-to-end GraphQL tests don't have to cover as many possibilities. ## Getting Started __NOTE__: Requires the latest gems, so make sure your `Gemfile` has: ```ruby # For CanCanIntegration: gem "graphql-pro", ">=1.7.11" # For list scoping: gem "graphql", ">=1.8.7" ``` Then, `bundle install`. Whenever you run queries, include `:current_user` in the context: ```ruby context = { current_user: current_user, # ... } MySchema.execute(..., context: context) ``` ## Authorizing Objects For each object type, you can assign a required action for Ruby objects of that type. To get started, include the `ObjectIntegration` in your base object class: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object # Add the CanCan integration: include GraphQL::Pro::CanCanIntegration::ObjectIntegration # By default, require `can :read, ...` can_can_action(:read) # Or, to require no permissions by default: # can_can_action(nil) end ``` Now, anyone fetching an object will need `can :read, ...` for that object. CanCan configurations are inherited, and can be overridden in subclasses. For example, to allow _all_ viewers to see the `Query` root type: ```ruby class Types::Query < Types::BaseObject # Allow anyone to see the query root can_can_action nil end ``` ### Bypassing CanCan `can_can_action(nil)` will override any inherited configuration and skip CanCan checks for an object, field, argument or mutation. ### Handling Unauthorized Objects When any CanCan check returns `false`, the unauthorized object is passed to {{ "Schema.unauthorized_object" | api_doc }}, as described in {% internal_link "Handling unauthorized objects", "/authorization/authorization#handling-unauthorized-objects" %}. ## Scopes #### ActiveRecord::Relation The CanCan integration adds [CanCan's `.accessible_by`](https://github.com/cancancommunity/cancancan/wiki/Fetching-Records) to GraphQL-Ruby's {% internal_link "list scoping", "/authorization/scoping" %} To scope lists of interface or union type, include the integration in your base union class and base interface module _and_ set a base `can_can_action`, if desired: ```ruby class BaseUnion < GraphQL::Schema::Union include GraphQL::Pro::CanCanIntegration::UnionIntegration # To provide a default action for scoping lists: can_can_action :read end module BaseInterface include GraphQL::Schema::Interface include GraphQL::Pro::CanCanIntegration::InterfaceIntegration # To provide a default action for scoping lists: can_can_action :read end ``` #### Array For Arrays, the CanCan integration will use `.select { ... }` to filter items using the `can_can_action` from the lists's type. #### Bypassing scopes To allow an unscoped relation to be returned from a field, disable scoping with `scope: false`, for example: ```ruby # Allow anyone to browse the job postings field :job_postings, [Types::JobPosting], null: false, scope: false ``` ## Authorizing Fields You can also require certain checks on a field-by-field basis. First, include the integration in your base field class: ```ruby # app/graphql/types/base_field.rb class Types::BaseField < GraphQL::Schema::Field # Add the CanCan integration: include GraphQL::Pro::CanCanIntegration::FieldIntegration # By default, don't require a role at field-level: can_can_action nil end ``` If you haven't already done so, you should also hook up your base field class to your base object and base interface: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object field_class Types::BaseField end # app/graphql/types/base_interface.rb module Types::BaseInterface # ... field_class Types::BaseField end # app/graphql/mutations/base_mutation.rb class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class Types::BaseField end ``` Then, you can add `can_can_action:` options to your fields: ```ruby class Types::JobPosting < Types::BaseObject # Only allow `can :review_applications, JobPosting` users # to see who has applied field :applicants, [Types::User], can_can_action: :review_applicants end ``` It will require the named action (`:review_applicants`) for the object being viewed (a `JobPosting`). ### Authorizing by attribute CanCan 3.0 added attribute-level authorization ([pull request](https://github.com/CanCanCommunity/cancancan/pull/474)). You can leverage this in your field definitions with the `can_can_attribute:` configuration: ```ruby # This will call `.can?(:read, user, :email_address)` field :email_address, String, can_can_action: :read, can_can_attribute: :email_address ``` You could also provide a _default value_ for `can_can_attribute` in your base field class: ```ruby class Types::BaseField def initialize(*args, **kwargs, &block) # pass all configs to the super class: super # Then set a new `can_can_attribute` value, if applicable if can_can_attribute.nil? && can_can_action.present? # `method_sym` is the thing GraphQL-Ruby will use to resolve this field can_can_attribute(method_sym) end end end ``` (See {{ "GraphQL::Schema::Field" | api_doc }} for the different values available for defaults.) ### Providing a Custom CanCan Subject Authorization checks are _skipped_ whenever the underlying `object` is `nil`. This can happen in root query fields, for example, when no `root_value: ...` is given. To provide a `can_can_subject` in this case, you can add it as a field configuration: ```ruby field :users, Types::User.connection_type, null: false, can_can_action: :manage, # `:all` will be used instead of `object` (which is `nil`) can_can_subject: :all ``` The configuration above will call `can?(:manage, :all)` whenever that field is requested. ## Authorizing Arguments Similar to field-level checks, you can require certain permissions to _use_ certain arguments. To do this, add the integration to your base argument class: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # Include the integration and default to no permissions required include GraphQL::Pro::CanCanIntegration::ArgumentIntegration can_can_action nil end ``` Then, make sure your base argument is hooked up to your base field and base input object: ```ruby class Types::BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument # PS: see "Authorizing Fields" to make sure your base field is hooked up to objects, interfaces and mutations end class Types::BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument end ``` Now, arguments accept a `can_can_action:` option, for example: ```ruby class Types::Company < Types::BaseObject field :employees, Types::Employee.connection_type do # Only admins can filter employees by email: argument :email, String, required: false, can_can_action: :admin end end ``` This will check for `can :admin, Company` (or a similar rule for the `company` being queried) for the current user. ## Authorizing Mutations There are a few ways to authorize GraphQL mutations with the CanCan integration: - Add a [mutation-level roles](#mutation-level-roles) - Run checks on [objects loaded by ID](#authorizing-loaded-objects) Also, you can configure [unauthorized object handling](#unauthorized-mutations) #### Setup Add `MutationIntegration` to your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation include GraphQL::Pro::CanCanIntegration::MutationIntegration # Also, to use argument-level authorization: argument_class Types::BaseArgument end ``` Also, you'll probably want a `BaseMutationPayload` where you can set a default role: ```ruby class Types::BaseMutationPayload < Types::BaseObject # If `BaseObject` requires some permissions, override that for mutation results. # Assume that anyone who can run a mutation can read their generated result types. can_can_action nil end ``` And hook it up to your base mutation: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation object_class Types::BaseMutationPayload field_class Types::BaseField end ``` #### Mutation-level roles Each mutation can have a class-level `can_can_action` which will be checked before loading objects or resolving, for example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation can_can_action :run_mutation end ``` In the example above, `can :run_mutation, Mutations::PromoteEmployee` will be checked before running the mutation. (The currently-running instance of `Mutations::PromoteEmployee` is passed to the ability checker.) #### Authorizing Loaded Objects Mutations can automatically load and authorize objects by ID using the `loads:` option. Beyond the normal [object reading permissions](#authorizing-objects), you can add an additional role for the specific mutation input using a `can_can_action:` option: ```ruby class Mutations::FireEmployee < Mutations::BaseMutation argument :employee_id, ID, loads: Types::Employee, can_can_action: :supervise, end ``` In the case above, the mutation will halt unless the `can :supervise, ...` check returns true. (The fetched instance of `Employee` is passed to the ability checker.) #### Unauthorized Mutations By default, an authorization failure in a mutation will raise a Ruby exception. You can customize this by implementing `#unauthorized_by_can_can(owner, value)` in your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation def unauthorized_by_can_can(owner, value) # No error, just return nil: nil end end ``` The method is called with: - `owner`: the `GraphQL::Schema::Argument` instance or mutation class whose role was not satisfied - `value`: the object which didn't pass for `context[:current_user]` Since it's a mutation method, you can also access `context` in that method. Whatever that method returns will be treated as an early return value for the mutation, so for example, you could return {% internal_link "errors as data", "/mutations/mutation_errors" %}: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field :errors, [String] def unauthorized_by_can_can(owner, value) # Return errors as data: { errors: ["Missing required permission: #{owner.can_can_action}, can't access #{value.inspect}"] } end end ``` ## Authorizing Resolvers Resolvers are authorized just like [mutations](#authorizing-mutations), and require similar setup: ```ruby # app/graphql/resolvers/base_resolver.rb class Resolvers::BaseResolver < GraphQL::Schema::Resolver include GraphQL::Pro::CanCanIntegration::ResolverIntegration argument_class BaseArgument # can_can_action(nil) # to disable authorization by default end ``` Beyond that, see [Authorizing Mutations](#authorizing-mutations) above for further details. ## Custom Abilities Class By default, the integration will look for a top-level `::Ability` class. If you're using a different class, provide an instance ahead-of-time as `context[:can_can_ability]` For example, you could _always_ add one in your schema's `#execute` method: ```ruby class MySchema < GraphQL::Schema # Override `execute` to provide a custom Abilities instance for the CanCan integration def self.execute(*args, context: {}, **kwargs) # Assign `context[:can_can_ability]` to an instance of our custom class context[:can_can_ability] = MyAuthorization::CustomAbilitiesClass.new(context[:current_user]) super end end ``` graphql-ruby-2.2.17/guides/authorization/overview.md000066400000000000000000000136451476434635200226170ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Overview desc: Overview of GraphQL authorization in general and an intro to the built-in framework. index: 0 --- Here's a conceptual approach to GraphQL authorization, followed by an introduction to the built-in authorization framework. Each part of the framework is described in detail in its own guide. ## Authorization: GraphQL vs REST In a REST API, the common authorization pattern is fairly simple. Before performing the requested action, the server asserts that the current client has the required permissions for that action. For example: ```ruby class PostsController < ApiController def create # First, check the client's permission level: if current_user.can?(:create_posts) # If the user is permitted, then perform the action: post = Post.create(params) render json: post else # Otherwise, return an error: render nothing: true, status: 403 end end end ``` However, this request-by-request mindset doesn't map well to GraphQL because there's only one controller and the requests that come to it may be _very_ different. To illustrate the problem: ```ruby class GraphqlController < ApplicationController def execute # What permission is required for `query_str`? # It depends on the string! So, you can't generalize at this level. if current_user.can?(:"???") MySchema.execute(query_str, context: ctx, variables: variables) end end end ``` So, what new mindset will work with a GraphQL API? For __mutations__, remember that each mutation is like an API request in itself. For example, `Posts#create` above would map to the `createPost(...)` mutation in GraphQL. So, each mutation should be authorized in its own right. For __queries__, you can think of each individual _object_ like a `GET` request to a REST API. So, each object should be authorized for reading in its own right. By applying this mindset, each part of the GraphQL query will be properly authorized before it is executed. Also, since the different units of code are each authorized on their own, you can be sure that each incoming query will be properly authorized, even if it's a brand new query that the server has never seen before. ## What About Authentication? As a reminder: - _Authentication_ is the process of determining what user is making the current request, for example, accepting a username and password, or finding a `User` in the database from `session[:current_user_id]`. - _Authorization_ is the process of verifying that the current user has permission to do something (or see something), for example, checking `admin?` status or looking up permission groups from the database. In general, authentication is _not_ addressed in GraphQL at all. Instead, your controller should get the current user based on the HTTP request (eg, an HTTP header or a cookie) and provide that information to the GraphQL query. For example: ```ruby class GraphqlController < ApplicationController def execute # Somehow get the current `User` from this HTTP request. current_user = get_logged_in_user(request) # Provide the current user in `context` for use during the query context = { current_user: current_user } MySchema.execute(query_str, context: context, ...) end end ``` After your HTTP handler has loaded the current user, you can access it via `context[:current_user]` in your GraphQL code. ## Authorization in Your Business Logic Before introducing GraphQL-specific authorization, consider the advantages of application-level authorization. (See the [GraphQL.org post](https://graphql.org/learn/authorization/) on the same topic.) For example, here's authorization mixed into the GraphQL API layer: ```ruby field :posts, [Types::Post], null: false def posts # Perform an auth check in the GraphQL field code: if context[:current_user].admin? Post.all else Post.published end end ``` The downside of this is that, when `Types::Post` is queried in other contexts, the same authorization check may not be applied. Additionally, since the authorization code is coupled with the GraphQL API, the only way to test it is via GraphQL queries, which adds some complexity to tests. Alternatively, you could move the authorization to your business logic, the `Post` class: ```ruby class Post < ActiveRecord::Base # Return the list of posts which `user` may see def self.posts_for(user) if user.admin? self.all else self.published end end end ``` Then, use this application method in your GraphQL code: ```ruby field :posts, [Types::Post], null: false def posts # Fetch the posts this user can see: Post.posts_for(context[:current_user]) end ``` In this case, `Post.posts_for(user)` could be tested _independently_ from GraphQL. Then, you have less to worry about in your GraphQL tests. As a bonus, you can use `Post.posts_for(user)` in _other_ parts of the app, too, such as the web UI or REST API. ## GraphQL-Ruby's Authorization Framework Despite the advantages of authorization at the application layer, as described above, there might be some reasons to authorize in the API layer: - Have an extra assurance that your API layer is secure - Authorize the API request _before_ running it (see "visibility" below) - Integrate with code that doesn't have authorization built-in To accomplish these, you can use GraphQL-Ruby's authorization framework. The framework has three levels, each of which is described in its own guide: - {% internal_link "Visibility", "/authorization/visibility" %} hides parts of the GraphQL schema from users who don't have full permission. - {% internal_link "Authorization", "/authorization/authorization" %} checks application objects during execution to be sure the user has permission to access them. Also, [GraphQL::Pro](https://graphql.pro) has integrations for {% internal_link "CanCan", "/authorization/can_can_integration" %} and {% internal_link "Pundit", "/authorization/pundit_integration" %}. graphql-ruby-2.2.17/guides/authorization/pundit_integration.md000066400000000000000000000370541476434635200246570ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Pundit Integration desc: Hook up GraphQL to Pundit policies index: 4 pro: true --- [GraphQL::Pro](https://graphql.pro) includes an integration for powering GraphQL authorization with [Pundit](https://github.com/varvet/pundit) policies. __Why bother?__ You _could_ put your authorization code in your GraphQL types themselves, but writing a separate authorization layer gives you a few advantages: - Since the authorization code isn't embedded in GraphQL, you can use the same logic in non-GraphQL (or legacy) parts of the app. - The authorization logic can be tested in isolation, so your end-to-end GraphQL tests don't have to cover as many possibilities. ## Getting Started __NOTE__: Requires the latest gems, so make sure your `Gemfile` has: ```ruby # For PunditIntegration: gem "graphql-pro", ">=1.7.9" # For list scoping: gem "graphql", ">=1.8.7" ``` Then, `bundle install`. Whenever you run queries, include `:current_user` in the context: ```ruby context = { current_user: current_user, # ... } MySchema.execute(..., context: context) ``` ## Authorizing Objects You can specify Pundit roles that must be satisfied in order for viewers to see objects of a certain type. To get started, include the `ObjectIntegration` in your base object class: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object # Add the Pundit integration: include GraphQL::Pro::PunditIntegration::ObjectIntegration # By default, require staff: pundit_role :staff # Or, to require no permissions by default: # pundit_role nil end ``` Now, anyone trying to read a GraphQL object will have to pass the `#staff?` check on that object's policy. Then, each child class can override that parent configuration. For example, allow _all_ viewers to read the `Query` root: ```ruby class Types::Query < Types::BaseObject # Allow anyone to see the query root pundit_role nil end ``` #### Policies and Methods For each object returned by GraphQL, the integration matches it to a policy and method. The policy is found using [`Pundit.policy!`](https://www.rubydoc.info/gems/pundit/Pundit%2Epolicy!), which looks up a policy using the object's class name. (This can be customized, see below.) Then, GraphQL will call a method on the policy to see whether the object is permitted or not. This method is assigned in the object class, for example: ```ruby class Types::Employee < Types::BaseObject # Only show employee objects to their bosses, # or when that employee is the current viewer pundit_role :employer_or_self # ... end ``` That configuration will call `#employer_or_self?` on the corresponding Pundit policy. #### Custom Policy Class By default, the integration uses `Pundit.policy!(current_user, object)` to find a policy. You can specify a policy class using `pundit_policy_class(...)`: ```ruby class Types::Employee < Types::BaseObject pundit_policy_class(Policies::CustomEmployeePolicy) # Or, you could use a string: # pundit_policy_class("Policies::CustomEmployeePolicy") end ``` For really custom policy lookup, see [Custom Policy Lookup](#custom-policy-lookup) below. #### Bypassing Policies The integration requires that every object with a `pundit_role` has a corresponding policy class. To allow objects to _skip_ authorization, you can pass `nil` as the role: ```ruby class Types::PublicProfile < Types::BaseObject # Anyone can see this pundit_role nil end ``` #### Handling Unauthorized Objects When any Policy method returns `false`, the unauthorized object is passed to {{ "Schema.unauthorized_object" | api_doc }}, as described in {% internal_link "Handling unauthorized objects", "/authorization/authorization#handling-unauthorized-objects" %}. ## Scopes The Pundit integration adds [Pundit scopes](https://github.com/varvet/pundit#scopes) to GraphQL-Ruby's {% internal_link "list scoping", "/authorization/scoping" %} feature. Any list or connection will be scoped. If a scope is missing, the query will crash rather than risk leaking unfiltered data. To scope lists of interface or union type, include the integration in your base union class and base interface module: ```ruby class BaseUnion < GraphQL::Schema::Union include GraphQL::Pro::PunditIntegration::UnionIntegration end module BaseInterface include GraphQL::Schema::Interface include GraphQL::Pro::PunditIntegration::InterfaceIntegration end ``` #### Bypassing scopes To allow an unscoped relation to be returned from a field, disable scoping with `scope: false`, for example: ```ruby # Allow anyone to browse the job postings field :job_postings, [Types::JobPosting], null: false, scope: false ``` ## Authorizing Fields You can also require certain checks on a field-by-field basis. First, include the integration in your base field class: ```ruby # app/graphql/types/base_field.rb class Types::BaseField < GraphQL::Schema::Field # Add the Pundit integration: include GraphQL::Pro::PunditIntegration::FieldIntegration # By default, don't require a role at field-level: pundit_role nil end ``` If you haven't already done so, you should also hook up your base field class to your base object and base interface: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object field_class Types::BaseField end # app/graphql/types/base_interface.rb module Types::BaseInterface # ... field_class Types::BaseField end # app/graphql/mutations/base_mutation.rb class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class Types::BaseField end ``` Then, you can add `pundit_role:` options to your fields: ```ruby class Types::JobPosting < Types::BaseObject # Allow signed-in users to browse listings pundit_role :signed_in # But, only allow `JobPostingPolicy#staff?` users to see # who has applied field :applicants, [Types::User], pundit_role: :staff end ``` It will call the named role (eg, `#staff?`) on the parent object's policy (eg `JobPostingPolicy`). #### Custom Policy Class You can override the policy class for a field using `pundit_policy_class:`, for example: ```ruby class Types::JobPosting < Types::BaseObject # Only allow `ApplicantsPolicy#staff?` users to see # who has applied field :applicants, [Types::User], pundit_role: :staff, pundit_policy_class: ApplicantsPolicy # Or with a string: # pundit_policy_class: "ApplicantsPolicy" end ``` This will initialize an `ApplicantsPolicy` with the parent object (a `Job`) and call `#staff?` on it. For really custom policy lookup, see [Custom Policy Lookup](#custom-policy-lookup) below. ## Authorizing Arguments Similar to field-level checks, you can require certain permissions to _use_ certain arguments. To do this, add the integration to your base argument class: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # Include the integration and default to no permissions required include GraphQL::Pro::PunditIntegration::ArgumentIntegration pundit_role nil end ``` Then, make sure your base argument is hooked up to your base field and base input object: ```ruby class Types::BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument # PS: see "Authorizing Fields" to make sure your base field is hooked up to objects, interfaces and mutations end class Types::BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument end ``` Now, arguments accept a `pundit_role:` option, for example: ```ruby class Types::Company < Types::BaseObject field :employees, Types::Employee.connection_type do # Only admins can filter employees by email: argument :email, String, required: false, pundit_role: :admin end end ``` The role will be called on the parent object's policy, for example `CompanyPolicy#admin?` in the case above. ## Authorizing Mutations There are a few ways to authorize GraphQL mutations with the Pundit integration: - Add a [mutation-level roles](#mutation-level-roles) - Run checks on [objects loaded by ID](#authorizing-loaded-objects) Also, you can configure [unauthorized object handling](#unauthorized-mutations) #### Setup Add `MutationIntegration` to your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation include GraphQL::Pro::PunditIntegration::MutationIntegration # Also, to use argument-level authorization: argument_class Types::BaseArgument end ``` Also, you'll probably want a `BaseMutationPayload` where you can set a default role: ```ruby class Types::BaseMutationPayload < Types::BaseObject # If `BaseObject` requires some permissions, override that for mutation results. # Assume that anyone who can run a mutation can read their generated result types. pundit_role nil end ``` And hook it up to your base mutation: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation object_class Types::BaseMutationPayload field_class Types::BaseField end ``` #### Mutation-level roles Each mutation can have a class-level `pundit_role` which will be checked before loading objects or resolving, for example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation pundit_role :admin end ``` In the example above, `PromoteEmployeePolicy#admin?` will be checked before running the mutation. #### Custom Policy Class By default, Pundit uses the mutation's class name to look up a policy. You can override this by defining `pundit_policy_class` on your mutation: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation pundit_policy_class ::UserPolicy pundit_role :admin end ``` Now, the mutation will check `UserPolicy#admin?` before running. For really custom policy lookup, see [Custom Policy Lookup](#custom-policy-lookup) below. #### Authorizing Loaded Objects Mutations can automatically load and authorize objects by ID using the `loads:` option. Beyond the normal [object reading permissions](#authorizing-objects), you can add an additional role for the specific mutation input using a `pundit_role:` option: ```ruby class Mutations::FireEmployee < Mutations::BaseMutation argument :employee_id, ID, loads: Types::Employee, pundit_role: :supervisor, end ``` In the case above, the mutation will halt unless the `EmployeePolicy#supervisor?` method returns true. #### Unauthorized Mutations By default, an authorization failure in a mutation will raise a Ruby exception. You can customize this by implementing `#unauthorized_by_pundit(owner, value)` in your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation def unauthorized_by_pundit(owner, value) # No error, just return nil: nil end end ``` The method is called with: - `owner`: the `GraphQL::Schema::Argument` or mutation class whose role was not satisfied - `value`: the object which didn't pass for `context[:current_user]` Since it's a mutation method, you can also access `context` in that method. Whatever that method returns will be treated as an early return value for the mutation, so for example, you could return {% internal_link "errors as data", "/mutations/mutation_errors" %}: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field :errors, [String] def unauthorized_by_pundit(owner, value) # Return errors as data: { errors: ["Missing required permission: #{owner.pundit_role}, can't access #{value.inspect}"] } end end ``` ## Authorizing Resolvers Resolvers are authorized just like [mutations](#authorizing-mutations), and require similar setup: ```ruby # app/graphql/resolvers/base_resolver.rb class Resolvers::BaseResolver < GraphQL::Schema::Resolver include GraphQL::Pro::PunditIntegration::ResolverIntegration argument_class BaseArgument # pundit_role nil # to disable authorization by default end ``` Beyond that, see [Authorizing Mutations](#authorizing-mutations) above for further details. ## Custom Policy Lookup By default, the integration uses `Pundit`'s top-level methods to interact with policies: - `Pundit.policy!(context[:current_user], object)` is called to find a policy instance - `Pundit.policy_scope!(context[:current_user], items)` is called to filter `items` ### Custom Policy Methods You can implement a custom lookup by defining the following methods in your schema: - `pundit_policy_class_for(object, context)` to return a policy class (or raise an error if one isn't found) - `pundit_role_for(object, context)` to return a role method (Symbol), or `nil` to bypass authorization - `scope_by_pundit_policy(context, items)` to apply a scope to `items` (or raise an error if one isn't found) Since different objects have different lifecycles, the hooks are installed slightly different ways: - Your base argument, field, and mutation classes should have _instance methods_ with those names - Your base type classes should have _class methods_ with that name Here's an example of how the custom hooks can be installed: ```ruby module CustomPolicyLookup # Lookup policies in the `SystemAdmin::` namespace for system_admin users # @return [Class] def pundit_policy_class_for(object, context) current_user = context[:current_user] if current_user.system_admin? SystemAdmin.const_get("#{object.class.name}Policy") else super end end # Require admin permissions if the object is pending_approval def pundit_role_for(object, context) if object.pending_approval? :admin else super # fall back to the normally-configured role end end end # Add policy hooks as class methods class Types::BaseObject < GraphQL::Schema::Object extend CustomPolicyLookup end class Types::BaseUnion < GraphQL::Schema::Union extend CustomPolicyLookup end module Types::BaseInterface include GraphQL::Schema::Interface # Add this as a class method that will be "inherited" by other interfaces: definition_methods do include CustomPolicyLookup end end # Add policy hooks as instance methods class Types::BaseField < GraphQL::Schema::Field include CustomPolicyLookup end class Types::BaseArgument < GraphQL::Schema::Argument include CustomPolicyLookup end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation include CustomPolicyLookup end ``` ### One Policy Per Class Another good approach is to have one policy per class. You can implement `policy_class_for(object, context)` to look up a policy _within_ the class, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation def policy_class_for(_object, _context) # Look up a nested `Policy` constant: self.class.const_get(:Policy) end end ``` Then, each mutation can define its policy inline, for example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation # This will be found by `BaseMutation.policy_class`, defined above: class Policy # ... end pundit_role :admin end ``` Now, `Mutations::PromoteEmployee::Policy#admin?` will be checked before running the mutation. ## Custom User Lookup By default, the Pundit integration looks for the current user in `context[:current_user]`. You can override this by implementing `#pundit_user` on your custom query context class. For example: ```ruby # app/graphql/query_context.rb class QueryContext < GraphQL::Query::Context def pundit_user # Lookup `context[:viewer]` instead: self[:viewer] end end ``` Then be sure to hook up your custom class in the schema: ```ruby class MySchema < GraphQL::Schema context_class(QueryContext) end ``` Then, the Pundit integration will use your `def pundit_user` to get the current user at runtime. graphql-ruby-2.2.17/guides/authorization/scoping.md000066400000000000000000000042651476434635200224110ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Scoping desc: Filter lists to match the current viewer and context index: 4 --- _Scoping_ is a complementary consideration to authorization. Rather than checking "can this user see this thing?", scoping takes a list of items filters it to the subset which is appropriate for the current viewer and context. For similar features, see [Pundit scopes](https://github.com/varvet/pundit#scopes) and [Cancan's `.accessible_by`](https://github.com/cancancommunity/cancancan/wiki/Fetching-Records). ## `scope:` option Fields accept a `scope:` option to enable (or disable) scoping, for example: ```ruby field :products, [Types::Product], scope: true # Or field :all_products, [Types::Product], scope: false ``` For __list__ and __connection__ fields, `scope: true` is the default. For all other fields, `scope: false` is the default. You can override this by using the `scope:` option. ## `.scope_items(items, ctx)` method Type classes may implement `.scope_items(items, ctx)`. This method is called when a field has `scope: true`. For example, ```ruby field :products, [Types::Product] # has `scope: true` by default ``` Will call: ```ruby class Types::Product < Types::BaseObject def self.scope_items(items, context) # filter items here end end ``` The method should return a new list with only the appropriate items for the current `context`. ## Bypassing object-level authorization If you know that any items returned from `.scope_items` should be visible to the current client, you can skip the normal `.authorized?(obj, ctx)` checks by configuring `reauthorize_scoped_objects(false)` in your type definition. For example: ```ruby class Types::Product < Types::BaseObject # Check that singly-loaded objects are visible to the current viewer def self.authorized?(object, context) object.visible_to?(context[:viewer]) end # Filter any list to only include objects that are visible to the current viewer def self.scope_items(items, context) items.visible_for(context[:viewer]) end # If an object of this type was returned from `.scope_items`, # don't call `.authorized?` with it. reauthorize_scoped_objects(false) end ``` graphql-ruby-2.2.17/guides/authorization/visibility.md000066400000000000000000000075551476434635200231430ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Visibility desc: Programatically hide parts of the GraphQL schema from some users. index: 1 redirect_from: - /schema/limiting_visibility --- With GraphQL-Ruby, it's possible to _hide_ parts of your schema from some users. This isn't exactly part of the GraphQL spec, but it's roughly within the bounds of the spec. Here are some reasons you might want to hide parts of your schema: - You don't want non-admin users to know about administration functions of the schema. - You're developing a new feature and want to make a gradual release to only a few users first. ## Hiding Parts of the Schema You can customize the visibility of parts of your schema by reimplementing various `visible?` methods: - Type classes have a `.visible?(context)` class method - Fields and arguments have a `#visible?(context)` instance method - Enum values have `#visible?(context)` instance method - Mutation classes have a `.visible?(context)` class method These methods are called with the query context, based on the hash you pass as `context:`. If the method returns false, then that member of the schema will be treated as though it doesn't exist for the entirety of the query. That is: - In introspection, the member will _not_ be included in the result - In normal queries, if a query references that member, it will return a validation error, since that member doesn't exist ## Object Visibility Let's say you're working on a new feature which should remain secret for a while. You can implement `.visible?` in a type: ```ruby class Types::SecretFeature < Types::BaseObject def self.visible?(context) # only show it to users with the secret_feature enabled super && context[:viewer].feature_enabled?(:secret_feature) end end ``` (Always call `super` to inherit the default behavior.) Now, the following bits of GraphQL will return validation errors: - Fields that return `SecretFeature`, eg `query { findSecretFeature { ... } }` - Fragments on `SecretFeature`, eg `Fragment SF on SecretFeature` And in introspection: - `__schema { types { ... } }` will not include `SecretFeature` - `__type(name: "SecretFeature")` will return `nil` - Any interfaces or unions which normally include `SecretFeature` will _not_ include it - Any fields that return `SecretFeature` will be excluded from introspection ## Field Visibility ```ruby class Types::BaseField < GraphQL::Schema::Field # Pass `field ..., require_admin: true` to hide this field from non-admin users def initialize(*args, require_admin: false, **kwargs, &block) @require_admin = require_admin super(*args, **kwargs, &block) end def visible?(ctx) # if `require_admin:` was given, then require the current user to be an admin super && (@require_admin ? ctx[:viewer]&.admin? : true) end end ``` For this to work, the base field class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-fields" %}. ## Argument Visibility ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # If `require_logged_in: true` is given, then this argument will be hidden from logged-out viewers def initialize(*args, require_logged_in: false, **kwargs, &block) @require_logged_in = require_logged_in super(*args, **kwargs, &block) end def visible?(ctx) super && (@require_logged_in ? ctx[:viewer].present? : true) end end ``` For this to work, the base argument class must be {% internal_link "configured with other GraphQL types", "/type_definitions/extensions.html#customizing-arguments" %}. ## Opting Out By default, GraphQL-Ruby always runs visibility checks. You can opt out of this by adding to your schema class: ```ruby class MySchema < GraphQL::Schema # ... # Opt out of GraphQL-Ruby's visibility feature: use GraphQL::Schema::AlwaysVisible end ``` For big schemas, this can be a worthwhile speed-up. graphql-ruby-2.2.17/guides/changesets/000077500000000000000000000000001476434635200176425ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/changesets/definition.md000066400000000000000000000300521476434635200223140ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Changesets title: Defining Changesets desc: Creating a set of modifications to release in an API version index: 2 --- After {% internal_link "installing Changeset integrations", "/changesets/installation" %} in your schema, you can create Changesets which modify parts of the schema. Changesets extend `GraphQL::Enterprise::Changeset` and include a `release` string. Once a Changeset class is defined, it can be referenced with `added_in: ...` or `removed_in: ...` configurations in the schema. __Note:__ Before GraphQL-Enterprise 1.3.0, Changesets were configured with `modifies ...` blocks. These blocks are still supported and you can find the documentation for that API [on GitHub](https://github.com/rmosolgo/graphql-ruby/blob/v2.0.22/guides/changesets/definition.md). ## Changeset Classes This Changeset will be available to any client whose `context[:changeset_version]` is on or after `2020-12-01`: ```ruby # app/graphql/changesets/deprecate_recipe_flag.rb class Changesets::DeprecateRecipeTags < GraphQL::Enterprise::Changeset release "2020-12-01" end ``` Additionally, Changesets must be {% internal_link "released", "/changesets/releases" %} for their changes to be published. ## Publishing with `added_in:` New things can be published in a changeset by adding `added_in: SomeChangeset` to their configuration. For example, to add a new argument to a field: ```ruby field :search_recipes, [Types::Recipe] do argument :query, String argument :tags, [Types::RecipeTag], required: false, added_in: Changesets::AddRecipeTags end ``` You can also provide a _replacement_ implementation by using `added_in:`. When a new definition has the same name as an existing definition, it implicitly replaces the previous definition in new versions of the API. For example: ```ruby field :rating, Integer, "A 1-5 score for this recipe" # This definition will be superseded by the following one field :rating, Float, "A 1.0-5.0 score for this recipe", added_in: Changesets::FloatingPointRatings ``` Here, a new implementation for `rating` will be used when clients requests an API version that includes `Changesets::FloatingPointRatings`. (If the client requests a version _before_ that changeset, then the preceding implementation would be used instead.) ## Removing with `removed_in:` A `removed_in:` configuration removes something in the named changeset. For example, these enum values are replaced with more clearly-named ones: ```ruby class Types::RecipeTag < Types::BaseEnum # These are replaced by *_HEAT below: value :SPICY, removed_in: Changesets::ClarifyHeatTags value :MEDIUM, removed_in: Changesets::ClarifyHeatTags value :MILD, removed_in: Changesets::ClarifyHeatTags # These new tags are more clear: value :SPICY_HEAT, added_in: Changesets::ClarifyHeatTags value :MEDIUM_HEAT, added_in: Changesets::ClarifyHeatTags value :MILD_HEAT, added_in: Changesets::ClarifyHeatTags end ``` If something has been defined several times, a `removed_in:` configuration removes _all_ definitions: ```ruby class Mutations::SubmitRecipeRating < Mutations::BaseMutation # This is replaced in future API versions by the following argument argument :rating, Integer # This replaces the previous, but in another future version, # it is removed completely (and so is the previous one) argument :rating, Float, added_in: Changesets::FloatingPointRatings, removed_in: Changesets::RemoveRatingsCompletely end ``` ## Examples See below for the different kind of modifications you can make in a changeset: - [Fields](#fields): adding, modifying, and removing fields - [Arguments](#arguments): adding, modifying, and removing arguments - [Enum values](#enum-values): adding, modifying, and removing arguments - [Unions](#unions): adding or removing object types from a union - [Interfaces](#interfaces): adding or removing interface implementations from object types - [Types](#types): changing one type definition for another - [Runtime](#runtime): choosing a behavior at runtime based on the current request and changeset ### Fields To add or redefine a field, use `field(..., added_in: ...)`, including all configuration values for the new implementation (see {{ "GraphQL::Schema::Field#initialize" | api_doc }}). The definition given here will override the previous definition (if there was one) whenever this Changeset applies. ```ruby class Types::Recipe < Types::BaseObject # This new field is available when `context[:changeset_version]` # is on or after the release date of `AddRecipeTags` field :tags, [Types::RecipeTag], added_in: Changeset::AddRecipeTags end ``` To remove a field, add a `removed_in: ...` configuration to the last definition of the field: ```ruby class Types::Recipe < Types::BaseObject # Even after migrating to floating point values, # the "rating" feature never took off, # so we removed it entirely eventually. field :rating, Integer field :rating, Float, added_in: Changeset::FloatingPointRatings, removed_in: Changeset::RemoveRatings end ``` When a field is removed, queries that request that field will be invalid, unless the client has requested a previous API version where the field is still available. ### Arguments You can add, redefine, or remove arguments that belong to fields, input objects, or resolvers. Use `added_in: ...` to provide a new (or updated) definition for an argument, for example: ```ruby class Types::RecipesFilter < Types::BaseInputObject argument :rating, Integer # This new definition is available when # the client's `context[:changeset_version]` includes `FloatingPointRatings` argument :rating, Float, added_in: Changesets::FloatingPointRatings end ``` To remove an argument entirely, add a `removed_in: ...` configuration to the last definition. It will remove _all_ implementations for that argument. For example: ```ruby class Mutations::SubmitRating < Mutations::BaseMutation # Remove this because it's irrelevant: argument :phone_number, String, removed_in: Changesets::StopCollectingPersonalInformation end ``` When arguments are removed, the schema will reject any queries which use them unless the client has requested a previous API version where the argument is still allowed. ### Enum Values With Changesets, you can add, redefine, or remove enum values. To add a new value (or provide a new implementation for a value), include `added_in:` in the `value(...)` configuration: ```ruby class Types::RecipeTag < Types::BaseEnum # This enum will accept and return `KETO` only when the client's API version # includes `AddKetoDietSupport`'s release date. value :KETO, added_in: Changesets::AddKetoDietSupport end ``` Values can be removed with `removed_in:`, for example: ```ruby class Types::RecipeTag < Types::BaseEnum # Old API versions will serve this value; # new versions won't accept it or return it. value :GRAPEFRUIT_DIET, removed_in: Changesets::RemoveLegacyDiets end ``` When enum values are removed, they won't be accepted as input and they won't be allowed as return values from fields unless the client has requested a previous API version where those values are still allowed. ### Unions You can add to or remove from a union's possible types. To release a new union member, include `added_in:` in the `possible_types` configuration: ```ruby class Types::Cookable < Types::BaseUnion possible_types Types::Recipe, Types::Ingredient # Add this to the union when clients opt in to our new feature: possible_types Types::Cuisine, added_in: Changeset::ReleaseCuisines ``` To remove a member from a union, move it to a `possible_types` call with `removed_in: ...`: ```ruby # Stop including this in the union in new API versions: possible_types Types::Chef, removed_in: Changeset::LessChefHype ``` When a possible type is removed, it will not be associated with the union type in introspection queries or schema dumps. ### Interfaces You can add to or remove from an object type's interface definitions. To add one or more interface implementations, use `implements(..., added_in:)`. This will add the interface and its fields to the object whenever this Changeset is active, for example: ```ruby class Types::Recipe < Types::BaseObject # Add this new implementation in new API versions only: implements Types::RssSubject, added_in: Changesets::AddRssSupport end ``` To remove one or more more interface implementations, add `removed_in:` to the `implements ...` configuration, for example: ```ruby implements Types::RssSubject, added_in: Changesets::AddRssSupport, # Sadly, nobody seems to want to use this, # so we removed it all: removed_in: Changesets::RemoveRssSupport ``` When an interface implementation is removed, then the interface will not be associated with the object in introspection queries or schema dumps. Also, any fields inherited from the interface will be hidden from clients. (If the object defines the field itself, it will still be visible.) ### Types Using Changesets, it's possible to define a new type using the same name as an old type. (Only one type per name is allowed for each query, but different queries can use different types for the same name.) First, to define two types with the same name, make two different type definitions. One of them will have to use `graphql_name(...)` to specify the conflicting type name. For example, to migrate an enum type to an object type, define two types: ```ruby # app/graphql/types/legacy_recipe_flag.rb # In the old version of the schema, "recipe tags" were limited to defined set of values. # This enum was renamed from `Types::RecipeTag`, then `graphql_name("RecipeTag")` # was added for GraphQL. class Types::LegacyRecipeTag < Types::BaseEnum graphql_name "RecipeTag" # ... end ``` ```ruby # app/graphql/types/recipe_flag.rb # But in the new schema, each tag is a full-fledged object with fields of its own class Types::RecipeTag < Types::BaseObject field :name, String, null: false field :is_vegetarian, Boolean, null: false # ... end ``` Then, add or update fields or arguments to use the _new_ type instead of the old one. For example: ```diff class Types::Recipe < Types::BaseObject # Change this definition to point at the newly-renamed _legacy_ type # (It's the same type definition, but the Ruby class has a new name) - field :tags, [Types::RecipeTag] + field :tags, [Types::LegacyRecipeTag] # And add a new field for the new type: + field :tags, [Types::RecipeTag], added_in: Changesets::MigrateRecipeTagToObject end ``` With that Changeset, `Recipe.tags` will return an object type instead of an enum type. Clients requesting older versions will still receive enum values from that field. The resolver will probably need an update, too, for example: ```ruby class Types::Recipe < Types::BaseObject # Here's the original definition which returns enum values: field :tags, [Types::LegacyRecipeTag], null: false # Here's the new definition which replaces the previous one on new API versions: field :tags, [Types::RecipeTag], null: false, added_in: Changesets::MigrateRecipeTagToObject def flags all_flag_objects = object.flag_objects if Changesets::MigrateRecipeTagToObject.active?(context) # Here's the new behavior, returning full objects: all_flag_objects else # Convert this to enum values, for legacy behavior: all_flag_objects.map { |f| f.name.upcase } end end end ``` That way, legacy clients will continue to receive enum values while new clients will receive objects. ## Runtime While a query is running, you can check if a changeset applies by using its `.active?(context)` method. For example: ```ruby class Types::Recipe field :flag, Types::RecipeFlag, null: true def flag # Check if this changeset applies to the current request: if Changesets::DeprecateRecipeFlag.active?(context) Stats.count(:deprecated_recipe_flag, context[:viewer]) end # ... end end ``` Besides observability, you can use a runtime check when a resolver needs to pick a different behavior depending on the API version. After defining a changeset, add it to the schema to {% internal_link "release it", "/changesets/releases" %}. graphql-ruby-2.2.17/guides/changesets/installation.md000066400000000000000000000067441476434635200227000ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Changesets title: Installing Changesets desc: Adding Changesets to your schema index: 1 --- Changesets require some updates to the schema (to define changesets) and some updates to your controller (to receive version headers from clients). ## Schema Setup To get started with [GraphQL-Enterprise](https://graphql.pro/enterprise) Changesets, you have to add them to your schema. They're added in several places: - To support versioning arguments, add the `ArgumentIntegration` to your base argument: ```ruby # app/graphql/types/base_argument.rb class Types::BaseArgument < GraphQL::Schema::Argument include GraphQL::Enterprise::Changeset::ArgumentIntegration end ``` Also, make sure that your `BaseField`, `BaseInputObject`, `BaseResolver`, and `BaseMutation` have `argument_class(Types::BaseArgument)` configured in them. - To support versioning fields, add the `FieldIntegration` to your base field: ```ruby # app/graphql/types/base_field.rb class Types::BaseField < GraphQL::Schema::Field include GraphQL::Enterprise::Changeset::FieldIntegration argument_class(Types::BaseArgument) end ``` Also, make sure that your `BaseObject`, `BaseInterface`, and `BaseMutation` have `field_class(Types::BaseField)` configured in them. - To support versioning enum values, add the `EnumValueIntegration` to your base enum value: ```ruby # app/graphql/types/base_enum_value.rb class Types::BaseEnumValue < GraphQL::Schema::EnumValue include GraphQL::Enterprise::Changeset::EnumValueIntegration end ``` Also, make sure that your `BaseEnum` has `enum_value_class(Types::BaseEnumValue)` configured in it. - To support versioning union memberships and interface implementations, add the `TypeMembershipIntegration` to your base type membership: ```ruby # app/graphql/types/base_type_membership.rb class Types::BaseTypeMembership < GraphQL::Schema::TypeMembership include GraphQL::Enterprise::Changeset::TypeMembershipIntegration end ``` Also, make sure that your `BaseUnion` and `BaseInterface` have `type_membership_class(Types::BaseTypeMembership)` configured in it. (`TypeMembership`s are used by GraphQL-Ruby to link object types to the union types they belong to and the interfaces they implement. By using a custom type membership class, you can make objects belong (or _not_ belong) to unions or interfaces, depending on the API version.) Once those integrations are set up, you're ready to {% internal_link "write a changeset", "/changesets/definition" %} and start {% internal_link "releasing API versions", "/changesets/releases" %}! ## Controller Setup Additionally, your controller must pass `context[:changeset_version]` when running queries. To provide this, update your controller: ```ruby class GraphqlController < ApplicationController def execute context = { # ... changeset_version: headers["API-Version"], # <- Your header here. Choose something for API clients to pass. } result = MyAppSchema.execute(..., context: context) # ... end end ``` In the example above, `API-Version: ...` will be parsed from the incoming request and used as `context[:changeset_version]`. If `context[:changeset_version]` is `nil`, then _no_ changesets will apply to that request. Now that Changesets are installed, read on to {% internal_link "define some changesets", "/changesets/definition" %}. graphql-ruby-2.2.17/guides/changesets/overview.md000066400000000000000000000046761476434635200220470ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Changesets title: API Versioning for GraphQL-Ruby desc: Evolve your schema over time, feature-by-feature index: 0 --- Out-of-the-box, GraphQL is [versionless by design](https://graphql.org/learn/best-practices/#versioning). GraphQL's openness to extension paves the way for continuously expanding and improving an API. You can _always_ add new fields, new arguments, and new types to implement new features and customize existing behavior. However, sometimes a business case may call for a different versioning scheme. [GraphQL-Enterprise](https://graphql.pro/enterprise)'s "Changesets" enable schemas to release _any_ change -- even breaking changes -- to clients, depending on what version of the schema they're using. With Changesets, you can redefine existing fields, define new types using old names, add or remove enum values -- anything, really -- while maintaining compatibility for existing clients. ## Why Changesets? Changesets are a _complementary_ evolution technique to continuous additions. In general, additive changes (new fields, new arguments, new types) are best added right to the existing schema. But if you need to _remove_ something from the schema or redefine existing parts of the schema in non-backwards-compatible ways, Changesets provide a handy way of doing so. For example, if you add a values to an Enum, you can just add it to the existing schema: ```diff class Types::RecipeTag < Types::BaseEnum value "LOW_FAT" value "LOW_CARB" + value "VEGAN" + value "KETO" + value "GRAPEFRUIT_DIET" end ``` However, if you want to change the schema in ways that would _break_ previous queries, you can do that with a Changeset: ```ruby class Types::RecipeTag < Types::BaseEnum # Turns out this makes you sick: value "GRAPEFRUIT_DIET", removed_in: Changesets::RemoveLegacyDiets end ``` Then, only clients requesting API versions _before_ this changeset would be able to use `GRAPEFRUIT_DIET`; clients requesting newer versions could not send it as input and would not receive it in responses. (Changesets _also_ support additive changes, if you prefer to make them that way.) ## Getting Started To start using Changesets, read on: - {% internal_link "Installing Changesets", "/changesets/installation" %} - {% internal_link "Writing Changesets", "/changesets/definition" %} - {% internal_link "Releasing Changesets", "/changesets/releases" %} graphql-ruby-2.2.17/guides/changesets/releases.md000066400000000000000000000057131476434635200217750ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Changesets title: Releasing Changesets desc: Associating changes to version numbers index: 3 --- To be available to clients, Changesets added to the schema with `use GraphQL::Enterprise::Changeset::Release changeset_dir: "..."`: ```ruby class MyAppSchema < GraphQL::Schema # Add this before root types so that newly-added types are also added to the schema use GraphQL::Enterprise::Changeset::Release, changeset_dir: "app/graphql/changesets" query(...) mutation(...) subscription(...) end ``` This attaches each Changeset defined in `app/graphql/changesets/*.rb` to the schema. (It assumes Rails conventions, where an underscored file like `app/graphql/changesets/add_some_feature.rb` contains a class like `Changesets::AddSomeFeature`.) {% callout warning %} Add `GraphQL::Enterprise::Changeset::Release` _before_ hooking up your root `query(...)`, `mutation(...)`, and `subscription(...)` types. Otherwise, the schema may not find links to types in new schema versions. {% endcallout %} Alternatively, Changesets can be explicitly attached using `changesets: [...]`, for example: ```ruby class MyAppSchema < GraphQL::Schema use GraphQL::Enterprise::Changeset::Release, changesets: [ Changesets::DeprecateRecipeFlag, Changesets::RemoveRecipeFlag, ] # ... end ``` Only changesets in the directory (or in the array) will be shown to clients. The `release ...` configuration in the changeset will be compared to `context[:changeset_version]` to determine if the changeset applies to the current request. ## Inspecting Releases To preview releases, you can create schema dumps by passing `context: { changeset_version: ... }` to {{ "Schema.to_definition" | api_doc }}. For example, to see how the schema looks with `API-Version: 2021-06-01`: ```ruby schema_sdl = MyAppSchema.to_definition(context: { changeset_version: "2021-06-01"}) # The GraphQL schema definition for the schema at version "2021-06-01": puts schema_sdl ``` To make sure schema versions don't change unexpectedly, use the techniques described in the {% internal_link "Schema structure guide", "/testing/schema_structure" %}. ### Introspection Methods You can also inspect a schema's changesets programmatically. `GraphQL::Enterprise` adds a `Schema.changesets` method which returns a `Set` of changeset classes: ```ruby MySchema.changesets # # ``` Additionally, each changeset has a `.changes` method describing its modifications: ```ruby AddNewFeature.changes # [ # #, # #, # #, # ... # ] ``` Each `Change` object responds to: - `.member`, the part of the schema that was modified - `.type`, the kind of modification (`:addition` when something new is added, `:removal` when a member is removed or replaced with a new definition) graphql-ruby-2.2.17/guides/css/000077500000000000000000000000001476434635200163065ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/css/main.scss000066400000000000000000000462171476434635200201410ustar00rootroot00000000000000--- --- @import "reset"; $brand-color: #a5152a; $dark-theme-brand-color: #e5534b; $brand-color-light: #ed8090; $brand-color-extralight: #f9e8ee; $dark-theme-brand-color-extralight: #262324; $experimental-background: #fff7cf; $experimental-color: hsla(50, 100%, 32%, 0.15); $pro-color: #406db5; $pro-background: hsla(217, 100%, 29%, 0.15); $enterprise-color: #238c44; $enterprise-background: hsla(135, 97%, 25%, 0.15); $dark-theme-code-border: #aaaaaa; $code-border: #d6d6d6; $dark-theme-code-background: #1e1b1b; $code-background: #fafafa; $dark-theme-code-color: #b5b5b5; $code-color: #777777; $code-border-radius: 2px; $muted-color: #777777; $subtle-color: #aaaaaa; $font: 'Rubik', sans-serif; $code-font: 'Monaco', monospace; $faint-color: #f0f0f0; $dark-theme-faint-color: #6a6969; $font-color: black; $dark-theme-font-color: #dbdbdb; $background-color: #fafafa; $dark-theme-background-color: #422e2e; $container-color: white; $dark-theme-container-color: #424242; body { font-family: $font; background: $background-color; .dark-theme-button::after { content: "☀" } } body.dark-theme { background: $dark-theme-code-background; color: $dark-theme-font-color; .dark-theme-button::after { content: "☽" } } strong, b { font-weight: bold; } // Algolia highlights: .ais-Highlight { font-style: normal; font-weight: bold; } .dark-theme { .header { background: $dark-theme-container-color; box-shadow: 0px 0px 10px 0px black; .nav a:hover { color: $font-color; background-color: $dark-theme-brand-color; } } } .header { box-shadow: 0px 0px 10px 0px #d6d6d6; z-index: 1; position: relative; background: $container-color; .nav { $height: 30px; $margin: 10px; display: flex; flex-wrap: wrap; align-items: center; $fade-time: 0.2s; .nav-links { margin-left: auto; display: flex; } .img-link { transition: background $fade-time; img { transition: filter $fade-time; transition: -webkit-filter $fade-time; filter: brightness(1) invert(0); -webkit-filter: brightness(1) invert(0); } &:hover { img { -webkit-filter: brightness(0) invert(1); filter: brightness(0) invert(1); } } } img { height: $height; width: auto; margin: $margin; } a, span { transition: background-color $fade-time; transition: color $fade-time; &:hover { background-color: $brand-color; color: white; } padding: $margin; height: $height; display: flex; align-items: center; text-decoration: none; } } } .header-container { margin: 0px 20px 0px 20px; } .container { max-width: 1200px; &.fullwidth { max-width: 100%; margin: 0px 20px 0px 20px; } margin: 0px auto; padding: 10px 20px; background: $container-color; } .dark-theme { .container { background: $dark-theme-container-color; } } .callout { .heading { font-size: 20px; font-weight: bold; margin-bottom: 20px; } padding: 20px 20px 10px 20px; margin: 20px; border: 2px; border-radius: 10px; &.callout-warning { background-color: rgba(255, 217, 0, 0.2); border-color: rgba(255, 217, 0, 0.5); } } pre { font-family: $code-font; padding: 0.5rem; border: 1px solid $code-border; border-radius: $code-border-radius; background-color: $code-background; margin: 10px 0px; overflow-x: auto; line-height: 1.4rem; } .dark-theme { pre { background-color: $dark-theme-code-background; border: 1px solid $dark-theme-code-border; } } p, li { line-height: 1.3rem; } li { margin-left: 15px; margin-top: 5px } p, ul { margin-bottom: 20px; } ul { list-style-type: disc; list-style-position: outside; } ol { list-style: decimal; margin-left: 5px; } code { font-family: $code-font; color: $code-color; font-weight: 400; } .dark-theme code { color: $dark-theme-code-color; } .code .line-numbers { display: none; } .dark-theme a { color: $dark-theme-brand-color; border-color: $dark-theme-brand-color; } a { color: $brand-color; border-color: $brand-color; text-decoration: none; } a:hover, a:hover code { text-decoration: underline; } #readme img { display: none; } .guide-container { a.img-link { background: none; &:hover { background: none; } img { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); transition: all 0.3s cubic-bezier(.25,.8,.25,1); max-height: 300px; max-width: 100%; &:hover { box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); } } } } .cell { overflow-x: scroll; } .monitoring-img-group { display: flex; flex-direction: row; margin-bottom: 20px; flex-wrap: wrap; justify-content: space-around; align-items: center; } .guides-toc { ul { list-style: none; display: flex; flex-wrap: wrap; } li { padding-bottom: 10px; padding-right: 10px; } } .guides { .guide-desc { color: $muted-color; margin-left: 5px; } ul { margin-left: 10px; list-style: none; } } @mixin doc-header($color, $background-color) { background-color: $background-color; color: $color; border-radius: $code-border-radius; padding: 10px 10px 10px 10px; margin-bottom: 10px; border: 1px solid $color; p { margin: 0px; padding: 0px; } a { color: $color; &:hover { background-color: $color; color: $background-color; } } } .experimental-header { @include doc-header($experimental-color, $experimental-background); } .pro-header { @include doc-header($pro-color, $pro-background); } .enterprise-header { @include doc-header($enterprise-color, $enterprise-background); } .dark-theme .guide-footer { background-color: $dark-theme-brand-color-extralight; } .guide-footer { background: $brand-color-extralight; margin: 25px 0px 0px 0px; padding: 10px; border-radius: $code-border-radius; } .dark-theme { .hero { .hero-part { &.shaded { background: $dark-theme-faint-color; } h2 { color: $dark-theme-brand-color; text-shadow: $dark-theme-background-color 1px 1px 1px; } } } } .hero { display: flex; flex-direction: column; justify-content: space-around; .hero-title { display: flex; justify-content: center; align-items: center; img, h1 { margin: 20px 10px 30px 10px; } } .hero-subtitle { p { margin: 5px auto; text-align: center; } padding: 10px 0px; } .hero-part { &.shaded { background: $faint-color; } h2 { color: $brand-color; text-shadow: #cccccc 1px 1px 1px; font-size: 1.4em; } display: flex; justify-content: space-between; flex-wrap: wrap; .hero-feature { padding: 15px; flex-basis: calc(50% - 60px); flex-grow: 1; } } } h1, h2, h3, h4, h5 { margin: 25px 0px 15px 0px; a { text-decoration: none; } } .guide-header { margin-bottom: 15px; } h1 { font-size: 1.5rem; } h2 { font-size: 1.3rem; } h3 { font-size: 1.2rem; } h4 { font-size: 1.1rem; } h5 { font-size: 1.05rem; } em { font-style: italic; } table { width: 100%; margin: 0px 0px 15px 0px; thead { text-align: left; border-bottom: 1px solid $subtle-color; } td, th { padding: 5px 10px 5px 0px; } } .dark-theme { .search-input { background: $dark-theme-code-background; color: $dark-theme-font-color; } .search-results-container { background-color: $dark-theme-background-color; #search-results { .search-result { &:focus, &:hover { background-color: $dark-theme-container-color; border-bottom-color: $dark-theme-brand-color; .search-title { color: $dark-theme-brand-color; } } .search-category { border: 1px solid $dark-theme-brand-color; color: $dark-theme-brand-color; } } } } } .search-input { font-size: 1em; padding: 5px; margin: 10px; border: 1px solid $subtle-color; border-radius: 3px; } .search-results-container { $bg: #eaeaea; $bg-highlight: #f9f9f9; background-color: $bg; #search-results { $fade-time: 0.1s; display: flex; flex-direction: column; max-width: 1040px; margin: 0 auto; .search-result { text-decoration: none; color: $font-color; padding: 6px 10px 0px 6px; line-height: 18px; border-bottom: 2px solid transparent; transition: border-bottom-color $fade-time; .search-title { font-weight: bold; margin-right: 8px; transition: color $fade-time; } .search-preview { color: $subtle-color; } .search-category { border: 1px solid $brand-color; border-radius: 3px; margin: 0 8px 0 0; padding: 3px; font-size: 0.7em; color: $brand-color; position: relative; top: -3px; } &:focus, &:hover { outline: none; .search-title { color: $brand-color; } background-color: $bg-highlight; border-bottom-color: $brand-color; } } } } .dark-theme ul.breadcrumb .jump-to-select { color: $dark-theme-brand-color; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='292.4' height='292.4'%3E%3Cpath fill='%23e5534b' d='M287 69.4a17.6 17.6 0 0 0-13-5.4H18.4c-5 0-9.3 1.8-12.9 5.4A17.6 17.6 0 0 0 0 82.2c0 5 1.8 9.3 5.4 12.9l128 127.9c3.6 3.6 7.8 5.4 12.8 5.4s9.2-1.8 12.8-5.4L287 95c3.5-3.5 5.4-7.8 5.4-12.8 0-5-1.9-9.2-5.5-12.8z'/%3E%3C/svg%3E"); } ul.breadcrumb { color: $muted-color; li { display: inline; list-style: none; margin: 0; } li:before { content: "»"; margin: 0px 4px 0px 2px; } li:first-child:before { content: ""; margin: 0; } .jump-to-select { box-sizing: border-box; -moz-appearance: none; -webkit-appearance: none; appearance: none; padding: 5px 20px 5px 5px; border: 1px solid $code-border; border-radius: 5px; background-color: transparent; color: $brand-color; font-size: 16px; font-weight: 500; line-height: 1.3; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='292.4' height='292.4'%3E%3Cpath fill='%23a5152a' d='M287 69.4a17.6 17.6 0 0 0-13-5.4H18.4c-5 0-9.3 1.8-12.9 5.4A17.6 17.6 0 0 0 0 82.2c0 5 1.8 9.3 5.4 12.9l128 127.9c3.6 3.6 7.8 5.4 12.8 5.4s9.2-1.8 12.8-5.4L287 95c3.5-3.5 5.4-7.8 5.4-12.8 0-5-1.9-9.2-5.5-12.8z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 8px center; background-size: 9px; cursor: default; &:hover { border-color: #777; } &:focus { border-color: #999; box-shadow: 0 0 1px 2px #6db4ff; outline: none; } option { color: black; } } } .dark-theme { .table-of-contents { background: $dark-theme-code-background; } } .table-of-contents { float: right; border: 1px solid $subtle-color; border-radius: 3px; padding: 15px; margin: 0 10px 10px 10px; width: 300px; background: $code-background; .contents-header { margin: 0 0 5px 20px; } .contents-list { margin: 0; list-style: decimal; padding-left: 5px; .contents-entry { &::marker { color: $muted-color; } .contents-entry { list-style: none; } } } } /* pygments CSS, github theme */ .highlight .hll { background-color: #ffffcc } .highlight .c { color: #999988; font-style: italic } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #000000; font-weight: bold } /* Keyword */ .highlight .o { color: #000000; font-weight: bold } /* Operator */ .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #999999 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ .highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #009999 } /* Literal.Number */ .highlight .s { color: #d01040 } /* Literal.String */ .highlight .na { color: #008080 } /* Name.Attribute */ .highlight .nb { color: #0086B3 } /* Name.Builtin */ .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ .highlight .no { color: #008080 } /* Name.Constant */ .highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ .highlight .ni { color: #800080 } /* Name.Entity */ .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ .highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ .highlight .nn { color: #555555 } /* Name.Namespace */ .highlight .nt { color: #000080 } /* Name.Tag */ .highlight .nv { color: #008080 } /* Name.Variable */ .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mf { color: #009999 } /* Literal.Number.Float */ .highlight .mh { color: #009999 } /* Literal.Number.Hex */ .highlight .mi { color: #009999 } /* Literal.Number.Integer */ .highlight .mo { color: #009999 } /* Literal.Number.Oct */ .highlight .sb { color: #d01040 } /* Literal.String.Backtick */ .highlight .sc { color: #d01040 } /* Literal.String.Char */ .highlight .sd { color: #d01040 } /* Literal.String.Doc */ .highlight .s2 { color: #d01040 } /* Literal.String.Double */ .highlight .se { color: #d01040 } /* Literal.String.Escape */ .highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ .highlight .si { color: #d01040 } /* Literal.String.Interpol */ .highlight .sx { color: #d01040 } /* Literal.String.Other */ .highlight .sr { color: #009926 } /* Literal.String.Regex */ .highlight .s1 { color: #d01040 } /* Literal.String.Single */ .highlight .ss { color: #990073 } /* Literal.String.Symbol */ .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #008080 } /* Name.Variable.Class */ .highlight .vg { color: #008080 } /* Name.Variable.Global */ .highlight .vi { color: #008080 } /* Name.Variable.Instance */ .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ .dark-theme { .highlight .hll { background-color: #49483e } pre.highlight { background: #272822; color: #f8f8f2 } .highlight .c { color: #75715e } /* Comment */ .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ .highlight .k { color: #66d9ef } /* Keyword */ .highlight .l { color: #ae81ff } /* Literal */ .highlight .n { color: #f8f8f2 } /* Name */ .highlight .o { color: #f92672 } /* Operator */ .highlight .p { color: #f8f8f2 } /* Punctuation */ .highlight .ch { color: #75715e } /* Comment.Hashbang */ .highlight .cm { color: #75715e } /* Comment.Multiline */ .highlight .cp { color: #75715e } /* Comment.Preproc */ .highlight .cpf { color: #75715e } /* Comment.PreprocFile */ .highlight .c1 { color: #75715e } /* Comment.Single */ .highlight .cs { color: #75715e } /* Comment.Special */ .highlight .gd { color: #f92672 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gi { color: #a6e22e } /* Generic.Inserted */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #75715e } /* Generic.Subheading */ .highlight .kc { color: #66d9ef } /* Keyword.Constant */ .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ .highlight .kn { color: #f92672 } /* Keyword.Namespace */ .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ .highlight .kt { color: #66d9ef } /* Keyword.Type */ .highlight .ld { color: #e6db74 } /* Literal.Date */ .highlight .m { color: #ae81ff } /* Literal.Number */ .highlight .s { color: #e6db74 } /* Literal.String */ .highlight .na { color: #a6e22e } /* Name.Attribute */ .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ .highlight .nc { color: #a6e22e } /* Name.Class */ .highlight .no { color: #66d9ef } /* Name.Constant */ .highlight .nd { color: #a6e22e } /* Name.Decorator */ .highlight .ni { color: #f8f8f2 } /* Name.Entity */ .highlight .ne { color: #a6e22e } /* Name.Exception */ .highlight .nf { color: #a6e22e } /* Name.Function */ .highlight .nl { color: #f8f8f2 } /* Name.Label */ .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ .highlight .nx { color: #a6e22e } /* Name.Other */ .highlight .py { color: #f8f8f2 } /* Name.Property */ .highlight .nt { color: #f92672 } /* Name.Tag */ .highlight .nv { color: #f8f8f2 } /* Name.Variable */ .highlight .ow { color: #f92672 } /* Operator.Word */ .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ .highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ .highlight .sc { color: #e6db74 } /* Literal.String.Char */ .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ .highlight .se { color: #ae81ff } /* Literal.String.Escape */ .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ .highlight .sx { color: #e6db74 } /* Literal.String.Other */ .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } graphql-ruby-2.2.17/guides/dataloader/000077500000000000000000000000001476434635200176165ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/dataloader/adopting.md000066400000000000000000000103741476434635200217520ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Dataloader vs. GraphQL-Batch desc: Comparing and Contrasting Batch Loading Options index: 3 --- {{ "GraphQL::Dataloader" | api_doc }} solves the same problem as [`GraphQL::Batch`](https://github.com/shopify/graphql-batch). There are a few major differences between the modules: - __Concurrency Primitive:__ GraphQL-Batch uses `Promise`s from [`promise.rb`](https://github.com/lgierth/promise.rb); GraphQL::Dataloader uses Ruby's [`Fiber` API](https://ruby-doc.org/core-3.0.0/Fiber.html). These primitives dictate how batch loading code is written (see below for comparisons). - __Maturity:__ Frankly, GraphQL-Batch is about as old as GraphQL-Ruby, and it's been in production at Shopify, GitHub, and others for many years. GraphQL::Dataloader is new, and although Ruby has supported `Fiber`s since 1.9, they still aren't widely used. - __Scope:__ It's not currently possible to use `GraphQL::Dataloader` _outside_ GraphQL. The incentive in writing `GraphQL::Dataloader` was to leverage `Fiber`'s ability to _transparently_ pause and resume work, which removes the need for `Promise`s (and removes the resulting complexity in the code). Additionally, `GraphQL::Dataloader` should _eventually_ support Ruby 3.0's `Fiber.scheduler` API, which runs I/O in the background by default. ## Comparison: Fetching a single object In this example, a single object is batch-loaded to satisfy a GraphQL field. - With __GraphQL-Batch__, you call a loader, which returns a `Promise`: ```ruby record_promise = Loaders::Record.load(1) ``` Then, under the hood, GraphQL-Ruby manages the promise (using its `lazy_resolve` feature, upstreamed from GraphQL-Batch many years ago). GraphQL-Ruby will call `.sync` on it when no futher execution is possible; `promise.rb` implements `Promise#sync` to execute the pending work. - With __GraphQL::Dataloader__, you get a source, then call `.load` on it, which may pause the current Fiber, but it returns the requested object. ```ruby dataloader.with(Sources::Record).load(1) ``` Since the requested object is (eventually) returned from `.load`, Nothing else is required. ## Comparison: Fetching objects in sequence (dependent) In this example, one object is loaded, then another object is loaded _based on_ the first one. - With __GraphQL-Batch__, `.then { ... }` is used to join dependent code blocks: ```ruby Loaders::Record.load(1).then do |record| Loaders::OtherRecord.load(record.other_record_id) end ``` That call returns a `Promise`, which is stored by GraphQL-Ruby, and finally `.sync`ed. - With __GraphQL-Dataloader__, `.load(...)` returns the requested object (after a potential `Fiber` pause), so no other method calls are necessary: ```ruby record = dataloader.with(Sources::Record).load(1) dataloader.with(Sources::OtherRecord).load(record.other_record_id) ``` ## Comparison: Fetching objects concurrently (independent) Sometimes, you need multiple _independent_ records to perform a calcuation. Each record is loaded, then they're combined in some bit of work. - With __GraphQL-Batch__, `Promise.all(...)` is used to to wait for several pending loads: ```ruby promise_1 = Loaders::Record.load(1) promise_2 = Loaders::OtherRecord.load(2) Promise.all([promise_1, promise_2]).then do |record, other_record| do_something(record, other_record) end ``` If the objects are loaded from the same loader, then `.load_many` also works: ```ruby Loaders::Record.load_many([1, 2]).then do |record, other_record| do_something(record, other_record) end ``` - With __GraphQL::Dataloader__, each request is registered with `.request(...)` (which never pauses the Fiber), then data is loaded with `.load` (which will pause the Fiber as needed): ```ruby # first, make some requests request_1 = dataloader.with(Sources::Record).request(1) request_2 = dataloader.with(Sources::OtherRecord).request(2) # then, load the objects and do something record = request_1.load other_record = request_2.load do_something(record, other_record) ``` If the objects come from the same `Source`, then `.load_all` will return the objects directly: ```ruby record, other_record = dataloader.with(Sources::Record).load_all([1, 2]) do_something(record, other_record) ``` graphql-ruby-2.2.17/guides/dataloader/async_dataloader.md000066400000000000000000000030221476434635200234320ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Async Source Execution desc: Using AsyncDataloader to fetch external data in parallel index: 5 --- `AsyncDataloader` will run {{ "GraphQL::Dataloader::Source#fetch" | api_doc }} calls in parallel, so that external service calls (like database queries or network calls) don't have to wait in a queue. To use `AsyncDataloader`, hook it up in your schema _instead of_ `GraphQL::Dataloader`: ```diff - use GraphQL::Dataloader + use GraphQL::Dataloader::AsyncDataloader ``` __Also__, add [the `async` gem](https://github.com/socketry/async) to your project, for example: ``` bundle add async ``` Now, {{ "GraphQL::Dataloader::AsyncDataloader" | api_doc }} will create `Async::Task` instances instead of plain `Fiber`s and the `async` gem will manage parallelism. For a demonstration of this behavior, see: [https://github.com/rmosolgo/rails-graphql-async-demo](https://github.com/rmosolgo/rails-graphql-async-demo) _You can also implement {% internal_link "manual parallelism", "/dataloader/parallelism" %} using `dataloader.yield`._ ## Rails For Rails, you'll need **Rails 7.1**, which properly supports fiber-based concurrency, and you'll also want to configure Rails to use Fibers for isolation: ```ruby class Application < Rails::Application # ... config.active_support.isolation_level = :fiber end ``` ## Other Options You can also manually implement parallelism with Dataloader. See the {% internal_link "Dataloader Parallelism", "/dataloader/parallelism" %} guide for details. graphql-ruby-2.2.17/guides/dataloader/dataloader.md000066400000000000000000000015151476434635200222420ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Dataloader desc: The Dataloader orchestrates Fibers and Sources index: 2 --- {{ "GraphQL::Dataloader" | api_doc }} instances are created for each query (or multiplex) and they: - Cache {% internal_link "Source", "/dataloader/sources" %} instances for the duration of GraphQL execution - Run pending Fibers to resolve data requirements and continue GraphQL execution During a query, you can access the dataloader instance with: - {{ "GraphQL::Query::Context#dataloader" | api_doc }} (`context.dataloader`, anywhere that query context is available) - {{ "GraphQL::Schema::Object#dataloader" | api_doc }} (`dataloader` inside a resolver method) - {{ "GraphQL::Schema::Resolver#dataloader" | api_doc }} (`dataloader` inside `def resolve` of a Resolver, Mutation, or Subscription class.) graphql-ruby-2.2.17/guides/dataloader/overview.md000066400000000000000000000130261476434635200220100ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Overview desc: Getting started with the Fiber-based Dataloader index: 0 --- {{ "GraphQL::Dataloader" | api_doc }} provides efficient, batched access to external services, backed by Ruby's `Fiber` concurrency primitive. It has a per-query result cache and {% internal_link "AsyncDataloader", "/dataloader/async_dataloader" %} supports truly parallel execution out-of-the-box. `GraphQL::Dataloader` is inspired by [`@bessey`'s proof-of-concept](https://github.com/bessey/graphql-fiber-test/tree/no-gem-changes) and [shopify/graphql-batch](https://github.com/shopify/graphql-batch). ## Batch Loading `GraphQL::Dataloader` facilitates a two-stage approach to fetching data from external sources (like databases or APIs): - First, GraphQL fields register their data requirements (eg, object IDs or query parameters) - Then, after as many requirements have been gathered as possible, `GraphQL::Dataloader` initiates _actual_ fetches to external services That cycle is repeated during execution: data requirements are gathered until no further GraphQL fields can be executed, then `GraphQL::Dataloader` triggers external calls based on those requirements and GraphQL execution resumes. ## Fibers `GraphQL::Dataloader` uses Ruby's `Fiber`, a lightweight concurrency primitive which supports application-level scheduling _within_ a `Thread`. By using `Fiber`, `GraphQL::Dataloader` can pause GraphQL execution when data is requested, then resume execution after the data is fetched. At a high level, `GraphQL::Dataloader`'s usage of `Fiber` looks like this: - GraphQL execution is run inside a Fiber. - When that Fiber returns, if the Fiber was paused to wait for data, then GraphQL execution resumes with the _next_ (sibling) GraphQL field inside a new Fiber. - That cycle continues until no further sibling fields are available and all known Fibers are paused. - `GraphQL::Dataloader` takes the first paused Fiber and resumes it, causing the `GraphQL::Dataloader::Source` to execute its `#fetch(...)` call. That Fiber continues execution as far as it can. - Likewise, paused Fibers are resumed, causing GraphQL execution to continue, until all paused Fibers are evaluated completely. Whenever `GraphQL::Dataloader` creates a new `Fiber`, it copies each pair from `Thread.current[...]` and reassigns them inside the new `Fiber`. `AsyncDataloader`, built on top of the [`async` gem](https://github.com/socketry/async), supports parallel I/O operations (like network and database communication) via Ruby's non-blocking `Fiber.schedule` API. {% internal_link "Learn more →", "/dataloader/async_dataloader" %}. ## Getting Started To install {{ "GraphQL::Dataloader" | api_doc }}, add it to your schema with `use ...`, for example: ```ruby class MySchema < GraphQL::Schema # ... use GraphQL::Dataloader end ``` Then, inside your schema, you can request batch-loaded objects by their lookup key with `dataloader.with(...).load(...)`: ```ruby field :user, Types::User do argument :handle, String end def user(handle:) dataloader.with(Sources::UserByHandle).load(handle) end ``` Or, load several objects by passing an array of lookup keys to `.load_all(...)`: ```ruby field :is_following, Boolean, null: false do argument :follower_handle, String argument :followed_handle, String end def is_following(follower_handle:, followed_handle:) follower, followed = dataloader .with(Sources::UserByHandle) .load_all([follower_handle, followed_handle]) followed && follower && follower.follows?(followed) end ``` To prepare requests from several sources, use `.request(...)`, then call `.load` after all requests are registered: ```ruby class AddToList < GraphQL::Schema::Mutation argument :handle, String argument :list, String, as: :list_name field :list, Types::UserList def resolve(handle:, list_name:) # first, register the requests: user_request = dataloader.with(Sources::UserByHandle).request(handle) list_request = dataloader.with(Sources::ListByName, context[:viewer]).request(list_name) # then, use `.load` to wait for the external call and return the object: user = user_request.load list = list_request.load # Now, all objects are ready. list.add_user!(user) { list: list } end end ``` ### `loads:` and `object_from_id` `dataloader` is also available as `context.dataloader`, so you can use it to implement `MySchema.object_from_id`. For example: ```ruby class MySchema < GraphQL::Schema def self.object_from_id(id, ctx) model_class, database_id = IdDecoder.decode(id) ctx.dataloader.with(Sources::RecordById, model_class).load(database_id) end end ``` Then, any arguments with `loads:` will use that method to fetch objects. For example: ```ruby class FollowUser < GraphQL::Schema::Mutation argument :follow_id, ID, loads: Types::User field :followed, Types::User def resolve(follow:) # `follow` was fetched using the Schema's `object_from_id` hook context[:viewer].follow!(follow) { followed: follow } end end ``` ## Data Sources To implement batch-loading data sources, see the {% internal_link "Sources guide", "/dataloader/sources" %}. ## Parallelism You can run I/O operations in parallel with GraphQL::Dataloader. There are two approaches: - `AsyncDataloader` uses the `async` gem to automatically background I/O from `Dataloader::Source#fetch` calls. {% internal_link "Read More", "/dataloader/async_dataloader" %} - You can manually call `dataloader.yield` after starting work in the background. {% internal_link "Read More", "/dataloader/parallelism" %} graphql-ruby-2.2.17/guides/dataloader/parallelism.md000066400000000000000000000055071476434635200224540ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Manual Parallelism desc: Yield to Dataloader after starting work index: 7 --- You can coordinate with {{ "GraphQL::Dataloader" | api_doc }} to run tasks in the background. To do this, call `dataloader.yield` inside `Source#fetch` after kicking off your task. For example: ```ruby def fetch(ids) # somehow queue up a background query, # see examples below future_result = async_query_for(ids) # return control to the dataloader dataloader.yield # dataloader will come back here # after calling other sources, # now wait for the value future_result.value end ``` _Alternatively, you can use {% internal_link "AsyncDataloader", "/dataloader/async_dataloader" %} to automatically background I/O inside `Source#fetch` calls._ ## Example: Rails load_async You can use Rails's `load_async` method to load `ActiveRecord::Relation`s in the background. For example: ```ruby class Sources::AsyncRelationSource < GraphQL::Dataloader::Source def fetch(relations) relations.each(&:load_async) # start loading them in the background dataloader.yield # hand back to GraphQL::Dataloader relations.each(&:load) # now, wait for the result, returning the now-loaded relation end end ``` You could call that source from a GraphQL field method: ```ruby field :direct_reports, [Person] def direct_reports # prepare an ActiveRecord::Relation: direct_reports = Person.where(manager: object) # pass it off to the source: dataloader .with(Sources::AsyncRelationSource) .load(direct_reports) end ``` ## Example: Rails async calculations In a Dataloader source, you can run Rails async calculations in the background while other work continues. For example: ```ruby class Sources::DirectReportsCount < GraphQL::Dataloader::Source def fetch(users) # Start the queries in the background: promises = users.map { |u| u.direct_reports.async_count } # Return to GraphQL::Dataloader: dataloader.yield # Now return the results, waiting if necessary: promises.map(&:value) end end ``` Which could be used in a GraphQL field: ```ruby field :direct_reports_count, Int def direct_reports_count dataloader.with(Sources::DirectReportsCount).load(object) end ``` ## Example: Concurrent::Future You could use `concurrent-ruby` to put work in a background thread. For example, using `Concurrent::Future`: ```ruby class Sources::ExternalDataSource < GraphQL::Dataloader::Source def fetch(urls) # Start some I/O-intensive work: futures = urls.map do |url| Concurrent::Future.execute { # Somehow fetch and parse data: get_remote_json(url) } end # Yield back to GraphQL::Dataloader: dataloader.yield # Dataloader has done what it can, # so now return the value, waiting if necessary: futures.map(&:value) end end ``` graphql-ruby-2.2.17/guides/dataloader/sources.md000066400000000000000000000160151476434635200216260ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Sources desc: Batch-loading objects for GraphQL::Dataloader index: 1 --- _Sources_ are what {{ "GraphQL::Dataloader" | api_doc }} uses to fetch data from external services. ## Source Concepts Sources are classes that inherit from `GraphQL::Dataloader::Source`. A Source _must_ implement `def fetch(keys)` to return a list of objects, one for each of the given keys. A source _may_ implement `def initialize(...)` to accept other batching parameters. Sources will receive two kinds of inputs from `GraphQL::Dataloader`: - _keys_, which correspond to objects requested by the application. Keys are passed to `def fetch(keys)`, which must return an object (or `nil`) for each of `keys`, in the same order as `keys`. Under the hood, each Source instance maintains a `key => object` cache. - _batch parameters_, which are the basis of batched groups. For example, if you're loading records from different database tables, the table name would be a batch parameter. Batch parameters are given to `dataloader.with(source_class, *batch_parameters)`, and the default is _no batch parameters_. When you define a source, you should add the batch parameters to `def initialize(...)` and store them in instance variables. (`dataloader.with(source_class, *batch_parameters)` returns an instance of `source_class` with the given batch parameters -- but it might be an instance which was cached by `dataloader`.) Additionally, batch parameters are used to de-duplicate Source initializations during a query run. `.with(...)` calls that have the same batch parameters will use the same Source instance under the hood. To customize how Sources are de-duplicated, see {{ "GraphQL::Dataloader::Source.batch_key_for" | api_doc }}. ## Example: Loading Strings from Redis by Key The simplest source might fetch values based on their keys. For example: ```ruby # app/graphql/sources/redis_string.rb class Sources::RedisString < GraphQL::Dataloader::Source REDIS = Redis.new def fetch(keys) # Redis's `mget` will return a value for each key with a `nil` for any not-found key. REDIS.mget(*keys) end end ``` This loader could be used in GraphQL like this: ```ruby some_string = dataloader.with(Sources::RedisString).load("some_key") ``` Calls to `.load(key)` will be batched, and when `GraphQL::Dataloader` can't go any further, it will dispatch a call to `def fetch(keys)` above. ## Example: Loading ActiveRecord Objects by ID To fetch ActiveRecord objects by ID, the source should also accept the _model class_ as a batching parameter. For example: ```ruby # app/graphql/sources/active_record_object.rb class Sources::ActiveRecordObject < GraphQL::Dataloader::Source def initialize(model_class) @model_class = model_class end def fetch(ids) records = @model_class.where(id: ids) # return a list with `nil` for any ID that wasn't found ids.map { |id| records.find { |r| r.id == id.to_i } } end end ``` This source could be used for any `model_class`, for example: ```ruby author = dataloader.with(Sources::ActiveRecordObject, ::User).load(1) post = dataloader.with(Sources::ActiveRecordObject, ::Post).load(1) ``` ## Example: Batched Calculations Besides fetching objects, Sources can return values from batched calculations. For example, a system could batch up checks for who a user follows: ```ruby # for a given user, batch checks to see whether this user follows another user. # (The default `user.followings.where(followed_user_id: followed).exists?` would cause N+1 queries.) class Sources::UserFollowingExists < GraphQL::Dataloader::Source def initialize(user) @user = user end def fetch(handles) # Prepare a `SELECT id FROM users WHERE handle IN(...)` statement user_ids = ::User.where(handle: handles).select(:id) # And use it to filter this user's followings: followings = @user.followings.where(followed_user_id: user_ids) # Now, for followings that _actually_ hit a user, get the handles for those users: followed_users = ::User.where(id: followings.select(:followed_user_id)) # Finally, return a result set, with one entry (true or false) for each of the given `handles` handles.map { |h| !!followed_users.find { |u| u.handle == h }} end end ``` It could be used like this: ```ruby is_following = dataloader.with(Sources::UserFollowingExists, context[:viewer]).load(handle) ``` After all requests were batched, `#fetch` will return a Boolean result to `is_following`. ## Example: Loading in a background thread Inside `Source#fetch(keys)`, you can call `dataloader.yield` to return control to the Dataloader. This way, it will proceed loading other Sources (if there are any), then return the source that yielded. A simple example, spinning up a new Thread: ```ruby def fetch(keys) # spin up some work in a background thread thread = Thread.new { fetch_external_data(keys) } # return control to the dataloader dataloader.yield # at this point, # the dataloader has tried everything else and come back to this source, # so block if necessary: thread.value end ``` See the {% internal_link "parallelism guide", "/dataloader/parallelism" %} for details about this approach. ## Filling the Dataloader Cache If you load records from the database, you can use them to populate a source's cache by using {{ "Dataloader::Source#merge" | api_doc }}. For example: ```ruby # Build a `{ key => value }` map to populate the cache comments_by_id = post.comments.each_with_object({}) { |comment, hash| hash[comment.id] = comment } # Merge the map into the source's cache dataloader.with(Sources::ActiveRecordObject, Comment).merge(comments_by_id) ``` After that, any calls to `.load(id)` will use those already-loaded records if they're available. ## De-duplicating equivalent objects Sometimes, _different_ objects in the application should load the same object from `fetch`. You can customize this behavior by implementing `def result_key_for(key)` in your application. For example, to map records from your ORM to their database ID: ```ruby # Load the `created_by` person for a record from our database class CreatedBySource < GraphQL::Dataloader::Source def result_key_for(key) key.id # Use the record's ID to deduplicate different `.load` calls end # Fetch a `person` for each of `records`, based on their created_by_id def fetch(records) PersonService.find_each(records.map(&:created_by_id)) end end ``` In this case, `records` will include the _first_ object for each unique `record.id` -- subsequent records with the same `.id` will be assumed to be duplicates. Under the hood, the `Source` will cache the result based on the record's `id`. Alternatively, you could use this to make the `Source` retain each incoming object, even when they would _otherwise_ be treated as duplicates. (This would come in handy when you need `def fetch` to mutate each object). For example, to treat _every_ incoming object as distinct: ```ruby def result_key_for(record) record.object_id # even if the records are equivalent, handle each distinct Ruby object separately end ``` graphql-ruby-2.2.17/guides/dataloader/testing.md000066400000000000000000000054351476434635200216240ustar00rootroot00000000000000--- layout: guide search: true section: Dataloader title: Testing desc: Tips for testing Dataloader implementation index: 4 --- There are a few techniques for testing your {{ "GraphQL::Dataloader" | api_doc }} setup. ## Integration Tests One important feature of `Dataloader` is how it manages database access while GraphQL runs queries. You can test that by listening for database queries while running queries, for example, with ActiveRecord: ```ruby def test_active_record_queries_are_batched_and_cached # set up a listener function database_queries = 0 callback = lambda {|_name, _started, _finished, _unique_id, _payload| database_queries += 1 } query_str = <<-GRAPHQL { a1: author(id: 1) { name } a2: author(id: 2) { name } b1: book(id: 1) { author { name } } b2: book(id: 2) { author { name } } } GRAPHQL # Run the query with the listener ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do MySchema.execute(query_str) end # One query for authors, one query for books assert_equal 2, database_queries end ``` You could also make specific assertions on the queries that are run (see the [`sql.active_record` docs](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#active-record)). For other frameworks and databases, check your ORM or library for instrumentation options. ## Testing Dataloader Sources You can also test `Dataloader` behavior outside of GraphQL using {{ "GraphQL::Dataloader.with_dataloading" | api_doc }}. For example, let's if you have a `Sources::ActiveRecord` source defined like so: ```ruby module Sources class User < GraphQL::Dataloader::Source def fetch(ids) records = User.where(id: ids) # return a list with `nil` for any ID that wasn't found, so the shape matches ids.map { |id| records.find { |r| r.id == id.to_i } } end end end ``` You can test it like so: ```ruby def test_it_fetches_objects_by_id user_1, user_2, user_3 = 3.times.map { User.create! } database_queries = 0 callback = lambda {|_name, _started, _finished, _unique_id, _payload| database_queries += 1 } ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do GraphQL::Dataloader.with_dataloading do |dataloader| req1 = dataloader.with(Sources::ActiveRecord).request(user_1.id) req2 = dataloader.with(Sources::ActiveRecord).request(user_2.id) req3 = dataloader.with(Sources::ActiveRecord).request(user_3.id) req4 = dataloader.with(Sources::ActiveRecord).request(-1) # Validate source's matching up of records expect(req1.load).to eq(user_1) expect(req2.load).to eq(user_2) expect(req3.load).to eq(user_3) expect(req4.load).to be_nil end end assert_equal 1, database_queries, "All users were looked up at once" end ``` graphql-ruby-2.2.17/guides/defer/000077500000000000000000000000001476434635200166035ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/defer/defer-graphiql-gif.gif000066400000000000000000002403171476434635200227360ustar00rootroot00000000000000GIF89aw! NETSCAPE2.0!,&5!*;7771D7I:L=P$-A%0B&4H+5E+3J(8L.6R+=T1:L4FX>HY/D`1Gb2Id3Kh`JMNPRTVX\\@a@dBhDkGpIsJuKyN}^`dnfphrkunxq{t~aaeegghgkjmkpnrpvtxu}y|  ORSUvx{}VWY[^``bdeghikmnp8_8`:c>j?kEpCrK{FwFxGyH|J}}KPMOPS^Vb|l뇂̕ܙ蜷H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0FI3͛8sɳϟ@ JѣHҬӧPJJիXj-t֯`ÊKٳhӪUu۷pʝKݻ*ӫ_Ͼ˟Oo(h&iğO F(VhfeB82ˈ$8K+1x,0(4*wч0##XEG5iH&L6IE84, GNf\v`rWQM#@Vp)tHM)|AFڠW硈&jݝmA4CoVjPfM'A6E^j:qꪬ&ѧШ&(Ry+6 J (a  bAN+dD +8ۮJZQnkFo  uk,JP'$0D9D0w DS)( ,'oC#4C3-+0"<' -ZFB:6:]6pKETA7;`ت -hsGnZ 76,$0wPȐ,a,و+*B۴͵6nV006-D0޴33A]7Dᬣ2Cs} s i/0l^0 l/` @ ˱ `6+mL=h:Cn~0ڱԖ1,}XAm㌓͢iCH=D$D}Ӛey}  r6+`s 7W4ͥmsl p6=@  .k;)\BH*RQxEDE p92m~ F@[ݢF1Np @ ~ HD!:H\Š$$/Q.ZRA4$ X M9a69Ңj̏I5[]K4W͗uJg̚ꪻڙl]w)< wY"NP);EWdOyW<(&y{U~y>q=z $}Kzԫ}^O~۴5tH|_B %#P= _5-9H aFo0H z18 a7P|i~ ̧2V`:W@&0z ` w)8 0 x;qYd gwȠ P ЀФYL(5cC6o!K &0 #h!2 _ P G03@Gf, FpGk؆a1299VVb ` 0 Ld}Md$A bHC- 4(`0 H-!h1Xx,È91S4w31 vp P L7*R~1d' Lhȁ aGE He8qE@QX_ Y6TvP p v PX>/X J8|d 2Pb aX^8Iq=! 0H7L4 0 `0 %MefieFw!P&)B*+9 `U~1yX+E. 񐽀 Ee}y\0ه АwT9V)E0 4Kxgo.;8MY(U`V8x)'203`2)h) O \O$I ` EXy{>0 f-fD9  N9 x落a x j)I8 UC䇞7%$.9 fIe$4* @D@D5 2 }!e0eP3  @*չ̙GT:GJIl(]2Pohʚi9o3%Ap+V&@ ,0$H9`0(WHB)* I H˜N ꇕ: fiڪz:rUIA&vb*wZ&80oc%[ڬm7>`! 11 zg`~b"r: Z=[Y0 `& $lZjKAkJ1}Bg{ [za:8D1 qkX'Z۱,- A*[Ѳ8 H 1l9]¬6[9[4?SfJ(η !}3a}qh :Xb9+ д3r~>0;'~jz qɩiejp0kqqApx`x b {d#;:[W Cf|`|)[18Qwpu@ 1w``{۲Ks#7pvY](M_Ag蟶nr8k*CʇaAs0t0 Kv0AQpk&B+4p~JpxJ Ny88kZ+芪mr0?w`l0HJ<n@ bj˹\ ("P,f鸎1=1h ()kH * {sp v@@\jF,{t@[+,pD<qfbU*ce<[f/Q^ A9܂G  ! ؙj`Kv@,L]Lɖ<\,ēl#5U@yBq괽l @Af ڍ}G00T"z*MzR)Jüة8axpOLw0hvVun>`1;@֝f|$q !r@w0a &*Jj諾Ե3=bh+=b_I6dU|WF,)ӫh*KF7W"+=.ni~=&9\rvO먮2v~,O>]unj,$g+{_Y.9MhݒRAf5)o-$%Fn9τ;Q}W}ׇZ˵^ ) 'g=zz y; H鈫|;d6_ pAb+/j x  e*1(nX[`.[Ǜ9`d(&F{ّk3~X\8 Q (@[q8e  A`~+,-,V,xH7 6DPA .4 D*qp A0?dcOr(A/# %t܂0$Ǒ?A$M +iO@ 3ͨPTPEJ@H`Fx%N92^p)!{ꅋ Re S*54Oɠʄ`J12LXBSYc vZjVAv[n[p"k7 1Pw+HRK4$̠Ri$Q_{xU8W;@o `C9Ҹc۴\SVyeuecyfÔ G$QA"q! Z!( J&zNٲ{z`Z&i v)5K_8:0Sb5H̅~d{kko|pnFeIQid @!D‘H6f0n2Bĸ2JA L]t5x!H7ݸ@I z?U2f;"#*42b8>H0|Wpw}5s aQ@ġB $Ɂ2b #b0H `2I*/t$ $ 4/xgI "B.xQ\!xOUp(4̒Aа;a{!RI}Kdb?(FQSa旳9 f֖/v&gP;$%+1@r7Zc*Q{QҪ 0HD&Rlc#HHj "-yILfRd'=IP6h}&MyJTde+]ʽZTe-myK\e/)j2$f1>_&SY:3@Ӥf5Y#ffS|0%kS%7yNtɛ'9Nx*2g=!Ni3g?uiOTs3!"v*(f<q9ނRH4 ?Qf-@*$oYU\jj04I4OTpV 1C 8a XQDzUҧZDp@xTHl`[l֬b s݂ZǸ) R )ĩ!YUĮ,e6zd &B [$/Hi$^O&0 +wmdB HতNz Ƞjv H`1!=H*+t`{]v,Z&+@ PȜB.rDj  I~㨷%/yBWܤMrm]^^(FAR#@E*T V`$!,H!Su-ܙfXßn{^ >KOG$BRk`c6Mo~ĸHGRH GQNei<9Ozg>{SH@f6e0tY"ЃԀ W AY#eu;hTdIя{$!.k'qA|,= ϲTTÃXX%KMaәBvM+ v{S|'YիߘT, :p3BkM\թ^|#"Ɇ*U ImdGkDWJ,֕LgYbx>}m'kCujt;prz$֖[BŻ&$N W(Pa +C)[:p4]oK;" #=T;{@C&#|#C Ԉ2:456z829Bw 9H;cA@ɘy9BJA,(t \B&\ DA)9#4(B;+& C1 '.B4LkBuC83\g: 5C=,t7C@29CC<#@lD1?@DDJ EBGDDJDP*NDS@NdB EVlŃ`ó[8EZ TİUtE]4/E!´EEb\[\EeL B(+,fTbt;YL\RcҨeTjtATFx3mCI *Ё ,: jr,))*҈c#R*r<tܤ`@zG̰yz| A9Aǹ`a"++ȫȾz4Ȳ(,T=$|ǒJ:aƳ +9H,*I^#ΣJ-bѢ/-JhKFK-8"**<.oC2.rJ: 1(;[ɼI̫JЁ+/ࢲ\* b/p/;?//8 [( !{0a5L,@ "̲;L)ϴƔlH>A@8HpDX{Iб11@ BS|Ka+?$6!60y+8,cٲ錧ЏNP+y3(=hE}KE}{ BςQJ zsڒ.qNS4NRSSPrP+(e$4 uٜEyyLa6X bی=T<_;Ӄ#h$9nS.Tq8r}RE,] OT*XU!Ӄ(}~񩀋<34;K8<PӇ;0?Ÿy=p#8DMEUnjTNx 9,=P1 ;yϢMh]hPs8AҭLn;[UxUv&^--xiDǁFp&VyC\]]4]#=e]m׽(]h_8H ܩOpH^/Hȥj\*p^_ ^ɩ$LBp>)X-6<+<ވ-ʣ 0``c`" i32( TKKET-Kת:H,ˈƒ4ԃxxL!֤"c_QAʹ*=$D:!͓/ldS/D$p*Ķ DNl ;$=^?9b{=;៩\e ޣ Ӄp@G2X56y8mFSeS!]=`W%n?uhh׷Kk&N ׾BC&8].;?[c{<ʳ3<-MX+Yك;Y=E m톌Ɩv*u>XqE >KZ CsM.@!xH!?꿰c([$:@Dn ўﮖ}dhp t ܄ܲ@7S qnX\qK腐Jͤq p/;= ? oX`"r#r9ὦ6vT''7:^?r1+gwR.O0'/_s8$2g,U5W!/8sYj:7;tRqSTuҨQEu߬u'u@ } - Ψ;vaq$KOtvZz}-`0^HMaVJ VJv8s^u.frmpb*p+w0ɂh>x&~Jx'h+J`b}B7Àu&Mm|й]*nKLKU0+T3P4L".759~:ȒGB@9Ĕvqy>A@Ei׬M/H=J Ki+td0 à z_*ŭzn')*+ErͨԀ1^fO+fk 4{gNe({2گP-go+||| DY6FBdԝ3Ŗ~<{_sTI^4L4'5&_(G"+ OB$|Wx(h „  hI0xpA/yW/_@*r1#BLh`㖃=yS*!<Љ#@xcT;s޹sQΩjǒ-k,ڴjײmm;ҥ{c܅ /.l0Ċ3n1Ȓ'Sl2̘F9/hC'`+ B/t1HbxPWFmD0LQe!q}3A\z*W*Qr(}*inƻas5;Á5ӯo_? 8 řgݧh-pEQq^1 %I! ECe%`B#!P>4qŵPr'c (vuPS]1BF*{ :$QJ9EZy%Yj%]z) RiQpE1p 2qD$+(t&f} 3''1 bWО}ҋѥ/dLZPzP AAr!C;.tid O y*Gt@aa0&*,_:,J;-gVy`hy;8"ph",8% aP eF /Bk/(H }B{pA^H%m@xtlwhaB,,&93kY3A =4E󅭂;jQ6=߉pE u[BFvqPBm=}6i#d4m6q7t4y}[~9kJ7N9衋>:o~:ꩫnx:]:>;~;뮸Y & ?<Ӿ;+|~K?=f3=k/B_=?>~ۛ>wl>>z3έ?'0h[XJ,~r M )+ C(-PY d z@II D-/ pj)X ~Ag!Z9 X2̃V-:Znp+ VP}2 ""QCcJ:=nS# N*rĄ*uҠ(Z+~#8.Rtƴ !ꃂ%D(0Alk z bJf@4R"a %B EK.Лi @Y@e A PU$:5yu}6y ʂaAJ$,aSCV&a*(H)a&LcUX2؛DmjXR† ]+IMƔ@)ҩS*>"ܳ cDDOP#^X<%E+Hzr 9B/R ajV#Sn)Q/Ks6jUAn U i"Ъ& x0 p!x)frA+Кecڃ"(h@? ʤ# IAFҋ7:bK`_ O 2He(?BRfJH* z|,b!/ˋ8yP 4CJb /0#`y7ot$Lׂ\G` E< 1e0)fOӬfսJE z ehE*H"|,r,,3jQ/`G:2i; I=̞͚C-j˵Io. T Nr@@@DjY'L PB GZRXl 2EE Ub`%+Z W՗}ma X42unvҩ+TP"k{CHD#:b^׽x~+`3xrB%F1X c6<2dtxnAbvsj *oMm6t-9΅[T@ [UZֶR|-lc+Kj.uuVOo:x=KYW1®ǯn;'&ϥv;7O^.?|d^#/(>[<3sx͓<;_y4S/ȯ|=s﵏u/ۼO3Ё~B&X Z$$~͏}'5l <}z%z RL2rEHDABJd-wT y-`uE @iļYZED48#(~0NŃ)D!!%#1]`9YV|R)A<# DR, 1A_o.D/i 1 2=B0Ӡ1THXQ5a6 8iE89 -@ QZA}蓹ƅ@Bk@D%%1quD EqWJb!HIUwTJ٢MLw 1Y!)A\REREǝ!@S YAWW XYHpZiU8#+"\`A^y__eYca%bV8dIeY1z1.$z%# Y0=@kK$@A ՚klqnaU/VB o%:DH @I:r)18QTLquySAw@x%d1$SrCD\I4b$B$@E#D+ё% ptKX6XhZ,zz]6f*HjA~*i*>ި*j{jƪ**ʪ]*YPY+ޱ>XkVNA+bΏRPYN+lkEP+k@j績2_B+ͫMEFN$ż'F, ,M*lZ|JMŖ%~ޤ&"bd6,Zh֬ژ,ZŦ"DpJ̞'@mbnʮDԢŎ^m.֒Pӊ^gNYa-֮-זةM[LؖlP-ZήM&ƝOg}BHnB^".~.,kZ(k綮ց.vQzO D*_fh.}[>&oe >*J B/zn/cTf/&-/bx/aצ/*+ʯƯF/ӎ>./0.o70!p/:0/ӯooz]0+10yfp / ' ǰ嵰 O 000?$o Ko1hO7+ Ol+oo1n'_:0- 1k.q31 î 22"".#7r?F$0%+%_ grn2''-(2ۖ)S0**r־2,,k--ײ.//+/2s311,2'3.63?/44OVs^366S,7ws~8s.9 93;;+<3γֳ=_->>s3@׬@c+A4&BG,C?C?FN4EE+FgnvGW+HH4t4JJ*K4ƴL*MM44OO*PuQ(RgR/5n65>5T+T?*UW51^fV W˩W555YY(Zu{[\\n5^^&_5ub`ga#a&6S.6ccc3$dGM6eqfeC4fcew}6hk6hh*ich6 klǶiw6NvʶnOom?07q ror/75s6tts_evS6wowv7y77z'zywf7}_}~w:7Z"^x$%&s?RFM8Kgl8 8 )Ӟz8u8ÏǸ̸ո^/鸐Ýy%9 9k?y]'dݒoty}9sdՑ9׎_5\999𹷠\',5OWIOwp:ׅyO,㴺s67##!z: _v"/5{nK{wS{C>2Fwmp;븛٬;{;wV;{;廾g9Ce~b/>~GOW>_>dO>wO+~x+s+⯾=>:>>>g ??k',6>G3Ck?c_v~?Ǿ#9#'??? 4xaB 6LbD)VxcF9vdH#I4yeJ+Ytҡh3iƴyE;yhPC5ziRK }jTSVzkV[)ڤY)Naɖ5{mZkٶukk\sֵ{oެ^F{kp_ 6|qz7vrdɓ1j8b͛9w4QʣI6}uꈖvlٳiwnݻyz&抶7~ym˙7w1p_'~{vIw|x҅w~}zw~g~s; < |N -Ü ܐ=0#м M<E?\P0Ssutƿd Tl"<q&|-$JtI(ܒK0,<46-&˛H:7ܓO:kN.<Q1dB}D%EjL5͏N=eR0ݔRT 5QM}VTXXuZ}[˕b dMX\=h\jJn'pkg=ݢ]ݺ%twvwo#~ ־߄k6I]|;x-[EyOUIx)WyWmfdgSo᜹+碡i|Fjg.4]Nj3{ٮ%:%_ mƳg}{Fo/:>|һ1DC8n-2'/|?Egs<uF_{:=cv)] vuދw>tߍwx'yՏoʽꑅ gxp_/W-88}_߿c>c~w~Ż!?z  8>p>R@ \N ~phan8p Q#Ԡ#PaSXCİ0Y><U9Td"fX"Y@  ;xEx-W f`#,=/Wac^Xh1!;E ؈;.^ $A C<7:#$BX1!PcjH=2<%IC$ فgQ Z(KYKpM.hK_$%/_&t-:C<"$<_1iLd^%j8\M59=l$B"&:9e6d5*aMiS5a= mgLc)P^!D(ST$CurF'\|V‡>(<;y4Y|@ykKESBj)OS '$*SfK dSaԋ UjHJfU~[iWUzQ%@zVkmYzDTmJмu)k=q,LĢsl.{t,6/ RvJ?siZtk#[َtIlkoXᒒq[,Vc.%]Nwյ`;HnseXVry-^eoΗ]}?ne_%3p. v j\W(a 7#|Z! h3?8(,b0@ }bOqz(gHf@2z@r"X@eXDSo|Xܲ@@Xb/h>a-|>}\g3$jԨBJ1]Bfk@)'Jot7Iӻ MsNuWMQ֧A\k+#=@znyM<_lVهis-l j&u8Ov:W72ܓu/ H2A/ζ7f\X౏<)^q_9q!I^r)WYr\ +yV. t&zڌ~t|]:nOUձ6oRzןqe7;Оva3uGwόUx 3|->rw 9O>aG1/zˋ~C;p( ,@ 0?A^zmh,@!%=Bb`9p,/ >A# |{ %eq,ܐdhA/ ~O} rN_}j`  pj` "pD@P o `` j O)o /Пtpo b`p4P0 É=b Xa Ih@X|JʛJ MD =^X  4pX M* p } ;nvpk.fBdA  lP`!DQf D `|@``@PԊbdN0֨`Pp!H`5#=q@Q u0 Bfc @DΨ0\}Pڬ-J PD`@Hqb11@QhB͐|ı0O0)f!$O#P }HП$O T%!sj $ff \r pD8r 0v Dr |Lo`n@#K(ά,m`h|P`0'ǒ,(2-(q ){N)RbRV*Rb!H1lp@ C0 K ۃP dh@4/(M5@5(Gqreo P-SKQ \ ,Pb 5/ |`[ /2g` )_! ڐ^gR$5\%:T0=b$;E>AM3Ml&@s)Ey*HOEIJIJUMP[PTaX[d]`~cmzzz/O9Z8_9`;e?kAj@lAnGuBqEuGxJ}I|KMOPQR]gh}"4;ABA;4"447B7FE\\QPCȋ[B[OYNYMVI8\Sd .JX&.T>!ćd>4TԸb$\, B+PPRK*j BUOOT ,ְrI!#%H~؀U.-X WG"\Ҁ)In^\` jRF>\T)bIFHLL˕%zUInJ@:s*DQ(TppaL$qZ(P`ؙ/n=!,+:  !)+%5(8%, /'* ):6$+;&.3/1<15???F.C -A&0B*3A6:D;>Q"4\%:T0=b$;E>Al&@s)Ey*Hy:RJIJUMPQQQ[PTZZZaX[d]`zzz/O9Z8_9`;e?k@lAnBqEuGxHzI|KMOPPR]@*DQQPHA6QJ?3(QE%?9QK26@<<<LO4CQFNKK/",QEOL4?QIވLQ!0<INQQ)2=$BQ-Q5?phD41ȇ G \\TPH> J$G8z{n 6os+8`p3ǠH2@Pi ,HJ5邫S#\!, ..  #(<* .'*!*;3$*:%-2/0?*2;15?6:???8v:{A,B -J 1N/;C;>T#6Y!7X#8b$@h'@n(Cx,Jp8NMGJJJJTLOQQQYPT\YZc]_c^`ntzzzADVX=` lpqloqty{,|D]nUpvsyx~VY\{be6U6Y8^bA9< #0sv2p;t;*9t$dq'E;'Hw$M28,WVM̎31ZcbpK#EwkJ;hQfEKUWq[œQOHEVeUG{; [ʔ8&$pCpJL"BdZaOy! *Q~\-8y(A%钞&%-||[Rq%w"p(j>mx$LQ1ӈ=IaԘp!mػ‡ d08E$Q*0BȇOSa@=-<![X[5TR 14Hn(R ,793P#$s┓bDCMl"y@Ms'Fl3EKyR0@ (d@bVH1Ƞ*fpb,8@/0A 7 "s1'Pæ8P 2DtT) >!QYG,Z!,#"mM  %, !*;6$+0//;&.3/1<15???8v:{&.@+5E.7H2;L6>P6@R9BT>FX?HYF.C -A&0B*3A6:D;>Q"4\%:b$;E>Al&@s)Ey*HJIJAK\UMPQQQ[PTYYYDM`FPaKSfLVhOYkPZlU^qWasYbu]fy^h{aX[d]`bk~ntzzzADVXlqmqloqty{.}L]BayWdmfphrlvnxpvu{qzx~[\be/O9Z8_9`;d@lAnBpEuGxHzI|VKMUnOPPR}ϮйĽhQDH4l(\ȰÅl4Bp3jܘ`Ǐ CIɓ(S\ɲ˗0cʜI@%N>ɳ!'pѣH*]ʴSJ5vׯ`Ê;gճ<1u#ٷpʝUNx!RSmF LI (ɲoQ#K4 bi0)#ϠCx2Z6J(SJ Ǭ` _ R0?4`( pRxLB\ @ @~"`>Ƚ S ABa7 !5p@,^})L"0/ GEo6@^-v/H> La HLFo>Hh0(@r,?*ĀO *`CAkAgL@ 17NAP  l0ҍ0HlJUF;!,FHfQ ^Y/`%QjdHPFGRR>F*L?V2̲c`@d63G擋\V`zi8?t+ ɾ|#9 P% ,#YF! /fMә&T9ÔeDdZ96+ʭ,qG7z0'!) \qgD r, 9R5{dT .T3ͮN1}+n׭321ױKAJemQ;elPѱ\)DO;7b5mh? !,vM7,6F.7H1:K6>P7@R9BT>GX?HZAJ\FPaISdMVhNXjQZmU^pV`rYbu\fx^h{aj}enfphrlvnxqz dihlp,tmx|`P@HC@+X2 sV@)&ZhR,R/P)-Di"@4tb2Pp0t2 to3O /i3sz2t1d2yt 2 v0 / f, [t )mW & * ϧ ( ٮ|./ p!,"r}&* -$3(8 ):777+5E-6H.8I1:K6>P7@R9BT>FX?HYAJ\DM`EPaKSfLUhOYkQYkX^jV`rYbu\fx^h{`j}joznt opmqz{-|S_R_dmfpirlvnxpvtyqzx~HbVY\{beYZZWήйD5$$"(:;<;:("7CJ$2;2IƂѫ6A9<9A1ǝN !M/GGpҥ#. ǰÇAEPAD!2 CLP$t`Ĩ & IP 1pe$JDyѣM9)DJJRM >ZׯȪT`ӪU+,hʝm%PW/%*KL]7Y#KS񁉇n\֩'A!=6{xׂ8hT~]aQj}h"M!jH'RnȠ4:N=( PBVJ:K>9PQhTCG-ie9$BP%H|d:3!#ÌPϘeƙ 1XC6kr 1$矪 0PL6:&Cp',B%'\BBIAyȩꪨĢ*+5Z 5 ~:뱨LDeQ0A.쵷T,b-(܆~"ŖK.VזbWZ472*1Cf|0+0,\ǥl J._ (3<9ǹs(, \2 sJ. A0d@5!Ё#smC,4(5 oL ՠ`w(\5 F54ܟ88(ؽGx0O0)Op8,4 +^J + rޙ{m0P V6饷};~v,\@u>솟b;O+M|{cy\N'V.J [o c.׏)(6>pͯpS Vne>i[B P6@R9BT>FX?HYBK]DM`FPaKSfMVhOYkQZlX^jU^qW`sYbu]fy^h{bk~jozdnfpislvnxqz@pH,ȤrL8ШtJZجvzxe4n|N<}zMtC uxogW %%! rGBɶ##q{## ڗ % sC#!% #D H.g, pNj ȑLx ˆ FXv,4vI3˂ 6,dhZr! aAfͣH,VYxJdbnJʵo#vBZl:4p9V +SSDb}{ Lp/ bi*9~t` JH3o[)RH ,XkdyZ_Q[Uk\jmvҁm?@{-UrĨ/Kϩ^"+6" Љ'Ooꥑ['"!|Àqf@UtF(߈0k_B aUZJ(b|CA4cU1xPf@^1|UW~oL_:`K̇oDp^ \,Vy_Q-i@_F"Õ V1fc ]ZtF' @m#${Rd :fqH(6fJEh饳h*j 0ޢި&@Iʪ2 ~b+ KNګ7xɊ ](ƳF攰Ab6`+nFV!/ŽS';ap #pk 'K<);|kq!<2%{2),(,X'-r54,y䜯,)}U1,tDxL.ʹ)(UUU ,uMLdyJoݪ,CiGmق"Zۡ❷޶^ͷ}#9 h}%ní9Η/N5_ 6Y:[=[:DsԏϮ'a٠뾻)B>Qo9 Ӽ<#2-yk}>>;k>[??|x?@ +@  38 r0: pB(Bא9! ¥. cҰ&B7=P <0)u#(Iap'JBg+N#H"źP-@`<F2Iֈ6BȀ;5ov d>l [: !";5" A&*N&Rn@\$(yMjFT%;By)YrE*gKmB {R&f"YLf d)9Y)}*P9ͯ|'B=AЅ}eB#jRZ$,Fe(H)ґ&(JS:ЕԠ})I%(SDpZ)NҝA>PZ C=*Ta`jSTժDjPSԫ8kM*SԬ,EkJjRԭ kGQ^ԮkDP.ԯlA+PӰDl>kOӱl;%NʞӲl85Mnӳ-3EKLҴD,UJ~ҵ"eHѶm-uEZѷOCи4Dn Bй(n +B~кnkANлoś@мDoԛ?Ͻo>Ͼo=VϿ;Cp|9Cp$ 8 sð45KKD3)CqT2wqd17)T~>̫O=rNdcnj e>b2z-wA"Ӳ*1;fMVf3_ov 69e=:.BBLg ˊe*SZ͏reʝ6/]I fUGjQZSXF}j*`+j}[ @un _:ž '[+gGan-lg-rS;~ͭ퓥[>qk\N T   Wz&@ pt+@Vo 0*83.pXݍo(LkoD 𕏬F( !, Loc!*;&.@+5E-6H2;L6?P7@R9AS>FX?HZBK]FPaKSfMViNYkQZmU^pWasYbv]fy^hzbl~ntdmfpirlvoxpvqz@pH,j:ШXآ`zxL.znp0'rnvYpx  } l İXЉ ^    t`:y GCO"JfI# CBAW Ȓȗ0P‚mZ<f s5AA5;ɴ))\]FRXRpU:p&J4;L=S:ʝ ]ՠG*[7J!h! B;Tn1\"R6<8X' ױ|6ͻ`uA ǖr@AE>@ɺuf@ ϣh81wMw"4]a#*h`u0PEHw,߂C!}9$qhPXPP ,q@y@eaHu!ƹz)ګ,+ * *%:|/J'[ *k {1ъ<2ǚ %)1 ͺ9ϮztqDm@:1ГvjqGG;2jGQE|k>lX;Hs]hW^ 'CEba\nu;b)xmR658?ָb(o[e9k9Z9yw$ũ7 m A7w/\>J -b=&dOc??/x??@ p),P QAL| {s  C.&<\R²Zy! }6t!s !Yd(Ę/QJP 0Q QI|D\#IJ|X=!`<ڡ1ZھF<yTX:&#ǍG9zn<䙂)G9n$'gvD@& IN꣍cVidzS &c)yYvv4&K:",Bwg#zq KkB!6SIyA@syyғR3O]𳟦'@I!ЁB2TZ|h)*Q(Fэ*m(HC ы#=ER})LE*ә6UFsJT >)*T/zH)Rpԥ6O-jT:՟VWiVmՙv_miXU:֓g iZ=֍oh\%:ׇ֕wMh^ ׁg`9{gbݹs6֛fd9YdVefcRvvdh9Z?}cjѸZ0bl8"Vanyֆapa8W=arI6׃`t18 V7m`vv7_x77~m_zӷ^z}^|7շvM]~Gs\7`8ov[Ͷ5jfZ6aUgYaaue&YA6bc'X!ba/&X6c~_7W~)ueq~ 1Y]G%3^N~r\.*7,Rނ,o`]54)'9dj^sO7W-J"Zٙf=ʆ> @=KhVjivjN:jkɺjVk:kkkk:ll l6Vl;mV׶f!,YL7,6F-6H.8I2;L6>P7@R:BT>FX>HYAK\DM`EPaIRdMVhNXjR\nT^pW`sYcv\fy^h{aj}dmfpislvnxqz diAC{p,UaeIPlcX qIZzE cm AJCpX0$#I"hG D VP)I IUG _aH n)~^ g_HoHC QH3G3gA z//J ] h&&O΀.!!-,xD,6F-7I+:K)>Q07H3:I8>K5?PgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AU3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaJTfLVh@]}R\nYcu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpMvOzP|dnfpjtlvnxq{gjemo s v x} y}   ÀʃԆUUWZ]^`bgijpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘1*\ȰÇ#JHŋ3j@)58Iɓ(Sraʗ0cʜIS +jɳOIѣ27XӧPXChԫXa֯`ÞZuسhJTT۷nKn֛9݋KÈ_ޠb%ǐ#KL˘3k̹ϠCM Dmy G0kز5zڲ< agg2<!!>]#gM ?X)C'o>arGt@Gv Gyc0 !!!xPx"xV0[n&!@xd@|yс}0HMaeUQ@"tDDA"*d\q "QqTwfkd$`N&fG# &Ugb1G G Q9"(HCD"Zs|f!(@Gj CF$Ő}'*e,twĪyb:,.PU(sV&A }0 }Ar{aUkI5v+kWDM Q@ 0FMH 9"D+ph}D!}$C$DB!\rFbH(d "9C;tI( A7|I" b  C4 } B$J !Ä!\g\"pdC@TnE&Ep#؈ RM D(&=mI {#B0! ᇚ#B"n \ k I &P0xM|AH ^cE!ؔEDKt '(LvD@Opgv~t` Bpl`( ZckxA'F@=+@((q]O!pBUR dd(6ݥ`s+#bBq!9 Pܣ7HS &8E.(MT-T#PPL'lR"T_ Tσh)'By'π /$ ZIO0ԄA65B(a 7%@PF4:( qە a6@3[D忋t!8PMvDA F x^?"r-@%22B*@BW"fYt!FyǯҚMjWֺlgKۣPa 7-jp+6EBH~ C7+<]15 >\t .ij4h܃Ѐ7݌q˜դ73Br`734|3VBnm1;'La&6q X. K9c 39Jx!ۑAF,UA ppE(ĸ;g$FOBYUc@$p pFϷ` @ \ ,s`eL` :}E(ohB$,aAcʐ&8t<)Ё_D@RG@ _ L : wAP:TTzTM.@pb tA C怇/HGcv#r5wp WX'`-d%( 9ᐆ<ïa Fm%"d"'ݯւjXէNa;oM 8@p%I0wB.)$ WH D?Njyz6Bڰra X_l!<*CxsoԦ35DA(@p$3D \)Pywɞr'ַѣ|*:jUq7*ÒXAC̐pcV'wȭx8j;8 Ws D[pЎu7 u=lNY!Ce vD(u@ B L=+ZQ]jt: Ra? fq'`/ր8Xx( 5`X_h5\!UjA^(ᵂh]iq]%a]1haт:*؃bq@ECGƁKx!<,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!,,jI,6F-7I,:K)>Q07H3;I8>K3>QgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AU3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaJTfLVh@]|R\nYcu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpMvOzP|dnfpislvnxq{gjemo s v x} y}   ÀʃԆUUWZ]^_`befhjjmnpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘H Vf\ȰÇ#JHŋ3J,‡CIɓ$iXY˗0c̛8s܉PΟ@ УHaeҧPaԫX:a% `^AŊسhӪ]˶۷pʝKݻx˷߿ .yc mpD1 P*fX#W)5ugA7 >Tj^&׽+)21 PLE2R d)ͥ`S+"bGE0q! ʇFS0╯ܢ58~3 7C&(K TA$PPJ'Q"|S ~T́|))Bu0)ps $YQ옴A.yJX $$hI05MV1h(AAds).F"#򱊝(HE*DLm(GL4B~E"RÄ0Z b-"EDAMk dH'+p UE_ t I8cE ի@\#Չ >@K< @kbİ!,-9Ԅ[βO!,ͬf7z hGKZ+\jϕu]lc*{Ѷ Wk)`1=F }F$ Ëelc q椛,e+k^"`f5X,h?CD"BLۮZǦJ!](Llo( ¶wX!7M M8F=(pAB8AfIt%Nyhg8w+b1uycȞG p5{%ΰl?2<@S}oA">0k`[*8ЅAu5A&)jxCw<Ąl5! Gm\:%gE,jDZ*FgL#Q1s@ip`8Dc?JN4!;hXWlH${ J>v&UIPR ]~="*?,t2F50}ׂYomk:w6AM%*™9!QahwFOҳ{gC̉Nuf\JKL ES}-+W~G ~ 4'ta9 r׼b?+4P_~+A${~XOϿ_2 D1g{!$x0f G&Qz#(o p*Q`qwq xq FЀ(H@ !@h!1*&wp`X `a0B4107B18*e$ &+by!d&wpupqb0^u-"qRRr c0 Ao&u耥 b!x@sCx!rzPqNQ8\pv wЂCR#/"e!EVpy9q q#$Ea(X(Z`茑hbA$NPBqXxl"G"(#!Xhиь!yvPX Yc'kPq x:gG/x(xaДa@&~'r†y@"Q Q rt'&i衔؍QYB(rm0 *%q)n`Lb옊t) is$Yp]w@Ÿ`JYP,6iH*"d$уq%HB0dG+w*2H@$P,@tq+0R' q9YY~7~z72|˗1{Ǡ1|.a|J-.QZ'!Z%j闢!<,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!,jI,6F-7I,:K)>Q07H3;I8>K2>QgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AU3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp>Yw!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaJTfLVh@]|R\nYbu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpMvOzP|Q~dnfpislvnxq{gjemo s v x} y}   ÀʃԆRTUVYZ]^_`befhjjmnpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘H Xh\ȰÇ#JHŋ3J,CIɓ$k`Y˗0c̛8s܉PΟ@ УHeҧPԫX:eJ `^aسhӪ]˶۷pʝKݻx˷߿ .m D2 P*fX#W*6g8 >Tj^f=nBnHA_jmӦ`DHA @ s/^QӪm٣G92ٛ'z3 9LSxqFf|&P@ftP݃NXCU`QED4[v'{!GuG#D,`C*t[8haG}{ &j}MhF$$hɥ(H%[vԁ%1@Di!&qB:p0QQSVk[r6Z!(`lD`FD&ZvĢ{j9zW(Qj%A }0}@W^jHdu6F+VkfDN@ @ɻA.IKI ]2DˮV8#C$EA\pF* I$]C@ r9T@C";A/޸VI"@!I dTx 4A/׽+*:1 PTE2R d*ͩ`S+"dGG0q! ʇFU@╯ܢ58~3H 7C&(K \A%RP'Q"|S ~T́)*BuP*ps $YS옴A.y$bK` %(hJ05MVޱ)&AQds*.H$򱊝/(HE*Dm(GNDB~E"RÄ0Z!r-"ELAMk H(+p UE_`t I8cE ի\+Չ ?@K< @kbİ!,-9Ԅ[‚O!ˬͬf7z hGKZ+\jϕu]lc*{Ѷ Wk)`q?F }F$ Ëelc q椛,e+k^"`f5X,h?cD#DLۮZǦJ!](Llo( ¶wX!7M N8F=(pAB8AfIt%Nyhg8w+b1uycȞG p5{%ΰl?2<@U}oA">0k`[*8Ѕu5A&*jxCw<l5! Gm\:%gE,jDZ*FQgL#Q1s@ip`XDc?JNT!;hXWlH$Ż J>v&UIPR ]~=,*?,t2F50}ׂYomk:w6AM%:™9"QahwFOҳ{gC̉NuflӞFߞ;pD;ҝٺ~*h{`\{7!`#Sb"WuH'yb:sy/:26;C;@;hJ=`TcM$0+XdrH'U6fH!f??BHfd@ffD`9tiON" sg1B%tB6h:/FCx>ma@VJT`4| P$jSTEٖE?8FFFk8kFd&pGfJ qE#~2QlHu4Gɶlܖa61VI{ITG&nq궃n;vKTo8gLT w MJPqsq 'N9qgodrƄr*w/'PiP0s s]xst%SXGSDQPRQRtHאK7u/eu Z@]waCJ0sTv;vQ SVqZwgb5@z'uwVX3xrEGWPW ay%XGQy aZ\ٕ^`b9dy,VP8Pla2jia&ty,tb)|)y,0+_QYS0~X{9,79v9_CqriBQSnYE@!YiiǚQ!zYyٛ11qY\YBp#ma9Z19!b&^YAɜQױp9B!H@ !j!1&yp`Z a0b1P7bQ*g$1 &+b{!& &7yJIK wd0`w-"qrR e0 aq&7 !z@uz!z qZ^x@T* yVY*вCR#/"g!EVr{ s@n q#$YJ\1U SʤςA$NPBsZzn"GB)#YjxpIz̹Yc'mP3**zi0 /ѭJcc`&@'r{` "q q tt'yފSb(o@ ұ*%s)ppLv) ;x[y@ [p\,I*"f$q% Hb0&G+qw*))jM `ʰpJ,`6+0(@ 뛰;[{Wq'()$+%Ӌ){λKa+!<,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!-,jI,6F-7I,:K)>Q07H4;I8>K2=QgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AV3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp>Yw!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaKTfLVh@]|R\nYbu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpMvOzP|Q~dnfpislvnxq{gjemo s v x} y}   ÀʃԆRTUVYZ\^_`befhjjmnpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘H Xh\ȰÇ#JHŋ3J,CIɓ$k`Y˗0c̛8s܉PΟ@ УHeҧPԫX:eJ `^aسhӪ]˶۷pʝKݻx˷߿ .m D2 P*fX#W*6g8 >Tj^f=nBnHA_jmӦ`DHA @ s/^QӪm٣G92ٛ'z3 9LSxqFf|&P@ftP݃NXCU`QED4[v'{!GuG#D,`C*t[8haG}{ &j}MhF$$hɥ(H%[vԁ%1@Di!&qB:p0QQSVk[r6Z!(`lD`FD&ZvĢ{j9zW(Qj%A }0}@W^jHdu6F+VkfDN@ @ɻA.IKI ]2DˮV8#C$EA\pF* I$]C@ r9T@C";A/޸VI"@!I dTx 4A/׽+*:1 PTE2R d*ͩ`S+"dGG0q! ʇFU@╯ܢ58~3H 7C&(K \A%RP'Q"|S ~T́)*BuP*ps $YS옴A.y$bK` %(hJ05MVޱ)&AQds*.H$򱊝/(HE*Dm(GNDB~E"RÄ0Z!r-"ELAMk H(+p UE_`t I8cE ի\+Չ ?@K< @kbİ!,-9Ԅ[‚O!ˬͬf7z hGKZ+\jϕu]lc*{Ѷ Wk)`q?F }F$ Ëelc q椛,e+k^"`f5X,h?cD#DLۮZǦJ!](Llo( ¶wX!7M N8F=(pAB8AfIt%Nyhg8w+b1uycȞG p5{%ΰl?2<@U}oA">0k`[*8Ѕu5A&*jxCw<l5! Gm\:%gE,jDZ*FQgL#Q1s@ip`XDc?JNT!;hXWlH$Ż J>v&UIPR ]~=,*?,t2F50}ׂYomk:w6AM%:™9"QahwFOҳ{gC̉NuflӞFߞ;pD;ҝٺ~*h{`\{7!`#Sb"WuH'yb:sy/:26;C;@;hJ=`TcM$0+XdrH'U6fH!f??BHfd@ffD`9tiON" sg1B%tB6h:/FCx>ma@VJT`4| P$jSTEٖE?8FFFk8kFd&pGfJ qE#~2QlHu4Gɶlܖa61VI{ITG&nq궃n;vKTo8gLT w MJPqsq 'N9qgodrƄr*',.PfJ3p9ׅ?QAGQ2u4Etu,%NttSRV u uu?T0T3H+jmU0Ulw5U{wV!VwQw}7?kU6 ?'WaxvWzW!N~7XEy ayzxybWhjlٖnprYuZ'w}ay 0in1Vue1]7g{շ67 ]܇};c^4HSd~~[ea]oe'G7 &y8Njia‰aǀ>0b#_bbx1; Xc@8fƒ?5(.!PGAƂqdQl׃Ch;KeFeI?\Vg i@ fedd)gCBahd8ghCVD!Clh$i֟t8DvhX<`8j3&kCG 1F_jtF3XV=W ~&t;(D1fmgz9A6ᆇ g4Pv$oɘoo#GhLEDpՈ=I0FQPf9юJ("GZrԨorXs@'Q ِGXtYŠTSquUS7YaWaڒJek1v7)h|xד9iwiw7oWwx`1[iXUa\i:;[{;aU{6p)3аRXfrJX MI?*1X(+;&۲B2q5+;"/qk/QS[ai(!Y kd:qT{XZ\۵^51qb[iYBp#maj;e1m;!b&^YA|Qױpr;B! O@ !k!1&yp`Z a0b1P7bQ*g$1 &+b{! &y wd0`w-"qrR¹ e0 aq&; !z@u{!zF q{^x@K- yп+ҷCR#/"g!ir{ s`,q#$[4l\Q+0ϢA$NPBs!n*r$tpAދ2b%AA]P\,1ly9 %1&z Qro umm[z}ly‡ y0<8<Ǝ\l"x"'л)"IIz@K'pS_ȫ\,\},d* l(¶o@ *%s)ppLP͐Ʒ:[y@)1[p,B*"f$q%@ĮHb0ɌG+s*:W˾/ Љۻ,+0(` I PR=T]V}ТQJA ѳd{AjDxm*˲q/ ul q|f!c-_mRk[M!;,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!-,jI,6F-7I,:K)>Q07H4;I8>K2=QgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AV3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp>Xv!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaKTfLVh@]|R\nYbu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpLuOzP|Q~dnfpislvnxq{gjemo s v x} y}   ÀʃԆRTUVYZ\^_`bbefghjjmnpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘H Xh\ȰÇ#JHŋ3J,CIɓ$k`Y˗0c̛8s܉PΟ@ УHeҧPԫX:eJ `^aسhӪ]˶۷pʝKݻx˷߿ .m D2 P*fX#W*6g8 >Tj^f=nBnHA_jmӦ`DHA @ s/^QӪm٣G92ٛ'z3 9LSxqFf|&P@ftP݃NXCU`QED4[v'{!GuG#D,`C*t[8haG}{ &j}MhF$$hɥ(H%[vԁ%1@Di!&qB:p0QQSVk[r6Z!(`lD`FD&ZvĢ{j9zW(Qj%A }0}@W^jHdu6F+VkfDN` `ɻA.IkI e2DˮV@$C$EA\pF + $eC< +!+ %E  C, E@ r9T@C";A/޸VI"@!I dTx$4A/+;դB T&ЇL K*;i) "H4!֛HF ქ%XH"j[vg I\ V\t-|@NH}NC ЖE$G'Qr@E Tp 'v ^t_@lk+wYbu|A_*>׽++>1 PXE2R d#+ͩ`S+"dGH0q! ʇFVH╯ܢ58~3H 7C&(K \%RP'Q"|S ~T́B)*Bu`*ps $YT옴A.y,K` %(hJ05MV**AYBds+.I$򱊝o(HE*D m(GOLB~E"RÄ0Z!r-"EPAMk $Hg)+p UE_t I8cE ի\/Չ ?@K< @kbİ!,-9Ԅ[‚O!ˬͬf7z hGKZ+\jϕu]lc*{Ѷ Wk)`?F }F$ Ëelc q椛,e+k^"`f5X,h?s#ELۮZǦJ!](Llo( ¶wX!7M N8F=(pAB8AfIt%Nyhg8w+b1uycȞG p5{%ΰl?2<@V}oA">0k`[*8Ѕu5A&%+jxCw<l5! Gm\:%gE,jDZ*FgL#Q1s@ip`hDc?JNd!;hXWlH$ J>v&UIPR ]~=4*?,t2F50}ׂYomk:w6AM%B™9#QahwFOҳ{gC̉NufӞFߞ;pD;ҝٺ~*+h{`\{7!`#Sb"WuH'yb:sy/:26;C;@;hJ=`TcM $0+XdrH'U6fH!f??BHfd@ffD`9tiON" sg1B%tB6h:/FCx>ma@VJT`4| P$jSTEٖE?8FFFk8kFd&pGfJ qE#~2QlHu4Gɶlܖa61VI{ITG&nq궃n;vKTo8gLT w MJPqsq 'N9qgodrƄr*',.PvJ3p9ׅ?QAGQ2u4Etu,%NttSRV u uu?T0T3 H+jmU0Ulw5U{wV!VwQw}7?kU6 ?'WaxvWzW!N~7XEy ayzxybWhjlٖnprYuZ'w}ay 0in1Vue1]7g{շ67 ]܇};c^4HSd~~[ea]oe'G7 &y8Njia‰aǀ>0b#_bbx1; Xc@8fƒ?5(.!P GAƂqdQl׃Ch;KeFeI?\Vg i@ fedd)gCBahd8ghCVD!Clh$i֟t8DvhX<`8j3&kCG 1F_jtF3XV=W( ~&t;(D1fmgz9A6ᆇ g4Pv$oɘoo#GhLEDpՈ=I0FQPf9юJ("GZrԨqg's@'Q ِGXtYĊTSquUS7 " aWqڒJek1v7)h|xד9iwiw7oWǮǔwX1[iXUa\ija9iAg ۰;wa1Y[w)˒03a](c}k6[r^YJpDk*:y`v7i`)7 6ȘY88)e'#& 1:٩.:4&7V:Vē@=C=2 dYIteB* QGfJQfpؠSg:gJ gi}֡6VV ֆ+=$@dC2ZDwxKOP;E1jn;Dj OktdG:Wzx6ȨF䊵ƨrڛttQ sKoԌJb;DpS!# ~>ǩN:NZJWvrNp ˫Fǐst*utRRʪʑY95tq&yv.:pDi\uwwCj:Bi7WjjUiLW9yW]YN[Ȇ|ȈȊȌȎ,VP8Aa2 :a&,8),௩,+_01Sʴ /1ʥ<"QœxǬ50XQl5l <\11q\iYBp#ma<e1<!b&л^YAMQױp<B!O@ !*m!1&yp`Z a0qўb1P7bQ#*g$1 &+b{!N &yrqs wd0` w-"qrR" e0 paq&_ϥ !z@u-}!Ӯ q8;^x@|׳ y0~-2CR#/"g!ir{$ sؖq#$]\}{M׶ A$NPBs!n*r$tAd2%]P-y׿Mϓ%1&z romPm+]zy ^ ny08>lx"'V^)"z@(M'_p1^5},*m("o@ b*%s)ppL=B߹:[Gy@5[p,!ҢB*"f$qq%ܮHb0OG+*:u-׵ ^r=,_`+0<( n~؞ھ^ļnʸL˳L|>N>ɬ>׬!<,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!,jI,6F-7I-:K)>Q07H4;I8>K2=QgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AV3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp>Xv!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaKTfLVh@]|R\nYbu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpLuOzP|P~dnfpislvnxq{gjemo s v x} y}   ÀʃԆRTUVYZ\^_`bbefghjjmnpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘H Xh\ȰÇ#JHŋ3J,CIɓ$k`Y˗0c̛8s܉PΟ@ УHeҧPԫX:eJ `^aسhӪ]˶۷pʝKݻx˷߿ .m D2 P*fX#W*6g8 >Tj^f=nBnHA_jmӦ`DHA @ s/^QӪm٣G92ٛ'z3 9LSxqFf|&P@ftP݃NXCU`QED4[v'{!GuG#D,`C*t[8haG}{ &j}MhF$$hɥ(H%[vԁ%1@Di!&qB:p0QQSVk[r6Z!(`lD`FD&ZvĢ{j9zW(Qj%A }0}@W^jHdu6F+VkfDN` `ɻA.IkI e2DˮV@$C$EA\pF + $eC< +!+ %E  C, E@ r9T@C";A/޸VI"@!I dTx$4A/+;դB T&ЇL K*;i) "H4!֛HF ქ%XH"j[vg I\ V\t-|@NH}NC ЖE$G'Qr@E Tp 'v ^t_@lk+wYbu|A_*>׽++>1 PXE2R d#+ͩ`S+"dGH0q! ʇFVH╯ܢ58~3H 7C&(K \%RP'Q"|S ~T́B)*Bu`*ps $YT옴A.y,K` %(hJ05MV**AYBds+.I$򱊝o(HE*D m(GOLB~E"RÄ0Z!r-"EPAMk $Hg)+p UE_t I8cE ի\/Չ ?@K< @kbİ!,-9Ԅ[‚O!ˬͬf7z hGKZ+\jϕu]lc*{Ѷ Wk)`?F }F$ Ëelc q椛,e+k^"`f5X,h?s#ELۮZǦJ!](Llo( ¶wX!7M N8F=(pAB8AfIt%Nyhg8w+b1uycȞG p5{%ΰl?2<@V}oA">0k`[*8Ѕu5A&%+jxCw<l5! Gm\:%gE,jDZ*FgL#Q1s@ip`hDc?JNd!;hXWlH$ J>v&UIPR ]~=4*?,t2F50}ׂYomk:w6AM%B™9#QahwFOҳ{gC̉NufӞFߞ;pD;ҝٺ~*+h{`\{7!`#Sb"WuH'yb:sy/:26;C;@;hJ=`TcM $0+XdrH'U6fH!f??BHfd@ffD`9tiON" sg1B%tB6h:/FCx>ma@VJT`4| P$jSTEٖE?8FFFk8kFd&pGfJ qE#~2QlHu4Gɶlܖa61VI{ITG&nq궃n;vKTo8gLT w MJPqsq 'N9qgodrƄr*',.PvJ3p9ׅ?QAGQ2u4Etu,%NttSRV u uu?T0T3 H+jmU0Ulw5U{wV!VwQw}7?kU6 ?'WaxvWzW!N~7XEy ayzxybWhjlٖnprYuZ'w}ay 0in1Vue1]7g{շ67 ]܇};c^4HSd~~[ea]oe'G7 &y8Njia‰aǀ>0b#_bbx1; Xc@8fƒ?5(.!P GAƂqdQl׃Ch;KeFeI?\Vg i@ fedd)gCBahd8ghCVD!Clh$i֟t8DvhX<`8j3&kCG 1F_jtF3XV=W( ~&t;(D1fmgz9A6ᆇ g4Pv$oɘoo#GhLEDpՈ=I0FQPf9юJ("GZrԨqg's@'Q ِGXtYĊTSquUS7 " aWqڒJek1v7)h|xד9iwiw7oWǮǔwX1[iXUa\ija9iAg ۰;wa1Y[w)˒03a](c}k6[r^YJpDk*:y`v7i`)7 6ȘY88)e'#& 1:٩.:4&7V:Vē@=C=2 dYIteB* QGfJQfpؠSg:gJ gi}֡6VV ֆ+=$@dC2ZDwxKOP;E1jn;Dj OktdG:Wzx6ȨF䊵ƨrڛttQ sKoԌJb;DpS!# ~>ǩN:NZJW!s|P jt Y:GRgM (e+٬uњSժj h3 w8IiUzw7檓+QJxsEbbx`QtՕyKH  Ȓ<ɔ\ɖ|ɘɚK[z[ѱAa 1WQAp;lB\]4{ѳصs*}9sUUwV|vR Ab T`C7i`HT0)e̶u cqڙyo3}c<YZø0˟8i+aB TXtl4Ч ^A[S;tfhC+. DËi+WZv(أ $VƤfԈEJkskvHbZH= !8{ 4Je=Dp $qMj o 80/`.VP8P0a2 e,t&)n,+_Q"S0*.!>4,a&8 x=50XQCK^T^V~XZ71q^^iYBp#maf>e1i>!b&`绁^YAxQױpn>B!O@ !n!1yp`Z `a0b1P7bQ*g$1 &+b{!&ypwd0`w-"qrR e0 aq& !z@u~!r q>^px@)y.ВCR#/"g!ir{s q#$1q^0/\,obA$NPBs!n*r$t0AN2"%]PX/-/y5 %1&z roqmm^zpy|/yy0?4lx"'̾)"EEz@N'O__(2,`* o(o@ *%s)ppLL-[y𒧋ExSO>4sw.~|~_02,m@ !;,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!-,jI,6F-7I-:K)>Q07H4;I8>K2=QgZk\avdzh~u`zb&GN&JN(AK&FX%OP%K^(BT$SR"ZU=@L2AV3CZ9CT>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp>Xv!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaKTfLVh@]|R\nYbu\fx^h{b\[ja^nd`ak}qfauic{ne~pflvyp{CcEfFhGkHmJpLtMwOzP|P~dnfpislvnxq{gjemo s v x} y}   ÀʃԆRTUVYZ\^_`bcefghjjmnpuhyk}nopquwxz}~ġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘H Yh\ȰÇ#JHŋ3J,GCIɓ$kdY˗0c̛8s܉PΟ@ УHeҧPԫX:J `^q%سhӪ]˶۷pʝKݻx˷߿ .ʕm DC3 P*fX#*6g8 >Tj^>oBnHaϚ_jn`DHA @ s/^QӮmG93ٟ'z3 C9LSxqFg|&PPgtP݃NXCVdaED4[w'{!GuG#D,`C*t[8hqG}| &j }MxF$$hɥ(H%[w؁%A@Di!&qB:p0QQSVk[r6Z!(pmD`FT&ZGwĢ|j9zX(Qj%A }0}@W^jHdu6F+VkfDNp pɻA.I{I i2DˮVDD$C$ EA\pF"I+ $ %i‰C@ r9T@C";A/޸VI"!I dTx(4A/Up 'v ^t_@lk_+wYbu|A*>׽+@+@1 PZE2R dc+ͩ`S+"dH0q! ʇVL╯ܢ58~3H 7C&(K \&RP'Q"|S ~T́b)*Buh*ps $YGU옴A.y0K` %(hJ05MVD*,A]bds[+.QJ$򱊝(HE*D,m(G4PPB~E"RÄ0!r-"ERAMk DH)+p UE_t I8cE ի \1Չ ?@K< @kbİ!,-9Ԅ[ʂO!ˬͬf7z hGKZ+\jϕu]lc*{Ѷ Wk)` ?F }F$ Ëelc q椛,e+k^"`f5X,h?{$TFLۮZǦJ!](Llo( ¶wX!7M N8F=(pAB8AfIt%Nyhg8w+b1uycȞG p5{%ΰl?20k`[*8Ѕ "u5A&e+jxCw<l5! Gm\:%gE,jDZ*FgL#Q1s@ip`pDc?JNl!;hXWlH$ J>v&UIPR ]~=8*?,t2F50}ׂYomk:w6AM%F™9@#QahwFOҳ{gC̉Nufma@VJT`4} P$jSTEٖE?8FFFk8kFd&pGfJ qE#~2QlHu4Gɶlܖa61VI{ITG&nq궃n;vKTo8gLT w0MJPqsq 'N9qgodrƄr*',.PJ3p9ׅ?QAGQ2u4Etu,%NttSRV u uu?T0T3 H+jmU0Ulw5U{wV!VwQw}7?kU6 ?'WaxvWzW!N~7XEy ayzxybWhjlٖnprYuZ'w}ay 0in1Vue1]7g{շ67 ]܇};c^4HSd~~[ea]oe'G7 &y8Njia‰aǀ>0b#_bbx1; Xc@8fƒ?5(.!Q GAƂqdQl׃Ch;KeFeI?\Vg i@ fedd9gCBahd8ghCVD!Clh$i֟t8DvhX<`8j3&kCG 1F_jtF3XV=W8 ~&t;(D1fmgz9A6ᆇ g4Pv $oɘoo#GhLEDpՈ=I0FQPf9юJ("GZrԨqg's@'Q ِGXtYĊTSquUS7 " aWڒJek1v7)h|xד9iwiw7oWǮǔwX1[iXUa\ija9iAg ۰;wa1Y[w)˒03a](c}k6[r^YJpDk*:y`v7i`)7 6ȘY88)e'#& 1:٩.:4&7V:Vē@=C=2 dYIteB* QGfJQfpؠSg:gJ gi}֡6VV ֆ+=$@dC2ZDwxKOP;E1jn;Dj OktdG:Wzx6ȨF䊵ƨrڛttQ sKoԌJb;DpS!# >ǩN:NZJW!s}P jt Y:GRgM (e+٬uњSժj h3 w8IiUzw7檓+QJxsEbbx`QtՕyKH  Ȓ<ɔ\ɖ|ɘɚK[z[ѱAa 1WQAp;lB\]4{ѳصs*}9sUUwf}vR Ab T`C7i`HT0)e̶u cqڙyo3}c<YZø0˟8i+aB TXtl4Ч ^A[S;tfhC+. DËi+WZv8أ $VƤfԈEJkskvHbZH= !8{ 4Je=Dp $qMj o 80/`^igqͺm\kY10 ܜрj ?tˁv۝3M9F'ЂDf= dXJ^!]K҆hRHmWjqvAɡ bh:n Լ;/jH}iQ5 =^裥@A]jJثmaoR*U[7Gl n=H؉ԦҦȦK ~m Q4z؁JǨK K=lp-xD퍉­q$gr=.(jaupsMQt&NYuR݋T\wsݶvc MjLEr<Ϯ~7aG9g~<~R߀g߆ ^ VXZ\^YW`8!a2da&no,)v_s,y/+_O}O#t-ŋlo PTh_5Qo~g1!${0i H&Q#(r s*Q`˿qzq G0( PA0P+(0A s@,tE `B!E$YIkf`=uA|HN=Eޑig > CG Ӝ| TҥYrSlb:8 z cJy 28A# ! 3@R;G"'4%Yig9ӁI+YC_)׭oz"7A1}/g=Nr a'Rt,|=b|AXn<mp@ zn((HM= B9*7b  4H>*΋HcƲLFdIP>H7n;P=p 5MF,FÁ18=p4̐téh0! 9s/$IN@ -47ДtRԔ;BP>(8X@N&c"8B5JLʡ`u’ %BУ  r `)F% N;#3X;O`ɪtb1-ee̲hZ'` HKJ>2 @BACydK6dSVye[vecyfkfsyg{gzh宰]gPi3]y,hzkYȢPn,6lv,m ֛Ro4pmZ'w+++|s!;,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!-,/.&J!*;&.@,5E-7I-:K)>Q07H3;J8>K1=QfZk\q_avdzh~u`|c&KN(CL&FX%OP%K^(BT$RR!ZU=@L2AV3CZ9BT>FX>HY$Na"Qe!Ui Ym [p7Ia8Mg9Oi:Qk;Tp>Xv!aXBENDGPGIQAJ\JJRPOTSQUXTWYVX]YZDM`FPaKTfLVhOYk@]|R\nU^qYbu\fx^h{b\[ja^nd`ak~ntqfauic{ne~pflvyp{mqz{.}S_R_CcEfFhGkHmJpLuMwNzP|P~dnfpislvnxu{q{Hbqgjelopt v x} y|   ŁʃԆRTUVYgYZ\^_`bcefghjjmnpuhyk}nYZZWblopquwxz}~иġɥͨЪ֮ڱ᷍㹎绐뾑Ĕǖȗ˘W k%#B@ŋ3jȱǏ CIɓ%We&— :Hʛ8sɳO*Wf[ т2kɴӧPz *ѣ/*(ׯ`f*46eXjP۷p$[6bi e+߿qֵK,ޭ+^TlVʸ@urM̠Cv\Za Nͺubv]y۸:>;[oj6s բVMsd&=Im9amË(e_|>&|?gǟw h*QHq p!(ah JSDbV*UD!I.dt"JDQbO6!F+樝hD5FSg$!) CO7Tvğ*=F1.A gPcuJ*T%.]zSg@1dxy'pfO@DtҩxJ1$TdnބJf uM4i[<:Vꫫ&" +PJY+FzdTc8#2g(#JkmHBze'bm[ѸO[.3-.M7o^ )'nD=!FN\DO ; 1J5h.DA7loDpq ҈#.21A ?dtk\4BA#\F0GHI/4JQNq[:d DGG#4E 8@]Y@#yX`Ev}m}"͊3 Qb:`H#0Bw:1b:PT:茠^s^gҪGDD@!|bk-SB$H ^'14~!!BI`VFhIkZ.+)8@/T$X %RHrÅdЈ4fqR N1YumE RIF2r ^@Ƞ}_`C f! 9L!R$ HE+^2ALX"M1)ȈK|bn)EQ"ʷ"!dh#X1a 8ìKn@ !ˢɅHY&[XH^w9% 4㐀O|'r|)Jk$YF,EWdeXh`ЌTġ,$1s)" h7(4F܌4Yf$H6EҼ_#Gpf#$ƗpTj5}2E܌r I| _TDi_hЀ Z-hiCM`;r 3R箠dž)&>q]"}\`ɏ22^@}fĂ϶s(˅nb̳y^D憮^c8qp3"[hpEtW$(Gҩ݌4xG.'50g WvPCN)NWXD0n HF,lլAExSbEp ox`r@ fN 6LPd*GR0c=H)c<&tE/ @PoƤ;i."ȗ/d_[?/PB!)`1@-SqIc\ɨRB!srX(xs h*׀t€h+8HR+R/S HS&T9S=UT@DՂTJuS'f^UUW ,`ehhVl%_MxWzW~V[eXcuf!Y5Y5Y0 z`bZ&1\Gm^nvd"Ar[ev8p\w_s>N]\5]E[U U.F]AacE^uqv_u__&`q` ` m7ajaj^^h0O"Fb&b=](36; ccstdCVddeRFeVedeeBfwxdfff[l˨Jtqva qg(g|g&hqxHx1 ! 6ihiiZj1jYmr65 )6@)kUkk'rf}l]Hl`lkWxl@)mX֒>#_F  Om#n&m 膇Inօ`oooqffh'pFp`p#CBt>COp\  RpHPGq?Ir>qB֑#7[%zIui.tM:$As xsasؙZ['f[GuJfutOuSwtItXt!m^wfv!Av3/vk7g Zg&AHyw}w'xy@y'q'X7y'70x_'zAz!qz4qfz0{q{{Wg|~'iG З|'Ly-ۇZg}G~~gN*aR/#OJ ӥ+Bd"B4yȦBY ?t"VMXsڧzM!F>^z ;3E㨏 *Fc!S!xJ%aT/BzT`hDу? 9aՅHVVmeN(P(H"X8XX5AYU* U=mWl_ŖQ{Ђj(E]QQXz9^g֊V^_`TZ `Fagaкq5&bpAqc6!v8ee0eUveJe`df(q\0 =vg6hGvT i2J#)d%mY)yf,s6sYFkyk8AlD9lGylGPRym 1Y dnEni|~)sIVuyo{i֪)pFm))I 04{9ƙgg+rkr9˚/Gois q?pҙt;_BR'ʉK[* iAl'5 W%QU|wנW'ڟ y g& AJ 'W :{D4{ w}+We4J/zMf}×J֣g@~AS~"1U W—_ g,fk<nrt\qxz~ȷ1q"8S(%o!O:U6Yy& \%qgLHl!qfZج˫![Z!U(ZʮJ{~[:\_hƌ;Z \1j̊%˰aȱa[HcШb X{8&j,@0J}9Jť}-.d NJQሊW^ EޤL:^<܄|_],SmMY[,].S6J!c^-e>aF8nG=yL2I4|$x*OwaȈ^[RΩ^Ja-Ƞ:L,baT&UL¢U(aʪL#yu~Yq1Dj 8'G[I˶&ʹ X߱v9-^Ka+ hh F涜&iMjs[̘D/' @ tհ6:c=qCkmnm+O9m6.ͬνhآwˠX٫ْKGz^q) &8Ɋ *QB25v, AR$Ʉ86%QŖJ%B#A 98^0K!SQNJ5UYnMx`Wa]8 QCYg!V Hw+3/i[&\aϐ㿉%O\>n4y/fСE&驥Ufݚ0jO]Ϧ]vضu}7o5qɕ+4~zs輛{^:靡вaܽ X"BgLJ$XͣW1;d*! R*YbN$aK@ hJ(9%YjI*f&@ĶƕJƮL1 K F(Cs GHAGڀ+ LHlF 1*0E!bB+ӥ3ƾ} /Hန/J R.Iv ŋ3P%--N-3̄  `(EaATTaa!aa+$L!N&W&҈ :+2! "Zl Q8@ۄڄ8㡬JUR0R f$hA@_d7_02cp(~WdGez+[\f뺅J E8!dR471#/c ST}y2l1IYWhZ9hbs0`_yI>c:F!e+(0xGBH$a_w^"n&- c$[߽x'#y!h b#kϺF녮JMBmBWƛ+h|IBPRGhxZ0_[]ˡarʭ\d.!es2!L^{#LXՅ`? xRbbaZyRXH@mY>wK|и &( 32 ^0!˚0fE~r1 'B,L)H6ѿeB" SP`a5 O)XWQQ% XZD ($ :I/NѯɄYY!bfLJHBA QA+ AW[(RDh#1O34&I fRI&$'WI~X(RrG+KHtCNqQ,bˤrV.WTF$VÙ`dfD4`6Sӄ&*yb&32崩n:g9CNs39Tg;N;Ӹs!>Kx&BgSLD4:B%HCKdDFE,RxNC rT!1G@p!;),R-vҙ൩.TuҴ6EIT<-$qE(C! "AB ZJRr"9)P)UuJUXŦJVp+^Em-Ќ,uZsE`+[BK.WQ!d^#~va V%ZXfڄLbCX31l:"Bx迬ejveU>Kӆ-ix^@ ],vbEiD2A \.$mkcnۼmӿA#p 'uO!B"ޫ7:m()"!ЉK#1R_uKfSnx(ŠQ)Dx#~\q~5M ARr{\5=PD9)F7D%J)S2,!] Y<4MdYRt)Bd*]i n:{K:`U|YZC%dI3%'FJ+bO>U]ڪjVkv+#;@;wk:VZdI-J^cDFYhV+:Z*X]miY+0 aH6KbmJ ܎M}y5.)BN¡WQ.d!fz> ԮߴwZ^r<ׯrby :2lۈ0i@/˲x #Q%IB`)3k81 I1۝y1!k0x/WI2P#Qҕ( *C,sز!"V"3ຕ3# .6 78R3C=C?S@4(4ӍD!0H47<*#"Kc"5z56ʴ*:52JQ+NEP+3(W6z#5D ]# KQ5`aDڈc6c6h$d$lKfCPⶡq[%sK YZ7wۊrGx;*_EC|wG7yG|)G~d}GtG7H{+Hl'LtZ9ȶ9}R9`C1?ع>LH(¨#KIui#HT*0PPo<;#2;2IZ;j;*#º<+!q{Pe-aDJƫW< <:ϫK$帬2=(9 CQ5Lt9<Bj$>>>yU-b>-,K@lK+?K)ꮘ'CcN/z;@Ԛ{0,@ī*@X9@$0t10 D ǁ T KÌ [ sAAڹA D!t  ⪌&Ğ'O&cu+b)0[ 6D3DC6339 => =O7D@A$!FDOP NI#JS"MDNSWP!E7=ELVNW,.EO E36FFO:F6e.i$oNr$60 oFV"Ipu9Ǭ@UHx SnUZ&[UjU]&^UfU`&XVVcU%du 2Hu*!h%eu 8V3 o}'i83+W8W˸3v pt%Pyz3WpH0W~]xׄUXt](%Uq%Wm%nU XY08k]_2@xV8&׈gY@Y Z] -8Zٜ]QZO% p pHMڬ p8h^L hfBYAhhAHA`Ѓ?p\D\[ChF@߽mF[C_FX@ ^F߿Bp[F(Xmpi_x߅̫p:\:@4E߄5H9D֩ PEZ0Eh@B vmedn LՍ>ᄠc8cHEpeUֵ޿@@ v_F(`BF@b^6ffCv`]P FePeDPTV4Aʸ[!ƃ`Fh?X`FdcVfF~=`f1P U&]95XmYEeAVs t- FmB fx[DKK~F0_RRV X6`9.cBF]ih %pAhEw.PAhjj^Da J^vEX\8PlcH;`EkXD6rag\ʝbCb<`E d(>`D`i ^E~l .CucU9cNE8a(j HnPWXV X]k:j noF .ohn\oUl~ZoshVooVnppȍ5oXOZO}ppWp3X Y  q _qqYTq!,jI,6F5=OCL^GPbISehrlvr|,s`ĉhhl0Iixw|;ѩI!, ..!*;S4PEpK{PWbl dihlpDaxp(Ĥ0H)~u[0j-3^Cn%\8HaGuzz e9z\8 i%K lJ oIK7rDz$#ŒCz|k6uwqD6$`CcYD^$TBX9&H6M*Z24=1 (";graphql-ruby-2.2.17/guides/defer/graphiql.md000066400000000000000000000045031476434635200207360ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Use with GraphiQL desc: Using @defer with the GraphiQL IDE index: 4 pro: true --- You can use `@defer` and `@stream` with [GraphiQL](https://github.com/graphql/graphiql/blob/main/packages/graphiql/README.md), an in-browser IDE. Using @defer with GraphiQL ## Incremental responses If you're using the proposed `incremental: ...` response syntax ([proposal](https://github.com/graphql/graphql-spec/pull/742), [Ruby support](/defer/setup.html#example-rails-with-apollo-client)), you'll need a custom "fetcher" function to handle the `incremental: ...` part of the response. For example: ```js import { meros } from "meros"; // for handling multipart responses const customFetcher = async function* (graphqlParams, fetcherOpts) { // Make the initial fetch var result = await fetch("/graphql", { method: "POST", body: JSON.stringify(graphqlParams), headers: { 'content-type': 'application/json', } }).then((r) => { // Use meros to turn multipart responses into streams return meros(r, { multiple: true }) }) if (!isAsyncIterable(result)) { // Return plain responses as promises return result.json() } else { // Handle multipart responses one chunk at a time for await (const chunk of result) { yield chunk.map(part => { // Move the incremental part of the response into top-level // This assumes there's only one `incremental` entry // which is currently true for GraphQL-Pro's @defer implementation var newJson = {...part.body} if (newJson.incremental) { newJson.data = newJson.incremental[0].data newJson.path = newJson.incremental[0].path delete newJson.incremental } return newJson }); } } } // Helper for checking for a multipart response: function isAsyncIterable(input) { return ( typeof input === "object" && input !== null && ( input[Symbol.toStringTag] === "AsyncGenerator" || (Symbol.asyncIterator && Symbol.asyncIterator in input) ) ); } ``` Hopefully a new GraphiQL version will support this out of the box; follow the [issue on GitHub](https://github.com/graphql/graphiql/issues/3470). graphql-ruby-2.2.17/guides/defer/overview.md000066400000000000000000000044311476434635200207750ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Overview desc: What is @defer, and why use it? index: 0 pro: true --- `@defer` is a {% internal_link "directive", "/type_definitions/directives" %} for streaming GraphQL responses from the server to the client. By streaming the response, the server can send the most critical (or most available) data _first_, following up with secondary data shortly afterward. `@defer` was first described by [Lee Byron at React Europe 2015](https://youtu.be/ViXL0YQnioU?t=768) and got experimental support in [Apollo in 2018](https://blog.apollographql.com/introducing-defer-in-apollo-server-f6797c4e9d6e). `@stream` is like `@defer`, but it returns list items one at a time. Find details in the {% internal_link "Stream guide", "/defer/stream" %}. ## Example GraphQL queries can be large and complex, requiring lots of computation or dependencies on slow external services. In this example, the local server maintains an index of items ("decks"), but the item data ("cards") is actually hosted on a remote server. So, GraphQL queries must make remote calls in order to serve that data. Without `@defer`, the whole query is blocked until the last field is done resolving: {{ "https://user-images.githubusercontent.com/2231765/53442028-4a122b00-39d6-11e9-8e33-b91791bf3b98.gif" | link_to_img:"Rails without defer" }} But, we can add `@defer` to slow fields: ```diff deck { slots { quantity - card + card @defer { name price } } } ``` Then, the response will stream to the client bit by bit, so the page can load progressively: {{ "https://user-images.githubusercontent.com/2231765/53442027-4a122b00-39d6-11e9-8d7b-feb7a4f7962a.gif" | link_to_img:"Rails with defer" }} This way, clients get a snappy feel from the app even while data is still loading. View the full demo at https://github.com/rmosolgo/graphql_defer_example. ## Considerations - `@defer` adds some overhead to the response, so only apply it judiciously. - `@defer` is single-threaded. `@defer`ed fields are still evaluated in sequence, but in a chunk-by-chunk way. ## Next Steps {% internal_link "Set up your server", "/defer/setup" %} to support `@defer` or read about {% internal_link "client usage", "/defer/usage" %} of it. graphql-ruby-2.2.17/guides/defer/setup.md000066400000000000000000000124551476434635200202740ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Server Setup desc: Configuring the schema and server to use @defer index: 1 pro: true --- Before using `@defer` in queries, you have to: - Update `graphql` and `graphql-pro` gems - Add `@defer` to your GraphQL schema - Update your HTTP handlers (eg, Rails controllers) to send streaming responses - Optionally, customize `@defer` to work with GraphQL-Batch You can also see a [full Rails & Apollo-Client demo](https://github.com/rmosolgo/graphql_defer_example). ## Updating the gems GraphQL-Ruby 1.9+ and GraphQL-Pro 1.10+ are required: ```ruby gem "graphql", "~>1.9" gem "graphql-pro", "~>1.10" ``` And then install them: ``` $ bundle update graphql graphql-pro ``` ## Adding `@defer` to your schema Then, add `GraphQL::Pro::Defer` to your schema as a plugin: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::Defer end ``` This will: - Attach a {% internal_link "custom directive", "/type_definitions/directives" %} called `@defer` - Add instrumentation to queries to track deferred work and execute it later ## Sending streaming responses Many web frameworks have support for streaming responses, for example: - Rails has [ActionController::Live](https://api.rubyonrails.org/classes/ActionController/Live.html) - Sinatra has [Sinatra::Streaming](http://sinatrarb.com/contrib/streaming.html) - Hanami::Controller can [stream responses](https://github.com/hanami/controller#streamed-responses) See below for how to integrate GraphQL's deferred patches with a streaming response API. To investigate support with a web framework, please {% open_an_issue "Server support for @defer with ..." %} or email `support@graphql.pro`. ### Checking for deferrals When a query has any `@defer`ed fields, you can check for `context[:defer]`: ```ruby if context[:defer] # some fields were `@defer`ed else # normal GraphQL, no `@defer` end ``` ### Working with deferrals To handle deferrals, you can enumerate over `context[:defer]`, for example: ```ruby context[:defer].each do |deferral| # do something with the `deferral`, eg # stream_to_client(deferral.to_h) end ``` The initial result is _also_ present in the deferrals, so you can treat it just like a patch. Each deferred patch has a few methods for building a response: - `.to_h` returns a hash with `path:`, `data:`, and/or `errors:`. (There is no `path:` for the root result.) - `.to_http_multipart(incremental: true)` returns a string which works with Apollo client's `@defer` support. (Use `incremental: true` to format patches for the forthcoming spec.) - `.path` returns the path to this patch in the response - `.data` returns successfully-resolved results of the patch - `.errors` returns an array of errors, if there were any Calling `.data` or `.errors` on a deferral will resume GraphQL execution until the patch is complete. ### Example: Rails with Apollo Client In this example, a Rails controller will stream HTTP Multipart patches to the client, in Apollo Client's supported format. ```ruby class GraphqlController < ApplicationController # Support `response.stream` below: include ActionController::Live def execute # ... result = MySchema.execute(query, variables: variables, context: context, operation_name: operation_name) # Check if this is a deferred query: if (deferred = result.context[:defer]) # Required for Rack 2.2+, see https://github.com/rack/rack/issues/1619 response.headers['Last-Modified'] = Time.now.httpdate # Use built-in `stream_http_multipart` with Apollo-Client & ActionController::Live deferred.stream_http_multipart(response, incremental: true) else # Return a plain, non-deferred result render json: result end ensure # Always make sure to close the stream response.stream.close end end ``` You can also investigate a [full Rails & Apollo-Client demo](https://github.com/rmosolgo/graphql_defer_example) ## With GraphQL-Batch `GraphQL-Batch` is a third-party data loading library that wraps GraphQL-Ruby execution. Deferred resolution happens outside the normal execution flow, so to work with GraphQL-Batch, you have to customize `GraphQL::Pro::Defer` a bit. Also, you'll need GraphQL-Pro `v1.24.6` or later. Here's a custom `Defer` implementation: ```ruby # app/graphql/directives/defer.rb module Directives # Modify the library's `@defer` implementation to work with GraphQL-Batch class Defer < GraphQL::Pro::Defer def self.resolve(obj, arguments, context, &block) # While the query is running, store the batch executor to re-use later context[:graphql_batch_executor] ||= GraphQL::Batch::Executor.current super end class Deferral < GraphQL::Pro::Defer::Deferral def resolve # Before calling the deferred execution, # set GraphQL-Batch back up: prev_executor = GraphQL::Batch::Executor.current GraphQL::Batch::Executor.current ||= @context[:graphql_batch_executor] super ensure # Clean up afterward: GraphQL::Batch::Executor.current = prev_executor end end end end ``` And update your schema to use your custom defer implementation: ```ruby # Use our GraphQL-Batch-compatible defer: use Directives::Defer ``` ## Next Steps Read about {% internal_link "client usage", "/defer/usage" %} of `@defer`. graphql-ruby-2.2.17/guides/defer/stream.md000066400000000000000000000033041476434635200204200ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Stream desc: Using @stream to receive list items one at a time index: 3 pro: true --- `@stream` works very much like `@defer`, except it only applies to list fields. When a field has `@stream` and it returns a list, then each item in the list is returned to the client as a patch. `@stream` is described in a [proposal to the GraphQL specification](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md). __Note:__ `@stream` was added in GraphQL-Pro 1.21.0 and requires GraphQL-Ruby 1.13.6+. ## Installation To support `@stream` in your schema, add it with `use GraphQL::Pro::Stream`: ```ruby class MySchema < GraphQL::Schema # ... use GraphQL::Pro::Stream end ``` Additionally, you should update your controller to handle deferred parts of the response. See the {% internal_link "@defer setup guide", "defer/setup#sending-streaming-responses" %} for details. (`@stream` uses the same deferral pipeline as `@defer`, so the same setup instructions apply.) ## Usage After that, you can include `@stream` in your queries, for example: ```ruby { # Send each movie in its own patch: nowPlaying @stream { title director { name } } } ``` If `@stream` is applied to non-list fields, it's ignored. `@stream` supports several arguments: - `if: Boolean = true`: when `false`, the list is _not_ streamed. Instead, all items are returned synchronously. - `label: String`: if present, the given string is returned in patches as `"label": "..."` - `initialCount: Int = 0`: this number of list items are returned synchronously. (If the list is shorter than `initialCount`, then the whole list is returned synchronously.) graphql-ruby-2.2.17/guides/defer/usage.md000066400000000000000000000024771476434635200202430ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Usage desc: Using @defer on the client side index: 2 pro: true --- `@defer` is a [GraphQL directive](https://graphql.org/learn/queries/#directives) which instructs the server to execute the field in a special way: ```graphql query GetPlayerInfo($handle: String!){ player(handle: $handle) { name # Send this field later, to avoid slowing down the initial response: topScore(from: 2000, to: 2020) @defer } } ``` The directives `@skip` and `@include` are built into any GraphQL server and client, but `@defer` requires special attention. Apollo-Client has [experimental support](https://www.apollographql.com/docs/react/features/defer-support.html) but it may [have some issues](https://github.com/apollographql/apollo-client/issues/4484), so you can try [this updated fork](https://github.com/rmosolgo/apollo-client) while they're worked out. `@defer` also accepts a `label:` option which will be included in outgoing patches when it's present in the query (eg, `@defer(label: "patch1")`). Want to use `@defer` with another client? Please {% open_an_issue "Client support for @defer with ..." %} or email `support@graphql.pro` and we'll dig in! ## Next Steps {% internal_link "Set up your server", "/defer/setup" %} to support `@defer`. graphql-ruby-2.2.17/guides/development.md000066400000000000000000000210131476434635200203570ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Development section: Other desc: Hacking on GraphQL Ruby --- So, you want to hack on GraphQL Ruby! Here are some tips for getting started. - [Setup](#setup) your development environment - [Run the tests](#running-the-tests) to verify your setup - [Debug](#debugging-with-pry) with pry - [Run the benchmarks](#running-the-benchmarks) to test performance in your environment - [Coding guidelines](#coding-guidelines) for working on your contribution - Special tools for building the [lexer and parser](#lexer-and-parser) - Building and publishing the [GraphQL Ruby website](#website) - [Versioning](#versioning) describes how changes are managed and released - [Releasing](#releasing) Gem versions ## Setup Get your own copy of graphql-ruby by forking [`rmosolgo/graphql-ruby` on GitHub](https://github.com/rmosolgo/graphql-ruby) and cloning your fork. Then, install the dependencies: - Install SQLite3 and MongoDB (eg, `brew install sqlite && brew tap mongodb/brew && brew install mongodb-community`) - `bundle install` - `rake compile # If you get warnings at this step, you can ignore them.` - Optional: [Ragel](https://www.colm.net/open-source/ragel/) is required to build the lexer ## Running the Tests ### Unit tests You can run the tests with ``` bundle exec rake # tests & Rubocop bundle exec rake test # tests only ``` You can run a __specific file__ with `TEST=`: ``` bundle exec rake test TEST=spec/graphql/query_spec.rb # run tests in `query_spec.rb` only ``` You can focus on a __specific example__ with `focus`: ```ruby focus it "does something cool" do # ... end ``` Then, only `focus`ed tests will run: ``` bundle exec rake test # only the focused test will be run ``` (This is provided by `minitest-focus`.) ### Integration tests You need to pick a specific gemfile from gemfiles/ to run integration tests. For example: ``` BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bundle install BUNDLE_GEMFILE=gemfiles/rails_6.1.gemfile bundle exec rake test TEST=spec/integration/rails/graphql/relay/array_connection_spec.rb ``` ### GraphQL-CParser tests To test the `graphql_cparser` gem, you have to build the binary first: ``` bundle exec rake build_ext ``` Then, run the test suite with `GRAPHQL_CPARSER=1`: ``` GRAPHQL_CPARSER=1 bundle exec rake test ``` (Add `TEST=` to pick a certain file.) ### Other tests There are system tests for checking ActionCable behavior, use: ``` bundle exec rake test:system ``` And JavaScript tests: ``` bundle exec rake test:js ``` ## Gemfiles, Gemfiles, Gemfiles `graphql-ruby` has several gemfiles to ensure support for various Rails versions. You can specify a gemfile with `BUNDLE_GEMFILE`, eg: ``` BUNDLE_GEMFILE=gemfiles/rails_5.gemfile bundle exec rake test ``` ## Debugging with Pry [`pry`](https://pryrepl.org/) is included with GraphQL-Ruby's development setup to help with debugging. To pause execution in Ruby code, add: ```ruby binding.pry ``` Then, the program will pause and your terminal will become a Ruby REPL. Feel free to use `pry` in your development process! ## Running the Benchmarks This project includes some Rake tasks to record benchmarks: ```sh $ bundle exec rake -T | grep bench: rake bench:profile # Generate a profile of the introspection query rake bench:query # Benchmark the introspection query rake bench:validate # Benchmark validation of several queries ``` You can save results by sending the output into a file: ```sh $ bundle exec rake bench:validate > before.txt $ cat before.txt # ... # --> benchmark output here ``` If you want to check performance, create a baseline by running these tasks before your changes. Then, make your changes and run the tasks again and compare your results. Keep these points in mind when using benchmarks: - The results are hardware-specific: computers with different hardware will have different results. So don't compare your results to results from other computers. - The results are environment-specific: CPU and memory availability are affected by other processes on your computer. So try to create similar environments for your before-and-after testing. ## Coding Guidelines GraphQL-Ruby uses a thorough test suite to make sure things work reliably day-after-day. Please include tests that describe your changes, for example: - If you contribute a bug fix, include a test for the code that _was_ broken (and is now fixed) - If you contribute a feature, include tests for all intended uses of that feature - If you modify existing behavior, update the tests to cover all intended behaviors for that code Don't fret about coding style or organization. There's a minimal Rubocop config in `.rubocop.yml` which runs during CI. You can run it manually with `bundle exec rake rubocop`. ## Website To update the website, update the `.md` files in `guides/`. To preview your changes, you can serve the website locally: ``` bundle exec rake site:serve ``` Then visit `http://localhost:4000`. To publish the website with GitHub pages, run the Rake task: ``` bundle exec rake site:publish ``` ### Search Index GraphQL-Ruby's search index is powered by Algolia. To update the index, you need the API key in an environment variable: ``` $ export ALGOLIA_API_KEY=... ``` Without this key, the search index will fall out-of-sync with the website. Contact @rmosolgo to gain access to this key. ### API Docs The GraphQL-Ruby website has its own rendered version of the gem's API docs. They're pushed to GitHub pages with a special process. First, generate local copies of the docs you want to publish: ``` $ bundle exec rake apidocs:gen_version[1.8.0] # for example, generate docs that you want to publish ``` Then, check them out locally: ``` $ bundle exec rake site:serve # then visit http://localhost:4000/api-doc/1.8.0/ ``` Then, publish them as part of the whole site: ``` $ bundle exec rake site:publish ``` Finally, check your work by visiting the docs on the website. ## Versioning GraphQL-Ruby does _not_ attempt to deliver "semantic versioning" for the reasons described in `jashkenas`' s post, ["Why Semantic Versioning Isn't"](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e). Instead, the following scheme is used as a guideline: - Version numbers consist of three parts, `MAJOR.MINOR.PATCH` - __`PATCH`__ version indicates bug fixes or small features for specific use cases. Ideally, you can upgrade patch versions with only a quick skim of the changelog. - __`MINOR`__ version indicates significant additions, internal refactors, or small breaking changes. When upgrading a minor version, check the changelog for any new features or breaking changes which apply to your system. The changelog will always include an upgrade path for any breaking changes. Minor versions may also include deprecation warnings to warn about upcoming breaking changes. - __`MAJOR`__ version indicates significant breaking changes. Do not expect code to run without some modification, especially if the code yielded deprecation warnings. This policy is inspired by the [Ruby 2.1.0+ version policy](https://www.ruby-lang.org/en/news/2013/12/21/ruby-version-policy-changes-with-2-1-0/). Pull requests and issues may be tagged with a [GitHub milestone](https://github.com/rmosolgo/graphql-ruby/milestones) to denote when they'll be released. The [changelog](https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md) should always contain accurate and thorough information so that users can upgrade. If you have trouble upgrading based on the changelog, please open an issue on GitHub. ## Releasing GraphQL-Ruby doesn't have a strict release schedule. If you think it should, consider opening an issue to share your thoughts. To cut a release: - Update `CHANGELOG.md` for the new version: - Add a new heading for the new version, and paste the four categories of changes into the new section - Open the GitHub milestone corresponding to the new version - Check each pull request and put it in the category (or categories) that it belongs in - If a change affects the default behavior of GraphQL-Ruby in a disruptive way, add it to `## Breaking Changes` and include migration notes if possible - Include the PR number beside the change description for future reference - Update `lib/graphql/version.rb` with the new version number - Commit changes to master - Push changes to GitHub: `git push origin master`. GitHub Actions will update the website. - Release to RubyGems: `bundle exec rake release`. This will also push the tag to GitHub which will kick off a GitHub Actions job to update the API docs. - Celebrate 🎊 ! graphql-ruby-2.2.17/guides/errors/000077500000000000000000000000001476434635200170325ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/errors/error_handling.md000066400000000000000000000036401476434635200223540ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Error Handling desc: Rescuing application errors from field resolvers index: 3 --- You can configure your schema to rescue application errors during field resolution. Errors during batch loading will also be rescued. Thanks to [`@exAspArk`](https://github.com/exaspark) for the [`graphql-errors`](https://github.com/exAspArk/graphql-errors) gem which inspired this behavior and [`@thiago-sydow`](https://github.com/thiago-sydow) who [suggested](https://github.com/rmosolgo/graphql-ruby/issues/2139#issuecomment-524913594) an implementation like this. ## Add error handlers Handlers are added with `rescue_from` configurations in the schema: ```ruby class MySchema < GraphQL::Schema # ... rescue_from(ActiveRecord::RecordNotFound) do |err, obj, args, ctx, field| # Raise a graphql-friendly error with a custom message raise GraphQL::ExecutionError, "#{field.type.unwrap.graphql_name} not found" end rescue_from(SearchIndex::UnavailableError) do |err, obj, args, ctx, field| # Log the error Bugsnag.notify(err) # replace it with nil nil end end ``` The handler is called with several arguments: - __`err`__ is the error that was raised during field execution, then rescued - __`obj`__ is the object which was having a field resolved against it - __`args`__ is the Hash of arguments passed to the resolver - __`ctx`__ is the query context - __`field`__ is the {{ "GraphQL::Schema::Field" | api_doc }} instance for the field where the error was rescued Inside the handler, you can: - Raise a GraphQL-friendly {{ "GraphQL::ExecutionError" | api_doc }} to return to the user - Re-raise the given `err` to crash the query and halt execution. (The error will propagate to your application, eg, the controller.) - Report some metrics from the error, if applicable - Return a new value to be used for the error case (if not raising another error) graphql-ruby-2.2.17/guides/errors/execution_errors.md000066400000000000000000000055261476434635200227630ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Top-level "errors" desc: The top-level "errors" array and how to use it. index: 1 --- The GraphQL specification [allows for a top-level `"errors"` key](https://graphql.github.io/graphql-spec/June2018/#sec-Errors) in the response which may contain information about what went wrong during execution. For example: ```ruby { "errors" => [ ... ] } ``` The response may include _both_ `"data"` and `"errors"` in the case of a partial success: ```ruby { "data" => { ... } # parts of the query that ran successfully "errors" => [ ... ] # errors that prevented some parts of the query from running } ``` ## When to Use Top-Level Errors In general, top-level errors should only be used for exceptional circumstances when a developer should be made aware that the system had some kind of problem. For example, the GraphQL specification says that when a non-null field returns `nil`, an error should be added to the `"errors"` key. This kind of error is not recoverable by the client. Instead, something on the server should be fixed to handle this case. When you want to notify a client some kind of recoverable issue, consider making error messages part of the schema, for example, as in {% internal_link "mutation errors", "/mutations/mutation_errors" %}. ## Adding Errors to the Array In GraphQL-Ruby, you can add entries to this array by raising `GraphQL::ExecutionError` (or a subclass of it), for example: ```ruby raise GraphQL::ExecutionError, "Can't continue with this query" ``` When this error is raised, its `message` will be added to the `"errors"` key and GraphQL-Ruby will automatically add the `line`, `column` and `path` to it. So, the above error might be: ```ruby { "errors" => [ { "message" => "Can't continue with this query", "locations" => [ { "line" => 2, "column" => 10, } ], "path" => ["user", "login"], } ] } ``` ## Customizing Error JSON The default error JSON includes `"message"`, `"locations"` and `"path"`. The [forthcoming version](https://spec.graphql.org/draft/#example-fce18) of the GraphQL spec recommends putting custom data in the `"extensions"` key of the error JSON. You can customize this in two ways: - Pass `extensions:` when raising an error, for example: ```ruby raise GraphQL::ExecutionError.new("Something went wrong", extensions: { "code" => "BROKEN" }) ``` In this case, `"extensions" => { "code" => "BROKEN" }` will be added to the error JSON. - Override `#to_h` in a subclass of `GraphQL::ExecutionError`, for example: ```ruby class ServiceUnavailableError < GraphQL::ExecutionError def to_h super.merge({ "extensions" => {"code" => "SERVICE_UNAVAILABLE"} }) end end ``` Now, `"extensions" => { "code" => "SERVICE_UNAVAILABLE" }` will be added to the error JSON. graphql-ruby-2.2.17/guides/errors/overview.md000066400000000000000000000060431476434635200212250ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Errors in GraphQL desc: A conceptual introduction to errors in GraphQL index: 0 redirect_from: - /schema/type_errors/ - /queries/error_handling/ --- There are a _lot_ of different kinds of errors in GraphQL! In this guide, we'll discuss some of the main categories and learn when they apply. ## Validation Errors Because GraphQL is strongly typed, it performs validation of all queries before executing them. If an incoming query is invalid, it isn't executed. Instead, a response is sent back with `"errors"`: ```ruby { "errors" => [ ... ] } ``` Each error has a message, line, column and path. The validation rules are part of the GraphQL specification and built into GraphQL-Ruby, so there's not really a way to customize this behavior, except to pass `validate: false` when executing a query, which skips validation altogether. You can configure your schema to stop validating after a certain number of errors by setting {{ "Schema.validate_max_errors" | api_doc }}. Also, you can add a timeout to this step with {{ "Schema.validate_timeout" | api_doc }}. ## Analysis Errors GraphQL-Ruby supports pre-execution analysis, which may return `"errors"` instead of running a query. You can find details in the {% internal_link "Analysis guide", "queries/ast_analysis" %}. ## GraphQL Invariants While GraphQL-Ruby is executing a query, some constraints must be satisfied. For example: - Non-null fields may not return `nil`. - Interface and union types must resolve objects to types that belong to that interface/union. These constraints are part of the GraphQL specification, and when they are violated, it must be addressed somehow. Read more in {% internal_link "Type Errors", "/errors/type_errors" %}. ## Top-level `"errors"` The GraphQL specification provides for a top-level `"errors"` key which may include information about errors during query execution. `"errors"` and `"data"` may _both_ be present in the case of a partial success. In your own schema, you can add to the `"errors"` key by raising `GraphQL::ExecutionError` (or subclasses of it) in your code. Read more in the {% internal_link "Execution Errors guide", "/errors/execution_errors" %}. ## Handled Errors A schema can be configured to handle certain errors during field execution with handlers that you give it, using `rescue_from`. Read more in the {% internal_link "Error Handling guide", "/errors/error_handling" %}. ## Unhandled Errors (Crashes) When a `raise`d error is not `rescue`d, the GraphQL query crashes entirely and the surrounding code (like a Rails controller) must handle the exception. For example, Rails will probably return a generic `500` page. ## Errors as Data When you want end users (human beings) to read error messages, you can express errors _in the schema_, using normal GraphQL fields and types. In this approach, errors are strongly-typed data, queryable in the schema, like any other application data. For more about this approach, see {% internal_link "Mutation Errors", "/mutations/mutation_errors" %} graphql-ruby-2.2.17/guides/errors/type_errors.md000066400000000000000000000023521476434635200217330ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Type Errors desc: Handling type errors index: 3 --- The GraphQL specification _requires_ certain assumptions to hold true when executing a query. However, it's possible that some code would violate that assumption, resulting in a type error. Here are two type errors that you can customize in GraphQL-Ruby: - A field with `null: false` returned `nil` - A field returned a value as a union or interface, but that value couldn't be resolved to a member of that union or interface. You can specify behavior in these cases by defining a {{ "Schema.type_error" | api_doc }} hook: ```ruby class MySchema < GraphQL::Schema def self.type_error(err, query_ctx) # Handle a failed runtime type coercion end end ``` It is called with an instance of {{ "GraphQL::UnresolvedTypeError" | api_doc }} or {{ "GraphQL::InvalidNullError" | api_doc }} and the query context (a {{ "GraphQL::Query::Context" | api_doc }}). If you don't specify a hook, you get the default behavior: - Unexpected `nil`s add an error the response's `"errors"` key - Unresolved Union / Interface types raise {{ "GraphQL::UnresolvedTypeError" | api_doc }} An object that fails type resolution is treated as `nil`. graphql-ruby-2.2.17/guides/faq.md000066400000000000000000000042551476434635200166150ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: FAQ other: true desc: How to do common tasks --- Returning Route URLs ==================== With GraphQL there is less of a need to include resource URLs to other REST resources, however sometimes you want to use Rails routing to include a URL as one of your fields. A common use case would be to build HTML format URLs to render a link in your React UI. In that case you can pass the request to your context, so that the helpers are able to build full URLs based on the incoming host, port and protocol. Example ------- ```ruby class Types::UserType < Types::BaseObject include ActionController::UrlFor include Rails.application.routes.url_helpers # Needed by ActionController::UrlFor to extract the host, port, protocol etc. from the current request def request context[:request] end # Needed by Rails.application.routes.url_helpers, it will then use the url_options defined by ActionController::UrlFor def default_url_options {} end field :profile_url, String, null: false def profile_url user_url(object) end end # In your GraphQL controller, add the request to `context`: MySchema.execute( params[:query], variables: params[:variables], context: { request: request }, ) ``` Returning ActiveStorage blob URLs ================================= If you are using ActiveStorage and need to return a URL to an attachment blob, you will find that using `Rails.application.routes.url_helpers.rails_blob_url` alone will throw an exception since Rails won't know what host, port or protocol to use in it. You can include `ActiveStorage::SetCurrent` in your GraphQL controller to pass on this information into your resolvers. Example ======= ```ruby class GraphqlController < ApplicationController include ActiveStorage::SetCurrent ... end class Types::UserType < Types::BaseObject field :picture_url, String, null: false def picture_url Rails.application.routes.url_helpers.rails_blob_url( object.picture, protocol: ActiveStorage::Current.url_options[:protocol], host: ActiveStorage::Current.url_options[:host], port: ActiveStorage::Current.url_options[:port] ) end end ``` graphql-ruby-2.2.17/guides/fields/000077500000000000000000000000001476434635200167645ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/fields/arguments.md000066400000000000000000000124031476434635200213130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Arguments desc: Fields may take arguments as inputs index: 1 --- Fields can take **arguments** as input. These can be used to determine the return value (eg, filtering search results) or to modify the application state (eg, updating the database in `MutationType`). Arguments are defined with the `argument` helper. These arguments are passed as [keyword arguments](https://robots.thoughtbot.com/ruby-2-keyword-arguments) to the resolver method: ```ruby field :search_posts, [PostType], null: false do argument :category, String end def search_posts(category:) Post.where(category: category).limit(10) end ``` ## Nullability To make an argument optional, set `required: false`, and set default values for the corresponding keyword arguments: ```ruby field :search_posts, [PostType], null: false do argument :category, String, required: false end def search_posts(category: nil) if category Post.where(category: category).limit(10) else Post.all.limit(10) end end ``` Be aware that if all arguments are optional and the query does not provide any arguments, then the resolver method will be called with no arguments. To prevent an `ArgumentError` in this case, you must either specify default values for all keyword arguments (as done in the prior example) or use the double splat operator argument in the method definition. For example: ```ruby def search_posts(**args) if args[:category] Post.where(category: args[:category]).limit(10) else Post.all.limit(10) end end ``` ### Default Values Another approach is to use `default_value: value` to provide a default value for the argument if it is not supplied in the query. ```ruby field :search_posts, [PostType], null: false do argument :category, String, required: false, default_value: "Programming" end def search_posts(category:) Post.where(category: category).limit(10) end ``` Arguments with `required: false` _do_ accept `null` as inputs from clients. This can be surprising in resolver code, for example, an argument with `Integer, required: false` can sometimes be `nil`. In this case, you can use `replace_null_with_default: true` to apply the given `default_value: ...` when clients provide `null`. For example: ```ruby # Even if clients send `query: null`, the resolver will receive `"*"` for this argument: argument :query, String, required: false, default_value: "*", replace_null_with_default: true ``` Finally, `required: :nullable` will require clients to pass the argument, although it will accept `null` as a valid input. For example: ```ruby # This argument _must_ be given -- send `null` if there's no other appropriate value: argument :email_address, String, required: :nullable ``` ## Deprecation **Experimental:** __Deprecated__ arguments can be marked by adding a `deprecation_reason:` keyword argument: ```ruby field :search_posts, [PostType], null: false do argument :name, String, required: false, deprecation_reason: "Use `query` instead." argument :query, String, required: false end ``` Note argument deprecation is a stage 2 GraphQL [proposal](https://github.com/graphql/graphql-spec/pull/525) so not all clients will leverage this information. ## Aliasing Use `as: :alternate_name` to use a different key from within your resolvers while exposing another key to clients. ```ruby field :post, PostType, null: false do argument :post_id, ID, as: :id end def post(id:) Post.find(id) end ``` ## Preprocessing Provide a `prepare` function to modify or validate the value of an argument before the field's resolver method is executed: ```ruby field :posts, [PostType], null: false do argument :start_date, String, prepare: ->(startDate, ctx) { # return the prepared argument. # raise a GraphQL::ExecutionError to halt the execution of the field and # add the exception's message to the `errors` key. } end def posts(start_date:) # use prepared start_date end ``` ## Automatic camelization Arguments that are snake_cased will be camelized in the GraphQL schema. Using the example of: ```ruby field :posts, [PostType], null: false do argument :start_year, Int end ``` The corresponding GraphQL query will look like: ```graphql { posts(startYear: 2018) { id } } ``` To disable auto-camelization, pass `camelize: false` to the `argument` method. ```ruby field :posts, [PostType], null: false do argument :start_year, Int, camelize: false end ``` Furthermore, if your argument is already camelCased, then it will remain camelized in the GraphQL schema. However, the argument will be converted to snake_case when it is passed to the resolver method: ```ruby field :posts, [PostType], null: false do argument :startYear, Int end def posts(start_year:) # ... end ``` ## Valid Argument Types Only certain types are valid for arguments: - {{ "GraphQL::Schema::Scalar" | api_doc }}, including built-in scalars (string, int, float, boolean, ID) - {{ "GraphQL::Schema::Enum" | api_doc }} - {{ "GraphQL::Schema::InputObject" | api_doc }}, which allows key-value pairs as input - {{ "GraphQL::Schema::List" | api_doc }}s of a valid input type, configured using `[...]` - {{ "GraphQL::Schema::NonNull" | api_doc }}s of a valid input type (arguments are non-null by default; use `required: false` to make optional arguments) graphql-ruby-2.2.17/guides/fields/introduction.md000066400000000000000000000230331476434635200220300ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Introduction desc: Implement fields and resolvers with the Ruby DSL index: 0 --- Object fields expose data about that object or connect the object to other objects. You can add fields to your object types with the `field(...)` class method, for example: ```ruby field :name, String, "The unique name of this list", null: false ``` {% internal_link "Objects", "/type_definitions/objects" %} and {% internal_link "Interfaces", "/type_definitions/interfaces" %} have fields. The different elements of field definition are addressed below: - [Names](#field-names) identify the field in GraphQL - [Return types](#field-return-type) say what kind of data this field returns - [Documentation](#field-documentation) includes description and deprecation notes - [Resolution behavior](#field-resolution) hooks up Ruby code to the GraphQL field - [Arguments](#field-arguments) allow fields to take input when they're queried - [Extra field metadata](#extra-field-metadata) for low-level access to the GraphQL-Ruby runtime - [Add default values for field parameters](#field-parameter-default-values) ## Field Names A field's name is provided as the first argument or as the `name:` option: ```ruby field :team_captain, ... # or: field ..., name: :team_captain ``` Under the hood, GraphQL-Ruby **camelizes** field names, so `field :team_captain, ...` would be `{ teamCaptain }` in GraphQL. You can disable this behavior by adding `camelize: false` to your field definition or to the [default field options](#field-parameter-default-values). The field's name is also used as the basis of [field resolution](#field-resolution). ## Field Return Type The second argument to `field(...)` is the return type. This can be: - A built-in GraphQL type (`Integer`, `Float`, `String`, `ID`, or `Boolean`) - A GraphQL type from your application - An _array_ of any of the above, which denotes a {% internal_link "list type", "/type_definitions/lists" %}. {% internal_link "Nullability", "/type_definitions/non_nulls" %} is expressed with the `null:` keyword: - `null: true` (default) means that the field _may_ return `nil` - `null: false` means the field is non-nullable; it may not return `nil`. If the implementation returns `nil`, GraphQL-Ruby will return an error to the client. Additionally, list types maybe nullable by adding `[..., null: true]` to the definition. Here are some examples: ```ruby field :name, String # `String`, may return a `String` or `nil` field :id, ID, null: false # `ID!`, always returns an `ID`, never `nil` field :teammates, [Types::User], null: false # `[User!]!`, always returns a list containing `User`s field :scores, [Integer, null: true] # `[Int]`, may return a list or `nil`, the list may contain a mix of `Integer`s and `nil`s ``` ## Field Documentation Fields may be documented with a __description__ and may be __deprecated__. __Descriptions__ can be added with the `field(...)` method as a positional argument, a keyword argument, or inside the block: ```ruby # 3rd positional argument field :name, String, "The name of this thing", null: false # `description:` keyword field :name, String, null: false, description: "The name of this thing" # inside the block field :name, String, null: false do description "The name of this thing" end ``` __Deprecated__ fields can be marked by adding a `deprecation_reason:` keyword argument: ```ruby field :email, String, deprecation_reason: "Users may have multiple emails, use `User.emails` instead." ``` Fields with a `deprecation_reason:` will appear as "deprecated" in GraphiQL. ## Field Resolution In general, fields return Ruby values corresponding to their GraphQL return types. For example, a field with the return type `String` should return a Ruby string, and a field with the return type `[User!]!` should return a Ruby array with zero or more `User` objects in it. By default, fields return values by: - Trying to call a method on the underlying object; _OR_ - If the underlying object is a `Hash`, lookup a key in that hash. - An optional `:fallback_value` can be supplied that will be used if the above fail. The method name or hash key corresponds to the field name, so in this example: ```ruby field :top_score, Integer, null: false ``` The default behavior is to look for a `#top_score` method, or lookup a `Hash` key, `:top_score` (symbol) or `"top_score"` (string). You can override the method name with the `method:` keyword, or override the hash key(s) with the `hash_key:` or `dig:` keyword, for example: ```ruby # Use the `#best_score` method to resolve this field field :top_score, Integer, null: false, method: :best_score # Lookup `hash["allPlayers"]` to resolve this field field :players, [User], null: false, hash_key: "allPlayers" # Use the `#dig` method on the hash with `:nested` and `:movies` keys field :movies, [Movie], null: false, dig: [:nested, :movies] ``` To pass-through the underlying object without calling a method on it, you can use `method: :itself`: ```ruby field :player, User, null: false, method: :itself ``` This is equivalent to: ```ruby field :player, User, null: false def player object end ``` If you don't want to delegate to the underlying object, you can define a method for each field: ```ruby # Use the custom method below to resolve this field field :total_games_played, Integer, null: false def total_games_played object.games.count end ``` Inside the method, you can access some helper methods: - `object` is the underlying application object (formerly `obj` to resolve functions) - `context` is the query context (passed as `context:` when executing queries, formerly `ctx` to resolve functions) Additionally, when you define arguments (see below), they're passed to the method definition, for example: ```ruby # Call the custom method with incoming arguments field :current_winning_streak, Integer, null: false do argument :include_ties, Boolean, required: false, default_value: false end def current_winning_streak(include_ties:) # Business logic goes here end ``` As the examples above show, by default the custom method name must match the field name. If you want to use a different custom method, the `resolver_method` option is available: ```ruby # Use the custom method with a non-default name below to resolve this field field :total_games_played, Integer, null: false, resolver_method: :games_played def games_played object.games.count end ``` `resolver_method` has two main use cases: 1. resolver re-use between multiple fields 2. dealing with method conflicts (specifically if you have fields named `context` or `object`) Note that `resolver_method` _cannot_ be used in combination with `method` or `hash_key`. ## Field Arguments _Arguments_ allow fields to take input to their resolution. For example: - A `search()` field may take a `term:` argument, which is the query to use for searching, eg `search(term: "GraphQL")` - A `user()` field may take an `id:` argument, which specifies which user to find, eg `user(id: 1)` - An `attachments()` field may take a `type:` argument, which filters the result by file type, eg `attachments(type: PHOTO)` Read more in the {% internal_link "Arguments guide", "/fields/arguments" %} ## Extra Field Metadata Inside a field method, you can access some low-level objects from the GraphQL-Ruby runtime. Be warned, these APIs are subject to change, so check the changelog when updating. A few `extras` are available: - `ast_node` - `graphql_name` (the field's name) - `owner` (the type that this field belongs to) - `lookahead` (see {% internal_link "Lookahead", "/queries/lookahead" %}) - `execution_errors`, whose `#add(err_or_msg)` method should be used for adding errors - `argument_details` (Intepreter only), an instance of {{ "GraphQL::Execution::Interpreter::Arguments" | api_doc }} with argument metadata - `parent` (the previous `object` in the query) - Custom extras, see below To inject them into your field method, first, add the `extras:` option to the field definition: ```ruby field :my_field, String, null: false, extras: [:ast_node] ``` Then add `ast_node:` keyword to the method signature: ```ruby def my_field(ast_node:) # ... end ``` At runtime, the requested runtime object will be passed to the field. __Custom extras__ are also possible. Any method on your field class can be passed to `extras: [...]`, and the value will be injected into the method. For example, `extras: [:owner]` will inject the object type who owns the field. Any new methods on your custom field class may be used, too. ## Field Parameter Default Values The field method requires you to pass `null:` keyword argument to determine whether the field is nullable or not. For another field you may want to override `camelize`, which is `true` by default. You can override this behavior by adding a custom field with overwritten `camelize` option, which is `true` by default. ```ruby class CustomField < GraphQL::Schema::Field # Add `null: false` and `camelize: false` which provide default values # in case the caller doesn't pass anything for those arguments. # **kwargs is a catch-all that will get everything else def initialize(*args, null: false, camelize: false, **kwargs, &block) # Then, call super _without_ any args, where Ruby will take # _all_ the args originally passed to this method and pass it to the super method. super end end ``` To use `CustomField` in your Objects and Interfaces, you'll need to register it as a `field_class` on those classes. See [Customizing Fields](https://graphql-ruby.org/type_definitions/extensions#customizing-fields) for more information on how to do so. graphql-ruby-2.2.17/guides/fields/limits.md000066400000000000000000000014501476434635200206070ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Limits desc: Always limit lists of items index: 4 --- ## List Fields Always limit the number of items which can be returned from a list field. For example, use a `limit:` argument and make sure it's not too big. The `prepare:` function provides a convenient place to cap the number of items: ```ruby field :items, [Types::ItemType] do # Cap the number of items at 30 argument :limit, Integer, default_value: 20, prepare: ->(limit, ctx) {[limit, 30].min} end def items(limit:) object.items.limit(limit) end ``` This way, you won't hit your database for 1000 items! ## Connections Connections accept a {% internal_link "`max_page_size` option","/pagination/using_connections#max-page-size" %} which limits the number of nodes. graphql-ruby-2.2.17/guides/fields/resolvers.md000066400000000000000000000144061476434635200213370ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Resolvers desc: Reusable, extendable resolution logic for complex fields index: 2 redirect_from: - /fields/functions --- A {{ "GraphQL::Schema::Resolver" | api_doc }} is a container for field signature and resolution logic. It can be attached to a field with the `resolver:` keyword: ```ruby # Use the resolver class to execute this field field :pending_orders, resolver: PendingOrders ``` Under the hood, {{ "GraphQL::Schema::Mutation" | api_doc }} is a specialized subclass of `Resolver`. ## First, ask yourself ... Do you really need a `Resolver`? Putting logic in a Resolver has some downsides: - Since it's coupled to GraphQL, it's harder to test than a plain ol' Ruby object in your app - Since the base class comes from GraphQL-Ruby, it's subject to upstream changes which may require updates in your code Here are a few alternatives to consider: - Put display logic (sorting, filtering, etc.) into a plain ol' Ruby class in your app, and test that class - Hook up that object with a method, for example: ```ruby field :recommended_items, [Types::Item], null: false def recommended_items ItemRecommendation.new(user: context[:viewer]).items end ``` - If you have lots of arguments to share, use a class method to generate fields, for example: ```ruby # Generate a field which returns a filtered, sorted list of items def self.items_field(name, override_options, &block) # Prepare options default_field_options = { type: [Types::Item], null: false } field_options = default_field_options.merge(override_options) # Create the field field(name, **field_options) do argument :order_by, Types::ItemOrder, required: false argument :category, Types::ItemCategory, required: false # Allow an override block to add more arguments instance_eval(&block) if block_given? end end # Then use the generator to create a field: items_field(:recommended_items) do |field| field.argument :similar_to_product_id, ID, required: false end # Implement the field def recommended_items # ... end ``` As a matter of code organization, that class method could be put in a module and shared between different classes that need it. - If you need the _same_ logic shared between several objects, consider using a Ruby module and its `self.included` hook, for example: ```ruby module HasRecommendedItems def self.included(child_class) # attach the field here child_class.field(:recommended_items, [Types::Item], null: false) end # then implement the field def recommended_items # ... end end # Add the field to some objects: class Types::User < BaseObject include HasRecommendedItems # adds the field end ``` - If the module approach looks good to you, also consider {% internal_link "Interfaces", "/type_definitions/interfaces" %}. They also share behavior between objects (since they're just modules that get included, after all), and they expose that commonality to clients via introspection. ## When do you really need a resolver? So, if there are other, better options, why does `Resolver` exist? Here are a few specific advantages: - __Isolation__. A `Resolver` is instantiated for each call to the field, so its instance variables are private to that object. If you need to use instance variables for some reason, this helps. You have a guarantee that those values won't hang around when the work is done. - __Complex Schema Generation__. `RelayClassicMutation` (which is a `Resolver` subclass) generates input types and return types for each mutation. Using a `Resolver` class makes it easier to implement, share and extend this code generation logic. ## Using `resolver` Use the base resolver class: ```ruby module Resolvers class RecommendedItems < BaseResolver type [Types::Item], null: false description "Items this user might like" argument :order_by, Types::ItemOrder, required: false argument :category, Types::ItemCategory, required: false def resolve(order_by: nil, category: nil) # call your application logic here: recommendations = ItemRecommendation.new( viewer: context[:viewer], recommended_for: object, order_by: order_by, category: category, ) # return the list of items recommendations.items end end end ``` And attach it to your field: ```ruby class Types::User < Types::BaseObject field :recommended_items, resolver: Resolvers::RecommendedItems end ``` Since the `Resolver` lifecycle is managed by the GraphQL runtime, the best way to test it is to execute GraphQL queries and check the results. ### Nesting resolvers of the same type You may run into cyclical loading issues when using a resolver within the definition of the type the resolver returns e.g. ```ruby # app/graphql/types/query_type.rb module Types class QueryType < Types::BaseObject field :tasks, resolver: Resolvers::TasksResolver end end # app/graphql/types/task_type.rb module Types class TaskType < Types::BaseObject field :title, String, null: false field :tasks, resolver: Resolvers::TasksResolver end end # app/graphql/resolvers/tasks_resolver.rb module Resolvers class TasksResolver < BaseResolver type [Types::TaskType], null: false def resolve [] end end end ``` The above can produce the following error: `Failed to build return type for Task.tasks from nil: Unexpected type input: (NilClass)`. A simple solution is to express the type as a string in the resolver: ```ruby module Resolvers class TasksResolver < BaseResolver type "[Types::TaskType]", null: false def resolve [] end end end ``` In doing so, you can defer the loading of the type class until the nested resolver has already been loaded. # Extensions In cases when you want your resolvers to add some extensions to the field they resolve, you can use `extension` method, which accepts extension class and options. Multiple extensions can be configured for a single resolver. ```ruby class GreetingExtension < GraphQL::Schema::FieldExtension def resolve(object:, arguments:, **rest) name = yield(object, arguments) "#{options[:greeting]}, #{name}!" end end class ResolverWithExtension < BaseResolver type String, null: false extension GreetingExtension, greeting: "Hi" def resolve "Robert" end end ``` graphql-ruby-2.2.17/guides/fields/validation.md000066400000000000000000000070771476434635200214530ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Validation desc: Rails-like validations for arguments index: 3 --- Arguments can be validated at runtime using built-in or custom validators. Validations are configured in `argument(...)` calls on fields or input objects: ```ruby argument :home_phone, String, description: "A US phone number", validates: { format: { with: /\d{3}-\d{3}-\d{4}/ } } ``` or, `validates required: { ... }` inside a `field ... do ... end` block: ```ruby field :comments, [Comment], description: "Find comments by author ID or author name" do argument :author_id, ID, required: false argument :author_name, String, required: false # Either `authorId` or `authorName` must be provided by the client, but not both: validates required: { one_of: [:author_id, :author_name] } end ``` Validations can be provided with a keyword (`validates: { ... }`) or with a method inside the configuration block (`validates ...`). ## Built-In Validations See each validator's API docs for details: - `length: { maximum: ..., minimum: ..., is: ..., within: ... }` {{ "Schema::Validator::LengthValidator" | api_doc }} - `format: { with: /.../, without: /.../ }` {{ "Schema::Validator::FormatValidator" | api_doc }} - `numericality: { greater_than:, greater_than_or_equal_to:, less_than:, less_than_or_equal_to:, other_than:, odd:, even: }` {{ "Schema::Validator::NumericalityValidator" | api_doc }} - `inclusion: { in: [...] }` {{ "Schema::Validator::InclusionValidator" | api_doc }} - `exclusion: { in: [...] }` {{ "Schema::Validator::ExclusionValidator" | api_doc }} - `required: { one_of: [...] }` {{ "Schema::Validator::RequiredValidator" | api_doc }} - `allow_blank: true|false` {{ "Schema::Validator::AllowBlankValidator" | api_doc }} - `allow_null: true|false` {{ "Schema::Validator::AllowNullValidator" | api_doc }} Some of the validators accept customizable messages for certain validation failures; see the API docs for examples. `allow_blank:` and `allow_null:` may affect other validations, for example: ```ruby validates: { format: { with: /\A\d{4}\Z/ }, allow_blank: true } ``` Will permit any String containing four digits, or the empty string (`""`) if Rails is loaded. (GraphQL-Ruby checks for `.blank?`, which is usually defined by Rails.) Alternatively, they can be used alone, for example: ```ruby argument :id, ID, required: false, validates: { allow_null: true } ``` Will reject any query that passes `id: null`. ## Custom Validators You can write custom validators, too. A validator is a class that extends `GraphQL::Schema::Validator`. It should implement: - `def initialize(..., **default_options)` to accept any validator-specific options and pass along the defaults to `super(**default_options)` - `def validate(object, context, value)` which is called at runtime to validate `value`. It may return a String error message or an Array of Strings. GraphQL-Ruby will add those messages to the top-level `"errors"` array along with runtime context information. Then, custom validators can be attached either: - directly, passed to `validates`, like `validates: { MyCustomValidator => { some: :options }` - by keyword, if the keyword is registered with `GraphQL::Schema::Validator.install(:custom, MyCustomValidator)`. (That would support `validates: { custom: { some: :options }})`.) Validators are initialized when the schema is constructed (at application boot), and `validate(...)` is called while executing the query. There's one `Validator` instance for each configuration on each field, argument, or input object. (`Validator` instances aren't shared.) graphql-ruby-2.2.17/guides/getting_started.md000066400000000000000000000103601476434635200212270ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Getting Started section: Other desc: Start here! --- ## Installation You can install `graphql` from RubyGems by adding to your application's `Gemfile`: ```ruby # Gemfile gem "graphql" ``` Then, running `bundle install`: ```sh $ bundle install ``` ## Getting Started On Rails, you can get started with a few [GraphQL generators](https://rmosolgo.github.io/graphql-ruby/schema/generators#graphqlinstall): ```sh # Add graphql-ruby boilerplate and mount graphiql in development $ rails g graphql:install # You may need to run bundle install again, as by default graphiql-rails is added on installation. $ bundle install # Make your first object type $ rails g graphql:object Post title:String rating:Int comments:[Comment] ``` Or, you can build a GraphQL server by hand: - Define some types - Connect them to a schema - Execute queries with your schema ### Declare types Types describe objects in your application and form the basis for [GraphQL's type system](https://graphql.org/learn/schema/#type-system). ```ruby # app/graphql/types/post_type.rb module Types class PostType < Types::BaseObject description "A blog post" field :id, ID, null: false field :title, String, null: false # fields should be queried in camel-case (this will be `truncatedPreview`) field :truncated_preview, String, null: false # Fields can return lists of other objects: field :comments, [Types::CommentType], # And fields can have their own descriptions: description: "This post's comments, or null if this post has comments disabled." end end # app/graphql/types/comment_type.rb module Types class CommentType < Types::BaseObject field :id, ID, null: false field :post, PostType, null: false end end ``` ### Build a Schema Before building a schema, you have to define an [entry point to your system, the "query root"](https://graphql.org/learn/schema/#the-query-and-mutation-types): ```ruby class QueryType < GraphQL::Schema::Object description "The query root of this schema" field :post, resolver: Resolvers::PostResolver end ``` Define how this field is resolved by creating a resolver class: ```ruby # app/graphql/resolvers/post_resolver.rb module Resolvers class PostResolver < BaseResolver type Types::PostType, null: false argument :id, ID def resolve(id:) ::Post.find(id) end end end ``` Then, build a schema with `QueryType` as the query entry point: ```ruby class Schema < GraphQL::Schema query Types::QueryType end ``` This schema is ready to serve GraphQL queries! {% internal_link "Browse the guides","/guides" %} to learn about other GraphQL Ruby features. ### Execute queries You can execute queries from a query string: ```ruby query_string = " { post(id: 1) { id title truncatedPreview } }" result_hash = Schema.execute(query_string) # { # "data" => { # "post" => { # "id" => 1, # "title" => "GraphQL is nice" # "truncatedPreview" => "GraphQL is..." # } # } # } ``` See {% internal_link "Executing Queries","/queries/executing_queries" %} for more information about running queries on your schema. ## Use with Relay If you're building a backend for [Relay](https://facebook.github.io/relay/), you'll need: - A JSON dump of the schema, which you can get by sending [`GraphQL::Introspection::INTROSPECTION_QUERY`](https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/introspection/introspection_query.rb) - Relay-specific helpers for GraphQL, see the {% internal_link "Connection guide", "/pagination/connection_concepts" %}, {% internal_link "Mutation guide", "mutations/mutation_classes" %}, and {% internal_link "Object Identification guide", "/schema/object_identification" %}. ## Use with Apollo Client [Apollo Client](https://www.apollographql.com/) is a full featured, simple to use GraphQL client with convenient integrations for popular view layers. You don't need to do anything special to connect Apollo Client to a `graphql-ruby` server. ## Use with GraphQL.js Client [GraphQL.js Client](https://github.com/f/graphql.js) is a tiny client that is platform- and framework-agnostic. It works well with `graphql-ruby` servers, since GraphQL requests are simple query strings transported over HTTP. graphql-ruby-2.2.17/guides/graphql-ruby-dark.png000066400000000000000000000043571476434635200215710ustar00rootroot00000000000000PNG  IHDR]H PLTEUU@@f3UUII`@UUMM]FUUNN[IUDPPZKUGQQYMUIQFNNUJRGNNULRIUMPHSKPIUNSLQJUHQKUISMQKUJSNUJSIRMUKSJULSJULRIUMTKRJUMTLRKQITLSKQJTMSKQJTISLRKTJSLRKTJSMRLTKSJRLTKSJRLTKSJRJTLSKRJTLSKRJTLSKRKTJSLSKTJSLSKRKSJSLRKSJSLRKSJSLRKRLTKSJRLRJTKRJTLRKSKTJSLSKTJSLSKTKSJSKTKSJSLTKSKSLRKSKRKSKSKRLSKSKRJSKSKRJSKRKSLSKRKSJSKSKTKSLSKTKSJSKSJSLTKSKSLTKSKSJTKSKSKTLSKSKRJSKSKRKSKSKRKSLSKRKSJSKSKSKSLSKSKSKSKSKSKSKSKSLSKSKSKSKSKSKSKSJTKSKSKSLSKRKSKSKSKSKSKSKSKSKSKSKSKSKSKSKSKSKSKSKSKSK CtRNS  "#$%&')*+,-.01234679;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrsvwxy|}lbKGDkjIDATs̳k3!9r96WQZ-"RTTXWXv&D%͝#%f}y^;mzewU*U{*& .W{S{+{;OjS{)h?[nSLGnUKWsjs`a Y I+ڬ#в"C Έ+W_t|[Lt1k7G^2]d4^5E8<)Km k*Y ʶּ%,Uc}ɨ CB67g,T!=Jяa#a)fM5Hw#T7)1fȦW'klj\P'H=ɡaP$iԱÿe5`X"e0MaK` Vj|_*} ~;J#/ád[ {+À ZzFx]b`캷hO;uiCoBhe i='urXuYC߭x.N0..wWSҤOݑiwmT#e>*xeo0d4QϘ3ۍ[7eLfO(up\LR.N#I ο} HS`l3Y9`&R~MKcjfZaS~G$`Z]LQ~NH*qQ*d|[Z*4f q ދn'5Ԫ0y*l2 y*l.Y )|] l \|GgpY$@C i6JTxnUh#[uV~]$?WiW<JS/]V *6"^wr<u꼽W{.OB}D!J\vKlhxh7VAr[l x$" ;.U<UQt}v\6ChTEqCUt}v\,84DUDCWϯcQU`Q~k ,)Qэ$Wŷ8߳f$p$gs:Z* q`S^j.i( @IENDB`graphql-ruby-2.2.17/guides/graphql-ruby-icon.png000066400000000000000000000100121476434635200215610ustar00rootroot00000000000000PNG  IHDRggA\bKGD pHYsaa?itIMEJ7IDATx]}xSU&)lKIR(<Jp|DGQeR*M˂]G>gQaGyQOqgG|`gwMR*|5G{Is~&iIOy;: 0` 0` HwW %yݜGbXLa`8Ol 5|L*o%`03cVޮ $9 [LK0,滚|ky)[=aT<J+u_%侖"mw,cIM"tۓyYpl?_5~ԊpQCGiK%c( f'{ifhk%G$B \TҊxb`Jh㡂Lky]R"v$f&@(Ia2]s0n0i*XטJg-ef߰73f ^bҶPG'y}~:4Mr0|1"d7<&^߻'mНؙfd8&an dӀ^헅y@@.KJƌjr6^M~eh)jBUyx+X{T*(@4t$ !EtIkyBV(_&K,I9 ׄRq`o\LPL#A\T7'MUĸA,6E-{ Pr1f]|3C^!ձ7U i+peL}Gp.aVW3nV(i0c^t8j81 ,܁r|Y,M?Q7y{286nVe!z LN'O | poHl]qY^2U/TM"oir<䄹]R=0IL7=P<.!rBeDPMJڥ)qR?d"6jSwc=]2nEϚj;;;uUVpuh=-hjlΗ2JNݱRvB0 @czͅ@ͱ(Bi3ر6= T)&ۭIdj7K7|ޜ/$d6\]ȩ; [WGAbΈ؜YVHD_~Pm- Ak$t9La[\_譤pX#T\9v7igRtcPo:zIw'*'Ԫ'kYI(/`zׁ c!Um$VxT,81 |ikNΩ8$3!e9z;rm n% `sͱ F%9M:{ψi@nsga'8.5ʂcGb a|MI&tYow~G0PC`~s YL1QDnWI-bI0VeJ& fE ~gð1W8ךL).q QBj \͇΀*nu?̔N&EjO(PdI< #~K?´@lq>/nزGO 5=z=ewu~AcyQ%a=r5Uzs A3b"vE`][ϊA*1;ߦkzɄUKu:h"EK)*3q4n8nX;=CF[ v Mr)"Le{omx0x*X;O !G~Ym꼁$zf!CB  _-r6,98 40xDP*] "h6dTaX3|xž$O,+ꦆCGwZ::U?݇S&niJ3LT;KW%7B{:ǃ+{<d٩ uBo3k~W`]I$E!T y_C:ׁ8DՄN^kIJt;ljaЃA$oo.3TF{$彷Z~EJHXw f'YԈa0 1}"'H$Eн4@ys}2=Mߨԗˉ[;s\dA8A`Zts_ HYr̞tހNEBIlUÄU!dSwޥ iVZUA?cJcc;NMǽVhR"[t[|#qujTD@XkU>2 zwk.zfo n}'>S{:xt֭&q"GJp C 9"/C ~,IWꑘh51DpĤrzysړZ<#GGEVe DE|וQ 2W@,ndLAyޯVxEeڔ1aA*,D/@-`W?!N=P_:Q$Hl3?dߧӔBN>=d)ꬎljBK _Ngi}oVRB6C+t M mJ@vT{ꬎ t.Q$0jd"r՝L`m:gߪaꃾ YiT~1A``XάNFcO:C>+tΐÄĸ[Z;fn8mxl8V..f ݿXAֱ[B \CwSΐ3]M0Y=jٚkj?^V粐g!R&md9|8|?xW$m&%uu@L*H hi:^P 3͖ME~ן\R]z:7L!L%bShJ&Дt%&ܺ< Wȷƈӄd Al&bt9J=ް9(fUoT#C~''NА} Rț7"51UO!8 ĔYē"Ep{jb@x![wՀ}՜UGy3:^եc?i:L LA]/E6YGNʥ0ss\$?zޒ?Wͣ;-]{U dʹ(1nv;ÃZfeu0-WڕG}-M@ɖ Ѻ"UErr] ދ jub8-Q;Ɍ靿#v|)+s`жYgPPx4' _1+{4)r]t! `|] eU} 19GN߫Ŀ19IOV|1Y2ki\$&gige4 D稅Ir钖dfphw/#7Ig`z[c(r6 `YZ̽\0U Ǚ>M96"9"Hc*b+1)[ {a 0` 0`o$r{IENDB`graphql-ruby-2.2.17/guides/graphql-ruby.png000066400000000000000000000122271476434635200206450ustar00rootroot00000000000000PNG  IHDRgOۢsRGB pHYsaa?iiTXtXML:com.adobe.xmp www.inkscape.org 1 UXeIDATx\ TřgFF LC#,FMD#(p#f$g(1(̃`| fW.`{fI,țaqkwzwgw뜙zU_U_<SIv"X`\c\Gz"Y0 Z5mcSR];|ESeB=BhϵF{F65.kuBa ;)ɊIa=nr`'6pp_ ZUww`sd8u\0_6ul);Sdbs0|N/DktI01-~'50&ÄcDrgat,}N`e-˖տVsC#Bgq$#6@%afT zrXyO/yGq+ul8묪 =)|Hm ? Zk4beGvbBjm NSc|1 V^1QU2tc= 'AWwL<(X$L M5G;`m~=g@rS4" ,x3yg+[ݏ'6Ԅs&Mk|-]V2ўN퓧ORM=?>f{%N,9S5i6#Ic~_rd v< qiHJHeQZ(95Ոo2YG_$lod IbSȼ5^~2]9{X `MP?-TAmtW-D__ͣMפLʬ4E2$ i)NIS X8 /GI*N +dM9= 5Ɲ\\aRⱋ;}"8Fbs{7 5eދ/›;5ଭ#-=Wi }  BW\lϞSn M(lنHf>Oׯ} :gO¹V~0aW4E^Ӹ*_//[Ϙ1ͧ!s Ht;4zYe *vq!~ {Pڕ1zIbO rx~HܗВTڤx"@x!ކ}nI aN6v4އ9fb<5O>){dJN{m÷*iL0X|.oߵzX$EsKKׅiT{x9?^ܴ[W"h31O` k&N0Ha Aj G1DM$t#@Vћp 6("60NĬ `!M/t9QCfg.kjJ#Eξ?; 31 M_IM$qv-) 锱 \і~xFN=qE_@!y Fa\VEX\yܥ!x}~=|^`:/Ipo2DD< ,BT6=H`g:D\;mdʧ3p ODvV6oK¸zjb?9қn,[8"@#gA$0{._Xl%GnQ}aFwSDj~ .?UCu,̋1 d6# :A "Vc/bs"%M lP䬧 )L[an^y9gT>7PAK@! olЇ'Nn2}"07jɔrOe/ b[6L:Z֖*]:29fr5l| Z0|V-+mP 7#cikoF>5=e}1[j$Fje߲Np:b!"9xu?N"k?j*+)r QϏ?\Ic$Ν7؄_0pQ%~)N-^ ֞j+Oy)USpm^=돆wi0S~ξ"G B~vթcdH@c;0p;fbPW bV$7n !W8cbLK3F\f+~Z6p"&#!ݚ[{U6 &X')m4W~>Y`c|ZMpgS{MӬծCXAP?2TNU\a9dS^U6kGLX.A 野nLWb $ZȦ±A x0Ʋ[x}~eWX]c+rx1b? |L>{b ^BbԴt*{$9)AeęLgKِ$W@zJ܇_'XtQpU G/=pHgLb%T9bCS ͧW(t_M h G۔Vz .qK㘝1w(RqᏉ1OlUeʬ/ sa.C w+aJ Ws :o7z%}$I7K%Qe.=*s[=JoH*i^RXF`g<@ܥE}Q{N&ʬ˖L=[yٔ)"-5cSg}pE i*~:?Ȃ( e Q}N”96sXAjtQ1j$~dn`J$aLdcgz1NO$Cbnm B83|-'́Fg5}S%Y:D f{ DM[쵟N'.pS9 VN vpcXKOCD\-mx>T¾B88]A "W,͘Օ.-&N^yӯ̶e\6U=UűE`Pvƈo/;X4zb+S.٭Ñ{4onjs2;Er|the=^\{|SZŏK_8Lis/K s $Zh/Po +?lD)}XgdYYvƃ_.oo*e1:!yu0Yӂ\?Cc)kk-d|̓9CC h(GQx? I 1&cyҥ-%k;! 1hm6ߟvgƤ_9'}+'p3&C)Ku,RD9N>Y|^!Mg`/6mxU;6ØbSq>pFޭVr'SIENDB`graphql-ruby-2.2.17/guides/guides.html000066400000000000000000000037361476434635200176750ustar00rootroot00000000000000--- title: Guides Index sections: - name: Schema - name: Queries - name: Type Definitions - name: Authorization - name: Fields - name: Mutations - name: Errors - name: Pagination - name: Relay - name: Dataloader - name: Subscriptions - name: GraphQL Pro - name: GraphQL Pro - OperationStore - name: GraphQL Pro - Defer - name: GraphQL Enterprise - Rate Limiters - name: GraphQL Enterprise - Object Cache - name: GraphQL Enterprise - Changesets - name: JavaScript Client - name: Language Tools - name: Testing - name: Other ---

Guides

{% assign sorted_pages = site.pages | sort: "index" %} {% assign pages_with_index = ''|split:'' %} {% assign pages_without_index = ''|split:'' %} {% for guide in sorted_pages %} {% if guide.layout == "guide" %} {% if guide.index %} {% assign pages_with_index = pages_with_index | push: guide %} {% else %} {% assign pages_without_index = pages_without_index | push: guide %} {% endif %} {% endif %} {% endfor %} {% assign sorted_guides = pages_with_index | concat: pages_without_index %} {% for section in page.sections %}

{{ section.name }}

    {% for guide in sorted_guides %} {% if guide.section == section.name %}
  • {{ guide.title }} {{ guide.desc }}

  • {% endif %} {% endfor %}
{% endfor %}
graphql-ruby-2.2.17/guides/index.html000066400000000000000000000115751476434635200175240ustar00rootroot00000000000000--- title: Welcome fullwidth: false ---
GraphQL Ruby Logo

GraphQL Ruby

The graphql gem implements the GraphQL Server Specification in Ruby.

Use it to add a GraphQL API to your Ruby or Rails app.

Install the Gem

Get going fast with the graphql gem, battle-tested and trusted by GitHub, Shopify, Flexport, Chime, and Kickstarter.

{% highlight bash %} # Download the gem: bundle add graphql # Setup with Rails: rails generate graphql:install {% endhighlight %}

Define Your Schema

Describe your application with a GraphQL schema to create a self-documenting, strongly-typed API.

{% highlight ruby %} # app/graphql/types/profile_type.rb class Types::ProfileType < Types::BaseObject field :id, ID, null: false field :name, String, null: false field :avatar, Types::PhotoType end {% endhighlight %}

Serve Queries

Provide custom data to clients and extend your API with {% internal_link "mutations", "/mutations/mutation_root" %}, {% internal_link "subscriptions", "/subscriptions/overview" %}, {% internal_link "streaming responses", "/defer/overview" %}, and {% internal_link "multiplexing", "/queries/multiplex" %}.

{% highlight ruby %} # app/controllers/graphql_controller.rb result = MySchema.execute( params[:query], variables: params[:variables], context: { current_user: current_user }, ) render json: result {% endhighlight %}

Harden Your API

Confidently deploy GraphQL with GraphQL-Ruby:

  • {% internal_link "Testing helpers", "/testing/overview" %} to validate your system
  • {% internal_link "Authorization", "/authorization/overview" %} integrates with your app's permission system
  • {% internal_link "GraphQL::Dataloader", "/dataloader/overview" %} optimizes access to data sources
  • {% internal_link "Complexity limits", "/queries/complexity_and_depth" %}, {% internal_link "timeouts", "/queries/timeout" %}, and {% internal_link "rate limits", "/limiters/overview" %} to protect your server resources
  • {% internal_link "Tracing", "/queries/tracing" %} for integration with your APM or custom usage
  • {% internal_link "API versioning", "/changesets/overview" %} to roll out changes while preserving client experience
  • {% internal_link "Persisted queries", "/operation_store/overview" %} to guarantee approved API usage
  • {% internal_link "Caching", "/object_cache/overview" %} to serve repeated data requests

Integrate with Client Libraries

{% internal_link "graphql-ruby-client", "/javascript_client/overview" %} provides integration with {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %}, {% internal_link "Relay", "/javascript_client/relay_subscriptions" %}, {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %}, {% internal_link "urql", "/javascript_client/urql_subscriptions" %}, or custom JavaScript.

Going Beyond

Customize your GraphQL API:

  • {% internal_link "Language tooling", "/language_tools/visitor/ %} for manipulating GraphQL documents
  • {% internal_link "Type system extensions", "/type_definitions/extensions/ %} for customizing your schema definition
  • {% internal_link "Query analysis", "/queries/ast_analysis" %} for ahead-of-time query inspection

Add GraphQL to your Ruby app. Get Started!

graphql-ruby-2.2.17/guides/javascript_client/000077500000000000000000000000001476434635200212225ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/javascript_client/ably_key.png000066400000000000000000003616541476434635200235460ustar00rootroot00000000000000PNG  IHDR,IZ iCCPICC ProfileHWXS[R -)7A@H! !,*v*b[ kE `ETVTޤ}|s3{duGHj',Dž0SRӘ'(Pes$"(e ܂PbM.O8+A|\#@hz"X[ @e2c6 q@b>j2BQqtrB d?xL^t[g|#1lV"r@"e?K^txSبY8YpLa*'1kA|Q,iXҾ# kl.;(bҜD%vf'(qxz2>Z(̍RY [xaLAH8İУEY hs )b5%9JGEY6bi92!q L7O2a炵d%)|$%jqyD%7 VW@ҷXǶrC^+XI6;"V9׀ 6A GAA e z{/H`1ad>A"Gy꿌hO{)-{gGl8 cJ &È!D:61]$y0;p:O7 f\{ndGWUsUy3#V?F n[Z%VikNH%"Dd1RE*H +r9\B:cy|B1j%:eh$NAh>Z.AWh5CϢWЛh'10caX ebblVaX'֋}ĉ8g6DxÛcJ vOB8!' wM  ~ll fAbO"Hv$oR M* 6NIȪdc39F{ɧɃ****1*\Y*Tv4\UVhR(ޔJ6e!rrVUUTCu@uj!ՋU?R@dzzFYhiJZ ]A-\6_RN+uu uEeGԯjhXjj5iTj׸ѯItҌ\W -VWksZ]tnFs;Dm+plRm}:Z:ttfTd` KF8#qqi(QQGum{Ѻ~<݃7u?1r=m'ߢ^whќ%g6ajohdj(2dxΰ׈agmQ1X`L?3YlfHM Z&.2=hЌb24[odgnl>|y=  EFVɖK--_XZ[YZ=YZ[W[߰!ڰlrl6۴ۢYWP;7;f11ccܶ?v`8D9,rwx5|l5c[~utuuxI)iSg[gs K|mwǕ:uk7w7wst*,mV,k#c n=bx㻼M۽;}>>|:}M|پվO~g 8>3pn ,(4$-X+81"Qi?6/5tv0BXdؚ͑Ȋ'QQ 脈 &<Fǀu1cbcH;r89q-i{V%ON&6%'MNIz63elܔ+Ԇ4RZRڮI6L:x)VSfN4UjԓԧI''M̎aW3328\?znϛ<;sm 7'7,W(ޚ>'&gwPnr !ε}%K/._qRzwߏ]u15kg]p#ƕ7;n%޺s{;;/}}Jj<,{d68yq'Owq^><ܽYs5/_ ic/E/{ڗZz͊zow?@z|d}lϤ_l4~`(ohHG 6437³C;5K.(G?aL.n qQ6 2Ҕ"tqVĢ [CH| n |ŝO&Dxf#Cm=lW pHYs%%IR$iTXtXML:com.adobe.xmp 1008 812 aʴiDOT(V[@IDATx }ScL1 EJJ! MW隆rJtM{F_xtK,DdM" H@$  H@ ݎST;fvH?xzߜ:zǾ@~lM[lE'.u~$  H@$ C`T .otwW^f+ȸ|< :o̝3DL1zӔSN^ծwaoxӫj^JW]uUfiҊ+8ƹ$  H`|Uf[h$5G~~%Y^y׿~{$  H@_}!xP_?EY$6_~_e/-+𼯵Zy(Ȁ 9#7~=W_}u{޴RK[I@$ @` ?y\;e7)M;8d>iyֱ͓KBUk]?Bz'-oI ,@mBƳ3Q 9p|U?CE30CuYC ^L3͔7 X_G^<,$0yw&Ō՟gyҢ.:h0؏y (#ċe~˲]wݕ{\,Lzꩋ=S>ƭޚ?Ԑ㴪PF+< Q(³4 ӟ4wZ1Kڼu*r 9y{$V8# o:ϚuP#M%%Y旼RDL}'ʓr`DgWDoP,+m8R8/kM)ZQ'9s9gˎvyr\%Bp/]' &wCeX":·C3k5y5Q_2Gϟ_'u#h_:|gay'NYh n4\$Ի~]{yqx#>O9up\V3<&"V\B300:Zξ0`)vM@b H J6D0p>j#fDuH#:N4p e05 䉎Lt錃3{s)?5Mo;Fe S\ʉ3'0C ' H@&0Q83VqxV\sX=೙ǃ+'`xATÃcil4Ӑgv!P<#oh0T@GtfߎU<κi>FTAC;`"()C=t2!9cv1x.1Gru#'"r,2Ɯ)C'?EE!/B˜&D(t # S53&ʲ4qtDD'fQgxlcYr,7"'EGEDM7C}u?񏳈:'Zo=GPeDfR0r O;2DP,$]G: #(By`v54p:¸oPftĆ.OΗ5S$  L4*xꌰr])>C fOl4aeVԹ?œկ%i!h73DcmcI}wO8Ӄ>qbU/wc/GgyGGx>!lK>` e>^ wܶ/Mk;Cxl׾\+ߊ{x;IL,}YBO H@$0/uLqIG]'ҋeofcNOy8 OntI97fgr7۵RL꬙c>tD( Z VC#E~ NF_,7 {f{Mr"a!fF8{2`\?CAn-,MǬ!c7Dc{i>`CxxyqUA?:⸞鐋ۗ/"#0|g8^Hpc-" ؏:ۍtu.L3e  fuk爲& H@$?}-Y,<;w*1=V^I;/E|p7Y⭎[ީg/~9IqAQ5(^Fl,|uz>oOvȳ T OG9A_?ߙqC*[zrax #۱',ǑW;0HP8nU55;ؾ1mnuuP+K"uWib?FedCgt0>N:C !:* H@&,ntD@ơ:C--d xxڱv3i5 gLM."!4Ÿqm7BIf4( !Ĭo*I0rƫhX3Zfr0L^u*$xE1^l3|uR7ؿr6&"Su0zœ^" 2dnx'CTqv?#4B^(BCE:w/: NêªjD@g2k$uKc>43Êr+-:lXƹّx;a>VC!F*[_6 eGG z1:8b>(' >~J@$ Lǻ S.X O=H,!Y~(!اYY7|2L'ƹ!3SꪼeH6CHkΗG@F,N*Q |U"^#)2DL~Iy9+}l[N|x~bձU6t@c\tP}gϪ&$  H@S&l&wC? fxX-OZ4LO46:ƒYFG>,V!z<q4 x,'g#aԬoǺ!9X{GjpF{d"y#^X"cHB_m~Ekc_^O༨̇;9QKȍ ת[}e_ʈqLHCx3!8eDBx˙Yt1rK馛}&xYf܁r$\󃍛vtftFDGf +(ٰPxro:G݋8$  H@B? #QwwuH.EJv;1Яx;!rH& !BX2Eo QPunD8s*0p&D j uf$ jf 3idžO!bAgAGb5:(RB3&]՜wJ\f}XOc==.o.$  H@N`=p<^>8giϡ)%/x'.["m$K/4]p\ #y<Ӗ@I $  H`Pp#+x]w5Ow^F=q':؁ ؈Nx7ax\ 0*A IM$  HKO,=s8.Nkj'H뉓2BU D<0vqڭI@$  H@㏀~I[9b5*wk%M0GxVs$  H@$09Lzb 1fg&c9ռ>{D̤$  H@$  E@&7$  H@$  -$  H@$ (F$  H@$ %[]$  H@$|[H$  H@$0cߣK@$  H@"o I@$  H@Ɩ~l{t H@$  H@@[mar# H@$  H@P-. H@$  H@h-Ln$ H@$  H@[ %  H@$  H@mPɍ$  H@$  H@cK@?=$  H@$  H- 0$  H@$  H`l (ǖG$  H@$  E@&7$  H@$  -$  H@$ (F$  H@$ %[]$  H@$|[H$  H@$0cߣK@$  H@"o I@$  H@Ɩ~l{t H@$  H@@[mar# H@$  H@Qꫯ{.ˉ$  H@$  H L1i)LO?}hو x>p8$  H@$034Өx_|,GGbTK˃I@$  H@&04Ӥ馛nTgJz H@$  H@+8gyQ9 ~zTNH@$  H@ƒ,2*W f" H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯPO>d^Q8ZwviTSM5OrcjPg_ק)f$  H@W\t7G}4M9MozSz;ޑ/}#I6}<#E]0ot?{ɝs=wZl'?ɴzif㎉~tP6xwtgc (/}CJ[mUZr%d[^ve^iUVik^衇J[ou뮛>ύI/tQGc/8]{k_Wzֻ8:ì ~wN_rg93`up$  H@ @cx]vI?MugLgn햦nAN3YsE'|饗G?2g=q#Ն+r+j!Ҧnn֦ԧ>gat!`{vi2bc_״2яvUWM7tSuYӽ;`xEg۳>Lg^;N$  H`h~vxuu;Q{G"!yy/| .OwO3Oߟ?#k,/;vϟQ K.Pm]:C׏sa5 oxCZ;YlDt3oqcL1u6i]-~cuP~iEMs9ggy/O=\#N8!wRg*H^FY) H@%B'<Yoy[&K~ DA_6x%:80qV[m(-9::N8HxOX5 +w}o}+-re:87R𣑏+B ЙDX|0hw1ܙg9JMc-1Op$  H@㑀RA#4o喜!ft26!=ؾe}tꩧZs5''juO&:+Z2~6K/H.Bx|ۼ+oDD;!\gMou< &3x.& H@P(Bfo|٣*;W54acx_i,x8owoM|;c쩧J?^8G<3y=ƳK7?'x"[ҩFW^ye#7pCc[2BO80Vm$<v[fC&k`xdlʜehH٧;R64wIHYNF0nҫRҘ>H;ӳeYE#T!Tk7d<<۟r)x^{C,m$[lјQl/~͏992x-}MpEp#r23"?x;Ve7uY[~0::Fq?=`H'4itpoS$  g JȱpD0%YLXhqtU6/4""Y箻F|t0S{x<vX$'Bb29Fi#%[6/ LDnC-;t` <87I믿~xYeS.?`u]OtP_5>(3iNhB f1"nꆖFu x:!`D yahA9䟼/'uoS{՝Un$  H`PwP$QU*VFc=ydа,CC!=Wyl >ƸYxJ3prK^NDzSƋJDcՐ뼰 _?`q"+Cȉ@Һ-Svԍ3:a\ x:.igE"hTQ:0 wЏڱcA6\ϰ&oLuz c{A78)먻L$ ^$0U&pfI2fPU=ץǯCdUt FP8R#2+mfxef+y1MhuxgӨǘ.}N=tB]H 8aӄ# L)C-[ved~5P"ʺҪN+[ ^^_ DaRKLVFX >fG9! s7TӀUo۶{8,ONt/ N :.$  H (;(5V24TDu)R.g,-㪛F{[Y+?11s4,E!1_5ZLO0z471\!:K[" 4(m| mϤfXU/-;xr";ƌчcDE\E +\ VcrAgs8`Uk4rى/QF+_\C?܄[]tV\{Cx^q~G¹1'LX780>) H@@/PwPjP&"xc|w; n%Jǯf)^9.3ѡآ`+}VSH1τ#3=^U2}5lUVYgfgHc|@3Vh xAzxO|Um Y+ꩧ}c9.sh_: qg>xb0/i4T+J:ݮj~#Ct#>:UAc#'u{fY\#Cs#{op|9s(TBQw8pRבv$  H@H@ao6VC# 3go2cqgm<csKCtlUS 2 Gja(=J1ZzٶlX@gf C 0&(Cxb|BB19n3v>Dr^YCJ-*H"mVvfnG3(ZMVM]֕fu ~7x!:^R$5'۶(ߙIp0)C\y啵 #% yn5ۉ'xmUyuYVDTjŭ@zJkƋ!'۳"uDönC]$  |J25j AK}|78{Y#+o1yn1m{׆~щ! k'l;'̖O/A _ʱX蠃!o[v9//!QD T֕xy6˝xry晬T!^'\ %Xy}T)Î:]D}!މ*aKiezu߹7QQ::^ c=;OgZ+gGsO ފW5LXIy⚡P4T) L} 7$  H@ .O<2ULcp<@J0ҥ!`<-󠕀|sZ:Oxͪ]Q֎+]wuu}sY#<0# fml'y1'coqcg0at`;c2LE]z%FEvK3܉o(6~$  H@ㅀ%Xd'DOL3͔Ö]xp!Z3>ҤP>$G#x _SrqvEY)bG:O&o;#rFxcDk=xxK;!sOji#!Iw\N70γAP!)1\'G3iq\::U o;#6nQDcAi֕v q7&/xۏ]@ <s=y= 3ڥ:X1!u!FZ䁺ɤr;Qu\;Exo]z-k&aO(2d8]G`b=zBCG;6\7FFOѮ!In`?;5/`zʜ:yP 5$  H@I@?ѱ8ʞYd 6t$  H@O vRF㍀~$  H@m &>N'a6&oJ@$  )|`x+# H@$0M|ęAcf'6>lWLmQ? H@$  L RҞ$  H@$  4|O$  H@$ B@?QJ$  H@$ &3$  H@$0Q('JI{$  H@$=]|f^$  H@& D)iS$  H@zK@$  H@D!(%yJ@$  H@@OPty H@$  H@(=O H@$  H@i .>3/ H@$  H@~) H@$  H@=M@aI}@IDATg%  H@$  H`PO<%  H@$  H ({̼$  H@$  L RҞ$  H@$  4|O$  H@$ B@?QJ$  H@$ &3$  H@$0Q('JI{$  H@$z^?3W_MSL1E0$  H@$ @h]>gyb}b~u$ҋ/#q Ӕ$  H@$  6wꩧNO?da<>Q)F" H@$  H@M!?L38 x2K/ƹx H@$  H@@O9ѲuG$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  QrK;Z{^{ 暫]#]uU?f_.$9W^y@?ӯƲUVY%9眍~$  H@$  FE{O/3Θ6x6ۤiCY6} _h~7-R[?9VX!wqӶnXv'[/$  H@$ X i%E}*;$  H@$0Dꪉ/=4묳G oxÀ5$  H@$ @`L_rn5\3/k,{.=裍>6~w ?|NrOs1ǀ $  H@$0q'/6d쑏O/xO|'  H@$  H`T K:}o{l_^_$  H@gIZ]v٥P' o}kcIcڿmo{キB -fa_y}fJL7 ;ll7*8uu WK|lgy",M6@#s5_&,ȫ {,=#{s^X[y|ꩧ5\|+)g=-2yj" H@$  H@}C` x3]VZ)uQ 'tR:S7s;t1Ǥ'x"jN;픿O;Cο5r,g=63/xW]xi}#yvmn&nFK/MvXz'sp~k]]5o&w}O?=:9+95G:([&/ΘSӟt_Eeԝ$  H@$  q+񖯲*ϴFw߽Nvio]ۯV_}/;sk*fɿ袋TSMwS#=teeceoDp V?)#|tAil3K@$  H@yVxٛsϴ.=_WҎ;X_x}Xc?f+ '>vmjfm-H{,\pzB&Pw6l6gO}_`Oihҏfy<pg/՟ ?tMWͪ%  H@$  q)[oupwxƫo!}(-oY g~_5{/4,guV7X|g4ڳ[?q1obX?X#^eX_v Dt>`l7cC9w&Ƴ5tUWC3<1@䃰z駉 $  H@$PG` <^39bRŃoۍ >U~x?яt~y1^nx&?>{kPl9^裏N/Nw;믿~z衇!) H@$  H`N6[zOxL\Ga8?akM)c>x膀\z啉fc,[n?g7駟> .HL{&Չ>O5BZӡLt|Ncuyҗ|<"'gvM$  H@D'0&]P3+<.ULl;&O/~'N=ԼPvuUdvC7|&+S~ !9~cUlfuy_z# `C"rrH@$  H@hƭGl2Z[&U_ ۩0|3y~%gY0n-"~cc6gy&jk馛6~7뮻6Ҩ#yx^z@s2$lr=y",2eՂ o}[]zWuU7|sb@\s솀'kD0Wʻ3{;߅1a:?~r!'?I^_'໙y9|>4L?;C ) H@$  H ; x㎗+V0+x/5fK,DG$ѹc x_:h`Rwjx 7ɯ+Zae]\w H@$  H@}K/<뮻l`0gf{lM6_|żptP}7놀g{7'袋WUo^!]w l |7,/ kUuLhWNp[DIh$  H@@_:]{U<>6lx?90&km_nxyep8r&a&X^kau!cLZAɡbv;"b{?%  H@$  *RKՖ]U3g?Y袋3O:s' U؟O&+; x g?r!}?qgO䳙U) nY^mQؼ[;V$  H@gyށrf8=ߌ#of x(B4n}1^Yǻ_~ح;֓Άn"M5T|}@{F:<.O7XEG a$  H@$ЏM<]zkg6 /t` /p"|1N;؎Pzf'}^Wc:wpܟ}#(w˯£S!o!|LVԈSO=uV&$  H@$  ;| O:ϻKkex<ݟg<WژP.o^əgEO?]yMnjZ x4tDs1(Lk 7to$  H@$   P>QL3M/76xK;M8c-ܒl&Y=N?yykk[nrU{.ϮwOw#-y{.첼b-~Ԧ ǿ/OϳG|'cXZ.$  H@$ &0* vXJO?}o3 ՙ05gn믟gFwuWzG2Αyo!7$  H@$=^#}:V_}<ƞo5;HŴ%  H@$  Lt ^Z? /ao" H@$  H@G@?z͑.xy_5O>m_⮱/$  H@$0#x|_N뭷^W> 3$&c\sΙxīx]c=Yf%) H@$  H@L@?p_~ymžW.vM$  H@Ǝ~؏ɑy\;tw6<̓z릩v$  H@$ !(z}::]u剘) H@$  H@~( H@$  H@O@eH@$  H@ )J@$  H@@P~z$  H@$0('@!{$  H@$_$  H@$  L PȞ$  H@$  >|g  H@$  H@~( H@$  H@O@eH@$  H@ )J@$  H@@P~z$  H@$0('@!{$  H@$_$  H@$  L PȞ$  H@$  >|g  H@$  H@~( H@$  H@O@eH@$  H@ )J@$  H@@P~z$  H@$0('@!{$  H@$_$  H@$  LBN30Cb)&rOQ$  H@$0tc"?;#M{ozgҔSN7E]4-iKSO=dgtI'|0 |+O?78-C㙀t:*ްz56_8{uM+Bc_Z/lXO#8"/?G~4s}s#|Igw|Z+cQ8~t7#if/RK 7ܐ}`8㆞{HG {7s9'gu,`m^`hǖ xַ`6쳧6,})|;<W _dvcI^ȇn^ʊe?|zQ6^<~p[c5Ҷn8,zhO}*暍u|iua?~6/?+ک]Za?77:rZ0t O;Cسz̉nΨvm<=Wy'/<޷׿S#G,Q_Pm;^sLs:9"bYfI믿~W}8uNgbhk^ .A)Qv[:y}ߟ<Fc.K?OOonʫ7SC+x9:}،;:Ҍ`؃/ꏖfd_n]O{,/JϺ :*cYAig:ޣw;dMG>B0^:k6>ؤ/MsδRK=g6l4æU-(RsOd( ˠL3P@1^yb0c10ЬՆ oM|lK^rx&K\pe3P9-Ju#ot.""?1|_.yH;>ZLNrŵsD+j&3fijwkGp;r_/,uVáؗm3xV~8Evx6w}w?'_by9f\5o_)c2"|_ͫxf3P+qNFs(<˺$vtrױixH$q^eUҎ;,ɖ!y|Gc.@c.zaT}o铟do{NN{yMuY:V Ue=˃I:BcuOC""8x:x5YyF}'aD0qc11^~iQ x^OzgcpO#%[lx\xu5y^x=|+\!nIΆ³~6l=ئ2x3 ':#6G#Ær}uCH\StP$U?Z`Q317YD0Rѩx8>@b?uc K?kg&ڸeM'VmI?m*6vqiRvm$? >Pg=7p3*g,#oXly}J<[ y1F=/gvNG xz|W^3kuC)/je-D:xei^¦r! %/)^Ή:چCPi/=ӉO<q.h<\ݹܺ`;?u:NkzTZM@' · /0ϛ:K1\a- [#9WDs\s\7eS)~>{g^Y0u&Ͻ7lӮstq/': ϨG; ڢi3wta3gmX7GZO(!Eq7u .U>IvOviFʰ]XN1y.Eά@)<ڜ\_u6g8)sS)hG⨋6uuJSy=lZ;i5^V*Qڤ+n*?ZJyAV|Y V/})7*bx`?!y晹מ^#GC1` )Qe(}nL&ģ l2ip YTn x1>6lYo e>n卲S8l;O BbX7:)BnqõiB=Ú{HU{GMUS.s78vh>JÇy 'z뭳)t̼ؤ6u)0:a3tdz,sl /u7zJyAV<"DFY=>"MF0{β0Ôz@Ǫ r24v$|e>e'ou͞I5彶NPZ;W7d\w;gjcq!~a<ϹQÈfB7o8b(Uglu{USyNN=:C'iV:׻ޕ;j36 )5}|fTDkҡ ~jf}btbEGxSסGz"j3,' },/.]#* '&<aPIΩd)K < `PM0C6_gAA\iq+,L+½6͘r [xyX `Y /lxW Tg%¾W O 86zjS+pD it5\?8FG[\ hDP|>ﵯnd,"2X?~T[zy@FFEV]mo&dz:2?GpfٖN{Q e<&Gtqd[tOu+*y7=3d~lmmy iۺ 'l}BV ׸FzMQƐ6!({,?lj c' b?zb[' Q00M{Pe('h8uC6n4-i粘fkh|=CX*5<m|GDq5EEd.>Z=sLmޯ;бJ_!*QM%dA&k백8Æ}  V >e$F߷# Kk!"4(FIbVe-|d8ucB*Qۜ:j{O*'S3k˅0Hrћa: Efq>phҩ m锿ٱD:#9xFi!e^r[=i?ɶwOuw8e=M~iNDC 5r4mX .Q\mL[;=Q|/ڰXЋFY"[<". gp#V'+w<{I? 1"0f>Sq]ms xxǚUFyհ4AB AsQGէ!L6o ֔XP7^( VF#z WFЙF]aJNIub uOg>'88Jc.?kKpPI9}iXy;bƭ K۽ϫm(k|p\tBi:A9:h0ο9*MF +=\T[ޔ ۧ_Cw2!.mmqΟmkWd)$_8E?&<Ɛ. Nh0Sh'峏G~yx1 Aǽw&<_ssy㤽T}.v>g:=/V[}"pv|e_]27ŵx < >N)&D;~qM g8'E}9> x.|P0~BAP05/ޑo,CDaja2?mH6>y#]4[9U;yܹ#׽5s݄1O9?y!oxv /$p"U F:guZhb1RZ: V4Q'h6_uBfU~amo調]y<=0`sxlQ9v摞qh8yJ9XٱRc)v"#Z v x=dmm6EDz䅺F|J1 +[w<L綶IsT.KWnD[/ްmNKS!Q u geQ:_׽+,@8=ya^DDRPF9*εq\/b%jN\,kqB"MFUFŢ}Xq L-!_pŽ +9R'$HϾuK[&m֖8nS'#N{E9̼T޹̳UNzP';?ƳqMgpA1(0_6yk|b|V)b%,_ui9fIzhx'5 IDATW8ʩ>{=?I{)TM|sylЯ _+Ơ N'E,%@'F{Gs-)gQ dl+y//%qtpYGH 2`K†xio:L42X!KAoMƹ4c}:9/Ms ClбAx`2Bk 2isr:Ǎy] `qzyA>q g.DgzF=gU63/v}C/~w[;iBqiԩ&C#x6b xz=lܶU'~9ZݨM:<;,mvt.l'2I^nq\GنJJ{@)"tXRe1ʋ_x22GQ|gU,5Fs,7((bQ8~3z|܃:M> x#ynjnl !tٱ1}BF{&qM GXMn|0"It&ɟN,:³ZbҘdܾ/,~tqnC{܄͝@)P]|#{!PvD}p!Hyh kYrl0;`hu9szMH`˵em,SZ8G~R_X/v^tGmsrT7jp\N>GzD;08lԺ^B65: LQopLRfL%{Q8FGvt0ƈrex+H!7#w9+l-4>[wHma2iv q9~C #Eym7`t_}9bS~y8kj $1?hF l252.|;As]\2e~Oii}=4➥]o8>s{9ژ|y .Ym;u) Жir9Gݡ`-{m{@o) H@$  H@{S_΍nte='$  H@$  L~^zig?_z뭫ۮ}ݧ2uvu|k~6|է>zZm?^`6;ն YgUs1a|3wq$_$  H@&YY]vunwxE6ڿN?ë:} 'P|`<՟j->-7ʗO|!R׾c=FIc$  H@$  X > ƫ& ..: 7ܰb{M6l͇(į,oZwu{o'$  H`uLw_&7p8k$  6}s./ԛvmu{]/ϯW/V+?^~_u[r1ԧ_o~}黟I&wէ?zc؊ cL _=qP$  H`(GG,G<bXzq[ ?nK)WsN=%r~s\“k!0m#HG?k*1s;ݩ?5OOsI{ֵU3{ 567AHgM7]cQ8—&RKY5?)鶕 eG}!/\k%6yaʜs< Ssͱ?̙fیD:."?4;M,=:>kDs_җ-آzЕ5I@$#x\"v$KW3jtOR%s9 [Zw [ֱQw*" jtNYxGED@fϢr̷řg<" ڎ8ZX!p| ?:wcS#>B0۰g#V!dxz7FQ'5 g"9~+_~zЃz>׷N;TOK'3 1>Suk 0D 1̸Nzn!:Xq uF9[_3Ϭ*9 ʟ2zPm\;IhZ`XOzғV^3eu7N+859"O,~VۘO9PO6g~wm6;*7jZKu˽}vaή)vc_Оm^Q40 F;9KM<j6yL ,>sχϾgqF<$  H@] ʦc{XI ?Ӟ5`uGֿΆQC,~Bq,'3PFd<7w=`C0&U*Q A|S.A&Mkub 8XQ8g1G!3#?y*b# BX8\#wZ ޸&uEqywY?Tu툷,"gKz5c#V; ξSF؟}Yԕ_5?10b!4/Q`c^0c1ctXUb 'Έb߳XI?"eNbD:[IPOp`!/mVN ¬,(w=,}iFT9Xq/.Q%;,w]+~ϵAs/S_g0Rﳐc=*NYs9ʹ9/u& vmWGIA{ OxB\; a t.M$  H@C@?BYdO(Ig:a1etvDtgE8A(٣*̫Dt7eOGTvͣ/yKUs:mu#)d]pՙ bc[̝ 5B~2ypr0 #pPFytc™k]E!x9gX䉴<:Qvwo}[{jF3 A?G,@8?!yHwdKDrDe;u A`-0x,`B]!aC#1yE)!fEobE~z}G}%# dE9Q*e38r["]"*8LЖ 4"⩯DD&#yp~qΔO8SGh3 'rJnk82G|SG;C{DűyD~ؗ-A\sL܋\'M$ %lt8!D0LR#eqɨ6 Pi׃dYM}mqsDH,ӍQ*Ÿn 13񈤛uGnkf b|}>B[qF)&ξ,#ʏ4q~ bǏ[q8ġR&DI` cMʚ{yhkVߏt|׻UG܍ QRmLDbuDp]A A$E@$  H`(G(,~%GDAt0#|t򘷎}l䋑C:!aFLean9'DNuّ%M.1{y?tۨ.sSY ˣ̣Fc]9ˣ#Р -D@a|S º=ILO"lil/-#Kqa䃈 -·x,~}ϣ}9cchh8i`@&ˣ18D? ^32r=è,ï -uXSE. &Y8~y|wPq/2Ҏ@dg;ލgZij;\oX5iڢp #YفciD2`[߷Ll$  0IzXJ.PF١R1…el\χXcy:E2F'm>FXP,"|fwS #P!ێQ{F'.&sB1F-V)eTw:c[Lhc]1Jyj!r>m`3I8b617z"e7rj q,_t™sDS4,BaCdǽf8042Oy*mM&7" OǃÀX'ʸt5E= :Oۚ&G"zفC4PȍxLgXLǠsTN>~B:c}#"x?%  H@XZ xOBj!m'm#T!Lfϗ!xFG6ez$8/&8&_#tF੟0l|I]3l@eiG b,~ Hv?֕f>߈ ` YƼsg )`' 9pxIeD/czC㳭`iO H@f~2go7aN8!u x!lo~8 !n_#_/N Vሊ4&>7"0"ġ@$#2b߈r^:5/0{,0kW(1 A;Φ~ģJ- 'NH,惗ɯmɽS!lK XÉB>8k7K]3eoNhkʈ>cϱ3G'B^ۚõetM$ !$<N~v-0^PCT1tcO2m9a,0җ4#{6wz^)g'^D0GqD-|HՖcui =.bBDa4Bn0dNjQyFY7$Ftn!P8'a0PvʗE"xX/>V_>0JcOd΁r44u0^Z=,Ӗ7CTqޘ˝aȄ>|y.l 9,q*hW_5<618?;<8?NFn(F]@"^?3a(̼|RD`iÈ5{IԥHc=|DZ?ʜO$^UB|21f7#al',1@M4!X~ 0"aդ|3b1^ a*8b:n#tSLQ'ASLwJ!i?"Z-T;Bc.B7yqrPa`QD AC'L,DƜnN+>sY|Q8 ?ٸNFJ'0LBq%);lj娆8G' +d߱9)x *ޠQ1~]F@~H<.,摏ZIN\#F9yőB~{^ܒ<0]OJ#φS vib& H@PP&VdӕfI Ͼy䑫EC9bn3҆"<!' ǍIO[G5 {Br\GB,^|-̲,r682]4j\#7Fb9:6 DIO6‘Q~ +?GXfٳN;-FDاmN~D*(A!# 9`}bnh%Sdt7"! \ˁ3);5FQ^,R CRWqӔ }5`G8zeS$DPWBEDM_0?Q).}By!&I7?]'0Gvv&Ϥq/L#y-nQ#6+OwBkS3ִ^H}#ra˔V'0?{ABV@ k"SM) H@lP@y ٌyEԌjtHI^ UF%D5:"h7{>q݌ez2!5kkSM'{9d#ϱO"eAfp8``M9} r?)|l=B܇f0J!3)ߨsUaD%ΔRv߶-&'f>EpoV܀1my%]eJUa2gm& H@|PG93,#tfB]$f L#i6;񓚠ZDTO>h՟%  H@1 +3:XeFTB]GO#%MaLa`t cGB5 ,D,}D]$  H@/S=F$V_{ ޘ?[ܙkGZMֶYӾ& H@$ &S̿6efR1w0me L:Ȩ'i&VgQH\qg3Hba$  H@!05B<.(< 8=V@ھǚ7 H@$  t礈xVeUeOg2F,>|$  H@$  H`*৑aӔ$  H@$  D X^$  H@$  $  H@$ H@Kk$  H@$ #"3$  H@$ (Wb{$  H@$0wsWdfX$  H@V"J,uY$  H@掀~ K@$  H@J$_5K@$  H@P]a H@$  H@X+Խf H@$  H@; +23, H@$  H@+~%, H@$  H@sG@?wEf%  H@$  H`%PR%  H@$  H`(̰$  H@$  D X^$  H@$  $  H@$ H@Kk$  H@$ #"3$  H@$ (Wb{$  H@$0wsWdfX$  H@V"J,uY$  H@掀~ K@$  H@J$_5K@$  H@P]a H@$  H@X+Խf H@$  H@; +23, H@$  H@+~%, H@$  H@sG`U.9EՕ;jՅSFz{Z{-z.$  H@$  ,5 xߏީ_kZk <[=$  H@$  L???ĴߘPOn$  H@$ y$05͖|= pt^$  H@$  H` LM-+dIUWU\rI/y=$  H@$   1gu1T_ת /˫\:V[mU~Wm&cc=\o<裫 6ؠy9|gug7^ATO6lë3$  H@F'YgU|E]Ԛº[/w}Fp衇VC8j7%D_]ve]zjvXL{i"V;bDӳW(/D`ժUu{%1m4TL^ʔ|_~W[lEnM,&+ FxfwK/D4<#niʾk\cN|1~ 64"WsN6,K/?{]f7kC9s$.lZ>+tWVߨmݶGܝMnrO%pj7K`I\pՙgY;'Zwi<#ɝ|u)wi?H<@IDAT vSy92R"O(JP6Ӝy߷ɐB(C $!syw}{s3|{5]Z{F( noqKuH4J͒cߙң7\8s~,f{6뮴椞go}[[wߝ㬲*i櫌íiy O}*?ͿO>K'/I_}z򓟜]w|TsseK/4|iKzxgɯ*Uv|o?\y啉qƘ_ywF`H8_'=)QCR-W%X#fj ow!0o}g3#015:x{t;LOgk-0=y}3i?yZaFEOL6l~vT`y?!>(򖷤]wuw=PG}tO{һ馛K^ oxCzߜ8o~3zӗ>M %x'9眣ףWUxG:|HSZ|swy 3&âӟ+^17*uBItg&C{Te0F` 25nO}s@\@`k Olnt '$Yd wYNe)-wiAYIs\bڄ[1cF:sXE*_Xİ^uU]vl)CR Z ox_fe-ܒfΜ9ށr[og4l[-sϝA,>, #:YOXʂp}g\s͕h'mCx"bVZQ)K,D\ ?>Т.wҾx /SN /xAV{I[5)[w sꊕOICK VZ)W#C4:) r~x -^ ?<%=qhvJ6/' TiK¤n1MWoqư1v# aΨ4}"]p]i*kyeR3a+(*C~ ۶e$9ɂ!ϣx6l@"% qL{,^LAlZ6QEg"[l1c9&כ¨>pc1” ]r'}CN@]hƈO}+_e[c ֹ;+LzF뮻.{EW|=3 B$> 0mk;ڽ & e:*'{TzNQyUe"ݽ;cpEϙM6$S 1P0Jj[FLW_=KRG̯Q 5M(-6\Xg1:w=bna\D#֜2?}> YS5\3g03`,vaTU\% x0GB+\|zB;!dGbDu/{*>^WDz9sKe"KjH|дoFM_V4bY+L^xO`pg=k>ֶO*+F >!1rmF #ʏz1DMTضxUWat]``3/`[e2OL("gӿK\ 1.)HML _w:?GbZ>/[fGfs1dL?ϊw,\#n%Ux'A π_ӻψu]E-3y_N"L0LJn_V _Bfm}5]SB N x4`ҀtJj*3yaae$-WLܻDq8g7)o,x1#d 7!cz&Ψ'^ KM\;찜L s<>,"PmF( P п .@N#^{#0#8+yHa^ ]D`h{8V2A~`†#i7UL4}9²uq ㎐>` 6bL](*)}qg N%>s }K u>ؤE0"Nq4g|]vQ`c LGDC~ۤ\v\Ɣ*QFҠo6y}(%w!g^(ȗNs(eq[ȟ{~^7:7(*-sQoZ /JmHl#/Ì])~H5yMv>hӷȠmQigp7i3ڂ9+K? E0" }m̉TWIQV0`gKiF0o!| eL<ҚI.A*.?ΖGا?W2OuGآ1k K&kmVEш!bL"cɆ/Ї43Ug((# ^yOD>_"?MX7}PP^J!tQ&g|ߝJe~xXg?YN{Ϲկ~>pgEh)a`6% ' UTgzݶ? ^w}h0xs#DPŀEJa?]Nq˸[+ B,"K.&BIߦlL)Cuਨ)&=H[AŏEkeaHu!b_* 5m.:3pۏ:4r|Ń9Pnfr=**xnH j~NH+'P,2*/+J_s6c4Pk L*y1o(2nмCJy"+aUfSQH`TyXvOX"]#DA|S^^-l c-w:$&̙7P0dAؖR|4Y˭g#̂-u"}oq\e=͡}2G\+;HUkQҿc,Kx٥=vy.}+BmemmKm橱#T%wq^Lb2-iqNJ6}Hq' QI'xfѦm"y%6uz!0z\| Fi *YxvSҎ]c2ƾoUʊUU`UX=\y3 RW^q1FQ[;/#T)X ]N":̽. ƑRCe-7eqga|x0Q]o(Ȑ.0ʔ &f2 ԡ,,e~WqXq$Lx^H{`AIb,&Vi>bA-4qܤx+7 $>H~bF9k5s$KA2 BBb$).eT߆GEMh4wrX/)4ی( 82 KigVB8Faxa.!DB=aNjF@CXPcuu@fE%?;kGE/Ǐ~QCRMVc*c68O6Q*nɊdF M5 [!lGD(fPUͺ*c>l[xF,`GuT~sq\ xq*7׀J+H/Hr K]!sb{pOiJNY?e`EM)hISyqŢ`y!|CJW`%k, ~ߝofir<,Uq`g+}`m(jkZ$pA -;k1=4Շ=`V\K_&&eW4BNoZ6 Ae>w(V7S,Q_uS'r@易 zpkVzQ^P pbJi'``,^裮x@SDʤ(y+6TW*`/ #]ˈ tMhNO{8$-w}wI(qb;y2 Y 10ʔ7R/B~ԕ.`x_Kv8D(g kOMǓ0c %5˕|?"]jڷˍ0BKIw蟌 Z'{9 +?eg ^kD O?E8DBa G¢W]CikuQ0=bl Sp5HK?"> ʣ*+ހqX%DSʏ7#jg"7|RP ȶRQ# v9QL_&,{OR{]oJ/7~DNXR~\51^ս[D eU=늧 Nr`.5F+;y׼U TlM-Zߙ'RF,(䘧!a,#Qƒ;@Au^PtYcb;FbغqE"kV&  w.`LUQX_0(mL }k*w/D YY s!xPh~Xo,}" kk՗zĥNu`^EbW]ۣ*.hJSj3OW~D< q#He,GCgR>S\ʬ0<_Gi~Kw8OJ<&gLT x1ͽS[.Ms-¨L2\" \+ LEI D\@C}kڿ2(86g@w8!+ҏA_GMR)RŢO|"?&=YYܰf4˴~kUΪ~+R,.e QxVn!DزraxW61B+U ENGuN߲"ju/ŋ%Q`~DW*171XMY4Ѵ 7h'sJiLîyՋ_2I8 l(c 42RoIҗHA0oʭr/f`a~qQsKR_**cnQFE%de)=.7eb|AUKeZ ( PL1R'~(G 8!/;v/% u{rU~EEk˒Ye>P[]WXʀ Cp'"O֥Odܰ6SV&o@QǸCĸgEU.־*Qv".xZ ;?Fux`~P6kdfyz)m'0:s OpU>?x⟰GbXO}&\ #犎 JBbң7<~7R FĚ4dX>!$|L#PG?{ ,pVE}+_ɯ8˭*,Ϙ@X!+y5ҭR<`E5 կ~5[(z{ߛ]xw؈5C'χ<~c%A8i^~ur^湘>/-B<G+rU]a)p1lbO!u i1&х臋DK`U&_1} U{cxOy{TO$N|D*YL8[FW_n܂2^e#Or_UcTBB(sGT"V`DAg0|F<؎XPA˜ )efU2R&0Q"\|q,Ia8j\mj^7 I!~Jz{}kg)c]g1檽/XpfKLGC0?[/egGO$4XmYM 4>p>c}ДaOOop0y\N,4Lt#mF$&U]EcAu)g?mޏ FMU=JWq'E EF;?B aOkUP8ЯahQRqW`'m4W2'1,Qu54zY0H.ĄUSVӜE7%Dnxܱmy4]e"\[TTF˭Uy戺qTڦr㡋'2Mti]^ 0uVOQr#b=Ci  xG\Sz[M7« t>-=s<5} 쵏]K]\t< o? SID'~,nA ߋ.}240X2Рd1 eBd1CPfpBu< 0A"T%sjOȑ'-]wʎ;B:` [Q{7d\fcUE'o^ƒC, C!<d:Ì`Չ=Feyi eR-0(sA'HaQ%LmPT@Kh,DXDN['tmݳ8!|ƈE@,d`N@ɜHO[л L08Kbcċ1D[AqM)xV P(LtxXN%5-2' }aFxĆyl)tdq*GVk?R %K&-YQ~Q"km,kb2+Yx>ݦcTgT̽kP0}@xÈȼ`?}>&B1ϘG)+}/bAEɇQ_,}̵R2oS3'+FbH˻(*7%w3zuT1'z *W13jδB8Ac}6I|@~Mצ.7)Vec>A#hox) ӴO*R2Z#"(*_XC$ぅWī85]$>*e<<5~$fDQ0Gi^,U!1Oǃ jgrHQu>иJg) 9/*GI( %xɫXwl}*,9&Y /7s&[M Ui Q ~헅zÆy$G8Ԯ1A"aT Jt4xro&$b4Ptc…YQ9hQ ,VX^'KȢ?(Զlđ,*,hl⮥6u HxVy*(~~Y#0O-Nݵ nĕ =@{36ԷK {}o)q)aa([$YWBۼfyK!RtY+e|P'"#N6e_9[;c%3)B,s#2((3~CJKmYj '1#JHB <ŶH}{a)ƭD"}q2Tn*ߺRDbde>uA82<k=fMqN_sk>hӷ_ʭSK1G 5 C2N>zWS+y*_PHk`a-c^MkD1&X~xq#TuFH ex .٥#+ _*PH;A?)]xBeY>C:B23#߈8zϵ%G`x Br|JA M& }̄AˎBòC%wF-0 ӅF$&51Fbc?$֐*7i¢Pl0XfX8^~x|W}"tq{ayIz΢&TS^j?bьB'biĚ-b`q#ݍ#! ) $ .` Q&+qnt游e$ HyM3g4Ũc/"n-[[lB kI/}wsOxkK.$MZ~3#`0F#` 9- /Ӟ,!.KvZ^۰4"XTER~^fm6pC7M{Aq_^:@Dvm?aT9%U'#qHy$tMFm'YZ^WC)?9{7kfr-G}Jg}vBȌL\s_%`׼&6h=pNZHXbi_*oGm<ze x&jswg+ 7ܐ~`|G~G,>(cڶ'mv'ge.0F#`0CsNpV,X](!!D#*XZ"\"3-m;N?X@Qk?^CQ%n \`b|_m{Ŷ+)B7ڶ< Uy0F#`0 :8 z-eFm=XmJ_}_hYY{xxw1^XipS*W9"~}mS  )UF#`0F ٣ai} B)\sM:SdqpgFEc9"{ݜWp]"So>=Fq=GMHG~;!PXo}E;Qt\nr;~/Z{I93>xj'CL(-wyYIߴ?\meQ2Qf'|bBY@[ʾv pVɷ33f4? Re#`0F@x ARP#Bd4 =79THYJ8yG!K+>e3]P^ߧH`DY8k]{R.|r{ni 7llE@{j _*82]9%jNї z$||:+M:]AQF V-p `/BQ@xv%%Pi/iﯽڹ۴'Q&|°0F#`0  ;w]'Ҍ:;x*/^<"<(+V]Ly8+!dFXSiiVo Dt]S`XKm{":6Ԧ X)+ڂѦ<k0F#`D` K=L(TS{ja2#`0F{:@IDAT#08,[:&5Tt㇪0F#`0X'=^~N~7#`0F#`@J ?az*L0F#`0FBd!|0F#`0F@| #`0F#`d!`~wF#`0F#`Z `Xj0F#`0F`?Y;_#`0F#`0-,5F#`0F#0YX,䝯0F#`0FXo#`0F#`,,O#`0F#`@ ,A0F#`0FL' yk0F#`0F[F#`0F#`&  𓅼5F#`0F# -rP#`0F#`0B#`0F#`h`90F#`0FBd!|0F#`0F@| #`0F#`d!`~wF#`0F#`Z `Xj0F#`0F`?Y;_#`0F#`0-,5F#`0F#0YX,䝯0F#`0FXo#`0F#`,,O#`0F#`@ ,A0F#`0FL' yk0F#`0F[F#`0F#`&  𓅼5F#`0F# -rP#`0F#`0B#`0F#`h`90F#`0FB`̙3ӽޛy iN;oZtE&#`0F#`0Äx;#=c\s͕Zj) 33F#`0FA#00N=Р_ϟ_|w~h0F#`0F`:"00np^Z?}5F#`0F#0뭷N*8.̍0F#`0F';ym9FmwMLoy[r[/}%ηtggx̂#`0F#`}g̘cn =гtAh/|c' /C2g0F#`XvA?X8Zkա3\n:뤕VZizV¥6F#`[p׿_>sX]]#  p% x2'XiN௺ꪄ2,vuiypi%'?9/k;zꫯNkfz뭧tY]8#P?tYgX 򕯬 V#rlIp +@`,`z԰});$!ä_җNmGqD}_:#9 ZyP|F/{챉ۘ!Ԃ^\r%fWN-\x >c:~uQ{Ioy Ӝx̝%rHCh ӧ=ii*BD!еLT&#N:)]{O}vӶ倗Óe/{Y6ȴF`ߡ!2jiy晭d7tSv+|͗M74-"*_J^zi|W]u<sCeǪMpW뮻.3LмΛHyt5?+3fg<uYsx衇&(*Ҿ袋2^(ZjNi8~a84sGXfn~^8㌜Ǎ78L7Fs]c?exV6O}{> +\ߌ+?Y ;.3Czի&;1׼Vs#(_}ksiC`s ҬkrH??uA?%\2ÉG`AN|wxhw/K\wuV[m/9 |xXzK,-cя~-h?(lR(zNg!{e7 /xABX) sNz^] wwΏ77 z=~wC|f+!p4~0]wqGB=#\|.õ _N?O+{s&|Q,袳Ãn-O7*eVhJ#[H8}acuE!D»7 mA`Aԓszdخ⊳z+"+yBP[n%. .ҌWmNY JMڭmcM'cԛw/X::3ƨ3B}pe8fbYBo!ډQ}UDJ\/4ӴoMn]' r,)]>O#-A S^3e߱s|AT͝J;^s[pm&II{5B|Abc.kHU;110 ,CD=IO)OyJV #<~O} * F=N'^R&ҫ**oB׶‘:RW0b _6{nz0O `CW`"g$\/Yɳ>#hg=bE펅ta2?ðsq'g Bx._Wr9:g?;/>҃_>|Tvw+}_Z]v%^,!p 1,dԕfTs"0 _qa(DRSşh_Sx[.(W xE^y4"{&ƶm}qÍENn~^a>Z0(k}0(V8(-B |҇a,1W]vl^00]XhÒ8BEb Wa\0:*9ﻔ[}22ϩ`jE#cMhs=wLoNFx[}بp1sXJ8 ?W6X?Ŵ͸'6}mQFP`qqahaKb<{ Rx9SNsl̗n:wU}+UJim{Pz3.,M -֡ՋEKb[}v/}e#b,5s}a>Ry b5H(HQްK9'W)+Z {cR+.m &Ak?=+ovbb_FѪێYΙg5DM³6 \4#0+gţѯ(7Pj#c~Ff`W(}C?Y8;œ .&130E뭷^Ƙ,0@}aVuWfƈE ːt<5qօeF UP‰FbE@B@aA0GbًSs !>@|6-mi 0$0`'h XO9Fn.C(c=9pm6@!\$Ǝa(ao7uҿ"b1#p\yCu0X`f#~eS9`PڈG8A!GJKGyRnI1c29uFX#~D׿2(G^P׏T?}~BQeG#̣"y7fi"lvT#Wss1!({?J4oC0!\9G=ؙ(?[lqPoD@}#Ae(q: D/ҫ{(<|J8?㈶#x!42C`9tՈR.b7&ʋ@x^җP402ߕDpW8?@1L(Wʿ2Op@ڂ>a)uwcNgߴ $P2w jSx:Mݨ倿G@OevmZؤ+_,Nz0O `CWHyvȥ)7~n"cmჾoetY<ַה'zH%%P"*W  TNG`F I.B"b…  R|~=,0b\WQZDbaI#!GLW Pwmƅa= PXQ"DsȂ #B%fy6ц`auB8Ga"-păy#^Hۢ̀J]|`<)##ZiSqDKDPI !e-P"Rǿ6} }?m0/A2`O{FN^B8ʀM8bIBh1_31ڎ6}k;``@; _Ѻ\NG"8DX ^/Wh<͓/$˜|hQpUdR/ͮϔډvdnb;1晫!B=Y3IXD'R?>ˠw\Q?;BT{ʇ0!^v[/q!^RΈ#c~].r exy=1NP8Ѿ"t'A׺҄]bx2˯tԦDCO?<@5WF0T}n A8Sߡ#X{<-$2EeDG ++$1-u>gjQ౰}ӫٮ0-haYd0JP}lU`;ʲÁN vQC@`AF] mXS5lCy#tj$Ϣ4bK~H,b 66(h~azadh70D Ef!~ZLy>%ۍxť7VZa#Bxo(K%e auW}ymt3鵥r~ G<^+t#8\9 ķ}i^˂C"вf(GwqumVvJ=KpWBg,0JźW5w9Z!0^_]ۃ5Gn,j%[GfTW3]b;%c57 ǚ(5[czI+IMQbWGc-p$E7q֨+OL/Zy~UWx14lD.277iT+]n# 8B5ҫ"2}׸jѥM*,!+}!$*NF?vBQ0a}H&4? VO𡯐_Pe -(hMI5)7i~ &%I9{ aޫpdΠѧQX*@-i#B ^5˸oڷc,wܲq5 !/$B!Ϫip?s17V7\uKʠNC%a™ΔJk{yuxΚ!`|@a R>O}Tw aKĥ5eAJw7uĺC'X`~ e}l"]Q+D4z3h:fتS}rKQ\eX`&fM37ha9Ed[.zFg!׿=dN1锉`e>,<1Y˸U.Q+\p-|y}T7uV&Ѩ-/,zV0 i1EɻQXްAULcdJ qb;A #I (º B1C/$`p/BZSÒPGr)=Mzx=0:wrGaLe9Tz\Q~-Uګ*$*ʂg dJWgq7[]1ʛ2? o^ ^x0IPJD^xynDxҎ ^EW]ۣ*_Eu]; R (&[qT_(YJRqѻxad^*&7lWױ4DeP\pQa|]/%K? O(*sClSxUxt;J}Fd@]njy$'8M'<):F,whUT@Dˍ&=Er{0,n # &ZIbIX6^y$aZ5jG ^L.'hqURXX`Ы[J;*j) pD,y]y:kˆ@m eiOK+JbAǂzsHN'm%.0X@rO< 3ܴ (]}0?ď Qe^ ,cks"_rU,a-lv!k'Ue*CWYR@Kr92ɷ_ڎ*w2R!zⸯPx9@is&@@'x>Ek潪e{ı38H Gܪ~+ͮ:Wj'Y=v N`-f`N!,Tӈa9?)9_u+R[/WsypAX:F[e>*cTalQ/XPB`g`蓾_xMghJ1#c0&}PcF1īHyj,whUd8 d{߫Wy?>k[X<!+xT}ӟίWO|b4hY, ;? 1J,b]qkJX ]ėu*m:Guӻ~W\a{o`ÌF+r[L^{bV !Xʛ6BPOx Yu,ȂĪ|<CC2.1|!1b` PDVVuZ{ A:Ux `|c;WɷK~[g¯Ⱥ:#錂*EOX},_]ڎ*w%y$~2NA J©UY9g dduniQי^ivm"m!c7![0z7 XBQZ xI"gaueS8 zѝ-(iMac)' 4}U2 喲2V^Uy˸}wen MiT0+/]5DZ1#cat #:Ajd,whY(`vJU.O0ةCXo6'.L84\No|c#>fVL|M3L ㊾ ӄdI:V#mx'@ueHF9$,JF)mY@xĴ5n~5” 7*Xx0b`#VvTUgĭ(R(ko38?3JUh5wvjsOẺ[{IG]IR׿*Z=V.V33JCc5lX0KR<*1!-ZEK9] 1_ uʿ6*$ⶠr u+M}8funPF3XP <]LUp*O]8eb?n#P<_x zD V;d6m\z:Ϟ{  *n 9q7{3sy,` `Ȣ\.>ֆ@Gs <(\靮X+W;'`pHw

3+L,`RB53L'HaJP6h%, Y9Gm \jScTRna2KOڄq҅v9cyKXҤ1i.e\i3-նcj+Jyx!0b`d@]JPẇ:}suX#Pp2b}8 M%LUPhH}1C߄"_HkڋgMѠ|}&#``n"x bw49AO1WN*xX99yVe=0]xßI"+ߖX~C‚ FuG-ڍPnd徧pA[E5wR?s6">1W Ӧ=9L~D)*RX;`^úÚ˼ 9Q\L#,GA $^Ej^(TL/k[($KfOPd*S%q'>k? _<چb29ygE׹IJ_ڎ,Ц3(0XeJ>1ff1^!>b8,0d0PL0O }6`Kp w`׍SUz6ڔQJ<ƒ- uq><ږ NPAA(l];X_uV$R|ˊkkis]Y<(.c:&5ړy 9c͸oӷThG k@t69|@ȷ*{I|/鑎UϪҌ&=buAՙF^X [9^kMuuUΘG6A4pe(s>+*o藬_[ OAぇ0S S2Tae3Lg29QG4@@2E(T8 j;-*B!@.[6P E Ebvi:f,sNG+j3MUܴh{a*T6Mx &M\zQXᶯ׬lGZS2737F'_sk.[LF``g{ {M0& &rdyŢYgRx- ;#0E:j.?ot1pTA z Lug软F#0X&;j>AgG=M3@ J7ƴXij s7$nQdg8.[8d!W-kDG9#`L&s`nmgQPxxƜٴS7Au|}N|6lՙ:uIΊ#`0FtC``'=]Z\"--O(#`0F#`{3=|.ecZlc*0F#`0F;>҄[/맯F#`0F#`=ooIg7;s#`0F#`0㉀hmo{[m[,iuMm]{k0F#`0F&XoR{M3f(V\>ZkUS#`0F#`0 v:#7<-ӻB.0F`!po=:iVF%wQ{n{*^UkIY/)O[0>xi嗟tg⊴j%SJ'xb<")]uUOJ]wԚ7zꫯ z)TU#<2=i-ʬ^pG{,miUW|Z;3_j5V׻F`: 0mx QL)${ct72- cBkM'tRZ|w17ȗ]vYV 5\i}Tλ+ 3Op UTkPm&o+ܧzhbBÒބl;c?vmK76a<7 ң:jRFu]SK2+'"e]*oQ^{8i+LrJVnT׻YL ޅk/!~*l:r9Q![oMw}w~΁w 뮻.m馕>p'܂ .V_} ""9a`&oWk-B -V\q\$?䡇yo _~ybΤ >"3%u\la`G@ݎ>,iVUI}G[wU7t U@@@AWiAjh> ;A)$RCoAzb {;YYdSa>'g A׺ֵmF$ʢFO~^&8Cv[s~/xA/r\dxֽ2DB`+c; w?x5Cwg?{ӟw#jWZa{XwQG桇}c믽E/Kb9}޽ ?΁y-}k_x.z׻vCnpt$X=Y>q{\-w|g( N}9\xC>D}֙Lg:S{k~1 u7ͽh'oX>~^W -o9h\绷-}o%xSbB71u`~_^W6kU-ַu<~~#OGwJ3#{Kk9pì{U%;Zɝ?^W/|aw\x !xfSf>O{ \8 UAg=Y;Y"sx;0I/B/t d|y$dOSTODŽ'?|p{qhzYo\=ka+" -xFCx-`hBmJƎVyyg?^^ #O!CEO<Һ8b x$a\ϘzV73DYן{EyUOye2ƾu"_8S_oQ"yN[ppȥ: 3f mY~{}yv3 5 ڭ|cx1Ǵga\˒9iX*gCw<6s폏ոeQ1f ũcf1>+̖}fT6/B[.sQXC,3vp-ڨ߆x/x0#o|q8`&a-nqd>OPw8 Ϝܐ E *veH / CDqzիUw3q.o\_h,'铱1#KMim!?C=e{!ر *;woGrq~ <&u{߻R Ƕbv+\gҘ}mo{`8yxH6Zx-XDa#2p߷LIx{Q2K& h,RjPڷd+(ox{h$<(ZQ A72,06(- ;C[‹h bEQq,]ju2Syޡ/((v b ! !-AiBr-/q(?k|q'NH?dDaȆbt-](apQ&95ۀ@IDATom U\ @Z:fT4V bLYzH X#ga5_xe̹n/6 # T(n py~  5+$eP=\ kX>aP/"DՏ1޴EadkR%!` MgPi~Q);h ȫMUe7O<)c~_[Qxm1-;vߎ)mқ_l3("$2u):Xk?RƒfQiB&S0Zll2a=Vr y!etfض.gu/NG`(w;I^P] <, ZH,R-^} dP(~y#D9䙰PnNkXxNy{1D̑|*&+WIGh3X,P֌}s \4x#5aA{)bq} _nW,ȢRcuxP޾f%+ke B'Eڲg|)KqGs4Bb$pzV_("Rb[H8h1^2A%~A/Yl[z9'SaiSqCW yvaS3k? dƜ1Ȩǖ ֆP~n8LIE-Ł!$~@Շ!+Q<'si/֬XB7!/1)OAN<#%1b)1c{x])|=?~_2g>_:C6~oݦb  ?>#5 dƙ2GՌQku^ݟu/B`7 |('9͙z|~l(񛥼xyL݆f A#.(,|=i}2B..ZeaV-O~YY-   uP>3.n蓒%x[P^.xǸ5Z륈(iLBhF$5}xj!U+8D<+2~S0,P i-Ѝ5 Z>;;?;ta"L Ł" AYk< +$Ms+i둌|}R8@cCq\oBze(Re_,ڟCyCWʻxzKѶ&Ax?l7P5d(W`8;#3^F[٘k Ч.b 1"cF>&g4P䱈D{ {8u2΢v o?CD1J[N77x8^MnCQQ.wM7_|ƚ7?;-"NbhN2c|dz1e]IFx 9{Z#fc47N*hWqa:a`ysÑԿB`! |N>}qy{>g6ۥctQ(Czf#腰\YYÛJk 0 X(ͬ؄0vf pY{HB[ނ n\ yҴ(^ykC{?~ø'P [BOPfrvlJľ1x[ QY'lkHgPYW8 C)yeIB,g ,!4BA#8z%Ŗ؊AQ&s8̊ `$\oJ^3`L©'Y?|`{K -¨Q҈~{~SAm+qЯ?yh"_^YXeyѰoefa\$BDP?*47|Q|:f2kET͸BSv_FP?fUUN=Xy yi$7aʡwA"+fAʵ% E)̂HB xgxz$d!yL0͊|OOeѼ( ^iG?0S-(UcŸވ:m5&yMl xo~gpo;ϱOUޕxJǐZމXdEq|hS6PH\|l -{cv4`=Da(W€cwqx^q-1Fփ_ψȞN]P<eQh=o *_?.O#SX c~)2oa'ְ)MB#m=1Eᤐ_޲w0/ˆ?dؘu︧!{v4wI6,=~WhZs7)Z>)j {4S>)|~C'/ ZCH^`"ڕ &<;9B b=8}{^B7B/;O\zC:0d1tJLy&8Ikogw#yLrB =vnkc͕v<]F^4/)cs yMFs"!GH5P~Z.V#8+xbd1^+ e]RiVX< }ʝG['"M>!3zNh #^DqS2Ȱ"lwB`!Ox QS'W_Pm.uPeo7UJVcrHF(< /p˖'UxEva:Y,LYtf"qݭ(hZOgP{48!@c?O'!%uD]BC _e3dCrHus !S4D1tI_8i1~vZg)'r?A13@9d,a1=Yr:kQVW3ze1jC[{BQoϜ,N9H 9(Eiƙ(yκiM3H{W yo9Ob)*Ţ-[谰* oxgV!k!z_*$ WJy_UVc_ bSք iMD&b)ODGw\Z_px'P>B{=Cf) )* 1r~u!ʁ~FZi~-Ao'2 ?NQ@Z7EeK~#s](Sgs_9 ;x$""m{(Bě}n3Zh#پh A7n_j=G$ 7cRTZy(-2N-C`}`RHDP{́z(/gp1'U\A`Gۨ5h-2F"}v*ݑG_dOhy"!~}qhb`m>[< o*c|AD"G}cf@?AS6 7 Fa=L)cwqX({;y#N#kX$ۼ6aip9!!RH1(kKG_YkuB](+n\aR4Lqއm-hcy.s>2urk(s'=,#m0 Nh Q7cQS2bPެ{Q,vTTV7Ky(b}{\# x} Xz z^I'P}cNI(UdAUKyg<r|»{_0v_o&Rx ,ʢLk'U?cou㜽'BDŽuʥ-Qv{2PJpn(2`!t3(AG+Fxs$߈bO9f σ 5F0v NL33eTmRQ%ĮŠLU&!~Vc?$?eAڡ<`,R.ʞ)g2/ØR&qq:&gD s^|eq"Osm Pm\FD1aƞinW0H7|sH̢OFL1GϔyL AXe00"8R( Yㄒ$"oα"2:儰<ۇExLceƶD* ^C!9oߍa.8δټh8n[kʉY7ʼ~wB!Cnx0O7;8i4!?|N_^jLk tƖg(AdT9Qu2DY״-o#׬(\l/#Y/€̰&&x-RCkM[yPWCe0Z\2K"2s8Y׬{QV}op[oE}`QY$pPB-h"Br٫~WʀЪkʰE'HP`BX{n @ a'6"-LJ߲l;C)BQI׆YSͼN<8EPgµ۔7cmg6?a(y/r-~qH~CixؙUP}m"ƐyE_o؝Wa:m:C14īaK;)y揿EH?ß9CMi2ԱcL>2V^;-rO?37h(klNG$! aP ݂@)𻥧ثͭ0v1Uخ|G]ur B` m]±gm̡6ʫ adg֬{QvQKJߗWٻ c݌F?+ZD{E_Yg桰ew),tuno(Nh,^'XH͊Î=?9W  "PtI+ ͺPU)&!e 9gwԤItL8G }@Zjwu'ٰ#^W Bc|v]e@!P,)?|ͺ_|5dSbs(K61ʪ^XsR{9Ρlc{[_~ݯʖ lddwQ"]@!P@!P@!P T 2 B( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P]V B( B( B(RױWM@!P@!P@!Pk@)kץՠB( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P]V B( B( B(RױWM@!P@!P@!Pk@)kץՠB( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P]V B( B( B(RױWM@!P@!P@!Pk@)kץՠB( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P[׿~u/;߷Nzғv'?ɻۯ( B( B( BX'L{o~m$'9IWa@!P@!P@!P[)?[]OySvg8B( B( B( B`7"e wmG??( B( B( Bl|gsֳu_@!P@!P@!P@)Ow}?}w󟿻E/tA;f?o{_?۲/~q7/ WBw;ien|8>5㛒YzVwkܔW&@!P@!P@!+(~B9Y\wJuGuT_mfpܠ׾֗ɰAwF]gs{߻խnjK?={_?]x.]z( B( B(R'teVvue7rqtg3K^,g9F}e|#pG=?ӽMQ ިus}~@!P@!Ph>}+_)9qMF@):2+Wҕ:-}{{[ߢR^%mG>{^֝^tַ]bm~cTr( f949Mw|3\8|<;]W_[B޿;uo@m u+;NSw;)X9oz_?g旅܍k17 G2n>Ot^7o`;&9l_ߗMnҝԧ^v8)0U_S鹈`$/~F7UVד;+jWU_ _ o7&ӟtw󞷻ֵ5/׎{K].ySfg{5nw'?ͳf4Ovu%~~݌Mۺ?G#Rn[p~W9bn~_ow"hin\~u/x STmnsn_:Z< p~{KpmI>oחw܏); p9yζ~o汏}lwӞ]"j?Ͻ}g?жY < {,Vf tŒt3u: 7-K;3jR><}t%:gL%{6K_S /Rn|E'?I?nj !'5{^_Gۈ& ~2}sNlB"7txynF۶--V16[zd@9 Nbn. \vҗ;cP}qb,~B/?<v߅ڏYAhCU!Pn+ $v_KЅۿKk+y^{x;N]o:oa٧͋YD|8Xf}=q>;׽u{'=I5HTz^z/ -rɁ"d♍qxeiG}t=n@ȇ2X˷-eT<6w <ߝZsկzo?%zo^}Y/ax2lQ.WnWvr9 CQ.!Ye288LDT؉CzGκnZY/iQЇvzի' E*ҔS '=" .vN:4Og !ط7Â=evm:vg{ }V}wfR]2pQVF cED֘PAQ6X38j~E0]MB/ "6?uOX]ζ-SVŸO 3Aȹ<\h!<7)xN_/K{c#"$¨E-C^\ʿX%ZYv| ՋaS_0187̇!mc zV:} bhEi>U[\A=ۺjQxn(yi{6F|kR>|s''^pxТcyreYH;ƾ}xOeCHcsyGH2^YƑ1GF",Գ%:h^E^~wcVD;yaH1.XU x>P^^}W[Sr"=+5~o|SH#?;b.Z/ +,Hrx}y|N{zx9X,2B)8 zYR/CO}S7~cE~ݭϪ%ԭo}>l=M ]%أ%!f) &{ݯ€=OWroDxz=!U2{3c < Ehk B<Eie-n s}{7|V u,L|=cg50(cJ[ŐDGd0ia 2}k?~J l۔ԛd *l~_58+w]YBEhJ`gk9f}C그[67HX?Cs Rhu5ƿ\a m&cq͖L7?~B+i'W2h3 `*ڂwPF2Ym*o7,~n<-PXy藾6I#2{,c8\v,/[nn[T!֫vmXyz.m{|<" #eĀa.fCFߗs1{2O(HAZX:-T:_'~MQrHWØ1n}fcX>qV~c-˔=}E= k2V^[_v,.#,2s<7c~  I4#<3>b. Ǝ话DR"-$Rе/| \}Kg($FS~ Qw9,Q&Y0*B%S0 Ttc|;œқZ["<v|Ԁ`y߳x1MFPV]0u}ɔ0x3 q?9紡x5Y,Є| Ǣmv/|7vH?g-l5J:a4Dy>X)xe JIG0^(o%岔/[+:¤-Zl۔K [$t t}( z?DDLPDP؋xU} u XyQGX{4(xWt"x\e"厵 #sYP’D*^(~C1]sx]M!Z#yl3:F'|:C3׌s}5e]\##sxSouW;xp,;Kcs3͘Oxra0Bޞn׌s83/`a|:B&p:p2 {x'/2׳oy.;˜#OkK9 ZE.:!W1ǔMOh'3hU>+c(+>IRF:WQ0E)?g}ףOyS.-w"K"^y˓ْ-Rll>;.9K'XjPrLh.Jiȓ n10,!ee6'Ye/x zm}ӛ^º Q})Q(mi!$*Qxn #g ̴Ċz (U򔻝m[,+WQ a=P< !1,tzcF =$0;3f⭡$ʇ@ dL\O%~, BygcHVN cpaܘR߾#h aMT 5G=?1܋(y-ڜەx?)?d~n#P2$e4N7k6h,y.xg ќD bhePh gݸcx173iOcn3 H Fy,C1W9}!l~jc1wϘ{ ^^>5'JD?QZf Oqf>o\d]\lu>ez^XkDf!?ediڹK= c/ [~Rt5KzrK p/υ&*+yO!"xxdl yf "4:^_UѠbĴ|3+#[BJ^ѐNv4cB, 1b|xԣߞf#0' z-\}4Ps?k?! ExBlO[f1KsDPXpۭ1Ye!R E!Vx~-ݰm++ - CFa,r)f %긧ukQ}+m›` xQFOq[M`lTJȄ`#VO=lI۳WۈG.\Vg C1 x e<-OoaFC[#vd_n+wm*3"[%\LJ Q;_VwO'́e [Dge 0!^ bYF Gg FcsE6)%/9F-<0x"w() |(~m^^Ƽ$CuO)F+ٸ((يfeECxN;<Sgǵ6͵ Oٶe ` Pz)\YK7f疭8zg+oËs{FW < !;A ( E@ [Eh{V b"|f P9Z_2ca߼&,f,f y;~BABn^G)cy^C3c B=bdsߐ6 JH>-^y }Nh 0|,`E$q̸CEpxżNEʞھ8(NCk9(SET_^Px9nǃ9)MmmihM>oM^̯\/25s^S!cr}#P > >Nj<=C^װb9WGΘg/9 \^S ,esme];g^mgۦjB`EqzyM0^;gC=BY oZɏg]>P \{m++T{<ͫ=U-R-ل [j#"WWmlw^Jڑ0Io)1=ѧĦF#B`+^|Ƣ!O!Z@ js!f999>mRVǫOPh qC#y2I3YyɆ,-z@NcoBs=n`B֟(3s\oQyJ}#{VPct).s%iHu9,82{s}˞0C|OSƲ;6T7~/D9l2cسy~m~|`4þ򫺢Nd2>5KI>5` %>[5s9 ǡghh O(³"/rCeҾ|PܐAXԒyȫdžrvϛ Mc~Filq/-&ssKL} SWF|rl 6jG yԂ[,ZYz׻J AY(oaNTGEL0TE?LC'>Xxy+(cOæԳk/:)x\‹@/cIE&(;i 3"!,({b6eҿ{^DCp1mÅRqζM) DPx&F!AYѡ<'bత8opAVESWF_qpAO_2VPB>cC'>G` a_a 3 z`VJ>oY%@Mb D̪}&PzgcAx)oF0}|WJ@Wj a1S);a/`r#2KcJꔱ,ysQ|xP.r ^ByU鿨Qܔܗ9){Q EJ{! s4'8_izI=|`ܙEl4 W  ˌ0"? Eeʞ>er;lQ1p!C${-;Ke|fhn1i`K^a![8}H> EzsC5@cs)~"L|Һ) xwߨk(~Bmo^&Ȅtz:ex˚Y{s> XX"ij@ -&I)B8ML{w7Ƣ,0,0'Y(qyE81,л>D&tWڜv^Y3l۔I&g"@QBa( !B:afB'bԋ)aHus)OS= !l52Mŀ@iefoA7 t?7Vy9B)*}2,mO/Ɵl)Pw\k#dq-/U{Ʈ~Gٛ]'L*_Ҩ׵0<" 2=;ܱyN;xcj_1,LB!\C &NZϑQwWL7l})ES=%#!d6ެUxv)Ɣ <8NwXS>\9*̼1<_DПׇ>)Sڗ Cx-bl\%C\G1&|_d,eE܌Yd GY^-P~ə"kVWA}Yıe|z`/d,(óƳF,KYs[ݏ@)p3xŲ 730PJM8,+Қ&$Ut1&} 7VyY˿-B&`{K<ʓ6 ni;"0W³#mFs3o @c QL(AA 8&L NEfwZ!N=B Sg?E̘fw`?Kx PnE-{<-|Ci>@s=57eG4V.! n⠯#ڒ~XXcm Dtx21˴HO9<d^)/3"g.~/SSQƲe\#ɼ eE܌o aܡ2d錐c1gy6^᝙zǷ4u~msyA >eS,*f=/9-aדb.GmYVD1 G- /eYRoQ%FEi/f`hYm:Stkmxb(>0^ cGaQ6üqa-" i[ޔyL-\/o>y8Q(K_K*-SN1`3-;OFCkq-E0h.+ˌh˖=}2y6є\2fP 5㩹oQƒ=#پU)dwJln=LB $*DW@!PBF3;u-[o^@!PLE`xۃao[IvR޷ʻ( B( B( B`_ hPY@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`(~T@!P@!P@!Zm* B( B( BX;J_.@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`(~T@!P@!P@!Zm* B( B( BX;J_.@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`(~T@!P@!P@!Zm* B( B( BX;J_.@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`R׿u/tғ;O~E@!P@!P@!P:!e IY=]:y{^-fg?1}_Rp󝯻U7r7vojVyYzVwϢun*ճ@!P@!P!P x ={{݃B2x߸% wuzh}o)> ox4SnlGk3yЃԽjn۽U@!P@!P P vF.Bg>Ӈq3{K^%^獂ՊWVU@9y]}ȇb߾@O[&r]_W#WKQwӞv BXx)Nq5hM5a/~ѝd'1џ*dDAXf_ tNHsKտַO{ԣ&[_Fo~ww|ݿ˿t/zыӝt6Vo3JJFJ̴MybXgF??~w~Cv}_sկ~6m4W̾j;O~7&[[~R6>v8&^ټ/~ʔue]iF{|׿@e.s^ܾ׮}]wt'??kV#P QekMozf_ ueՊ B `^Оͦnfw-Wҽu&{w6 uGc}K]R.w6m4DSTmY$B裏ynwmUßm\O9#/6/Iw' Z@G02-{'>*m><ַ8"g?Y~sD$87vVDݼ64.-P0:H *ovg&p7)zng B~M$B/nonպ;d33 r0gnpLfsx5ܘ46wS @0C9dK%oUm>-aJcMZc+^ {.o"oz;ܡ︔gO?թNՇG.pc̨?|#կ~^a}?^^3>3 VBw-gz׻v%J1u{[E.o)$2׽nw]zo3<1ܶYMmw ~71čnt~Rc"G?ڇ׹ur}@Y1oy[\:i)vQb%Эo}49ͦ^W Gqu)b׾ 90ο}룺T}r.pk+C__:?pn%jNʊlρټqRe%Oe>ݫ^E KaHǔQ;Y $R)cDyYG <֔h<6[!7Rѯ18K(=;U??W 䃜{[>t? 4fo`p _½nٳu2Ġs\ ax:F4GULЯFjl/֍h+$x ՔW:~}J=P펱/2|gZ0'6"7 Y.|-A%E"~KЪmO Űq4Ѯ~ ,kV\cWi!poǝCeƐ{pm1<5fnq[lO%e0[ʉdz# ;ϙ۳xhNExя>9Զ s>"k;cQaYҍ2Ս{֯xs/03L3@D8QFtcgųpPo|}{(ېGEd2˜ETp Huy2|=)k _ƂriIk$nїƗ3^=ȸ7\ [#_f5,ReTwSzxxx6C|9Nm^xg!0@)c̸Y`!vY $cUy $?q+>+X7K9`ŸXB .Jp 0'K\3l^am C} *nCz"&c=m .y"O{Cΐ`B׋ (FC 0CD&Xْ[ sZKUzվw0:.Br/}ioƿE5y#AWW E_W g򳵦%ϊ1>&07E@i#.IQOLM<I}{{a-_ܓ_c`^Ҩ:+ا m{-ɚ #AiGo󃢗Rj;ND[2},<#+o,sRdMOE SS?olzŶh&f#c8b7.(:d܌Q~|%Px(>A*u o>mqE?JkB)-B1!ѡM76 ꔉF6[|BC Z`V!=)O|C~\5h{IpbD`{o]fṠw)mH@}|c'L)a "rFa({j2~XsƚP <(e/UF9YDh[D}_ $ ^iM3Gw^Z}(/ۤEyNn=qe!UPv5n\< Oha}'AVXQ~v0XbΠ82f}1WkĞ?kSsQaq%qaȑ2B61sNˬMRni<2 8?㽜 ѬB B BBPfEcX>u Go.9Ix(зhys6،9?`EyMnj"x@[Q;F]a՟YeƘ=Ͻ}E2.XD5@yY(Aq(̖P^'vdR !'%?ڡhl}3ˍ3j&|͚l~ژҹOIkj1FhO./dz~ѰoGGDOx?F'1 ci_V6MokCC|1^+ ƈȨ0֯?af$ʔqP)'$sS]wԵ>ƹx8#`,2{Ev P ?q{jNF #L(&!Jࡇz!!Ŝe +O%Q#R$@mO% dP@1h^D[ 83<RHw)yVƏ1Nw o< !T¢c4cyu^x< Q)Ƭ!)TDY8{dG Z4A]=9PѦ߅V"P tq,"-*RJ-NrX)_y? CH3+j@w9G?5J{Т a{ ϬeO!wj} w `gOd=ClXoܶY vW,Ccm!dTE{qyͺsN4)9<1=X|x(|F %g|lE(2gmF O-@O6@QjqYcezn*ߏ|YwԵ>X8&DBZmϟ`%S~m 9m}/ R' xBH/" <-oڲBгP5kt#"D7z] JG豹GD0晓A❄8.{[9k?WoWVhy<@J1rCaο,X樆y -Yzx~f~ ㏴>Ƕl)sNVe K׃"c9asSu7=C~nW\2ֵ{Zp D-|mm]lO@u3x vUl BhLoǢ-lxԧn$execd <&e%BO 50t#!2`Xܝ>OiDK8#kşAqY}Mic ۬󼶡\00,B\Q(9F*$"xK#ڃkd>8|[VϖXñ#%a C?W c.2b+DP[ʓcLi|w;P*Q5߽bM+⭡!J?$r G|qiO{_>}>=熈R Ž(OC bp_Vy][ .ߛnJq߷^W[!9,Nn=ϳ Crs(|(+2&@EO=e8Aȑ"5WoNu񈈼ah#ǥ8t #ɾxHy6#;\wdxQLQOB msSQZ$M9Cm* nKYțxvۼ`$7fm^Y pi(8c0rR6'[Wmy!~4Og>f܈r`%2QFbG!.b٠egxt쉤,EՀG,9T#f-yUJhTPFDE3b`Ψ( sp^3;;U_ öbEl"oO2`\?i=esxfORyA6r }:v@9_Uy{=WD^cMn(_D1 $+*94=}2i'H_#$ux`H; 7i"0ly](v"PV#"J^Iq}: K^#{{;=RÔ9`d%p yDMB隆k1ޠ_d:o~1fsw=d|\>:,P]E@~Pi hk2g*?x)XSV^nذܼ` <1C8TsC0ona(D0x e}$t'(`Qƃ&2{YaRQH~<2Ha<£Pcy;eGgP`7t0s5~bOjˍ>% 6%e+֛PO1's_'~IT, Uß:چAg^6h H6FxxyC8,bH:}|8bo i3fb(H zDP$ }y)8 :=  Pʁ?HYX~V\s?9luu"Pn `@C4A/ᥥOk9CG0o;5Mm˹'œ2Rf]Ștc5jҬo06у3J&տP} ckqy8F>bG"R"o,oBŰ}%YF{/F>yc80Cˁ#E8<|\ %Mx4 ;8*@B+roYy+C|;mc10rhHG\-C,m}fܥ|!C[z1zj\roL uWzP2xxbz] |pRC/+ ZQ>TL9(^ɡs1rpCEDs~((}@<$eÝWeCa2l~zG>y}m,a"|0A3n$ /΃i;(Wu\Id?e+H=/?8e t=# Q~F0`arMQC"/1oxHq>^u; <&Fj| p܋&M5a}:/mNLaPmE>m]ϐŜs N?̔!*VGqZi9uG { xjop5?qDU' 1:oGeC'u,'sy.mQ~csBƇE |a crěA^I史c`$!{roem}S%H2/!ei@><i3шlo{7)ܢ<߄4Y>w)ߤ{Ȥ2s }91Ǹmon})OD@~v 5B401#Mnx|Q(9/<Qhǃ9bPlQQ`B/R(qpF4,b{< <r^a ePȋc(A oy=X c.yVn\>7w<-&> r(P2 f;۹^X%*B9dL`"Mޜ) >? j@WE^QkgS˽ +9$r_Emp-½*<8טcU(|Y"y G웖nVgF+%"K}JK>>sϠVpdzlèڧ$$?-kẠ>((GD 'Ʋ[:X q!wqWb}_a'>\{<`;,{f}1%qO{o{dC[|<^w#Ii囕"z*q,GC|%j*&<"}<ĹA0ȩoƓƅ2ƒ$7A a͔ &XnZ>H2P.(Eևr0GC˖9' AB#xnȓ)19fzGڴz80c/>M(P;HQ/>!c\YŶ(/P7u~ z ڬB L szQxQ&IgIi̺o:EԍA.u qs&}I&2qRYڦ/Ц\cKcӯ$69(ypEƫ{K{~pq(!7J@$  H@ΌVw[w{4![ouS$  H@$p'2f6۬kL8S-r]53 H@$  H@8kP_YC;?q2@/nvvu"9WUݱ[2y _.>OuvXsÆ ݍo|V%  H@$  4B@~Άo~eׇ?ᅧ;*y>nm$  H@$PPӟ}+82LwЅg#n=$?ݧ??ɾeonst{z]?^gVE/cm HG~U]R:$  lV*sESwu{㊧;Gyd7;#|9x [B:^s+Xavp+(%oش]^L@#QGUz׻޵.jGuO<hw+]mo[$  H@X)92h#{K\{=y^ǜZ*yた%1ǮGƔc|sۀcpu3Wu>OvwTO+AO:SXFLK{^O|mby =B~xws{ql%  H@X 2nWκ nr!3u(xtmj _.wWv 4Fǣ!!MZp ݯvm]zsI #7i (}9zH-\jZ׺VQΙ}ge>eXh;ϕ2Ń3C9kXF NKkWU1bn>Gmo{[w'MVY0OYEp@Inh; x`Uծv2mF7&Pzh=Onߤ ,X-s';i.qkt-'> ">4ʬGuc~X gx1x$  H@X 90>93H:J4yS}<|fw;ޱg'=iX<,wI'm+^.+w]<ܛxm٦R?DG,|cJ8y:7^} .Q |#ʷ[ Q a̸al+x9y|&!X t10} [Qsl|-{[Z"#nu[G}B"j|;_I#@t})[neo?b\ 0ʰ&C#@dP:bL9;moS _*ִ!чHѿ`KG;V), 駹܋|&}cM]2}<2fXV'qmNܻ( z]8vZ8tݬ$  H@+'?Zˍ'%o` I7F~(|n )^ז̀7+&<^WE&-F#x~_l2c׿6!b! ^Ѭ}}ߩ^.k .ykiD<`TO1X#i(gs1 |P71D[bG} |vz=a! DRV5#<:Pb('yi_W/| O6c;F#a; ܩ#}i&1%,eCRkQ B8x؞\&\A ŝBYI3_6>rApoĈs~'h'*m5OR H@$^T$կ~{_ݡda;BCQ(YEExx#61Noy[[x2k夁G""P§@%_2GQDP~«CPH .Oiah0ؐRa,6>'.?C)OK30d˃|X?#v8q}D>D/0~>kH#s +$  H`/4^?q%Yw<;1.!x ',emxb-;N  (x Q g<%'4? ґ.{D0]F0Ja Ԙc肣:| D! QNa"<@7Dm)onŻ>M?e F6rw(r@G;#˖eOI}E&r v/.C}* f_1 YvQ&1)rlW$  H@ 0<(x€㧥e{(>aA'CrjirqFuh ?'I@$ /ol3O&ܗ9@!( h/PdCY+z#1"v}2C)EPP6^3ǂR sBa9v#OK8<݄g&F 9uP m,F[a9y/<Ǔ`pc9L(T1REܵz'cj>2vB: |1Vs_Z@"i8?sQQ$x-_)R_֧fe@Ҝ# y)pf c H@$T@%9 gs"xbv/-gf75yc'|x1O@yY̢s>aLÈeV/Rه"I.e_TPx'5'|>ylTtb~l}ŀwt~ ?ԩOk(9X9JLK0|<ɣ,m B k C p{$uH5 0wP1z aT{yL9X&$üxH>5ii?Ep 2o9/vvn"E:B0K+ H@!?= dW 1V&̓( Yx'9#6l((ܼ c=ޝyS9BeHg L2QbAasՋs&)pȃB4Q>(R.o"jxK 蘅͈PC޴-BA"OTxoRjX<5xwz(Ce'=#W2|wuwg.~^c"&#m-%&@q<#`C ,%94&yPIi?8I2O'3quU d\?A"C3)I@$ yHy,«SGX/^}(>S8N8xXQ2vmK=P#(G0A_%>>PlOyc8Dz;A# ""_E V/ 8AXp̵gUtwŒ:%2C Vf=<Pp%V_sOr;J G8<aXck}"8,@<0S ? H@$D*sKkX.ϑ#lon'&eV1sxQ ZQڍQ9O?a*-!(~7<^Ss9cI,@H&+k#ׄJN%b*퓾2 QNZ+}iaP tmIkN[»YO$|'5ҴQN: QYozΧJ+г"Bn ?"-7YlsZ5e偲LY.(gCտ9k# P`?$%F?/ i1uMүS0J3Z.u}`tV.)5#LоrM$  K@~^r4 ~xQY`G`\/Dm C' OO`y3|-$  H@Poiآ>`Ўȃh!{9":xc{O>|$T_i^|ڙ(ovD$ 3)|3B(i~(yJ@C< IIqn$  H@V3u`w9A?nIENDB`graphql-ruby-2.2.17/guides/javascript_client/apollo_subscriptions.md000066400000000000000000000164031476434635200260250ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: Apollo Subscriptions desc: GraphQL subscriptions with GraphQL-Ruby and Apollo Client index: 2 --- GraphQL-Ruby's JavaScript client includes several kinds of support for Apollo Client: - Apollo Link (2.x, 3.x): - [Overview](#apollo-link) - [Pusher](#apollo-link--pusher) - [Ably](#apollo-link--ably) - [ActionCable](#apollo-link--actioncable) - Apollo 1.x: - [Overview](#apollo-1) - [Pusher](#apollo-1--pusher) - [ActionCable](#apollo-1--actioncable) ## Apollo Link Apollo Links are used by Apollo client 2.x and 3.x. ## Apollo Link -- Pusher `graphql-ruby-client` includes support for subscriptions with Pusher and ApolloLink. To use it, add `PusherLink` before your `HttpLink`. For example: ```js // Load Apollo stuff import { ApolloClient, HttpLink, ApolloLink, InMemoryCache } from "@apollo/client"; // Load PusherLink from graphql-ruby-client import PusherLink from 'graphql-ruby-client/subscriptions/PusherLink'; // Load Pusher and create a client import Pusher from "pusher-js" var pusherClient = new Pusher("your-app-key", { cluster: "us2" }) // Make the HTTP link which actually sends the queries const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' }); // Make the Pusher link which will pick up on subscriptions const pusherLink = new PusherLink({pusher: pusherClient}) // Combine the two links to work together const link = ApolloLink.from([pusherLink, httpLink]) // Initialize the client const client = new ApolloClient({ link: link, cache: new InMemoryCache() }); ``` This link will check responses for the `X-Subscription-ID` header, and if it's present, it will use that value to subscribe to Pusher for future updates. If you're using {% internal_link "compressed payloads", "/subscriptions/pusher_implementation#compressed-payloads" %}, configure a `decompress:` function, too: ```javascript // Add `pako` to the project for gunzipping import pako from "pako" const pusherLink = new PusherLink({ pusher: pusherClient, decompress: function(compressed) { // Decode base64 const data = atob(compressed) .split('') .map(x => x.charCodeAt(0)); // Decompress const payloadString = pako.inflate(new Uint8Array(data), { to: 'string' }); // Parse into an object return JSON.parse(payloadString); } }) ``` ## Apollo Link -- Ably `graphql-ruby-client` includes support for subscriptions with Ably and ApolloLink. To use it, add `AblyLink` before your `HttpLink`. For example: ```js // Load Apollo stuff import { ApolloClient, HttpLink, ApolloLink, InMemoryCache } from '@apollo/client'; // Load Ably subscriptions link import AblyLink from 'graphql-ruby-client/subscriptions/AblyLink' // Load Ably and create a client const Ably = require("ably") const ablyClient = new Ably.Realtime({ key: "your-app-key" }) // Make the HTTP link which actually sends the queries const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' }); // Make the Ably link which will pick up on subscriptions const ablyLink = new AblyLink({ably: ablyClient}) // Combine the two links to work together const link = ApolloLink.from([ablyLink, httpLink]) // Initialize the client const client = new ApolloClient({ link: link, cache: new InMemoryCache() }); ``` This link will check responses for the `X-Subscription-ID` header, and if it's present, it will use that value to subscribe to Ably for future updates. For your __app key__, make a key with "Subscribe" and "Presence" privileges and use that: {{ "/javascript_client/ably_key.png" | link_to_img:"Ably Subscription Key Privileges" }} ## Apollo Link -- ActionCable `graphql-ruby-client` includes support for subscriptions with ActionCable and ApolloLink. To use it, construct a split link that routes: - subscription queries to an `ActionCableLink`; and - other queries to an `HttpLink` For example: ```js import { ApolloClient, HttpLink, ApolloLink, InMemoryCache } from '@apollo/client'; import { createConsumer } from '@rails/actioncable'; import ActionCableLink from 'graphql-ruby-client/subscriptions/ActionCableLink'; const cable = createConsumer() const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' }); const hasSubscriptionOperation = ({ query: { definitions } }) => { return definitions.some( ({ kind, operation }) => kind === 'OperationDefinition' && operation === 'subscription' ) } const link = ApolloLink.split( hasSubscriptionOperation, new ActionCableLink({cable}), httpLink ); const client = new ApolloClient({ link: link, cache: new InMemoryCache() }); ``` Note that for Rails 5, the ActionCable client package is `actioncable`, not `@rails/actioncable`. ## Apollo 1 `graphql-ruby-client` includes support for Apollo 1 client subscriptions over {% internal_link "Pusher", "/subscriptions/pusher_implementation" %} or {% internal_link "ActionCable", "/subscriptions/action_cable_implementation" %}. To use it, require `subscriptions/addGraphQLSubscriptions` and call the function with your network interface and transport client (example below). See the {% internal_link "Subscriptions guide", "/subscriptions/overview" %} for information about server-side setup. ### Apollo 1 -- Pusher Pass `{pusher: pusherClient}` to use Pusher: ```js // Load Pusher and create a client var Pusher = require("pusher-js") var pusherClient = new Pusher(appKey, options) // Add subscriptions to the network interface with the `pusher:` options import addGraphQLSubscriptions from "graphql-ruby-client/subscriptions/addGraphQLSubscriptions" addGraphQLSubscriptions(myNetworkInterface, {pusher: pusherClient}) // Optionally, add persisted query support: var OperationStoreClient = require("./OperationStoreClient") RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) ``` If you're using {% internal_link "compressed payloads", "/subscriptions/pusher_implementation#compressed-payloads" %}, configure a `decompress:` function, too: ```javascript // Add `pako` to the project for gunzipping import pako from "pako" addGraphQLSubscriptions(myNetworkInterface, { pusher: pusherClient, decompress: function(compressed) { // Decode base64 const data = btoa(compressed) // Decompress const payloadString = pako.inflate(data, { to: 'string' }) // Parse into an object return JSON.parse(payloadString); } }) ``` ### Apollo 1 -- ActionCable By passing `{cable: cable}`, all `subscription` queries will be routed to ActionCable. For example: ```js // Load ActionCable and create a consumer var ActionCable = require('@rails/actioncable') var cable = ActionCable.createConsumer() window.cable = cable // Load ApolloClient and create a network interface var apollo = require('apollo-client') var RailsNetworkInterface = apollo.createNetworkInterface({ uri: '/graphql', opts: { credentials: 'include', }, headers: { 'X-CSRF-Token': $("meta[name=csrf-token]").attr("content"), } }); // Add subscriptions to the network interface import addGraphQLSubscriptions from "graphql-ruby-client/subscriptions/addGraphQLSubscriptions" addGraphQLSubscriptions(RailsNetworkInterface, {cable: cable}) // Optionally, add persisted query support: var OperationStoreClient = require("./OperationStoreClient") RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) ``` graphql-ruby-2.2.17/guides/javascript_client/graphiql_subscriptions.md000066400000000000000000000074441476434635200263530ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: GraphiQL Subscriptions desc: Testing GraphQL subscriptions in the GraphiQL IDE index: 5 --- After setting up your server, you can integrate subscriptions into [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#readme), the in-browser GraphQL IDE. ## Adding GraphiQL to your app To get started, make a page for rendering GraphiQL, for example: ```html

``` Then, install GraphiQL (eg, `yarn add graphiql`) and add JavaScript code to import GraphiQL and render it on your page: ```js import { GraphiQL } from 'graphiql'; import React from 'react'; import { createRoot } from 'react-dom/client'; import 'graphiql/graphiql.css'; import { createGraphiQLFetcher } from '@graphiql/toolkit'; const fetcher = createGraphiQLFetcher({ url: '/graphql' }); const root = createRoot(document.getElementById('root')); root.render(); ``` After that, you should be able to load the page in your app and see the GraphiQL editor. ## Ably To integrate {% internal_link "Ably subscriptions", "subscriptions/ably_implementation" %}, use `createAblyFetcher`, for example: ```js import Ably from "ably" import createAblyFetcher from 'graphql-ruby-client/subscriptions/createAblyFetcher'; // Initialize a client // the key must have "subscribe" and "presence" permissions const ably = new Ably.Realtime({ key: "your.application.key" }) // Initialize a new fetcher and pass it to GraphiQL below var fetcher = createAblyFetcher({ ably: ably, url: "/graphql" }) const root = createRoot(document.getElementById('root')); root.render(); ``` Under the hood, it will use `window.fetch` to send GraphQL operations to the server, then listen for `X-Subscription-ID` headers in responses. To customize its HTTP requests, you can pass a `fetchOptions:` object or a custom `fetch:` function to `createAblyFetcher({ ... })`. ## Pusher To integrate {% internal_link "Pusher subscriptions", "subscriptions/pusher_implementation" %}, use `createPusherFetcher`, for example: ```js import Pusher from "pusher-js" import createPusherFetcher from 'graphql-ruby-client/subscriptions/createPusherFetcher'; // Initialize a client const pusher = new Pusher("your-app-key", { cluster: "your-cluster" }) // Initialize a new fetcher and pass it to GraphiQL below var fetcher = createPusherFetcher({ pusher: pusher, url: "/graphql" }) const root = createRoot(document.getElementById('root')); root.render(); ``` Under the hood, it will use `window.fetch` to send GraphQL operations to the server, then listen for `X-Subscription-ID` headers in responses. To customize its HTTP requests, you can pass a `fetchOptions:` object or a custom `fetch:` function to `createPusherFetcher({ ... })`. ## ActionCable To integrate {% internal_link "ActionCable subscriptions", "subscriptions/action_cable_implementation" %}, use `createActionCableFetcher`, for example: ```js import { createConsumer } from "@rails/actioncable" import createActionCableFetcher from 'graphql-ruby-client/subscriptions/createActionCableFetcher'; // Initialize a client const actionCable = createConsumer() // Initialize a new fetcher and pass it to GraphiQL below var fetcher = createActionCableFetcher({ consumer: actionCable, url: "/graphql" }) const root = createRoot(document.getElementById('root')); root.render(); ``` Under the hood, it will split traffic: it will send `subscription { ... }` operations via ActionCable and send queries and mutations via HTTP `POST` using `window.fetch`. To customize its HTTP requests, you can pass a `fetchOptions:` object or a custom `fetch:` function to `createPusherFetcher({ ... })`. graphql-ruby-2.2.17/guides/javascript_client/overview.md000066400000000000000000000020451476434635200234130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: Overview desc: Getting Started with GraphQL-Ruby's Javascript client, graphql-ruby-client. index: 0 --- There is a JavaScript client for GraphQL-Ruby, `graphql-ruby-client`. You can install it from NPM or Yarn: ```sh yarn add graphql-ruby-client # Or: npm install graphql-ruby-client ``` The source code is [in the graphql-ruby repository](https://github.com/rmosolgo/graphql-ruby/tree/master/javascript_client). See detailed guides for more info about its features: - {% internal_link "sync CLI", "javascript_client/sync" %} for use with [graphql-pro](https://graphql.pro)'s persisted query backend - Subscription support: - {% internal_link "Apollo integration", "/javascript_client/apollo_subscriptions" %} - {% internal_link "Relay integration", "/javascript_client/relay_subscriptions" %} - {% internal_link "urql integration", "/javascript_client/urql_subscriptions" %} - {% internal_link "GraphiQL integration", "/javascript_client/graphiql_subscriptions" %} graphql-ruby-2.2.17/guides/javascript_client/relay_subscriptions.md000066400000000000000000000154721476434635200256600ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: Relay Subscriptions desc: GraphQL subscriptions with GraphQL-Ruby and Relay Modern index: 3 --- `graphql-ruby-client` includes three kinds of support for subscriptions with Relay Modern: - [Pusher](#pusher) - [Ably](#ably) - [ActionCable](#actioncable) To use it, require `graphql-ruby-client/subscriptions/createRelaySubscriptionHandler` and call the function with your client and optionally, your OperationStoreClient. __Note:__ For Relay <11, use `import { createLegacyRelaySubscriptionHandler } from "graphql-ruby-client/subscriptions/createRelaySubscriptionHandler"` instead; the signature changed in Relay 11. See the {% internal_link "Subscriptions guide", "/subscriptions/overview" %} for information about server-side setup. ## Pusher Subscriptions with {% internal_link "Pusher", "/subscriptions/pusher_implementation" %} require two things: - A client from the [`pusher-js` library](https://github.com/pusher/pusher-js) - A [`fetchOperation` function](#fetchoperation-function) for sending the `subscription` operation to the server ### Pusher client Pass `pusher:` to get Subscription updates over Pusher: ```js // Load the helper function import createRelaySubscriptionHandler from "graphql-ruby-client/subscriptions/createRelaySubscriptionHandler" // Prepare a Pusher client var Pusher = require("pusher-js") var pusherClient = new Pusher(appKey, options) // Create a fetchOperation, see below for more details function fetchOperation(operation, variables, cacheConfig) { return fetch(...) } // Create a Relay Modern-compatible handler var subscriptionHandler = createRelaySubscriptionHandler({ pusher: pusherClient, fetchOperation: fetchOperation }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` ### Compressed Payloads If you're using {% internal_link "compressed payloads", "/subscriptions/pusher_implementation#compressed-payloads" %}, configure a `decompress:` function, too: ```javascript // Add `pako` to the project for gunzipping import pako from "pako" var subscriptionHandler = createRelaySubscriptionHandler({ pusher: pusherClient, fetchOperation: fetchOperation, decompress: function(compressed) { // Decode base64 const data = btoa(compressed) // Decompress const payloadString = pako.inflate(data, { to: 'string' }) // Parse into an object return JSON.parse(payloadString); } }) ``` ## Ably Subscriptions with {% internal_link "Ably", "/subscriptions/ably_implementation" %} require two things: - A client from the [`ably-js` library](https://github.com/ably/ably-js) - A [`fetchOperation` function](#fetchoperation-function) for sending the `subscription` operation to the server ### Ably client Pass `ably:` to get Subscription updates over Ably: ```js // Load the helper function import createRelaySubscriptionHandler from "graphql-ruby-client/subscriptions/createRelaySubscriptionHandler" // Load Ably and create a client const Ably = require("ably") const ablyClient = new Ably.Realtime({ key: "your-app-key" }) // create a fetchOperation, see below for more details function fetchOperation(operation, variables, cacheConfig) { return fetch(...) } // Create a Relay Modern-compatible handler var subscriptionHandler = createRelaySubscriptionHandler({ ably: ablyClient, fetchOperation: fetchOperation }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` ## ActionCable With this configuration, `subscription` queries will be routed to {% internal_link "ActionCable", "/subscriptions/action_cable_implementation" %}. For example: ```js // Require the helper function import createRelaySubscriptionHandler from "graphql-ruby-client/subscriptions/createRelaySubscriptionHandler") // Optionally, load your OperationStoreClient var OperationStoreClient = require("./OperationStoreClient") // Create a Relay Modern-compatible handler var subscriptionHandler = createRelaySubscriptionHandler({ cable: createConsumer(...), operations: OperationStoreClient, }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` ## With Relay Persisted Queries If you're using Relay's built-in [persisted query support](https://relay.dev/docs/guides/persisted-queries/), you can pass `clientName:` to the handler in order to build IDs that work with the {% internal_link "OperationStore", "/operation_store/overview.html" %}. For example: ```js var subscriptionHandler = createRelaySubscriptionHandler({ cable: createConsumer(...), clientName: "web-frontend", // This should match the one you use for `sync` }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` Then, the ActionCable handler will use Relay's provided operation IDs to interact with the OperationStore. ## fetchOperation function The `fetchOperation` function can be extracted from your `fetchQuery` function. Its signature is: ```js // Returns a promise from `fetch` function fetchOperation(operation, variables, cacheConfig) { return fetch(...) } ``` - `operation`, `variables`, and `cacheConfig` are the first three arguments to the `fetchQuery` function. - The function should call `fetch` and return the result (a Promise of a `Response`). For example, `Environment.js` may look like: ```js // This function sends a GraphQL query to the server const fetchOperation = function(operation, variables, cacheConfig) { const bodyValues = { variables, operationName: operation.name, } const useStoredOperations = process.env.NODE_ENV === "production" if (useStoredOperations) { // In production, use the stored operation bodyValues.operationId = OperationStoreClient.getOperationId(operation.name) } else { // In development, use the query text bodyValues.query = operation.text } return fetch('http://localhost:3000/graphql', { method: 'POST', opts: { credentials: 'include', }, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(bodyValues), }) } // `fetchQuery` uses `fetchOperation`, but returns a Promise of JSON const fetchQuery = (operation, variables, cacheConfig, uploadables) => { return fetchOperation(operation, variables, cacheConfig).then(response => { return response.json() }) } // Subscriptions uses the same `fetchOperation` function for initial subscription requests const subscriptionHandler = createRelaySubscriptionHandler({pusher: pusherClient, fetchOperation: fetchOperation}) // Combine them into a `Network` const network = Network.create(fetchQuery, subscriptionHandler) ``` Since `OperationStoreClient` is in the `fetchOperation` function, it will apply to all GraphQL operations. graphql-ruby-2.2.17/guides/javascript_client/sync.md000066400000000000000000000274051476434635200225300ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: OperationStore Sync desc: Javascript tooling for persisted queries with GraphQL-Ruby index: 1 --- JavaScript support for GraphQL projects using [graphql-pro](https://graphql.pro)'s `OperationStore` for persisted queries. - [`sync` CLI](#sync-utility) - [Relay <2 support](#use-with-relay-2) - [Relay 2+ support](#use-with-relay-persisted-output) - [Apollo Client support](#use-with-apollo-client) - [Apollo Link support](#use-with-apollo-link) - [Apollo Codegen Support](#use-with-apollo-codegen) - [Apollo Android support](#use-with-apollo-android) - [Apollo Persisted Queries Support](#use-with-apollo-persisted-queries) - [Plain JS support](#use-with-plain-javascript) - [Authorization](#authorization) See the {% internal_link "OperationStore guide", "/operation_store/overview" %} for server-side setup. ## `sync` utility This package contains a command line utility, `graphql-ruby-client sync`: ``` $ graphql-ruby-client sync # ... Authorizing with HMAC Syncing 4 operations to http://myapp.com/graphql/operations... 3 added 1 not modified 0 failed Generating client module in app/javascript/graphql/OperationStoreClient.js... ✓ Done! ``` `sync` Takes several options: option | description --------|---------- `--url` | {% internal_link "Sync API", "/operation_store/getting_started.html#add-routes" %} url `--path` | Local directory to search for `.graphql` / `.graphql.js` files `--relay-persisted-output` | Path to a `.json` file from `relay-compiler ... --persist-output` `--apollo-codegen-json-output` | Path to a `.json` file from `apollo client:codegen ... --target json` `--apollo-android-operation-output` | Path to an `OperationOutput.json` file from Apollo Android `--client` | Client ID ({% internal_link "created on server", "/operation_store/client_workflow" %}) `--secret` | Client Secret ({% internal_link "created on server", "/operation_store/client_workflow" %}) `--outfile` | Destination for generated code `--outfile-type` | What kind of code to generate (`js` or `json`) `--header={key}:{value}` | Add a header to the outgoing HTTP request (may be repeated) `--add-typename` | Add `__typename` to all selection sets (for use with Apollo Client) `--verbose` | Output some debug information `--changeset-version` | Set a {% internal_link "Changeset Version", "/changesets/installation#controller-setup" %} when syncing these queries. (`context[:changeset_version]` will also be required at runtime, when running these stored operations.) You can see these and a few others with `graphql-ruby-client sync --help`. ## Use with Relay <2 `graphql-ruby-client` can persist queries from `relay-compiler` using the embedded `@relayHash` value. (This was created in Relay before 2.0.0. See below for Relay 2.0+.) To sync your queries with the server, use the `--path` option to point to your `__generated__` directory, for example: ```bash # sync a Relay project $ graphql-ruby-client sync --path=src/__generated__ --outfile=src/OperationStoreClient.js --url=... ``` Then, the generated code may be integrated with Relay's [Network Layer](https://facebook.github.io/relay/docs/network-layer.html): ```js // ... // require the generated module: const OperationStoreClient = require('./OperationStoreClient') // ... function fetchQuery(operation, variables, cacheConfig, uploadables) { const requestParams = { variables, operationName: operation.name, } if (process.env.NODE_ENV === "production") // In production, use the stored operation requestParams.operationId = OperationStoreClient.getOperationId(operation.name) } else { // In development, use the query text requestParams.query = operation.text, } return fetch('/graphql', { method: 'POST', headers: { /*...*/ }, body: JSON.stringify(requestParams), }).then(/* ... */); } // ... ``` (Only Relay Modern is supported. Legacy Relay can't generate static queries.) ## Use With Relay Persisted Output To use Relay's persisted output, add a `"file": ...` to your project's [`persistConfig` object](https://relay.dev/docs/guides/persisted-queries/). For example: ```json "relay": { ... "persistConfig": { "file": "./persisted-queries.json" } }, ``` Then, push Relay's generated queries to your OperationStore server with `--relay-persisted-output`: ``` $ graphql-ruby-client sync --relay-persisted-output=path/to/persisted-queries.json --url=... ``` In this case, `sync` _won't_ generate a JavaScript module because `relay-compiler` has already prepared its queries for persisted use. Instead, update your network layer to include the _client name_ and _operation id_ in the HTTP params: ```js const operationStoreClientName = "MyRelayApp"; function fetchQuery(operation, variables,) { return fetch('/graphql', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ // Pass the client name and the operation ID, joined by `/` documentId: operationStoreClientName + "/" + operation.id, // query: operation.text, // this is now obsolete because text is null variables, }), }).then(response => { return response.json(); }); } ``` (Inspired by https://relay.dev/docs/guides/persisted-queries/#network-layer-changes.) Now, your Relay app will only send operation IDs over the wire to the server. ## Use with Apollo Client Use the `--path` option to point at your `.graphql` files: ``` $ graphql-ruby-client sync --path=src/graphql/ --url=... ``` Then, load the generated module and add its `.apolloMiddleware` to your network interface with `.use([...])`: ```js // load the generated module var OperationStoreClient = require("./OperationStoreClient") // attach it as middleware in production // (in development, send queries to the server as normal) if (process.env.NODE_ENV === "production") { MyNetworkInterface.use([OperationStoreClient.apolloMiddleware]) } ``` Now, the middleware will replace query strings with `operationId`s. ## Use with Apollo Link Use the `--path` option to point at your `.graphql` files: ``` $ graphql-ruby-client sync --path=src/graphql/ --url=... ``` Then, load the generated module and add its `.apolloLink` to your Apollo Link: ```js // load the generated module var OperationStoreClient = require("./OperationStoreClient") // Integrate the link to another link: const link = ApolloLink.from([ authLink, OperationStoreClient.apolloLink, httpLink, ]) // Create a client const client = new ApolloClient({ link: link, cache: new InMemoryCache(), }); ``` __Update the controller__: Apollo Link supports extra parameters _nested_ as `params[:extensions][:operationId]`, so update your controller to add that param to context: ```ruby # app/controllers/graphql_controller.rb context = { # ... # Support Apollo Link: operation_id: params[:extensions][:operationId] } ``` Now, `context[:operation_id]` will be used to fetch a query from the database. ## Use with Apollo Codegen Use `apollo client:codegen ... --target json` to build a JSON artifact containing your app's queries. Then, pass the path to that artifact to `graphql-ruby-client sync --apollo-codegen-json-output path/to/output.json ...`. `sync` will use Apollo-generated `operationId`s to populate the `OperationStore`. Then, to use Apollo-style persisted query IDs, hook up the __Persisted Queries Link__ as described in [Apollo's documentation](https://www.apollographql.com/docs/react/api/link/persisted-queries/) Finally, __update the controller__ to pass the Apollo-style persisted query ID as the operation ID: ```ruby # app/controllers/graphql_controller.rb context = { # ... # Support already-synced Apollo Persisted Queries: operation_id: params[:extensions][:operationId] } ``` Now, Apollo-style persisted query IDs will be used to fetch operations from the server's `OperationStore`. ## Use with Apollo Android Apollo Android's [generateOperationOutput option](https://www.apollographql.com/docs/android/advanced/persisted-queries/#operationoutputjson) builds an `OperationOutput.json` file which works with the OperationStore. To sync those queries, __use the `--apollo-android-operation-output` option__: ```sh graphql-ruby-client sync --apollo-android-operation-output=path/to/OperationOutput.json --url=... ``` That way, the OperationStore will use the query IDs generated by Apollo Android. On the server, you'll have to __update your controller__ to receive the client name and the operation ID. For example: ```ruby # app/controllers/graphql_controller.rb context = { ... } # Check for an incoming operation ID from Apollo Client: apollo_android_operation_id = request.headers["X-APOLLO-OPERATION-ID"] if apollo_android_operation_id.present? # Check the incoming request to confirm that # it's your first-party client with stored operations client_name = # ... if client_name.present? # If we received an incoming operation ID # _and_ identified the client, run a persisted operation. context[:operation_id] = "#{client_name}/#{apollo_android_operation_id}" end end ``` You may also have to __update your app__ to send an identifier, so that the server can determine the "client name" used with the operation store. (Apollo Android sends a query hash, but the operation store expects IDs in the form `#{client_name}/#{query_hash}`.) ## Use with Apollo Persisted Queries Apollo client has a [Persisted Queries Link](https://www.apollographql.com/docs/react/api/link/persisted-queries/). You can use that link with GraphQL-Pro's {% internal_link "OperationStore", "/operation_store/overview" %}. First, create a manifest with [`generate-persisted-query-manifest`](https://www.apollographql.com/docs/react/api/link/persisted-queries/#1-generate-operation-manifests), then, pass the path to that file to `sync`: ```sh $ graphql-ruby-client sync --apollo-persisted-query-manifest=path/to/manifest.json ... ``` Then, configure Apollo Client to [use your persisted query manifest](https://www.apollographql.com/docs/react/api/link/persisted-queries/#persisted-queries-implementation). Finally, update your controller to receive the operation ID and pass it as `context[:operation_id]`: ```ruby client_name = "..." # TODO: send the client name as a query param or header persisted_query_hash = params[:extensions][:persistedQuery][:sha256Hash] context = { # ... operation_id: "#{client_name}/#{persisted_query_hash}" } ``` The `operation_id` will also need your client name. Using Apollo Client, you could send this as a [custom header](https://www.apollographql.com/docs/react/networking/basic-http-networking/#customizing-request-headers) or another way that works for your application (eg, session or user agent). ## Use with plain JavaScript `OperationStoreClient.getOperationId` takes an operation name as input and returns the server-side alias for that operation: ```js var OperationStoreClient = require("./OperationStoreClient") OperationStoreClient.getOperationId("AppHomeQuery") // => "my-frontend-app/7a8078c7555e20744cb1ff5a62e44aa92c6e0f02554868a15b8a1cbf2e776b6f" OperationStoreClient.getOperationId("ProductDetailQuery") // => "my-frontend-app/6726a3b816e99b9971a1d25a1205ca81ecadc6eb1d5dd3a71028c4b01cc254c1" ``` Post the `operationId` in your GraphQL requests: ```js // Lookup the operation name: var operationId = OperationStoreClient.getOperationId(operationName) // Include it in the params: $.post("/graphql", { operationId: operationId, variables: queryVariables, }, function(response) { // ... }) ``` ## Authorization `OperationStore` uses HMAC-SHA256 to {% internal_link "authenticate requests" , "/operation_store/access_control" %}. Pass the key to `graphql-ruby-client sync` as `--secret` to authenticate it: ```bash $ export MY_SECRET_KEY= "abcdefg..." $ graphql-ruby-client sync ... --secret=$MY_SECRET_KEY # ... Authenticating with HMAC # ... ``` graphql-ruby-2.2.17/guides/javascript_client/urql_subscriptions.md000066400000000000000000000016641476434635200255250ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: urql Subscriptions desc: GraphQL subscriptions with GraphQL-Ruby and urql index: 4 --- GraphQL-Ruby currently supports using `urql` with the {% internal_link "Pusher implementation", "/subscriptions/pusher_implementation" %}. For example: ```js import SubscriptionExchange from "graphql-ruby-client/subscriptions/SubscriptionExchange" import Pusher from "pusher" import { Client, defaultExchanges, subscriptionExchange } from 'urql' const pusherClient = new Pusher("your-app-key", { cluster: "us2" }) const forwardToPusher = SubscriptionExchange.create({ pusher: pusherClient }) const client = new Client({ url: '/graphql', exchanges: [ ...defaultExchanges, subscriptionExchange({ forwardSubscription: forwardToPusher }), ], }); ``` Want to use `urql` with another subscription backend? Please {% open_an_issue "Using urql with ..." %}. graphql-ruby-2.2.17/guides/js/000077500000000000000000000000001476434635200161325ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/js/search.js000066400000000000000000000066071476434635200177460ustar00rootroot00000000000000var client = algoliasearch('8VO8708WUV', '1f3e2b6f6a503fa82efdec331fd9c55e'); var index = client.initIndex('prod_graphql_ruby'); var GraphQLRubySearch = { // Respond to a change event on `el` by: // - Searching the index // - Rendering the results run: function(el) { var searchTerm = el.value var searchResults = document.querySelector("#search-results") if (!searchTerm) { // If there's no search term, clear the results pane searchResults.innerHTML = "" } else { index.search({ query: searchTerm, hitsPerPage: 8, }, function(err, content) { if (err) { console.error(err) } var results = content.hits // Clear the previous results searchResults.innerHTML = "" results.forEach(function(result) { // Create a wrapper hyperlink var container = document.createElement("a") container.className = "search-result" container.href = (result.rubydoc_url || result.url) + (result.anchor ? "#" + result.anchor : "") // This helper will be used to accumulate text into the search-result function createSpan(text, className) { var txt = document.createElement("span") txt.className = className txt.innerHTML = text container.appendChild(txt) } if (result.rubydoc_url) { createSpan("API Doc", "search-category") createSpan(result.title, "search-title") } else { createSpan(result.section, "search-category") var resultHeader = [result.title].concat(result.headings).join(" > ") createSpan(resultHeader, "search-title") var preview = result._snippetResult.content.value createSpan(preview, "search-preview") } searchResults.appendChild(container) }) var seeAll = document.createElement("a") seeAll.href = "/search?query=" + content.query seeAll.className = "search-see-all" seeAll.innerHTML = "See All Results (" + content.nbHits + ")" searchResults.appendChild(seeAll) }) } }, // Return true if we actually highlighted something _moveHighlight: function(diff) { var allResults = document.querySelectorAll(".search-result") var highlightClass = "highlight-search-result" if (!allResults.length) { // No search results to highlight return false } var highlightedResult = document.querySelector("." + highlightClass) var nextHighlightedResult var result for (var i = 0; i < allResults.length; i++) { result = allResults[i] if (result == highlightedResult) { nextHighlightedResult = allResults[i + diff] break } } if (!nextHighlightedResult) { // Either nothing was highlighted yet, // or we were at the end of results and we loop around nextHighlightedResult = allResults[0] } if (highlightedResult) { highlightedResult.classList.remove(highlightClass) } nextHighlightedResult.classList.add(highlightClass) nextHighlightedResult.focus() return true } } document.addEventListener("keydown", function(ev) { var diff = ev.keyCode == 38 ? -1 : (ev.keyCode == 40 ? 1 : 0) if (diff) { var highlighted = GraphQLRubySearch._moveHighlight(diff) if (highlighted) { ev.preventDefault() } } }) graphql-ruby-2.2.17/guides/language_tools/000077500000000000000000000000001476434635200205215ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/language_tools/c_parser.md000066400000000000000000000015651476434635200226500ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Language Tools title: C-based Parser desc: The GraphQL::CParser gem is a drop-in replacement for the built-in parser index: 1 --- GraphQL-Ruby includes a plain-Ruby parser, but a faster parser is available as a C extension. To use it, add the [`graphql-c_parser` gem](https://rubygems.org/gems/graphql-c_parser) to your project, for example: ```ruby bundle add graphql-c_parser ``` When `graphql-c_parser` is `require`d by your app, the C-based parser is installed as the default parser (as {{ "GraphQL.default_parser" | api_doc }}). Bundler requires the library automatically, but you can also require it manually: ```ruby require "graphql/c_parser" ``` This alternative parser is faster and uses less memory. The library also adds `GraphQL.scan_with_c` and `GraphQL.parse_with_c` for calling the C-based parser directly. graphql-ruby-2.2.17/guides/language_tools/visitor.md000066400000000000000000000122041476434635200225410ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Language Tools title: AST Visitor desc: Analyze and modify parsed GraphQL code index: 0 --- GraphQL code is usually contained in a string, for example: ```ruby query_string = "query { user(id: \"1\") { userName } }" ``` You can perform programmatic analysis and modifications to GraphQL code using a three-step process: - __Parse__ the code into an abstract syntax tree - __Analyze/Modify__ the code with a visitor - __Print__ the code back to a string ## Parse {{ "GraphQL.parse" | api_doc }} turns a string into a GraphQL document: ```ruby parsed_doc = GraphQL.parse("{ user(id: \"1\") { userName } }") # => # ``` Also, {{ "GraphQL.parse_file" | api_doc }} parses the contents of the named file and includes a `filename` in the parsed document. #### AST Nodes The parsed document is a tree of nodes, called an _abstract syntax tree_ (AST). This tree is _immutable_: once a document has been parsed, those Ruby objects can't be changed. Modifications are performed by _copying_ existing nodes, applying changes to the copy, then making a new tree to hold the copied node. Where possible, unmodified nodes are retained in the new tree (it's _persistent_). The copy-and-modify workflow is supported by a few methods on the AST nodes: - `.merge(new_attrs)` returns a copy of the node with `new_attrs` applied. This new copy can replace the original node. - `.add_{child}(new_child_attrs)` makes a new node with `new_child_attrs`, adds it to the array specified by `{child}`, and returns a copy whose `{children}` array contains the newly created node. For example, to rename a field and add an argument to it, you could: ```ruby modified_node = field_node # Apply a new name .merge(name: "newName") # Add an argument to this field's arguments .add_argument(name: "newArgument", value: "newValue") ``` Above, `field_node` is unmodified, but `modified_node` reflects the new name and new argument. ## Analyze/Modify To inspect or modify a parsed document, extend {{ "GraphQL::Language::Visitor" | api_doc }} and implement its various hooks. It's an implementation of the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern). In short, each node of the tree will be "visited" by calling a method, and those methods can gather information and perform modifications. In the visitor, each node class has a hook, for example: - {{ "GraphQL::Language::Nodes::Field" | api_doc }}s are routed to `#on_field` - {{ "GraphQL::Language::Nodes::Argument" | api_doc }}s are routed to `#on_argument` See the {{ "GraphQL::Language::Visitor" | api_doc }} API docs for a full list of methods. Each method is called with `(node, parent)`, where: - `node` is the AST node currently visited - `parent` is the AST node above this node in the tree The method has a few options for analyzing or modifying the AST: #### Continue/Halt To continue visiting, the hook should call `super`. This allows the visit to continue to `node`'s children in the tree, for example: ```ruby def on_field(_node, _parent) # Do nothing, this is the default behavior: super end ``` To _halt_ the visit, a method may skip the call to `super`. For example, if the visitor encountered an error, it might want to return early instead of continuing to visit. #### Modify a Node Visitor hooks are expected to return the `(node, parent)` they are called with. If they return a different node, then that node will replace the original `node`. When you call `super(node, parent)`, the `node` is returned. So, to modify a node and continue visiting: - Make a modified copy of `node` - Pass the modified copy to `super(new_node, parent)` For example, to rename an argument: ```ruby def on_argument(node, parent) # make a copy of `node` with a new name modified_node = node.merge(name: "renamed") # continue visiting with the modified node and parent super(modified_node, parent) end ``` #### Delete a Node To delete the currently-visited `node`, don't pass `node` to `super(...)`. Instead, pass a magic constant, `DELETE_NODE`, in place of `node`. For example, to delete a directive: ```ruby def on_directive(node, parent) # Don't pass `node` to `super`, # instead, pass `DELETE_NODE` super(DELETE_NODE, parent) end ``` #### Insert a Node Inserting nodes is similar to modifying nodes. To insert a new child into `node`, call one of its `.add_` helpers. This returns a copied node with a new child added. For example, to add a selection to a field's selection set: ```ruby def on_field(node, parent) node_with_selection = node.add_selection(name: "emailAddress") super(node_with_selection, parent) end ``` This will add `emailAddress` the fields selection on `node`. (These `.add_*` helpers are wrappers around {{ "GraphQL::Language::Nodes::AbstractNode#merge" | api_doc }}.) ## Print The easiest way to turn an AST back into a string of GraphQL is {{ "GraphQL::Language::Nodes::AbstractNode#to_query_string" | api_doc }}, for example: ```ruby parsed_doc.to_query_string # => '{ user(id: "1") { userName } }' ``` You can also create a subclass of {{ "GraphQL::Language::Printer" | api_doc }} to customize how nodes are printed. graphql-ruby-2.2.17/guides/limiters/000077500000000000000000000000001476434635200173465ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/limiters/active_operation_limiter_dashboard.png000066400000000000000000001524651476434635200271600ustar00rootroot00000000000000PNG  IHDR!1P@iCCPICC Profile(c``H,(aa``+) rwRR` `\\TQk .ȬǶoc=,)<zZ qbrAQ c\^Rb"E@G3@t{ a r)@ [' I< ؍MB}-M PZQ *23J!`d`d o7(Ɓ+c`!B,v9> +H,J;KqͽuÁ^d`{Áo^ FVeXIfMM*iD!ASCIIScreenshotߵiTXtXML:com.adobe.xmp 1057 Screenshot 663 g@IDATxx4 ^CcH&@DQ*. *+"@Ŏ&Ez )@}.dSvI<6ݹs׶]KB!&0lnMYY3% aSO}X "HHHHHHHHeA$@$@$@$@$@$@$@K>$@$@$@$@$@$@$@!@Kpf-$@$@$@$@$@$@$ P        ?Y yTBGHHHHHHH? B#R_/Y+rlZ]QRRH|+ )TP i/]'HHHHHH2GJc P%H4}q^LBK/zl٥K\m\W)V; |TBx$@$@$@$@$@$ITBd g?GK' gʦ?7; Rj(R]J=t\L.&\H3__-[@$@$@$@$@$@9cB{ǖ'Z\Z'-H4i(lK#mC7H_>|*=qg       y)I Jhrd&YtamHV$9 ɭ:U+K-egS/-G?|"ZIAED@W6HHHHH "ӼwEJip)V PD\)Ӹ\VSȶ _JblW+_?9it#?*"s+&    9*!|r@I("bp5CkJFue˷?j Jbc%vͤMke#     L"= k""cfϏ-~P+C@ɐh@L%^s'NUS&8C""n0[C$@$@$@$@$ P oqpZDq"B!weE?3<;vB:7VBR:xHHHHHH ]Dgp1qN!-"`7 n~-Aat` a⚔e7'ߒ蹋e#c$1<)` xOJY1e#`b=@qI)ڠ|[SQLұd~O] JxRmP?YE$/a5A!     H?SBT)5kԐIIm69u0We˖&MȬY33TKɒ%Ք%JCdm,xm4KɒRp>xN?Ŀ%n! )VTֿݸ١0 \(.]$K1ȢGvxf@qJ>BHHHHHHI `7Y .$[ٳζw }Sd^z2fiРA<Kƍ'11שee!ݙ0aԭSm1/ʚukd„s&TʂbED-ѵP }&n*Gmԫib.j'Nʩ[eYRG'jƜ[;`EDP+=؁3" j d ;q1| Ruظv)THgWYR K~>^TY2{""ȹs r~ƖkDBrßѣRpԋݘ)S3))V3wHHH B 11rukٲul޴Iu/I-Zj2Ҩa#y٧3{ΏlzB٧չsdҥrA]Ɩҫg/_|g0 +̋jITJ[떪=&~(sl߱=+{YX 6(D9y̻qtCYԊ* JHh !l+פ[9,!r"tҬispbblذ!,6i&JY/[5o<,pr唵SRG!#βqFٷow@6mdk{I)O<CjoӺ7oOI|v<ϮʎWӧe|^,矫c (Gaسgt M&Ekƥ~Xbl޼YNYW.DIFeܹ%~߮]V}{2Z$ @ {)N@kU䥗_?å~}cnݺ :x43fcP?j(\믿!U*W!C{R*Bҋ/I351bw}~i*ѱ%ke娗t5C ^50WED¥-D|Vh32{1%T@AUzF,U4lP6m*'OHZj% 4*e)í/utB^zE[vL4Q)'nWuz-fRV-sXaݶm[źTXQ]aٶm,XYwN%Dʹ")Ճ!`!TD8I|oJ ߪGӕ!H,{%isڗ2`IhԨƸ4 䪮ݤi͠!(SoҸeӧO)wxu6UHl29s.q[NAfVT.RYxV6! Rx7m큙8]:wŊi=G}vUfFjwZ3SwN wHll7o.SN'Eeu-{VH6m<w` rɟQfޔ4$@$@M ەj:zӖ"Ծkf.Y+&.º!IǂЊ o?B7 Tyoe $1r\BYhQc( L];{V2_ݻP{Abᒀ&AaU jMBiN:)˖/s*90"e ҺUZa<ǎVx=up^R<ΕҥHݺueߗkՖk8ǀ㮁Q5h-:!!v|BA;ɯr1ucǏݻ[(lWrYh7,NJ*)9,PfN` + ~OmuKwaW"#(^ѡ̙;eƶlٲX)fϙ̎:^qDDD`x.6mZ+uzg 7X{{{br=rwoO2gs.Jw\;{\}w]L3GYY`Kxf (I]TPI׍$|r%Vex-gʕJyO 2_͚5 p@?S+p[2LPܹS+|:p@]ի, ^s*# NoXmݺUCT|_ઁ ju_qRCK ߪUnS~3gX'(3fv{97nkӑ#Gtkf'|T @!]1#$@pzv^-%ٳG>Cyǵra萡ZdoͷT';g/Fw1`?y1wN(5~;pǟ~{-}zq:;v: D +*U/>RyS. =H`=c O>9ڛj6 [kRWvMSo @TD( 7)ELN8ڄ*QeƪWߑ蹋wzRJ%GYD>H~zC `lh)VD༺J+I v%Գޙ&D(_*3rH[m(0heܵS?wۿOPpQ9Dٳ[+I >FBU߿|JWm@,0=h5i#GA!dX׉A v6@0+ZP|y\h;.!/VDM U   h u`yskJ:VAZ<#{ۦ[.)+'!-;X$ ( &WZu}J7(?Ž j =@swo#l.ځr1 5ʺgb^E V7€]3F}P2`0 bTSօP s 6)) I7ۀA8 ~g׭[`QOx>q߸qc]-[d͚5p˖-rߦVOxgCAew E~fkTuzϟ vj /ڏH_~^N=j0,$@;Jzʺeɒ%{A~5 # :Οsۄscm=Y \nۢ]22bɒNw$ {*%3+%fWZ]f"(#ȳ% ~7[ha{[>Ynm3^Gϛ/k~Cignκ1jVyu,Ei&jT4.TX+yY=s& 5/ 'R ̷mۦP}kz_c =JɄ<7+(W0h"e1Eܡ*ԩ<B&/ (!0b ZV-Fr&hLԶC@Z'ϲUtxwb (J($@$@y@J l7IaA5Яߍ;TKF I6^|ךnҪu+f6m>a@o$Qu,!&漙mM-x*Y|*}ժZîAZ;k ֎>o-9C-$1NSKrB-EVbCcM>G`]]G27'}|`p-̍:+kz쇇9*.k_'-E^P삊Ȃ9Xt@NFs.f!,s$ 2f|1c'8H]Ihx32FwnD #K:9nߚA.qHmȅdE`"P]O^Ų`cd]-v] 3l0łɇJ V;_g%b'RS̅]Zw7PizI+܍>ViěvAe?ǀ ~Z&}^(б߮/\2L`>+AOZRz >^=/o^F<|Y}{}fmO3J@`I0\ByxSl/#ea .j/t"~QtI)Z}¸OUVD;wiKR P\.Z>5B9\@r Ȯ9+"`] U,22R7ݸ0`3^|6,:h^/ $p@: 6 rU`POau*a9͌ R%DXcPZHY79~1k8VT 5K~ZϙX#,`̔? \:Ku~Ѱa#1:jԬc! ((@|<UFp? VO H{X"Ђg+GFVJ;lEpacE (/p7i`tw FQ|Gt &?07m @utGtePղt4s *᥋jҩT+#1[vJJrL`Ռ$jFNQ>p) t@*p' >|_>>cЌk7?#Vv3.LL| >X ttψX JzPJe N.b9A\Xuj^~wgJ{ialP.Pj4o2[8%;+|ѹj`Ծ]+:TȊpǠP:` b{ }2C*XY5K,0r #*0`XqA9{StVobrlB #ȃ!r+uwo.@ͮe Vw wϗF<A3V7F:DEaE <`cՉ?(A)vW; "``@Ql`U~M3^+![?]uUh˜9sL-JƢclMs8s̞###;Y3RU޽v{*6eA)k ]!cp]BJ#Cx1i% ]m&u{qBFTg?/J!Q^kÈHw?Y n)w8QoeoH>#z,YUE˝3ˀ]4ۄa_3 ʬ4`nǍ{[/$:W1fs J*.uYs5Ң3 /lˏJjeow|ݷذ5e*^gdfI+JÑ;,#ԳcX1ޑs GIht7zD4/W|,V-&H?գm;!M䇃=;g][|v,-tb0@wҘf)o(bd.Bi~ӎzVX d;%ie73P@ѓsi )}]fg;X7 %˟LO&7?gLDj1*GuO&M(CDZ g޽1{6A̓ht`"7 d:O0f:|rH~Ӏ2j(i۶NW3QPHTV~6lpyg8_t(ae= LNhS~K'v >3"8xb|g1bV IG=&M=3ޢh}@cEBDڅBuV $$T]"rhG2/J5Ij{YQ[\2is>L==5l*fd#P*U,o2M:J9Έn~g)+xSWNIc^yAj33ˬYeq|]6-&L5RǕinK[93NFxgťWOH[KZa^Tjܷr90}{k<ӭ,0m玬$ SE\;,&ONbϣ7=^ MHC<b{&   j \y9ZNho 1I)˕r.nGB*k-5TI,yrFYz>P.@$`,.ta99M =l y%C셣=biWq:u(LwJ b*"N|UIPFV朧"bލCO׌sOȅT @n%_~s TrZ:3DEK7 j%eCx\"5H|G6l}v.E"\rzϾԲ yy^ 矀\Whzb /_VyH DR*Z!b7 |> W*'m?z]~S.K      wlYGrSYгrhR (*!$@$@$@$@$@$@$+P +n#/HHHHHHB      A$@$@$@$@$@$@OJGl! TBȋ       '@%D#HHHHHHr*!rmE @"[H$@$@$@$@$@$@6"HHHHHHH P -$      \AJ\qy$@$@$@$@$@$@$ ~B  ~ϟgJ\\`B$[!!!R`A)\`B$@$;P ;#HHN8!RD )S6M$Ν3gѣG%<<\HHH u)_HH|C&*TM,H M)[liHH P # F "&C/"6W8/i8x.1W]/&g - F TB3+! ifkiMg{I ضm.]1"rλ6nG6')\zޅ+6g9K- \/Y @N# A!NI!FT@䶻sϞ?p9[J$@$GX#""5* ;&HH sJǏ{|y߷_8w/\-. ýq'ޱcMrw:G6۷o tg\"[%'w2K @8%K{uʷ~dw.O>dr1nݦjJ/Ö"A6@_~YZn-5jmZ\q駟AQꫯ֟oVر̟?8裏ּy˕W^)~Kvڥ8? @v8%?#k֬ӧOL0 IQn$22RJ*#H ҥ|f x|AرcSO~r?7apwARhQ)\o*@:غuW9la206/H@tt/ Vi[  x?)k]QFdÆ J}^yHH,]x[3<#{Aɋ/ר7yar+ˀ2ayͩ\sue˖-9O$; $,2   %ӧO&MK/`ʔ)^] 8>;^eC"X]Oj,?5uTW+. l7,$0k ̄g z:u,;K5syCe KGAxVСu'9rݩ,;:0[Y&pN.Ʀ>OOZ @!٥DY]<'(G,DuR3 ܑ`3c(%O?1ڴi I"1|z޴iSV  X]vby̜9SsuE#Nk=0X;pq4hB سxn>[N2dIPDPZ>bH]VǓ@;ԩ׹q<^b6sO>׀g/)X@-[9 sœyu"z}8`}]7~[F9cǎ?X9R4kL+ܗݻw$=#C뮻ƽwBy?iMa7d]#SųeY2"#ռ۬tMBΝw{>;V27׃Av-z=]Z,>E\\}(i:XG"ӪnnWj^DMUD_yhE{>|z&9z5~ܮ:|q뇬k/#n9:\Jv󳟷֟۷ e,"`pm`~zٳ>!ߒ%KtիW x keƴiӤ|ZɁիWwi&>vJ.SvYx\RN#$ +1x``ѢE:+d_3PB&ۭ5IM`)ټy`0 ٻwo<<\w̑si~ΎxB5-xw!p0\>w.+m?~Zp?Rl9 A~ @%@-%liQ?rv~9ul|{[J:L,'E IXbRh6,5qa[/bErRi=9|i,6RE~+E>N2@P8eP^?XGs{|u맬k/c[5D콇埲~&b6D ̺옍2wJg@р5`SNzEA䭷ҧ1lܸL-4y[4Ys̞SBA ( 0/1{'&N}RfԒ9~Z߸&(u A;!iȑ#e.A`kF#<E0|3_FI =a0 pY1;|⫯Җ<; 0/qy`.t{֜`Ft`?\rf*MUf{  @14zh֖$9s4[_~1[((By@U\BU/^ꥠ;Όj'ry2sdK;ۆA ]L$T\9/ wIAظq-Y>iY@iLԶh *UrIc8g?.'H ` oپCe~%zJͯRlgBgkʏLJ٢Q{uz4R X~Rn|*#YqUFcV1Yj6YB<Ìܪ@̬:qmvF@ I(f`Up` ?grC̸%fk&fnoѩA 58F1ee](Kʔ)Jc%n:C/@pp'p,%1H{VIԂRw ib̽69A)=򲿫Z^#͞y\:\At$*׊ʱd=Jx$^ٲTunڴEbCpbf9}NhW*/zD Ulb t[wP =pƀҘ8x>iF@ Q<`u 1;ԤGr1V#qq=:9?J soKUa9h .yhPn;WPaS^cu 8 hBB3Q/ mJAPʮyYoRNm)׶Q ̅7wEv͋oɩjR8Bv|,0Yv> )ZPXW]nt1&aphR)X[[̚'_"kpɍC̅ΘF!ŋJ< e}zI6U{:^'pSKdX:mH +UB GV|7?_p~ C=Yžo{rf3cf\+Ǯ;ʾ?J1UWRoS>$@#R a]w](3{Vw ]S~XH4e9&Py_:A`¾JbO&q=)psP^}֡L~iEѵժJ:Jr#_۫~F4ME_^q[juz°_ߒwsbF*.Tt)yE+m@) /^)qֈ{$v>dh9 w]Ҙǽ]ny5* u[3>aeSI@(![3 ^믿?F|[nE#%,bįp7 3k; i3f7 xퟙ6魧SΔQ`Xdݦ,4 5 J(ImjBO|l']Nsf$[ RC5"PXZ&k/Rl }{IBy$n/98cZ\uqX\;'VKy}!PJoޡPBE=@lMU Qxqf1N-3g}❁4P>8A})hUr\V1C緦WPQuQ(-8q37M_03q{aB!8J VV Rn Ǭ9\*|R.u+ѓrfNٰMуhkY%u* pMh%ĩR(!-T Uof  -_Q Qq]~b9_ryrl +'3W~;IJwm,=Fۣ!Pr_^ j UVp)8V}sl$`f_B /ٲV`&njM37u Hs8PCalWXXa+p qrXSԘIo"Uisk귦> b~*0~q⡧Ji1i~@Ԉ4v /F\"y`a#b|vs{>sVBMA.߲g_SMJ Aõcp М6Tn+Y)x(7u)QaQKeJk!="P?,7437 HBGŔr"P<`H("`phժK͛kW "ߗ(0B (( ~a͓UF}˗/Վ1"Npp'pAAtܥ1QUŚ5k\fyԃs0Eڼ,S}ȝ`YWrwH|GKsCwtm[D>9,2+茞`šOJP0_PvX4&Hš?=fjw=CZ.3o۵['Yv(_>{ caK k+Ӿl1qxAxA!ab1xSFr]&7MlM=)yI\ [\7z"S]틥r 7N /V\̞/y^@Gir_Oٖ-Rr,f#Qx9UĀBuNol2\iX[Z~,b@OTq*CIO|?N`esrcr|O*"z\3VfUt$e\jK[Dt2#&@&@ٴZF߾}G6`LC)9ؚ>4S&{6V0ȇ)4-Z$oRF(t_]`BN̬}O&??GފqR(J&xf]sy$%:zGf/tm+ou)h#Prdj4Xj Ta(MƤnfaf"/P>` r[-La`}`Wk/!`m?4usV7[F($@%b I]I[V5O)ުQlNrZŐݱORyCɧ~/NlZ?k326h"me e=z+'"=Dw7J.rɡd"n؞ؾ%(gF1.kVW8FG&ꎁX 1NeW^zk_~A464nXQ7ozK.,'۷bЌM0HĖciGuoAq`G`e @(om}Wu>@f͚W0@Y0?u 3c9>`I,!Y"Ƶ_yҹsggl7c n:0Kvx@Cp_` sw V@aÆt6X{fX@6XFҗNY.]=}'\uΗ9Ž?`Or*ҔރC)zU#Ii ,RopYs2RrAvm.=,)/W./ڵ+қ5.W{e˄OuR =V<3Yo5\.<-+GDԫ)>2#C*![y@`G:5۵k'ӧO=('gd22YYaa{}Y]@ M0f0 eȐ!cYo/?x%Hy0 PL8ym:O&KjÒ"9ޤ`5ÁP؀QF/X \fX g8ạ4V (ı1mFY>:қgXKx073 hy;geɒ%駟j ",i~<;ga6MOy@he.KZjL `e倘,) 'eZSI*ezMS]N*Lbҧ=nAoG d3kf ٳڱd׏k,J(7˛9+K@  =P@eU=4(PL~+ ~Gr A{Pi)3cJ׏Ajy5~aفk5ޓ|{ִXz6Yq\XWō@^\H /,yiå6J͇27$?&ύ5ό5ZRF52Mz5Ə?,k_'~yZzy+Ȃ343]ƥD}L-;JB&cV1]|). Fm+:c*`%DDsiBEA-ıT}Pe/Jqe@.ܩ9!Nuqj &Rta #: W&hWf>z,0L*t >}S+ rxzz\)%N$@$@$ bXrEIHrR[KЂkv҅$vSهۑPv2ϒ% d@jx2_z֖`]:Ky秊IHH$f6oFS} )թ e.Jt# ;v:[l eHHOA sjB$@$@Dq:t@j-TB؀䆏XiB$@$@$@$@$@$@Fa{HHjtc@4 $~Μ9X4 @z ;&HH ";HH .,'Na ,H wB$@$s P s[N$@$C0CM$-|;ĘHy_*  PD IHH"" @"ȍB ٭rr<{x%_WzHHH -[Vel۶M"""$44eQ$01  |)$[ 4=Bt/%Ku0( KEEE]WeHHr*ٳg%..NO!-] %b@÷Y: ͺHHHHHHH `L<|y$@$@$@$@$@$@$OTB6"      bIHHHHHH\ P ʃHHHHHHH|DJe$@$@$@$@$@$@$@pO$@$@$@$@$@$@$@>"@%X       WTB'       G`Y, +\t钜H.%%^q:?%'H̡h\q'N|̇qp.3EؼYΝ;'Zk ?yǛiH`:ai>0Z'vM HZե}pf5Ǵ.~ԨQrK&MR$;vTZUn<O̙3˚5kdʔ)rJ.]&L;v %Jhv+Wv9S>d5ǜrl' "8<2ԼE*n17'-E+ xޛl^t$%z6HIL U7Z:2O<,{-eKGy]W %ji]4p'8{S]T F IΝiӦ{sꫯri_pl^A{ũeɊ+2>_~Y (XNӮ%    @"F^ wKqKJפHw外>s᥊K:5u2lkt 9}۬MzS\b2?f$r2{̛ V,]wǪܕXw'=mhhԨQy# .iӦi+ śI0oʁ)5E3]9^D3GI_ H*4/Ǿ+' r"J.4Ck6ȚIןY/)>Ro6ԦV< -XP$s , I+JR&+Zm;$q)TξlzeYqQxy4e8{`"R}+ٿz\nƨ7J& 31R\i}ĮO$^Y@S-A1bh- Jqsdo3%ɕW?jbb}(JV+׋'dR&9RƝ:?6rhu,+1r:]Z 'Ajv|$  oOkĩ߻7&*]ۃتػjTm\ZYJT,KvY,mտ*iDMjg ~1:.XwIe: ipMUe|# ޞ$g];JnЈ²/\YR|9iuٻxI2q؇!!J@D dI* uEvQt ( ₨b 0DɈ ̀$a@r {ovW:szSoJqW÷[v'Xb.W\1/I_ߧ>#&nvqb<\sMzӻb;Nzp%~\}6xlou+_.򖷤w=SNI6tIY$a~s- usjfm#of̘?x0.~'L)ε@>d\oF>0)kSLɆ,}f:/{w6]wմeI @@o^ ;RC5&O:̺7M9䀴kѯ)zR[h Oqўh#\sgmL7ZhBf .ُ͆gA}K/f@i7twT!ąz oEtWe3Do{6E0$.=4yl}\|GgYos=7,Hbv /]GĜp SwYvlM׾.l}-"xI,ӟtOa _M6$E &mʎ7\# [l~_fuoCLM;,o5xnB @@~1/+?*ͽ'myyO7 E= ^s_Zvj}H[zbl̼tg]S7ݖZi4g# o/džoCݑ Ѩ b]㪛l/;Z{VZnu8ip# ,R~nL7u^!@{ >:kf_K؉eEe-Pd1yo>nu1t#G6Aa)ʊ z/j}HCv?O ַ Z( nľw=\DЏX㎬^Dc`ϵYfiCKbJ\%zKl?9: #͖z3_׳^../ @O`+gDࠋE&.q RKd/_=1!'K?>cCv!byv:qա.Cvb-_uE?\ xNcy.ba׼qs4LXxE'1/uW/>lmnGڰX?#鲯nٿEj#l[k76- ψ"_ti'gAj=KQ /xnh9 d1Tme,L~p .cNK a~]<DOK}1gbHDPR=-q162@HK%hTvb8Jb.F6-.ψ"^zi:䓳 Grba!@ @`x*”ZtZ/"}ø k]//>\_/ղHm̥W;bHTKνh҂ 4D^hEk=},xӯcQ_}_f?cW[%m7Kb.zyd/76|䎋gkNI67D}ːk+6יZ0 >>Wne,#pރK `%\ZŪ=믟.l(~~Ae=2ZazOXhM^bXb8J/h?Oy2x]w5ſ3gfu_W>I'@ @ J (,挈>GC^qu k1$/z<[+"%W\>=~nqcs'{oM0xy=V`g<^v_(e.YoDbFeN2pIOz_,E{&gB-(NgOWoW_oS~(ړKbrgj=Mz][oM<"z"L7aH"Ĥ%z$~4cI-_~!C<>c=aX97&ӌc8 pIX$@ @wQ;#̾t5DaDg / "}*l@څ Q{b%/jEjiQ%Y{ڶ*1kO&m{8w]e<7wn7eZ=mZxJqT]LkVHу2aH}gvf~,rL/^Gvpqwo?u &=*4[Qo=gמ1kQk=-/i%9PnQ'}yţG_w}<&Jo7zEF}tqHW}GٿHIxӲFeO'TFW~`^q\Rп˞ kb9&COc у>a~ *;xl(F|c{EeAxnj~s']aZ^dG*1q~y}Cٿ'?E1!/G~X`2CZ_1kE=bԝ"O'~s1ٮWS}xg=iw,țHHwG\7;עB E:<Ǔ8"0hcĄ}/}i@_x1=ЃYY=b(+z4#D"oF{'8Ho @ao9X_9Q-#.]8O\mS2☰G6ʷ>(niW%VZ]C[6C6bZ7ڗhnwwEρɵ Z-/]6i hKtIGEJO @@C"t=F G͡ǰ.5@  @(DpBGw&1!zX @ h6tc˖? @x6ZJ5||W8 @. 1ߛ @ @jBTՒ @wA @TC@ @ @` B&p @ @hg$@ @]@b7 @ @F;% @  @ D5Y-  @ @|M @ @@5!jI @滀 |o@ @! QvVK @ 0!{8 @ P ըfZΜ9u[\s$!@ @m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ DuT/@IDAT^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @* Q* @ @@u!jN @J([a @ @m{5'@ @ Bʭ0 @ P]A궽 @ @R!JV @ Du^  @ @@r+ @TW@m @ @TARF @+ QݶWs @ P D #@ @n۫9 @(U@Tn @ @ BT՜ @*0Xa\pAkӬY3<^y啮k8nܸ˦u]7M2%]eG @ 0!h>uY{{͋`<ٿ+"z> @ 0"!:lN:)]|yH|_N~{:#; @ @` )+=__WyM @F Df!eht8hu @ @`Blb~.1G @fA OBc“zC*C @(Q@"vӰ.?|?p @ @` B Sˎ?>-iPo_zٳl @eAaj{7~r_tEe]n2 @ @@ Qb+)gqF* @ Aa}I?x:sJ,UQ @ @`dBnmZB @ @#O@6^ƍ kE @ @(mv}챜W_}u ) @LAan@,9β'@ @/ 1mFٳg{,0;˞ @F 01L}a.I @ @`d Bqt @ @`BhxHXFq @ @O`d\]p'#\veGq8 @ Ѝ Dɓ'H5I]w/D  @ @`!rn9R B @F Di2,#%Y~>|ș @ DN+gIv Or%@ @% B䄎;C&{;ޡDr#@ @ \z "GydG)) @FauvT3glRG bJguVz{ȩ1D 0Dk'[  @ @` D14#]pkMfJqƥe]6c8)]Sڑ @F D Gy$]i!0vet!  @ @Hef9= @$hg @&,"AnnG @I@֊a" j @ @(: D@/+ @7Q@D#@ @/ 1 M.!1 $@ @/ 1LM,!1L%@ @/ 1M48!1ز&@ @/0a(?Dԗ$@ @U(gΜ4mٶVi"k4*m빺{C @ Pzʍ @h" j @ @XAb=F @4hc5 @ P Dr#@ @B4 @(V@XO @ @M!XM @+ Q @ @&M`&@ @(Sn @ @@A&0V @ @ B)7 @ @ D  @ @b! @ D@  @ @@zʍ @h" j @ @XAb=F @4hc5 @ P Dr#@ @B4 @(V@XO @ @M!XM @+ Q @ @&M`&@ @(Sn @ @@A&0V @ @ B)7 @ @ D  @ @b! @ D@  @ @@NnU9sf ԟ @ j @ @XAb=F @40 գ[~lW`1=! @=!FJK8 @ 0!x @)#% @cU @A @qA1G @F Hi A @Ƹ o`#@ @#E@b @ @c\@b7 @ @" 1RZq @ @1. 1X @ @H)-8 @ @ z @ @`Bp @ @` BV= @ 0R!FJK8 @ 0!x @)#% @cU @A @qA1G @F Hi A @Ƹ o`#@ @#E@b @ @c\@b7 @ @" 1RZq @ @1. 1X @ @H)-8 @ @ z @ @`Bp @ @` BV= @ 0R!FJK8 @ 0!x @)#% @xTdKT @=!FKK9N @ 0!Fy:| @ 0Z!FKK9N @ 0!Fy:| @ 0Z!FKK9N @ 0!Fy:| @ 0Z!FKK9N @ 0!Fy:| @ 0Z!FKK9N @ 0!Fy:| @ 0Z!FKK9N @ 0!Fy:| @ 0Z!FKK9N @ 0!Fy:| @ 0Zx̯W%@ @^XkJARj @ @`D1"A @ @/ 1X  @ @  @ @m @ @`DBfp @ @` B6VC @ 0"qG#+u]|4gΜ"q]X`a(ulfɲveYeij8O,(6'fq,Y'PlNeq²8ȉgq,Y'PlN_p@=0D߰M6!˶D%W,[teG\m-W,[teG\Mc O? 6 ]|iV,sBH2RIna~; ޲<tep vcUuO{@|uWs`Yg%=\:ꨣtG~iM{,Yg9,Γ%ɹY'K s8O,(6J!=t%s9' o;9"b}m.ݬeٍZ}x8y&d,;Xg9,Γ%)Ϲ{{>sbG)J /W^y%-馛nJ?ă>6xl+*eq ϲ8ȩ&l~ś3 ˜P9 #H9 3Y+Os"eg^RlJ!,)ַv-M:5-~v 6IYp3Z$gM,;kg6MRm:̳CYp$L'L뭷^c&V`G)_W}:`W*_:bG)_W}:`W}q퓌<@8qb}Fc-Xϲ8ȉgq,Y'PlNC=4vai;蘬_LW^ye^`9ϢW,{'/Bꪫ${Dz,{&=a߀YM'fڧ,;j1nt=wW椻+mv u]vlbL,; Xg9ug}C,1Yg9,Γ%ͩs,[te'ZVb8FD_~袋fc7ptygô+ D љe.V.y=~t=gBM,#'ydY@9=7}ooβQ,JKW z뭗}tg*1恈x G?JKi GXT,koYFNy=fsD1^Y j5ޏgcnֲF>,t6Y7ʛe^|*9sO|)r'[,ysSpn,k\YFNIdNewnbHu<'"ݿgٽ]=y6RnŲJxvo7xOEϲ#8ƛ+LK-TZxᅇTx-HӶn>Ϥ &d$"`Y܉8ȩnGZpgM,;kg6MRm:,[KɲB+tn~%X"mFiܸyCƏ_|k[.}LrHZ`JV| NYFNx.2~[l&Iγ PYv&)6@nָ,[te'Zλ➷2ƏLK;Sа[mUDZ8}s˟yR,Yg9,Γ%ͩչ{{g,;je+*q<nAva>:tI[nI+bz׻ޕw_A }rIkXv+xfW]uU:䓳VZ) a}-˺D?Yn?5z{Ͳ7{fwn˲sf{l&|}A=bHG\-sNZo6m,Yg9uyu]y&`,@k 8nb!X,[tO[lVhT9!+z@̜93܅^N;\ieqβ8)g qdO)Nn,kKYFNX`߳ޮў<tewnbHu<'"ݿgٽ]=y6RnROFEuӦMKwuWzZFyS+/]LH=*V:mcٙW< 2U, uμZfJm,!Θ_|1'[EL^:K/fj,; Xg9uye{c(75&,#'ydY@9uznܟesN/6!ұ2xY%_~4iҤ4y\sʹ +d1cFg}6Jc%\X6@aU'SLIx{CcwWŵ-,#'ydY@9urnFۛln *Ww_{*cx=k֬twf)ZjB -=^`Ǘ,{{' /pm]e],{7fٛ_Yu'fڧ,i BsJ1cC=$"01qַ5ޒ`_,{7"ݿgٽ=Y=ͲFoY7xoEϲ{{'"1+ @ @` c޲&@ @ BQxA @ pʛ @"f<~/T-Xh#ˎ&mI^e{)Xʗg>ZӱlV)?#iqo]wݕ7\$]ןetCvd9x]wyOswfYY,2rY'K GydY@JzBtk/8|ivJwqG9H^`Y' ׾y^oЮ,gvgǗ,{G@zx˲AtJ{׽.wyiܸqi[nɷTCX!zˮ{n{ۧ[oa:+ lo7˼R'^4`إ?|[/iʔ)iӼtcVn~,terإ,Qk.ݮ٭X5v ˁrXz ywiO~Ô -Pk_}ճG}&Z`a\Ş /p6os= .`kҞ{6hb @na[lϜ'˜P9́A`Iʲ PYv#`Oߏr5I² LYCg1RK- */==#}㤊(.N<@6(#A̞=;𠈺Z \|9V7ڞeq=@9Xʑkz;Oowڋ<^<SKes3Kig,;:,ԱS#Ϩ睷y;˘}ƌo~sWlp/_]{)[m&}ӟNK/tewg?y62Ct{ҟ_&M>vWXsWOg* y[>3i„ nggm7<=<*esBQz5ߟ} x$_k/~~׻ޕ"ӟeqm<;~,6,mټN?" v,xƤqׄgk8_dw"[c=6#, ̫YO>)˟t7|i4M'[oeu\<^vq7"H/'@70m~lsz뭗xߏkѲkd'ONm݀dΜ9)Pߘgc=m׿5Lm|UwdgLFy'|]JogY\zY9ԗxC5,Zy^wu1`ԩٜ0s5 ^LO>oxQGוXk}nq#2dn&^{k PXmߘ~.>+1O@^ϸ?ѰIܫ%p,N8! BDogi-2%AȀ1dL*jqqWoǤU*ocY\ǗoC2 wPohH \sGb(P $1\/N_aDѵxV04'4˜Pmyܵߥǧ hms㼵4`إӵM7ݔ=~;nDH$qPꫯN[ET*}^Ϗ|#3f 7L?2 }rIk>lODc8P,qg*w̙3ӚkYgV};&W/<ݺ6_Rbz/ſc MTܰe]%0.~{|9W@ܭam{!O!KT,;ds Őߠ>2>y=vu:jn/rZDǘ,iM7ݴi*oheCSRL$7h"07"AX="%+f~_gu ]ٍ;(nL'eY\hEY$#d]c}_+qlijs1>3#O3GIDATX|ŕ8vsbYlw}Y6w=PӅ-qƲp'/ +Wω4pXXziꓲϿgÆ},qAN:ix`gJnƬvlyoX[!:l{,U].{gQ.'%VRݺGwL s=|isxs&ģ.|Fv7$igdOt&33slf2f=|U;b{B|zwx$|LXki,3[\>zx\6αk8}s$uxB7ͬGYO?tZguo~&ԓ9H 5-a+{X4u݄z#/袬 /]xK̮dzgZ f"5kVf2ˤx׿>ӿ*Τ[wIDc~rtXwuqf~>Vq|Տ`CsnDI&eŹs3_,.#K|fj5η3{3Zm?7/>741T oi-1c~q3gfk[7bPp'g~qwcx̣fs]gA=cM|o!qWyWuF[nl.0bȵe@bΉ"H=x[|9>G!K{ǜDY,#p|cmt.[4.Cxt|_/ ]SNILNG?tUWgwKvt/aki.OnkRf,{G..\77ɖ@t#8E_܁':쮉s.g|fa33M [ꟙ333gs3 (lmhk33)l$z]0z򗿜ݜ )cuڲ_zvNƝ . {Cܽ ,> BMDPK<Ķ(ߐ'Z}2nxp.Z+>^}^|yj,qK}Cʂ2T b|q)ӫD5-oyK3f*w?gXa|Ɏ%.]X5 i:3~э|񨪕VZhMj3 Lcnӧ®.]b'͸[cNξF){i|f1|gfc\kiL:x駟Mk,S4&_q7X İN8!Y8UJa83rRgz'mT/q5Aǘ)͝w9{£aSR )k ob#+W@D;CO,5fI,-8S bzK=ʚx\dtE:Yoxe6 Ks:ꨦ_c2{ ̌7ljX_}ƅ̺Ho?"f*^x>77]uU81qGcǏogf >e֦Mbx,qW?.Ç\q1N{jyŗQ7VC& T1Kܔk4| {|ޚ%Do=SsϋkQDK/Y5ه?ϴK:3eʔC>zB΃M.T,,M޿*t͖. abf+~%.:O6&4)✍egf'}f:DEPg<^^}n^{Y^.Ygf|ɮfևU^h}xѹ1z9roPYgY 繋Jb(j|>YO{XL Ƅ;},k VE4w袥I7#@^܌sf]wy6(~33zgfЛL gaSw磗I a?>ĢkW|)oqݽYť0_Kk_l cx%@E938crqq*&F9Ks3;ns|q1R]̌μs?qv>a:frs=7n+c[|fv߇2e ?<ܤYg CDyܤYd}!Q;ż(w7aFc/BG^n7q&o1;?7>CM9“YAݙs;G<#|թ+Mz @ @Pzyp @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @՗ @ @ +`V'8 @h !Z}IK @Flu @ @ ;{L7IENDB`graphql-ruby-2.2.17/guides/limiters/active_operations.md000066400000000000000000000127231476434635200234130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Rate Limiters title: Active Operation Limiter desc: Limit the number of concurrent GraphQL operations index: 2 --- `GraphQL::Enterprise::ActiveOperationLimiter` prevents clients from running too many GraphQL operations at the same time. It uses {% internal_link "Redis", "limiters/redis" %} to track currently-running operations. ## Why? Some clients may suddently swamp a server with tons of requests, occupying all available Ruby processes and therefore interrupting service for other clients. This limiter aims to prevent that at the GraphQL level by halting queries when a client already has lots of queries running. That way, server processes will remain available for other clients' requests. ## Setup To use this limiter, update the schema configuration and include `context[:limiter_key]` in your queries. #### Schema Setup To setup the schema, add `use GraphQL::Enterprise::ActiveOperationLimiter` with a default `limit:` value: ```ruby class MySchema < GraphQL::Schema # ... use GraphQL::Enterprise::ActiveOperationLimiter, redis: Redis.new(...), limit: 5 end ``` `limit: false` may also be given, which defaults to _no limit_ for this limiter. It also accepts a `stale_request_seconds:` option. The limiter uses that value to clean up request data in case of a crash or other unexpected scenario. Before requests will actually be halted, ["soft mode"](#soft-limits) must be disabled as described below. #### Query Setup In order to limit clients, the limiter needs a client identifier for each GraphQL operation. By default, it checks `context[:limiter_key]` to find it: ```ruby context = { viewer: current_user, # for example: limiter_key: logged_in? ? "user:#{current_user.id}" : "anon-ip:#{request.remote_ip}", # ... } result = MySchema.execute(query_str, context: context) ``` Operations with the same `context[:limiter_key]` will rate limited in the same buckets. A limiter key is required; if a query is run without one, the limiter will raise an error. To provide a client identifier another way, see [Customization](#customization). ## Soft Limits By default, the limiter doesn't actually halt queries; instead, it starts out in "soft mode". In this mode: - limited/unlimited requests are counted in the [Dashboard](#dashboard) - but, no requests are actually halted This mode is for assessing the impact of the limiter before it's applied to production traffic. Additionally, if you release the limiter but find that it's affecting production traffic adversely, you can re-enable "soft mode" to stop blocking traffic. To disable "soft mode" and start limiting, use the [Dashboard](#dashboard) or [customize the limiter](#customization). You can also disable "soft mode" in Ruby: ```ruby # Turn "soft mode" off for the ActiveOperationLimiter MySchema.enterprise_active_operation_limiter.set_soft_limit(false) ``` ## Dashboard Once installed, your {% internal_link "GraphQL-Pro dashboard", "/pro/dashboard" %} will include a simple metrics view: {{ "/limiters/active_operation_limiter_dashboard.png" | link_to_img:"GraphQL Active Operation Limiter Dashboard" }} See [Instrumentation](#instrumentation) below for more details on limiter metrics. To disable dashboard charts, add `use(... dashboard_charts: false)` to your configuration. Also, the dashboard includes a link to enable or disable "soft mode": {{ "/limiters/soft_button.png" | link_to_img:"GraphQL Rate Limiter Soft Mode Button" }} When "soft mode" is enabled, limited requests are _not_ actually halted (although they are _counted_). When "soft mode" is disabled, any over-limit requests are halted. ## Customization `GraphQL::Enterprise::ActiveOperationLimiter` provides several hooks for customizing its behavior. To use these, make a subclass of the limiter and override methods as described: ```ruby # app/graphql/limiters/active_operations.rb class Limiters::ActiveOperations < GraphQL::Enterprise::ActiveOperationsLimiter # override methods here end ``` The hooks are: - `def limiter_key(query)` should return a string which identifies the current client for `query`. - `def limit_for(key, query)` should return an integer or `nil`. If an integer is returned, that limit is applied for the current query. If `nil` is returned, no limit is applied to the current query. - `def soft_limit?(key, query)` can be implemented to customize the application of "soft mode". By default, it checks a setting in redis. - `def handle_redis_error(err)` is called when the limit rescues an error from Redis. By default, it's passed to `warn` and the query is _not_ halted. ## Instrumentation While the limiter is installed, it adds some information to the query context about its operation. It can be acccessed at `context[:active_operation_limiter]`: ```ruby result = MySchema.execute(...) pp result.context[:active_operation_limiter] # {:key=>"user:123", :limit=>2, :soft=>false, :limited=>true} ``` It returns a Hash containing: - `key: [String]`, the limiter key used for this query - `limit: [Integer, nil]`, the limit applied to this query - `soft: [Boolean]`, `true` if the query was run in "soft mode" - `limited: [Boolean]`, `true` if the query exceeded the rate limit (but if `soft:` was also `true`, then the query was _not_ halted) You could use this to add detailed metrics to your application monitoring system, for example: ```ruby MyMetrics.increment("graphql.active_operation_limiter", tags: result.context[:active_operation_limiter]) ``` graphql-ruby-2.2.17/guides/limiters/overview.md000066400000000000000000000037001476434635200215360ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Rate Limiters title: Rate Limiters for GraphQL desc: Manage access to your GraphQL API index: 0 --- `GraphQL::Enterprise` includes rate limiters built especially for GraphQL. For REST APIs, rate limiters often count _requests_ and block clients when they exceed their limit over a certain period of time. However, this paradigm doesn't translate well to GraphQL because the cost of serving a request may vary dramatically depending on the GraphQL query contained in the request. Instead, `GraphQL::Enterprise` implements two other kinds of limiters: - An __active operation limiter__ which allows clients to run a certain number of operations _at a time_. For example, if the limit is five concurrent operations, and a client sends six requests simultaneously, then only five of those incoming operations will be executed; the sixth will be returned with an error and it may be retried when one of the five others finishes. - A __runtime limiter__ which limits the amount of processing time a client may consume during a given window. For example, a limit of 120 seconds per minute would allow two concurrent requests on average -- although in practice, it might be spiky: perhaps five concurrent, 20-second-long requests, followed by 40 seconds of no requests. There's some overlap in these limiters; both of them constrain the amount of _time_ a client may force the server to spend in handling requests. The active operation limiter puts an upper bound on how _many_ processes a client may occupy while the runtime limiter puts a bound on total processing time (regardless of the number of concurrent operations at a given moment). To get started, read on: - {% internal_link "Configure Redis", "limiters/redis" %} for the limiters' backend - {% internal_link "Active Operation Limiter", "limiters/active_operations" %} - {% internal_link "Runtime Limiter", "limiters/runtime" %} graphql-ruby-2.2.17/guides/limiters/redis.md000066400000000000000000000024251476434635200210010ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Rate Limiters title: Configuring Redis desc: Preparing the rate limiter backend index: 1 --- Rate limiting requires persistent Redis instance, just like [Sidekiq](https://github.com/mperham/sidekiq/wiki/Using-Redis) or the {% internal_link "Operation Store", "/operation_store/redis_backend" %}. Set `maxmemory-policy noeviction` in `redis.conf` to ensure that Redis doesn't silently drop keys when it reaches its memory limit. ## Memory Usage Estimating memory usage depends on the string used to identify clients, since those are used in the Redis keys. Using 100-character client keys, the runtime limiter uses 400 bytes per client (two keys). Memory usage by the active operation limiter depends on the limit because each concurrent operation uses some memory; a higher limit permits more concurrent operations. With 10 active operations and a 100-character client key, the active operation limiter uses 350 bytes per client. Additionally, the limiters use up to 35kb for dashboards (2 limiters, for each one: 2x 60 per-minute keys, 24 hourly keys, and 30 daily keys @ 72 bytes per key). By those estimates, 1 gigabyte of memory would support both rate limiters for over 1.4 million active clients. graphql-ruby-2.2.17/guides/limiters/runtime.md000066400000000000000000000152431476434635200213600ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Rate Limiters title: Runtime Limiter desc: Limit the total runtime of a client's GraphQL Operations index: 3 --- `GraphQL::Enterprise::RuntimeLimiter` applies an upper bound to processing time consumed by a single client. It uses {% internal_link "Redis", "limiters/redis" %} track time with a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm. ## Why? This limiter prevents a single client from consuming too much processing time, regardless of whether it comes a burst of short-lived queries (which the {% internal_link "Active Operation Limiter", "/limiters/active_operations" %} can prevent) or a small number of long-running queries. Unlike request counters or complexity calculations, the runtime limiter pays no attention to the structure of the incoming request. Instead, it simply measures the time spent on the request _as a whole_ and halts queries when a client consumes more than the limit. ## Setup To use this limiter, update the schema configuration and include `context[:limiter_key]` in your queries. ### Schema Setup To setup the schema, add `use GraphQL::Enterprise::RuntimeLimiter` with a default `limit_ms:` value: ```ruby class MySchema < GraphQL::Schema # ... use GraphQL::Enterprise::RuntimeLimiter, redis: Redis.new(...), limit_ms: 90 * 1000 # 90 seconds per minute end ``` `limit_ms: false` may also be given, which defaults to _no limit_ for this limiter. It also accepts a `window_ms:` option, which is the duration over which `limit_ms:` is added to a client's bucket. It defaults to `60_000` (one minute). Before requests will actually be halted, ["soft mode"](#soft-limits) must be disabled as described below. ### Query Setup In order to limit clients, the limiter needs a client identifier for each GraphQL operation. By default, it checks `context[:limiter_key]` to find it: ```ruby context = { viewer: current_user, # for example: limiter_key: logged_in? ? "user:#{current_user.id}" : "anon-ip:#{request.remote_ip}", # ... } result = MySchema.execute(query_str, context: context) ``` Operations with the same `context[:limiter_key]` will rate limited in the same buckets. A limiter key is required; if a query is run without one, the limiter will raise an error. To provide a client identifier another way, see [Customization](#customization). ## Soft Limits By default, the limiter doesn't actually halt queries; instead, it starts out in "soft mode". In this mode: - limited/unlimited requests are counted in the [Dashboard](#dashboard) - but, no requests are actually halted This mode is for assessing the impact of the limiter before it's applied to production traffic. Additionally, if you release the limiter but find that it's affecting production traffic adversely, you can re-enable "soft mode" to stop blocking traffic. To disable "soft mode" and start limiting, use the [Dashboard](#dashboard) or [customize the limiter](#customization). You can also disable "soft mode" in Ruby: ```ruby # Turn "soft mode" off for the RuntimeLimiter MySchema.enterprise_runtime_limiter.set_soft_limit(false) ``` ## Dashboard Once installed, your {% internal_link "GraphQL-Pro dashboard", "/pro/dashboard" %} will include a simple metrics view: {{ "/limiters/runtime_limiter_dashboard.png" | link_to_img:"GraphQL Runtime Limiter Dashboard" }} See [Instrumentation](#instrumentation) below for more details on limiter metrics. To disable dashboard charts, add `use(... dashboard_charts: false)` to your configuration. Also, the dashboard includes a link to enable or disable "soft mode": {{ "/limiters/soft_button.png" | link_to_img:"GraphQL Rate Limiter Soft Mode Button" }} When "soft mode" is enabled, limited requests are _not_ actually halted (although they are _counted_). When "soft mode" is disabled, any over-limit requests are halted. ## Customization `GraphQL::Enterprise::RuntimeLimiter` provides several hooks for customizing its behavior. To use these, make a subclass of the limiter and override methods as described: ```ruby # app/graphql/limiters/runtime.rb class Limiters::Runtime < GraphQL::Enterprise::RuntimeLimiter # override methods here end ``` The hooks are: - `def limiter_key(query)` should return a string which identifies the current client for `query`. - `def limit_for(key, query)` should return an integer or `nil`. If an integer is returned, that limit is applied for the current query. If `nil` is returned, no limit is applied to the current query. - `def soft_limit?(key, query)` can be implemented to customize the application of "soft mode". By default, it checks a setting in redis. - `def handle_redis_error(err)` is called when the limit rescues an error from Redis. By default, it's passed to `warn` and the query is _not_ halted. ## Instrumentation While the limiter is installed, it adds some information to the query context about its operation. It can be acccessed at `context[:runtime_limiter]`: ```ruby result = MySchema.execute(...) pp result.context[:runtime_limiter] # {:key=>"custom-key-9", # :limit_ms=>800, # :remaining_ms=>0, # :soft=>true, # :limited=>true, # :window_ms=>60_000} ``` It returns a Hash containing: - `key: [String]`, the limiter key used for this query - `limit_ms: [Integer, nil]`, the limit applied to this query - `remaining_ms: [Integer, nil]`, the amount of time remaining in this client's bucket - `soft: [Boolean]`, `true` if the query was run in "soft mode" - `limited: [Boolean]`, `true` if the query exceeded the rate limit (but if `soft:` was also `true`, then the query was _not_ halted) - `window_ms: [Integer]` the configured `window_ms:` for the limiter You could use this to add detailed metrics to your application monitoring system, for example: ```ruby MyMetrics.increment("graphql.runtime_limiter", tags: result.context[:runtime_limiter]) ``` ## Some Caveats The limiter will not _interrupt_ a long-running field. Instead, it stops executing new fields after a client exceeds its allowed processing time. This is because interrupting arbitrary code may have unintended consequences for I/O operations, see ["Timeout: Ruby's most dangerous API"](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). Also, the limiter only checks remaining time at the _start_ of a query and it only decreases the remaining time at the _end_ of a query. This means that simulaneous queries may consume the remainder at the same time. Use the {% internal_link "Active Operation Limiter", "/limiters/active_operations" %} to limit behavior in this regard. This implementation is basically a trade-off: more granular updates would require more communication with Redis which would add overhead to each request. graphql-ruby-2.2.17/guides/limiters/runtime_limiter_dashboard.png000066400000000000000000001476121476434635200253060ustar00rootroot00000000000000PNG  IHDRGMm@iCCPICC Profile(c``H,(aa``+) rwRR` `\\TQk .ȬǶoc=,)<zZ qbrAQ c\^Rb"E@G3@t{ a r)@ [' I< ؍MB}-M PZQ *23J!`d`d o7(Ɓ+c`!B,v9> +H,J;KqͽuÁ^d`{Áo^ FVeXIfMM*iDGASCIIScreenshot@iTXtXML:com.adobe.xmp 1095 Screenshot 655 z@IDATx|E$$!z/ޛH/**͆]+(bAHWtC$g.smK%<<;~mڶN k"}E6-`#HH qfvI@u$A$@$}Ml xMרHHHHHHH Pū6 xMרHHHHHHH Pū6 xMרHHHHHHH FM$*Տ,VE<T )(sUtMj GEb@P4;R".btǂ$07^UuxHHHHH#y"YO`BLlz{ݸ5Ho߫wŤGd#O;f@PIhr4逸J1 ű/JS1b9R*DQ^̀IRuO"hUlڼc3^-%*]neVb,Y4h@4i"'OHq?\6b:æM+fMrVWwD+W"U#*NMA= TS+;vn#U"S[)cٲ.~Oy}r&fuwP!GhK[J:q ;&? ti # yJZ~ 61 JرC #'23S%  $'őz֟fBg(ʣaҾ]8ұCG)-Z;xi)su2FխCHh٤FyǑ^[3ghbŊ>~Zʙ :E-[H~1=x8zqXaYF>`Mn]8bmyqRfM[aݦMźPA1Rm* .to0-HMF#(q $ o/n*GՕ ˞|QZ=9G~:ԃ~P}9q8_v1ucǏݻ[u4JV+!N%!@,wjO"BlN"}c SL\ Q+WT R ըQCSl"# ፇ;wG.~M馛tnW*kyUW9ڛb6zЄV)ڸx)(@$)ݸS8Q=GsdpIXY;rd6cvU)R#M.NNvzEJBA>bc[6ˑ#GhxQٺ*JmSL4@bսΙ&Hj)WO90U"gT@~vک;t('{vvuE\K58={vkXD*.bU .;&5**RwVFjh'(uөC;{NGrL?w\ù0x];-X :W1 th Bݺk&:~_qZ-Td5=W]m(Iya !VuVUV!Bxk{a)Th ?w6vbx%ZwPސ/E!!66f۶휟g ~ biTUޘyAܹC`+}ʣ|7@@'6QÖ꠳_~vЂSfmΟփ=maB#ݡpA=hb%tdcT=" [GG>6:FC{RZ $0tË΢SL}A3O^LBiN(!Mk`WԻ^ vC1YBOb{&\T2"CêlF^|L<*jv. /x@a):YAM׭۶5eK T7+/FWZw8D֠& oږKzD/x1 e f(뜧T0f{MҫWo< ԫ{ӎ>3A;GXVP3d--Q5YФ+ d@G(n`n=Bk] ~Uq3g5jƤA#oVt{GyHFp>s>3:dlvIs1ѱrIHtwÖ4nq YOO5nv^kL} # s~ڶ+E+ MX=!z%?z515^/xR. @)TSs(R$DMKܧ=. ˖[ Dp.l"NF'q`ڨ|K,=p/E8ERR>!~mis"+->@}'I`M4>b5Ly0 ujOu)!JœPIrBM-ʯs +`jup7 19g7xHe{jf" q[ g@\={f !;F$@$@YI e'+sϦ.Qw3^ư đ nzm|:v.}&u7;ߜqt<tЬv^=DMjVly}Yׇ̱%u|1rs 7r_{n]_K3lR+E Q1Hy:M8XZVZu6>/"]+#}D[/{+=ߞ60_|9pmgyُE"/#ya k/ew3j c 5ǺcQvĩZ!Yz̼?b )x-:7 P45jTRvpp-4[|  +ٽpŠ{Aq_w qc/BɷZ>ƣOhQi/(;-^˞'>FN-[ӿ?3zu%Kh9mVw1]MU=ؿ0~v |ٿ_1؂i,bJ**x&3Bu#~`#gUo{;pOTܫXѮ]ɹM$@$@"~QlΝ;tj '^xs'Mt^VNWw9~R$cgr1oYX62/ΝMܻöT`hoF . KԢ VZDY/GHg{A:;3}bL^0a":vXi ի[OWVZutbiDX: ;O' 8lbF["`jjM 5\oĀ^`2=5_z3fHkX50{>""j1шi X! {ƍu]A)[35~g0uØ2+gkcavX,ڍ6Օ3o< /^6mڤaF9c- ݿoJgߓXyD%K-|rVBʂ gߛ4  JyrZtܵ}ӫw2I ;|# ӧ;I^TZj` apw׮];4V<]QC6W|共//V{tʛr1W_}ǺM^IIxxz'_s~Ctlx`z૧JS=TR5-@P .&I@HTQO --[vJhDE,?+X&QbWDxL 'Cò`5L A1`FgIa^>`&Y1ۘ>cb`:ޭvb#bq@ʕ:|+Ք<|#V1 MPCyZi6-[5knxO7Ҿ TsЏG] VX?+V8V@;p!C{=>!`c+1%l '\B ?tX -ZtpСk׮>fX\`gb rSSCZ{@.j ">i5/=R?1qCgܕt2aކ+sςYV['g κغtWmo3ATO */?݇rw{G]?ڵGqu;wg%aV>jfkHhvDDV4jY޽RT&X6p](>LAP1!< 4|%  kӶu]f3G0<( :~L@0~;.ŏJ()Fڶ iltԴz8 Qoq~N^|D8XVAT_'/h?pry #SNuejx528KN[;wfSuܸ%BsuS/,lV1pT\٥,k~{bxH‹4vLE)4ǿ;^M]^Rw'QMZ}RCi0>}Ou ^͹ #HA}̕m%,ٷ`DJ7'xWҪ6c'zr|M]M;Sih]<1,ϸ|2=]VL5NzWsZ(8ʌK)I #FH^RqSFO93~S.Kz$.ߑj/OyLO>3-?p-[6Z x8s9~/g=X6 @.h'|,m,<!4ЁwUq  t"O~HF?9Z7nD: c>}6짩QtHsE!00X &=֞VA:|P F6mڪͪpU=rGпC;SP]cyRO**c؉SfۇYKc;=3|yyo408#u=M^YƛJ;;͛58fIܕ{]>ؗYQY8>K,+r oȳ#ˁQ8pjO6F%q&:F!p s00#_VU:MϑեsɲG?~* gⵗzgSqqRe޺Kv}3S.Ib,T!W~(  sHHjH d@=U̜j:ƌeO+#rt39ӗ#e4= LqNv"~GRG̦̞>\S_yB.}Y5M&TԼyT # !ŋIվWЁYʺ&٨K&,1u0cQ7C@b 9'5A^m Mbx: @^$3H^:\y)`fo'!eK{}\7ASa۶**=fYV?0,ڒe~9dv8CEgQ(ʑ #X=f0@)YLS[G=uܾST U#ꢼHbeʧ_ʻĘKXj ^cMr|3z*IHHHHH2NH}?Z%Pj 9w舴y _KZ?dRI9/j&Yp=%HmJNٗک'@z l%s [\KÊ *bHr|. \>0r緒qőv$@$ `U<V|>|HHH 8W//_ݶe킸vCII1lmVUi|jDK.KWm! G O#wEԩm۶2p@yrZMZM,ZX^~e=jp´89r$c[ … KA7;>'hmn HcgnFuyH $>#I?j$  pO |$%\t0^SnK}:l.%.긜| ቹO# 08v옜:uJC?z &ܹs=^~or|2bYb 0@ׯ'M6u7,j̔O>-g65f4x5>iժ*TZU+ ,I-[ ƍ+_kr:p {nwɳdi53/+چo9W_" bpW_}U3{!CȜ9s䮻1H L^ǿ+?oSm-ޘj{YnYp.˞={<;w.C5KHHm۶ ^wjph145CoD%۷/ULgux<#A+`y^6 HV5*KSv=t!A^;#_ȼ^7?]-$g|OKβX=N$Vo\bwXvWyL{HRxZ+<=Ƶ))CE…APc֭[LwDzشi>˕+'Pbڵ2|ޣsT\IPVvC̙3u֭[벪U,͔24h " s='/.ہo%22Rƍ%;CpPBt{-~X\NʂnsL)_tA3d׫W٢4?< @23c!U*pmY9HÑJ^W}A%ITbkj™Ԓz,IIT~,)9괻C} ta&KoE! Ds.wfHH;Ɗ.6E[Ў ʩUV,p 0)IR$N;(u(Tlǥyp& _|+EHzJ&uYБ- oZ߫f@8e/'YvrQMy

՞Ƕ&f՛Kpb?뉀ϊ#XhqLs9~g2zhyo>QF2iҤt߿yOa^$e˖F=K {b^%Kӧ<|s[o[fgEuYf@=zЬaK/ D @'W @pV4@T(y07\ A.}dAÍ;K[~s,N%ݺ<XIm}wgf B{zd><H (MЧk=ΐJo aʺW^UKq!W`[Iپ3jˢ<'<:̈o/_lF|Zʕ+7`x a3cԩ2o޼*F}Y0L۷o/;w^*[nMVZG >|X^uakȗ_~/$rNYV-#u L 0<r?C`9qlͥoJP wɎʅSgsd'Զ/H|+R|7V .[^|OvLNNEѥZCo#q潸m] K։Ic^"k:krBM5 U۶OA6FK/^RjGo8E gfoΫ5?+n?WUsi\=s]GLKq89d8jV^~껱T%Jmw'_Iνk=)ImiHje{آ7OHɎ-t$PySޮ;>FojkOZ*}Bŝ+2o޼~Zf RK>"OnNmHcy>Խ{w}zޑmbFy]h$@I`SiyrlzYNgc!7osn/*N|]}= Kưϼ%&|[OxU7'_}Wvp\QJU[?Btlt v=)s[Iv P5t!C.{0^@|P^?؎2HZSA2:ŚOjQLAی ĩ@01΂CleY넶6QȚ.HB7Q'^1.u `:1 S({3x+\xX5iJ@IDAT$?ܰYOXRH T1dqG P3Y3-9sT]Bd7?A\ YJvW(Te^8:[ 0PH ԒΗ?]#8ɊgC$j"IL&T,\:5M<>ȼ>%ZFE\KʶnѱīCc$H-u_dq E UJ8p Y#7quwˎWBT(^,i|MdhŊŸ/S1b4x<}9C*'mo|5I{V{}`Oգ_|%uu&ym& -5EY9d9۬h[R9zWj+DJth)]KW ҳ;io#Ͳ&qtOO_2;/StE7 \Ό|M>Zy}ÓGj,[f@ˆ1ta}Wx 8l*U|YnydO$&Pa M jz0OeՃO[oڶƯB+ۓ;K5o"6oXv~XJL)ڢ9_Kw<GpsIf@PY뢚Jô aSI`='jJӁKB01;L%Kb:^ dnn^+ ^,zn !`+W1G7Mj&,KZfL:ĉeɟ$@4 @B9I +-g-S!!l-kȀsKLz &`@6 ~3R35%bt'leG+O/!1vLڬ~ vGYi]ǘ=tQa\ŐҥԣG^+ݛjF_y^$ ;{Y֞Kaߟm̡i+z6kPz 1{ڼm1Ϗ``50O}iРԩ['\D}t3Mύ>2ծOt,~1]ؚ!J(84{K䲝&P#lBIcz4x"Hw7@`bDa?s,{kG ڃb*Pe5*k\oLRg>HH;I̡ÂXOSf'6Yi%|rno1Ĵkr.U!T(ob-`%Z,>ҙ8MX=w).Ǔq0}qRzb6`:V?-ް˩a#cA/9sj̅5j̘1C/e{:>Z.=;ӦMs<2)>Gڵk'{Gf̖΋es~a-_j׮}7sСN.i w_,`OJ:1dVKݾWLT_^"nY6O""uF 'OjmO8O{ckֹe[fGP[{nYlc6tgq DH~ۇ_;_9!O#p_Vgxw}l2璾ZҢȑ#O?u^dž ʔ)S䡇rF7M mE=@?a_ vmtgjㆇ6d8pv"k[#?/7xIꫩ_1چbIoMn9޶aT n&Lcn3>8K/>i/$@C0UA'~,E`jYu]ms7:Í2`LU脛!𬀇Ji` 1JAaDk}2e!z>v~ko>P՜7ϼq skL>xE9ֺWTQsJ4` \gL۲&{ ug%p#b^[^oһջëWO="^~Gr\g_NM9,pކ0iGRb'YW|!v,# o]1ådGG. [񎬾)90g^Ź5n ^vA< 1*P+`ŘBJPazR:@>.+Ś5ud6qݲh)Jؑ%kcG{Ͼ|%A%IʎUq S4mLѣŪ)W.(SjʌΚC Wu$1Kr_0H Vp/J {(η=ĸx9A<a-ClΌCx<KZacx9VKvYZ̳ܴ=e3N+- d=?Q>TrLJ(ǖ"ju\*T5鵛 X K2\EOnRx&=5왠M[w'Fjy^ִE \L_ Jn2 ެ!ˋOM8sV8,!*8~4MzJHrş_y:UG^I5M^:]Zκb*g&O$G}i_#y$@$@$vxS<  Q%; ɡKm )Ru Q Wu   &PF<1"\K#  H4l/=DRSk5}88}(l E UNNmi,HH @ &4c+S%HHHHHHHHPk ?A#]4K.箚6$@$@$#qb*  \@PBr̙\PVHJK|>i$@$@$W PɫW& |H 44TN8[&@&%>4  ȫ(+z @>$}l=L>\rZM> w(xljHHr ŋ)joFyF$py .o%Xz!{ /oƗ B$@$@2em۶NYXX{L$@YGWcSi1#H?z6@.%\ʰ>O9_2Ç'R  C >>^bbb$66VF$0}WcSi7K  Gr3K!      ȥs$^VHHHHHHH gP,HHHHHHH 8K/ E$@$@$@$@$@$@$3( gB$@$@$@$@$@$@$K Pɥ"      Gr3K!      ȥ( j #9Ù RGraHHHHHHHrőRHHHHHHHr)#°Z$@$@$@$@$@$@$@9CHpf)$@$@$@$@$@$@$@ő\zaX-       !@q$g8       \JH.0 @(3d])vʺ̘ @TV-CƓ8 6̍,Y'      6lSl @z PI/1'      )G|r1$@$@$@$@$@$@$@%@q$ĘHHHHHHHPƐ őcz       "@qħ.'C$@$@$@$@$@$@$^GKIHHHHHH|l @z PKJJIRbeo&;/ч{vf6cIdgJ9 !..NΝ;Žv!Ivy9r^;y25Ώ#?HHHHy'hfojYTi2mov.+9v)(Qޗ>yǛLK@2z۴3-ɝp@)]N *bv׬VG%ƍHꫯJ*UoNqӎO?TΜ9#?YFN*\st)ieƍ+He2i$y<כ+00Pz!'N(;vp9 ŋ*Ur,ld5Ǽn֓HHHH+|%*q$㵮جY*j17\G˗_L4$]( ~.Hff:#e+Ȗ Q^f5Ǵ 111ywQ]76h(W^y4iczo[ʖ-p7y|ŪnI &+V"//P\^ @~#z-xѠkݖi~~~)^W:Ev'i )YLJ֮zϙ;ݞMޤqm7`j_*r[z3#'i_۷Ǣn_r1w|{`^s7G$@$@$@$@$;P\سsR,\V}9)gɹ_ @7'm.w@ +UR:z@BչZAL)W›r!a#k;oJXG> I!JنuT>\Ο=;p*UD5:":m;RT }>k+OzN]uY;yZ+o>7‹Hv-e1:/J\fCcS#$l)ĮOƀjv^1^rZ1W@4qoseϳ%!T0}G3wBB`DFF:y 0@S)t7Mpd=vB0퉺Ï8(tF2E[Jͤ7H*dȎh_]MJ<:ѪVC,w4: u9@9zTju(YBe%.:F#k׮Mv ٳe:A`aܹ!Mtt{ cMUA.;Y76yrTk:tS .>^ H1G ֈ);v뮻NgצM7n̙3GQ!1cҽ@XQFukժ/B(5o\~vO1Ud:WjtǞy4     E#i\5ˆ2u#:F豿Rf{7^ (Q#8RjJ8T) >  TmUS/%*o"eHyǕG F]siZTV "xd6=#؇:VhToev\^VPM:[TuuW`ɚUu;4N 8ʪAikѢKF@S,덁.͚9+ }i.ĦWqA"б7^+:T?~\z&L -]<2Sx`۶m2^۳g3B1LT"xz0e\ b! VdB;CB#    \v[R0"viIP鿽 ,Au+ Tq<ʼnUZ4:W9:.mjL;nSWQKo D );J.N݅s*vQUӇ`W_P6>OT7^[ 6<,eBY·Z|)i/>`U^l57$34SV}DW49mϘ'5Fnm0(C1u]7k2<m|1Jry5ZDˑ[oH~1ͣ2ŹvWfɬY~"@c1dmtg#9_df; @ +wʲOŻt[+?1ݬuC ܺt2)C~.:[iޥϲx͟3]$f篷_w.OiՁ.:?g1 KY+PtÙd`md?_Φm<{?e8%/媁irF FT 7?0;J~}w/̺q?hZeixiglV%]]bܑ[@ (G7?c Eeyƃ}bA9O:lƘ sFXN9e N}-4bZCDˑ 'T1i/ZC F:D#_K2Ek#!1L b|;%'K4_ 64Y;T?;p9㲴wH#8yq**r/ "enU~ײ;P,~ @ 01[MtYӹy1 d֝$D!i]y`̑d/5`',7&m,q gmn61w9_wL}3M[YC5R8[Mkfwd ʰ΂3}bYk|= 4o_} <设ٔي^80 q^ry/>-SS"_jϏ +(NS~N#ӟY֊$^r_t鷾t-hC/Gl٬/1cLcj8`txAO?]C3V YD,1}J> -.]U4\zYc 7 Y|4>G7瞑UBLG=^?k?>SJLA38#moEp?a/1NwGo~Y7XAX5+n,1q+9b,T4" G> @ VS8Dk n,y]p:o'@ @dVlm@6& 0R @ # 8c5 @ #Q>% @ # 8c5 @ #Q>% @ # 8c5 @ #Q>% @ # 8c5 @ #Q>% @ # 8c5 @ #Q>% @ # 8c5 @ #Q>% @ # 8c5 @ #Q>% @ # 8c5 @ cs5lA%%@ @*H]kF @ @qf @ @uk( @ 0.#, @ @#u"@ @E@pd\eB @U@p5\ @ @ L @ @ Եf @qa  @ PW֌r @ @" 82.2!@ @* 8RךQ. @ @`\GƅY& @ @@]GZ3E @ȸ0˄ @H]kF @ @qf @ @uk( @ 0.#, @ @#u"@ @E@pd\eB @U@p5\ @ @ L @ @ Եf @qa  @ PW֌r @ @" 82.2!@ @* 8RךQ. @ @`\GƅY& @ @@]GZ3E @ȸ0˄ @H]kF @ @qf @ @uk( @ 0.#, @ @#u"@ @E@pd\eB @U@p5\ @ @ L @ @ Եf @qa  @ PW֌r @ @"0m\ra&}⋣*¨3 @ P-GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @[ @IDAT PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C@p @ @@E#˖ @H=A) @ @G*- @ PzԃR @ @ T/[ @ @# @ @*^ @ @@=GQJA @T$ 8Rl  @ @zԣ @H@p"x @ @G=( @ PHE%@ @! 8RzP  @ @"eK @C`Z=])ΝR @ @rɁ @g @Ɂ @g @Ɂ @g @Ɂ @g @Ɂ @g @Ɂ @g @Ɂ @g @Ɂ @g @Ɂ @g @+>/ݴW:}i9);2eJZmofΜv};IA @* 8R3"sn)ᇲ=VZzI=E`eٿ/8x$' @.[M3n&uӵ$%'?ɞ @ нHa_:*w駟.@R. @ @\\+xFʼn~[?  @ @1icT$ @ @`lGr|Hc՜$c: zd̙e@ @}. 8stuX.:C @ @ɩ;ݓe|WtS]{ʍ @H}?e|W}㛡 @ @@ T$g^xf(7 @ g#}V>. @ 0T`зޕ!^{iƌiOw8c @ @%  /tAGK7|Sz ^fkc= @n5lOo-;Hw{gg^ @ @u)&6l4ut' fG~/˴2ˤӧC @ @eVSǸ+.!G]}SI,X0d7 @ @@Z\#Km~sr8 @ @ 2s3c?t礲 @R@pd#0|6-oQ:  @Q@pGvNY`Ox`[% @P@pd|sO9蠃ҍ7XrG @)`5r,0lMifƚ @ @&#%V:묓"0;k>ޚ?U^ @ @ Xgyf @ @`sdԕ @ @c 8:%-e|WOW\n @ osj|e2W[mPn @ @Gr*|ͧe|Wo㛡 @ @@ T2gΜ9ʍ @HNoJ,u|VO>=㓙\ @ @@ ֯KЦt6 @ @eQ#[{6)nk^FƎב  @ @ E aֺ@ @ @ L+h` yJ9ݔCc)cJc1#v` @ 0B@pdI&]ywǦx;X;eʔj7f)&  @(Y`BG.Kf(~ 2+/ӻV @ @c-`̑wi}aϡ1FxG @[@pGj&@"0# @ @@#5@"02 t @ @GF mD`dv'@ @% Z4@"0RC @ @GJ ) a @ @@#%HFJDv( @ PHɠ$#%; @(Y`Zs4׍u~ @ @.ΝvWv6`ǰ @{jX @ @@Gj\9F @ˁ @H+G @ @{c9 @ @5q( @ 0#co, @ @#5E#@ @^@pd@ @X@pƕh @ @  @ @ Ըr @{7 @ PcW @ @c/ 82r @ @j, 8RQ4 @ @`GX @ @@Gj\9F @ˁ @H+G @ @{c9 @ @5q( @ 0K͟?gS^;# @ @= 71 oH?ӌugL-ӱ,gy,Yg2#,yɒeyɹYgLyGgwޛo3P<7{Ln5Q!gqF? &Q_җҎ;8Qr,gy͖_szeٛ[^9va~wzXvontƲ;vY~`'zݛۃg;5[Fo5n ,kM&epOOw^:ҋ^曳1H?c4?yZek^Un~E,0rgk17'>$흊lu^Q*gqrG޴&|+i=TiYϳԥw^o3Ln5QYe]<,sϙ3'&~qcd2Vne25;@gY$<ꐔe.6@R &yߟ.r%t^zj7rϑ&uͤDzvK1k"8:뤙3gfSiͲU<(Kr֬YO}jy*.\z|flo!(Vް]Y[k+(=b#\X@*+a xrx-ve-GZ-ܒuYhQlҖ[n*uX@" .:$e,*gIX*e.CR̲ Iy@I&uˑ8sҧ>tק 70䪫Jyk5M:iǦmf(5Rsb/,_L}.YvU )H,U H]$V1㾽Syg"z#i2Lڈmݖ>謮N;~]v%]s5颋.JguV⋳?O'SYXKʳhzի^M׽.tACI}{ӊ+8{ #K.$jp,)Jy X>K/{2<{k(=b#\nc`7OW\qňں'^{m:`j=µح}KrU,Yc%=s4mڴVd,=xr~i饗{ކe6-g90=fZ]xrS'KAY*g#=x`:O>9=)OI[lE23SNM+bp ꫯ/w]ix5;%fY.0<|S =68=lZ.,s`zXͲ6ln,ݷeާۭ<8韌 L2\ҩS3gx :c.~߶<ދ_S6q-J]|)~?'ّX[lr\GbYn},ϓ%=s<Ϣ,tgbOߗ-GT]tQ7IBe Q9 a2,GbW-Pz\ŲGl2U< |eʳ[-Y|C,diƌio 6HkF69s>3GgYn,ϳ˙3go,H,˭LydY@Grnٍeqߞo2ߦ-<{Q>}=HDye nK7|sVk+rseI1.٫\veW_= d,=xɒeyɹYg7ۻlV݊7}_GUK`qwd*xE]l`j=µ؍e W.g790=fZ.,s`z\ͳGl*=3GjZ1E @@:>r!@ @. 8RR> @ @`LGƔ  @ @ t}7MT6`YDxŭ: ?S2 ,E]`uHʲPYvU )HXf]U|ꡇzdEyOlcZv-zivJK/(w ɲZYgLWy ,K@l:&Qd9JY6ag O½=s\ϱ:#d7ptk6.馛n갇y,dz[ϳ7V{=Y{ u,{@k 68]nb%X,gh97ʁa5³ N6 g?3HSLIgN]w]$i%Jxn7|ϰ<ӳw1]Óx_PeAx*eIX*gA}{IX@" .*J:|k%KE+ /0͙3']ve{MlMjlIV,˩8Q:Y.^&X[7|szҦn[l?)mvi-MX[EtgeY&]~iw֯ٔZjaG!!Qe]6E?|0;^z^{=3meGbY^%,2ijfvm)~$fP E/bx_~{쑾?Y7'/xA\z-O=tUWeqrbYY*gAX@*eAx*l{h9IXgp&8+gb`%.Ķkpu`ڥqN~{ֵ+`I.\WD-:YG,˭ydY@GrnҽQqkŭYD>i T:d뭷N}{~7%Ze/K믟ҫ:뮃xReZdIvHmQk~lٳgU;M\gLIiYf+n:٨hN1x{b,9MųT}.!;l솸F _Bzk_;]bܑgi-hƹxGK/4;L|?1VR$vyKͲNwXv)5NBŷ,n)%NBmٝW]Rk9Ң&bޯnK1m>echnXp*,<0F(vgلQK% >qF,(Hr=>[|+7<5 Fݤ^'̜[ngwsOr* =مg.7Mrmpzij]Z[7je[7욬 ڢ SL:lFF:ς, BLvgCvmpo|(&2cY̯9fѿ9z>h~6 l(e7%d72>W{'˘ `Μ9iv}ωW\uU+H Yf8$ρz(kgo6b|hKSL?)͘1#}=Ig뮻nG>2Ezh-4mڴ!c6~ۓgyu>ܲݑcf=S%m,˭N/yzY>W^BLu]w%5kV6=֕[oM1^~6 dm&s#l…?ӂ [oDgx8ŋy睗K:7,_ekwt7 YJlj<́_XuYi޼y,;X^W+%ZXzٛ[^pt)ͦ=/5Z-\rIc=iZDz Q|j9sҝwޙk>; )EG?Q#IJsg?Yvw}ف<9ٸ#r?iW"P-,E,cec8$#[mU:裳+y]Lg1vN8k)yo^~ߧkvp ^/Xt_r;^ǃe/{Y/@oO7%KR1~70K[nɮR\gq*R Qocx{}vV_jj>jt16Ncƒ/+v.!;C6Pličg n3~EPy}Zx/zыG1$r.]1@|}M;H֍<;!HZAVYx%Ƿ(mviyrk?n׿8h<7RO`EY;Wt7sMdz`<|5G l]`$yKSNMz,b-`r㼵`ڥ<5\>Od_T@I ltK~;ޑ.׾lb|;Y8#b0;e DW~QGe0O$YܪdmS1 stYfu_NNoy:]$F`9d4k<Ȭ+WDkW,m^|}.qs 6ؠa,~ *XX6$7;nbFſhk КoؼgF44{{l袋i6xf/&G6_,g>g]cKb F7HE$[Z,[lZ~>ciM7Ո1]t/rܴ!ZyF7_ ˣƗ"jGڸG?hJ o"qsug31lFo~s 2o6M,˭q=Q[n岟wuWm!w3x_nh,˫8+vq}8ij\}St#mU^nH,˭F mu2ךkYnh,Ǧb UWY5XwF6W׾66G?8W_}u #Lc+ t0S MM]w,M$`YܪH3)7L1J| {*r~Oò3 9ٷG61Bb^gfoyt>Ff#X.5y-j7Z+hw@#bX R7b{lfn-{SUӟ5E c 5 x㍳~gyfzG@Ab %=Θ1#1(7)х^ 8Ly|ιf5ιh)9skeq$7t{534_3c^ 1E%kf{V[_\Dw[NJ=1b}7/Y,#%ۣG|kg!G W_=%|c:o)AXH91` +=uTpYj;ƽe^`$ScX_l53Z4N|0 \?^j<ozӛޔ}chF344v=Ʒ1JWgg297;+q 53KΖq݌V͘ueFxXஙh\3#0H75sRkbLl1O:(b.ap@ u윌g}v6JvO>GMHKVӼĘ-Ŋ%1ޘYiƗE1 Pȅ0 IDAT+x VSK.h?hTE9xp%. o{۲`IܤZF D%h3񌑉DϘnZ Z2 nh<fe h `Ŏ_!=#f-=o! {vH4K|Y$0yA J_}(l(z DPoѢE3g-7c]+btow͌5UdbF>~ߛ$w-xBX_ChwMb,fᕷc1cl6Kk81Nt)k>쓵ZnqLrY 70v㑴;mpobڥ}_"kV1xc)qQ[=\..d/T 5 b*}a*#cLms@\"ۚ#fߤtzPq>Z̘1} ׸fMfv87]7cnp`;gM[]3#p߸fƹ3nxb-$_YkfߩF ! q=EbPXEC̉D GK@̒v}ebDKѸO|lϥD${cVI2kigd^9RPNp6}/~1f).bqMf)%D4^̥7TbGT?YT4zY Ĺ71^F4y㛹hoi4} l6ݎbj/g[Z ĸpk\7cfqg>RɕaxGg3TMiLY 53k~fƹe\3c3Dי87RX򗿜f=s1kZ,qM/Z}R<) l#k41O~򓬏|1;] >,]t1c,KqdSO;e?1}tvŏ)cPF.zAĴ1 {b6۶[zC?kPѾo&?ZCRI}Agq~nVoy[\3 YygF!5s' Ϙ&Bͦ$̂J?پ-jy|qrm;E) 82ҤF4-SǍgH2G񍞵QZ)+vO!1S]3Go8fCϺ f֭FzE> @ @ȟ{m|D @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ / 8oc  @ #}P>" @ߎ0 z `CY  @@ #mc!@ @đ'; @hqm, @ @8rd' @ @-  @8GD @ @đ @ @H @86 @ @@@9x  @ @GB @#Ov" @ HX @ @qN$@ @Z@i  @ p <ى @ @@ #mc!@ @đ'; @hqm, @ @|,IENDB`graphql-ruby-2.2.17/guides/limiters/soft_button.png000066400000000000000000000111771476434635200224310ustar00rootroot00000000000000PNG  IHDR@o7) @iCCPICC Profile(c``H,(aa``+) rwRR` `\\TQk .ȬǶoc=,)<zZ qbrAQ c\^Rb"E@G3@t{ a r)@ [' I< ؍MB}-M PZQ *23J!`d`d o7(Ɓ+c`!B,v9> +H,J;KqͽuÁ^d`{Áo^ FVeXIfMM*iDҠ@ASCIIScreenshot%ٺiTXtXML:com.adobe.xmp 210 Screenshot 64 -IIDATx]XG~ HobQXAh,1=^b^sQs%1J4Q{, Aip 첋 {;:/ )JQ_)h~?:VtDDxA15$DD͡PTOhIm 5 9n!.P{~D5A$M%GG@IHtPD҄Y}T{9ڣ:XV\}}k>yX[x V.0mP%Y(+*qK/*ݑ\]U>;=q]Ve~=;ߐĞ>8{5(\ 6F:Zrn:a ǁNHuN*N:j΍BNuexa]>oVWNHou qʟ='IH=w wo!bB2]9 S¬~/$nٍu$RQr*6q4q)Ht<\qLjk4hzYɋcV̐87x$# ыW 9x0d<2?xrWH={ܼܙ8%CYD%Nύ}m}0)+3~31"; vٜp|L3^<~ w(HCQf6#X/{:|47x7~hNY!q N 9qgV'5/*N ~9VA-!޺Wڙ]&S_qiq| eH@9|Xk 59tK|vmWis yCV='ۉtA.OPyz̼dΊ:Z1 }Th՝XK33c@2~I a- c799Ĕce Cf*Sm=2Sϼ}~[?FհDzL۩i3’cA7)U|Cji>v~Va%(n_4?s\z FÛЫ;ǟv0"C//\?s{۷*u3D7q Τc˝n27dr8.{pFDQ6!&fsb*q⟹9DF)xrHo߼?V^]xY)URy׎Z0DT:4MHw]"8cxy?ԍM8roֈ~΢ea=gy);Uo҄/ $"p{xp 朧;DF{GOA <͂3+;9Ir(JIUN"Z,O;H:z2;: >+3S9iIBܣ=zl\6ޜ*f(d(Rh쨒 <}㑺003޹:h^_>$|̵|{S)Sêg7/As\N9>14@vͧt=O'QɟF683~6lS)GdҮ(OL(z>F[$yb]6$mɇ){)AyIQj#|Pb^^yNFi */3K=wpae#Nc/z_Sf\szdt|Sوٸg.c+])t KԐ 8 Gi`%ɪ#wB:n;}L[ .1k~lI ^gGӽ3]u`cRv;C>+dF\G\&T(6ڀ'[UwWk8&E)i0cVŭG K̒C)J~D|.anK}XhP]? D6OQmk3#Txt>.YwF&*_s4-?ps]0?Y婿¶_35[MTıX>uX8ّ#:93a@&fŵ֓G`v_:P 8l20cMjʳ9:UmTթQ#VyTb왗L\Fe_wi_⌿!a7{5l7IYbM `L7*L#!(+y Sf(vQTy"3"3:JRPӜC^$}ĎsD1yXq>xj;/*%i|QYt̺Зc]:2Bv@u /S_./@ѽpdS¼)썰dKC8nҵ סP%(BomӡCM t|^`ȎU\!Gm(5EȱVvkSΧn%M۷2n|_rJ[ " okwc&h^ A"id/H;7g M" s#zA4 g5D4h JVǞojHul^i9G&yN]u@IL;u@@JDSZvD@IJ4.E@Ik^ \JDSZvD@IJ4.E@Ik^ \JDSZvD@IJ4.E@Ik^ \JDSZvD R2cIENDB`graphql-ruby-2.2.17/guides/mutations/000077500000000000000000000000001476434635200175415ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/mutations/mutation_authorization.md000066400000000000000000000124101476434635200247010ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation authorization desc: Checking permissions for mutations index: 3 --- Before running a mutation, you probably want to do a few things: - Make sure the current user has permission to try this mutation - Load some objects from the database, using some `ID` inputs - Check if the user has permission to modify those loaded objects This guide describes how to accomplish that workflow with GraphQL-Ruby. ## Checking conditions before instantiating the mutation ```ruby class UpdateUserMutation < BaseMutation # ... def resolve(update_user_input:, user:) # ... end def self.authorized?(obj, ctx) super && ctx[:viewer].present? end end ``` ## Checking the user permissions Before loading any data from the database, you might want to see if the user has a certain permission level. For example, maybe only `.admin?` users can run `Mutation.promoteEmployee`. This check can be implemented using the `#ready?` method in a mutation: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation def ready?(**args) # Called with mutation args. # Use keyword args such as employee_id: or **args to collect them if !context[:current_user].admin? raise GraphQL::ExecutionError, "Only admins can run this mutation" else # Return true to continue the mutation: true end end # ... end ``` Now, when any non-`admin` user tries to run the mutation, it won't run. Instead, they'll get an error in the response. Additionally, `#ready?` may return `false, { ... }` to return {% internal_link "errors as data", "/mutations/mutation_errors" %}: ```ruby def ready? if !context[:current_user].allowed? return false, { errors: ["You don't have permission to do this"]} else true end end ``` ## Loading and authorizing objects Often, mutations take `ID`s as input and use them to load records from the database. GraphQL-Ruby can load IDs for you when you provide a `loads:` option. In short, here's an example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation # `employeeId` is an ID, Types::Employee is an _Object_ type argument :employee_id, ID, loads: Types::Employee # Behind the scenes, `:employee_id` is used to fetch an object from the database, # then the object is authorized with `Employee.authorized?`, then # if all is well, the object is injected here: def resolve(employee:) employee.promote! end end ``` It works like this: if you pass a `loads:` option, it will: - Automatically remove `_id` from the name and pass that name for the `as:` option - Add a prepare hook to fetch an object with the given `ID` (using {{ "Schema.object_from_id" | api_doc }}) - Check that the fetched object's type matches the `loads:` type (using {{ "Schema.resolve_type" | api_doc }}) - Run the fetched object through its type's `.authorized?` hook (see {% internal_link "Authorization", "/authorization/authorization" %}) - Inject it into `#resolve` using the object-style name (`employee:`) In this case, if the argument value is provided by `object_from_id` doesn't return a value, the mutation will fail with an error. Alternatively if your `ID` doesn't specify both class _and_ id, resolvers have a `load_#{argument}` method that can be overridden. ```ruby argument :employee_id, ID, loads: Types::Employee def load_employee(id) ::Employee.find(id) end ``` If you don't want this behavior, don't use it. Instead, create arguments with type `ID` and use them your own way, for example: ```ruby # No special loading behavior: argument :employee_id, ID ``` ## Can _this user_ perform _this action_? Sometimes you need to authorize a specific user-object(s)-action combination. For example, `.admin?` users can't promote _all_ employees! They can only promote employees which they manage. You can add this check by implementing a `#authorized?` method, for example: ```ruby def authorized?(employee:) context[:current_user].manager_of?(employee) end ``` When `#authorized?` returns `false` (or something falsey), the mutation will be halted. If it returns `true` (or something truthy), the mutation will continue. #### Adding errors To add errors as data (as described in {% internal_link "Mutation errors", "/mutations/mutation_errors" %}), return a value _along with_ `false`, for example: ```ruby def authorized?(employee:) if context[:current_user].manager_of?(employee) true else return false, { errors: ["Can't promote an employee you don't manage"] } end end ``` Alternatively, you can add top-level errors by raising `GraphQL::ExecutionError`, for example: ```ruby def authorized?(employee:) return true if context[:current_user].manager_of?(employee) raise GraphQL::ExecutionError, "You can only promote your _own_ employees" end ``` In either case (returning `[false, data]` or raising an error), the mutation will be halted. ## Finally, doing the work Now that the user has been authorized in general, data has been loaded, and objects have been validated in particular, you can modify the database using `#resolve`: ```ruby def resolve(employee:) if employee.promote { employee: employee, errors: [], } else # See "Mutation Errors" for more: { errors: employee.errors.full_messages } end end ``` graphql-ruby-2.2.17/guides/mutations/mutation_classes.md000066400000000000000000000153031476434635200234420ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation Classes desc: Use mutation classes to implement behavior, then hook them up to your schema. index: 1 redirect_from: - /queries/mutations/ - /relay/mutations/ --- GraphQL _mutations_ are special fields: instead of reading data or performing calculations, they may _modify_ the application state. For example, mutation fields may: - Create, update or destroy records in the database - Establish associations between already-existing records in the database - Increment counters - Create, modify or delete files - Clear caches These actions are called _side effects_. Like all GraphQL fields, mutation fields: - Accept inputs, called _arguments_ - Return values via _fields_ GraphQL-Ruby includes two classes to help you write mutations: - {{ "GraphQL::Schema::Mutation" | api_doc }}, a bare-bones base class - {{ "GraphQL::Schema::RelayClassicMutation" | api_doc }}, a base class with a set of nice conventions that also supports the Relay Classic mutation specification. Besides those, you can also use the plain {% internal_link "field API", "/type_definitions/objects#fields" %} to write mutation fields. ## Example mutation class If you used the {% internal_link "install generator", "/schema/generators#graphqlinstall" %}, a base mutation class will already have been generated for you. If that's not the case, you should add a base class to your application, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation # Add your custom classes if you have them: # This is used for generating payload types object_class Types::BaseObject # This is used for return fields on the mutation's payload field_class Types::BaseField # This is used for generating the `input: { ... }` object type input_object_class Types::BaseInputObject end ``` Then extend it for your mutations: ```ruby class Mutations::CreateComment < Mutations::BaseMutation null true argument :body, String argument :post_id, ID field :comment, Types::Comment field :errors, [String], null: false def resolve(body:, post_id:) post = Post.find(post_id) comment = post.comments.build(body: body, author: context[:current_user]) if comment.save # Successful creation, return the created object with no errors { comment: comment, errors: [], } else # Failed save, return the errors to the client { comment: nil, errors: comment.errors.full_messages } end end end ``` The `#resolve` method should return a hash whose symbols match the `field` names. (See {% internal_link "Mutation Errors", "/mutations/mutation_errors" %} for more information about returning errors.) Also, you can configure `null(false)` in your mutation class to make the generated payload class non-null. ## Hooking up mutations Mutations must be attached to the mutation root using the `mutation:` keyword, for example: ```ruby class Types::Mutation < Types::BaseObject field :create_comment, mutation: Mutations::CreateComment end ``` ## Auto-loading arguments In most cases, a GraphQL mutation will act against a given global relay ID. Loading objects from these global relay IDs can require a lot of boilerplate code in the mutation's resolver. An alternative approach is to use the `loads:` argument when defining the argument: ```ruby class Mutations::AddStar < Mutations::BaseMutation argument :post_id, ID, loads: Types::Post field :post, Types::Post def resolve(post:) post.star { post: post, } end end ``` By specifying that the `post_id` argument loads a `Types::Post` object type, a `Post` object will be loaded via {% internal_link "`Schema#object_from_id`", "/schema/definition.html#object-identification-hooks" %} with the provided `post_id`. All arguments that end in `_id` and use the `loads:` method will have their `_id` suffix removed. For example, the mutation resolver above receives a `post` argument which contains the loaded object, instead of a `post_id` argument. The `loads:` option also works with list of IDs, for example: ```ruby class Mutations::AddStars < Mutations::BaseMutation argument :post_ids, [ID], loads: Types::Post field :posts, [Types::Post] def resolve(posts:) posts.map(&:star) { posts: posts, } end end ``` All arguments that end in `_ids` and use the `loads:` method will have their `_ids` suffix removed and an `s` appended to their name. For example, the mutation resolver above receives a `posts` argument which contains all the loaded objects, instead of a `post_ids` argument. In some cases, you may want to control the resulting argument name. This can be done using the `as:` argument, for example: ```ruby class Mutations::AddStar < Mutations::BaseMutation argument :post_id, ID, loads: Types::Post, as: :something field :post, Types::Post def resolve(something:) something.star { post: something } end end ``` In the above examples, `loads:` is provided a concrete type, but it also supports abstract types (i.e. interfaces and unions). ### Resolving the type of loaded objects When `loads:` gets an object from {{ "Schema.object_from_id" | api_doc }}, it passes that object to {{ "Schema.resolve_type" | api_doc }} to confirm that it resolves to the same type originally configured with `loads:`. ### Handling failed loads If `loads:` fails to find an object or if the loaded object isn't resolved to the specified `loads:` type (using {{ "Schema.resolve_type" | api_doc }}), a {{ "GraphQL::LoadApplicationObjectFailedError" | api_doc }} is raised and returned to the client. You can customize this behavior by implementing `def load_application_object_failed` in your mutation class, for example: ```ruby def load_application_object_failed(error) raise GraphQL::ExecutionError, "Couldn't find an object for ID: `#{error.id}`" end ``` Or, if `load_application_object_failed` returns a new object, that object will be used as the `loads:` result. ### Handling unauthorized loaded objects When an object is _loaded_ but fails its {% internal_link "`.authorized?` check", "/authorization/authorization#object-authorization" %}, a {{ "GraphQL::UnauthorizedError" | api_doc }} is raised. By default, it's passed to {{ "Schema.unauthorized_object" | api_doc }} (see {% internal_link "Handling Unauthorized Objects", "/authorization/authorization.html#handling-unauthorized-objects" %}). You can customize this behavior by implementing `def unauthorized_object(err)` in your mutation, for example: ```ruby def unauthorized_object(error) # Raise a nice user-facing error instead raise GraphQL::ExecutionError, "You don't have permission to modify the loaded #{error.type.graphql_name}." end ``` graphql-ruby-2.2.17/guides/mutations/mutation_errors.md000066400000000000000000000076561476434635200233350ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation errors desc: Tips for handling and returning errors from mutations index: 2 --- How can you handle errors inside mutations? Let's explore a couple of options. ## Raising Errors One way to handle an error is by raising, for example: ```ruby def resolve(id:, attributes:) # Will crash the query if the data is invalid: Post.find(id).update!(attributes.to_h) # ... end ``` Or: ```ruby def resolve(id:, attributes:) if post.update(attributes) { post: post } else raise GraphQL::ExecutionError, post.errors.full_messages.join(", ") end end ``` This kind of error handling _does_ express error state (either via `HTTP 500` or by the top-level `"errors"` key), but it doesn't take advantage of GraphQL's type system and can only express one error at a time. It works, but a stronger solution is to treat errors as data. ## Errors as Data Another way to handle rich error information is to add _error types_ to your schema, for example: ```ruby class Types::UserError < Types::BaseObject description "A user-readable error" field :message, String, null: false, description: "A description of the error" field :path, [String], description: "Which input value this error came from" end ``` Then, add a field to your mutation which uses this error type: ```ruby class Mutations::UpdatePost < Mutations::BaseMutation # ... field :errors, [Types::UserError], null: false end ``` And in the mutation's `resolve` method, be sure to return `errors:` in the hash: ```ruby def resolve(id:, attributes:) post = Post.find(id) if post.update(attributes) { post: post, errors: [], } else # Convert Rails model errors into GraphQL-ready error hashes user_errors = post.errors.map do |error| # This is the GraphQL argument which corresponds to the validation error: path = ["attributes", error.attribute.to_s.camelize(:lower)] { path: path, message: error.message, } end { post: post, errors: user_errors, } end end ``` Now that the field returns `errors` in its payload, it supports `errors` as part of the incoming mutations, for example: ```graphql mutation($postId: ID!, $postAttributes: PostAttributes!) { updatePost(id: $postId, attributes: $postAttributes) { # This will be present in case of success or failure: post { title comments { body } } # In case of failure, there will be errors in this list: errors { path message } } } ``` In case of a failure, you might get a response like: ```ruby { "data" => { "createPost" => { "post" => nil, "errors" => [ { "message" => "Title can't be blank", "path" => ["attributes", "title"] }, { "message" => "Body can't be blank", "path" => ["attributes", "body"] } ] } } } ``` Then, client apps can show the error messages to end users, so they might correct the right fields in a form, for example. ## Nullable Mutation Payload Fields To benefit from "Errors as Data" described above, mutation fields must not have `null: false`. Why? Well, for _non-null_ fields (which have `null: false`), if they return `nil`, then GraphQL aborts the query and removes those fields from the response altogether. In mutations, when errors happen, the other fields may return `nil`. So, if those other fields have `null: false`, but they return `nil`, the GraphQL will panic and remove the whole mutation from the response, _including_ the errors! In order to have the rich error data, even when other fields are `nil`, those fields must have `null: true` (which is the default) so that the type system can be obeyed when errors happen. Here's an example of a nullable field (good!): ```ruby class Mutations::UpdatePost < Mutations::BaseMutation # Use the default `null: true` to support rich errors: field :post, Types::Post # ... end ``` graphql-ruby-2.2.17/guides/mutations/mutation_root.md000066400000000000000000000022161476434635200227670ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation Root desc: The Mutation object is the entry point for mutation operations. index: 0 --- GraphQL mutations all begin with the `mutation` keyword: ```graphql mutation($accountNumber: ID!, $newBalance: Int!) { # ^^^^ here setAccountBalance(accountNumber: $accountNumber, newBalance: $newBalance) { # ... } } ``` Operations that begin with `mutation` get special treatment by the GraphQL runtime: root fields are guaranteed to be executed sequentially. This way, the effect of a series of mutations is predictable. Mutations are executed by a specific GraphQL object, `Mutation`. This object is defined like any other GraphQL object: ```ruby class Types::Mutation < Types::BaseObject # ... end ``` Then, it must be attached to your schema with the `mutation(...)` configuration: ```ruby class Schema < GraphQL::Schema # ... mutation(Types::Mutation) end ``` Now, whenever an incoming request uses the `mutation` keyword, it will go to `Mutation`. See {% internal_link "Mutation Classes", "/mutations/mutation_classes" %} for some helpers to define mutation fields. graphql-ruby-2.2.17/guides/object_cache/000077500000000000000000000000001476434635200201075ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/object_cache/caching.md000066400000000000000000000143161476434635200220320ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Object Cache title: Caching Results desc: Configuration options for caching objects and fields index: 2 --- `GraphQL::Enterprise::ObjectCache` supports several different caching configurations for objects and fields. To get started, include the extension in your base object class and base field class and use `cacheable(...)` to set up the default cache behavior: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object include GraphQL::Enterprise::ObjectCache::ObjectIntegration field_class Types::BaseField cacheable(...) # see below # ... end ``` ```ruby # app/graphql/types/base_field.rb class Types::BaseField < GraphQL::Schema::Field include GraphQL::Enterprise::ObjectCache::FieldIntegration cacheable(...) # see below # ... end ``` Also, make sure your base interface module is using your field class: ```ruby # app/graphql/types/base_interface.md module Types::BaseInterface field_class Types::BaseField end ``` Field caching can be configured per-field, too, for example: ```ruby field :latest_update, Types::Update, null: false, cacheable: { ttl: 60 } field :random_number, Int, null: false, cacheable: false ``` Only _queries_ are cached. `ObjectCache` skips mutations and subscriptions altogether. ## `cacheable(true|false)` `cacheable(true)` means that the configured type or field may be stored in the cache until its cache fingerprint changes. It also defaults to `public: false`, meaning that clients will _not_ share cached responses. See [`public:`](#public) below for more about this option. `cacheable(false)` disables caching for the configured type or field. Any query that includes this type or field will neither check for an already-cached value nor update the cache with its result. ## `public:` `cacheable(public: false)` means that a type or field may be _cached_, but {% internal_link "`Schema.private_context_fingerprint_for(ctx)`", "/object_cache/schema_setup#context-fingerprint" %} should be included in its cache key. In practice, this means that each client can have its own cached responses. Any query that contains a `cacheable(public: false)` type or field will use a private cache key. `cacheable(public: true)` means that cached values from this type or field may be shared by _all_ clients. Use this for public-facing data which is the same for all viewers. Queries that include _only_ `public: true` types and fields will not include `Schema.private_context_fingerprint_for(ctx)` in their cache keys. That way their responses will be shared by all clients who request them. ## `ttl:` `cacheable(ttl: seconds)` expires any cached value after the given number of seconds, regardless of cache fingerprint. `ttl:` shines in a few cases: - Objects that can't reliably generate a fingerprint value (for example, they have no `.updated_at` timestamp). In this case, a conservative `ttl` may be the only option for cache expiration. - Or, root-level fields that should be expired after a certain amount of time. The root-level `Query` often has _no_ backing object, so it won't have a cache fingerprint, either. Adding `cacheable: { ttl: ... }` to root level fields will provide some caching along with a guarantee about when they'll be expired. - Or, list responses that may be difficult to invalidate properly (see below). Under the hood, `ttl:` is implemented with Redis's `EXPIRE`. ## Caching lists and connections Lists and connections require a little extra consideration. In order to effectively bust the cache, items that belong to the list of "parent" object should update the parent whenever they're modified in a way that changes the state of the list. For example, if there's a list of players on a team: ```graphql { team { players { totalCount } } } ``` None of the _specific_ `Player`s will be part of the cached response, but the `Team` will be. To properly invalidate the cache, the `Team`'s `updated_at` (or other cache key) should be updated whenever a `Player` is added or removed from the `Team`. If a list may be sorted, then updates to `Player`s should also update the `Team` so that any sorted results in the cache are invalidated, too. Alternatively (or additionally), you could use a `ttl:` to expire cached results after a certain duration, just to be sure that results are eventually expired. By default, connection-related objects (like `*Connection` and `*Edge` types) "inherit" cacheability from their node types. You can override this in your base classes as long as `GraphQL::Enterprise::ObjectCache::ObjectIntegration` is included in the inheritance chain somewhere. ## Caching Introspection By default, introspection fields are considered _public_ for all queries. This means that they are considered cacheable and their results will be reused for any clients who request them. When {% internal_link "adding the ObjectCache to your schema", "/object_cache/schema_setup#add-the-cache", %}, you can provide some options to customize this behavior: - `cache_introspection: { public: false, ... }` to use [`public: false`](#public) for all introspection fields. Use this if you hide schema members for some clients. - `cache_introspection: false` to completely disable caching on introspection fields. - `cache_introspection: { ttl: ..., ... }` to set a [ttl](#ttl) (in seconds) for introspection fields. ## Object Dependencies By default, the `object` of a GraphQL Object type is used for caching the fields selected on that object. But, you can specify what object (or objects) should be used to check the cache by implementing `def self.cache_dependencies_for(object, context)` in your type definition. For example: ```ruby class Types::Player def self.cache_dependencies_for(player, context) # we update the team's timestamp whenever player details change, # so ignore the `player` for caching purposes player.team end end ``` Use this to: - improve performance when caching lists of children that belong to a parent object - register other objects with the ObjectCache when running a query. (`cacheable_object(obj)` or `def self.object_fingerprint_for` can also be used in this case.) If this method returns an `Array`, each object in the array will be registered with the cache. graphql-ruby-2.2.17/guides/object_cache/overview.md000066400000000000000000000046311476434635200223030ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Object Cache title: GraphQL ObjectCache desc: A server-side cache for GraphQL-Ruby index: 0 --- `GraphQL::Enterprise::ObjectCache` is an application-level cache for GraphQL-Ruby servers. It works by storing a {% internal_link "_cache fingerprint_ for each object", "/object_cache/schema_setup#object-fingerprint" %} in a query, then serving a cached response as long as those fingerprints don't change. The cache can also be customized with {% internal_link "TTLs", "/object_cache/caching#ttl" %}. ## Why? `ObjectCache` can greatly reduce GraphQL response times by serving cached responses when the underlying data for a query hasn't changed. Usually, a GraphQL query alternates between data fetching and calling application logic: {{ "/object_cache/query-without-cache.png" | link_to_img:"GraphQL-Ruby profile, without caching" }} But with `ObjectCache`, it checks the cache first, returning a cached response if possible: {{ "/object_cache/query-with-cache.png" | link_to_img:"GraphQL-Ruby profile, with ObjectCache" }} This reduces latency for clients and reduces the load on your database and application server. ## How Before running a query, `ObjectCache` creates a fingerprint for the query using {{ "GraphQL::Query#fingerprint" | api_doc }} and {% internal_link "`Schema.context_fingerprint_for(ctx)`", "/object_cache/schema_setup#context-fingerprint" %}. Then, it checks the backend for a cached response which matches the fingerprint. If a match is found, the `ObjectCache` fetches the objects previously visited by this query and compares their current fingerprints to the ones in the cache. If the fingerprints all match, then the cached response returned. If there is no cached response or if the fingerprints don't match, then the incoming query is re-evaluated. While it's executed, `ObjectCache` gathers the IDs and fingerprints of each object it encounters. When the query is done, the result and the new object fingerprints are written to the cache. ## Setup To get started with the object cache: - {% internal_link "Prepare the schema", "/object_cache/schema_setup" %} - {% internal_link "Set up a Redis backend", "/object_cache/redis" %} - {% internal_link "Configure types and fields for caching", "/object_cache/caching" %} - Check out the {% internal_link "runtime considerations", "/object_cache/runtime_considerations" %} graphql-ruby-2.2.17/guides/object_cache/query-with-cache.png000066400000000000000000000313641476434635200240030ustar00rootroot00000000000000PNG  IHDR2IDATx^uǦcfbff!ffvlǔqrvfw{sq睔ԪHHg~~<'U E[o'B!B!B!B!B!B!B=޳}E%?޳Bq01) .7B!& h:!xB'B!i:ЉBh*t ! h:!xB'B!i:ЉBh*t ! h:!xB'B!i:ЉBh?MOWתwLýdYտMwV9]=?Vԓ 寑h:!xt;PTSeF+[9]0?KvsH\n]VTUYleN0BM ՞C4_K>mj:UJ"[['˗Ա2W1reS-a+h:!xt[4Զ9YLz++M!Et"_kq1jpa"DE>=ԫ)ōuUZԨMy"GWR=ZԐT;lǿi׮ٲ:z?:f7[mY>R-Yݲ|oS]Vb_4ˍ 6L=Ng}Z&5ʪO-i:vkZ;ԸM=?no8oiU.pj69|q^p1Q4BMDLti:YFh'O5-H !:@Ӂ:?C Ȇ k`*4ψnS:Nmh*, QZO8S0rMHޙ/\=96/77BZQfJTbB2#m!It` SfN?ܥ>MG`} dYCK0>(_TZsxEtB5KHkPI)@2t ` ",hU+[Aɕ-ʒ19}#T-[X:{y/.rET)SX _074KC/Ӂ"rJ}f>`1,>S:*#϶+Bd`dg1  (&yg: *h `fa}U!|~f$Q~!BN ܖLVX[*VumTzŞlX2 Ni:!x选@ң9hu/Ӂ)\38@{$ObO6+Ko@ cդ^"SB:SߧLV` ,c͖9t `Uaٱ.˟;W2,Xsipl|\_g !:DC26`>,&O ]ח@")1)Ǡ ɣ AKUpW`s.2gn|²TL X!57b cb(pM3IyB?v[Cک>$ԢNyOt 0`0mzhW@a DkPA D[z}$B!Cd:0!'m` )xe: òq+=zH.E.rC) sߝ}Xwe 7&I) DUrIɁN`1~rxX aΜa =k{e^ !OӁA雋+mKSK, 2L]/,kW>ZǍ]3u= k i]k*8iWc_^Y-߈ؘ}` XCNXE,=ua r{ d.E^ Xy$h:!xtDn LwmP!M!2B,B&B!d:Bh*t ! h:!xB'B!i:ЉBh*t ! h:!xB'B!i:ЉBh*t ! h:!xB'B!Ψ}E%I=%EQT8Gq61?(*\c"B(*ܢ ġtPntPh:( h:q(4E[48-B MEQM!pBAQTEAC(*ܢ ġtPntPh:( h:q('۪~~=mCS;ݫ7ʓ>|Km^J}zmEEh:q(=S=;tQ)P1ͽ+GNut횵Rҧj5S_ΙVSwzA>DtP1?=LU,[^T/'p 3{*eJ64 rh:q( CF8k>ѳ*yr 2EA9M48Mǯ2g̤ʭk~)#Ɗ)ɗMr@uJ+QI;I -4QPuTKQ+q`pzʗ.ʔ(>ɉe:vڨzwWǞ՛UˆMUE|O&뷩-Z*,}|}Ԩ\UMtٲ[͝8MU)_Q.^RU=򡭿/:I7bX^+tP5/>t`tG,E_]tۧsw)oX XSHmct0H#їA=TҦSn,uLӁ6Ci۴<۹G5

K= N|[ dy%+UB+M1Ncr-.5WX1#[XMs.±atK&tT(SNz Β+]*K̒W\Ѡf󡨤*Bc㢕2.1׶/>iӡ#Z\?oZNk0\R]KLz0(w\T &hI\ }kH/_93UP1gߨH<ΕCԉiӁ%:N^rL`[5=4}DACtӁwu.UJ:WMxϖ%O7O\iLL cYǗ7h,F,ؽjP'#"mULۺ׫R>{TS:05=T{?E%5tP5xO,jZmU&Y<Jj ġk: 7qz;d`TF>v\;rFP}}ߦcᔷdy%f(*@BfLYM &ϔ[sk:/țփM15f'L+[<`\Pj-K0=tjsR% f ǚ am4i_9L w̝(BJ ZƝj(GnZ5,r ,9ſGDKD `20]oEEMás=Laj(i) ?ɳ"ApQ3s1>:wM=v[Və[KdugvS|}A{R_T 9HbZe *J* ġj:0]@$PI_h4#C F/겶LzxG՘20050LM`U JQr59?=ZD~ 1;a½b&ӁQKDq?|Dh:q(08#ʀfīS%x(R}V87`[ȷ@ŋA[C} +^#ɴ~s?E%etP^tPE#B MEQM!pBAQTEAC(*ܢ ġtPntPh:( h:q(4E[48-B MEQM!pBAQTEAC(*ܢ ġtPntBm˵s ]?/Ͼe냢џd~!$Ah:"P4TEA MbtB#EAX4@tP!M!$(h:"P4TEA MbtB#EAX4@tP!M!$(h:"P4TEA MbtB#EAX4@tP!M!$(5//TNW;LW^=k+Ocm{ufRiD[yE:lӽբЮZ?u1[=fw̳Z;fMUǗ/'9wT73upl,[yb*J y S=^Eak٫ԭ.o\eNZ"\?em먎ԩRxBQ?V7Atu*ST)SyrLv@Yʫj/c'ʕ-j\<Ëk>m7Se'NXVd+h:!AhVztpO}[Q/MxoZec&[;7Ȝ1hr޸]FztB^ Bi:,/?2gRc1mo?o@U,>bkӆ^ѵ*/JKF-ZD``6xzا*?Jk/+f\L Fڭ8F̚Ew۲CjHvrnӦUUƎp[ Wԏ>9okЛi\𒹪Rr}.#>TLUbkY5OMWre$RR@~5_/ӻ~uV~{LI,9DrW=DVVM*'|sR۶Rt`׶ֿC5WWwQ zu{A3y=cH!$}5ԚIc>] zj:`8GB#|`8y"֣y]kӁ4?>Hr`9>͜LJvϚ)|&&7oOw=-Wwfk=N\~|Z rl / lh:!Aȑ%m F&c$U"˂1PR^Luut|۾ӁvX~z )1\qbʦղm5^(TaYSe@uoov#t`#:b۾ӁYޮ~1z`e}k[`,;m\ ?Mdž”!$Ӂ s_|fniuf y &ļT(/u1c;RtLa¬c &u㚂9` kmփi>@tb^'a*u- G% jH!\L^M!$(Ba:119|Sq^ t`jZQ|t sE(G[?N\f:+?>WsU^VXM7yϵTE03 _s:8_)CNom:gatܱAFЗyZx^$goʗpwm7PbtB" ukCDi: $$߿=$& ֹx-D`0 8kvl?:gt DHU9LlD0Ed1V`!{sKCW}gNtPyGVڵyo,`^A,.3$w DQrֳ4Nz0)*M E(L~񣊕)rG ~uB2~/8r, Z׭%Ǹ}W9ޱIxrl=O:/>Ӂ "a%DL~0a` ׌]eK_ӁrB0F"+uރJ|񨡲m.SfR)|p' #tlef")`>>8wWr>b{1X`~b~ɱ(7%M!$(Ba: @} ☂2R,D^ !OX݀7FEP\jArYҫ 0Ǐ~Ê,xQ,B+@NLdz],z˓yq*ƱUrnXex/(ǵXM#|?tqOa{PpOP}#Q$B= Ψci#hDjpqfU|v];||pHVt<`{Hvϝ^C@«~

_!b9~TBԆ~bĠi-ZaKޖ1GVN`0!DX9pO{[iS'sӸ0 {02cat= x7 =2Q\ǵC8 f'0~` |}A.T>g "k)vWc# gDn}0| u0b. L!n36WZ8&gq١G_u>)LZF4ti}A )Yǎ+A s03ee Bl:Bn ѫ>9Ba.)M,;<ܗp/r]SX鄷ޚU`g ̼WM!$(a:0+OpzuM!$(h:"P4IFND^ImV{u]EA MGrwW^tB#EAX4@tP!M!$(h:"P4TEA MbtB#EAX4@tP!M!$(h:"P4TEA MbtB#EAX4@tP!M!$(h:"P4TEA!$l<~OE%-~YB!54IS4B")B!MGM!#iBHAӑ4EA!$H qt$MtB8h:h:!D4IS4B")B!MGM!#iBH 5jlZeO6OGV?Zm^WtB85{O_W1TTYKl;Ujs7mҡ ZֶuM!#P1ltrD@:/%'^jk,i:bEA!$xUӡ*W(&m:u'?zÿ!Lǣa+|c*>z|Vh:!De:)Kooq77_TLՠѓ܃4̒-˙;Zu HRUUO:6cS:vW)S Ug:GL)^}>3eVzSw>]%o>/Z^Or#RNl.̫^ q$8qMwY-d>qzZyY7cAtmP|͎wD7*WSK7V[c!cF%k614MI5Ւ;UBET4i+Fu6;jOxl+W4B"0H mӹGT@(þ3yG*Vu-XM*ۈ`ۚl ahҲ&et(]IЫiKz,a|ر݉x)3xW+^H_N"B!GyxWq,U@vRP Vm9Ȓm^5+亰|W`DA!$t|ٰ{jQS0;S?R3`QEITyt0BjuJbu q6L±uda}$=Jȱt98?LaG%bBH+B!MGM!#iBHAӑ4EA!$H qt$MtB8h:h:!D4IS4B")B!MGM!#iBHAӑ4EA!$H qt$MtB8g?K*i,j~V/de*zQ'B_HQaCB!ENtBq..pBsqtS4B# \\4M!h:!8MG8EA!Ĺh:)B!ENtBq..pBsqtS4B# \\ nÆjjBEu'k꾪6ݯ:vn<Ķ/>=i|&wYդfY4B+ӱnn*U*Q*UUuT TdTtԶCڼϘbNQy˶/>y;x,OT$nEA!Ĺ4dɖME/ĕk*k_|mkp=Oʱ}ɗ8t:ynEA!Ĺ4Ϝ|ܴ}ędmrzmOۮl`,ӗxUp}ah:!827nB={mg㗯/r(!A[+7oj۬u=yI"5YUrwP9լKu|ʮǽڛҫݥ?Ru4)$}nM[sd?EA!Ĺ4P:ue-]E)V\ z!69w y!*}4X%d:PѶMNbZjÒXDWpͭwPcNW{R)SБDA!Ĺ0"xy"z@ 5Kk32[bNQnL7}`/[`*"m;wQvzxA\Qoh2^c}=pH \\DA!Ĺh:)B!ENtBq..pBsqtS4B# \\4M!h:!8MG8EA!Ĺh:)B!ENtBq..pBsqtS4B# \Ęz=m'B!B!B!B!B!B!$3 zx]wn&3M&'֧6k V^#Xa99E#uٲpL`n]?\ Y~wMSmu>7_uq|_mu6MMFunmͳf%KZD _`J:>ػ2!mӭ]sp3^ZUC2P6mU:ԩQT:yf ۺ2\7Lh&]TPΚ?>k/g:qC*槗Q/FSlsQBdjV`mV,sRd {:?zlYd˟R T3ʔ6.lfc?cݚUBKfNԵm;v\ɒqSx$W6ZSVt)3kBeFUOi 0X<<]ڳ{PW#52}:ɠh[2-[ôm~9 kDKfJ RǕӈޮE85t| +ϭ is]Vk;'I:OKg4RܵE6g&yQ߿֓QrewRq~ͶX,(Nj4H4R|Po#s 6lnvK!vOP`)ͻ)\Oz4{;q|Vp/Z$װnuW&:Bo>Ԏi^Sy:G3X'W ڷޝu~ -1osj#ϟ.kTtZ4i B=n~4r u2; 8iI -4*P>n:낦ϗ?4gLU6tN&f}-RMPO瓬RȤ!ꟃB.8չm15 xe5XJ̡e5GΡ`@q K1~Ո6-NfJ ֿq[8osB 'b##o4%b0`B{L%pet\$Lr\\ F4K9sZֈs`D^pdQ???B.S LZ,(NzDO[_ Jr%pey,HmBn\f<\W)#^LOl|j;͎LҠvyc|(gYfܾ"}k_`=]߱x|爐ݙڑ/%nT+3,O0XP(,P@W:Ow/p<Ԯ^fiqu2XiJd0=ɍ yyy_:O{'խa6̽,tL~Y.HW (AF4J/L"?̃Z;4MAg1rR t\}w`fU3ۺ [TE0~G ܹܳ ;/N0(" ' `iIܼ)^ͪxG2XqrPҋNS<){LX0^ЫuK^:V^rh2.RɻQ,'K5$ӣ:4J#sn_2 ?RdNG0kiսKmz5lZwE%C2yI0XPH`&кd%NƸiK2dJ 2GzI@kg_*?ViIOYuJQӻ,J0dt#jFT'}h.`@q V&I 4TR `X)/G( 'B0XP`B ,T`@q U,(N`P  *Ta8B* '0XP 0XP`B ,T`@q U,(N_jPaa1B!~PYO[k ndB!2B?m&T)n{]Y B!P& Q`B!I`@1,Be0Xe  !B$ DB!P& Q`B!I`@1,Be0Xe  !B$ DB!P& Q`B!I`@1,Be0XeL˷n2w<_)guh[B(c4X;V6^lvhLo.5=?כBB!+ QƤhNc^#om`B  I`ݱfLh]B!2T,2 63NyMͧ/NX`z_[ O^[l.ri|R ӨAu3Y3g0m[5:ܵnl|g~顙 ˧l+t1'֩b?OsO5 GʹiԱ٩yBBB )%.u9mcf}ǤQVX|ji}͒k꙲eJQIjڸ)S5o7dMwALl"\xCNmh F]|ٰ[^*rfƄfټc_2<B!T`@1)fMj[ӑfM왰Yxj <7VsrãVxm|;oszSnUb_}SwMדq;)֠6XR^!aʕ-m~ʢ/ΚE`9B! A,2&EU %62P ;BYǯüϕ wQvݫ^c5ԢiP=O d J?s l5!B(`@1)dCy{xB9[F&s&J kݼ]}(;K#HP1XD)T?C®vwGfAWGG3XYn:~O1G !B( DSKε;ܯ64jT!{Z˕6ldk?3ф(Q†"uvyWI9^*,wnJʔCŝ 7M7u]AB0XeLc&v4Hzp[_z2>Jɮ=[˔][K*$e#TY}oMd2lzOUb B 5Z&&S[իo˂K!:ƸK:{7ePXd 1ި ,xJZ$ B!ҬγɷzLQYJb`@~ V~fRO;Iwk<۶K%TCҎ!Cwk%îv)̄z_k~B ̩'ײu+H^PFV$ B! I4XUKezP:P]=2u80X&IURBdI 1T;|yIs܋Rb oC!P!P0dB!0Xi  !B$ DB!P& Q`B!I`@1,Be0Xe  !B$ DB!P& Q`B!I`@1,Be0Xe  !B$ DB!P& Q`B!I`@1,Be0Xe  !B$ D4&B(Mz . ֗ 2bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa bB(`!TPa :ffEQF4[gwCu{/7{5;.5{oOxl_曡uA꫶{<ZfGjUܗc+ ܷnÕWu , D 3MeMlSSR%SvmƍtmX *ճƪBrfPiW}}ӡuAkwˬYu'{իZpJz*4 m۶5 צ vc]jU(v~sm{_kc͕ɌVXі;S80ҝ, D ւIlriFΕ(3(2)<`hLօ;e$.:{-)K,"_`}쳶sia>Q`/l'k޼>{gB [N8iA޽\^PtKmڶf'l~~L>ֻy>P蒛:C渒%MM͟z+^ȏ1e:}vRɨ K0sǏ7bfO2,"_`v8{q!5XԦ/T~,:Vv\M 4rUfM.@ KnKbͳ[uQϞ pYgVUFewoSF syޙgG֮M%mbjVfNloof:;Dm%u=;\7qbB}=z=me[`"d4(7n3wx=:ڴ̕Lo2PK2?h|>s|1?''ݯuOjմ+`=ۖCN{Wezt>u`)IE%W_m)}wKDS_ , D$ ֿ8`35 nd-bd4%{f@׮!l3XbJ]oՖsN֦iF;@˂KǐIR( l?- 5}ɒt2X0XET2XZ`wޱG2X-4n]Wr5-HMqs$:fT%|sshX0ل2-c|50vW$2sZ5X6Kǖף %7 PXUӾU:_ S]gh9YPB-_=rdޞnH3X~dԋpjD߯S~Ka1:ޜC*,E4re`iNa~gl0Xe"dd0rh d*Wf/ KR M?G`POv'L`)&:s_" D$ Fp%g8[J7l2#jr.,Axm4I~] {U\9tn!:,9e1݃ ,T ~/۹ ֖_B4GIʮey6wVM:4-Kfߟ՞hwa Z׼]պ DI-Ԯ(`@tM`Imq W]e&7 o$&rgZ.kz(zCwM7 zAgNS .srr?| y)E^KP,"&k2vrü^ȫ`i?Z>jT'uKpNcps`)PjpTo* D$ k`a)Xܶm6B!JZ\%M0v2^2 4w4%)Mz_[υI,=h?2m  7X2 [ }K0Xe0Xvyҥv9, T>qA׷߶Iz]{4Cn@i KnK1z 6Q,|Le|'Sl$O*wKs ~*ܙ1SnU~K,h%}RC *?J$)`0u:ݿk ֑)Q,P,?J)d/]I.;aNI5Is\fY?Ʌ_i4J/hve8E krwaЅ蒛>ٳ4i6\?*?%AB)kϢ$ȭ2%p#TS*c@/cV̿UP /SBPTTg]Y 4z@[w ݄zϠ L!o47Ljs"aeyi[gQZ蒛 J FA}02IHMڏ7OVzϔL_s{/Yt,V_[2\VKN?P2 սe0X]r `2UJ bfCBJQӷ#riDߏ@( D,2X P_)l7 AB+ݻCH!ާ?.Ba fRR?,*TaRB :0%R-!O,.YdB(#B`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@t`!PzB`@' ,}[})z{VI?ؑV پ;|Б/a `e0XL a2V,~0X) +`EE  Vf Da0XQ+c`a2Q, VTXa `e0XL a2V,~0X) +`EE  Vf Da0XQ+c`a2Q, VTXa `e0XL a2V,~0X) +`EE  Vf Da0XQ+c`a2Q, VTXa I`]yB3eւo}Pcѻs,]hY|PyTZ0x3`@Z;zٿdIhdv?rKh]a*Y܁OBף4}s[1ͱX7>=?ZU%k~reZt?h>axvɓͼC六(}N.=eavƱcC噦T oCכ,:{|ݿET,~R1X%;69 e|shdgԄBQ;ʣT VeCJ(a.2 +L%kx l|%œ߽rus4s~h]TzB _nwN6]Ԯ)六(e\}խRżhQhdTzusީ3Ms;u}GAj~_u7жɨ񩧙Vm'# @I`q9rC7R i' Va\{nGwZb&LZ'7F:v$K' Va`lк:fli뼿lYh]~fu𫿄IS9~jh]2`%O !3phBFZW]BJ!#KevZW:1P\<[֭͟mKϱ ۴1W.%;[g-e I՜BMukH=h5ץg{t v}eL]_qkܕ샙tMmy.۰ӮsQJMG4iYSryTG5R֧?%{>穳0X`IUװf-!5v;EI(f \unc&ϰd4WJ]&ڛ"m;wɪå ;5JF깋l]=Pz]]Ͼa z_=кFfQj,u7ߟ0Jݼ6kzT.֭T|jۨ9vP11!r1S#f6]~ 1Rx׉Uû"[YE+ن)ї:2π5(k@\?7yuB_{vu7^~ה+_7fuZ}-;[,]MFMkk#{3o魶 v5d-SI]ch5̡%t,]7ֵ7 Am#oV۞|r|YKۖ/SLl70z][udS }禛B:`36+Vwwm=~fD2,tн@n]^UG}?:jjWlCuT: Ի[?o[+זhZ GkDtoP(CQkβ7|D+7C&,s`2W%K4;v?v*~RO?UwveNlp/h]q=t}a2k5k׵fNm7>'Yau#W`=z TzUe2X* flS%=4PlYí!R\p3lvY Qc852vϤFJ|Y5$T V՘AѡCK!}仇))U%szht[> oV[:?=~=t,eS=hYZx丄zm\9sey^tX[<$mQ V5ͫu`Ȑd4Nd z)`QBA|*UuٯMS|y~ۤjTW>X>}l3Ve4 '?E`i4onҽSƷI`2t</׭e2ҥz^mJŮTGE6XjoTuDmjԨ^yw׽@N,?صQ~hmvY#zTTW_k=S/zl|l ֻ?}O$ d?6XM[ {P'ˌ=,/%^`zUWLKz{@^x+}PG zTo2&jYy/|~QȄnCME,=05=HU3l[C$DyʕVWm~J5:zxpev`=e aV'+`I5kT#3X'?0DÅ@>J@׭N#v㦪T F9ukTezumR5X 6z,?7嗑3]Ub`瘱puΛg:*E`ըX~ﮓES3A#j8qbf]fW.NI*st,eUmߑB k"\ows5j5­oW&I<{+SR!N=mBA{?MQ,~m˭`F`;ɍ`id@N?,{wo2Xz@2=*Jjd4?,5 r Zq*ժz&R5X~]SXÔ=X 1ܲTWZV8-?6sfhNw`LB7aGQ&V'w$e qKf?'e5:teП]zz\F\fs|HLZ$oS*U qyv5攪ҵדdyf|Y]ջ򱌪JQ2XOo5VQ+N3Ouo2$^4-#l0nk0W 1X[gdtQGF׬VqWOK׹G|Y4'ge-OztsysgC$]G( d?4X 2%)Ne uEv>B \ zdTҧ9'XR蟓3Xlՠ)A /rY^ Crұd8Qu_0ZxW$,IiKBb',=\ֱLhŊP])4-+Q˽ZW(K0XO 1&Pq~K&5B?(SF=}t=jO-ku+97cjʃ5Z#=͖ϣe]t,,'h]IG3Xj ,$mƗݹ(F2(K('u!`$gPJ<ٕsyv\;K_ ZJR]XU-}@mMiZmLt,EMhD]pC|iIR;蟏v̽_K(aҾްbS|+h>J:ߥ:(B)0X`NײnZV-7خsay,ŀ^HV*Ρуi^sK<.=kn -\x׺e͓qNs50W=J$t,̓}7J~}o¹ժ&_ѭ}͉2;+0&`iQ6qu!u.Bry,]?QΫ+Acq :ܜBysKԈ\&kN:1im+XS>`)W iKe<>~h7X 5Ö- u[vxdP&.X~Zzwsd,*K\I*R!||5mn5*@sN9%-ۺ]}@{ώE4X2ZNBWR8)lo0Bu4?~ 5/`)9XOYx7'%G]kYmڶg^tS'LWITu<'K)z5 ƭ!Bs5Ņtݬ螤+Im) v@QHuk"\c T ꤇J7Cy^zUr6LJsԻy#.\+L2Nʄ԰uȬQdWXjt,5jܾ˾Jz2\jet> Rf@Wu]juHWԠhbzՀu'?5RjP?WT.%)AGF~I гf(d0Z`x ,+bVg h$^c攪?x]0:]ACb4R -47P:erWؠ:]W2jdu w )Puﰙ cwRiS״Fp}*Os>ݨKBu2.ЅJXjOʔU B|*Sڌ}0W`u8{]hۑ;P7:f.LU`d^;4mjp>Ʊ߾L޽wyZ*Y,Mj#S$"_^CA,}aX BV}72.~ T~ߙjO6a;gR۪\-m/ât?ps}et/ӫDt,mpym|鸚+WLlq{c\ԙ0XbOvUB?H2*划jrm|a̗FDKonͳRxU uRæ,IjӃK)=tjtJuԨz ջjTWߊcdkh)%ne* B..!KﰩYH2iƱcm=k2jY!U R]fiDK?q$5a+Y%k_ f)Bj] zROOh{Q+Ia gUE%2[2Jz`| m(K׻t\}=2rv:sPFPk۪ z,pkǪd QR M4(*DPI(tκ5*5[7\e{ ʘQ[㨪~k_J|9l~(۱2%QB/YbR80:Nduc%񥿇3ER1X}e4RZDsku0iYs95ՕoZuq7 /ddvn*߽N9"^6&aW#W sWak|_ܽPInLBϩz wGv( d?,TtJ`e42qYڜ5XhDi`^sRQ VI!m+{aa*T,~0X,HR+3U̙^u`Cu> `@LE`=7o KRżlYN*`efR4 :4N/6ݩSh}a `@LE`}vv؀mm}`efz%`Deק* VR͓S__`e0X`2SQ3X+35na# V +3`e0X`@La0X( +*`e0X`2S, V& 0X+ d? `a" V +3`e0X`@La0X( +*`e0X`2S, V& 0X+ d? `a" V +3`e0X`@я#?!,}'c z=w~}a{UqO۷Eşo-|Б;/5~|tP#T?"r *a,"Ut`d79 `0XMnr0XE' @v*:a V `N,&Ut`d79 `0XMnrR4X<=a9S's!#.5+7o1~]nA|F3뺅͔טG`|]zE* @vyoe9;y\ݶTRs}o%l۬EK; 0ի^3p0a睦udɒUO=,z뛫ko\.IW/b:vjʼnӦۑy%% @v)_rH:ߛ:u6%;}f5M5VRԩ[TVݚ:ڤwc Y1lipv[g|w6EPpOV\x(/f?Sމ֔xak $im|5+F3 ʬ$ڼֹf Ͻu&4lxLKz} +|PO#J: _` /bx]Y=ܻ@Bӈ ``Ÿ+8nLMk4'!I2IiC|%C7l~l-K`M>îzB_ِF:~mG I`ZlX5[O-WҋeHr!2Xww^Khk]ըUGFNjbu M ?ھ* @vRBZtCB|J~$(zY |/!}C*O`F6aIss>0XMNKRb %8R;y -_0`MH \)etw>&PA?L9h9/Yبqz9ite=)|P /^yк $i=/V݆ҽ tB /2b uyc5J-Z%c^|=n$L :7z~wnӳz:_ͷ6G I`D)El;Jdқ\]}iAQhBj׭kʔ)瓒1Xi-ObWLھk΍I'7qzsdF^ sTVC}6t)S]65/* @vEi~=ܚwׇlfS5u\u{rOB.={ WO mLAw#%å2PG5=`iP n^n.IsR3ԪSf->W,&' ( @v*:a V `N,&Ut`d79 `0XMnr0XE' @v*:a V `N,&Ut`d79 ǃ9* ~@hPۃIENDB`graphql-ruby-2.2.17/guides/object_cache/redis.md000066400000000000000000000035321476434635200215420ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Object Cache title: Redis Configuration desc: Setting up the Redis backend index: 3 --- `GraphQL::Enterprise::ObjectCache` requires a Redis connection to store cached responses. Unlike `OperationStore` or rate limiters, this Redis instance should be configured to evict keys as needed. ## Data Structure Under the hood, `ObjectCache` stores a mapping of queries and objects. Additionally, there are back-references from objects to queries that reference them. In general, like this: ``` "query1:result" => '{"data":{...}}' "query1:objects" => ["obj1:v1", "obj2:v2"] "query2:result" => '{"data":{...}}' "query2:objects" => ["obj2:v2", "obj3:v1"] "obj1:v1" => { "fingerprint" => "...", "id" => "...", "type_name" => "..." } "obj2:v2" => { "fingerprint" => "...", "id" => "...", "type_name" => "..." } "obj3:v1" => { "fingerprint" => "...", "id" => "...", "type_name" => "..." } "obj1:v1:queries" => ["query1"] "obj2:v2:queries" => ["query1", "query2"] "obj3:v1:queries" => ["query2"] ``` These mappings enable proper clean-up when queries or objects are expired from the cache. Additionally, whenever `ObjectCache` finds incomplete data in storage (for example, a necessary key was evicted), then it invalidates the whole query and re-runs it. ## Memory Management Memory consumption is hard to estimate since it depends on how many queries the cache receives, how many objects those queries reference, how big the response is for those queries, and how long the fingerprints are for each object and query. To manage memory, configure the Redis instance with a `maxmemory` and `maxmemory-policy` directive, for example: ``` maxmemory 1gb maxmemory-policy allkeys-lfu ``` Additionally, consider conditionally skipping the cache to prioritize your most critical GraphQL traffic. graphql-ruby-2.2.17/guides/object_cache/runtime_considerations.md000066400000000000000000000041431476434635200252220ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Object Cache title: Runtime Considerations desc: Settings and observability per-query index: 4 --- With caching configured, here are a few more things to keep in mind while queries are running. ## Skipping the cache You can set `skip_object_cache: true` in your query `context: { ... }` to disable `ObjectCache` for a given query. ## Manually adding an object to caching By default, `ObjectCache` gathers the objects "behind" each GraphQL object in the result, then uses their fingerprints as cache keys. To manually register another object with the cache while a query is running, call `Schema::Object.cacheable_object(...)`, passing the object and `context`. For example: ```ruby field :team_member_count, Integer, null: true do argument :name, String, required: true end def team_member_count(name:) team = Team.find_by(name: name) if team # Register this object so that the cached result # will be invalidated when the team is updated: Types::Team.cacheable_object(team, context) team.members.count else nil end end ``` (When the cache is disabled, `cacheable_object(...)` is a no-op.) ## Measuring the cache While the cache is running, it logs some data in a Hash as `context[:object_cache]`. For example: ```ruby result = MySchema.execute(...) pp result.context[:object_cache] { key: "...", # the cache key used for this query write: true, # if this query caused an update to the cache ttl: 15, # the smallest `ttl:` value encountered in this query (used for this query's result) hit: true, # if this query returned a cached result public: false, # true or false, whether this query used a public cache key or a private one messages: ["...", "..."], # status messages about the cache's behavior objects: Set(...), # application objects encountered during the query uncacheable: true, # if ObjectCache found a reason that this query couldn't be cached (see `messages: ...` for reason) } ``` graphql-ruby-2.2.17/guides/object_cache/schema_setup.md000066400000000000000000000075001476434635200231130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true enterprise: true section: GraphQL Enterprise - Object Cache title: Schema Setup desc: Prepare your schema to serve cached responses index: 1 --- To prepare the schema to serve cached responses, you have to add `GraphQL::Enterprise::ObjectCache` and implement a few hooks. ## Add the Cache In your schema, add `use GraphQL::Enterprise::ObjectCache, redis: ...`: ```ruby class MySchema < GraphQL::Schema use GraphQL::Enterprise::ObjectCache, redis: CACHE_REDIS end ``` See the {% internal_link "Redis guide", "/object_cache/redis" %} for details about configuring cache storage. Additionally, it accepts some options for customizing how introspection is cached, see {% internal_link "Caching Introspection", "/object_cache/caching#caching-introspection" %} ## Context Fingerprint Additionally, you should implement `def self.private_context_fingerprint_for(context)` to return a string identifying the private scope of the given context. This method will be called whenever a query includes a {% internal_link "`public: false` type or field", "/object_cache/caching#public" %}. For example: ```ruby class MySchema < GraphQL::Schema # ... def self.private_context_fingerprint_for(context) viewer = context[:viewer] if viewer.nil? # This should never happen, but just in case: raise("Invariant: No viewer in context! Can't create a private context fingerprint" ) end # include permissions in the fingerprint so that if the viewer's permissions change, the cache will be invalidated permission_fingerprint = viewer.team_memberships.map { |tm| "#{tm.team_id}/#{tm.permission}" }.join(":") "user:#{viewer.id}:#{permission_fingerprint}" end end ``` Whenever queries including `public: false` are cached, the private context fingerprint will be part of the cache key, preventing responses from being shared between different viewers. The returned String should reflect any aspects of `context` that, if changed, should invalidate the cache. For example, if a user's permission level or team memberships change, then any previously-cached responses should be ignored. ## Object Fingerprint In order to determine whether cached results should be returned or invalidated, GraphQL needs a way to determine the "version" of each object in the query. It uses `Schema.object_fingerprint_for(object)` to do this. By default, it checks `.cache_key_with_version` (implemented by Rails), then `.to_param`, then it returns `nil`. Returning `nil` tells the cache not to use the cache _at all_. To customize this behavior, you can implement `def self.object_fingerprint_for(object)` in your schema: ```ruby class MySchema < GraphQL::Schema # ... # For example, if you defined `.custom_cache_key` and `.uncacheable?` # on objects in your application: def self.object_fingerprint_for(object) if object.respond_to?(:custom_cache_key) object.custom_cache_key elsif object.respond_to?(:uncacheable?) && object.uncacheable? nil # don't cache queries containing this object else super end end end ``` The returned strings are used as cache keys in the database -- whenever they change, stale data is left to be {% internal_link "cleaned up by Redis", "/object_cache/redis#memory-management" %}. ## Object Identification `ObjectCache` depends on object identification hooks used elsewhere in GraphQL-Ruby: - `def self.id_from_object(object, type, context)` which returns a globally-unique String id for `object` - `def self.object_from_id(id, context)` which returns the application object for the given globally-unique `id` - `def self.resolve_type(abstract_type, object, context)` which returns a GraphQL object type definition to use for `object` After your schema is setup, you can {% internal_link "configure caching on your types and fields", "/object_cache/caching", %}. graphql-ruby-2.2.17/guides/operation_store/000077500000000000000000000000001476434635200207325ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/operation_store/access_control.md000066400000000000000000000025311476434635200242560ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Access Control desc: Manage authentication & visibility for your OperationStore server. index: 6 pro: true --- The `OperationStore` has a built-in mechanism for authenticating incoming `sync` requests. This way, you can be sure that all registered queries came from legitimate sources. ## Authentication When you [add a client]({{ site.base_url }}/operation_store/client_workflow#add-a-client), you also associate a _secret_ with that client. You can use the default or provide your own and you can update a client secret at any time. By updating a secret, old secrets become invalid. This secret is used to add an authorization header, generated with HMAC-SHA256. With this header, the server can assert: - The request came from an authorized client - The request was not corrupted in transit For more info about HMAC, see [Wikipedia](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) or Ruby's [OpenSSL::HMAC](https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/HMAC.html) support. The Authorization header takes the form: ```ruby "GraphQL::Pro #{client_name} #{hmac}" ``` {% internal_link "graphql-ruby-client", "/javascript_client/sync" %} adds this header to outgoing requests by using the provided `--client` and `--secret` values. graphql-ruby-2.2.17/guides/operation_store/active_record_backend.md000066400000000000000000000060151476434635200255360ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: ActiveRecord Backend desc: Storing persisted queries with ActiveRecord index: 2 pro: true --- GraphQL-Pro's `OperationStore` can use ActiveRecord to store persisted queries. After setting up the database, it will read and write using those tables as needed. ## Database Setup To use ActiveRecord, `GraphQL::Pro::OperationStore` requires some database tables. You can add these with a migration: ```bash $ rails generate migration SetupOperationStore ``` Then open the migration file and add: ```ruby # ... # implement the change method with: def change create_table :graphql_clients, primary_key: :id do |t| t.column :name, :string, null: false t.column :secret, :string, null: false t.timestamps end add_index :graphql_clients, :name, unique: true add_index :graphql_clients, :secret, unique: true create_table :graphql_operations, primary_key: :id do |t| t.column :digest, :string, null: false t.column :body, :text, null: false t.column :name, :string, null: false t.timestamps end add_index :graphql_operations, :digest, unique: true create_table :graphql_client_operations, primary_key: :id do |t| t.references :graphql_client, null: false t.references :graphql_operation, null: false t.column :alias, :string, null: false t.column :last_used_at, :datetime t.column :is_archived, :boolean, default: false t.timestamps end add_index :graphql_client_operations, [:graphql_client_id, :alias], unique: true, name: "graphql_client_operations_pairs" add_index :graphql_client_operations, :is_archived create_table :graphql_index_entries, primary_key: :id do |t| t.column :name, :string, null: false end add_index :graphql_index_entries, :name, unique: true create_table :graphql_index_references, primary_key: :id do |t| t.references :graphql_index_entry, null: false t.references :graphql_operation, null: false end add_index :graphql_index_references, [:graphql_index_entry_id, :graphql_operation_id], unique: true, name: "graphql_index_reference_pairs" end ``` Then run the migration: ``` $ bundle exec rake db:migrate ``` (You'll have to run that migration on any staging or production servers, too.) Now, `OperationStore` has what it needs to save queries using ActiveRecord! ## Database Update GraphQL-Pro 1.15.0 introduced new features for the OperationStore. To enable them, add some columns to your database: ```ruby add_column :graphql_client_operations, :is_archived, :boolean, default: false add_column :graphql_client_operations, :last_used_at, :datetime ``` ## Updating `last_used_at` By default, GraphQL-Pro updates `last_used_at` values in a background thread every 5 seconds. You can customize this by passing a number of seconds to `update_last_used_at_every:` when installing the OperationStore: ```ruby use GraphQL::Pro::OperationStore, update_last_used_at_every: 1 # seconds ``` To update that column inline each time an operation is accessed, pass `0`. graphql-ruby-2.2.17/guides/operation_store/add_a_client.png000066400000000000000000001451251476434635200240360ustar00rootroot00000000000000PNG  IHDR֠/a|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 414 904 1 VQ@IDATx \Ud8L2("<琙٠נuoVn--oV6iYZVfuS#s!QDQ<߻8j~rYkw_~z]FFF1HHHHHHj=ZO!>>999(.!4K$@$@$@$@$P(Tzz:ѧO4nvvv"~ MK&&&}a I     ujj-7Fr > b ̘se5&IHHHHjbq     @{J$@$@$@$@$@5JJAjԫ>c#3f̨;wN;B:5cոlقC!99Yk-Za}78qkye^wɓ'5֭FzU[[4D$@$@$@$@$@9sKU2C%`իWEիL|\rEjսoϞ=Op1dee}vԯ_6mBTT&͕*9HHH@6m*qF̙3GV4m۶Ƽa_  Y]V\$o X awڭۅHIq8s.uMWBFr,Gř䋨SnuK#~% n>+pUYi)*tJ_ʴG3#WӓҥKw=)), Mu{J~s=ooo&RRRkiu{VV_rbk>aj{v^[Y͍y$T5@qcT1I%֯@cxQP&w :ʚ@UhK>S``H.'agU>$ѩ򔪏0Ԕ8[ |Qxz e~y,{v5ipFum(=-xh%V'jopS"c w0QX}C1S/Mdq;a!WkL㵰rh~\2o!L6˽o\@T0wD;ǣ="Z52`O?&o^Чb NJX!݃ͬlv&wUX;p_x?~[/xg H~b} )-^Pg$ngߟ k˶ɻhӹl-^ZwBrՈIUX87AFױbjc$std\'圈 zTl#3r!5'үB}9ڥY@J,Zzr~'^㐞%v!U 3ω`KFʒ>ƛh,IhM|Kn-q蝯͞_Ҷ27#Uޫ3\Sc|؟!gyn_h!Y.f-t@&A1PdocH D 7Q`id ǮVۿTIpC~:b%=)}\'wFRNXVS< IM .#z"%)h,Ή[Jry@$Nc{/+b{:Ib<:G" rv&i,ΦW8Wf<    ~G8{O /TgZh*MU<>>>U=ŪilJ-+5ƩS(O"QE5@<-j!.~3En%M^j1SIT|[t@ "vÔPL]3 b"#}jgM>'Wie[k1~xiw7ScNM+;lp<1~}ySÝr6]u: A֋'ֽŻ3S9PWc)늺͏?Wޚ4ZgWƤTRG|x 6x4#B|:"gb#l<,1m4v1x$V>zDXApKb#ˏMC뇧ogp,yevp@p}2z#g~D=:ؠDl/<-_M`9 &me&͕,err`n w}N?)S0Y{eJ@p wоtvo-$=waўV1j8(.8y mwcp#h#&)qR썙IG6w۱='u GDqLHƑ upB!WJ'=js֘x$`x6D_FԵ#GF.FohVxE"@U6z /L8XeZi,O\a[u0mxe#ĐJJo' ` CMyr5J\f0Cͬ'-lZ4Í-0뷒'+W7HOOÉ b˩84e2w_(,l Ũ~-LUBumf݆cam~n62(/4Ć!1-Pl#t(F\AAFmFOi1rSy;U 3.{RuN∜/f#5I]UCIo-7&hnөoX(#addҥ$YϢ,bm  {_t wD0jD41C. #iӢ^6Sq٬Saft0,ԫ9uD%^[ٷ_PZ!,9a9Ptp܃nr"wD'@#qj?5am"sGGƋ׆ <KbAR~N%Umkp:ܫ'ōH[.@m#WġJ!g7[etvxe!l]3ss)MeP+d@1_;배0Cb@ZfV:W6A$@$@$p(s"3cX'P1CTKMUkM*"hyMroV7|pSTgL~%:s[c%ל+n56wiE:e#%n 6|MPGʅo`,R5[%`˝+:<$  )TD%Ի?c-Jz-q%^kj׮vzaeI-<|0.\PY*wM[2^eQYRuRuN?OZT/+eayʗD(ϕ@2o`H:Kcgf1>}Uz t18ʇY",Ms*L9IdjK F fkrZ?T 7^rK}xHc@w-1~X ],v'h&[]ѧ[zQ%h z6{(,Z )iHI؏%VYhnP>XCH<OCWFk :MܻphjBqbL(?n cܿTg d,=71nA©ٗ>L2V ۯrI,5.mŒW]Pt1?8$ɼ}Q1pMde=zy!?f9>[ صNҺʌ鋫vͬٴz` |lOA}kcLحcZQ?@~[!?#3> D5[>!w-Y^ᭌshmc3tXKJGO`X$FwFS%סο+d*D.ez ^7?TmT2WU \t$rS'Ԅ8TոT@Kj5hZnHݻWzWbcfѲw~\^=e>"\1xx,i8Ʌ;1PLĹ"}JmMWʲ)XTeDAtsxyB| -D-T]C;=Ŵ[{1A "!{x nhh߃qK|f,l!^~wO0{oy'']'7c8C`Lxt9)wkEJIGyCNY ;`tW,vbv=3FΕFD@J L.JtJ?cnū/ժwnpw6duX\Q >$DwFIDzQ-1:ϖlhU 38enU04/FԚ%CP%üU9i[OƘ+K"j=NcpO/_q/')HY}vfyWDK^\^i(p CCeTTU*i c> KT%خZ_.WKFF߫6%xM6K۪% j-FG'.Y"zd$VHH*]3hz6EGcqݸ;U8WmHHH"`Q\Q><=6le],Yjɢe=eTBw%Z-}qFU> ZAAAZqxRyx t,98٤,VlyAnwDU )7KDmQCv鳐a\Mnmddp: s'{g4ti8 s_"BJKCeo: _,8{y($l 4Q>o!fbTΰ[=.:Z۵()mXwq!1}-3BW _(NOۼ__`厵NzՌP/}WSUQMUMx YV<ͩR |+m]㏶miͰ,AVN\ aC_vA׺^k;8'$)U\ HHHFؒV YzwҚjj//-[@ųgjKO',}TKQk* 8PVz7i Hs]wiJ+V1wAr%+Y[JV*+\L熆\ɩ.hذw;WAyuPH:],S,"9  K`7TD)ۢP WbWF\pW.r\B37[2j|u|X:Xג03iMR+{9v,ulmۢ5Wu\u(Y; {Tbd#"F2[ @MԃXmVŃWgPƧ<9"K ՜Xlu%ht(yUB\QZvWm 9)q9C0+O^y*c =22DW5p د7*Z&_ޘFV7t`̸D-0͕L}=?zQ$[/$@$@$@$@$@OҢnę7hܥxQX;y6"xf12qlѷppq8TD$@$@$@$@$Ps(k-- ܏ CШc&CɲӸt4 +8k-M{Ǣwcdˀb T7ww#q= G*L/  &vts[Ջv'g&pyYi8w3/+VvTNIЗ:Y"[W?RnzED[彗Kpml\8|/9z8ȧfo9?е-wXvt$wƭ #2&=rc )b+Fr,?q1H?BZY}Z@{_jH*as#W3ǎDbE78y.O7vF^CSp&9UC2iL|4 5&tν%M]4QCۄbxmaQ'Ȟ¾}n;a=0dY $0mfW=Jd!ylQ缊~;Et W;\tS0'kRxȩp`S54vΛ؝DX2Xd#6cS2L`AgqJE%C> sR,~(8Jan.\Djߨdut $@3ŹM;l4&Pd.Ԣ"䦥Kd/c]{]ok֖wK\G޽Deه8i\lKƮ!9dV8J[X>7gMB-jl,w vQ}]8[۫:p}Ս\=\r`s  ? 2wԚ(eNٷw#aV`un6eh'~7&*op~:u,l=n6$Ka">'>7Yn%= >{=<:_Z/9iZYo-޼-~b.S;wԪV_⑚sO~xKƬ<6eo&߇ys&SUIEZf]$iX;KaSi,;QJ O)_֢F*â<? qZ@jp ~yƏFw+}*I2ho7/D{:LhͰi;06YQt1%Oxna6.EVp{zɷX,|Orsoa|kd|<h?rӵZrtNf#2BnޣA8wBz^-[ /áȽH)~FF^~iD:}%)~rAQ %~a6.ƿ}E:l?p#/8}E8croh(<F/"q3LR^Z>=@gڥs1 # i7oԄx9Fة+ȋqU*c]3NwŒTNѶ^")`X $@$@7!* :"@x_ұC ,MAZܼ[7𶩘>ql $OI-dVmUERW9Z%~&?VX55}+鯘mؕJ90g,5 gu>JcQTNҷ`륙LMH2mxbz?ԇg6eD+5_(ɋQ180- g$̞T 9Vtzy$؀ñpu l,93OճR1874~Jѡ]cL*n>7<]l^AFnWϲ1< ~0n468=Z/.nQ/l400@-WgyT|l'iW,tyģǐ@@t;qA5à^SuO"^iC\Pu.p F YdQazgh,z-'BOxJ9ȼYE9`(㑅hբ) ڞ7{Z-DVEiмUZtMkpJĮ]6Ɉ=.G/j{r-Z(3ͅѠ.2g5@,9\4ojN0*ʮ˲]{bæM 6h |\uam9~ "2Q!i^(/75,.x@R\`啷rLřsyh`Tfl ۲Nmַ+k7m.E[K˒Z& FvUKB'aQ{'N(3M63v7/-fy*͖Sp7GF_\w@naqZO8<Ȳ=-p؊>,ݼ ۶F_^nWu}C 8I@\j.O\+^Y H0oAx8\wb-A> 3ʯcndaLpBҳw[Yja-]y%f@ʽz& Xde1HLNcDW:Br>oY/uU0u;.{mycލdv26mZ CüᎣH=d.G# 1D,^Efc߆PN&cvmƍ;i3:HԆ4)g1;Z$&\`;|~pS`Q "1CP:FrZ8Oo }+Z*hL_3DZP#ѻ] 6pG;3bLfI$@$@fw8F${.:7H3VBmU/hP剷~6f<N<B:Y增~_{JLV$X_אcY\=Y\;J]mf͕\|<ݯ;~huZI[ )N;Q<+sRGoo`o*iԲb9g앱A&EȖCy"CxT%7CΖzOg\~sXXUAw> Uiq4|j9P-7d/uDiG,<.<LE9v9i2wKՑT^V?)9Bԃo3LEUt_v{Hr#{8IšV1JΦs~^X\E:I*.PGNi(œqHZCJ9l5껽]jS u YUf`<4Bdylv  ֮)8r&Bu3)`G6Nfy84mSnցqߌRpS {VfFӶ]${UQqhTݸ$Bjx .`8^2-QVK%%WqUCKctB>mDzX{'+o:>uMUTMr?ei+y(HW,R>2.苰NOj[F~ZxX*9,… G µM**Mm"Ǫ]3`ҾhmE=HTCϮ*`"  *r{+v>p ߼p_Yjeٓtgߎ[}]>+Ken}%͗fc⵱tn,||s-lڑ~J1Wr~z˻__7MMz7+K;㬅@)/9* L?%x+9zFh0?lxa&%7]\t>9G^UT;iУ{򄓛+dA4a"R~%(uUi)/PnNGx$UZ厎}0Wɹxqj>B+C 1[ǰHeơ} x9'ZFn㎲C[[R UWJG&}a$7ˣ|ÛgcҴ #)S‰setqG֤-*Iġ:š6LYg!)f#l _@~wyäثWU..硝]CUf;kP7(P9L$@$@$P12a3X2p Kx;=RE6D3?hIgv~nL4}Z8a-x,=SB"E3OĴ$>O4mg .{k~}Ge5FȻ1(#E;9R?MDH54ׅa+/brt8v(Y@,/lM֙sLR:Ç+дagm?uN-Ѭk̻PMo Gڤ^ޓ6N gg  ; AơH\D.=+K L5yz8\,,j7 jl6m|~nz^~#QKUAoi+Xkql'-з\h h /Sߕ4(>4C./@I֭ \7^|">WrcLG h9Ga'cd_vK$)[p<+s~h'be)aId\cLjT//{ \'-\4\/1ȒS4Y& l3G/,@ͫzqbXk5+5qB]zú@f<   X?6xe~D]}4 hq@IDAT潋%QCMv_a x`>.1SlH:y:w}~tgI9X,kUy˻?ˌs)B PmiF[[Y՞`'[d/7$7F0T"zM_,'d%am=D=u~qgΜу_buJ ~dX:V[|sX{ gcjӘp|!Wiž!❿.GhHFEX?0onII_-dtA3+Q[& RpNz[^٤9:" jd_oh W䁛!LS>VK<!P:O,!Ŀ }-CЦ+NOWuЮ{{KhhܩrvASh wǎh>"7zk IKt 'pbx҂s8u ZAMРA#h$pe욱k}CaZPGD&xX1 C>ּ(ּ7ƛăش?nnR6$/Hlir])/ ΅ǰY7|8 ?W~&c% ײ7@_udOCw}1uy[cDQOك!^A 1W9| eds5 S&Ϫ* d:"2ޅƮfWu+KC=p~quE6h2f[p\ĊJ}[2w셖Bѵ/fGTԖp% GKnmMZJOw6]3M:gJz6ix+*\[ a- VΣ/b|"}2׫WC'=-%dWnvY'Q`?x/|%M#*QYj[yhG\.o9kY]    K@KNTڶ}(̫,^zrVgk* &iX>ujl3đϿ.[     J M]"b     (k#d+$@$@$@$@$@B"l?>:he,DnkLy_݈kg6C ңS@'߼u%y^wLsdџ6!    i Լ@K9+1pǻ P,zkczwACqO)ژ)QǴndffbw2Ou$@$@$@$@$%p}`|}?p0|Tid޵[mmH*k#7=RK-46?׾ZV lTu9oؿehyڛncNUƫq%ƕ+"5F[7Mj urN:3WUHHHHHv.Q#w wߣ숫U[@z #dϟgGw eyw:@H C}Z3r#ge`ۿIdHHHHHKZjq;~~x7jKM#fw~B{_ce)Z/m,< >rxNܥG2")vqWє[Ӯ ׫dMh)cƝxmوf}5qPG Ey>n 6=5@s9عE_5xe_~x! _u\8_>}=p \ivg2 vc惷Ka\UQK^ 6Fk5,R~Z*jh@pU^Plga]ݹz\P6jy89&]:/3JY$Y\}~:)RRTLj:f>=UfHHHHHC DW/'|G'^{R!~ʟn-T'=ְ[o3U¥J^/ cOz0=aVas*+: (/܌â,zә[遚!G„r #IHHHHH rLA,}(GY5]Ad4e+q姃>V\e'KxS9UM(?q^+o/&JhVJه2TEJkmՄnxaN (9 ~f S     8c9bbck\y?f<:}SWUcv@ߛ#Ì_ak5T#}R\ʇk*7Sj:m,88ҍ|u BC+0C9:Ʌl7$tC$@$@$@$@$pQQhS1,퓲Mc~7q𦈟o-D_>Jnvm[_u[_t큖!R5F:KGCRL<ϟ+1Ac뛋-񨧎˙WtoDEoⳍ'"Fw$@$@$@$@$@9 $#Y}& yӸF+ʴ‡fg1bm^M,%\OdliaQQ ܯFa{pŋB#0b`_mU4+[Xf ;8\V7mV݄/CVqK1R2] w 3ʷMM"RX3kmb<ɍ M0br( 8huxy}ܯ mCq= |/bxX ޖ/k(Ͱ= %K*b?h2Ե3I/K[%~Si"s?F[KIWqu    xhH i4|#E}!&E.Txr"z*)NZzs1$    zIIMTs_1$@$@$@$@$@$- f V %     G b3ƘHHHHHlJ$@$@$@$@$@$PA}y @-X)HHHHHr*/c     T+ @#@1cL$@$@$@$@$@B b`P     } Ts_1$@$@$@$@$@$- f V %     G _IfQx!" % 0`퉠pu lW5tu;R=i^p'$@$@$@$@$@$@9D D[65iQP!׬:oóS<|v'OFn_͖Cݾǟ:# 8 \=3Qzٟx"6W_ C&mםƏGWqg_"z,MZO? c6Ԡ ]4&hbi⫨eeaFl{;kJ #UQ(g`$@$@$@$@$@$p~QpS/ǡ#LŇèfK KqS]x/o k?œ1v_7ccL7u]% Pz~;3 C9Q[ 8>X =x@$@$@$@$@$@ cbjr$om7-_[`*GaX' $Y{Y?WFh"WkTƑg\\ nymπQNF }>Cx^N\aq<HHHHH.PgeQ5 ߺ<] _ܿ_ >b]`urP4'dcد8|_B5q(4ZneW 3     `UQQh }c6Z驍ڳ# ᓣgVŖVqBxTl};-[9-|&@ B[t(Ѣ)>$@$@$@$@$@$p!8&kQtx"x̔/N0Ǝrs&~\ދbH(\^EcՂϑxFBHHHHH;9 x]xL{tl?56lSsgʣ[mbj_Vn4* T|u{?o#[G/ی #     8 䈂8eHVN ˦ŬA7vph G>Q.m.\ y^ &_>Ub[9^3WP xR@'B6̐-Cyc:vC     8 $%%Ld6`!Éb6k.~C0 K     @^(N     - 斜bc`kxߝ—zS6q&,@;v 1sW 0}}vLmmP S6EV\Tuxk⿶w8_ىofaf}{PG>sr C\4y:| sѦUґ(tk Ӯ6ø]xH$@$@$@$@$paH9 :!2֕/4ELKȈo Sʲ_w?ʯC=M9]AGtԾR&y+ݍwƐnM$^bkEiR~RuGK}ZaabSnۢ^o+?{*5nR_ 0 ;x W]ˆxUQ`\>Jvv*fo yN Do+}{c꺟ټko/ލ!8b/U%^󯞝C:^Ư>RŌ?ۇs;zx00f;g\{I>_j1Eǿ4tEӱ}| )@ 1ݫ\61R{h^J_ 0 k0潊=5&ϯ;cm;p>,| 1ߎIH yMXf]J^؎g[W0xTń{[V]~oa'eY}3HeNFR`TbnǺɦ_O!KڵO.]H0܃aoj nr|?Θ/hH$(ӵ]1\95,99p]x"45eiyxTWožqD(&4넶ډQ=J?0 f熂xU»_l:|,{K9} M:jѾ*{2tļ*j_j_`(l65g1K]ꦾ&C*uƀq5QM; QPzfߞ?T#  +=R[,$GVMorv29+A2,7'    8 #&ghmh|?VL@.D1/mg-ƞ0NAoP]ׅ 5ڧߓ|Sdї^J9AWd`^AnzXvp/Tv!,Ϭ zHPR9+;~R 5o Dɖ-=DSHHHHHCWAuv#K4hSj,^&zAe|Q؊7~NgNHM9 ͭ[v._ٻ5r}/H0O Mp\չ{0 e &Z 5w?tAjҾ{V9ymK ωG$@$@$@$@$pȵ&*ix͓vC]q`h xm=[/mB=xS_Q"n-Ed6%(m-]p!05qצxPxG#ͼŽDcSxT)ud:9* xHHHH.9 Z)WC.x-;YiG9T "]'.)GuDvJ-RS{k<#pQ{w`Fo|ܺ'&i7H>a:bpDA|u<؀{nwi# F~zF{UMd[ ^onYdlsgOHVw$@$@$@$@$@  2}#} (,ߝQPYгR|K{ [N)+HFhs)}Tk-Qpap3tH:Ecyh4ƷOp/U%΄ITHHHHH ,J-ARQP'%%4~9yqI6{ovrP%:bPE2*0Nn#>6}|㌶t°"5*aods1zŐHHHHHr)\ &cD9NGJvl_4JC1Y98ɰIH T+2µI2%    Y4g1t     84lg#     ;:     ĈQ!  ԑ @2X2b9:T) \Ax<Ia#7      !,)KaYeX"E \@i`&e Pzҿ[+$DF+'H͊R 'r.)=,+,WUCٔ&{8ŃDHHHHHH (Ro)P_Y ٢ }\8v@IQf"A$@$@$@$@$@sJjPQ Wkܓ d-~1kyR ZTsm1$@$@$@$@$@$ f-OJ#     \K b:FHHHHHĬIi$@$@$@$@$@$k 58=ǰc^VGsd;Vl>Voptð3CN>N+zx,0ڵ'Q/gn{*0p:ՌعtdKE?K@Lĉb:VyX9I J4˱2O]\S$?u>J?}ڕ[Cs;UۑaKW_pV5JBKkW( u>~u,V'ci\tC‡kxy/bRŕf{wO<^[6K3!tFW+ׁ[%x}̗8b_V!6s@u%aqS^xf(Mot"rqe#[Yܷ{O o9toշ64bI=v/tÇ`1"pbN\4똰&s]5_`D]Ցy>Rv^¹~ã( r'4ꄶ Jn~1Wbo\?S?~a?Zz,sHHHH.(III!ӛVo ]mv\r3h{>1ۖZ-srV?){1<{ߛ˱UM\zs\gЦv " ~ v3Rwǐ _a5qg#F2 4ja ?{`[ dhiQ0%,*:>Ȩ^~c5[W&!ebehԽ_yc(W k67bd]Odr.Sێ„xj^|I\I I0xS|BD9܊$oi FוXQN}.xﹷ0{>hP䵱hG#Gc]Q)H9w.ye0UMJHQ8A|R•v_rXe߇fˋYS& $yVXuA+ l0ib߲xs8ؠ.2xT򰢤o7Ʒk`l܍^ϡv)OavB2*ޓ0t|Օ2wt=F&e.)sy}@A K,Ú-QbIYHgy=|1PNF*a2$vlI  صnNTU.]#pè{K(-OMQ[**y#VDQN'g-Pɭa4jZdܮŋJݱ 0 T(N<>C%*\+maFʡi(1Ev[F3߫̋T{80q}UQĽ)%ucb4TսUTIzLYz\=rYBe,$DF+@%!n vY{E6<mX'#mЌ[uhB5CuM7U|lʠc;QOnR^{(Z+ƢW&/#Oq\"e -$c!p G2BJ|0ʍF%tkq3 C[Qnr5GD9;&'c(qWgEQTSX ÆvF)eޒ}GJDK.yE9Ua) cR?ڊr6r(mH9}t>oL#    Jst#J<ףqɏJL%u0fj*G`TzDT55r-9qܱn[l_PeQ4'qTq1>rS6"#z3 Cúb*^8(up{??79cթz Z>nV7kpI.#zWY޲_A?pjS\4 {;On^o+.z,7e_fmי:aq]ej7skDB@w$/R#0.  8:ntڸ =|l("aجڋ:%v.Oĩƍ^Xhk(WR—x=v4ym)?kyNٵX"yV2 1UW?}[*&Sk^9*n e{h\+/6ᕿg4(&#զCUWA)ǣJL^%yF@˕zT py# "O—Q\ԛO﷕_Qe/'eN:%R<\ ↊"OV)0ML\w WtժgI('*bWU1(xV$J͑]2nNVzZlGD>Y5['ᴬ^F2lpHAK/ub󜐴L ~"|뻓)1sD>[ҡ}sycⒿ31T+/ZiA)1I݂M"3&[ T4W!-˯h5U8mx? ɇhasf3:TJiW~-?U+r̿`˔ wI,[uZteXE8.&ǖ1^/.<]SUG| |F;!<&    @6 4*يa˱iY3l\wj_e(,y2FL3 @IDATw|#u[9lb9(џmE:Y#ϵiYMr F]mFVH 6jVOk[a-R oQ}R+ 2:FK$?I;ЛNQ/~;EY :\Iݢ\ű˛)=P9+qg' /* E93Jމ?a!)p^qYs;Nv R:p˜0,+X [U̍q$     \I "\4 9p ⿝w1O      ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     s@وu_é 1xzPxug# _ ×@s#NFHHHHHH d/3"oVj7İ-Px[<䜙8m?3O_$@$@$@$@$@$p>DOzķK7:zlAwXuY>zr HHHHH!;AIj$و^~sNOǎB -})Y%vԺ}fj?zmn-T p-ׇދk%w`㉀pyB$@$@$@$@$@v 5v۷vO|H@W6~ɾÇF2a͟F?(z,A xw",:y+V>yAJ]:vAŐ}_>r&-?^\wK)3e݇ZrkDюw$@$@$@$@$@$pA߿+P@'Z-6f(ɷm@D'XxvUz`\xf<{ѯhE,kNjO_ՉÜd<@qϢ?ѿiLjVOWF6tqBxʩ8zsځO{[Qby'4qFH͞哱ɋGImb'EEeYiʍ*p[nF;^y](m{_m`n(d^ d²uQh\y;5g*qpuA}"5;V./nI>(~)elظ;\G94e_\ 'b)'ЩqeX=۱ 5ؒypmQILJWLXx"\F)pD9,W+q%6\cqxh:$C㯺O`)?)?/zrWw"Sय़Y۝x0'\E!     A [q'Mw᧯F_bppV 8Qn/q9e.\ yS;JM2lѢ&aþ#8{%t\bpzqw>䫟o\> -߄v,_:UA+gȇi#}&6=? Ė ^7v~ɇ*R3QX3kyϐ{[wPk:!z8EGTitpO$@$@$@$@$pd*pP/<΢TuORtl"2Gܱ^YUiG\%}M[J=hq\aR8>Otn6mcx T/x?4ϭ򹍅OWtB uuROl~z} Jj^fbIMnis8vżzi6W8qsqgrUK@ѼU)HHHHHH [4D'!2WЗb/Fˈ_9#&˷#9ލ>G˜?e{dcb"ZEVe~6'f=%ߓs.*^?TL dp b5-"!! Q2R"9{'Sh C(p?9_=יq C'x@$@$@$@$@$p ijs.$*z~qTs_1$@$@$@$@$@$- f V %     G b3ƘHHHHH9 &бIᅱw̗5 K!$U})"?qO"M~6E[1Q=E wޞyK # s A+[?}>D_O:u{{JdPw #Kb0pʼn:OChOx) 3ѳcOI*-?e_nIkb\<ۼ><sX+%|`mśpdUzEJg7['E\e+OU}\])i= dw˥(v{zS):{֛"ǼdGfgPW1(vU1sKZN'ޕuA܃n=6fnPې2,6%aTx0i*m;?#JoN2/忛Շ0MݳulwG9dt4oWҔaR=Fʄ# zm,?FXʮex]$e BD!yFH +Ƽ64voIRW:@D|U,! Tz{doP!r# ߿пcǎY3d;0`^?!k`N A]#v;<ֽ͞猏/6~?sQRB9︋KG9֏q٭ѯÝ̍ᅻzjӎG!ݚزkR^1tK'.E kvU2d NNV1Aǣb~fY6oO3ahF6, $ϼuVxPmt|`ԟCKvySʓl~l*39AfΏ~yK:֛*\!HݴD1nu8R<;:vwu2Q7H!o6o )So?T:e*`S:ĆP بuH<]^%) TNikɑ PtGNUӏӁrU%Oyd}`UٹruC! Iw޻kt^ 8/g,i?եea*R\Ôi 1"Y녈륍!vc~f! ;J\a([@鎸ԯdU&L]AG1vu#PϷ0Йԕ깣0vFI];k灺!zvh?NjۡʄmzǬk~Xn q{ǑWw re}v80ly꥖d`*!,L\bh__+Ҥ_I:8R.]B ;7Ґz&2i'ʨ0U@`w%bv=BO4!r5Q!DQ% \N X[t縉 },sĤӘF迄_Gr+~B[s9Ɍ``.fv ONWӏIA&Uo߄0叽 ~< u]扨o)^S#h?-yԼ4:)YgV*R(ٶ/8у;m!,cmcP=_tf-͓(5m.2#˷W̻\f`h{xk wxZ%H! - (~%1k1'[L"r2i=o({!/d0,SM1l3qc];/_JQw*NͭHժRۀm+~ 0T7wL탮K>r!I] R69ˬR31vVL&Ǫ-:;b|$Hȹsn,)R#|4 kxߌ V5ۧS~3 FNZ Qq_ndW9NQcZT,tB_b\ b>bl=,2a:nS6qV+p4PfV|Jtx~zݚgbIکo}\m_aQɂr§RFyqc;\T(u vʷzϦ5?6t#&ֻjK70U\}cGZ ^mjKT]5]齼!%?gKdgqda=UiNyYStUTAvu<Լ o]dۇ/?픹誏aI뀭`.u;Q $@$ Y3#)m" mb&سY͙vH&/Aoy3#K°LZoV.&͸Tlui~dH֊c.ƈQ#֕sgd$9H&;oݦ"fb2dTPnP_d& 3՛cejx?}bL;3n-ޮG"q׍mbE-nG ~Ye~(z;yvuycG_̴ݨg䥆(ߦz4^kHW<`{ö~Gڄ7} z[xwkFfww5km5+`•f/~,]^gPFU' xqw-`UC_[eDm#~ZW3C٧) u9#.Fͤ%􈤎6QeG"n?1zt{Ȋ2Qұ?5 OKt\3Wic.-f@!,&A{WWa/1뮌4}EdeNL7<ggQxwt_*m:?[s8wljYF7^k$̇7;dFmah˄6"mR|tiP?j$i&vʛ,Aζ`Љ;V[Vnu}XRVu13<ÌK&q#}FJjϛʸNl)~K ݓKz$obi&mxkJEv|%~T9z=M7 =oկ뿟G+!'ȭ229gb(;;luj|3`;}-zTxΟ_׫fSs0Ld[Y h/g1aӦ.gL6^6;.QLU;af: [kF_Hy+, J@}k?L f⮴(6#唣*_O+7ZVg/Mb6^Si}?G6]ZZAќ<\./(1vxD,0 Hꩆds]D7TrDI6IH!}R>1Q7i:GWG?F"yٍypx:~N~K#&ot$P2 11;zTCgԐw_MHW#Kͳbg(Mv#|iYbjݴXZ~KF?hdge$ThJU+G5A a8iSaiɨLC~L(+[!,eG+:]dn7ar͝mܮs9aZ!ԵJME% xQى+EV΂U~Xgfn[U2yGDnǫ5"*/kJ"/J[:}: <#J?z[zi~vosVY*Gy!lF3K[r~.M# @F `FJ8Dqpn\sC!Wu =1P3dΥ>*pq-iũֺ0 MToWU:ҙ INn8ȍyGs y? _Fv߲:feT_p컄K'2v40֊-+eٵLҦ9wbD@p݁'d1U~,+)-sƔ>[e4 S_UyN~4˒6[U77jH:--J+;i;;`;k&6ˎSC+º~\׫4uTW}w;[?1i7&EcޒMARBH(׈.N.a^5b,N⒩M(u2^S4.`}ˉ>V(Npov"S^ע/2 '!7/ɀt y0E V,Y=B;u7a19<4` ~J|\xIWV,_>Gk>\qWzTX\c(E{a$E8 j{q&dŠif&<U#dYqE%,X̌NUJ,2弍Y/d-P}1 |+F8*N9A=ߛT6$ v(S7J|V;y>ln-'7M_3۫ŵ=_ZϚzRTb *eBy[-T` G-ie13Lʷz ďlZź~9"M{Ykm߲D^Y׭~rT]"M A.Syu7|RMUDWmpu'eg3A)p-"v[_"-"5"vt2zf* j<(㑴{)AH];>RPn<ں\ <&, |bsTp":ܓ @pkiZneSsQo~eӎTe%!! ɛkW$;4oP(z΅qF!Ԝ5O>ad-FLfn: r0]q`}b25bPt6K6sP3 Y(yKz鲺}5kǣiFrS|eiYڼM%ʝZb)H\s0O?J;B'?JB/"ʡu!_wГUUR??^9#) 7(͟m&wβ;&]ՕLs OxހHڽfSDWU39I?uH烮o]pwpA~&I͂ݻ@qэ(dػ5F:hZT! gfYEwltG&Av:0*bY4HiToa.f!VvzV.ʂ/6PFQʠR'B,rM攽&_wVW[ZfHTeNxhg;{InVxiVJTy;81`ecˍyG;s Jd,Z$EJA)Ƣˌɣ5#FATeYcؼ!c_ X,&IFvQ9 na gr7yx2m ~b "~U@{ȯ_Fa ($zS%C+)AVɴ+ 6TfL7~*"ԙ$ayƼYݙ V|w"+P\fʖnyѾc&sM:$`-/seMyWV洏yR^LJhk"N$@q|DWU.3KW GER>;mEGATo ]<=oLRYٍ[^lG+͟bcv]w)FR_"-JqHq=u25qe ]Sϝ%R; E|4vB/(&<噱xLY /?T @—sR>Q|:Z" I7zrsrc:*of#ƫ:gn:ʳZȠOcݤkaWFˍ:n'VC;iݓnVoBVWYEo,s>Q΋;&i'[J%k^>v~(#H˜7t~hiG_J2fkSZ$Cw4o Zz?"!MX[TT&CQ*YXTxYWN6[At3i Z7jq /390:3X> /IV 7 H]pwuP^CsF 2_ϕ>( U`M'p4gebsHЛb9 S@kɧ8YŶP8Z s^rNw̤Io{<jP7ED+J`:5,Tύ=1%i^20*~}&ȅi5U:"/G4 SXs 6vRj:H"3來י A) @'Y0ؽ[)t{ԉ<̭@lG/X`ĉ7PbQ^@G3V$g܄ăL~J ;hH|cB09Yn2O Hd "S#a|Ybŋϋe J!$JG)U<}24&#ާGl1Y> 'a&K 3LTOҨ%T_mIr㓹tEgK9d'V%Fz=3f|~a&s6Qz#LG(-b"35+n NM'r̼Ei˔;iOʀWtD9ҩ`F[OuEX#nDic_Y+@=sJh1?( .ǏWNJ ^wn(*iL$@$@$@$@$@Rs| +Z     8w PAyGi @@pѽsC@@@Gr   ^{熞!   1@@@D =C@@ :Mc   {z  8*@@t@@@ ;7 @@pT(7!   wn   QnC@@+@@t3@@@Q4  W޹g   DGi @@z|OLbHA@@jZ . k7ar@@@NC5   ap*Z m@@á=1ԍ  \@U{@@@*Pв|>_5LLL&P    *V5Fݠ@@@1$    @@Yf   @(  D1f1"  !C@    heƈ   @@ "   @4a#   1$    @@Yf   @(  D7<1UPk0áef?WP-6@@j@Xbmà   @4 y;   $@@ `@@fb4>cG@@A"  ,@@g   @1]@@@ <@@+*HR[G V8əˌ } ΚOu7jRǝcݫ7^~[unyDcqV@@@?>4%+utߴZ=SW:)_5}`XcGȅ!\Nf@@"CF/1Q+d%nV\:(>7h4c4M?R]Z)h*sp0щ>!  P jv$@vc?tVUWgtƤv/Okהtv)t52Q}z& +օs~oJӰ O7,{Q)^vy:V:+_Q; /O^}c}s;_vԳU]3[_UZO{TY7Rۇ^/ݞn2e*~tIk2 Jzhpj?Kx@@@@LVc&ۺt2V7MHC?n:D_C`Guz7f(wugmǾ=ujf R_;sQ@?oѱ"7ӝˍ<Ό9 ^zTxjK, Pi~޼˄zJQ!6@@Xj=դPmRy.,YS?Cg%ViDl{R.^ڲ?϶k}*]Y]4wKj`uZkģ#1ɬ(f(<'2-2<ѬHLnA@@h6lWM+MNܭ]K^U31ޭ=2u[^2C;,bu~)iBfѮoY =l^Q`¥}:SΨגZwDY+m3ջ睹ZZUZ[d} ղڳ]ktxWS67ߡE˷Wjxy"iyuS;9:n݋_?Y?;VtN(_7Bzfʠ{ ټWEpW$V?VmtQ{v֛ e(R@@j@ &=Rs=Hut>#h-5-F9ꞔ߿Vܛj^<4pY *\CD&VL %m'$gjB?]!:vqh`IDATuÔչM8ls-U/lR;ۏQjxhUW'YlWѬp=μ#-bqRtH;֍;9Qtkdw#cԩ)c^IgCy/a+ָyyUJqz݅5Ɗ72)ӱ,;Ŵ褑O?d뉩fS`1OIڳ.S7//S^Ԙ}.@@@v xrrr*jH UC: @gJJejS e; cc+Rzv~sbUL|O@@@(,,rdaĴ@@@DWN B@@ :oN   +:  8/@@tޜ@@@W ]9-t @@p^9-"   rZ   ysZD@@\)@@t)@@@y洈  RiS   Di@@pѕB@@@Λ"   J+N!   7E@@@DWN B@@ :oN   +*,, >   @ EP@@@ /1@@@ Q4 @@r:C@@HEP@@@ /1@@@ Q4 @@''@IENDB`graphql-ruby-2.2.17/guides/operation_store/client_workflow.md000066400000000000000000000057251476434635200244750ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Client Workflow desc: Add clients to the system, then sync their operations with the database. index: 4 pro: true --- To use persisted queries with your client application, you must: - Set up `OperationStore`, as described in {% internal_link "Getting Started","/operation_store/getting_started" %} - [Add the client](#add-a-client) to the system - [Sync operations](#syncing) from the client to the server - [Send `params[:operationId]`](#client-usage) from the client app This documentation also touches on {% internal_link "graphql-ruby-client sync", "/javascript_client/sync" %}, a JavaScript client library for using `OperationStore`. ## Add a Client Clients are registered via {% internal_link "the dashboard","/operation_store/getting_started#add-routes" %}: {{ "/operation_store/add_a_client.png" | link_to_img:"Add a Client for Persisted Queries" }} A default `secret` is provided for you, but you can also enter your own. The `secret` is used for {% internal_link "HMAC authentication", "/operation_store/access_control" %}. (Are you interested in a Ruby API for this? Please {% open_an_issue "OperationStore Ruby API" %} or email `support@graphql.pro`.) ## Syncing Once a client is registered, it can push queries to the server via {% internal_link "the Sync API","/operation_store/getting_started#add-routes" %}. The easiest way to sync is with `graphql-ruby-client sync`, a command-line tool written in JavaScript ({% internal_link "Sync Guide", "/javascript_client/sync" %}) In short, it: - Finds GraphQL queries from `.graphql` files or `relay-compiler` output in the provided `--path` - Adds an {% internal_link "Authentication header","/operation_store/access_control" %} based on the provided `--client` and `--secret` - Sends the operations to the provided `--url` - Generates a JavaScript module into the provided `--outfile` For example: {{ "/operation_store/sync_example.png" | link_to_img:"OperationStore client sync" }} For help syncing in another language, you can take inspiration from the [JavaScript implementation](https://github.com/rmosolgo/graphql-ruby/tree/master/javascript_client), {% open_an_issue "Implementing operation sync in another language" %}, or email `support@graphql.pro`. ## Client Usage See the {% internal_link "Sync Guide", "/javascript_client/sync" %} for using OperationStore with Relay Modern, Apollo 1.x, Apollo Link, or plain JavaScript. To run stored operations from another client, send a param called `operationId` which is composed of: ```ruby { # ... operationId: "my-relay-app/ce79aa2784fc..." # ^ client id / ^ operation id } ``` The server will use those values to fetch an operation from the database. ### Next Steps Learn more about `OperationStore`'s {% internal_link "authentication", "/operation_store/access_control" %} or read some tips for {% internal_link "server management","/operation_store/server_management" %}. graphql-ruby-2.2.17/guides/operation_store/getting_started.md000066400000000000000000000105071476434635200244460ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Getting Started desc: Add GraphQL::Pro::OperationStore to your app index: 1 pro: true --- To use `GraphQL::Pro::OperationStore` with your app, follow these steps: - [Check the dependencies](#dependencies) to make sure `OperationStore` is supported - [Prepare the database](#prepare-the-database) for `OperationStore`'s data - [Add `OperationStore`](#add-operationstore) to your GraphQL schema - [Add routes](#add-routes) for the Dashboard and sync API - [Update your controller](#update-the-controller) to support persisted queries - {% internal_link "Add a client","/operation_store/client_workflow" %} to start syncing queries ## Dependencies `OperationStore` requires two gems in your application environment: - {% internal_link "ActiveRecord", "/operation_store/active_record_backend" %} or {% internal_link "Redis", "/operation_store/redis_backend" %} for persistence. (Using another ORM or backend? Please {% open_an_issue "Backend support request for OperationStore" %} to request support!) - `Rack`: to serve the Dashboard and Sync API. (In Rails, this is provided by `config/routes.rb`.) These are bundled with Rails by default. ## Prepare the Database If you're going to store data with ActiveRecord, {% internal_link "migrate the database", "/operation_store/active_record_backend" %} to prepare tables for it. ## Add `OperationStore` To hook up the storage to your schema, add the plugin: ```ruby class MySchema < GraphQL::Schema # Add it _after_ other tracing-related features, for example: # use GraphQL::Tracing::DataDogTracing # ... use GraphQL::Pro::OperationStore end ``` Make sure to add this feature _after_ other {% internal_link "Tracing", "/queries/tracing" %}-based features so that those other features will have access to the loaded query string. Otherwise, you may get `"No query string was present"` errors. By default, it uses `ActiveRecord`. It also accepts: - `redis:`, for using a {% internal_link "Redis backend", "/operation_store/redis_backend" %}; OR - `backend_class:`, for implementing custom persistence. Also, you can disable updates to "last used at" with `default_touch_last_used_at: false`. (This can also be configured per-query with `context[:operation_store_touch_last_used_at] = true|false`.) ## Add Routes To use `OperationStore`, add two routes to your app: ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Add the Dashboard # TODO: authorize, see the dashboard guide mount MySchema.dashboard, at: "/graphql/dashboard" # Add the Sync API (authorization built-in) mount MySchema.operation_store_sync, at: "/graphql/sync" end ``` `MySchema.operation_store_sync` receives pushes from clients. See {% internal_link "Client Workflow","/operation_store/client_workflow" %} for more info on how this endpoint is used. `MySchema.dashboard` includes a web view to the `OperationStore`, visible at `/graphql/dashboard`. See the {% internal_link "Dashboard guide", "/pro/dashboard" %} for more details, including authorization. {{ "/operation_store/graphql_ui.png" | link_to_img:"GraphQL Persisted Operations Dashboard" }} `operation_store_sync` and `dashboard` are both Rack apps, so you can mount them in Rails, Sinatra, or any other Rack app. __Alternatively__, you can configure the routes to load your schema lazily, during the first request: ```ruby # Provide the fully-qualified class name of your schema: lazy_routes = GraphQL::Pro::Routes::Lazy.new("MySchema") mount lazy_routes.dashboard, at: "/graphql/dashboard" mount lazy_routes.operation_store_sync, at: "/graphql/sync" ``` ## Update the Controller Add `operation_id:` to your GraphQL context: ```ruby # app/controllers/graphql_controller.rb context = { # Relay / Apollo 1.x: operation_id: params[:operationId] # Or, Apollo Link: # operation_id: params[:extensions][:operationId] } MySchema.execute( # ... context: context, ) ``` `OperationStore` will use `operation_id` to fetch the operation from the database. See {% internal_link "Server Management","/operation_store/server_management" %} for details about rejecting GraphQL from `params[:query]`. ## Next Steps Sync your operations with the {% internal_link "Client Workflow","/operation_store/client_workflow" %}. graphql-ruby-2.2.17/guides/operation_store/graphql_ui.png000066400000000000000000001423071476434635200236020ustar00rootroot00000000000000PNG  IHDRE"@iCCPICC Profile(c``H,(aa``+) rwRR```\\TQk .Ȭg~~|AKoZ0գd  Jl_( {a `5!A@ [ 9#1~d$!#I)JR+J@s~AeQfzF#0RD``.߇S{}Ep0~c)N3630Ns8˚ {100b`8 `pBiTXtXML:com.adobe.xmp 1018 207 v@IDATx|E_I C* U `W b{Q;I!|Lcos-#y۝ݾu25lPH J{]KRǁ(HGocK6#   (2ʉe# @!@E?|(۾YK.%G")Kf?wH$@$@$@$@$@c;^,Y);!{@)U$s` ʕSn.gPUw& pq폒E-^0L22N+HHHHH2,jH'sAX#z&UZj(^̑cu/:KpɔHHHHHR&9O!Kr|fY;${co54wɭ;$mi=߂OSHHHHHH H~ 羢QR9_VOl&$PjPȥ3g=|@P8|9KSwɕ8HHHHH3D);CL2]D9sF.o>*XW?@:u(ǏO1WyɓG2+%5_ѣGeذe 7WYn| %uū&ZJR;&SN_`{]1rf~9zRi=rRt:-wLt =m_PRqï|d\fqIHHHHH 9|V[h|('NݻwI@@f)YԭSWg2sO-Z*ޞIrʕ(EJ{xx8xV)"6lL4Q1(QBie2ԪUKe&QQQN#̙SYz)٣GZ@~"lUUa ɩyKΉ ;rVQ̯|^9u2~W q :% eTD0.\\GLL[Nf'#'X͜qY.'--ԿO{2HPP^t5kVwS@HHH CI 7^S+L: Ztzf[vTw:H/o~xVrG(ޟ}tE&O,ׯwlQ /_nݻ&>o޼_i^L`xH! $ޢsOܫ^UpóPBTyCTN:B(0>}q9r t k֬]vɝw8˗͛ɒŧG. ΞNz97nkN-XDž#߆uI|BQ߲e߿߱ΗjUk͒ #|韜6*VRFp6_wֲbr9z쨣˭ZKDaYfcHHHH|r>@?!v%֡{vC#'?b)ޱ֯SQ&WG L6'ݶd+ ?>Ӊ't:H>}qGL"HH-cşUU&7J>;i$ݻ/-[Ԝ&OEd" (7@z $wܲ\)ӧO˖ e^0,qW7Cv-7mLo3 u"($@$@$@$p3 vY>QF=>TB JiOHe5ҠAC`VM>'CwFѣ0ҿO>UxJ tRڪe+fۡj DDD8[K3sFmxxc _tbȑ#dOc0~ͬq P=ua>B'X)2(U+I[`-ߌ֞ҽʮqSTfݺuZ)ѣ#^|(?f6m#f?*Wl_ǵ c!*_o>m)ŋQQnu/`p&M3xbA-ZʣJiG3gȪ+ xx *˔wB~i+ٻGEԑo>~> [n0"@!uAFެY3ߵ[mkתرtupCYzW ֹܱPpΈ'֭[҆ԃ6Hٲekn#!nE۵} kYZ~_dHJٿp>v[4}u6#hA}vAz mHmFฐ;loIuZJΝ;ehѢWtϞ=h";7V7kLfϙ夘H#կSN5zB7VRUVhk`!+TQìYZvQFMtzYXh}ھ}:uJE 8 7#* k:ӬX\_e˔URY}ծUGUm۶Jn njJ9w>H|z0ǍڲuJا 8`c^fS1HHH2<}SQjߞ JQ6PWQAyHX2J5xlYp Y}8^x`xlIyIL%7lh5Vj<3$Ay!TRh/7[lC:)fRCJ( [P(RF *6m/" !߸q2B R)P?oV 9s0 `?Pw_JP sN`{??Hd# b FҥKk … Qm: %⊋/###$ZM$FE`^^*J*9y?   Iѷ{fvx):( >y_Gp+vB¤ɓ9.'w(fBR~<e̘J_dZ92 mRQ]:>(>BAZ7 (DԈ2h F߾ݴFv<@BqCjU`/RᇂO RǕbY9y*4e{I_ !?^))XB*o<=4`kz2^?Qa6(FWe ^kA(xq} ]w3܏8!0^6@tBGFE8qʩicUD[x%l6ݦu[]K>m'W,DB aB$@$@$@ JM&%K:6թ]GvBvոQci/P,Сh2.̝;ݱw^퍄 xb\ <P@ I2}xjowi]יx8Dpիͷp6XWV]o6Ʀy]Khe(. xl'ʷ]>CU.V^GFMi@gdlà LυݮHT|''OUmFSouxž33 )THWZ3m):wtXxq%GU7$z@fE J`.Ӯ]VpFIqjAٷ9L͗R9xOUdstU}5cnH@̛7OE(f3ZL];5k^.o`_NB1/Upv?pk7 ϼULմi^O8׏T:GNly|_YĪ.U(# <4)K(;z mq7 쭫c{:~<ɱR-*F}{*H?0{0+w[~cqHHH IяR9h ]ɛP9ȨF^Nŋ@h9t8aEc33Ͼ| tZk1FH󱮃B|r9Pp/%Քˢ (^ RBJHX8857eu-1 qQS"r3f = @#弐 4_!vXB{L)V!nh0+qUI7}}E (P<ʥ1 "r*xsu|vIiW\S׊&c=E qjf(+QԎ0/ 8xlgkV|*rAȁi^m:&ڸdX/~N)O \}v0< w{0n.؎HHH Iʃb:0C!=MQ1xdwb]ܳGO{l]ٽi5ou[b׬]}< c?árRUgWۓpP(oͶﻨ 5iDڕS,*5wR $/; A}rn!==_^]%Dy|޵W8L#/u!>T r1$;#LJ><~5kj —1λx:?Pӧ'PlG;w~ ~2m"I0AA87(O{+' c`b#!?jx4^Z߆*`JUQ8(ˆ@GaݻW"a|>TϝzDakq&#{}v0!5a;|׏7G ow%ƈkb$*㜠̣=% aiu`?vkN}?5P+8V/8"WMxq`XZ@!   ;B0=~ߴA%Liŗ_8(`߈ao7~Gw] S!oV,+rzmC#>7J$J8|MqgVF,ٳPRD+P?:l9D>= Hǎo 3dCq6;<#ٕ+WK.Vb <-[sRaPWX!˗/>8Nk-CR #(c0cj=!c`3}׭[ש.ItL뇙 `s.qY/<."3,y:aeQ+WЊru3=6!VB? -ZvkurL">.=!_N:˰dϚ՞E>}ʀbv"Lh3;vtѯ>TޠJNB"Jfd9d޻z})qt=PIxxiklǃotx@Ng$K?_JRݲtn}beJL O> ~j@8$Th~"rCU:o +^xGnfeåȎ5 ߵtxG>qu;(Vz:32W2K &R%nTQa*SRy5H '6l Gw\j#E=Mߦ/r@9tp_zxᱴ"y2dSHHHH HVO6=g+崰RޣT=׍(y^])J?rʹ?,GI53{ej œ$JMwJyp*/1gsAyW+~lTWԄ޺@_bH mS·HiXHHHB M(t}RN}nCTI;55+9%JYU}D["vVʽ C3S.LJ/ظ;)Yrd~o}HHHHH:4u<~$[X]9?O2%H ^Yʦ/wvqIHHHHH U*>.~mګy _S i'A$@$@$@$@$@7I?{^'o]F6M?wL$@$@$@$@$@$ϝq_$@$@$@$@$@$@$@7˗ _ P+nHHHHHHn,*7/G'      W XTo,_N$@$@$@$@$@$@~%@E߯3      2EDD\$@$@$@$@$@$@$@"@Hs?$@$@$@$@$@$@$T       / }?@.HHHHHHH_4C$@$@$@$@$@$@~ @E       *" PdHHHHHHEHs?$@$@$@$@$@$@$T       / }?@.HHHHHHH_4C$@$@$@$@$@$@~ @E       ,C$@$@$@$@$@$@e/b 5C"_zB wyS  '%/_KH*7~gC!pb&YQ}J$@$@$@$@$ O%?ʉ??\򠢟xA$@$@$@$@$@$@THHHHHHH '{ @%#{ .x<K.ylg߈~gIHHHHHH Iꫯq_'uE>|4lPJ.-jՒ%J-"ƍ+W$OUծ][kҤO粟3gΔrI۶m<}p; V>MWX1}yu:'NHNdRdIy@r!0a 4H%K:wtYo.P^ Kʼy矗'jCݼ1b={V6l s̑6mڸi$@$@$@$@$@$@$\׾=]w%˻ᆱ~X^~e6<32m4f|ZWs~ر2j(8pvOo^pBY]VAE1n#     Ho| wuCG^sRO?Tr)SŋzWJ|ldȐ!zԩS]?CoѡuȀ "mIJČo^:uF$U6qiz&U^|E.q:+$Iu|;ʼ jm8lVÌqU}K1n+/66iH/O!   'z+R/lvRĞ:jt'Eǎ:Ν߼y^~G B}$sr뭷(G}$!!!2x`A uIc+e CTb?+=gr;+RIYL#m6͕f*dݏ$`MjR(t[+#H]C= \kxWZwZPSR׮];Rr.qx:BZxWx衇?"8V{[nn y$@$@$@c] NrP9 q1gMK9P5թ.pqeYr!ꘔ{Tzv%oU?$sHzH<˞=e/Nm&G-;o.>)Nxrxx͎uf5'E}/ʑcP ^ۣG(-[U'>[2c q5u(֊~"rɉU%1y۳_j}=TrU*'K2%g]Psb^Hr'CCi3C@nOJ6@ glYf>)Z-Xhأxt;ugkއ }myo.TUO0{Ow 2*>uB}z;C5j(/(תU+0`;SKr1] rxwtToY&FKݝԫǞ<%PrzV)zmyٲJ1\S.|~^396(UBto/5,Su{GU*HgH-3c&DDGG01Ί+t'&-Ǐ)B0 Cf!~=_IHHH(98_)ѳnvj9xVYu0I\%qv?(.|~R ~2i$&Y9xȆԩSMݻ;) fCʕu-[*kR8:'b;vq^@,޼A~ e[לKke+^, ˊnwR汾gϲo{sA$AÿFIX沪 9rn I%:(e JfusKE6Q4\P[>x .78OH_H 4o uG*!di6o5 Νߐ2Oeo;9_65LJ WO-ꪏ> eK)(}zj]  mڵg- =2zE144TZlpX2fJѣx(PNb fb=ʀ1xL?져Ca=>c< Rm * Cpm/^\ 0XގWZp~qwkDG(,0 N1M8!0NRkLJ4Ha4h9nHHHC^kȖOGƛ_am(kPevY֨u#֊oɅ#Jeѽ%ןg־llS?( |=39r>b5gٝ *s:ޫHG;`8i^}VE1CBj$[= CּXI/` _ju# iB{NĒwv\hwN XЂ ceqߧ FhNH+ڸUm{pͷ${@PU5w/xo%:#G_|QzJ Ppas}N-0M땋Y)ft%qgI`W$0$oIlC֪*sd J.hGH^BZŷYE=!ĒS鵧ŪZ~ agJ_S{cr;OJyh@)ҭ^PgT|V751\Q win,LY,'?s/2(={͑ڂ(g:2ۼye|.4ع[.]tژsBJEժU(. 3yEQbEFL/zF_- ?3&_IHHHuNag)eP![6Vy5"irx#BX=gtP3gQ:oG^$ yDv|6El偮sm.w y*9QtC*}CϚ]}3u ªAo{CS'7em" fֲH.av%rVq[b|'7W!Ů7a6Ke$N.WJ>aJ1LQ Gev{d_(w;",Lۤ$#~nCMsilzp[_}|'N&y̘1.1ʑRTYOMb'};9G $VmixOn Жy0 @ɇm֏qaU.D} uhԥmt|Pb&u "&'M然06dU:A(mP/L>N[B9 @i+hHXc|Xg?(8|DؕTcЍR? XC<k?'F|X:5 Ezf7c#n,ab"G$@$@$@>@8drm{\i@jZ?hcCe,TP!a-0xߍUjyQ;  u ǯEyn8E:ܪCu ͓U+HzG%11MRSzUp> F|(s^ܩ Rnmo/)>ʄaMh^^lxH٤t㖈el+ncy6x`B(+ox݉oxƍ=P$\ <xVwy5)ġ={3NJwe=*"A[wʵ+|.F[+KU>%UTQYvW7̶Z Gɖw>(uc<*Zs-ex}5F{`XcU%;z:NF#̟0]bKWFEW:   0BJgV9#r =< *Uȕؖu7Rnׯ4 ׭y]9PWs^M釼s(F2eVX!_׵^$S@2ou>fSλzGGT̑髜U7M+(ʒ/"%?qiz7xQQ*PRѶ % P8ٳihP|(D!=D7߸P4;w: eX/(0H0Xgn7fZEfX)uIHHH %˦J߻7PcVN!}ߟ\*P7t*@NB3pllȻ7]S+*~aw8^sz[0$l]<дAOo_o5r:?KPE!0/>u>y+ {T=F) {}j?#pSĤ.myMG;}wCpYx۷oF> !2OoGjCX>cQeӔSAp ^{ੳ+] %U1j $OTTs Z8,ޫn*SU_3ES]YFo:+,Uq Jτģ(ƗJJnCszvY<Պ` */?\Y^=jTVzP/ٺX=TX TE6cs(&HksXڿuTXrJ!/sSؾjL({DW{ah ByTAѣuwT7('G%yTG$ mmXv#ջm4' ء~ o#hs j> *蟉:vfWFb%/D= Fpln:d?/Lj ;@C Hƻny(܇׽SjtTǏQԳ= \Mod/<|<{PE]U.Hmnן\'C@0qgUUT2yk^'dY]')w+"v:R oۃTx)ܮ^w本{CP{S3Vgc[g&q〚{όO)5fpc.x1&|`ڱ'x"AU~<^}(* ?dD `j-ڵ6L`UQӏ8gc= 2Ic< bUPoӛ%HXs{YgMʨ 7ےt+yO)||I(go{ /z{\!(_S[)rgN"4o$P} dɕüub3/hա/ennzv^}NJX@EuWP@1@~3GY>gϞLe=LevPqB`ݚ@_Dՠ^_|Ao޽`۷VM#='9PM}mqV0<°"ɷ/91U^Qf:,゘Z>?#\S˝Uͥ :$ʙ A{A<{D+APHr MwCpؠfzPԧx#bQ7ʲu0cs* 3pΜ9b{;?_Ӵb\-q C>O\:   ieSzpNeG&vgp]<#e3+U-[#ԵbOmN{%)ih^x4Z! :VP7oy;7}%+šr^EF[T{)|T1t4@D:*WQ=:Z]"{ UʹNZVoݒN}R{36`L)Ȧұo<CȽ{$2LJ?"}R9 d\eUmAM?#sf]zy5¦Ι2i06hɰ<:      H*$@$@$@$@$@$@$ POݟHHHHHHE~p1 nTSã#      D(\lL$@$@$@$@$@$@HHHHHHH Qxk~zoMHHHHHHH^O{G'LlD$@$@$@$@$@$@i9(IHHHHHH'T}F$@$@$@$@$@$@$6POHHHHHH|"@E'LlD$@$@$@$@$@$@iתi4x$@$@$@$@$@$@ًO%4:Y斜u\7wF$@$@$@$@$@$濎U<5KN$wEiA @ O~zPi+Y2rH8q}5ߓ@#Knĉ mӦMO8S{. &uAɚ5kmnj#xH:]v.+}z|b;R3\3fV $8TX=5kٲev\A$@$p-߹[}_Ǚ9sI\\tաr 8={/ň0vm'uիرc}v {7~$ $K7'ɓ'["\qvk[.,\P+&tkݶQ)RĺJ/r&\A$@i@b[SS{83y}VJC8$coײnO_K~֬YҹsgiԨ ڀB)I 9?\ rEW̺dWرl޼Y!ߝ argF?,ժU]믿JXXz]fʹ{ڒWvmcU`o?cgϞp= hxkӦ4o\`ڳg*U-\gWXQYneɖ-tAC4u;֭+=}97 @!wʕ+H3A׮]]DDnwm޼yw5*E5Mėq- ׯc6mTn*7h@8t萼;?rOZF" Nw}'b-e<C<=S̛7O Bٳf*~mp;K.9r = O\?.Æ cgɒEB3"/܌ո-B1itȗSְ=)"w (ًۓ4x*l~``<Ɇ ܞ҈#8=*S}i(믽ކgyFKի|KM0z)=ƻᆱ?D?HP?~pC'ٶm9rD79|Xϟ/Gv ~w`uK! HVN#?f͚駟:)<_xF'b ]30YJq~HB~> 0;ޤ% gԩ#L7nFu81F@@N7Yx(P@ׯeS#SLZ釣} j:~t 9k>g$אַ$56H?7ycҥ͎eoH%&|!-YD-Zp4Ǘ?AqȐ{NnUJ?x`ĻKK@!W)kpzsV̙3:Z~ezޠ=z8_\x 4תnHH QzWTIBCCoAO^Μ9~{5M^uC3{ p@8- S ҁz.P[ti2IgTQ&bdɒҿ3EHH3<\4 x.^nF܀(\ > ;DO?zjӦM z02"gL-@ԁ1M`_SX綒|iRo8zJ2%ּTy Χ 9kT4P2e5/ 'TۥH@`9_i( ͯF̘''M}+ȚȩۥPR${xf  _Yz=h%E+9֩t(l *8Bp*8_{u X VhA¿֮]kVy|J>KC4YUla?VM4DPHH >|Z ~ر#;1%}=|/C|G7o"p\  4(DA9"bG3E9q aD ܃0pAg^TȮ]F2~W6D]C5c5c@^=:I-rfnwIz{юe7c_sȋRGe#g/2#~}J?!1G$[Pr9N`\0vy*'e=J5WCⱗLC<{4fu=]rdR)u_V^)=vg K^VH`REy%[P!m@$͖Sq" ʸX|w/(cܴM+ß]rm_$ @ Lߕn McxTF=T㇒Qk}B#h6XG!y HO|nC>b \*xGb|7**JG"qĉګhM!HЀ+H jS)bwpA5Rw%ܠl#^t믿tm,$=!{;LQiF.YJ 8R_1WDcxN޽sQB!G;̵bg 8)Io?a0:ꂓ (KHc;6afBn"h0C*-"^ylX 0&~Y= C%c9)G-'N'}lj me݀1"{Vֱ2S@|>c*Ju?<7ÛV=OR%M}M}sG2kX>nr܄r)!9puB񣢿3@›XQ!MUH`# 1E<:?p'P yiW H݊V]&CARO(/mR\9 Df *#ėqP||Y2g3q'/u\Θg`[pM^2_Dag O:U?8xE<3 Y'qg X8F܃&]^H@: o({TEDEˈ}NPpi3x2yq&>W iBs,&V9P%zF۶-y'7lwPX~Ow9T| RQÀ%sp6B,?7Q<]`Mj<갊#LӅULx4,vpF{3ڙ~ S}/Cif2jv/p]k ۬Wvwb6  ?>@Z xztcG$@i@b[ egȐ!<\DB;ҝ xnn 1϶ sM/_у_o"n݆e< lFBd"r}BvfarHbDʾxw_)l30h.룏>Say#x6tBZ \s?H(0 H׮]q>G̈s:32"&Njʔ):U݁5aD=zc6%w>84e>r󭂜O"?z]U׷W}IY ׬j觗m1nfuAHh::\Pf9[|Tׯ퇲z[ڻ_u^w3_3쩝/`%ʓ𥟯m067c`ۑ@r _}Lj}k_Ʊ2 un|gS5Ex^Ft#tc:I8}y=%֬OkܹIE`{qU?"Evkdy{]UL8{ד@r!̾$@rx o7'/x$c=IJ>SAmܧZXG!E Kw Sx~j;A d$T3ҧs%     H'H$@$@$@$@$@$PH6ϕHHHHHH ?b Yk7p8ůU}r @"2K%oo [[5QNIgǞ$@$@$@$@$@$@$POu HHHHHHN~ٱ' :TSG"      tvI$@$@$@$@$@$@@wVonniEDE.B.QE@)|,{9g/0sfv<9R*O,t8>, v:|{8;u8d᠚gɔ9dΖ-]ݹSvm"9JWiپj(ZX+̖LYHd| K,[-|K3[xR7J<$K^=ٵqd˟^]6d$ϔ{dז-@ 6Μ#GmRʋb櫽vɞm;5_Sq21F`sFɖ7dd߇oo̙3?Om &s/_>ɒ%K|Qk 6HBhol1^f˕+Wxz@(PXQ3U-ƶm$Ŭs`xҁ ɥC`ʕҫW/;wn,^zŊ .c9K;OjU۟ʏ%xLxپ| _}_y3YC{dF͢xo?JZͅuFSgJ:-e_2J3|;7͙/6=O&?zwa36;'nCqcq-dT7eإy %KNÐ&i9cʏIK{6ɜ#[ ҳT=S\t5nT]嗪M"}07w:۟\?G^=0#G'1YO^O_5_4~ϐ?ȻnYvy<>`?"Y1/ɒ= p8USن?cbN%_| ~iV9Z׬)ϽvfӦ+nyiC.l,\;!~e]~[™/}F,]O(m Ӊ/,1X|rWǤOJ*%{Y;ulNu/[L.R)\{Z?e{9GsdI،R׺u(+WN|dk;wT2 aei~_oɬYAOO:uHɒ%xҨQ#9rh_=;ըQCn馘{ǎEq߳)UF!mԡ6}η?o\GGƎc1]D C 'x2w}zj\|۷7zygyƻSz|**^|U*1=rb3ڤcv^W$'} ?ZƟvƚb/G zE73.U_qg@'ż"bf&".?' 'gvZ0mN)z6Ⱦ2oy3i~M|W5=Aq?cjX/g5eJ*SN9E+oIvy^zN'k4`?C9m' y5/"UT}?,UVSO=U¯{ҬY3eNa=n8Z1J]\s 6lT\YxfQk׮1y4X,詛/^,vfrWFy8or 7(;&u2m4)[l*c|X/֭s9ÔX֬]VƏ/gSZ֗^zI۾XeKm~gekiK@/BO?TyI"EԞ2m'5kg;?s2gꫯ4}ժUo=ܓf]b;Rn]͛0>`*nGAiA+[|+PTZS p%BPb6[K*\FNCYiSR%~9g_`__y:ڐ9[V)uVˤϳ\[=`2XWFȝ/LJIּyB֗i2g]=d|M)]sP W޷`Syyʞo-o1e&}H@Ms.) :>B 3kEc \l_Z\t4MZ&$S;+c5w./fԶ׸l@QVfZj1={2RpzKyQag Qcv. 'E]> ڵH{77Z}0X$"4\wuʸ`91xZ5I&I'oCԊP|a|^yWucy/1CZwqRfMfQ߈#ªUq2?VH۷ :mWRG}]}4YR]ۖ.np|cɺyta<Q̅1M7Oh0Ŝ:Nd"'Si>tx>8h f0#[*P3Qz ֲeK`/ ߮%,hR:R^>H_}{?0D0.u?,f͕ObRޛ5]z/S-fC.AΙ8Pr(svi˝~WVmKR֮ϛ"_{Žo=L)$KF0Yysep Xg!^A=`Pfy 3}&?jY; X5} NsNi~ru2 |*w\Fo~,"a^pBs52|K[ҢwW)HN`>:yr߿zaq! Oʏkl{}i!_L BX4.9_t7{?W Vï#8/D`_J!cQuK[fZo1dLKjc@9 de&_|vS7eʔW4h20h=Xۄ06m*cƌ?^aO0P9DEӏR&hA}>4ɟoFt颂`P)̀ 7K +vΑ{XGkz _{LvZ"hWe L5͟юcm`η)l ?oeA$h`QPB жm2>=U]~Ee]&3fP!;-:>nQNG<tQ04h0i&,v?tQAmͺj8ev`UOJ}1G``DcOkG?y](қ|I֌M5]?uFԯ^O׼r}*8B0,&z.@0{#:خ5}ًV`]C\vDQ jLO|4w1Ύ{>evo3"я= d2tQ*4R\#T;J_}FktxT~kzzRekA3D>~2?G*2 'g9v/PV>=w)>_6^\]7ǜy0~`@8Rt !\c|`4}/>A 9Yny#/AaڿR DGaw)!F-A/ޢ cn'i}3oZ%N;EL \ ;A/_a'_K<#W*$揍a[ argkoy?0' x1D+RHJ>xuk?0?ВL >p]6;foQ[yqJh!͎L=>9s~,cy ʵ^w$#㈛/nʂD?' uӏ+1 }00'r/ocTRW\z]ha3{ $ןמM ba![t&~Kٲe|* l^w<8hFM?R6p{d MhYxh_4Oh?pah4hb`vsUm~O-y)z?}VaiY,Yh4@ۖԶM1 `}aYQx@IDAT?仆y_ AV,Z|\*\[vg ~*~jS`*,cϢܞs-07U8f?oۘ\[{D8?{_z^j뛌*X2,&CnD:{va0#Q۷Zhp5Byч0|o[w:uH~0 Ad&P|̳])/eI>4$4-sj$72 ?QniMKĜ['~X @L2գ'a-a X9 7a; ڬG0hXF!"K2jѢ.xѦԍ-pcJ4[(m;Kޕ ps@.ǘHv=P&,MKoB^O4L>9r9ᡇR聸Tpuc%xtJZk(=F" kcXIv|V /VO. E |ڴisB$  DGs )jf?.Ѧ1I#վF~Le1G%$ `8څ?-941 B',YW8Ň} II ph5\IA!m ABhVǶ9OP&~h1}.E{FA'@2ϳ??m3!>-/_cl=W̙3Fxܷ~0X7Ks.'a̭=y3Ļ{=&{ů~ѢE tKF 1w`aTLo߾gz96$Dg1{b[*^-1:{oi~8g3G֣`,,u4H;ץ]k\a_է䢅ϝ=25Z2a32^/M#.'+ gɠ??PWnO?`.+Z,E#$ܞ3O}6~l#6ϘQ?7~И~A[4e~CL 獑|)U︞lJڀP {_'zxMO:Nc0b9 ƣuZu & mFZ4 Ll"¼<@~{;O< o@'z^X`CN4A:sR#, Jm/1GQUSv/^f+Ns 9d(`r1 6?k۹~c&Z=c"/'=θ.;mE ^ T"(avKzAon덥X@?#&Kᅢ_6 ٷn?| e¶}Al:G[K.R7?-\P}us  ?"6ծ]ip(f#, 9}˽DTB݁qҰr/v?8 fa/ܷqU9ie&O#í0!j Dep b a#\rI'.ԺdzxwfޭbMW->o2hV֌&~P¢ Zuzu 9ŧes45ӐYPAh1[ǔ.\@& |`WHN`ljB.Dy&f/l.*,XzL~1 m_?wo\`j>D5eX8fc<@~h_klui`>;_˞&1n05m?["d[k?/d\j$6u5[frz?^bztό]sۣ9A3Y\4\#/,+Trl9ł?=ߕ;Ly_-ұ..)0("%Vk(M@v/JaS'~,-x& )Ͻ>M}GBToiAHxst7y*^w7ZLm|zsojp0Bڅ?B  ,LFNҟF^IדP[|sl-SgWfd @nx@12{p 9c&߼j a7͞ߋpϫ{n{= 0i#P췡z;MK3,OXZzıƲ_obݠ꼁=&[,&(Qq bBef8\l bOt{5-W{%.!MCw‚kF@k9QA$#\4va, 0QXO!%(Bp ċ˚F؃[ fwb0X0cA@<VEcua ckaGl4aZiԾO!,@駟ʦM ׅV+Qh>l xb5cǔF1W_pd{[E YL}Q ոZ?.N{x>0ŻuMp:჈OP2 z1\O+20'B9 I2B(h`BU4X^齏M@8oc$#20q1gz#s€!'FX훪XZEi7뜘N40I:D40ϬbS7&b m2eR+Oi +[nLȭi/gMkG`h`/b}2kޞ0O"ό='>lg[_P펾xF*#_v;ۚE-N/j,F! ">\Fo 菉2F 9W*3 lGH>-\v|נqv%.sT_D7_V^ `G*%O{cF""PBK^~{TjI#~6 1R7g4=ˎ=fV(i)g,SvʋonjE?@t2ygvxw3Pjςd#`rqm 6A/"|0cexK o'k\"x". ~JTogT\w*^0h!Ɲ92?S&? :kw FDQ 3׌C\{owp+%@z6xY~@<IEeeK4x#CY VOU3?["N ^a[Bgx& )^0H~-FSwun%QQbЬs" fvD5`oM]l~hy[O:Q,a@Ol=e5,1j]¤%[ѣFƶ#K߾}'~i g1Z)/օ6W0 ??,a^0͏GhɈOf n6Y8o#^a%䏥[A)# v'[7J0F吝=σ6-Bpx… ǻٙNHF0<9g2ؔD/f}HzCĢ@H~)X?f3'<zg)l f<1qD~qh݇G įfh۾b2a&:G9 llF?R5 sD݇ hgwY/ًT nzއ}*"*D럽p;6n6x\5/ߖa$þ<[7,(Rx%=%5,8?k|,h[99-ygYZx ;1?>譹`<{5j-|{3I5 -n?oLqfڟ?u^u~#x-h Wg aֿ-S_[z!Ͼ_ϹF6͝*T0<d-ʨ曵؆HVIׂU^x1mL%!+'O9?MAeΜb^nz MdQMS$؏D3AXMZ: *q2/U6e&M`nzM;ڏ=΃̅3?(wIz/dO^zVG'OKʹ+ٲeS&g2ڵgØg͚Uxŋ$J]k׮/X"Ehwql߾=DvZ;/^ȴRuUϼ4{2sLV<`/w}','sZjɰaEӜiީlٲrWٳ/4j({[1j{%/AF:gĥw=S3RV{Sf}ι˧;1nθ4e$|5k6o0e)GM\GMi3SV]gÍj6X>eZ5mȱx#Ja<k&NIГJyW;RnVe﯌eaaƌ!;MaNt '{zF},)SNղC (uޠ`㣏>t\+sa0\p)^ziO/BH'w5ר A10s `f842Z /?q#2͟ t϶ck&\2_;KO&~q| 'ˇ%Jb:_0r-SK.Ybv-o4Ȗ/ݱ#MMїR )B_m{l_F1GcɢfY$覹 d7_7iG?]M?/U cVd|VRf±fST˓Y-e~l_z|Eb:%06@={\#wݫY%7hY3M[Om2g*[,\ SX18Rmu#`>b~SใS s{D,*_FJ *V'd/T0Qq2|mv؀'l.}J6͋ԑ #bbԸ ՗kQGK'YA'9bO`>?"]P_;A˻;Jðu]% S,?Һu܅;ThQ=&{RfM1Z㘢||1Y=p-ȗ_~)O>d=E|#G38_D2sLLzRJA5J SPy.C bKBF=0%BUc7`Y0X=@>]wmZf>%_l !դW:Iϑ 4L`=48 339N0NH޳IG ꎘ&W 8k,u h}F~y#xL*Ӻwu'× a+_~ '\eSOy<0=rϑ#.Ay7&Bc;aYfb4ʈpׯ__𻇂9q`*~vZy+HQ tEv]Y+ b[o%0~"!tb,=Z }e:Zl)e_czBUTQ%cY!#F`!p8 \4Iow<)O>_h\:s)SRk+~RCBԺ&ٮxeh1Ϙ' „iן\tE1 1@xhd=㫮ƴ`%`U/n[= C}YMBh_kHoi+W `^w M\'X3p hLeҤIzp1!{Q7ިyޑ<Td7gaEF5e!Xq6mhCaz7fB0nIʗ/oC!p$@ ɩ|TMGӁ$QW=Q1ck[?<4k7`[vhwxVIz?3&-%oqbDr^Ζ}jlS3V' 5ǒ똢9nh [4{FsH`/0>0RМD1Q'Ae`P$ٲyK0*h1!ۛSm%wKfmXG ׮00] 4n'WnGuj;'kr%ژ򗜣x0&>u-YpL~{Q3Օ ]!ڂ BTB0O`,:iUoJ~=*F GgE'\P<؏2/FNα=1n| 悱z*_Nqa)o| u1Q!L?n3%Fˇ~( _X11+wtҪi2˗/Wfm6̟(u:T#%'R|2AG+^=+;w-[0~jQ>QÞWB|Z%-z}]}'{8r!GફJ|'$q}_&Xaf3#C!po^Zecʼd_d+WL / 5 d ;O,L-O|Ok0aZz9{sܦq?g c@.wߤL~~&ߖE@$I4mGˏ:0}m > 'Tj%=?+!>0^}J6fC0EXe W=Kcwjth sliXUɦS0izS!/:J*7<\U3O >6fɨ8 m i1ll]L5:1ovy= \ЗqLBhǟy晘7bduc޼yseo9Nl')^]h֭B 1=x/m۶z p[ sjl6=h3BH?ύI$.wB2{a@ C`}K~&km5ޱnCHuY\9&\l^iuF+, `$\5Rj,m&@{^zQYԞ gd B揅1A኿ s }^_ۃ8AXha]㎶vKC*b%_Bwγ7cz5b[!0cE\a̸bFh!X/Of1i30H1V8&/  c%O};֮5 JgE Ko "~fDhM:N4 mNڠQ%B}A?&NIp5 hlF(DOlxҠ4 e.2qJ֧0BVb4bOķ"!Vdj%UXa<>Zx #A0Gnjϴ~a/lqimA\-%wwyml1=Zwb `"_Uß.9FPZ% #p@`x{N:aD(c#@` wXѷe&T [%NB޽"jb>_lkC!pd"MY:ѩai|/Ps.aAŠL eJv!WEsIxˇ2@[t0 a-f[MCC5̙0cm!%0)DCG[4Ky X`~]7s0•_5>&F>HgI6%rÕ5G $4CkT1AS%bD|cjl502|i|˔P/;T3A*ҰP2{I4Abv/Z|~BvZ!H-Z@{h?ŞGڡ`y. xX@`^wM5~Ho.C!88`FLmnUc&(#BM<(CERmᰛЅ=7n(Qy(Qha ǖr .T3vLmDu7z0ho65+Q"?BCˎa駟w'=kwܡ?5Ҡx] >;~uEy eAz٘_}պMwRoN>@CM* (#CF (D>t,jZJ1׶Fa$^h\Ld՚h6)BTHYw/*nj߹=~ ~,~DMf fb[&ea3$gȖh# dI¬"{9[;%uňzO^LaBX =!Lͷ->P62T4kw+DNk{a `:0`YBs G:q=z>ލ׺ƘD7y4{=4q" []9cbѝeg5{m __t sVzb aP ؾ@oav~ȋ ;ȘRLWF駭K+z߳Q(bIcn >3Uh1nd)Z|qga V}'{l7c`mc)্@^Zk9CG(?u12T5+b臟q^4,Z~7>41ƘSqߺmSbAvذB(sye^ѬP(2Ovs?ѯ=p~8" ㏭0s9F``NsIG `w~ٳm9& u_}?unҵj:7&{ʀ„E\`v팠tW _[bDfFZ-f{c<|Fx)gq5CDi,;Hm\f) fp&X09U,w5ۚʇb)ɶ3n]mr"<40a~&@92Xl7oqa0nb7Ļ~+V0A|g9ׇ߬:tqG~zh!HwQ-Չ:ة 6tIDATW\O f'pu8G_Bя !p8|NfB~ κ=QO JY!p8nt{{C!p8C!8觵K=€pp8C!p8 M!p8C!p8xw{AC!p8C!8p]C!p8CG1G|t8C!p8 M!p8C!p8xw{AC!p8C!8?_IENDB`graphql-ruby-2.2.17/guides/operation_store/operation_index.png000066400000000000000000002147551476434635200246450ustar00rootroot00000000000000PNG  IHDR/9@iCCPICC Profile(c``H,(aa``+) rwRR` Š\\TQk .,+v*^13;f2zZ qbrAQ c\^Rb"E@G3@t{ a r)@ [' I< ؍MB}-M PZQ *23J!`d`d o7(Ɓ+c`!B,v9> +H,J;KqͽuÁ^d`{Áop]~liTXtXML:com.adobe.xmp 1016 422 J@IDATx_: @@iiPPݭXOT)A:$4mk Hk/=cy z\kKJض-[b֭V~MUSfX|yÀDD@D@D@D@D@D )pCvw#Vl3o^1ibֲ|lKG4ߐt:+" " " " " 9rsكƶ[>n^kkV]s@#At{_ЮW};Kڟ?@ j/8k.'H!+X-m =ke+׾ wJmFH~ls8O\`;B@ rwym7;|iJg/Z͂[{zm^)x𰭜<(l/" " " " "CH!bF@ߺ)(μZ~ קp^AI`{ 6@ǡlsﳵYLJ,$" " " " "^wNr#o <4xVǮ^|A= v%pQa?h6y& H˿}X~ēn~/Pxc74E>[}m\kNF@@個yz<θ| _"{y  B?}Z|g?[am<>xeKLSKPe浭7YprV1-kӪ~MvH]u?mΜ99YʕQFֳgWrm2eؼysmʔ)nݺ}vq)"EX 2ڸq͟??\=y ]_%CƤ' W.om{M[lD@D@D@D@D@D`")ζ; .ڵkO>ϻ}np~0ƣ:ږ/_aCIyJjX'> _z!sydwӧەW]Y /J`@/f7[?~-8qb6neP֊4=J5om}ӆatU?Ă/ Λa*u:´XߺuXD4/ng6-_gH6lUVYr"{as0",Q c\mZ@hѢΨv]EĪUf+W^{EW ]jM<)v]oyhKPkP~lksαI&و#Pد~оr*6|0aB7lx')7?kڷoE?d_~U]P˯TXi֫gEJT׿_][P BNI8P#Jذw28/{ </?ht_;k[7lTd8XA(~XmQIuԩS ;!ݯ_?Ղ>;%ձfrBSV^e'CZ;צT#q++dYэ"> <\)~$x+VhW\qEQ+W Ͽ!OSOj7x}{}Y#e]B8#m_Fy7yӃԃh`,qW:e+]%Ӧ.X?c֤q <3{5]RliQ!W+μZv{9ϘQ 7mg^=vؠ/NUkv|\ gt;%KttW_<ׄuQPiڎ?ݓxiCGqDͷ~I.^y/1,XزF?}/##}N Rγq )Ryp|w߹)I2҆ćG& †P|Ɗnw |_8 prwH!rx\8G7$k=t3V3jMQ'ڶ޿r]b7<$0W<֡}>O+^/Wlc۸ְVFm]Ά x~:w:yK*| KZKޮUX!S}gC q9M7 ƕw^`4S397f͚igLw}UP 3jH?U掽brø)`Kʄh= mx`ܦVDD@D@D@<~!;9?&mFdx 񡈣ugo{X]3)q1߹t? ɧGIyt~x';"kԨy(K$TkGr9{^~Xoc?΍XpÍN~ݷ]H*o]w@1 ~OuJ>} %+׮yDw={(iOqi:8dW5ʡ '܇,"z"w傒O?EΎ(ٸqc׆ȍ^#^zu\z(`6 >>PhŘbԦ0oWɓ]!H"1@/hhg0H]@h/n?խ[׍+X1h>5o(|71{:̂HER R,Jk-߭3g#(ӄ#D{<1P~.]b{:FAЌ@{w]>2_ 6fϲ9x($KM(3q2s}3)˖-wƾ8UHѠD 3FwDSyfCʕ|fW0^p^SN /A." " " @J|ҚE+/>'^ŰKo{ëlP eM2C ?E0Mzr:҉'G}n/(a 8߻!s^%K;%p;h+VԳLruqvFQλ?b\YH] 䨑njo5o-Ӯ﩯yilx۸4Bm+.\?P['?.n(.l?'mOXe->z/Xd=*w1XDP8bؾ%/\O `bAYsn3b6k׮jʭ^#[q衇F^`yc%=׌H/Gv/w&ӧODkҤIDi𓦃=Ɔc=֭k֬I=g4r)5k: m2b睯x`?wy{q 0 :v꺁"5kr>}SgC1#HDn^w|ٌf{Fh~[1mEfa?&wͱ(}|Oq;o3DSE@D@D@n)*y3PSO>a=J4{?p_'4;ʽFy<1B.!{#^0yl޲7%0H |߁L.?-$ֺpoD._`Sx}Wr4 0Q@=QwpɻR xՓؘkH;AUlN { pVAo\1Va ||Zj9CNP iӦs,Y.:62(Gmҩ-_fSo֬y{U+# 6+dg+n\!?!J+4U΃/^5{m(PoB 0|gm<^/LvǤ{}_LrɧCn#KiOsnt!y ^~**'a؆tʚ򰍿]v;z _Vl#~^ne^тGy[6;ǏVD T^b^O_v!ƌA(}1+\'~{ttSd^ʹxʡRFRAA}kɨh@u~*&4O<\p?²94bx}xyun u??d(MneF%"r4hh Ӯ[֭ˬw=@J >HorQc|^xsዬGq!lqT%hz{$!e]HVADkY&Q,v:ߎ(^)[:}᩼Am]-/;f&C!~â.T6l˔Ilܼvm^%:܆;N!ho%&|xgA0 m>"ȷ;Ԙ ooeX#s I5O{(0Hq< ij!L?R$ljUWw=u=&a(^(.,'R>PБ7:zǻO \3T^Ul*|]pɖc}G2X^eϜ p&oZ," " "{)O! "B_Ҿ C=v-7WA}<ʧҸ#2{wfqwEPNn+޿v(O2 gVيשm?KS}H}18%?xOɿΚJ7o\3m9.~!h}| EW)GP9>㽏V`L!E;^9wޮ EPQkǐl}xgM:c}@' s>0z>ᴝXc~ܽk=ub:<E/e^`{BEEo(b+ ӱaZCQF:ns-)B.E;/C33x:~j%򃈎Ν)N2)lk dchCI96m3x#ȒXǦ@e{P ]hA:Df -s dQ(]f])yy2L5=J'|ytUX^vn;5jTwUyX _uϿ;BSO="Zbe\)~8D 'ګ)xȄ2&PL("u27+>[vw:#M!svPſrPڇ+'#KV/^#`Wc`E,;BK5o5;ʵmf/zo{oZoߴaAGy?zl8M"J+ J6JttTob⭏͉':Tq(61u.Ogyf+E @A-(a:|Na#m .8&$ 駟=]P6Н&A<h =B1&Z|>ӆ]Á?/[4L2TWP;qDӍWXH?_l?z^|b:6~ݨQ#"ƨB" " "  W\G(ax(yEъ'jjbC/W4z}0>G ǚ6?(8xՕW"=Ҽne6־Lq#I)AX= *a8Ϊ(n l&H7҅}۰^vt~ƹO_!p=%~F3V~ck}z.O̞}G*6~,{{g93 ǯ"<+$b#M.T=@u?Mzx Cقsxkլ)F%w9}>fX}[BO3v63vyx{f{YGo1Wkmxn'(^ʵkn5?J5i`4oě 2we0ް3ߡ6'l1;./8fZn݂H.YvgE-:10%DD@D@D@r='AA4yRꄟ'{`Sϟg}b+&9}Gxg (xzB,ষE W.o>c7"Co (l[(z5 %?"~>J<VOICE\45@ FD@D@D@a?N{aÇ痝'-HO(cjQu&^V#ȭgۊSlC/~C#AqGg =<|Ն''BrA"R+ȉī1B W( O:YS Zkg[>vS櫟uU޶]gslnx6,Z90Ȇ aOӮvJ>ƥqM" " " " " " ?0^K񝂐>~xlm5UӏqjQû9*kcx1pxk[ǴpS-C`1t"YB Y9"ZlVRyuv%<m^wu" " " "@6|'^Sth95|6[W"" " " " " {*){a5wۈ`3^x_]A`)++h" " " " " " "C HϡFZj+" " " " " " 9za4, )" " " " " " "C HϡFZj+" " " " " " 9@ nˡcӰD@D@D@D@D@D@D@R$ ~LD@D@D@D@D@D@r2)9hl" " " " " " ""))R3䫣@JD@D@D@D@D@D@D '&" " " " " " )"(5L@ ~N:H@ ~LD@D@D@D@D@D@r2)9hl" " " " " " ""))R3䫣@JD@D@D@D@D@D@D '&" " " " " " )ȟb;5@sՉ@N"hѢ4\7)i" " " " " "wX5lq탶~eU+ZH[-?!m]Pu{J\ww !Gw)6G9 h"~oWaTo+" " " " " " YH@ ~TW" " " " " " "H]u\B;oڴϟok֬¡ZV\}_ۼys Z#" " " " " "k ~֮];w}CZjYF졇˗4/=ܝ'7vwYͳcLjҰaC{56\F e[ok֖.]jW^yw}v':jmڴq^9ك?c 5kXbg&wƍիUC *C4h}gvG;o#[n1~idD@D@D@D@D@D@D@v <#FpA+̟?wy֩S'_o۶-ZdׯO̶nj .4SR֮]js۲e-^8n{۰aC #YG(>٨;#MMׁ|cQkE@D@D@D@D@ظdmY޾yZ۰hIVw|`<`<*T(fΜ կ_ߪWr{ク&L`gu֠A~؜9s?PcS5kZVcD+SNu'*Dʕrv@_(O>7@ժUN8L}*V7޽{v:iҤxHv믿'lUT8Q˖->pX0={v}8 .wkqfL|Oo lnOR j׮ֱ޿XnoꫯN0uֵW^y%j$" " " " "9WWg=Kb}j^ZZmn([.[C%}`ߊ-aP(1‚bڱcG nN=z!t6f駟 |HzYfv9غuGƍ^z7废znuJ,ڿo> \.]3ϴ)۷#GZ5"ܷn}># RK.̂qg(0<\ʥE|s=K/bŊ9?p?k?ܫVj'N-<1 q3\_|9D…]TIdE6-` C)%" " " " " YO`ۂ`2Vcmÿm=K-kY?rzܺ)))vaֿ|N,_|3'9(^Pz۶mA-ZM}}/^<S%2e[n21xȐ!.:e袋 Ay%%1 ~tJ-D=.{gE6o; 5F pڠMCFhAi>(xww wIӦM/vQx ;G];iw |g,YO~}DbH]nJD/w'|<-G xG/PuKsռ\z96Wb+#'sH&TMMg}(|H1skG&;1^wy/q${FIGa~<EG>C?RK/]w_tG |TRvg;/36 ~-^j#|~loٲCڼy +l'jVwV@/;05j G'jwߵ"E;sʽ7`PȬpӧ`Gq,.Dq (I` ?ؓpW׌h/@FEJ'iU`X+ӢtC+ZGakX;O^|?5Ͷ_{ndC;c'A4\{oovӢ͙oÏ$omχ72Ďΰc +RR@M=a(5(8(4P|pXÇ;#Ax; btߎy;vuٍϭ G~=a^1 mry,!7PKsc+C#sڇOfŧDG+,矼9&n!#<2j9!v>70 |HQDR|y;\Bٲe3=o0t8jP~ß1Si rڵkZbTd$Pv>6/lW?يkOYwʧ6oҭ۲c#hRyΘmokx6Vl#kavQ׈rz\gu7;Bw8`h!mYb 7ѩ c'ѺT@}LLDP;(VNۼzMd%ժ_6=ʉS-_"n{َZ~8/(ݳ4{vk?{k"M+Vٶ@ˆKlâ"룫J~VIҞȧ'ëRZN6;kQSCV  `5MuHrUWL19pwER+0>xAKӰ}x 3 \fJpsM7fw`t#v`E>Eu{3U^t?<+1.u=r1^qz7pPM6uԞB?c51p޾⋑nn~J-^N }n6l?췺9"ys!uOg9(29}kkWakI-bܤ`܋>]paw_"mf%cDhmCC(W 8}.PDP\/[GDd <$=2Dtl ';gB ɶ7 YdsV_"; &dJ}wt[//臶mzVwv'xM8|M;vG{~4ԭ[׮j;C>3gδO?*T`?[סCk_~_ԩSYfvM7{\pad9r*Tڴig}GD@D@bSƍm/_>{w]c<\{q駟nŊIko?l,h®Jo\v#/TT)ر.]=o=z_|axv%X˖-fo5˗7=묳,S9D 'sB*U3ϴN87w\\EOѢE/J(a7px}F_;#'Ry>|>:ay)4$RPy$੧.S@& [oՅqON>aH>쳶_o/vC#!R?31)8"E6na ?&Olܱh@|xE=J3>ay-~1~[oYʕ]d;0`,.P=o Goܿko믿nxfF#|ΈL; |u=҇D`O TN;Ǝ=X#c-Ƴ'|k^zS1±kgd_*z&Mr#M4q:=Ϗ8ɠA ^я~G:dcvQY8蠃gy}= B:{1QԨQ#@N"A$w-?&4SOuJ|Æ o߾p^&pnadխÇq!t M~,}5׸|nR!v0Vh  ҥKf3) Rǫߩc9ƅ1ѓBuX!#B#8;H2e|wν\rDq0w@"" @)4OCǃ (DȗǛk;?yd-$" " #;D#?a2c/,FV^(@+.#}.,RQz5A$.5+0=.\ޗ_~v2o{>{rGF6%Ry)o7EZ9%"O~.(" 9@3#׉\|TF=s$#˿Cl==uQb{eվ{r<aFmy&A&ϕBlgvr{# q:H#xGb6B/RB?{&=6En%=}8f̘)?{l)0d_6y3Ʉ{1&τT8VtgR5-'ƠS?6/fҧz" "dT¹VJ77BxgȗC@"mV[}L?˃Ot޴3nj(Dx~)\s!# R a|ٟP^"B8 /b}Dxg*1_ 5"K³VŽA;Q/Sg&?rLE8> / |7g=Rw_am3p6:3Pܑd_*z܌M`@ &{DF+UEA_̙>R=D6v*DX$.ϏϝgaxfPwg;Ⱀp0g ˄!R1L*iYD@D?x}N+Ǐ6,H>੉6d z& †ÒoF#Pa@q`_XoEggߩs*U*hC Fxmbg%J7(%/ٳһVb.Uc @D@D@D`G $<ހA|f͚hOD@D@D@rd ~FE; HD@D@b8ꨣW^v9'uִi;iC/NKD@Ȑ֪U4hSD@D@D`/"̃//2TE@D@D@D@D@D@r/d ~O{Qi" " " " " " "{ HϽN#)ZK@ ~vDHЂ^RsE@D@D@D@D@D@D B@ ~D@D@D@D@D@D@D ȳpm_hDMD@D@D@D@D@D@D`Xvm£ȃ6@ ?w\'RD@D@D@D@D@D@6@ ?w\'RD@D@D@D@D@D@6@ ?w\'RD@D@D@D@D@D@6@ ?w\'RD@D@D@+l֭Yڧ,;л@FF߶]`۲Hʚ5.-keMgED@Pcƌg}{1ۼys85k$U\֯_o/%]r;N~^{֮]K@,|'w_6mZ;.^|7ƍ @2Yl6W>s[*>yzɒ%IhYD`#S ;5H]`h۶;nf*T!| 6؝wiŋ5jX%믏k,d۷7^ wzYޏRlY;c ?73KN Jr mu[J20x7ow֠At}?Q;$7O<)Æ 3N:{GoٲU^o0x0`]uUv7JW8/2yS+C *Ǎg-J:VCA`_83GP`D<#*g"P|va9;vDiŒ^zѣoZZ'"k 씂P,n^%4}>:Ŭ?uy t )>K(ώ,'v 7<,c:tۃڗk%^yJ<VCq0^qvi\p~_Ê0Š :#G߾}3OfX" @Z,e@V.l8Mlpӭemȴ'^|lVBYna{{/+j=`钶qr~EvWg{8(2&3-qU6ض[x:Tiڏ"8=1'FhCVb oLlw=Rm3;GH'۟h;^|ߍlg}ܮ&|>/s=ׅ9ǽ{k)A(ۗ_~y!ϝ;ׅ_wunLFr~.e/xmٲŊ-jM4"ϗ/<^"ZKA?J& "O$=JX}77i$ڠE蠱cZV"?Vǎ'+/Rٽiƽ)@}$мyڳ`nu*R91|Yg^팓DJev]w9;#Gyap 39{]{O{@ - >V,|{w[7!@LͫV;n6׬7YVm]z+(Ӌ6kzN۰`Q?kfϳV|lWvm |!l?eí> sF}lqX}+Yn/YBmOڨW[A_>Vokk|= /9ׯn͖ i.<ʴllfcϿ Wh vZHH3h J{dcJ; 6^C9̙s?3!(#E]^ƍ;6xnQOomrk:cX_B x!5jˆQ&>s;s\;[񞱝a^7-?dg <<B} ^>X?ƭh7އ{]nڭ_ t>$?܎~?T o/8q?N?l&LnͭNU*ߞ="JkE$)HW=άU|y{ѹsg2dk};^٦ug$Pv<L&Vq}+R->鷬GZ㏟_RysM,mQS.(s?, oZ-0-G7/ HJ@_`?% u] /} ?BH " 9@=@h} KxٯNqڵkGc_̏ܰ0Br|'R/%KtEv%<@1JJXPy&2z+#W 'Qp.(G+D W&*&M@Bg%#5,R1@S\kYv~+c"NK /N:>(s]t7T:P *{0Zbmwlck+|㙎m1TPI#9DqR@OC~< 9˗/재oy1X!&\C> DsߐCJ IxX2Wx2>/ކa’V!ܯ_r';cCǸDмbA ?v I0>~/-/ƹyh 9%W]D@*̞8 9xj8D>^('T_v2L?((1"V(_(9PX3 :PQÕIs~yfIDSL1>o򵙡޺v|!',7m4Bwީ/KLq jQX3ZRe9DMe\P)JW*G'4?TJ4յ\{7kL]?jvW_}s /|-L$qs/ļ$$M 3>}e5ۅEСCunsxu릯 I5Gymڴqw*C$R d (')jM{d7w1*\_v(ZPߋDzt^@hZ26!u9[ Xs~w Ix+_Z~B?M9:f v.fMwQ^~u";:m{bWQAV"DiRY~KCe]Թ~bdH!Ox$Lv.fo^m x +'sw6ē;0bBᦏ_Xs#^~}~6 >q;e<+(C_Pd X*sjN (2pU - @pBQIX\,'`3ǜJ4b_AC7VpPk pHBh<0xo$4u0y~xk LxADS0I "O 3qRBm~o=ه/U,p} C zX UtMZ>YnH׏ W;׭wu~왵ߥ$IC3wRTa <\Fa5{PeRٮeʝ2zHTE]Lg$& ܭn~Lk\78sL¨h>mȳ趧D@t'Vї6{bt>ûe$@@PP׎bT:2$ FkQDXqQ8HXΝ+U9 `S$&}TKtc5aK3hI$@Mq$@$K `' AQE".Pr" T@a 6)΢ۀp$@$@$@$@$@$@$ P*HHHHHH#@y$@$@$@$@$@$@qHHHHHH;TL$@$@$@$@$@$ P*HHHHHH#@y$@$@$@$@$@$@qHHHHHH; ûɓWIHHHHHH@PPhIHHHHHH v;'HHHHHH @ @?v|O% x%@+$      A ~8J      J W<'R$~ vr . S4|$Nʥ;$@$+/Jʔ)%Arm6l:uJ+&/r< W\੯Ӓ1cFnܸH8tMs^ԩSXN$@$@$@>^29^״EeiVx56UԬVYlؘlOlOtyKd.e! @QڵVӦM+ &zɱcǼ:j(wE7o|2gGUVI>[lʊ+c.mѣu_|!*TWvR;$@$@$@$QR] jJ] ڿ_dvZ1$MbRal?IWlD%jcHwo`S޺-!︔qH# ^ǏXw!W^Vwz<СCNl٬:cǎO_v*Xww,ޤH" ŋV-Jaub*YXʍFV}D5}(nQ$F3Wk\2T5~ T}H/GVivw~Mo{왥duwoWw헵;vamO[Tϭ5XdHƛ`m% +AGN7eF imݾcew#tD.e[ɉׂ$E|RvגFE]s Ց"_/F!ӌj$@ɓ'̙3p KFCz@D֗^zI/ZH{z;xSue˖+W.}͛eْ;wnٽ{.;rry=1uidÆ rKOBkx'u;I>]Ć]F7o\_'=N8ϟ:เ~x6`%C o>{IHHH "mcʢ$1}`u@?M1P u MZVūqčCNUT2L)˱)sd3mqL s$;z ə˥O_K?ojS:4ןU!f,WeOexz\Xcs7POKݥDOʎ2O ˩ًu~K?K<9dՋ]x *=ġR>zcafb0qk{ą՘6'KJ\J}J^:ғ$& cZ=9$7]\\H .\پGw Yd`͛7Ž0\\ϟoNן=SLzРA:&i^#ma!ѕar\$@$@$@$9ˇIҥdwU,\)TBZNYJ ӓ +ooMXl` Ч%Ou-84m>Qn<+gʲb\o;8^_Os$Q0:RNuhm(& ~Drs/]WP$VP~Ʌ5$CJr:ɲgqafKGyv5VdoH+ww1iZŜJ}%Q.@96]tq\39>eH^pH"`;/J}X}m߿>|X*ڷ{vƏzP}]`ç3˝ ŋ-77jeab?< `l +`B((CBԩ"I&R [ʐ!C= ^,/BXHH 6nw'yU<#{]*R𣶒+v`8q)SYu¸mY}]pXvkt}_\G~ \Н|ᲤQY,ͳR[yLabn=Gk#Tb0DR !?=Ya|8ϊ#CsO!`u{U_-baw'Pd#lnem@F SkCrtҹ !9ڞCgg= -&*U M2jpܹұcG 7U<5> @t H|M'O0AΞ=שž`+L Q^۶mtm&@0I.s]xCY@W D($@$@/qȚD)KqM;txʢ\-SsXvϞ?1p@ÿm6rs疯$@@Yct3v.o٩c"WNl)Rf TD!SSc)>|S+h]%"؝ $֓.}ABD?6 @! Tv9D32n;vX6 n]=ܥCd]"Px%̛7Oۯkya/NPO^ EYwIHHH <Vam~!ȿҙ vkֽ K<2csKɳ$=E9rz$7NKwmuHpLo~!{>;cKuXJorxY+R900d#pI;+ʶ|oK~V;ڵկ/ۺЧ` vȨgƎ_hfXe`Zu)Pԑ8׉UBֶXH~orm @'G}${Gk׮Bhs^)֐3i^P|wڥ[v{oK.pd9P'NX#+XqHgn$Eذa}L2ոqtرc2#;/x 8΋y> G R.ҤY>ĠCQ;Nrí>APwo,Ki?גb>y۾,WO S}B9̪_AedCf^k98jOLjM-%uQ/d=U~W}ڨ2mH/qԒX]#Vj3F |.ItQAD)ƌA=x*(GR"j@IDATl@|#y4S 2* }CjT+S}4IƀJų]W=`|cUTzBńI jdiu5|_~ ;/. @%еkWCutΞ4$W{@U_5;<n$~hǏ/SL1Jbt|OwcF{pxzi?ŝ ##/"?) XJFɜ9sPC\9XEB$@$@$@!O*΃C">đ6!,AGNH i%}a}ׂq }?ʬڤ֯O ?^j\/W,0d2eϿ7dY3Jt); ڌlpG{xy(͟|IxUkSv{mvΉ[($~O>%2|^~}UV &1{ >Xnz2nݺRzwq?vG΃.]x xCbW$@$@$@0H?͡ ďS7n}$NXzVXQjԨq|<@$@$@$@>2˿k^ [t5忂u%VYlX}npYM=-`߾z-Zg$@q=zH,Y$G*U*iܸ?~oZ]EI^z~t\n 2k;vÇڵk֋`4aG}$Kŋ޽{yA$@$@$%d5a~U]2AL.o!K=%/kJ#K*&mECB{vrn7hi MF%'O(׮]~Z|?9rT^]O 2'NK@S>1^XIjժRpaoբu_ 0aB[ʕ+}6B$@$@$l Sy+UbRsLMg|冈rwn!^<:>yCH}hCzb x$@5k׮SOivʼb <./V VJÆ ={HZ$I$nsBO!wc}9]DIڵu(i+"ڌٲe .D%   0%?QꔒI}z[Z34wd?ʡʭW%IRg'Y} o'-;?'7q%EuiR bW7|]-,]eGѺ֕rz| }GR/(Ň|!˕QK;q~j'F(>$ɔ^تReKۮrIՇVNJ$˞E_ټSv$VF'͖I Yhxzrll\ټKK-_m"$/^)w_r|f7%[-\`9$ V޼ﵲ_ްM6T" U+j'v A$X8uꔎGE.PǾi&V>wL&\z@JÇeԩSeɓ'^xA``~ \/]$Vh#+Wu۬Yd˖-rKOB5kԇ0p@y뭷t}e.ƍǟy~ЯKȓ'uʡC5sy Kt"n @ DڂGe~$4`}~| 7ҿ$כ/iEs E2}e}wtw\UJ]n<#n+eRlPw4o$ϓ]Vm)y[v`)7 wX5`y h&Bt{oɒ%`m   DS,;7ݞ}~IPQ}`\FR.*rZ!Y'`$sТ@U.,['yU9^{^3?Ey꜔6\R*K7_zvdD}=VOD)C31K+9AiCYTg"IgMe ]pQQkJSu@ KZa sM$@Nv ӂOO(㏃H92r~o`.N}L /w(H|gmۦy(-w:mbb ;Ga,?SHH"D ;Aq޶aj8uV_ Tn$ϻ2munҬaɩJfw{*K'r%?,UҼq粺QkZuRdzu񊤭ZZiA >MSH~91yua=OL {B?}Nʒٝq=`5ၸ|H&@b % :⃘~xNg' KVgob$Sldӧky%ڝ!]iӺ 9̙3ˁ\gʔI6lؠ|,~@~>s &+ X>cưжS@!&'#z}QTX~<<0Ye?R]*W1I ]'K"{ًvCڒ@ nM,y(VB?4t%_R' ~PwRuoB][Z65QrX#an <RqFP #;EA|$ۼy,YR]J+ =&"C?m43uq}]Dy-[ ӵ$&Oqn/Kw|9uHHhI''\ ue.VtdG\y${$"\Xܫ%u ?;gK,@VnS/\۹m>d`WNwЭE D t~(HNwf\X^wx`c-յ%ɐNA~Ⱦ6{J}-__:, x| 1Pjd]#f`uoܸG K:&?ʯj/6^ -1E""L0yaWNPza.#S;pHHHADZ~aSo,ݎ VMg̠̟6 8:^|]ӷuHpusƉӲ09p֎=$XY踩:^  K+=/GFOS:K=HBS-:%aMսoj5NN .*${Y_|J υ| HY-wy&$Ѫ\޸C:O$xe  s3pAE{wIXm>P|ݫ{9ŋk%|Ĉs\_zy>GVLM$Eرz L4IgGpĉ!2#;/>8C   W&Tkêl,81*\_[YٱV=aX!Xn+>26!u(5ww^P_8Uj ok%ʞrtj 3Fɦ;2{~]dG޲cO}mcH/QTV<=+7y^vԹ~ i}A=\bo_.Oɧִ߬ꙕ@ uFH"1_*R|hCY^-T%x\ˑQU׬A$@@ΝqG9\(q>>M}iW{1l-ħ;!3> &X`?q^w&R̜9:pzY:OwcF{Uwha )SGL?2/XcI<ּ[VƂx;׉r D@[R|,P̍b9ֹ~$Θ-0˙UM *|69&xDJA//kZQ+ |X*W,3f̐sylfٲe(Q"i޼DEXc=tDŽy$d޽qOw_D4=ҽ{(ԯO:9(u 6۶mʸAQM֭[;n8W\jI{ͫɺz|o;ζ'Oz +t]6eԨQ qiYf!VK/䲏gw-]ه˗G? +_* ʥ aGy%SV&X\ɟ"E"SܹsF!~tw DSJfOɹ{n_ddǗdπ嫒4K)'筑0C6wF~'8M*A 3%=wPxe>s X4AsUYf9>uܾ#Jr#J㨿-ޒ4s}|Jv%C@%m!YrAՇdYQʏNrd6ng_SvǞ,{f)3i]v`ؖ$K*;ڈ;q4^Ky[NSU*-U7em;\_]j  +Rzo tΜ9*]{  ڟ7oK5kViӦ}+%:*U׭['UV~"pEWVy뭷aYF OږK#vp;O/`oxؽpva(d|ᇂ{XDF0d޴3W_}=0LŰޣ>H - >.-erh}Hwyn iJ({"髕 WVIٵ|z/JW񋇎IeIwW) l M ə˥O_KyՄ(jSj ,\)Y_#$~IJ񽞲Rw ƅ%SݪZo I3R J9"pwէuICQ.SVP\}"Qa>ܱcV`Pd p(ٲe"^ x=LˇŤDYJEy.HrtM6Ynĺ >bk)$]G/L4/"{QOy(` \rȻwmߛ. p?iqJuM.#3_'7P'}BQ/pu{SWR*ƄB'@DP[~nD'&b01V_-:tp0I$3MO.ܽEK:U'髖4e_܎J Ie$+o' |Euvj)i{E} ÿNVɳR}XI,:+m:G /Ӻu /=#VדN;&rX{@7VCjhb}{4);ow;0H~ޟ I$oLv?fO7Ue ,qC4EF$LV"fm!Ija< *D¥#kx2XBXB$hT.ӲxKV0mG;k,ӗlM4gHn.=*^PqS{H4$Rp_ceq_oKb5)ŰhšMV%KGtqahvس7m$)& &$@$U눶 'Úwb(&iϞeP+fOXiWTrpv+Suw{(F A K͚5ǭVlBl/6O.I  O$3I+xۦ(O(yk*^+sHi_f׶io߾+}rA+3upPz ΄sWfr~V7}\yU8]j+π)LUcٍvHB$@QIN 2C0Q`mCPO…CL9ad501ـ XP /Lt'P]]@D t!+AaXx6;$P(yXKۘ %x\.r qNp}P1+=GDr}E}̘1j >uP-ɇpo0++v vQ # !5>ϻ^ȵ+3'pLJ`yO<{z<{,G!xDQI#۫f:Kt붨dvu#qjOx(K7rI?OQm2F+dbޟzV:ᵍ}rcRnN)UW] ߤ7`qF:{sm>/%VЀ qN/l5Gp{,qǸ{ 'ؽ%tÁB$~acK?3{YX\X*KǹkYꞢ_c ]}~ɥ;Uv6z:$@ze7?5Xݹ~C/N_L҉RdW`:wzm0[-rv_$@L>2Ûկ/ۺN"g}mp'X* 2S^y۾,k_,H.$w)$@$&MbŋHPK9 ~߾}2qD aD;`/"ׅ+dڊo A0N!<Ydі=Iv,$9цj2~WA roPaF2=oXM4щm(MDŽ&0!f^EzX17 Ҿt%f"ݴO V]F{{ Bm/3 2g}e5ۅB* x6/O^$ b'yjAHam= >!+BuQ z1Xڡ|B6XnE2;u^jI^}(WO S}B9̪_A6ZF!Ažr{ y?Gw҃Tq]qQep+^⴩%FV1J.KCI2WqA<R#FMɿIk~~9?|CM`,%OzlȒoZ>XM, ?;F6UI$@"KxwNk96C ֗EXoWjwwG=bRxA`zFp;e^v/Xno6! b~am۶z2Hg\\X1abo[5E!cwXr $7.cOo#xVUXk*䧟~/s K!Wٱz Xp%A=L4I7j]z-xppܸq#z>u3wɒ%K",'rd˖Ms;SAAAt>09d~Kږܗk!$$DΟ?m2M${ DJ٥,,ḓ٩Kko΍hd[G4' pCɓҴiSI:ȑCd"?/_.7oKǎoFFi=uɐ!C~I&[/~70an棏>ҥKŋEɊXy~"p%w*^x2e-[V ,hbެY3)WUߵv[nmqn8^fn3cƌ)S&xwom۶I͚5%mڴz!O<2b{믿 ᾄ 6C-`RZj_/WV\7oʧ~*)R\rIʔ)w8YsN}޳g0ۘĽb/X ٳôx8p,$x"(׏SRwu ![aGqCY($@$@'ꫯj+̙3eңGBpBoذA?Ci/j*Ν;BVq?_Nyꩧܹs\X+U$9s۷ѣG[n& ,ZHڷo/@5 oi/mY'ѫW/ٴi={km_Xr Nzĉ2zhqdƌ; >R@0p1Lt֪UKO 5JW7kܹseO( @o7͓O*a?}xaRbС^u_Xr ~{ /= ^s0 6LO)&40I$ǂ ´p# C add!yH26 c*[L.fa#1fvS֋ uH񟾒iRzG![w˅%qԒ} ћ/9rMXy8VmzIv`,^PJS?^'ȭKW’5n5)6HHq [Bjժ. о}\;LgȐA!t˖-RLmɛӧOk{D|4X` \X ( Ɂh s='ߟs-Aە+W.!Y(8%J} wZp ɓ'^_.p?uVF:lnSNn \_%]t&ؐt@Ʉ[~K/V$PZV8ۆ FmPBS`ƍ7ߗ]0WtL+7x^źOᾅ_ri~$y}a509 ^~e{S.?wE"2&L'05j0.XQB$h<,{{K?3w?".kmg])eH xܾzMv|[V7h%WO'GFM Icήة\-KȿlU1e'ޏ<3g$ɜ^ . SȲ/H} %IZ&ھ,jU  x N>vobaCv;VC %xH3gV^ ^` moBAlرCwE<2+B cB(pq!OęBOڵkڭJ,ǏHT11 XC,Y2+^m,thRE0YLyBio3:k/|ӭOwm#v)p68v1WJ]v1f\m +8&q {]ܣn*;wž{AN@&~m ;sRќ@IDAT.5V{:ŠVX!=D[}ȏ?2Ab8={Zy5"ʴ횺$$"&_^b~i=Ia{*`o! F 7>G!xtDڂK |RNZa KJI>qoҎ$~W/(Bh6 =zm&Tpυ^U G@(=ޓe+$mZ:|Jo:GFLp)΍ڡQ(mj|X}X*K_o,,I˙U.ܠɭ~A$@3jLx@|i-^>Y38dQq\իWX6HD2?1I=XD ~ˈP585cz`F`)W<8`ecP3QI?ݿ.&y1{z~I$LO]TV|w/>%O|#d$Szm:)y7R~R&vt [ڸqcmł71vcE\*nwkPxMblmA =2C/ׁM@@. 28I ?Mb m$u&uHNek8b7Hxm#fNI(~g8nϮDcYquWiޗ}{޼yzיh'V]] $drD7DsMIwwއ~=0O^]c !%1ffj+}wF_\A!^AKȽuݝke}-Хſ,w;$@$ ڵKgEṷjDq>,BF2ĸe.x71l!VE{,ec` }UĄ0aWL9|CQEJ4y{z`swS;3<FA'} m&X >NA x8'CëO>I9rk[1gΜ3#y![<񇽺/lnH{X:ys/;\-j/vҥu"VgH7H@*{KJ z *~K|lkYۤ<}.rk\?vRn]*{{Kjn; xݳaÆ:K6CTLaBIǃ~Dh!GVځ%<}k`//v<G}L`l&u >2Cс ˑAy2k׮X0.pzΰɌrs)$$EsV|9a&!1OPK* Ht[}u0|xf͚! L⏕(:t` 﫯9~y{[fBЗ0N{We,(3W3>}e5 ^<(`fX1nݺkB>spOצMw2,jD!L$\#|T)ZH?q"2ꕟl|#9$ΐV-7Tr{DiTQ$@ C'MClԐg/BO8^ﭶ}dQK$@(Pr. YR0 }Jž={öyF[8Ǹڝ ,J?AІv>c-fxWnF`ǒx #.ĈU}̈́Bv ]nI!`.&ΐ ޞS}:+9Vxw^PV!.Pﯕu$CwcGL2h\A-o>ylϹ߯S7~xkA=)X^DpO 3qL{rD%o|z_iOzkH@YN$@qbR,P#*H:w(&60yK8dus#S2@& 6fPz`D,~$Y=s}D ~cP aTm~O]%" 5ޑ^Ko*"*ةDT (? қ{キ$@Hr&βeCnofwy/w3Y__B@b$_/o28{pb={y$@7@DDΒL;* $;P ,.]ZMv5jranmJ }SbX<ʀ"y$ [[HH6#5k֨gDfM`D1۾V,!  /_9w      ۆ@ ?Qoj $3T 7TQa $3T 7TQa $3T 7TQa $3T 7!2yK $҂ IHHHHHH y<'HHHHHH IHHHHHH y<'HHHHHH IHHHHHH y<'HHHHHH IHHHHHH y<'HHHJ… TZXb}H$@$m\qgxr7Ơӕt\HLG2nРA^˙I$ps $HzD<*M;jCcx%&Z ϵװѷuu&}KNR'1'%/M3H͚5ery `?|ݻy%_|q8tPyW|_={H l2JVٰ87mT)6lM6{׼bwwܡgJ߾}k׮Ν+[o%yTO?(Yf'{kɓ'v|'2fرc2n88q א%=4o\J.e+V/7V ~bڴij*]x$HBcL+'wxΡSUʯ97ϙ㫎Ye^ddyW=^bLt:AW~@cTC,In.ɔ)硇{ܹs7 v6y˖-Sq2 %sp1fkʛ0a9r 3M6 yO<ӧOWm?깕e pd%Ĉ诺Vn>ȖݫW/ORlywyĩ`0om,EcS\9{9s:7%V*'˱)sqiM>l*$|!Nk#MI,y% A ~%ƶǦ͓|YFG(u& G6.l)-!3HÅclβ2x}9>s;TjFN/Y-}lcSɑfJKGJH2rYUMv헝SI.qv2Mj=iTב 3lc9b9ly )c̥RjP'f-T Y*-&|+?y[tvzlQUޅM;$9uֈpr5IbڭʭcbR>Ra`Ou2t0G/>WZ,9t,:=q/'x, En|󍄄ZhHE}YSGc ן1cX^!CݻÛl׶p?\:$M4EݺuU"a?~ypG*Q:* \ԩSutrrؾ`MPkժefEGG)+O0q -Aٳ3xb%Ν;Y'$@7@P2YIC)ݰl2{d,U6TKc% )g*^Rr֭CL&ʻc?ifd|Wl d}%ƸqLZO5S$]:}dr_\Wm)BRIms%e4\}$$>yr~+˽LU眵J*%CJ~C`jB>Yh'eO.vĂA 0/ICzr=*1WJM3D_:0f}YBO<<v .^->\9vJN-Y.ܻtl [ӊvk!pC.S_ݓ~we-je+9?^Y' śl[/1*UrLd(\":uX!Fe9[NYA=u/J6\]W߯/t > C?u`r TdPְ/GO*y7Pn\\ظ] <XH)-EUx{\QI.j*0{Ể+7XJA–]NlY7jc:"Ml{  @=ZEG9W_I.]tV#`~bضmjL_spqx-`…^iE{<+`e`>`8x{5nnoSE' E (.RUPs6J=2uLRCr┧˛+N^|3"Op. Wc'yIol?LrnF9D#`2o\PK6 k|R̮ zOJ[[2@{)l>ewZ4Y;U- N¶~_{'V-ڲHHH ~fϞ[nmkJޟWl :^fM}ZEY F$Wd[Q;E;Gfus2 qk,k;9 *WZmU@@sYf%K4imۀ€2gٶk*OP>xTZU{1D$pkӪ?Wi~RM3GJ^UF’xEcIhB=[eM!ĬE. Ict~Ve !@;[VnwշhB_Vsjyۚx=G,|?Ӎ`; D$TrHۘwh\Wp?{}UkihYE*7(x"tu Q2Y|{/>&K[uPT/=Qc@1(xh4sDdd+: jP }?m|c}o~z:pňI){jxR1wjAz埉HHH xG 5J>/ҠAS)w#hXLoJ[#<iџl(؛x]"kN;t蠬p͇ $"h!0j(ਣŻc[J:zek#a| -+7sʽjlEŖ-[nQ_[/`<}U('NT D;ʖ-k"|#H#ǸiLk_y߳{>/?RsRY;gR*Գ;5NN/[;_5[.HW7pϢ̮Ҕr mkLQ3f+3Y?33Ug/{6Ygv̾w{YsX֏V>&Hƥ^U=Ë@qup`SFrߋ p>h}inf]_RߕkY [01\0ord$@6b1޶}ZׯC`ȶ#<<\`ׯȳܗ,oc! "]U T2%cƌ%EH7$S e$@$@$@$@!x SvB=ܱݰ| e PeHHHHHH5Ȟa& @0 P&M"      $"@?[      &*IY$@$@$@$@$@$@$D'xvK$@$@$@$@$@$@$@?4)HHHHHH$nIHHHHHH &e @D- @H aၪHHHHHHH Ђ_'      ` A$@$@$@$@$@$@IL ~HHHHHHA ~0(R $1*I{      *H$@$@$@$@$@$@$'IHHHHHH "e @_'   pISv BIH6!p(1W#%:mՓgl}zȀ/k93In.)C؟3P"h<9 KU?T F ,Z{덒#"H<(5iӦ)QrO;Y*ʭ3H޼yLᅲ;~퓻&?3*zH)˗/({ri5jH…e˖-r!ݻϟ/:u_]j^Uߍ,}ԩS~kafNzر2j(ٳg2ydX~,I&RT)kQsx8@yǘqIÆ ȑ#U]qd c̙f9{rf \ RPwsl,r䄮o諎|[7K~|;l=q7LeKIH ZJ)Q?% M%?~;O촶C>pR1@y(clٲySI9h PP\9T[X&N}PC6#V2Q᧟~uk,8"eɓJ[Y?X:t98믥Cc)pi߾mCoBuV#V X"Ӄs΍#oqA$tgD?"'Ϸb_J*wp,kLVQf"kE ],LT^#|kXdS>2+ouaܭ.PSm XR\bwO G˶wνe]B-9#zض8{-9hm<  `x7'_US6e <gϖ+WJ& 2,y(pwxC9{N6^n:Y*ؠAYh4v+'_թ][Yr?/_VXDVZڢ,X#'f+Ǐv-zRԴiSپ}Zh!~MyHn ! Fhɢ`>/e3JR}*#dqG$SRu0&[ +=+uW]ں[+֑3 k6 `9yoi{l[jVo5${ѨC$@$|vv옵:2O+K.<իWRN>e?W\"E :7n,K.@n0+H $XKyEYRrԫ.; pח %MШ?$etRcO"U*U힊Orvj^;6SH R_I4iT^خ,˩v!ҩŃk$UgxCϥl\[o=;)nOJꭙ,)Ӧ9WkJo~s5{?.HDL$p@хuk?lٲf<9s,ϕ*UKYxqk&ZxƻU~ws&m fb{E>}ta_A|왇SJHG~,7#XSbԂ>OxX|7jZ$@IGݯ=2YJr4GvK*kq6kyVMMe _ܰTT-{6GJrVO!baXZSGF)%ߣ m9 7Bʸ9d|cGwqv ۱O.mޡHHH\3foWw~w\H%g~l s='pɭlDZGp8(iӦ5eZ-:[.ҏqACv,iIPhPܲ|)Wx@)DE$OW^QGiFʕU,^h9!a.:VT][!`Gx C ( >Sdin^5qmWSJ(8O=+f5 ɔQ{ eXtP8[VUqLZd[72oy3IZLw?8a 8>#[ ,ط mJ>I(hHnd1B{=AvD eޙ`MF .XB}kt,z\a֯;r$YdQAJ?|tr )Wˇz1>Ƨ]u>"kVv a̅, =b ڵ˶C# @jT{a/>KwcY͓zyCΜ*ԙc`Ȉ336uy_NX ~$35Bٲ}˾v'   MQV;nsK>u7n?p?sW ٿtMJ5A]`*V%\G}e<6nMemg=5keXh֬`;5ŇU9Xs,jJWY `[uCCkKx/[ݻ+Ǻ[] |AS1GG%D[fo -Zf?5kə[x5,QaJ1 O]"jVV?2؜ӳ+O|E,1yB$@$@$@@"o\h]Hhk1",YRe/uo[bc ^vD.>`/x*UL >\{*Mt+ɔ)S^~k֭[Bbr*pGy"]e˖fsCea$V^-D$pH!RKG?L;)gKs{YH'zt≊mKz_CEnngj|7*BXGNVrq*C$@$@$@P4oqZT)א@WXQp{ݶm[O k8Ƃ *w{iF :<`ǫ:;wl_~*d˖&Ks# p_p)z(nYUիWkKm&CU4?mxz愠zaaarA^xr^LAPR @P$Ȃ%xA_)1SI ^ ȺgޔCkn2od4^:ʸQZI= 7v4ٲHníꩳ2Lc>7eaE-QD佮*(`b͡ᚿ2DCs_~BOa'1Y FFŬ H XD$<h7K."#[dcVma0`H6U#G#>L)ױcG*}C \59 3 ?;w ZPu@A]}Vn`a^QL;v SB/T^]Y}߷7k?nY`s G Veצ}KGv/aO:D>uREe0 s@Sg$UcyloT) "A`FwJ}X*mGxx@WY{K^Wa! 1/Vї7Oz'Hn%wT,$    DN);c.^(p}wZu9$@$p++; $Hj=cb5       dG$@$@$@$@$@$@IA ~RPg$@$@$@$@$@$@$dT HHHHHHH )PO HHHHHHL ~R $*IA} @ P2P#      @UXN$@$@$@$@$@$@$"""@ _<,$      A ~8J      K _<,$      A ~8J      K _<,$      A ~8J      K _<,$      A ~8J      K _<,$   ۓ $&&&Q&eJ$@ F)oyM_'.؊׶B^ sιn=~xݻ 0uĮxŀ]9sF\^B+H%>;wnBŲ= ě~Yf;wƻmdGDDHxx}`q P}I|u+Ww3<^X2־xN$| $HнL-R[ĸxK$.!Qao7oӻdsoŷYLQI"w`6yg9:iybB^A0S ѣGԩSK%s_2|YflRzaօW_}eݬ˗v7h Kh#GʨQ_W.gϖ۷'T4 ?^RHSx8tƎ+ժU2eʘeP;ͼEyٮ];)C 3 ?ؚmܸQҦM#Æ ~?(} u6l(s+%TSMܲr3k?W^7xCRL)9sTRI|.lٲEݳwaҥK ͅ]oʔ)qd  >D$_=uPtʡqSP7 h<9s-2bEy牎狱3 @6m Ü9sСC˫}b )TRQee+PbƽZ1|BBBE)S&YpaeS{>?˗/C+b8p@j֬)'OӧO{ *daqnڴR7l 6mR~`;Pٳgo߾ҵkWbNv|z-x͛~i]EY28ڵkɓ~kafN>3for17nL8%{9i޼.])v (w77n[U_â4m4Yj.V< K $m5P =M <}[/|om<2Ї 9a.7㷽Ē? ${[n%K,W6,$aTYP<(QBYxܺKU|y૭:u$`/["P̘H  (`eVIDATaQGUP4i"5RJ /`ևRE58eO0A)bN-l=#|XaGz{U<`4h o.\ZeĉVҥK?#FK/䷅VndȐ!jQ'TE=[N-~|G ܫݦ"E qǂ7|#]t1~>k,@cf'1XxN$p $XZ_UM+nSV[6}d°^mZW4Xdˢ\ڱWί*49Jמ:˚ʡS$UעÊKo98fDGHֻJQ$Kg9d\>zBԼeR~9ԂUiΒITSRXJ*;?A"ϜWu(ezvR ϩ/L9:esiaH඿K&HHIVrIݺumE͚5]vL͛WeAmRXX:[^V- ӧ+ v;5 /ƅ'-_j<׌{/d׫WO)4zL_~@ɯ\@.˖-s{q۶m/Ա͐!bN↹akʕ+޽ۚs 3fZB-`ꩧ6ŋjUm>.xcP+ho5jիWKڵM^zұcG]-#SneF@Ʌ5[EYr3kG:fKXqzQܽ{w=z[e_l{|AׯMD$pkHtqP(l9TMz3Gڛ3=B3b(u& .^$?.M7P.l){#yח ٕdÛ˾cc<;T)_!}SjOa?6u˗K|_)}/+? [)isfWJ/گ-ZPJtyFr7nYB߲8Kv!ҦŮ*gJYV.9>klzg/OWNYޮq͝;+ 7,`H{=_:wl[&y$@7@| ~ Ž4,޹֔MJo$Kd%UpXe=g*2P-ZRr֭d 'iҨE߈I5%[bdHXXSZ!ML1ܪN|eϷJޯSfJJCa{U\u I=rLV,EuӴ[YJ<| _jHi&!2*V|Z< ϩ˟Mmr5!OƗ ć8!L<#KKa0+XNdɢ_xpbk <,KxŃ&#Πu*y ,΄S%$uv(F)ߘ7,Q$}S n..\R-ξh6YF}Ȓ5SVm  @ 4,VNг*@ R(Z.Z+{<ՊkX,jMڍW*r"-X@P ' ܣc/Ə= 4{fV7Xg9IFQpN';mj]%`x ڲe˔2(:HX¢!toș:V<-ylGx8ضm;wJ5\]GHC?PE"#_K-tb /Rɒ%U4}D= [:p_3jޘkq?e"u[s1^(si%K[w\ k}Z^>vR=G ŖMbp&G6̄kX'(3nQc~kCӋW,dm<6k\l5TF3)eE2X1_]WUGp߆ݹZx#aѥh %lx To۹c #4?{fP zxna8p!Oh<}.+ƫRgښ+zTyԟ;Fr;ri>G5Wu qu'S  pR(_[-[+ xmKm﮵q$blmu漢^r bO+|;$%#""Y"~8 ]# x֭m͡*;B/MĚ&X>v1[8p/5nք>ȡ[:a u1u#Kt~m z;]|XU>k,`;ӤIl9?;_/[;x  x(1H i f*MO(RTdŋKh:d"e!| eeb 3௞<#5nT+vKHWq}T#@B[}1$+e52] 鍸(3HvW7{VP7a!6{D5_.x5a I !^ =Vk#ƈaQc^P~9bX"]|Bi'fe k/kX:MH6ÕAVLas{@[ӭ[7mr+95/x!![-+7spƽm"V.5;\/k#x80 zRt9NxB+VӰJ7^wNֽ,ju<-[([~^W3Jt q= F|Xws?|a2tR__㧪}m%CP½SmvݱQkf &w eT|{nu}}HMp7E x Exr+9^1Kb ġ`'42{aч1a=X(uXڠ`4.X(qn;F\'t~l{m(:%^'ߵVu#hXL'`-D@K8 {ӱO}/ vک#^k3\{k2A KF\حYm[D{#y3~nYU. Wx,`7?z}U(X"<p v[F4`˦< H~꬙ZS{2Mj5Jԫ;GY֦aK;ʼ߈iE%d 3Sie|7+|jXtך)mї32|HjN!/xk=Yv7^߇S!` 6`dMhoevGGνkU*r?: Oޖ]U{WL1s l⣣RJ~GX *)j+#9]<j:| m$ڽyxPPqmS<,5M6U >"y[]_xذa&Pv1yX5RH tCj m gx!2v˗7G+>ނƹVbkJ"0lM$@7Ù d{oe˖dUzpo!SE$@$@$@$7Ђ$$@$@$@$@$@$@$ 5~2! mI mrR$@$@$@$@$@$@5Tk8K$@$@$@$@$@$p[[~ 7 ܖߖ_+'E$@$@$@$@$@$_#@s$@$@$@$@$@$@%*I pxxx*,'      Hb'IHHHHHH "e @_'      `? Ѹ\ IENDB`graphql-ruby-2.2.17/guides/operation_store/overview.md000066400000000000000000000120531476434635200231230ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Overview desc: Learn how persisted queries work and how OperationStore implements them. index: 0 pro: true --- `GraphQL::Pro::OperationStore` uses `Rack` and a storage backend ({% internal_link "ActiveRecord", "/operation_store/active_record_backend" %} or {% internal_link "Redis", "/operation_store/active_record_backend" %}) to maintain a normalized, deduplicated database of _persisted queries_ for your GraphQL system. In this guide, you'll find: - [Description of persisted queries](#what-are-persisted-queries) - [The rationale](#why-persisted-queries) behind them - [How `OperationStore` works](#how-it-works), in brief In other guides, you can read more about: - {% internal_link "Getting Started","/operation_store/getting_started" %} installing `OperationStore` in your app - {% internal_link "Workflow","/operation_store/client_workflow" %} and usage for client apps - {% internal_link "Authentication","/operation_store/access_control" %} for the sync API - {% internal_link "Server Management","/operation_store/server_management" %} after your system is running Also, you can find a [demo app on GitHub](https://github.com/rmosolgo/graphql-pro-operation-store-example). ## What are Persisted Queries? _Persisted queries_ are GraphQL queries (`query`, `mutation`, or `subscription`) that are saved on the server and invoked by clients by _reference_. In this arrangement, clients don't send GraphQL queries over the network. Instead, clients send: - __Client name__, to identify the client who is making the request - __Query alias__, to specify which stored operation to run - __Query variables__, to provide values for the stored operation Then, the server uses the identifier to fetch the full GraphQL document from the database. Without persisted queries, clients send the whole document: ```ruby # Before, without persisted queries query_string = "query GetUserDetails($userId: ID!) { ... }" MyGraphQLEndpoint.post({ query: query_string, operationName: "GetUserDetails", variables: { userId: "100" }, }) ``` But with persisted queries, the full document isn't sent because the server already has a copy of it: ```ruby # After, with persisted queries: MyGraphQLEndpoint.post({ operationId: { "relay-app-v1/fc84dbba3623383fdc", # client name / query alias (eg, @relayHash) variables: { userId: "100" }, }) ``` ## Why Persisted Queries? Using persisted queries improves the _security_, _efficiency_ and _visibility_ of your GraphQL system. ### Security Persisted queries improve security because you can reject arbitrary GraphQL queries, removing an attack vector from your system. The query database serves a whitelist, so you can be sure that no unexpected queries will hit your system. For example, after all clients have migrated to persisted queries, you can reject arbitrary GraphQL in production: ```ruby # app/controllers/graphql_controller.rb if Rails.env.production? && params[:query].present? # Reject arbitrary GraphQL in production: render json: { errors: [{ message: "Raw GraphQL is not accepted" }]} else # ... end ``` ### Efficiency Persisted queries improve the _efficiency_ of your system by reducing HTTP traffic. Instead of repeatedly sending GraphQL over the wire, queries are fetched from the database, so your requests require less bandwidth. For example, _before_ using persisted queries, the entire query is sent to the server: {{ "/operation_store/request_before.png" | link_to_img:"GraphQL request without persisted queries" }} But _after_ using persisted queries, only the query identification info is sent to the server: {{ "/operation_store/request_after.png" | link_to_img:"GraphQL request with persisted queries" }} ### Visibility Persisted queries improve _visibility_ because you can track GraphQL usage from a single location. `OperationStore` maintains an index of type, field and argument usage so that you can analyze your traffic. {{ "/operation_store/operation_index.png" | link_to_img:"Index of GraphQL usage with persisted queries" }} ## How it Works `OperationStore` uses tables in your database to store normalized, deduplicated GraphQL strings. The database is immutable: new operations may be added, but operations are never modified or removed. When clients {% internal_link "sync their operations","/operation_store/client_workflow" %}, requests are {% internal_link "authenticated","/operation_store/access_control" %}, then the incoming GraphQL is validated, normalized, and added to the database if needed. Also, the incoming client name is associated with all operations in the payload. Then, at runtime, clients send an _operation ID_ to run a persisted query. It looks like this in `params`: ```ruby params[:operationId] # => "relay-app-v1/810c97f6631001..." ``` `OperationStore` uses this to fetch the matching operation from the database. From there, the query is evaluated normally. ## Getting Started See the {% internal_link "getting started guide","/operation_store/getting_started" %} to add `OperationStore` to your app. graphql-ruby-2.2.17/guides/operation_store/redis_backend.md000066400000000000000000000011071476434635200240300ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Redis Backend desc: Storing persisted queries with Redis index: 3 pro: true --- `OperationStore` can use Redis to store persisted queries. Pass a `redis:` option when adding the plugin: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::OperationStore, redis: Redis.new end ``` (You can initialize `Redis` with any options you need.) __Note:__ Be sure that this Redis instance is configured as a _persistent database_, not as a cache. You don't want it to throw away old keys! graphql-ruby-2.2.17/guides/operation_store/request_after.png000066400000000000000000000702361476434635200243210ustar00rootroot00000000000000PNG  IHDRb$|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 98 951 1 ç9@IDATx@GG(`ĊbkbCD{E{cbXbWL,`GR`#w^L7o~SܷvO&P' p' p' pGL#֝ p' p' p' q' p' p' pGOߴ ddL9 SS])pF|8+^G#Z7vΥR^лq\ q}h?-mXHyW0"+VU%at3 O<`d^^' p' p' G/35ȀKK0j6k׮úǑGm0^Ƈҡ@A )]<0ZQ0o3sӖ۬8N8N8N l;/F```L,GΑVSP0RWL˸ _Ͽ-_Ģ`8}hnc{ ދK~ Ea ie1pTyM0שh "zaƮ|6~)Բ^x/+NO'0{fE'҉84n;l92 *~:,EDU|\LƩ>VbV֨u,cQݡ ~eٶA_G]@70@զ1o#1C-XSһ T\EnRT(.]eUp4\H?!MXy( UIi<'lFl b3Ԉ ӔyxY%3c0AۤGؽdΆQ ?C5='Vm0(6jEtfu#O7m0Zuw0W =|n&dhyIؽ z bL$' p' p'ȶsóE8f(dD'x$ٌ;a=?ne{4+YpߗfhݶprJp}QZ(Biģx?H ~Jzޡ*o Gє]*($x}]/}ߒKF>Z^Zιu1*߽-)cL&B^hlWMDܕ1ƱLAPsx#BZ1O-9X =Qrԫm?._CPEh'Yo%D8@=' %@06mx<_fU)cHSǜ' p' p@ qҹ3^/E-,,ĸt5)dҟ1@=w.:H@AGKS8~`BE p]x~y&h]\唌r5UVS-QeOz'zZvDhҬ4Μ ]Mw}]5FV{#3\\3( Ѱ+1r#U{?Q*=!UI1<`\0V p`3j*DnumyM2?8N8N8D qkie;b.i'K;|u]1Kk&ѡhL{bWnYӖcZ2nߺZ:SIO%#UqʠgiM/,4ȰK@9KЦWq;"K'A#?vn;V$dRUZnr,q,K9ul\<$÷l+wx5nU/zBE:%Yq*(qq_P O"~ 1z-J4FS2UF/jzP|+>Cڱ-a%݇;(M:mRHHwGM4%3K1baz4Im1{ű4% aCt|:`)H;`m (Ow:s4^HS{*Qt"XYFest_֬5Qb(UdݼwasRZ ˖;(_Xdޤ)kDƊޠmz2=+ަd]߻ [v>n)Xm*ڢZN#Ӱr*Z4RՀ%=G,S=UuB*$ͿP>{**.}By*F;Ԃ,86 tѰwD$^ >|Ǚp*`2 íZ7-޽&/3+׃5.duX,'–#7n5Qܐek8t2Mٛf^W,k֜DBV:#atC["zK#Vc+:f8N8N85n u&{T.*an FU:cw,ދFT.߭I/Vڍ;NhQz+m3?T u= Đر,s 60bSV_2gH5[SE% uڢUzt.^X;^f2kC+_PZזdG7tC=41oL+id˞ǜdRcd1[BR2 )d1{hJ2l@ϼ j 0bghPlC1M,VbI.I#I{YAlߦQSp#z y]íkux]N@=k\+-g'a+p^+YXms>2apW~'ʨg:YRcoihG' p' p'  B8$#?̢2+Mt,"`Ӱq-]OU?ӓ<BQ[Xe}d%ɔAشd2l`Y$):GkŅ:evޒ}VbrOTji!SOi fqњ#^̥jk.~E|xgvM [{PAԧ;yԑi>9hgαMz 7Gy~ p' p' p;Z7-b ;{tkT PlWR+e,(P\T_| ϔ0>#im}2lY՚#ќ39AѮu7tAnI{zBuQ]AnAyVq7?8N8N8wNeKL&Ƕ;v>B '<' p' p' pʸq9N8N8N><9.4q' p' p' pm>x2' p' p' pO~q 9N8N8N!|dN8N8N87n?>r' p' p' Cɜ' p' p' p>׸P@~*8kޝ^x8_?N*q' |>H6xHʍV"V ( ) 3#}|V_"Z3|r(j{$G#?R#AOޠ/JC:FʦpX-*^eN̫VO7Woyonm7hM9mm)O?"3NZ[V4raΐϳp' p@A|ƭvcI^ ۂu\Dp)R]vVmKgO_O ~2v|c~w1Q4d f伈 otj\=1Y%ʰ 13Gw,;BV鎉r_4Zztnl4w]q;8D/z & lZv.Ի:`ۑe!Xqry2Ӌۤ^RPͩ/Kr' p@[[l  qqBzQd?7\p(3Nx,Ƥ =J Kg¥XyD,=TU6AYK" ףUiIB<.\LT%\$Dz~'gPJV9T{gBYBh0j&FuJ RAFQ Bmj. q"-.s|y qÅ ̖B'Ÿg¥;RziQm}bRJܲRd J}& r(+Y|^K%ag"E:g  >pF ńSΫ2K1i x *J8땹&=n\ bn܉(kg v%X([Z|Hv]3+2"palUc eO7T?.4@V0![}8_|~莕T/{K6өqTgA*8ך``Ne] 49(kΐT| lboY׵"?CkD^cǫP !GcdYZʲuoem]z ;;yuz^ԬLD^:j+h % AgJdQXBs<5#U1jnW W lV(-:_h6Y#Ս @=Ptnl GP4M a{'=ۚ3 $ݺE뷣 et3ް}.:~w:s=+R>j$}$USz!dc+܅~j.ߋ+ 6{&9IFɚ?Ǣ}.lkǼV趕iR2GYx!֩'} O%*gTY@35ˤW[4Y+nj 9-sQH1Pmf/GJX:-<KVˆuc6P6^/4T@^? /FN8 G4҅JMDM>Anq GWs?|(6EI#ynyF J׬窼f%_a8;\ |:2`fmc; VG' p' ĩ;J*%<":ːms7Fq70lJEv ao mp'hG@=2ğB&_ iwH࿏RBd/_![ٶ樦h n=xpeZڡc`ؐ~6$3.\C$C lpJ(e?n5GɩA3@dUtgX &䚕qd-Ns~~dG~uUJ#1tetjeT0/U |#~zegyѕ#r!Fu*'x~v%^ِh8l#WaEdOAN˱O|;W_kJ [sv dz8W)mqNJ7=/9PmW ymoY;VG!_9j6oAߜ&ݜbʨKXnW{u\r WkSt'PFr=2 %Ʋ~)"Y;VvsukTϿʨش5dݾ@waK/D2CAڤG8r1yHcOQ,z.̩ v 1͈$ Ck%|._I[ +|q /'CϰI:qXpDžFlmE@ft`?#g>gkӻ._߇ :bBAg8N  u&VS5H c)Zr}뗚1ַ^_G !QGTCte/R (.Dz8w}=yR=YM/h#9㻆R6U/ %Yrr&.hr3XLQ;3OU>oƢC] LPn!dwljlg Pt }nER(Ky1dp`zrj8xjʌ6_7DjK˕1UQ/tK?$(3x|-cFzq VӁ Y*}y.WH/h𵬾V-{31K[JBVW{t k3K_u8(c!\l)Yofz 4VN,茶*|r؏jʹ)9v,,DYa[sz̹^ڢvIb.;,⛧MrUvF5r[3F5,SD(\TfEONm#bj]aogI!nZrXpmUT& iu~ukjTᵒƿYo|׏nTjLEr]}hFuyMT+Cj쏝hrOt3(Ბ?ZMA=ct3\' pGF@ƞ9t7I{᚟vYDw ]A!,]:LNDe+]_S]6&BJf~L5lXƈm+ӛv4ⓩ~:J "CB&Ezijez:~7CvՁWP6ζ:i<Ɗ:YMj}W`/MʀUB7:7UVꭴ{s5KL9j-kC%%ݛ*7Y mb;lkƿ6/ynk}XQ||R`mV7ݧurΧ p' p!AQ>FQ 3=m]ə蹽 ZCiӻf|Rߜ' p'qx N (cNk J?U8N8N(n p' p' p'(ૅ?p8N8N8Nq' p' p' p?n~}[ p' p' p7nw|5 A~S5.IXer;_|Z+pu_ȃ7Irę5%>AOCx' p' pEo?/c$%QK12 ,/_MfM0!$ibA |ձ=~O6}ǖ2dg2UmG9AT3>t )J$E:O7axk!6yYOh_@/O3(Ql9ЮV#8N8N8nܾN@TpIIw|]i珅I8b!Pۊ5,>S w5q+뾎Hئj\WP6cP:,JFlYo6"7BWX{ Fg`,8N8N8C&s2^jsx~Zi!ѡ5G1]UP /0t()66m_$_C ˸ JVd=D f n(7>F11۹`ܺ/Pr&’^!0`3**&>vO QNޙ k"/3cA9 b66*tEeoB){:Nr|-wZ-HWWw <vv|,3F.@ QF?y="}f*  >d*7pb֮4lF+!W~'D>4l`d#aS-GYPt iD3Q^6`C՛QDe9~(q8j' p' p'$*I nd G!". ߆2r` WPsDi#Ȏ!K( dǞ4,&fLfH4FE1(L)aiL*#{ܐMnڐ"/-JS9ŕ'Ɉc[fdR9֨.Ȁs, WɕnGikQE70nMZUA^0o? щ04%NeUG90miTg$;>S'"`t-CM~rTW[-k,AG`8MqAQc(7}Db(u-`p+gF9۽5Bs^JCDRmLlh4~s!Xx@9ep)hO3e6hW v(6" Owoy ?2L <}d2Ft/?9ϜvFl&(մ3_qVYdQTer&m3rd| J=j׳NW@m*q' p' pEqKeB^ L0s/C!5 ۜIc;O"H%t)L XF:.po8֭83E:Sɐ4׉(CJIof*a-a"ˀ"R)S \ q~XR̭Y}vzZG2#v)17+ib_ d9K!C<хML%d2lhǚUpcg: Nz޵?P%y0BľΈ>Jn] A: `Ag,HҧPFz>N Ӝv*-óۑI!W4FgV\?KHZ BEc,ȒMEf:X&Jg 3N8N8N |n(N•#~bC}Pўn|'~D*VI9tY R|I[[dDG^:EH}a#;yb(B7e3Z@v(^FI.ZQz{KѠdu?DM},Nj]'2%ڢ,XA]G[$3y >sE5[Ć!>gi^Ea4R (l7Z ~+;¹3m {w#Rt~4ڭ5-x-R"#j|tFKi p$2U/"mrRKԾŶU j.D r SYJm;xZWnV ̥gpYdfh٫*,0k/Ъܪ J; !G!߬?Њ+^'Ro M` {iSKLPȄIPw XWgDzB)+4>U [mL6X3VN%'\Gh8Qb!NH>fE$wdk2_7dmFT%@6h;3;qyLҴZvmO/Q2jFru##h9jvք)QjJvn~IU}OiSB^pd"-U7ɫSFѧ;Dg_BERCGxϢ Ul%}TA {O cɽ7ǪBOdV+M?VO=Fk/=]$5+nKh0*|+ʙSrBzANdQY jϓ%Q{4Gqԧ(j9Y9~( vJ?'yL]mSLڝ{t{I^.L>ѹll9 ' p' p'&z/,p-7Θ,<8N8N8Nqg~.oP:78N8N8N@?OV[y,' p' p' p)|_Dn,N8N8N8#ܸ?LN8N8Nq_ ^ p' |x8Nxq&; >FO qg>Ԑ`$'wDΨh%!>ާ}3!FCؘ DPgPO[]m =b2T7ߔl]9z9$G#?R&?-Fozs41@F5y5aZWi ֥W1z5/[Ή}x3:[OrU[ˡCx p3;RXhRzeNNڿUP I~).P]b%ap%dTM{)^N՟vcSukR% pf:~8/MO7Em)[Z(6Ǐ I)*D]B[SMS|{A$eS\$8/Nv,拘>RH.>:ZO jM=M& SG#s4e5j kSzU}޿Ӕ)bw$>Ej~l  6rBPzSBm4.!yQMbad+QB@6٩zWhJnq<8N-vn1m4HIַR[ -ICױ}f}cg!?腆r"ֱly [Y.X{ &}pm1ߘ۸nwv,f`Q7_@n]cZM#12d=]r7G-0NM$|}i7_EʕrvƌgPtSh5֘[NX=8 >~Y:Va?v}}.D{ M{j=GS_zwp/F-Kj7x~!߉qfR1mc#q.M/v21۹pi7 !IMj|g,Ge uF qUx qw}wÝul-5I^x#w^ݣS[oȊqO- ]S0V傋7g1gY9s!J 9sܖ{M'Zl<6FvE>PJZ+gj֥WszK?&9UWǸprJJ#F#O4Yy=Zﭝ Iq' qdee8yxM+X SM Ŵ?Tf.0_SfXq,]I?pWr <&nȃS'?89R3EA !M`VabR;B6?T.p:L\9r ^׺-'FOm =9aZ:Q2Zc6 n,IDATLr!yBc&շp2 S;ޚxuJ8zW6Hi%vz1dS&0 ^ۦ&1Ui+WNk7`wep^p,Je82yɋqhS˚m6:isk1d7F]]kY6VrXWY&哛!y((1:14ڰ ^` };ej XhגfTWiTv6 M nHZ Cg-:/; EĘyCaG8zb!9]ggaՀP)PQcTPU}05¬ͣ!ꔫ iP@H~惼oĝW5ئT$<7B֍KӪ5`ħy 'P|Zɬ+A/ZGcڷ 4=d1&V CH,c{w{H լz/.v60k1pJ)af,X|dَèF%{DzR$ף4QM۩3oilV#ܓU{-ԯT^⍦!|ԁzb>!x/zr;I#Аyq$&Pރip7V ݏE͍5&ED5"uհ洘L>έƎ.z  \.{k挺(z|ckYԉz!{")@%)ZIY{5bUx=@I3Bq3k'Ѱ+1 m׎=kz_uEj ֎=Iif;x`s)P}=Z|V1?8Nx[n9`!?,ܼy3kKf ǟqjlp'--U}|Bt=8^HLUx.fOyvS%_fy GBK•' !fac/V;r0iGe^ ׄԴXOsX.)Mq\nm\,$=>/̠:73'O _]Tyܙv.'$RR Bvm"!Zx / T[S$N6HtHv WB 3m^uuAAqmKA 5.N$wSڸWUH},$)n_mqenv .݉PǠ;NNat ! T! !\׍10fP\e ׸Jt9>C g䒪v3_h_)r{nv\bveo徝toqڝ|Mp'}+,Q9Hw/Zı&>2kG*A =ZCcCqsV9N:yq ^Z$85 똩%D05 vdD1W3TvKkhBGGvl*2.Q{F;G_E n ԃh=k* |a}ddH[k LI-fܘfKO(P1oȐU fEPh"e$#S}Lm,G5:ƖRQK-Pش4,?beB gJ⤎`ߊ(D_7@Np"P(UOYl(a2&1sB 2f+UEs<1ng02B 'ͭ~Li$BX?~^TDMi*R*ayf%klx)3-e#rw9nrDy`WT9׫=o 'tl큧Spyq B**P~9n"74cT0|mMЯ ^NX0v@'6@T܆Yߍнh.ԧ솅Ca譡H;0Bz)DOON?vD{|/&j}A;P׊\?"pb-C 5fѸvPKx@s7Û`0څ}6T-H_}m'y!7!ef~Cjn'H5a,n>03F(طz(DIoTS>9cÛ: ʣj&έhF b>Fbh,lݗ"ShYIÍFa~+":aqӖf`BL;qZ- ൻOQmdmuJ;1!uua-P-+gNmUjk"Ɔ.; G'}F{ZjY^=}VǒmgVvHַ3?u_eN?OԍfWZ7xt2J^tչ 0&1nײeKDFDawlo;6Xd(ɾ9W:Nk0^~2a}TaQo^<Zv=<67ǼFQ&T$GL| f"OdnutX:#ƷAu/]A==dM6ng2Y.uV()ēwYN{;;oFE!}yJt@8|g|+>xg')>D0ڱhDKlj?ZqT{f|_1BgYo,C-a5/Ӧ˜H{ E0 o%W  [٘"/ס]ԨVۈ u28zoyhG<2Îb*W\ E] Fq-<^ݕy;Oҽ{ t\Ѝ:~|`gFW4;0(&R[AIf}L:*M[I!ҁk# ySnpdvi ]ˤ"=QCΏP|qF@ֈv|dt"‹]ܳPMR]Fb$ʠM m\0`dHs<)"M2 a^Cw%w1L܍M1 O3d2"eGc)oN àX@R?R/k)cWϕ;]{yg,Jt~ƠǛcUZQQl?: ǔ*VH?x }G\Mĵ2ZI.cR|_z7?;Zf)7S<ԯ9ʿ*~!Uꣃ"_x!*`L +*^ {rEb]oպT:*sw32I,**@;ve%$^NG#(᣹V_UZ'־Muҏ9QUIҁFT~U-T"-n*Y)vX #%ͩZf^hcqbv<>U!*y/*VAm nVE't#w !WԎʄr /^Je%(G]1YQx},*Qr1qw-WyYʹV:;0^sB_Oe*S*>bZ9e9_a'O-sr}\6٬C ~fZsĨAaevݵ(CϰCrauZ?Βޒ~\Qov㑢4K"ѧT>le͈rvo? BE}FQ=ƣ$jFCb8@WHy< 0&`L ؤqkzM_SNʼn' ҫ}hpGdc0{dlm\Ywkf6(>`2 n7'J2=0z.2ZUʳIu18py L_46_^(J+Ri{ S'`poV[iE/%y83.)QAl0ftX?`*->$ ?Ż?#⓸t*;1l:4w"?qaBxdvΞ=e?1 ;u_Ӡ2 hcKGI->J;G=x2eMFys;㵸؟Ph\yzV)l?0 ߍ~JbLsжC[q#7X*#+Ju%y'` }s}=Dە<Ōǐz*N%oCv($JD$jIzl޼/*jWWbȳr`L 0&aL/ֶjA/pTN D;)mi_fu bYbBD{I8Uoj\f}eP .莦N?k̨@1 sO5v`k($~OuP!5DG!tFTX(2`KyJݻk}T}x/`5TA \G p(JAøP_osOxK%(W=<{Ó^A0=/  }4rucR.RY.du؝݆I/5NUUTIQQ~ v.\|-Bz5~/Hi#MhW_vnf5 o-i蜡IcR?Z۫ ,,n).T(EY0\C-UD/^sE#xXgsϞԞO;(C;9g(4Bw| uNL 0&`LOۇzaaa DjڐPE~n-{Dw"$2d[kJɪ2I2ʩ:RBiuRJ.Njmjy2GS;\xaL9[v8;ӱ? })Y>.@γQX7LGkWJm6:ߏ"2x<U oZ?Ǘ3b0blS f~mB2Kr͌poQR*nN8j:nW -LwUW;ԆNx~sBR%bi4dͲDMrXTh|UJ,bcGtj\ *7$zѵ*N2֢#2&`L 0$Pb5!wA޽-uD)bbp5/w2eXDM%z?@(# %Qooѻ)` 磨H-}\K SpK0깸879r_ΥB߬+sɋ i$]kYTCW :6|ѯ!鐕 Ϟ4z1> *fR us]2Jh.i~3,3Tv&AxW!ˊƖTY~ٟ=:\2M-GQOE,0Ш,S "!&VfHdQX8HF8zȅ+9zVaJP7b u#?f/.Z ,QOUJVZQc^.dL 0&`er@-[DdD$6V= mfpU*x;z;C3sre `󋐼Ix Lf#0 ybͭӓXH?"^"CA+bwe8YF~)ڤݷ_fȆݍAdQNwW~@FQ1zpbWLMK`Qg.V}t01K^]u;Ώ+t\InJ2в>0MJ .g,%>郻cQBö|;я|ۀ>k7"jE[ >7>55MkiluC,+JŏNñGL7@Tv"WfǺ>l4F 8Y]Z((638>Gb5" ʳ9WaL 0&`6K@d!tNn{`E#M2=]5և;6EMxWG޿r1/ o)ۍϺEg #YB/ybc]Ԕϝ*sW-PGe CwQ?T%.ǨҕN^ Aȫ+FuXf c0O/w`L 0&'q{?1r_L$U!f<.ϔ7ź;)܊ 0&`L 06n`LG#x5pL 0&`LJۿ̱L 0&`L 0&`$`%&`L 0&`V` H\ 0&`L 0&l=?`L 0&`L XA[+ q&`L 0&mlvL 0&`L 0&`6nU`L 0&`L qk1&`L 0&ظWaL 0&`L 0&ƭmk`L 0&`V` H\ 0&`L 0&l=?`L 0&`L XA[+ q&`L 0&mlvL 0&`L 0&`6nU`L 0&`L ? ]IENDB`graphql-ruby-2.2.17/guides/operation_store/request_before.png000066400000000000000000000551461476434635200244650ustar00rootroot00000000000000PNG  IHDR5]HF|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 93 821 1 dC@IDATx\y*(*޸W*iNͲ2#GVL-]4W+E \w hhý=9sss*4d 0&`L 0&0{Af`L 0&`Vj"0&`L 0&B`.> 0&`L 0Vj0&`L 0&B(*C6MŴ]aii%*胉cDiC}cl.kX^,oĻ_Ooᵉkis)؏&[Ȳ]fG/83G0eg.'`L 0&_#Pd+5I]Yc){F "7yg Z~[zF$˝[k %!{(&9#0 J"X. Ukh[i&q3&`L 0\Yp!"Wƍ͊~'(,TrS_\w~ pIqn& yuf/ލ$J=$WGKfT=bJ9~K~̦9X{4)is#DqH~آd%;FLʀP'yѢ$#o^<N'޳x{+6]#ע4mKi 蚰sD~1 Svo$OD~Հ-SEtXbKT^.QQ2Pnⷯ@9{@\ 4w}?h (4°q=~;Z{liY߼;K[gNFdгRvnae8v-, W?w9 sۊ(Y$6Z`֬/Ø<~e Դ ETO0Cd+3)hg>(u  *186IQaL 0&x~L z{: ;fʨo_G;s';=J&&۾o".> ~8 .!<>FNP#C$&B-B*PF|-"}+)HH@ȶips y|D# +Qi \'nG͌#߈!04n-{1bLl#rJ8:wv_@M=tjSqjՏ)瀔DOi'Mj-}݃/fqq%?"CT| 5 _s)1m9$&e/S1T @ObnUǡi>/cߑ\4= A ܙ"!LE?p"&  .BMG}uo1mpp!Oj"R V6˼#)'ꄛ8>DͨGO sv\D졟}Fࣁmo/f`L 0&#+5\Ѯ];?~BFni.n[RFJwG[z f?n<7=Hslu5"7#b%Ց2E8rӪQDĨsoRw/D{o<KJc#xTi鴞RyLUʑvw ǚ=st-Okj8x`w΀A܏"%]Ӗ>erhJCT~\Ke'>= ezXV!}6nFjo$>gBv`sVNGm%:_łFŵ xw [__{t fG24x roΣRoh+GVB5URhD̪z(  uvm輓Ƽ F28cJf w`;n%Ny—ʱ w &`L 0gD _z–-ʋvz1.f` ĆECm? ngpKO•gp3 \^4E]Y9J@OSVhJY}?JbGGM6Ti5d`EJ%?'oa=*{`۾t86vnc~HOCbU$yx7m/ưɈ?mwb]^vSMs3T_IK :Vp4NCAdPQp/?#H&٥XL]8]B{(;<_1&`L <{-d;wK@#ht$6X2]ū6cۖ z0@55k6,v&tзN ]V.d4߿~{mϋWc]H/KdllWj-') m@yX|cB0 Rd'yYᲤI%պ5-V+;aZY ;,FH.sBDXI{n kՔOp`r>(H6umgeV=[ҞSHs\m؃Xw,e낻ۥ ,&++MUѱn']ٺ .=j6CRM(3Xŗ% Gul+nP\+'߭'%QWZ68\yY"N[!jPR[:g=K`L 0&!`H`tۋkZb{{r9cP>WoŦTƻ Ƣ|žjIضe`^%?eJ}Gak/'"o:X!DZy1ܧs/mGD20櫞p"> ^y!pnm=FBgJ#0K850h@}U!Zu&7 kgt_Ҕ}:eWvvm&hBqr6\MΘ;*tF)ӗ/MH}:-"ʕV>e$y|N;ɊZ^Kg`8F0tZB89%R~5Q"zvի~KTǔ}vԴiٗp|6M)HYE[ľ3p3Ly~VǗOoAKv:;`;-7&9o [8 cׂI,9ul|`L 0& (4du㑔nE:|VǮFw1zJ>n9~<4aSëO+&vdG0kؕ1$W$k:)CVJԲncךpzzS!ikieIeJhZ䲗|i_(/ZH(^P6<*S[e|P?JA:54kiGrk" JDtsL 0&`ώsYt,a^ͬHIHHX';'dG=M VJQt@dͣp$_ge<~~ HZ߼rPr;^o3Z*QDҎ䌘|Ұ eޖ5"]̨dL 0&xJMarA2Ihb5)0e~`L 0&@q$PlebL 0&`L}bI1&`L 0&!JM>p؉ 0&`L 0O_F,!`L 0&``&8`L 0&@'JM/# 0&`L 0|RvbL 0&`Lx1 *տ#sp&`L 0& b\2FW"G5T\ yX8 `L 0&`ŏ@1&)#M!x' ſH=Ww|c/ 88(`L 0&`ŇQ+ .DPP0øq㌺ KUB#"Pj 6'0&`L 0&2xf=ffyv͔Q Ze)J|đ0&`L 0&PL2`\Ѯ]< ;ۨpgf=DmgOۺLů7PDI`L 0&(*LzŵkT0pg*EԠJLq߽}5yɳ `L 0&xz~`=xyNɾ׫V֠GpI'w[;^?FƳ3!wĶڑ:YëE}JQ(ƲuspW;0C8Q8;wybe &`L X)MI&buD;N¼0x4/L/ӏ^3Py4WX+}hjOɄ.h4Bz zƎdi ӷQ"l]g#tY@mۂҵ"1i#؛P8[ ֕)WhLg&`L ,^h%WKl[diXU-z̓ )Y-QZo}ҷbp3)l y5;ԟ9 ohQ:OH=jJ(K efԔxU&}xꍎ_{h-,`IWi4ӿ%u[/|һ>B¦^q_ >/TÇ  :潉"B27E > c%[T! PA&B QEh9JW8~<4;R\6F΀:>+ 4db>ԟ Nڐ;F,&"hFlG=aG|g_>Cѝ(n(Bwof? M TwAVy(8ՒY-+9k jjLӮYp/Ka20J簔b~4v"ÍhQd Pnd=Ld S).S.݅;|tKZ2duGK;Ѭ(qlӢT)tɆ 0&`/=d.(!0!u ތ`ZHRʌu-Ix9OIYý灶3@ npƻ_+ }J¨7r+m7?AٙȚc_.*)4"}.IԖrj8R &+NlZx'K@jb\Y$R@q Hhj  hdو3df4TiȚp͛ЪĆVE[k8*%2ћdO&AAkɳQV2cq? m=zI["j/*0}K)HC=Q݆gCEsK 5UV 2Tʖr@D%)-J*."E75RNoT[I 7s8UCc2֙`L 0&_ r(5TR{&{bVVD%&wfPD kXX.m6ʂ.h Nˏ uJ E!.-l F_&-TRY ;X(Pe#26/My}Mź 1d)tX[p@d'◍ZZz$?maI,I 9ہV_] 1aεA *hy2K76Ek"@p.AmmX:>:k0uA?5 (*Gs>he)=w 1iɊϡT 6TGo]c5M ;m`W5g``L 0(c,hxnT>~HXfCtM'Y N4$#ԱLҍ׈ԛ|na0 akr(LmN3i[ O uHCdi@guԫdN13"zJBHKgʴm %l${eN-Aʢ:WlB1Xy2c+bҤm`) H3*%)Y H D G89"zDJ J8.)I[)LA+kdtۇ=X!R:FڅsPW֥;*8|v)x#^)+55hl')R[HlMIHq!{Wl&\mvEl" #QE4#JKXb`L 0& $+5UnH,s?(l3CNyp.\;5t5e6a0ܳwMvsx9{h`;y\OM/)PI:t)A4}rIvBfWKOyy24Y"?o3/!xP=Z.߻!絻eN+/"6\u~]V?Z/%6AZ^&R=QGܧ6ňMɁ~916j3c G(ҊG~?"Qx"{:o֞)u403`/GDy*Ȏ~Qvy=Q}4-(g#0O'#餉IqO -K]rL 0&ox#ete 5Pi&܈bL 0&`L&+5ȸtg`L 0&q/Rc-2_7#zsKSu'˲ q)qL @׫ b6QgW?u;lݧPjy\_n/^ a#@Wo_5Pz̷.c37\{lYUnׯ-;J܂w`Ba!-O N0UdUg@g&>AYGpu%b}iS! 77­m&UYw}T5יL܀[ua~=saMcG. :PuHnpj Tͣ=T㐺d5UQޤhYI?"sל/}8uuO3Rbp6Zߊύ qlP=]r8jP檧|^G1_r͚䵿4; 9郝0TD`+n[cP/7~+St-q}n$' w?䚥 4Q^=?i[([&@ETlnGʼۦPmczs1b.~? ܀aM ۍ"m[[C%RnZK\R+4 /Ĝ~:_H ځi=7ԴM٬1Fn 1zB,H Ch'ft62(=MaD~Eĩ7.Fgv*lT} hw_P&Ǚ#- w%F7+oC׾rYؕ#\kaWZPz9NnM K(S'W'VI@9;\X al-'wS\LI$g2YUX; +mx W# RʏˏhSDjkS\6ҷj Z^Rw>C$gS#2AIS$湟Den'ݻޟ;]}̯,E{w fFx3i(5 R?G#{H~iV.#5 uC2,=x7$>+M =5 |hgсܼw{Wh&ZMڨyN85;~%!l?>2.H<>JH吾0gCm0u>#j'!zn͆"b%9}jIΕS`55#PWUm RۙFYnPVm4$?p^J7wE *TnHy#cQeOAkoye>j ܎wIh@3b.2"iiU)X,(NDW yG)OE܋G ٺYߓF"(OR,_huLOZ;Û;chPTTY_-_^hYF@QN*T]; G*UP2G hM2 c28)-OėHZE[Ԍe([nNVԌTԞ/qh8]LD[ pTYX|VuXn íWWWOZFGzpJ@CH@Vt mafh]Z*^}y3O]Gi2KQ'(9V~ ,%{VK(M{"kC3diw,^f"κE]ڂPk(supw<\Zj^U{%짆!mD@9;S\|S=Pô˱~@L N޺#w3XVVuiǭ R6E+r$[a5})ov?嶲Υ(mk,;b`vT^ie8y>;T7T= 쩙H݅nÒĽ?`)ofK Nv+ n?CZϜ[h- YNyqg, i}i\~@ZJ_c'nf(oz3T3K *V6Mq-ʛ'J7 iFY׼}M\+:@qH,s ڶ5B3.hƤ DIT[nJO@Mއ3I,% (7IR +'5c罟L{t+p v~_tFT@+E 9ޏ >f)i*O[iDd1VɍXvIWy5lř՘ZJ9d#f4#zb4R<#Ve@b}Λ!:zQ]ؖJX>ך{[(EoQ.Ƒu rQn7QtLY4C& Sx0#Havj"W q4o"nX]vL:n8nKayTC"GxPcuRgҩ84f(,>K G >yOk%y;C]lsniV-w7\?VP9T63v܅V lCA)H>ԣO@@12/LhA[d>"1T"$M#nR)6T4x0}6.toQYF񧫄2+!%ʾU̓VXA[ގ,T|tZjb\YD-:<i-DpвA]Jl.][fu|CF<UZ: >RNԕ܍}$WW&|CJʠSfHJJjCR\5]`ѸR (QsoRh Ꝍ^L˺JeoQ3q?rvU2czIH gʧ-o721*1K"~ KdTʮƒkǎ]gP?Bii,Jv6f|%A?=[A3-q!&W1ƕf[k8*K+)gdݫ'GgjcLqu*B<鈉pGUK|g ?) c ]ְ=&ֳ:(bO#Toɲ~Xӽa-Wq3b}zC76FL Q;Ci5zmmU_8dݣi|auzfTͪf T#i2uTmsq O 5SX+2[Aj[Jv0wmߔ||1z$ y˗CfކƳJi#FUjԖ2xD kXT-ʣ4PG Vbݏ[м]o %HctbSB4 'NlBh@h \maҌR([RǢJ C8? 9)}. nV yh$نjk*D8ka`P\0R~gjAdhh21vb׵2nlUޡ9 ^ e | \h0V{7S[W3TFLٲ6:/"s n~ Uh9ز[vw Ώ p&`"Ď2K76Ej-\+Ppql֦hMMCu*GC&?Ym*DL%Ԥkhl|YHkCS&rQTҕa&zut 34@MV4Σ6CXP?8ՓK&hU_ܝN#NCz9Mah7his{PC&^}p(e5-Ƕ\}-n.ja% 4R;r'ŅJ4#ΟģpM[R&)ڪrQ3U=X܂vT &`dla)~ޑɫ&XKQ;`g[#*KaF1"Fk:FAcvkp>%C£mPh5WR~/]H+ 5¬8Dh@̷{,Qn+u`SVLxHѦ͠zi<ʓAKR? |Mԕ RD*40-`lb1cHNSһp@Χ.KqZ8Ef'dJ1EOCޘbD[lQј }|)tPfE9]XԀ-mYhgM g46wc,\vi4PS vR݂O˼WhUvIH*+SY%#e>G$0ŕܑu6R N^AY /[{.'S{"QVM/Q-njRYuST _ hk8 jϫ<3@km1ԮPۢЮtKغ8FR QgvJXREB4s9>SmIϠʛ6OHΆ ؁SݩM.R?/<}~<+ F.9_&rW.ՠ^UR6=} u]P^i|0&W7G @! ,E.;`5JV㊲q4qD}>me8Fsj^TiC\Ee ;WT=xbp@=,jr4 ,\f+;4{]C bn!?&H/Wà:p7Xy1 Ȣ!Zcs+m\JRX/̅$ W@u \~ ,?\^n-~B݌fj!Yah蘈wHrNoՕθ>:Jr7j]QmDth4nD&e"ޖ5mߤG <pvM~9 LߊqF lQ0sc0)ŭu,=Ҿ0ӌ& zMIDATiYb9=w{ɲ[7'WA+ply~RGrHD/h "49Nj hC_$RvR:bA~\典jAJPW4X5+ 9FtiVHWIw;]*SԾ"`T#Vu%-vk.=]K0c Gr2]Wt,bAQV>6,!i8.Dn(D(^lPt8B0m8DԦ&8hRR鍬IF__Y!Ȕv9KUL0 IO;cΊ=>ַ泽AUWߏm^(Ya֞/euGL@5^ _ӏڕ`f+-qqB,{pOR*wo_J=(;<ץBhlRmy 8}+%4)469]ۧ ViM$=f^vӉMr]yLV}-\S!ԮhKVeȑoh1t-,#B4GEre-7GV-dR Wa{ ~U,+NRNg6or&&d! R!ܺNVӃh>_|j=#jfX~g.WY_6ח*Џ1o~P@'}H?M _8}ԝOۂWh&OP}juYil8/ eĽh'D:o_}hQ&4e e7E+\f'nzzێSTOы iew6C;ZeG\OY=F#]rM(I\b(F"{P5vOP|)*ڢK+/ ,))?t!_זyi^/_;(v;!h*6֖*ʶ8OE5| {<*z搚H=e ɜZȳ_MQM\i!r|@SpQ(?h}1q_䐠Š,@B˥蘨a':&MB#ۓ|G8kFS&;S4)$ia0`P lŀ݋mv4BeO BK?݄8gH li3ܚA۰'Nz घ G %"@.,p o# fG)Oy‰ie}+NғbKn ^nDOu/1d^9ئ( J =j}TU泊dܣe#,Y$78(zRRt<,9E|;`FЌV[POj=Y4⇭`LH ܎'u\sҙ7wf*9M%Uj66 0&`L 0B ٷ"`L 0&( Vj C0&`L 0&Pl RSl :}&`/Vj-O5"CqCt %738sgI'2ӶK ]*A[;iVo^BDBFg p#Aӊh# sj0n\_!&|3P֍-cD0ʫw0qzx;20s9YH ;]˖ܕ}LH>(yܚd$%#}~ }?˛RU`(!굥|㳰#$':y!c3 ΔK>+!z k<\6AloAӚ+3pmfJJ3X:5ŪT\vFՄDmQ=Jn1U1(+6』0Wk*{cEXKxkBv76B"Bp|@(lk BƯ!Q%5,x[-Q9U9>s[aWHȘ3?zW!?R[Ʃ-i>J8+Xy])aHI.hܬ1'|؁?`8jC) k,[n_jMHMFf<vF9 á %ދԦثcǎsѺLů7P<3L 0&(F tm|oDUQ;|n,Ij4ijdꂷVRkF%,]aG[r0_y#ҋۣ9в4Q }ff4כ$D1`$v\5,FyJE2qbyry*cS'}HصVsPY8َ9ݾ,W||jӾF廽ޣ;Z;oֽq o (bmO p(?IIBDЬ Q -yoPBUgUh Ty7h}%- ](,"V5vIޥz]tFbfH|AV:~#rg}>Tpp0(D\)롉lP _m;ʢcjVoMt㫨gZTgcQA#'([PXRxte[Il^{“ZPYJL1߽hƆ 0&`/FE^йsg]`ݽNTtE\nȳb{فN98Z7\4('i ƚBSYXb\LVVJh2eO%J+ )Rv7t,>lzWpN ~tpk]zFZC^UPI{${{o'Šn_1O黆BȌț23: B韶`sk#3{%(ګ_"%s@M*+*^k~ÏKA%V||JC" " J(wRoO$ńh`%< m,3 ^^H1?Ɯ\CV&ה"[!E=+ҺG1 YJF8Wq l3e'8Q_&`L  yb!VBС~tըB#o֣UޝQqSRlIin]Bbkm`GbGL?h4Q"i&@+X5F[Є,(,VR]a,vnR,{^>3ǁ9M30Aiэ]"w#j>1u׬^@dfW7>oNyH~8u!s^ k`Y.RTQM-2jtJjutFuu#j䝳aܞ ?& 奎oߊV8Uƍ[o`gW]8 (tR1ˍpotN [J!W`O9µ|v? w.LW .Gx{rE؛1wką}r(v;]šNcX;vR*ቴFgrˆq6-_^?3oY1*0\ Cg#t-Be7O3~ } ';9baL'َ(ïB c`q.@ Q $p3A.< A (kA58 ΀ 7}X}0 $-1EG F$IFR Dy )G Wrt!wy|B1z:uC}p4fh]nBh=zD0EbEa)X:&`XVaM=_Ǻ#N8<_ķx=ކ_{A+F%X<$BaPAK8N8>;" ]wL"%$n#"!E&yHlRtttG@V$A\L '"_#?#+(*x(D)p(VأФpUOaJ1xQ(YŔM:9EEE#EwɊ|E+^TQHUZQSb*>j . F3h)*Z ,]N)TPR^KeeSe_EG*(UTP2JWuPRU]_s5ZWmnjtnLsK{}DusP,2j44fkTjf` 3F(#qqi8qq+Ս6&KYyH'-VVZڸdY۵ixptJuEutctm ۬wVo@_J߀nm7Xopӗlc wv2zhL1v3N7^oj|"o➉^F^l]^LT>>l*,c7K?{?q[Ҁ@-2jBCֆ քk džo a!hN n҃HHAdC Z0<:?ѓ+'?qs!;#v8q- Sj'$'v'Ot%Y;ܘBJIHٛ24%pʆ)}SL5|ikOϙ~r Q*PZhִA?g#]yyҽӟgxeɬ_edz/{$'1P.975@M-h?s̮<뼒| p^"&j,Pǜv'qOwaeY V->jΊ9ϊ~mg8ow-@-h]hp¾ESg/ؾ%MK.ZSO%J%’}Dsҧ c ң}В١%Dv_"~&_@ 446 1150 1 Y@IDATx \Dd(zA%hc$%1fƤj,Wk>IڼIM߾6K㋶&ڪF%.u`(n!l^f޹s/s/333gy<9Ϲgȑw 9ϟϟ7NrD"@ D"@ -OK%D"@ D"@PF ?8Q,"@ D"@ D8diqMF"@ D"@ D@2(D D"@ D"5 L D"@ De"D"@ D"@@#@d$0 D"@ D"@ Ï2N"@ D"@ -~Z\D"@ D"@PF ?8Q," ;ǚ)D"@ D"@@'2 ?~/`ѭ"9joiK'a FT"l;k$6!D"@ D"ФtE'`Bl~\|Ei"RpV46^*pъ<;Cb$oG`BMe%.\g^[>K D"@ D 4{O1{0P_w=&@<$.jXH,|*о=,%A-a"@ D"@ 4^s`bP rv=b;2qVX`+1jf԰/l}cR߿8T D"@ DU?5Bi?3ժ#o5o F vy'ͺ@わM`W D"@ D", 4_ϽHXqx!n.c?pU%p5w)_wwѣSO7O}y\*%yoE D"@h9.~pײ;6gaEށ?v6|Qq?PW5]/a?~휓xޡ :) $&u7;  [VOgf%gp|(&Mfle\_~i~:y6ڠ'7c癩xd c{td!-<{ڈC;Y}ïנ,[>j)wcL0{o=˦v#s) `$3P~/aIYO>Nkr7!ub{Xx+\0 ops= gk45X: c`fgNzSxb2L3鵭Lr,xq09brn '=xO"b+˛c2^Jl=<ºk}1~H'<̟5ͧ2|Jo]0tꁘ^ dm}[ [/v+G}hQ1aiFx.}"@ D&# oh2qmqzd7O}.l'8/Y4FSso3O4 3<=|u=G6esW8m!+B}V@ڙm˝v 0ai0([$dQ.>֕ز9 7`UmacF^盅y@#fMU?(Ő%(2ςyZjt3b1OnϤ1k@3Fļ_`8z/1<1^Eɕԉ}/H _G(}5}poJџ:4E`Ӈ] '3Ji;WY>(9_G_o,>uEc/΂;erť3"@ D"@Ѽ⪋Kٲ!ײBtV^å>}xh6\v0._O: <2Yd؆8}!]Ơ>|rCS;{ 0#\[NFoρh/F.]D<0;-bBt^9I,XE2~HѝC~.r3|G7i\v,l^d~Mxp1O#[ctl˳6M8\ч.qzOZ b"]u5FΫ4Myo̼Wzч}:rznL D"@@0ӏ]0}L{tZ%O 8[qѻ{w>Ts R=|w਋-[8tWYqiߣ {ip5./7ZQ!= ,3vۣ\lf#vR_mroۖj~č\\/d'ˡj>Oxnvʟ{cMǚ=<_<ؾn 3wfݿ}M)NYهS9Κ 榆=*ŪS~.\^Vtx5^^^+ uAZV~8) bG`exq@E"kiꗂq;ˏΆ(i&D"@ D蕦) #Uڇ%{pKgCiRr JyͽCgU;4~V3WY7"V?6bpNEEqe1ɱgT!*fu<7931XO:"4w0R J3vLD?0p'/g0w gK€"Kzq3N{]%RzvDp3+>{$ퟡgh2QSMS30"*<=X7_0ioJÐ!85cIC}~Q2WV7,\`־l7@9IJqhĉ9m{xanQ1mxW}x7|\Y4苈Cu.w$\9n_*G"@ D"@%)DlB3ԗ¾uul@.A#*msxǶЁu%]:.lr/.V}| 1MKPig)P[ťA"zŜDT"ܖ .me&zd'oꈭWzY /;3-1sn|]C2y>|q\pܾ^^@Wįo۝Lx(?9ﶋ`N"V%[r6[6gnS^_&^6 m©B? hFqoa%;b~~ r λ'747^u+YA5b=[Ruvm5JO`Y IH]{TD [UĊi)?KC\$%= `8F#h\.d9lſ>_? W]ȺlσnŃfя[Uy Vu&ңZ֖qٽz1ؔ) `F{t>#Vm97*}qN5pE~U}THQ D"@7J?#8q)qEGlfߋ׳p)m| ΫDNȝ^@Ip#fUVb75t[B6mB[ĹflÝRV *K ݾp_k#F1?@I, n bYG6 ! [;h'WVv [8:{98=re|D \_d+/q}RdFt"@ D$Kz̡L wY߼jTCNa'bpEcKQjA7m]ًťYNf+f|b/g{XWrЗ8^]_c(I=?ĴTRGjKO6L:B}JPR*AuvDZA<>a.̤k\Qq-h|䷘1)E0]f_0]xqz3Xp#o<ryi%Ն5 @!4[?:KΡ2jjkѦ]tꖄF_+J0-'y :X3Is!vNmVg6LJʕ8mY58il|񢒟ŎAB3R_#}Z?uS}lNbWȼ8wŠgb D΂qo񬩩]:óo}Kp5 xm#z྄>8ak/JŞBKhjSlblxWU#0څ=|q>iZJ4D"@ DCft| )s q&цK<^.\ ]ށ9D>@͍3%d۸^sA }SĪ5$)?Dž¬rߘմujC'6 a[y>Kc{q`I|O\㘰MPwS_;˽T U ̈xٌYU[ ?4cGkѧ lyYvﳥUGO]DQ yMv )ױ]N݅/=g~N[X%hKLϏYElμѧt.^^yUL%@eyLbFԩRVq^r./:0 Fʫؾ%e7?ʩ5X)U (=C3f >Wc[l;+MV/ܜo<6}1kM{S=zD3o/^b  D"@ D9gȑw̟??7n#)3||^lYW>20A%P|ɹ[_ge"OޥnsY i:0^&w[ 8C"Ye(/e& IԦ{[E]:jsn-VODgf a3 }Yߙl%tnd|0 x Ӗ?DZX,3$8.6>OgYG0Oeb;zl\*.uۚmEc[D0wb܅MZ|Q/Ae]SF$Kgmt-7e@ D"@"MfSU[/b'qooy=U_[xi1].hd" D"@@I}h%!&?e`}bk>'޲K }iG"@ D ÏRD@)) Abȿ| =x'EG+Y棴$V:"@ D Ïw8S)D-F`DiYi0HZ,[җL$$ D"@h, |S!mJ({KOr61iw2%Sfn_|x݋Fq=Wg3sV)xlxLE;%1咑;%%]PgeʐV-           hQ:@3~ؗ/%0bl$SfJ4- t8Wr +_~'DW7cȮ¥z-Nֿ6 ~̪OOkT֔ D"@ D܅lߘګ8#zvAvPPpvI?f8bzv邲wW#@v]r .r-V 4{!Ĵx x *w0mZҌL 8}WŊ5U2˙#zar4́\YPNi9.\?(WS$.9I _M zӽj9Qu8})_̷8D(s-϶K/0bFujlY2U_~"@ D"@^V),Mٱ0唧e1w;8M[΅Z}PձeӖI-cn|H&՝;sV iIK|d3jL&9Ӗeebrfک\ey~2ɿ 3 dd6M9l΅K׀IKU嬽4~nRW7_QvNA\ !6w_f&] ȗC~wxer-#vYn>˜=Wp:6g:mw`_lsNĄ|;5()cWټJŒ%=WWU*/x@!-3ErJ޾ ;]4ڋ\ey~_lqWJSv? o]y_C8-X5CkT m2$Қ]5\4V8ehct=3o ?mAeش+l)̀t"@ D"@PH@{_AF:ʘ~f)kK.ҟ$.܊w&Eq ~~A0uDdxR|=bn7.;*)h:l#$q ĨiejANo>~QDc³3I$.zͤ:mT@i!Vǫ -wP.Z}'=~%.܌u 3,IǝغT(& D"@ D 1`T/sFQ wOêĦ(Z3*2|qshu3Tݐ‡Kc3iX9-Z~f&9(oGSJB NX<-=&Z۔`d| &!޳i=?T(+cձE0[* l ~*j`Ⱦe꫊p* nZw¸Wq?"@ D"@caN]E,~U2'qH}Ԗ`Hvw W;)~}Nl/B||>3E㕝bԛ;8/bxXZx-,lȴ ~b w +|nYM b%mPeT_ ҟM#/~;tA6#3 Ver8A.d*Ttg>+{ߠ]N.yX:8 ('"@ D"@h} 3`œؒ+DZMa*/Ls:w+:`{ʱ׎~Wv;:) )r[b? PK80DZR.*5oH"-7^QSrp+WۥKS{%e?H|¿:^z6b)0!fX5m,͟ʼnql)كi)F^VĈβ/ @ D"@ qFz<ͯrcv>.*>M^1j{MMxvnܪϦުߚ~&c_Z&ƶe"*wg~ߋ,?Yp9o&c1[~gX.nL=.3(ښzR!Fgդa%mV=fq㦹1yyv<pA$ZD~<$5 D"@ D02nylxmE5_- c$beNJx.4RG;͈ˮ7W Ki9+{}N.aS o8,21 ę@b .p.˕Ч4s_s=i'o?Mws&"Y/Xs@1Ŗ_B6K*pgL1 D"@ D%`6 wN>ͶY=Wٝ/.ܰ[{v!?w]2LRvDklʟot5XUX|gPrzqĸ\3zR#0m}y`=. m6cn7g8!j'dP9MP"ȑB{)#j U'ġKD"@ D"@~o/#wggY_ };νggIxk"|īp) 6rmyȱc~iW [7%^jr% &ѹS?E[ٛ?h0IbT10mcŕٲdxOH[7-;w,[Z(` 򙎆([4OV,'-vZjm{ll&,K?ضޭMM D"@ - ?oGlߧmnվy"xqob`g cSH}犙OKt쇑o@Ue-.^e`GV5Sb\##.'\:*?7iWo8lFJ<R+^->+;rvx1 JKnJoJ<"Z=lk~]6JfhSk:kH}W]sm{ȵN GLzS:n8eDlq/MB_^]#rm»+W)Pn~Yr3hZlX8ߠs"@ D"@ D/m|:y$Ԕ"]&6R "@ D"@ DY ?V "@ D"@ DExͫ~$  D"@ D"@Zdk*N D"@ Dvdi-L#D"@ D"@Zdk*N D"@ Dvdi-L#D"@ D"@Zdk*N D"@ Dvdi-L#D"@ D"@Z3D‹a(] *NZݲ&iij1l*lJχf$PLbTI߂<75$g,ӑ&.2@>3c5Qn^zqI.k?x~R!oɖڏd]p Spo73v!doY|}>~~ѮMӔo|}=!mgFhu8 E$znrnt|PSzY9>B:*PgM;^l&L~(aC'r}3w HCy)=S_KӴ㒶W\sdwόn1oVV֏x+.6MBLemu5Q_] _ 1{KH&x:#CR@0 -NIž0Ψ8{ч{0$P_| ?S[Tj̛ulyXgnOcs[oa AMAF/wj_c Ycyg{71>l9Ehx?"@ D;?ؒ dDBg-QZvD4/~By4\,B k[QStzG_ }q3i4~2 Ռ׺+cZi5:qZ{cF;i""'k(\B{hT@J8?+a8+=WgB^ߗ𔈭F_t1."_rPoJju|=5~˗shI\e.z3S(<}6 M{TJYNUjAm Dİ^VՖJ]/AU_^XXousc=Zsp2Qa^0Clud0j13s鮗mx' LqC0!89k⢶"O\֕_[y;lAq̑?FG#}4IJvgɶ-5.y}5JM`GCBB4Z~y8zƌ FNA;9O䝀ף.] ΈfEfϫs9kzmƔ'oJļyc }sGj /?N-|)%Uova`m(QAh"ERWb6~ 3VJSk:^b8Sv9tve&EEi{۞\flg`g1J PthX&'a,WRoa/8qaWSELZmah}>h +3#{sj{j~k-Uquy-㮵K֨_z+\TTs|DIÄb䞽qD% ^`|UU4ö~X3f=N0UTȏYRY>sf S0}3D<72S$9*idSQpmw0 q$'#'vcFZ\8X?L}hYCѧ`vKO0جn}tsg坐ver4*Se;8| "1| f$bmKCU$`hS_-+7ΞOJۏvqN%SX>86[*0̌IqZ[m >lw@~+3Vs%jR,# ݅YL[ ߁~}QE(؋"Nx_oFZ''Vk:`.`sZwO9KQ=l4 AH~G{ٻG%/OQ"UE'q'6c/XRW/aP8< #ǹh܍rsk4 ip+.sfv C}}lO~)3mtx} ^]x +h Kddoŏ3QB:ru,;l/h. KэJA<|AM;Em;lY? ,51wb^}2?>sFyw7'ˮܹ[v(>|4f~; 8b '6'>XLj3Nk3Ff\^}QE)ï]xm)h?x]xqJc}ps@IDAT =O*yZjM')樵2/-Pt\%kiXX2=3}I OWMO U.5(~]p%ynj?u[Ԍ6Ex06>yp$ě B38 IDŽi1a&"&2Vo45e$tL3\8j KI}E(*C [Re;LCư9}l.>%p7i%I;~E!'PJ2 padwhp9̜j=,8ܖvؚuX`PKq} /&.f[wǫ.8߄z&Z!qxaS~QM]XK&fӚEM5b֪gۏm~8o1~jxJԚ樵4^J ̥hN|KDɿoTy_sy~c{s~ښ^k+3ޗԣ/6.Zq+ .6UV~3$<ҔΘ~+3^ujz6r t=Wp.dzhG nVj.#Kԍ CsN!4#,ž}ysZ] @F` SZV$s7a?j6ޭ ?VrߣRʌW_QCN&[Dۼam'mft\g.̽cXbD#QQ ?}Py;4RH㷵C2ج~vKlG #ˮ^i>M5B^c^ `K|"R …;a4q@d~?T\?zWI56f\z VxUԴ"G|pT0G讟JZHݫ@I%#eQHN߼Ԥ~.8`Sň̟TgƼ/)G_xZqd m6{h$<ҖΈ~t-Ns- =OsE[Ex6e8eW/Vj,?9_3{e!֗Y*K O`$RBt- í˛j9'2m1l[sۨ\^l}SL l1QiG/r7ZBPkUhf5:f_~L$<0bݼX#0hN5苦*7hZ\TךCm";KT{9yU8jwC?iҘz۽Y' o6K֛߉좓O" 5U54A,f}Z:=%"mku'M6y)]K-Ns͖[iDS\ak9wG5_-T-AN~l.+G+z~ -)?:8z^GYExveOJýloG4ˤ*o\tiR7(ʰ(6Gڡ09'ƛ1>Vc>.wΙGI%Md:ژ~ kfQQnn* c18.߄#kP!'7~zr6N̐8|ݧaa̱0"\0w"Vކ:"0練xL0+pnV !Vrȋ99jTaX4l~g"7+kd pI1/ӛ;qCѡȫrf~mF>s4;3J>Vl fy0i.^CzW:װ"߲? cP`!9=W[ckxZjKQP\"76S?<[Mu:㘙gFps Rurp.QwK y>HZ_li? z%cE3OW*SPfۖ^ɩv0ߪQD?w5ExbpS p=_-^{Maa:4ìO{S'`Ygug;fp`.9^OXS331c P^5/H$OF'sSSR6U;eX 1a_v.s`!3uE.D24v"_ FyFa#¯̹CW;|>k~Om9g.Uq#{U`ʆ= C"dnηε_r"8z0Jf'5<̃ 0+%|"SoA08q.kf ct Yeh@}qd9Sou׬3[W#==]n0LwTAu:BUG#FGW$%v,=*;Rsy:C|4j}ˢi3ߗˆ4-7b >`"XYH>md4@a2!}kpw0}PNER{?l߼$SS_z烐`a,O!mfPYCWyI띛\aSdƆ0^ť fIZOM2X}' 5G` 1qqnS|9b-na.ȶ'c}S^7>2CuT9?^(DE-31]+X9ѧو+@ q1J_doN'j񚫻LT[?eo_Uܷ?n_cѧǿuEޑqI9 r:o=#G#gq㤗ՇG'Qzd3 B0Gg?2Oyشڢc̸b̌<vWLsTL^~-Pvq b1 3]+.6^+dFh`$ϙ6X4 5g˺2C#3d'E(`k=ףHiB8thJ^WEd37u_ƥ粲 /mFft /kJaxԎE6*=mP2^u =s.S鹚i%yx<#5簑:&l {s] #dZI Z]F陫%zHϝ4{Ͱ6zĺlXʀqlt?W+.C{W=jfE(1K@8Ee5udh}HM*[q튆\]'ʟDN%$Q67(N#jm>o}81r~QlS_<"~Q.3xMQ^.-jiZ 3iyjZ맕t155Q!X]Q6V.n3usSkyz^f|P/nв18oɿ/{<wQf[P}5@e;hT?rjʪ'o>YG74 ?1l̐_:6tcf28r쳀kD@8ÉwPSب(t }[Z- '{ea/VTN+%@aZD" ?.ÿ'0% A(*.A[64;wa2jۋ=aMtS^ 4jVf/"xgyYA)C^h/N㧱pԚNfL[Ÿ^RpEd eٜEcjN5}V1zƍR"@thkHy@oDݜL])c s|غg0 ÜXIlʰU&cz7eE<[vxsT!-Âs? }gfIKp#@6k,ԚNGłٮ&>21ʢ3:}E:k^@_H-ԬimL:RR"p7h!χ4s jV\`j]U]a>QrQ2LnU!/*SUH Hz;JtPGu`;Zֻy?"kH_< qD=VZimlknt顥Rz__tU#5೶1Gj`GIL=ZaSlO+l D"@ D"@"@35"I"@ D"@ D O3k"@ D"@ F ÏQ$)"@ D"@ D43dif B"@ D"@ D(d1$C D"@ DfF ?ͬAH"@ D"@ DE ?F| D"@ D"53yH"@ D"2D‹O$ؗHߑ2d&) w5h$$uD[??X,UY<DL,nwfeU  e2(h)s0K Sz{l?^;2׌ EbO [F? 3SbWoҷ(Nnw]^~Fo2c}ft@Hcs*-G?G)Fޏ.V9GơerwΝ4N˩eroҼO)G\>Ab^7' Gblwdx Ep~Y1X&}-*J-LK&;9p,:> Kt_):q1uq':c%XWNfa~Ͽs'>~b:G }Eճo*T,F~no<1w#)fmUUwڌ)OO{kx"-&/>Tу0qX/\ $ .׏=lVm}qv:Q6@~$U]l*~7.%8o'1|ۣ#W~1H_|1kG1#^zw t9aq&ijpŗsucۏc$$c8>mma'qᐏČ$xeEWpdFdW?u4M,Q=DIFLQOaLb7`$C쏬YCk٠ '#uP ǞՑ]vу-c[. FN|~DݒUȒIhUL."dmq6~"+EGjl,Ydl<}ތLD7F_"ܬ 6hY8fPDX47ƾ5Rۮvh#CRԩ/`@da_Ew[$`6ރ ?ZөSͣ"AQ]BE[PDP A1c88L9OƛcYܙzN;jʹukj*7299usy1R1/#18EA[D`޻w ^Yg=YpEnOY.dlebЇGݷ|(%ؼkk ;^(or("s`ƂZ~paߧ9,V Sh(IKstd$[=;CuػDy:K"y/\ҘcD[c^aI&H6a+ǏvY" %MN8cK<Ȃ<f4~V*i8 Um }nO—jP8֜mÁa& 6ůtIÕoA.=9 bNH0)w U $ea[yk]Q }C AX d/gWFl(PϊBYkOZq q;~Ϩ6zB7PZ'bEuZۑV>9Q1-=%BP+ xGv1őf0Wl>E"V׉gf\ >OL.==~¨'&3;*&>ӔȄD}WϜАx`A'N: 3vhŖ7B>-AX;k @@nFv`{% l%Q݉#HxXMv0b!~ڶNz1l/ƾt 阎 $ܑV׏~m9Hw/n͒YYVe *onjnR*l  9PZ s/iXPMjV-9$L1ֿ',i=iYt<l.ArN7%㟄d=6lr obljsXwKuƃc- rF2/J[j[3"FY0]uOpFmfWg#ߎj9؎}yp*Fg yszujJ4 _߿! P%dP-c3 ~Q 4ԯVOW jyA|DΒex $ڑ0ě4)($#@y>$pXxEB \q;; Pq(8Ά }bg*ᡜ8y yS2gdSR'A(sc__1?0tOF]Uq"?ga\B4=%1ґ,,7. DWB!7D([S/ڋz7Hh%$J^b?˟vМg}r*4,{vd네5NSuDٰ¤PW;~JX\n8/dW;2>W':SKHZ9 lF,;ϖ|9$͛[dw0y+^ G:O6 5qNN%6"Co<%z2UN7jj&x"g,qAOPI򜓩fڃy큋.iNCjdܒY%~2.>\SgTWU QU[Y)4&yN6IZ#26ϧlMLm0r|aCWL 9 ǿ|P/bƇzz6 뜅/DFDv(KԃΦIp4"o( e)Yw#,́t 'tgȹZµd;4E.}ͤZEXo;t>GAGKQgoQH :gNY0'(щ?Qݎ~>/? A/=$0b P2sԐ尿L7-?b+oDnYw_EAj?\F>y zUȷQ,q SH$GQV[/.)[gѨd<0J* VB eKu [ҍ~qqa^6YL?aHqIxޯH :zKV]"1 [D_i_A^Vh֬w|x,bQPM;=DGgԋKqZZ(5D6CMx3^?9&*PQ Gw%nˀ'bU'd?#`e[E|^:Ҽ~70$ƢEc؏IVƢDy$DIj-HFExQ27c&ld@6΄@y5&=DJ* —?`Й}7 Yleؿ,=OxƣrxFtrCa4j4ȧ[6zI F6 sdDøu$yt*?*H!7^@%Xp?)ս Wb߮T2x2 ]gC৊ w}wSݴ"3+f`%#a1FMߠ$]~# AoFCӫ)P֍ i$ԗ Ə}ܻۃ'#dӒ3E$ܲg^`~WʗJA MG$?>LFށ!)kwGz{የ{k3u,?ǏwB_Զ#GՐ+D&#G~m6VN!~5 "0&\, ,/M~7=R^@Br}7/ lǿ0I J(LG Zb wtth. I9{1~;~tBO EkgiӼ~>2J>N۝!5 lzs>?8 ^_DouVa$d_Uť7IP3G̷N;30ʜ$G~$=x}/L`щiRu$ dEGq'+~bЉƕ=#{{z;̖&4I6xq,}ƍEg7)LJB˥{[Gv`ciŹ4GDZLg .ʄeLTb#Ynݞy~޾{+,(ټ%+R !mNNBrsOJMtEB}6Kez ~ļhcM%#O[mvp)SMBq^k&qutՑ ñTBBo`௥()q T舌#!k6Nj X_S{SŀC a򏡇AiR$(xjRB}8"iBbZ׍&D'$8A(([C ^1T:m؋JNiV?$n\Kf2dLR@UH|7\O l6Q2@ y/cKN( NHB ZЂ؁o8BZ`'9b_]Q&v6tgZi衮db0-@m ~Jo30O{q-6Szʁ˓/!æ=NH6d8!*'7R5qniV?9gw\n@ 7!sDʾ 1 p 5%7ge 9R+M~ يށJE I_Ab*W.#W>HHch> &8% D{?qs, JVowJp$g*@<_ϝ8 =_t8Ҽ-jc^6mp&`FKy6^Bȩz1KBB 6 R> 1m`<ǖ>ò`h\罼 ξ'6NNvZ9zR ^2!)&W+?֜Ĭ$ѣ/7q͡.s&?Cɠeߐ:)U9i {%%Ы-bhN}OFK (G?< /VRjG>') %:Nj8X6;1FQn6E_($vӱϊz)|(D \mq!M$DE(%X+T w@1yG] bpO}WAOs0IKXg]ո4rzOGsL 'R;_Uhy(KvN^FEqF?F\9QӐ'n>ǬZkAo=pGP0*Օkn-NnW; ՐOdו s*L!LĘO1U2a3kJЇ1m8 Qe":8Zڭ>g>B_- ^*cl2)Dp,ߎm4L$uf>\.Y#Ybvc"Y6؆Bx]$TV")`&ȏx?tY]9wAhdž]PzD<IQu'OCoتN[5CxۈY")f$(y>CsG|UFN~(ςl60en$41N4?GvԽ_`5` '>|M]-hvd>A^WEZbHBILӎġCx5?ڀ؎D4)FqE{m;L h3r*j̛}t-s4rϴ 4{8 m#5-L_ݎ=(%m&K=עSzBF4f:OF>^;^ h4{q|Ђ ގi3'z=5OS9qcZ%t??#/V& Ɗ`wAW5i#t:vQTʂl el=_F92AZoBQ=~g- 6{:pa,pŰFE)o]NI4gip6咺:;5Έ )6jHHL DF'ŁTp}?mU dg'B,~Ywn1nwOqѐevCvdf_\i+?iL],&y6 تʐO&"ijujˇ;OBS96HS@qDbO|ʇ%͛Ӈі-d̥ZЧqKǹHW0Lnҍ_>P0i,߄يa-hr%7 BrEKb;ZenD 9LNsYvJn}lF4oMGȀ,IC7}q K`4[k%k`= Gn5Єu)>ݎtf|W3c>TX(6aq+ UvbFvR-Lm(Ub ~NC;JÏ(.f,` U|S7=yoy( 52RuËQCZ̓n^.*LB~R??$~Y2YTq Ɍ&z ՛֣0mX 8) ^I@#BdҜs[_FޖέY{P/gގI%-[b?DF:R(]q"ۃY9PNrs0Gz`*Hj*.f/V;2炞tXOBs9 9WgBHمn^+ģq*S1Ao?蓱h5`cg-:]i2yQxg5G)*8aG '1Hz^*mxj\p-<\Ֆ؅X 7 %pWȱOG`vCݵ~Aȗ8%n8n҂!' O"b:jg ܃s҅<,x;oY̠!=_O@)x&ugτAr:~ PMuRo ㋋!=+ڟ/p s̚7PD6@3s7ZO=wkrMЂ5d)xS%a.a}cA;G.\d| jc_Nb.b H# q7)k =|.KfkVNm(6}4'"֜Eh> #%5MX넬zf$s@IDATEkG M^|ޔ~~'˖,Hϐ"qs ņi+]o]Qxx;uA466Oy³faL_ZUヌ+•o6Pr]ww۷7ףf{Sx۫E :7^ ЇhN!;b-8;v#[ߵHljs\j~M2gsns r[pboX7vDF(]uZ~tvFI]ޕb,յ>?1^QU2S}>/n7n_B8*KP[{S[m7zS|pTc $<`? f0l:A+=< b;뒵+_)N~S!Z5O[훂⩟OTFg-sFTV ?y:Qm7ݗ.h8OkHبɭ8u[H;! mّ7͐w[Qۺ h/ity8I(\3BU55kʳT$x=kQ!7pfȼ.,WW3vrz6 ԳzS"L9r&ԎgU|ތ~~" (ܹrZ5Tj?Ӟˁ3,)M2>kEnuT'Jtgfdʍٞka/6χe\Ɲ$ҳ[ԏ7leWނcm0:dSX+vIVmxyw1͇̐¢*ێ}(Xڂxqȓk&6K(/$j3X@s%^G:ǍEKPƳ:F(Ц׆A+|CrJ4.,,A} c,qf]<~9[ȹ,MܵZxeճxYճ[Μy Ʒx`p3I;&ba2Gqak'ێšv>\xϛ~K?PF!,7]iіh+*PH<yO)=Mmص 6:LK~ 'iUzGd=0hN0ru)fÉ8m/! r%4>xHTA&'M`%nK8v:88D( YZSM}ű]41M_d=U$ {sM%i^(L^ ^\k uf|[Wէ;qG {jiJjG=ghɒI V?uBgvF>]Kj"+ MrtW m BNԥ='IW'mc9dw!'0`y/(}vVJ*Cw>76`EQ!·^DIgO *O~^Y?i^ #=lYN$a*=Nݏzz w>zwvitez9I*2ۑJ _wLa~r@b*ɰ^0D-~VNQ !&Rxc6k5pwz۳"c8h-+; 6DZ_:d1IY]A]ɩ>p+n'6-wOan1Gzmu5_'Lxia:w :W^ZEn|gkuU4+EatTFt">:԰>ab;r6 Gf:>-F ىU#3~QAlx^x!φDIHkaV }x6Fft(x ቍgV;_6E%Ӂ\}ϴsȓQ\*?c폎ytܱ'8bL;a14ar(*ĬVx]STq|Ri }-^ϥhlY(̙x!a<Ou  ੸nkЌܡ;J崗I5UVxk/.E޼ ٚ$*i>bV顤@߽q+#g>/όχgzBߍXhc&qQ ~>yW5`L+lNQOqPU}&s*g"O7RlGqc%0 -`fbV'N7ICŊ!9| 62rǟnM8hHD_2%g iz+n Cwıf^_2GM ͢N08I9Lbal$b @ˑ.kĞ |ItRKWNr@ `<0Z̗͓xb0ɞ}5L/#tbJz&͡c{0w h8%1acah+n¼!<&-XKꐫ ]f`0<8x" {6ѥ9ab{G-W>DZVY5勐1`L9g4b(+ê4o52Xe0@fU-/B6ݺh˱R>2B~s8:>S?$oUzΜ_Bt>2hm z'C! ΰoJgEdX9&i3ٌ3lC!PBQB@=b*cA^a9לa!<+;]S؟yqa:ujϟKjX܌ GF!Uq#9f9`0"4~LFMo[Y~ ]ICSMl*]puB0 ?Td0 py8!`0 C!`0 C@&Qy3 C!`0 C!0t/A?C!`0 C!`0 G`0 C!`0 C`#?ӽ C!`0 C!`0``7C!`0 C!`0 Ld3 C!`0 C!P@ ~wqExER ޟbLZ6Q `0 @a9x-v*@p>m<W,m؋dWmDG쓹l|XEU"|r0撟xj3uxʑ<ڋO|y5rj>x0s I>7>E+?[)6Ydzkhnԛڷ>hvnD,$S4Nu~8;m4yQQȘ6_ #n^n-߬v;=9L.,uC!`A ~&džm! 3!&KJC6z":1}r:IJ\8LDz Ϙg{h7e񦥞MD&f"H718#!)^ka2-;JJHHom7ŷKNsq6U?"HQg o?q$3IlyE.YӢoȯv; -w6 h!`0&1k#/[8Kč_\ ?s}LcR)!3WP|.nCXML=a*kPfKx5S?f^F3\6~͒vEyv\DbUlVpӍ3p)PR ͻ&i=Ow͘nB=\q!bAFz6r.ĜEqQ$9u> %~(\׾µ{"hF<a[s/NnڱGC!`LǔDiWϜPEق#\pu8:eo|̄jg&$HRcgM!IGGOFH6V՝8iGlڪ [BGC4\t5*I#D%<hXqMlěEشM9uKP&%X!`0fل2ɡ8KDŽo'MrW*?{u)y(.7>bn͇q %Do;HGj0o\ L@UOƼ^*?B #:6G: &^. wѱ`x-{y9?ޑ!:!,7@{ԗ4gRj6{1P;jGt|s2yփ@,b.>6{萯A&I .!D)XGZhsYy'Y-V!|a ~P(Ώ)Nx͊z jꙐWiyփb R4I{/)[gxBAI?F+?fgx(m:{0L+ŔVR-4'%QeBF,cNc} Ag{k.Hp—eQA_q w}wE[K*221Z҄\BIE)փnަp?/Fv]-5^,OJM=B)mSXleؿ,r_ PWhmVl Dƽw?DvJX!EY~65]xJ02 wG+ R2mzIMm8&Յ$YQˏ+i(:}<8 khuozn}sQOsً݉x,ŭhGz;唻YM4=>;?x8|:"rbbW#;X$, Iii4zx!jvq#Q37H[lXV@D#r2k8BƎḢ-U@1y=L}]Hq?C`t ׀V?'*Ҁ⳦1! k8p9A]=ہr'F0>ԍߓ'F oMx1,qahVN=E. RB3p1f4)F[]Gfn8x0j t"ᩒYU, AAE0Չ/#tS xq"rj; yob%t-D) ~=QQ?fp4ԵuPW_]ncî}(~GN]ԏlk)w44I7Nm} OS}_`D`"xe4 C!>Vc^]SիZ_ ]lm6jpti=x<%Px?<*.LFn\-7ʧ 6wҸ"_ωC焴Bݽ2`G@4wn8(pn=dT֗""!SS rܱoxꮟ9j D]$&IXQ &7&фg1Q{~^1’;9=LʻxCH0_;qc.Kʻ֎@.)a,$)| ~ <~_ r7wz\LjrIL1/}r@7/^Y C! `\w`jGG(^8t+f,Wj [k$_z%Z/+&n.(ݰR;㏕ Is8lB-ߎĵs A_xp_'Z^dD\WI4oGahL+\}=p Dž| A=F ψ^p'&\:N9*Ry r6/95څw'JjD4-mlK:#5b6,lWK/.R4t&gܚi@:tW k3ZLG MORJ1'ꋡGNTԞ >j(*i>Rxt3yp䎺r.YϜGgt=:u1JUDSE9 i^e!`0SZPbXMNɰ>AȡaVKlzve ?d5zV2qK$1> j@_E.Obqe ~FP'8ƗT',zzTf6Vc3X%[b7kmNSʟ޴3a%jID,uxP_` k":4j0Nw'=(L\1 C!0]`Ao?n2> XtTi[aVݵ~[a$rbuu•Ɨ5Ǔj=oP^¾̣tJ`$4PԮ],G{q-'3ғoCӤn&pWFWE^*Gz4q#Ưz_$9#aS9I9E7x/.~t_>!5~a,_d{sPCXrDŖG˻0&y1$ODoʋ?rIȟzNMQSЀXΟ <.wu)6;ڼt-C!`00.[vFc>ƅR8y.J'gFepuyYVFwzF y*ż a2z9e&6kx-y)9K baLK*$WZo Φbuصw A}WO님I@?>mQ(Gh[l%xE<<҄-2嫱HrG$|IF}s1$ۜl6Au-f$!9o]!_81丫OyJ+Gu%R۳O85+hK%GYl@}#J'_'b^i .HX#&"|:*|DX3+חz`PZڑ!';|l\wc` ȰGM ^!3*e%(_2QxHcO:g@E!t}tQWYMfwldXg uf(G[tpi{Q\K6\!DFv ŒD߀]AY^Z =yVɢa/*阊{Q[4i/f)Ǐ."OFq1xj0+ni٠]W!D޵PZUY3I;n x]j{*[n$(>ZhH)ߝ >}B#{q) =%$%cc*Ӳ宔+A~ 3;٪+ʃ-s&^ w;\!v< {Z]cich[bvbx^!`0 LO(pOôMrNTujK4N(dѬzhY0']Nzx?/f੅Yy\P֬޷6.ntȪEϳz4hHD?]WuZqW2P\t;Y0}2?k-w}Ei!`0 C7l(oiMXw[ ˶Yn߉muFbkts/Ba0 `!e C!Ə*AMod#?U寯~;[8z|=zkۄvTb=6 7N(K G9 0%{][9ķ`V1'p'8%/:aFw {a.uPNZwwP9# ~~I]Oj!mP:w>%˰4 ї2 C!`0 C`B$ . 0lͣxz[xpZqiH4Q/xc^?+>PZ#LܵC<ۑ%İ%@>F|'% BU>{FxWsC ; Nl'}a_ZW[aiWC!`0 C!0QLgKs3򰶡OA\*-& x5~/NO*b)w#`[ I[/ cuT"g>?mT3{A!`0 C!`L0wBn{l̯X5± #`0d}r&WlLxl?^4߆ }$T#C!`0 C!kl]A{t g/wKf"sX2fXNOqQtI0O/N[_G}y>xpF<(t&+¶9b`,hlh~BkulEk֠/i!&qÿ#Ϟ<`1g΀㻻m=_5a뢙D׾ąkx!߭<.ZAHE0;!=HEts4f̞L,]5٧?V*/rcp^ ]1?oq sn]BVp޾'$H}|X ?];; cøsq dG&e0`<-O=H>wE ~ֿ +_"Գh傮SA̘v$Fd^Y7?6z| cbnÅ$th/Z>Ã#xa C!`0 C`20U굗P[w~0o/$ƪ%3~MK bWa\w.~y:jmg}x-Q}}V8%>: q_#W!e,cZ[9$m i4,䆺}COH͠z)r.(5c2b2b}҇\h宻3yHLǪ}Pjvyb>wd4%kyp!oZ  6TϜ_H1?[R-07DHC!`0 C!+ ~^X=| }_Ҝ3xkۺ@,Q0|Qᰎ¿>2t7b Kfd%[?O.gЇOԯpfwVԯ[*KLMlB,J_^//Ȟ:,UZW 4H&'<ʁQ`}o'd1/2|RZl\ XkN!t~ʗ'Kwp6JHsIt]b 47.]u,P* mGZQ ?c.yFMLcx}w.UTO"  بSF !RS =}#UlPMJ( cR M@p)@~ٗ{Nf_r03kYߵְzVPȴ>}P>ƑK Iy&P Kd$ qX C/IWݕ>kj4(R:lSQgGp !]K-* w:ĺC;qY5&^mw[{@G~⏯Om8X~!q[KN]j(SǔgP*M11jh癫&      xaxU@STaފd5)>B\4^lLq,rwHpWuM*DvZ/kήNCQxS[ 4tmܢK.~HtCxYVchEsx*+`殘0<#JE={SbD~ѝ碛펗<^|ͱRnhʠ]8YJ=x$:jK7SYeç< d YG-skh;eE3h8Se?Dl]W86S7[G;8?]f[OT)X`pbTeيK~K*  ijݲ%EU]W{( yVj-޸ѣA$@$@$@$@$@O2cRHYX8Bpm ܓ,6 ez:tFg5VFZ.ζ s6tzr4CMeFZ/-c5ĈQC>OWuKjxjF >ҎfgvHۉϳb|kGw43:I$@$@$@$@$@-D >Gd_{/58vUM 3ȾuSַ$Tiȟ( OJF3yycSW3bӲ{Khسm^-eC:.Hv;+~=EڧϋstO a1-_2<nG]! zݲp텕ۦ*zd$uu*=Z!3ys҆f4v%+(@A{x]>_g^l-D4ԘG$@$@$@$@$@'O<9U=3/6|YE~blwߍX0MŠ SRʓ֗om/yP3YYi`ZOĐ f9{ *Ҿz"KPIc 0Yv;RvgsOKNjTTZ{dhylx#7}Yq=f,Gʄ +~au("q$6KZ+Vyi*QЪ}_4]FFuX{RbqUȱԥ3B:wE0E✷kt6JwE 5,Gdu'1o6^,@.NBfV{h4 `G6Ogf]~0ÅQqcƛ%3peːlߣ̣ D LD:i]5ʬ_}nϓ&.8W5떸xQdlB*Epa#e^74)x/QV{{f!U˵Oe]iѳx>'<+i'uN`YA@G\Z}.u7unnsB CmyFg7ULߖ] އبEyNUt爾h mFcFX!E Y*[ho ˶&JC$@$@$@$@$@$p%{וm!=ɯ.ENf>|B˪mZ*}HHHHHHM6 A"Х{-)Q^V8-Yn;(W҂װ@ rHHHHHHHTY?8e.зNXb ^\Lk= T$m@I~6:6B;t3'b䯣O2C$@$@$@$@$@$B $7e䑃HHHHHH:wno=FyIHHHHHHH$*~Lb2       hoio=FyIHHHHHHH$*~Lb2       hoio=FyIHHHHHHH$*~Lb2       hoio=FyIHHHHHHH$*~LrNԴ4;G6HHHHHZ@3(~1yNV+r -]|eJ/Aԩxp Z #@|r2M 4+q|66PrBl[pVڕ(;yukpt $%ߛ[b^/c[-ͦ[k8>=x}o?N9 pM܉Mb78spiTق3=שi|I2hqc%{-<|fi^mg ӑ H 8mJH0BCo+q۾"л{&rX/ĉ"}]L1qfwT|$흷f!7{5g?by_w%:y_# trȰ !^[o=|%V~1?Xs-ww~g55(="A~.QKno㳥̸NM :v {ߏ/@Ing?.;;vw b/t#sϠvպ5j?3\HP#F菏?آ\ [XȱaRKfF%>>`IM`ĒdVJeY2B H-6X6l0>6mdYyBc66siS,ؕ7>+x>s-D gK,bu`+ryۦXxGVn| Kb9;tmMSrn_gS~ˌtKɩs:K-x'-NeTN=yL[aqX O>ilM?\Z}QF%y@ƀ^飜,XI{Q]EtN@,TGBu>e y63/ mi˶Sܔ&|M,L/㳥Nj5Ut z(t.Xe#16⹉Z:_Scnl_k>VXr p 1 ? ye}1ڐOpҖ$@@v X+wʜ3W:SFrBa[8w"wݪƟ`R>{wů$vkGEQIH @GkϾibUIv%%M;ɉDS+iyS3[z4vA TlBMCZTwcߩagJД3>CDpND1!m"9c2 Ż3EXg8$b^{EًLxdL~*u֢ts2Peԇ HNNGi:4)>!gHz"@1v%Mݷ e{/upRp Lg=BooNśYe @bb_ =kv9GvlۓД&}n.px7|jj$ME_sF0|GF% :zYT,_<۽qV HIaȭ=P}ٲk>xɟ7tc*g8=]Vߢnr|%}\C#pKd82Kʾقuk>@^cP|k)~Ѥ XwY6,`}9 s$G {/рtݸ3_vHƧ|1ѷ8{ H>o퍐Zڷ ƛ|L0o烒.J/ľ=ѳy<ɫqOz>INƓ G PKrV|ZئL~,K'Ы%d^j,ӂ4K"-LP ƦZr׸Q)Gǒ5v_2iYڽm69~Wgf2rbWҊBm=KwP YK{6^ ϰ/W/c5jYf%(mK,s7|$L#m8ji~ə ,7XgS$3c+r1?fK%!Y+>+?vaWoAX.춤̵Tyڲjne;{ RN{hea/lR=Үs-gf^푉dY_F'5Z]Sw,j.%]trI_Rx,.4djm.<3>q8ͲWTS,[ڻr`|~G1-)-u7`Bc9ϔ\Y)UO2f>h /Ʃ?c[ly5R4cTOOy'\Ň|}~j<ɉccZU[͚"1~JJkbuPX^+c dK1ǨJՖ JLEGV,ž]PY]I|SS?YX< Cåo߈CG}O9EȞC5?:UUU*Tשk2})gBf.gE\#ɯ\}.W&e0K.%X0+ʹ{|,JETX+=彇{*E OGQgI/Y/X=^e{yyB)! ǿ9ִH#7rm#6?@D\Km=9T,)12lPk;J+q']`'IeGj۵vn %@lC 16N[jS>t u|>//yfI%~d}Q!*EUM:ut!˥<}⋘y.wBNI,s^BÅsu/`DLv:kQox~<ˬ!sx؝G>ɪM0XR[ *X9~(8dž߅lH}z+YMe5•sźGۿ9fq/NS> mTm\G>3 YV #׶KbV|m@|I[⛋[pq3Ty;_lڀΆDd?{O] <% @`,~4M:YЬ[`In'yb%ǓՏI ش’f#>RV,FdYi_,EAaH%jmiŐ\grZĤ]YUH;N6K4$#JGeic+X&'7ne.E Ga79EaɛliYcYBVdə츏9ڸ7솕";\"gxqzև[6XV,wd!6#>V/6PՕBKE1k%vm(N{ٞƂo߄\87Rmc >r1-KAnkheӬc?R`rhħX]n44a˦ī,N[_}'.tewβ6;]s;D vK[""7KCR">>_gXʔvXéXMMm+/U։݆lBt`N&Z&as<`Z]ˑo)鳷:Lϧw.s9wȩm1@P^3E1_4ڧU6KzIp,Bv˖UӜ-dQqمFdr8TodUuxXl2|~e?xz^i.:v.c?矴/Nu-k~&b ,鰞St!+#'+o Ge`{[kcc"]">P~v OL4L"wꝅw1T<;O_tqʕ 1bmTaY`)㢣c#3łGpr+Ku.uZ.c]%,[nRprz鳗)wAo薹dtZypٸpDa琹HGEڣ ,4Li?zҳb}V\߃1S(FeѫY^Ԕg繍%+ƔǓ02) Ir|O_[zَ$a"~˶w?x=n]ɇ֥yH<ҴpS\{ܫǧ/CyŚnv~k%=*sB:!((OqJz\!7 =řuA0:+OSX=Q>.==3#2_Ÿ {$ֻ'3;$=+xFik>Ec_BI)l_N.ymIif2ˣG;{M,CsgQ NVK{@J786pʩm(04 rq-%y=>u괰 ௰sAo݀j|-Mp zHM}kY}S\ 5OͣXb{#S`}J}M PwÀ1/kjʅc`>qQQ\?)矈|oYd_f𹑰}}>^.\9`g ]mi T[{J+P LMk"-֝Aak .yg+gۗa{%qFG5{>‼`Tc啜iQJ?ez~A6ǷdgN¤IS)>1&qR ~1˓^t5$JM][]g-gϵzC0ȠΊW8w VH,λUL>w5NaÖ9FE!^,p>_UJ;Xe85Xj=ޛ&8˞jL?x5E/P LD\LNDHf %/8B>bE:Gui42;c^y~ܗNQ/QvēȘiC}5;2&aaHiWf'/37?55t+)mEQ֔]loբX&hs.G?[=`t3REkp5רGs/a^m5]p,+9%.U=֥pT-ۉr([dK3;;`"d7yq߻7V|ĩj|惯3sAD ;oN{*Cs.CȞ[5`./I??M 2% +@?b̛2fbWbI!qJljKR4Tn,wb1clbzȞ/ 7Sr?PH; $b!gx6wG(gU!KѝIU@{xN~Zeԕm F!^$fBLkwO.NBZ|N1Eɲ>f &鬀EMWWE֫ei#/ǵu~5*Hz  WH6P <pGhyBCyDpK&:pp q̶`c%2Gg;D|ݱ_uBaGvs`Oqkenԧ\ 7O9ů)DD!j|뾀qqBb|)<m*n]YO5.OHH*$L+eHKTBh?Gd\wT_"Â4ۍ<2L Y-<84~%5Q\R蛻iu*ΫxŖ-VG!=o-z{Fb9ԓz\pqj8C6e] @*.γkj䨮Xͽ:}L.ˆS-^k%Vi H I+B`7pSx^l_˽ o:$8\,9}8XڽC%!y6\4ߥg.1f@8Rg/_c. JV Zwg_nzoj}fBusi78e)5=YI}w@惯3/C j,Λ}yP|)%`w_7 i\1cIH м+ÜIb}vR7r?t+6qSԖbR.k}9ejRu]L}^cU{T̠웺;l[ nuec=}g2b"fe4cp#hmD6'ʜ%i+9oEb˓0g L{(C7~/^nSgNTzc8pDž!7Y='q>%-O|9wCYSp)筽~Zrayֲ\,?}N+Ś#1~fL=6ve)N>=?M΄$@$pEhŏB+5;,)Û۩s^Wn+Y+Ћe;p}ϐQ^dl餚R WLLLjQ:.J{/"EK~wy.মkQf9v'ܫ̨n]iV\zP[6ro HwJw"`t~\7g?o@Yq>^aRk?ੜ 4e&Xa?6{mǿ*4|'0ytL3W-Y> p:M v%Q)n09n?f[J! j%-˰􃋁tW ⥿}='t?06B:4 ŢTfg gV:-ٛAR(G-aQy֒\T?G^ۙwo7~;[p#X2[Q(_b@zY cބݬ*-[`+12J8· ([ N [uN!7ls;JOƒ"g)*jv0%XY9e  Oݏbw{UFʧY觾3wӢcSbrVg4Vhݗ-n9ԈFRq+0.Y˰xXm]RKK2_/Mlk\L6H`&lWB aQ<;UR&bfLkB>|gSxs>{LXQ0RCdxzܞ78&d7iawI1G]ƅ@?\o7^3KtXtC DĘ!}m ;;r8;ƾoB0Μv c\?-sbE惣4)zeXoyԮ~7^[w}y~z(1!i.$=$un|NH'O|2'78#~2RɾJ>`S+wiB#"&%V%(UA:u̟ E!6&g[۝c>_\Y:Q^FJT:=5jd䮞u19&~QF"̊n2ssM>^$k]y+bw\hc+bHr3OȘ%NW :gsŧʥTJccxAИWn!5#͍["e~KK0!ٓU*Gi ƪRB=7-ΥQ,{°cbTO&Θrk>䰮+vc1NI"/3gb~r\(g;ġ+}@Z${&hPʉsbv7{r0F/Ɗ.7 s3r?CN1{\:q8㷈 V.wiDv{"3i͝%%eqvMwJq߭; 󸖒̎i lȝ_Vi3Ub7ѡ(a3O~\P{ᯛ>^ |]֘}zU9̬¥y[!Oü76\{vZc 8Zd\l¤a|߃]s-]Ɛ ԯgs9?McnjGŁPZVCpc"HuNNw,YƼw:l+v :<=b"7(|G|S#dQ.Zh`͚iCQ 2%`xѧPUy@/Z;)v{\*>ԥf)׳5mqL|E MIX=EʒEƞlQ!5wX= wI{+0{繤J 3~ OMFա#8z8^EqW Dvy -d5x4 &^2M(T㹂xlmC{A7^vVxϮP$‹pq]:x,eC9ѣYb+ywvEG>3vǷFg3*6רW6&y:Ui>z?|?>{I ):i|_?'PZq E>q>8U̩.Kc{d矂a /b?? ֽ11B>Zɽju+D݃2]]2t>yI$@>֧\ireaȮ$qX4 ƋdFt˄,P%KN<:.KT˚hCtt\Q>Tt*ѕ"<<\=Bh[.9gy+WMSwf#M2Ed\ըV,pD!$fVOCmֿ%+ 5%,+xIYoꔄŮgsiA @tL bU"ւ"<ʙr "c04110.Z>8Y ウCnPII GiHUPSNŔezfɃϤb<\)C1O Ej (}Pkx~h"C+ZZu ~$ v)J=țe:DI\4+ R CmGUsF}kS\w?3pkk aw+}XD؀ qH\qB9-6j%d^ĻY-^x8ZUt-;]n%7BxKqjSbL8t{wٲfQ?+}ZYBBLl;bKJ)}$̕PmYM-}cU_%{mp'Q߄;Xܑϱ`hH_ǵbf|Jg+w tb݇~GE9^]o\b>QFM'b]ww֭l)"9kbhx䉢!3MU+˺gS% _;n*{sߏO@B\7٫@R;}puczGkBtޝhmұ /7䟐cdRd?0cMWXW썦 M*<˰]ܛ̶09mS%XVXYGؿXmwݺHd]:Ғ(=oy$R3@<9zs6WlRqT%(xު^QNu'Wm2|7|=c_bًczd)sc!2v.olAk>}'N'*>·7//"A&,h n#GOoO6ɉV7]5h&l$p~ EZ؉uJ[SڽMOGDLs$%܅>?@XN\ayz%ϑLkwɏ;.70͛/PH{K7ذ/Xydo=5_ěa6鉋^&X FY;n[ Y|uR,&̚RrLzWQrtFo 9G|-~O^bٲeeKa%o6Vg5TV[|$m|lXjkܫݭśrǀXB%s KѱG޷[a[|e:;U'foT%qϼ?FcZlA$>O([C!hK1ե,{B"VQ/kV@/k    cE%^]80ܵ^ܔUa1lAt{b-7 H bÓ5: h`q*c_x ʩt!S%e N︳?4(-x LrY7vY/"Y  ]{ ]W~9L~G],O[bsYZyW'L]D㺆zb#.df   8>_9ch}u(߸#O VD\db Q=:qي%xqUf#`!IJ@BM; ' UCo NOǀx'sͿ\SS/9hɫ<%Vm?De;p _ H~t$@$@@ Ν?>uF+NewQz1Z7"~Zبgĉl9j4Q#n:gPR[) ]d8*9md$@Fh7ih?^/f_J^[0i3LןM'֣ K+o2       ?W`IHHHHHH\T\}˖ \O$@$@$@$@$@$@$pnM ~rB+l{'2/sW$@$@$@$@$@$@$@>h(,[k ʼnJ ڕ3QaPF xA*~GQ6UE yi0a E#mۇƤ$@$@$@$@$@$@$@$`@;U\^k% m/0Y?\HHHHHHH4Թ%cw6jl$       @;ibdR1YaX|u,3;)NpVkI~ vw@XT)_'`]$m+u >g`!      h)W(˼$\%ay d7քyp=#;GO\^Yo~/Zʲ '\ HHHHHHH+Ol;~b"5o6(QX]K2Q,Ջ+#evQBXޔՍFSEJPJ$_-Y_+ &a`X$Eʖ?bqEk}ΥHHHHHHH?=D"iH@IDAT %|V-~L.kr47])}ͿtNGQa`3Aq<=E2IU?ʬ/|KU*g?׳꧷,#@z>֧$@$@$@$@$@$@$@mMg^E10gg V2OD R.WoBhQIqU(}|w=jU"9eAQATv;_       6HPDjK%4)> SS{<Qܨ(vdcRO>r3W?z鬌|O+IHHHHHH(C;6*Xi,^L9%کí~}'ӫMo@k%lB(Ȫ |ϠF \#5 P?e,jźZl7Y+-OKZav4Ƚ92*a$      h+ڱG^kg(|KiU4Fl86|6#M|ZHHHHHHH>;Q 9O^xgSDRm)H\nieOR1|24Fք*`G)ske&DLB$@$@$@$@$@$@$zVPM#Y&j2 ZUGV"˿[w>YLql7O$zɺLMol3/;ҭPb&,HHHHHHHZ@TYɈoYF(_,irw:B~\~|OV[%- <v$fDS>J V-۵ho2WӕS       6GAis5*`)[WF`[@,R6(LV)[K7(z3Q,_C"H\jqzJV:,wtsE~7DSQ. >rXEI]"a곕O       6G}Z8FyD4'<7DQԈBc[butz϶]M'O@aQ>HKjk>)OH$_)EJ;(VA)~<ԧg       hs1bUc6}tdԨQmN6!Y7߈d(̆G懢9( |ͧ Jt(^~QPnn3,$      h~.kv_ YKX-[|ͧ% ʌ$@$@$@$@$@$@$5M@ƣ +z+B%u#(},.5#fh K0l?z6P9KYIϢ0Y2#E!%|:%>|0ŧHHHHHH$N? XF6ItLMc $wm;&>9 oC_w5)ULH:(G a iMM@l WR6dEN]pc>5״DS4|ILI$@$@$@$@$@$p0|m; ܳmץyM11ȟUym9HIH ACōW(bgoai SS4T!oXdJ{}MIHHHHHHS:<ΝII c S ۚ1/Irk&.)Ƴ^`|ڞ9&>Z}MP$@$@$@$@$@$@W +rW~0*H6$xܬ.ŗ[ڳJd{HHHHHH&N-~ﳛzt4Ojn( JJJ <8^>ksД?ɸ/7:Ha8Ӳ:ڶ3%К4g/M (|r, ?틍OƐ;#&3Z?ox͙a2k)#c򃸥GWZnU\\HHHHHHoMV[PY敐7SZWQMHw餇H̍^lj^գ s?*)u m\XmAx }Y&D]&?>YK0;&R⽈l?MĬ_>KmYH?X]:ۓթtٞMU9 Py3De+(! ]";X-[Rimȑ3i.CڜUOuu-Cs2fQ4T.4Aa,[hiuz>>oBf."YwEٷ8Q#t߀=(S Epr+k?S I%H2.6NJO-V* 9w=5]0姚ҪzNJuPL"Ezʮ WyM$@$@$@$@$@$ph׻z sM"      +G1ت-mfh5_C llYZ4ڟi^q6z311Q󏤏n/ť{!7^3_\`      B?񸭗ew=-.++"Cca#C40Qlq3.b}o9Y⤆&2<n(cSaOO0-4 &$     vODVFG_!TcB3+Y$dJ?xA~hHd495#ͭ\5C }:&L@^l CJG|Sgv:M &+?إ1$۵݄YxuC1p.i-K!     hLiik}RSbSU(۳{jv?~Do y-IWf xh%oۍcTT` s12J{RE"Uc[/ ^ix(rc f H AXTxNԅ 2BSUy`y?s˽Bc5?Q[a"c`>{`p ; gAYvnn7E!}trԨQ6x \gOaߎϰ(U|cе:uEB+$E;,T[Ufi %      6O*~?TQ( x[lذ'z qIX008MYlOج]8WsOA@ xReD Д֨Yf;3V4:8orIn^&I-L-HQoڗ>Wp]g={l}vnx;UsGI&`L 0&`Lv"L ?}ԯFCt*'Ad ġGWZfBqgp;QGFIOގ o'2&`L 0&` EIHK=Fh;df[VIK=z_\[V'EqIO`L 0&`L 0&",=~Yty2 ǰ. 1Z"֔bh*jPоuue5R8Z@֦Mʘ^vy7NtjœQp6: w@| .xdg`L 0&`L hPt s^|gCіFy9‡z Ş"SU :tbeuyxkvIQxb|M-QOBF}+æ6w9cDK[ E~gL 0&`L 0&e+DSi/j_6cDi3jC.FzQdDR^uy}6ƯzwJUR *owСڀ O2xݠ\ٶ-|=[ F=FQ0(1 2ĩѾD.wቧ=֚d1toJ%WC_[e׵ۜL 0&`L 0&exTWˤı ȁ~!mZRKw2M5C ||$S]U-h2-[֠^-] QSv6mf53<9 46)_Gibk#eO ~|~k[8<76>BlR]y'>5W~uY8)`x"& Tp 0&`L 0&pg/"*\.25"q0:kOչC:`]!gw+vIFI7;  Ř{{UjKUCz1-eTM:~qLGl6ξ?fe'}Lqo|/WaL 0&`L 0&p{pzMi`< ^37b߆f"tT{ƫYWFcsP} 7I߮h+jBN:\!fDqG;bNdfw&`L 0&`Li4?2H*/v]㩡żsr`#R~誖r7jߐNb'trhPGy'z=d!YKy9<[(20T ~8lV3`L 0&`L 0&& ?*hNoֈ!zܸ36,Pl&][YB|P,X|FA车R/ _TS&|`L 0&`L"ĦX hK(aQ#8nSAW!Zӈtkh'\,6E5!C[3[ՋTvi[>:p.`L 0&`L 07 4Δfhpa՟1[JY^t?y۟/z5GMtwN\Rb0۳zII}u1QW1Ws 0&`L 0&p fhtQs44L!ꮜGiŸV%Y|:ve| {+[_t᣸ BC5w^]-/i`Z}ȋ̮r`L 0&`L 0&~~,7X7p6eܑyU.c`~thY+WiݚpPHRơzofa33x-8{7hqZ;_ay ޻;=GcҠ@˥B:BE+W@}6\ҳ%.mGlՐ25Ϗm?C |x.t_(A-<۾;t 1Hws ><% AY' rl'^@i> 9`L 0&`L @މUU 7~_zbz V}Z ܣk+bx>7ן=h]W'yʹlE;5p􂯯AkkH xz;?|=v 5v٧]:hG|͗ҧd+/`h!ׂ˨e>aL 0&`L 0&%bȑ:u#sOǎξ+tl7P]Z_~؉ֶj8]Qq4=ܰN)wEѮ_.)TlC!/K.`c㐍j| 0&`L 0&hDtH1lBc{s4}4sH1kcNonn^s`L 0&`L 0whKA`L 0&`L 0&@3&fX+^"6etg ~e^ND6oX9/M˿ؓv3k4v FhD?aI*1xJOV˙_A!< 7/u;xz{Ã^eO7eQOqNSmqL_RC?]7ަj) SEh'ȶ*J{@zCYN9I } mB4y\e\M D.ϱ,IG\9l鉴ٮiBok*];/ .ּpC!x0>,p>r:ɤx{X7 ߶~TaǹtvƧ"9䙜}{G ^faM;blJGH;3fna"v{kyDG뢣ct gꖮLjK] kgvNHѭX[<-$& t9tF_$uhNQ}Ҷ+4tx\C@6cQINNZ>M1[?_t7*O { Z~k+#èN3';.%`mqYKO1W_l1KX:B߼b]S;?VJ,sk+Ǯtcϭ`Ԝy*9ou17>ֳgܘ=XE1PY}X?-}dafUhCDs&  mxӜG{Eo:|:Omqu%O#KW5A¯E1w&1yhPl*!UhZ+{ 1z@o^il0$-1CA_E݊LtcƠOnƵ8]\.Z.TCQԾ$I m[<Fc0h|Kh|.Gb3+**Prei,v=s<3QFE099.`}*ưit_0~z{x䙸?;@3r HN QhaEQcaT=QcthGgSYl|wthڒ 8q$s8w"/~Q@ܹȖ2;}<:};yofF m0ֳ1 I]Uڸ%fbcыmcv2e˝>4s{dF'Y<8Z[ щIbP.B J.O{vOߑdo몹)ƽ}û5X)4Ȫň'LZTl=p/tZ N䟦Z#<} imZVJsU6.F=1}z].̶0“s%ϐR*:(3ݐ|MJ.WC_X~r1Z_ _nԲ&-ҝ=uyݐh.䱐*= c|RnM|.+XQ':Tdd8gB}Ҡg|GM_;xB,m4fGxe{HGoSI1~r -:]yn.1:?/^נкs,ʏTטM9S5Ԭ<]QȾd,E}5(X5ud%5j]^I5A5맠kO˧~5Kp/jFV ?8\Vp^C,\kL!ո|V鿔ې4<ڨGё]C|qXqq;pS_rLƵѯ/KȡC(*؇> U3*ˑbx?K8^T*gҍH}&~DGa#8^X q x(V= .A&(yk{F }W_Ο?EQR-5.w/ee"Fn7+lqC8^Py `.1)l} Rt&l?R@}i'Gyڭg̓Q8ʐ 8s"hZJdrѓbuJ /)DvQoOō޵Q!Sc^܀ok06H(r I6-BQiVTd-WM 2.%GJ%k;8 ^cҚ4/Lԟ`Z{S OU/GWy.!on޽G pZzxZ ))gnTT;;y2!=]u_0lzRUJL˨0(5b Ŧ}_/E.T_]spѿKk]-J.ǥWQUge̎ų[_i>HM; ;>Wqq7ԙ^ʒN|r_y()^o7'y^a'7p1 ȇ $o*M_ij߾,zw%*bD+r\\{O>75Z;3 xfN!,jˡ 2Hߟ5}ʲ۫gIÕG'yrK愾;>#Źyw/<8.,tK ?w]x}$Fhܨ@)0u.Rܩ cH~?fG{$6 8rxQL1|?4vQ-?A/NǰG1e,Q?i1&xISg`֌8S+U^/EEi)J7 \2WUHz"wcQMŃxmK\.Ҍᢑ`=iX" sN t,␱`,qFϤg;CssngNԓ}zftFţms֢/vAvHĨIpِ[-K'ϾC-?טUԁZK1;^bF|h$7;b&gsYwIT9!jыEҴS' hhC:'~CrqwXti Ahds:V= yugw =HuC^4Ϋ&%?iR[aGKv~pY~QhC[ Ӿ})r 5u7p  Sk7P[s W7QG+ʏ}I-бK cVf='5ਾ(}S#$E/ZOFd2(cإ>"Cp14gkG[ի:l<ųQ %@>qtV¸/q}V-/X ~cѸ7FT|$XՠRYbIAv aҀi2c],eغ!ӌsI=k\/ZI^gJgspHO}Ϳ͛/5UMQ:MX0?Ex7U)Sxd:z뙾gޝw:3+]?vYwvd$aͼJ hF_}dOiQE_\Ǹ ʧjEO(e'zq2zO{(?AP%qg/]',"Ĺ Р+Ē0Lzs E4ʑY}՟*?UJ mUnC⫙JB| py%?lڣOK-_)!|u5Tq}<q_7a?]nh1H FwK|`^s\gv̓j[i:be^1v OE~lPɬNkw0mcܭgjH`%E3{:vQ9`gS]){_\ʖ}EuXé>h=ˣ^7w`ec1pן/Gݝ{Fg(=-VJ xL QrJ}@jSUGߧ {_/<3x]m5T}1Jen ׵YR2y—b(34m-3B;`v;?nZJyrΟݛ KK5Ld4 ճ ^Cav8u$Qy_AoVԗ_Y>}$U)Y?UU3fC XCO.F\Xsu2pPM˱炦{5P,Hu~F $m@cLl6 5!mk<ޛʬQctb^E:> -fTn3;y&4'Íԇ:gv΃ׂpן߇׍ܭ$-pw>B:#%,_\!nx?u+ܯgk8yo זp=w"wUf(%h_T_Ō?F zruR6NT7K0; i1}|A#2$=Apy8E})gxyG#;ncQ<%IDAT澳ζ N/pb7+ņL KlV5Vi׳9ÕyEՏxa}QFlOQ%CX)%&\Ÿ]mg> ~w$ye5s$ƽQ?.P*z$Z^~u02*F}11Nmz͖;9Cqad@UTՒYxz!,y/zt{Qny}[9N/#äamZ)&B4SKҎ=\cQ};>#wo䮘Q%KF( # cP|{6jꉳ ?E/חkVxN!wtl[>[.@nL!FQ^o TsegypkjevE6ۇa\n!|]LD|kEݤg j8'6E]e.'R3 :}i6x).V|0tרT$*1=Jd"S!0K7|{is78Zy+n_LZkj_9žN ѳKr!va)Ϭeh霾Ƚ:ʳ,X \b625߉ ~GdOIȶvSFh,o noc9 ?B8!'7)‡"ѯ[ZS3ZZUJ1e/O ©5Me򞊋a͡G6q7J׵ܪg^[{6tZ D|KjL=sw̻Ȟb+Y[5VuvKi#;~|J;ЦiY.yX=paG;z@]VUL"gZ2I,~O30?u8W!u|rF _4ݙBrC⨘&гZ7Ңd3?1/557t/5?;//K^FT۝wS‚1vp@调g;oU᪃MhK=5q4TQ^'.tQI2;;Nqy=Ғ;KwM>>GlGJ_(}qӁZ2ϽR6iov=4ܹ@%?ٌO"L#ۜ㧤e=<46aW1W}iM\q/.W 7(b%D'czlƑ'e=9huqK3 h͕J o)pT O\A=6,y q.x-0&ɉ U ?GJs09RmPT,wR,~V ( P*0ȘΟuٕ󐗵 m<}//Ϥ(6ϓ/ w Ťm}XECzf&ky<zRĵbܣya$_6R9hh\B QN6h43qã'ũg-"3g2{)z?̴tާfYtk:nceɝ簞ee{߇F3@vM›#M7$1K3܂c*(_ 2M[gJ|&}e|M/H\iӲRy _w75LT=8EbuMsƬV=3M/ؔ#4 &bOg>O$]>|:S8ٲOG9, L} _GOأȱrBW'.E&QXo31f(8SZK_ d~r9&C>oK瘞75RwulUGD%uwT7PQ/oRP~|s -U&3MA Ɛ'CǹKո>rW_bb?b&S=/lVfwqh\*3qg) c;twccKXa`1PH|*QrQp:!pb &O}Ɨ_> fzz{'c Ӟh:˜v"6~aTnҗmzX9mbkw3q'V>CAN,-'imڣ3A_NXgI#.(? fQ=+<ݩgj]/\Ă/x8޾ =D#bJJ?ճϐ{1 A,ǦQ'Q!$ȋ^gnѼ[їwFrZOuNg^]21\~2C7;3bD½(2(fUUmڳ"v1>~rn:P+nӚYf׉\[K.2* bvy4FHpoqWBɥK(.z/ VCǧ4d zҽ8"*=9(EUm =!1L29LVᛷ&cT1Ia;E-H6ɽoZl|dzU$Ay8IKa=[9s+xص)Q7 -8s1jN;/F:ӨN/0:Rx曷sBQo^w*N">kZG"62ҳ;ҧd ;yfӯoO o8}:tBΝкYZpڨ+{O]h}dK>e,+ۙ< aBQu?t =^i7߁v SW\8{ǏÙ+g ~[ݘ-=˔ y|$zŸKX)f_E F7o*y>Gb]w rRϢ83>[6|;y7noܸs}Byz]<::0٬<]AQ.5&XɬnC02C[Z0mƃHv -Cb`p !O 6[!K:ןdhV*3H 7S_Z/ƬUh_S)yxX(SO6_X $}Qt o[.; 4lsWPZ >ؕf" mzirȤTt&  NBw5?_okq qfg&`}14f1%f۸/Te3_E\ʻ8.0(6F b9f֯f=S1u(>XϣPSq_/mPg֥yzmKλT9j9myE 9ZXfwF}ꯟ8,BJWmTUZ~*FUdE=E ђ?;Pn 琉Ѧgr\HyKZf8~O+JG0HxKOt #qwSv;,4F+f{ggG"lמR1Wde,{S-ZFiM?hY2b>{z#W8FZk9gkJѧ>]: )ߓC+OŨGy5bD5TpQuiK?dӽz!x01SMt(.yp8gyKз$|%I?r0=4`E@4ȑ#ujΝ+;Vm:z fa]K-*_An:[k. cѫ[G NcVX8 wŕXQMFc#{ѯ5r !٘titdĄwDYXaRzmH;6J-JD`G[F\ҙ\u&OOCESҙn\(DΪ Λ5=SRZꧥkywo)i"<.aL,YiI$~y.2iL;pW|/(ʳ(#W'_phg=ϓ9&ܭ:ZGͰ(=\,6`gEc1^jOœ5;|F˺<9DMY{Cq }%]UxrHӳ.ݻɲ!Uz;}3A\έxeY(R++/S)%}kgElo=j䱖gZ&gj@.Y#ӏ.//FP;>Ea'1WWϟ= wys6rkغq$_ҙ_Sq?~e?b7lޭ^.A^',6 S~"a˦m>+vGʉ9!y0]8uN2ϖsA-v>6hz jFÏe!8aܭ۳gnÂ1N$Gxǎk kV*C8yjƘ kmLbMFmQj};&If䩖z^zp6Բt?g>̄܎:/޻v|>fiɉV<-m  hf | 0&pRQouM[>sqؼrE?.g$/ zL 0&p[VO y`Gm054{AAq,rC`0'b3 {'/@3%`q8ZOCӆ"|t9t%Qx T+@2W/0H)p8&k("ҠN]A @0q %[ք71&xqv\ 0 [: ?6 }; KRh Xq5^]>j.fL@n,lBg+m8ZJsZԿ#ze\Ge{7|VM/ws؆ ]2n-Sd_pS%k^Jv9ؼF2&D7Qܹ69rXp҃I5 J7le7& "hw^ Ν;l3s[<M3=Nqp:N]CA;x\g ;nH:G*_~o,˸|nFLӦ!h^) L D& 7†7@.`L 0&`L 0&8sSP>`L 0&`L 0&lqd 0&`L 0&`MA ?MAdL 0&`L 0& &`L 0&`L 464u 0&`L 0&`n 7@.`L 0&`L 0&ԹO&`L 0&`L ~`L 0&`L 0&@SlN xI3:p$8 z{Hv!0&`L 0&`My~ e Щj_W"x2v ǩ22Ki 0&`L 0&`64XvP3oG kŵ@ ^/|iq1f)`L 0&`L 0&Xo,m,3}HgY@9<摇L26`zLvMoj6ҳL 0&`L 0&@pspgQ}[8{DZ%:Ɛ]ka Z|0&`L 0&`L@zO>zO_(VkDtr3׿ 5`$N9H5ZOI(kAhѪVPi@V26lHVjcYLm~!jh| 0&`L 0&p;~^tUJiӍ8vݖ M4au#g隟xH/cH} %d5dyR%h9aj}M^bdZJoH&c#RUwF*#/Nzdz1j-`L 0&`L 0ۖ<~%<[:n9?iw2ɦ޳6d#/dyZiCKp7c^"_Dc2xgs ]'xPϩ`Lc5"!SҮEkt}to L Aީ#2&`L 0&`L (δźlx9pxQO!#CI1D/VuTFNaG8ڪo)?vSL H26Y;V]dQٔ|&`L 0&`L V @KKJL&;(@ )֎OeÔzBǒ*_p/29 Y0|`bsB6"& 0&`L 0&`hܥ^da_i;PZ:%꒺%hc6 ?Ų!F‰"/V B֌W.䡶^hmok9g1&`L 0&`8X ϞɰR,Ķ*/]vd#,o)"r)A_ + ArӵRjζ-ǙL 0&`L 0&0p'b,X6AA]><}lt޸C-# /HX̡w! giI2 S*$L 0&`L 0&NyO[~C[=A xB a'8&ˆAAEsfA [}> 񓼭a@ r8 1#`L 0&`L 0&ppR/ v|Aӛ .eooi4h'1q u{ۈ~vkza`:<2y7.;&`L 0&`L ?$a ;ZՐܼ͟0 &8[_ *!J!u<"=wH6A1he$#~-V+I6LIL 0&`L 0&J^BӟqCma!#_z{4~!c y 42 0ɘ )U#Po-/ Do F&u~A;Qۏ'02#ل#vBwS?qZIgL 0&`L 0&B5|j/dpDUK]%=_>D;h' 0$(f'x9z6N/.g޴m3/d>Em~UK*􁊔8`L 0&`L 0Z9ReUΝ+;v 63VdBnf*fj*BcvFh? '͇4'%x;?!}fXET?~S0&,˲ѧ(;SX"gk&V*Y`L 0&`L 0&(Zgx)fi2sWZ@=\-<dr)7]j!ϜlRO@GeW,ml1HG9ǔ+^ڃ -z.fdq,`L 0&`L 0\je=-EK_i?OD]y?ʭWjLl) h` fëfК1&q hZr?/Z"i.`L 0&` ~nB4SCpߎ@ C+6R0 ?ށm(,!K~—z0vmDL!wR[]I KDߦ_7CX⸇&qZ Z nd Lj:6!<6$OyE]Ws\Mr0.Ĉ$\0>2"}zHo6^ UtGDP0ZN@흙{*C`1KBpN( ȇf&#g0&`L 0&0%(qT [ۢE Zl!ݲEKt½}bڄp<؟".dL!ɟ^ ]r jQUNgeK09{NhW/F'G2;I ח"}8SF܏$lBAULZ \Oۍc26`a%q4'|#AmHGёo9 @aV1a#ʯm(z1Zwr|6FSA 3ӥ+SGTĕ#OXۏUO79!ӊMHP;Qs?C1;`rl$:Ǻ |ɨژ0o;SWѦMtzC;/ `L 0&`L 0k\X#h;[O7?DQ{`G)rRW]`!ZxVࡤx`&ҙ*Wa2(G.\V- &,~B"kdj箛''!A6KAwg>?U*hK ʑYEg%>12[_GNa%=1zRU /{́vIE(ʼnHɫAgJ|z{|p:RW)уp~ݻW IABljrll$OEXVH^m#nF <3ڄA1q6s"McJAbmc:[ Gvb\ 0&`L 0&nec {@:OݎwM˾k#7 /:`bޑ߲h;w{'C)Ws_"wo,r!JaGbV: ?GS,)ߐ] yCRعmZ1Kߙ`L 0&`L@#~qA}rATPco/,ȿ(~#6.쮳B֣䊴@2PY;r qŚIu9Zg)_1Yk+PZJTZjÝy^~m#ai2@W_i9 R5N2]2Fd:üldk+rOJNA*s1?))8~^ b'b=lbUu9`L 0&`L[zkpDJss'*Uc ~7P]11LݡCQ skqS ;CMa\(}+hKxidW(t硪\\=rGvMTϳ.kERY Iqb HjL 0&`L 0&ĝCtDkZނ~p LOTb/͍$u3*])Wv oHߣ@^ڕ㧤%D=0TSRmn W*q#/+WF@}g]x6j| ]5~,JBܰIx7xw1Qq߈J;<(Wl4$'ZlP,©h疒k7hr?@:0 !~jx"s=_?͌(@ P(@ PRI9dx `tJl cuL. GžOa" ; ٛ|F.Z'#25!`F>MŰY=#܍twg2&uy$ln(;ӥB\R+zq]?sc`Yf=&7Zˆ%#7o;"o!}7sK2Ϗ<>D P(@ PK@~ c|n6x.%|o*<:1_WI76V`mGXD8>*, 0Jy>{',zz D4$C+,Co] j߅}`1S^_]&=c w:Y3q>C510VU4ϸg +rҺ7cNVhU^Ю}`4bH=g+Ŏ_fv~a:oELR#LW?OP?v,3 P(@ PnXݿ/X=TZqE8{gn୏ӧayU5)e x KbKS[a"j,,Yk؏f!^ !(3~!6lGAVC+Σ5pXyg (ΙYa,6=hHf|=Eb9|Zj"`9X{`զ۸Z;hA~õbs L*(C |ijQ:KW\>GD?)R(`y9 [ JN$< (@ P(@ +$i ,p@~ cl<hooAS% Cy]i-:Gw@2oH 2-Mu8:\ǥihtޘEଭUԵ Cpp ð"1TGMo|b<(@ P(@~'+/Wԅ9}ۏ[5Zڤd:(@ P(@ P-Dzu?t~/OJ P(@ P(nUP(@ P(@ A~.Q(@ P(@w0EA P(@ P?}Ka(@ P(@ P;q"(@ P(@ P}P? M_@9IENDB`graphql-ruby-2.2.17/guides/pagination/000077500000000000000000000000001476434635200176475ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/pagination/connection_concepts.md000066400000000000000000000073471476434635200242410ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Connection Concepts desc: Introduction to Connections index: 1 --- __Connections__ are a pagination solution which started with [Relay JS](https://facebook.github.io/relay), but now it's used for almost any GraphQL API. Connections are composed of a few kinds of objects: - `Connection` types are generics that expose pagination-related metadata and access to the items - `Edge` types are also generics. They represent the relationship between the parent and child (eg, a `PostEdge` represents the link from `Blog` to `Post`). - _nodes_ are actual list items. In a `PostsConnection`, each node is a `Post`. Connections have some advantages over offset-based pagination: - First-class support for relationship metadata - Cursor implementations can support efficient, stable pagination ## Connections, Edges and Nodes Connection pagination has three core objects: connections, edges, and nodes. ### Nodes Nodes are items in a list. A `node` is usually an object in your schema. For example, a `node` in a `posts` connection is a `Post`: ```ruby { posts(first: 5) { edges { node { # This is a `Post` object: title body publishedAt } } } } ``` ### Connections Connections are objects that _represent_ a one-to-many relation. They contain _metadata_ about the list of items and _access to the items_. Connections are often generated from object types. Their list items, called _nodes_, are members of that object type. Connections can also be generated from union types and interface types. ##### Connection metadata Connections can tell you about the list in general. For example, if you {% internal_link "add a total count field", "type_definitions/extensions#customizing-connections" %}, they can tell you the count: ```ruby { posts { # This is a PostsConnection totalCount } } ``` ##### Connection items The list items in a connection are called _nodes_. They can generally be accessed two ways: - via edges: `posts { edges { node { ... } } }` - via nodes: `posts { nodes { ... } }` The differences is that `edges { node { ... } }` has more room for relationship metadata. For example, when listing the members of a team, you might include _when_ someone joined the team as edge metadata: ```ruby team { members(first: 10) { edges { # when did this person join the team? joinedAt # information about the person: node { name } } } } ``` Alternatively, `nodes` provides easier access to the items, but can't expose relationship metadata: ```ruby team { members(first: 10) { nodes { # shows the team members' names name } } } ``` There's no way to show `joinedAt` above without using `edges { ... }`. ### Edges Edges are _like_ join tables in that they can expose relationship metadata between a parent object and its children. For example, let's say that someone may be the member of _several_ teams. You would make a join table in the database (eg, `team_memberships`) which connect people to each of the teams they're on. This join table could also include information about _how_ that person is related to the team: when they joined, what role they have, etc. Edges can reveal this information about the relationship, for example: ```ruby team { # this is the team name name members(first: 10) { edges { # this is a team membership joinedAt role node { # this is the person on the team name } } } } ``` So, edges really help when the _relationship_ between two objects has special data associated with it. If you use a join table, that's a clue that you might use a custom edge to model the relationship. graphql-ruby-2.2.17/guides/pagination/cursors.md000066400000000000000000000030461476434635200216740ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Cursors desc: Advancing through lists with opaque cursors index: 4 --- Connections use _cursors_ to advance through paginated lists. A cursor is an opaque string that indicates a specific point in this. Here, _opaque_ means that the string has no meaning except its value. cursors shouldn't be decoded, reverse-engineered, or generated ad-hoc. The only guarantee of a cursor is that, after you retrieve one, you can use it to request subsequent or preceeding items in the list. Although cursors can be tricky, they were chosen for Relay-style connections because they can be implemented in stable and high-performing ways. ## Customizing Cursors By default, cursors are encoded in base64 to make them opaque to a human client. You can specify a custom encoder with `Schema.cursor_encoder`. The value should be an object which responds to `.encode(plain_text, nonce:)` and `.decode(encoded_text, nonce: false)`. For example, to use URL-safe base-64 encoding: ```ruby module URLSafeBase64Encoder def self.encode(txt, nonce: false) Base64.urlsafe_encode64(txt) end def self.decode(txt, nonce: false) Base64.urlsafe_decode64(txt) end end class MySchema < GraphQL::Schema # ... cursor_encoder(URLSafeBase64Encoder) end ``` Now, all connections will use URL-safe base-64 encoding. From a connection instance, the `cursor_encoders` methods are available via {{ "GraphQL::Pagination::Connection#encode" | api_doc }} and {{ "GraphQL::Pagination::Connection#decode" | api_doc }} graphql-ruby-2.2.17/guides/pagination/custom_connections.md000066400000000000000000000146431476434635200241150ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Custom Connections desc: Building & using cursor-based connections in GraphQL-Ruby index: 3 --- GraphQL-Ruby ships with built-in connection support for ActiveRecord, Sequel, Mongoid, and Ruby Arrays. You can read more in the {% internal_link "Using Connections", "/pagination/using_connections" %} guide. When you want to serve a connection based on your _own_ data object, you can create a custom connection. The implementation will have several components: - The __application object__ -- the list of items that you want to paginate in GraphQL - The __connection wrapper__ which wraps the application object and implements methods used by GraphQL - The __connection type__, a GraphQL object type which implements the connection contract For this example, we'll imagine that your application communicates with an external search engine, and expresses all search results with a `SearchEngine::Result` class. (There isn't _really_ any class like this; it's an arbitrary example of an application-specific collection of items.) ## Application Object In Ruby, _everything_ is an object, and that includes _lists of objects_. For example, we think of an Array as a _list_ of objects, but Arrays are also objects in their own right. Some list objects have very fancy implementations. Think of an `ActiveRecord::Relation`: it gathers up the parts of a SQL query, and at the right moment, it dispatches a call to your database to fetch the objects in the list. An `ActiveRecord::Relation` is also a list object. Your application probably has other list objects that you want to paginate via GraphQL connections. For example, you might show a user some search results, or a list of files from a fileserver. Those lists are modeled with _list objects_, and those list objects can be wrapped with connection wrappers. ## Connection Wrapper A connection wrapper is an adapter between a plain-Ruby list object (like an Array, Relation, or something application-specific, like `SearchEngine::Result`) and a GraphQL connection type. The connection wrapper implements methods which the GraphQL connection type requires, and it implements those methods based on the underlying list object. You can extend {{ "GraphQL::Pagination::Connection" | api_doc }} to get started on a custom connection wrapper, for example: ```ruby # app/graphql/connections/search_results_connection.rb class Connections::SearchResultsConnection < GraphQL::Pagination::Connection # implementation here ... end ``` The methods you must implement are: - `#nodes`, which returns a paginated slice of `@items` based on the given arguments - `#has_next_page`, which returns `true` if there are items after the ones in `#nodes` - `#has_previous_page`, which returns `true` if there are items before the ones in `#nodes` - `#cursor_for(item)`, which returns a String to serve as the cursor for `item` How to implement these methods (efficiently!) depends on your backend and how you communicate with it. For inspiration, you can see the built-in connections: - {{ "GraphQL::Pagination::ArrayConnection" | api_doc }} - {{ "GraphQL::Pagination::ActiveRecordRelationConnection" | api_doc }} - {{ "GraphQL::Pagination::SequelDatasetConnection" | api_doc }} - {{ "GraphQL::Pagination::MongoidRelationConnection" | api_doc }} ### Using a Custom Connection To integrate your custom connection wrapper with GraphQL, you have two options: - Map the wrapper to a list object at the _schema level_, so that those list objects are _always_ automatically wrapped by your wrapper; OR - Use the wrapper manually in resolve methods, to override any automatic mapping The first case is very convenient, and the second case makes it possible to customize connections for specific situations. To __map the wrapper to a class of objects__, add it to your schema: ```ruby class MySchema < GraphQL::Schema # Hook up a custom wrapper connections.add(SearchEngine::Result, Connections::SearchResultsConnection) end ``` Now, any time a field returns an instance of `SearchEngine::Result`, it will be wrapped with `Connections::SearchResultsConnection` Alternatively, you can apply a connection wrapper on a case-by-case basis by applying it during the resolver (method or {{ "GraphQL::Schema::Resolver" | api_doc }}): ```ruby field :search, Types::SearchResult.connection_type, null: false do argument :query, String end def search(query:) search = SearchEngine::Search.new(query: query, viewer: context[:current_user]) results = search.results # Apply the connection wrapper and return it Connections::SearchResultsConnection.new(results) end ``` GraphQL-Ruby will use the provided connection wrapper in that case. You can use this fine-grained approach to handle special cases or implement performance optimizations. ## Connection Type Connection types are GraphQL object types which comply to the [Relay connection specification](https://relay.dev/graphql/connections.htm). GraphQL-Ruby ships with some tools to help you create those object types: - {{ "GraphQL::Types::Relay::BaseConnection" | api_doc }} and {{ "GraphQL::Types::Relay::BaseEdge" | api_doc }} are example implementations of the spec. They don't inherit from your application's base object class though, so you might not be able to use them out of the box. - Type classes respond to `.connection_type` which returns a generated connection type based on that class. By default, it inherits from the provided `GraphQL::Types::Relay::BaseConnection`, but you can override that by setting `connection_type_class(Types::MyBaseConnectionObject)` in your base classes. For example, you could implement a base connection class: ```ruby class Types::BaseConnectionObject < Types::BaseObject # implement based on `GraphQL::Types::Relay::BaseConnection`, etc end ``` Then hook it up to your base classes: ```ruby class Types::BaseObject < GraphQL::Schema::Object # ... connection_type_class(Types::BaseConnectionObject) end class Types::BaseUnion < GraphQL::Schema::Union connection_type_class(Types::BaseConnectionObject) end module Types::BaseInterface include GraphQL::Schema::Interface connection_type_class(Types::BaseConnectionObject) end ``` Then, when defining fields, you could use `.connection_type` to use your connection class hierarchy: ```ruby field :posts, Types::Post.connection_type, null: false ``` (Those fields get `connection: true` by default, because the generated connection type's name ends in `*Connection`.) graphql-ruby-2.2.17/guides/pagination/overview.md000066400000000000000000000014231476434635200220370ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Overview desc: Introduction to pagination in GraphQL index: 0 redirect_from: - /relay/connections/ --- GraphQL-Ruby ships with several implementations of Relay's "connection"-style pagination. You can familiarize yourself with connections in {% internal_link "Connection Concepts", "/pagination/connection_concepts" %} and see how to use them in {% internal_link "Using Connections", "/pagination/using_connections" %}. It also supports custom connection implementations and type definitions, which you can explore in the {% internal_link "Custom Connections", "/pagination/custom_connections" %} guide. GraphQL has its own [great pagination docs](https://graphql.org/learn/pagination/) for further reading. graphql-ruby-2.2.17/guides/pagination/stable_relation_connections.md000066400000000000000000000113611476434635200257440ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Stable Relation Connections desc: Advanced pagination for ActiveRecord index: 4 pro: true --- `GraphQL::Pro` includes a mechanism for serving _stable_ connections for `ActiveRecord::Relation`s based on column values. If objects are created or destroyed during pagination, the list of items won't be disrupted. These connection implementations are database-specific so that they can build proper queries with regard to `NULL` handling. (Postgres treats nulls as _larger_ than other values while MySQL and SQLite treat them as _smaller_ than other values.) ## What's the difference? The default {{ "GraphQL::Pagination::ActiveRecordRelationConnection" | api_doc }} (which turns an `ActiveRecord::Relation` into a GraphQL-ready connection) uses _offset_ as a cursor. This naive approach is sufficient for many cases, but it's subject to a specific set of bugs. Let's say you're looking at the second page of 10 items (`LIMIT 10 OFFSET 10`). During that time, one of the items on page 1 is deleted. When you navigate to page 3 (`LIMIT 10 OFFSET 20`), you'll actually _miss_ one item. The entire list shifted "up" one position when a previous item was deleted. To solve this bug, we should use a _value_ to page through items (instead of _offset_). For example, if items are ordered by `id`, use the `id` for pagination: ```sql LIMIT 10 -- page 1 WHERE id > :last_id LIMIT 10 -- page 2 ``` This way, even when items are added or removed, pagination will continue without interruption. For more information about this issue, see ["Pagination: You're (Probably) Doing It Wrong"](https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong). ## Installation You can use a stable connection for _all_ `ActiveRecord::Relation`s by installing it at the schema level: ```ruby class MyAppSchema < GraphQL::Schema # Hook up the stable connection that matches your database connections.add(ActiveRecord::Relation, GraphQL::Pro::PostgresStableRelationConnection) # Or... # connections.add(ActiveRecord::Relation, GraphQL::Pro::MySQLStableRelationConnection) # connections.add(ActiveRecord::Relation, GraphQL::Pro::SqliteStableRelationConnection) end ``` Alternatively, you can apply the stable connection wrapper on a _field-by-field_ basis. For example: ```ruby field :items, Types::ItemType.connection_type, null: false def items # Build an ActiveRecord::Relation relation = Item.all # And wrap it with a connection implementation, then return the connection GraphQL::Pro::MySQLStableRelationConnection.new(relation) end ``` That way, you can adopt stable cursors bit-by-bit. (See below for [backwards compatibility](#backwards-compatibility) notes.) Similarly, if you enable stable connections for the whole schema, you can wrap _specific_ relations with `GraphQL::Pagination::ActiveRecordRelationConnection` when you want to use index-based cursors. (This is handy for relations whose ordering is too complicated for cursor generation.) ## Implementation Notes Keep these points in mind when using value-based cursors: - For a given `ActiveRecord::Relation`, only columns of that specific model can be used in pagination. (This is because column names are turned into `WHERE` conditions.) - The connection may add an additional `primary_key` ordering to ensure that the cursor value is unique. This behavior is inspired by `Relation#reverse_order` which also assumes that `primary_key` is the default sort. - The connection will add fields to the relation's `SELECT` clause, so that cursors can be reliably constructed from the database results. ## Grouped Relations When using a grouped `ActiveRecord::Relation`, include a unique ID in your sort to ensure that each row in the result has a unique cursor. For example: ```ruby # Bad: If two results have the same `max(price)`, # they will be identical from a pagination perspective: Products.select("max(price) as price").group("category_id").order("price") # Good: `category_id` is used to disambiguate any results with the same price: Products.select("max(price) as price").group("category_id").order("price, category_id") ``` For ungrouped relations, this issue is handled automatically by adding the model's `primary_key` to the order values. If you provide an unordered, grouped relation, `GraphQL::Pro::RelationConnection::InvalidRelationError` will be raised because an unordered relation _cannot_ be paginated in a stable way. ## Backwards Compatibility `GraphQL::Pro`'s stable relation connection is backwards-compatible. If it receives an offset-based cursor, it uses that cursor for the next resolution, then returns value-based cursors in the next result. ## ActiveRecord Versions Stable relation connections support ActiveRecord `>= 4.1.0`. graphql-ruby-2.2.17/guides/pagination/using_connections.md000066400000000000000000000115111476434635200237170ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Using Connections desc: Pagination with GraphQL-Ruby's built-in connections index: 2 --- GraphQL-Ruby ships with a few implementations of the {% internal_link "connection pattern", "pagination/connection_concepts" %} that you can use out of the box. They support Ruby Arrays, Mongoid, Sequel, and ActiveRecord. Additionally, connections allow you to limit the number of items returned with [`max_page_size`](#max-page-size) and set the default number of items returned with [`default_page_size`](#default-page-size). ## Make Connection Fields Use `.connection_type` to generate a connection type for paginating over objects of a given type: ```ruby field :items, Types::ItemType.connection_type, null: false ``` The generated return type will be called `ItemConnection`. Since it ends in `*Connection`, the `field(...)` will automatically be configured with `connection: true`. If the connection type's name doesn't end in `Connection`, you have to add that configuration yourself: ```ruby # here's a custom type whose name doesn't end in "Connection", so `connection: true` is required: field :items, Types::ItemConnectionPage, null: false, connection: true ``` The field will be given some arguments by default: `first`, `last`, `after`, and `before`. ### Opting out of default connection handling To opt out of GraphQL-Ruby's default connection handling, add `connection: false` to the field definition: ```diff - field :items, Types::ItemType.connection_type, null: false + field :items, Types::ItemType.connection_type, null: false, connection: false ``` Then, add any arguments you want (`first`, `last`, `after`, `before`) and make sure that your resolver returns an object that can fulfill the fields of the configured return type. ## Return Collections With connection fields, you can return collection objects from fields or resolvers: ```ruby def items object.items # => eg, returns an ActiveRecord Relation end ``` The collection object (Array, Mongoid relation, Sequel dataset, ActiveRecord relation) will be automatically paginated with the provided arguments. Cursors will be generated based on the offset of nodes in the collection. ## Make Custom Connections If you want to paginate something that _isn't_ supported out-of-the-box, you can implement your own pagination wrapper and hook it up to GraphQL-Ruby. Read more in {% internal_link "Custom Connections", "/pagination/custom_connections" %}. ## Special Cases Sometimes, you have _one collection_ that needs special handling, unlike other instances of its class. For cases like this, you can manually apply the connection wrapper in the resolver. For example: ```ruby def items # Get the ActiveRecord relation to paginate relation = object.items # Apply a custom wrapper Connections::ItemsConnection.new(relation) end ``` This way, you can handle this _particular_ `relation` with custom code. ## Max Page Size You can apply `max_page_size` to limit the number of items returned and queried from the database, regardless of what the client requests. - __For the whole schema__, you can add it to your schema definition: ```ruby class MyAppSchema < GraphQL::Schema default_max_page_size 50 end ``` At runtime, that value will be applied to _every_ connection, unless an override is provided as described below. - __For a given field__, add it to the field definition with a keyword: ```ruby field :items, Item.connection_type, null: false, max_page_size: 25 ``` - __Dynamically__, you can add `max_page_size:` when you apply custom connection wrappers: ```ruby def items relation = object.items Connections::ItemsConnection.new(relation, max_page_size: 10) end ``` To _remove_ a `max_page_size` setting, you can pass `nil`. That will allow unbounded collections to be returned to clients. ## Default Page Size You can apply `default_page_size` to limit the number of items returned and queried from the database when no `first` or `last` is provided. - __For the whole schema__, you can add it to your schema definition: ```ruby class MyAppSchema < GraphQL::Schema default_page_size 50 end ``` At runtime, that value will be applied to _every_ connection, unless an override is provided as described below. - __For a given field__, add it to the field definition with a keyword: ```ruby field :items, Item.connection_type, null: false, default_page_size: 25 ``` - __Dynamically__, you can add `default_page_size:` when you apply custom connection wrappers: ```ruby def items relation = object.items Connections::ItemsConnection.new(relation, default_page_size: 10) end ``` If `max_page_size` is set and `default_page_size` is higher than it, the `default_page_size` will be clamped down to match `max_page_size`. If both `default_page_size` and `max_page_size` are set to `nil`, unbounded collections will be returned. graphql-ruby-2.2.17/guides/parser_cache.md000066400000000000000000000014321476434635200204570ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: GraphQL Parser cache section: Other desc: How to make parsing GraphQL faster with caching --- Parser caching may be optionally enabled by setting `config.graphql.parser_cache = true` in your Rails application. The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new("some_dir")`. This will create a directory (`tmp/cache/graphql` by default) that stores a cache of parsed files. Much like [bootsnap](https://github.com/Shopify/bootsnap), the parser cache needs to be cleaned up manually. You will need to clear the cache directory for each new deployment of your application. Also note that the parser cache will grow as your schema is loaded, so the cache directory must be writable. graphql-ruby-2.2.17/guides/pro/000077500000000000000000000000001476434635200163165ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/pro/checksums/000077500000000000000000000000001476434635200203035ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/pro/checksums/graphql-1.26.1.txt000066400000000000000000000002011476434635200232160ustar00rootroot0000000000000033537b3b1a5afef64ea25817d607afae983de531902c18dc5c99bd4b52538b5b03cb3f7032dc3a53b5690ef85a7abd7a0422cdead9a871a187b3bc755ba4062c graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.0.0.txt000066400000000000000000000002011476434635200253030ustar00rootroot00000000000000960ec05f3ef88fff70bef68ca02fa3d37b512f9ad668345a903766776aaca08fba52795a9625cde39f9c2080ae13ec6c76b8757f05c26ccdfb27edcc69112ca8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.0.1.txt000066400000000000000000000002011476434635200253040ustar00rootroot0000000000000037364b0f6dbb5e97203e30172d3ed21fdd3c90f78de3e93479d15008d00b170d4e1656dc86b686de69fe060f4573d743f341a1ee55eeb793426694619ffc3cb0 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.0.txt000066400000000000000000000002011476434635200253040ustar00rootroot000000000000009de179b3c8a2f374c112146cbad6436cc72b553f2a7fb289171af50201137609545636aa5fa972c6fd19f371f8414c9339a708394169dc1582ebef04c68f9ecc graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.1.txt000066400000000000000000000002011476434635200253050ustar00rootroot000000000000002438c01b8bfd1ad23ff41f3dffd68b2a3cf150ca12f2dd45e2ba06fb063e6344dfca0d38e3d5f55693e050d9b44738e563abb484f24799fec80c6b78662075cf graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.10.txt000066400000000000000000000002011476434635200253650ustar00rootroot000000000000004fd53a85c197db33fdf2a73593a016dd687bd9e8a5e9eb01e8e09594bbfae2cd7f5a27a17a7555603e2ff14b779f26362091e478b37737b3ed8a1a7cb8b0fce2 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.11.txt000066400000000000000000000002011476434635200253660ustar00rootroot00000000000000af885c3e261efe1ce3d14861d46d2150de3195d71debbfd0ac0de866eb7de45b6f5080a3686d8750522cd8f0eb499c6b1abb2e54bc9d449c62d9efe678440127 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.12.txt000066400000000000000000000002011476434635200253670ustar00rootroot00000000000000dd4d30115510d1767dcbce8a64e91c241d48a4f6b4c9d57df29e334143431958783a17ed20cf7601675a1957b000c200dca4b9a649b1c433dbf879e5344d1c67 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.13.txt000066400000000000000000000002011476434635200253700ustar00rootroot00000000000000545dc5c0ec289b6bec7c2ab59bf41708885f1e3a746dfd6b070d3d87eb82e4107ee154110b9340896144875384dca6ee7a4a5676b3301ed76884308d389fd96e graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.14.txt000066400000000000000000000002011476434635200253710ustar00rootroot0000000000000090f5193f787414b85ae649881d252dc0b0c1b6a880920ed117cea40aefa21350c0d0fc91e7a5a980c4958a5fdd7932874db5df966db81365a3882631fbae4eb5 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.2.txt000066400000000000000000000002011476434635200253060ustar00rootroot00000000000000c512384b9d0482a44925a0e47026b6cd48f48282c3e22f9bb90fad6452dd5209f394752b0ecfe1e5ea41cd5f848325fd4fb9c68c54d63c36774fa568cfc93c01 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.3.txt000066400000000000000000000002011476434635200253070ustar00rootroot000000000000004c46c862544ad3bd836949a57e7dc272a4bd5e6a71757879b6de8729d063c866d8ff2f2f6959001fc1120c4f748e25379039fa855fd9b1b717ae6ad5c43ed02f graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.4.txt000066400000000000000000000002011476434635200253100ustar00rootroot000000000000008320d4f89f1986c23e92cfe1dae0fad37c7ca6d7b39e5f05d110e7167aa3d0ce6ae995d514986941caa90f76aa3fa96679810e7fe3fe5232500c954c5d3645a3 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.5.txt000066400000000000000000000002011476434635200253110ustar00rootroot000000000000006c6248cf76ea0ca250f9e0ff587e6475a51c3fa3b4cdb6a517a35baf9226d87be2b009f073a952ac33a19bbe46cdf44889466b160dfa20fc24d8a365bab4238b graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.6.txt000066400000000000000000000002011476434635200253120ustar00rootroot00000000000000875b3f09b699e006733a2ffee31379db3f775b79408b35726a24c8a00386b1a7fcfafc56c57163a71dadafa22fab24aa7dec5608feae1cce8c59bb652e60e439 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.7.txt000066400000000000000000000002011476434635200253130ustar00rootroot00000000000000cc841f4c0365ddfd65b40b1af2c93217f6c5ab531331fa0e5e84e70dfbd7f997f20db2a5d741da7652c3efcb5dde42987668ed29387a378db22234b2aa18153a graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.8.txt000066400000000000000000000002011476434635200253140ustar00rootroot0000000000000066710fed7209fef5223602a3d8722c70c83f79540dff9752b42caad6ab53be7d74cd304dfc7e789ade3972d590ed3198aa42ea8e89dcb46c929fa1b9d3a997fa graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.1.9.txt000066400000000000000000000002011476434635200253150ustar00rootroot000000000000000bfb112eaca92cb98adb4beffed43f22a68993dacf67bf3631bec05f30b29954181629b5ab11d429619d75ec76c7295ae74f7eeba9940aadec2cdef7e5d96a95 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.2.0.txt000066400000000000000000000002011476434635200253050ustar00rootroot00000000000000a4c9d7118ab03cb27bf49c97b0fe95e5695ace646bf9f459ec327bc18190dc9bcdcc772438873a7a7531213ba4d8d10af151e195a4b8971342230e5ad2d92346 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.3.0.txt000066400000000000000000000002011476434635200253060ustar00rootroot00000000000000b1a909bf8582e6c253bdb901f9865634dd970519529eb34021c138b6cbfe76c1573756899d7966c16f32878dd11272d6f6c8be7abde3654c9a5969e93af62b0a graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.3.1.txt000066400000000000000000000002011476434635200253070ustar00rootroot000000000000006b23b11331e345a757ba31edb3af77e3174996603162dd1fb698971886545ff8344dfc39f21c6096d2206f7be1d7f529efbddc6f86cbe15ddf81232e4b631603 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.3.2.txt000066400000000000000000000002011476434635200253100ustar00rootroot00000000000000cc215bc8fee73b02c40ce29070244a31072f42e8a68a3b7038ef449572eab977180d13bc61fa3cbd02cc6855fadaa05bcfc6db033b517e8b8136960a188bf01a graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.3.3.txt000066400000000000000000000002011476434635200253110ustar00rootroot00000000000000019e92e06ffab9cbecde167c72321136e0826db4bd4ae7a74c33b0d915e6b253c847f688f3efdc4a7f27428f0cd36b2ffa34fa02769ff3a431e101b88fcd7533 graphql-ruby-2.2.17/guides/pro/checksums/graphql-enterprise-1.3.4.txt000066400000000000000000000002011476434635200253120ustar00rootroot00000000000000e08061b7eae9ac0deb15a4d7361deddc9cd881d89931652806fb4a2a544c5b29e58245f850322c228dbbd3000920ce94d2de2bc2882cec29dc0c46698943058f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.0.0.txt000066400000000000000000000002011476434635200237230ustar00rootroot000000000000004de5bbda7a3e9ed29eb3cdb25a0cea1a91f57dd34bb73827628f49a605cec244583cf0b9b6d9dfab932573f99c2ada4c98f4ef569fc82fa32b046e0ffb3e976c graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.0.1.txt000066400000000000000000000002011476434635200237240ustar00rootroot000000000000004951e730ead0e493c21ad337af84eb99551a1e4c66b35c8ebfabc000b1bc59d13c4c45c3081fa68719f56522ab1f1bc4c4258e634c0da3414cb82041ed2e122f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.0.2.txt000066400000000000000000000002011476434635200237250ustar00rootroot00000000000000bc165db6aaf32d51e710626eb2baa36851e0d9ca4ff88676d0fdd9120264e599e326dd1602713197308d010392a46f7f3472e805b5a1677af18887e3e7b0bdaa graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.0.3.txt000066400000000000000000000002011476434635200237260ustar00rootroot00000000000000077630f850706f87104890dd7ccaca7fc01e44f73c2f5bf5750bfcb5b15a4b58e2028aec04ed3066abbeecf7863af399bfbb4ad1bbc7aea9c034faf949118b5e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.0.4.txt000066400000000000000000000002011476434635200237270ustar00rootroot00000000000000e9be770ff2117a4692a11f5b5c8e5e9f5095bd7aa7ca24a9e085dd278a548caed28396d100302ec52ab295aafd7fab849c83420a49311034779ec30eb3b4a232 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.1.0.txt000066400000000000000000000002011476434635200237240ustar00rootroot00000000000000c934c912626c926921a4e5260edb3e1d108d3bd3b680137a3077a0baa6e802966399fcbb52c3b2d60333db9d8a5e508a4c8e3344ff2ef05555aeb76967537be7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.1.1.txt000066400000000000000000000002011476434635200237250ustar00rootroot00000000000000deca840ea6676077e39608a63b3d33cbc032f69ab147d2a1c679da8852e087204b3c6b643cde3bde411d7d1bb7cf79bbc21b504fd08940ea5b6cd7cd868c3e12 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.0.txt000066400000000000000000000002011476434635200240040ustar00rootroot0000000000000070b8ebe4f3b0c99a16a544aa26bf0d9c8605c4e1aed1fd9374c93f7818bc08860690f60b3f28f48a6cfa8c5864312a2ea94b7c8caead40f6f609f07739d55ec7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.1.txt000066400000000000000000000002011476434635200240050ustar00rootroot00000000000000d061d329ed1518519382f182ffc48ccbaa9cf7a2abea701a563477bfd3dd39418accc67449d6891afff6482c0c14f05a7a459ac42e801dac6fd1c91e6a0de974 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.2.txt000066400000000000000000000002011476434635200240060ustar00rootroot000000000000006f8c9be62438f95aa146e302c7234d55ea592fda72d483873b20140412cc912b3e1dba8789451448f600c15c38f40989bc34c42aa18ceb1f796cab50c774afb5 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.3.txt000066400000000000000000000000001476434635200240040ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.4.txt000066400000000000000000000002011476434635200240100ustar00rootroot000000000000006e900c64b27dbeee3180e5e624dd8eff409305bddd95bb651e8105a00a699bbcf9dc321df87c815372221e02b332ffbb9512800a60e0daf4ed323b181f4e681a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.5.txt000066400000000000000000000002011476434635200240110ustar00rootroot00000000000000aff26d02fb3eb8751f763b12e4085c24e7efca383f1117b3b5ad119c328334f7f3abfdb294f9fc3f33a466c031f0ed856bf459d979eac93e7eff4f0f76610774 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.6.txt000066400000000000000000000002011476434635200240120ustar00rootroot00000000000000e304f7f23f6e940159d34f0d871054070a8235d657c141157072655d00787e9182629280509012160c757dcd1afd12982875dc0197e381393051d0798293cdb2 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.7.txt000066400000000000000000000002011476434635200240130ustar00rootroot0000000000000008d220a1b2580d9e763238d553838b692f26262be563b685e9ff69e02a6c03fe125647bd6b193256a4dab58aadeb45921ad986b51ad0d019e69b77e8ecc61650 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.10.8.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000de7618f67acf9016e74bde682a3fbcac61a422f6e61d06f85eebcf0aabb10111a55d2e05054bd68841909e0a53c73104c369c3e919447329ad6e1aaa2e8e7f26 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.11.0.txt000066400000000000000000000002001476434635200240040ustar00rootroot000000000000008f856d904af40ac0424743a1a40bad96408908202c031d7f0d67a74a085c000bb5db17e9e1c97dfbc79095b4006f82d18ca6f67fe830a655895ebda0e614ce71graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.12.0.txt000066400000000000000000000002011476434635200240060ustar00rootroot0000000000000075208ae66c220aa3d275a3765a7e15e9f589efb4fa7135fee57b33ecd13347f12ce8b4f5207b8457693a9b8f68eab5f11db746afbf2d0eba436396faf5502421 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.12.1.txt000066400000000000000000000002011476434635200240070ustar00rootroot000000000000000da30804e51647104a2b0be0bc9c5ca3ed43d8c64db33ae9afd7f7b73923a9243824e2d44a37cbb47372ec6a5c6b5aa54765d481dc51b885267ba7ab4672484d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.12.2.txt000066400000000000000000000002011476434635200240100ustar00rootroot000000000000002a12902ed9321419fedf166021e50fdbfd695ca7b6245bf10a4354f95f38ac60de7bc4465ec873d6ed1af7adf0f631308739de799243e88a893f7527b8df9df9 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.0.txt000066400000000000000000000002011476434635200240070ustar00rootroot000000000000005446303fdfad4e2cb2bb47535d60bdf11a3e039c7b1edf8b02f9e8218ea1b87367f59726e6ad227e32d74a51f39f776b36d5f0fe59de1e72475a38376509cfa6 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.1.txt000066400000000000000000000002011476434635200240100ustar00rootroot0000000000000079f699934f0e9da8f328256b5c67144046341011e0629234d0391f206a807a00f457c71e00eb03b8fd4f0779156d189b54b56f6d7525904acb5ea19d061fceaf graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.2.txt000066400000000000000000000002011476434635200240110ustar00rootroot00000000000000e1aa0bc146fa154e3fb3655fd8e96e4fa7adc27b165ebf2c56880f64ac1938e3ba8ce8e7c7c02e921d54255ddfe9f7e44ecc3fad8675eaa0b1055813e989c2ef graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.3.txt000066400000000000000000000002011476434635200240120ustar00rootroot00000000000000a1f2ed08b503cf48d8fe7e0ac7f09d194e5f215de1ddce9acad8a0a6e9abc0865d62583b84af9e2a18a8ede3caa47d8d0b148caca986813bd4d03ff4f5c9ff5e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.4.txt000066400000000000000000000002011476434635200240130ustar00rootroot000000000000003e1684ac0a06712bb565d0c4eff852f8240cee55824b75899d044db2815ae60c20fd367686a69e0b4f3bdd9764da18691d8af736561a13303d8be5cb9648b56a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.5.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000d481fb6595d6b0cf5a5cf0fe2c29367dd4687c6fa8f7a12373b896dca6e0b4464ccaa6a46d8f8dcabc247ce0b1818161091b18f39ff14b8d2bbbc72769a0df23 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.13.6.txt000066400000000000000000000002011476434635200240150ustar00rootroot00000000000000e380c037088472eac1d71a64b419dc917eadbcd5e91d0273cf97d394d9833cbd93070d9ae3f8227e401a953a6982571713a3672df9b7d8ca37f1f216b9d3011a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.14.0.txt000066400000000000000000000002011476434635200240100ustar00rootroot0000000000000022543434eb5bbb551f8a47fa2fa7f41f9210128b34e3bb8d88e47faf02fed606c9269d493fd3702cd8ae9861998ad967fff7095775a0fb8af8aa012b9dda06f2 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.14.1.txt000066400000000000000000000002011476434635200240110ustar00rootroot000000000000002c41c982e963f91f777e9b7c31dffc62d30e10cc741affdc168d94233016e3b07ae8ebe3c9631ea57b7a2433fd353f2087557dcfe2394d64e379485a20723893 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.0.txt000066400000000000000000000002011476434635200240110ustar00rootroot0000000000000006757f5a7d878584a29864cc3790e9687d119aaed5fe3c66afdf2ff498ceaba63c0bf9bc4fe81fdaad2b5e933acd7a889c33cec825abb673f6f624c16c5a110d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.2.txt000066400000000000000000000002011476434635200240130ustar00rootroot00000000000000ec02be5c1b0e7defc6686d42d3033397a144de006ef7ae9ce0168d3aee459529edab8ca357a95f204010bfa7cbd14b7a0c7049892cad968ff2ede267c5cc5224 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.3.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000ba4c18f634b340b6064fbb919f4b33445d0c0c16e81f462d929c9437498c1349571f055d73e2bc0388a7e34a54316592a9f606cff12095b9e70483d962349a71 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.4.txt000066400000000000000000000002011476434635200240150ustar00rootroot00000000000000bca1561e9657893e6143404525b0857be42dbbf2148b64004e09207c5dce5b5c98bb431381989ac73b846d58e68d5eae12240ff01abf89660d0c7c66ec2f5b07 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.5.txt000066400000000000000000000002011476434635200240160ustar00rootroot00000000000000dad57401f7f2029b45dbd3b3c307025320b999e9d22396888cbfcec41ebac21838d4fb7ab63925ca3fc4fed67323842cd6788ccd20c8e6f315dd54f76d848e3b graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.6.txt000066400000000000000000000002011476434635200240170ustar00rootroot00000000000000476db12210ef3f92de3ec5743d06fa371495932773161f7e7dc574db0b8f45b5beef99d677394b3c4e5371a1b92ffb7c923e02c27f39ec3c570812b5480acaa7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.15.7.txt000066400000000000000000000002011476434635200240200ustar00rootroot00000000000000fc69c5d1a8335ce8eba380e2d4aaf61e0f704e9dfd293fd435b485d3f84a191bb32b3eec91c8f718d307306abaea3b23f941e2e68cb8623c220d4009aa94b0fc graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.16.0.txt000066400000000000000000000002001476434635200240110ustar00rootroot0000000000000028dde609677a6303795e12452bad9e35b828bdc3cbfdaff5c40a8d30b6f1f53df6158fcb42659a5d55b89a696c9c0ec484c218c3df8c68abee4b02a141c5d284graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.16.1.txt000066400000000000000000000002011476434635200240130ustar00rootroot00000000000000d0d27966bcb3df9bb4f7b4188db4715321ce98beef0d57f759b003f79edf1d03dc6691380b0f4eef9f1124d5bc6932878e262af0ce98dcdd23a421b22c5232c8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.16.2.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000b181b6fc70695498b5faa3216664b9849a6b8e32ae36517e3cd82ea6373e61821e4b0e193339ce2e92ee5528d9c694794bfeb27a3f2cd822c9576168e37e6f77 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.0.txt000066400000000000000000000002011476434635200240130ustar00rootroot00000000000000ade1d11d1a0dad72e0472ac4bbd157a444d179ffb420246cea1d4bdd45d241bd520753f57816df6851e553669573455117940859c49b9840790621c827acd8e8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.1.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000594becf061695edb79ac93675a2c574cc06f7f4069d6af57974ba6da3b47a48876a0fce44dca6524042bc6dc97f1e415c534160424973ecf004efbea8d283b0e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.10.txt000066400000000000000000000002011476434635200240740ustar00rootroot000000000000009c8b3e77aba8029499736dc4ce306654805d105fd9149e32c7078e59aaf573590f0c8590d2b714547d7033a0b964e7d4ab4deb8ceb469bdb6b92f0d155776c24 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.11.txt000066400000000000000000000002011476434635200240750ustar00rootroot000000000000004445d2a146ffba7b5d4c89826755984a17bc20cf6013f8271372d4feca6cd31b37be1314af9b2dc26b77978ad5681e75788b5c89532d4a093f9776d970d5eb8e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.12.txt000066400000000000000000000002011476434635200240760ustar00rootroot00000000000000be3250e7a13552e41a8a27d3a9e25f2fcd6dd8e6433887f13ff5e0be03a28d67478e937c5eca33f9d3a322b36f1e5d3f596c7c5b026842e07d0603ed78872736 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.13.txt000066400000000000000000000002011476434635200240770ustar00rootroot00000000000000f14f76518749a8c6727efcfac579344c1fffc53f04cb26e0a94349f9f91a667529dd7fc1d838b82a478b50ae4ef11b03bff04e35add8d6035d11092c4bc8f625 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.14.txt000066400000000000000000000002011476434635200241000ustar00rootroot0000000000000079563aa9deb6b955e65c6e4a6c0326d98432932ad6c328886f7e4a13a0d22ba7813888cd07085c4503ade24068cb1a4d8a6b09e33b76b642ffc90bf560032a9f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.15.txt000066400000000000000000000002011476434635200241010ustar00rootroot0000000000000040b16be98dd17dd8b41979d9f9015e72119a9913648d5d10ab91f1dc1ee07e391e9f54f116d7a258ef722720ed61983533df79b0b58647fb2cf0f3399aac675a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.2.txt000066400000000000000000000002011476434635200240150ustar00rootroot00000000000000a33ca96756a41030479fd094c9cb907f28d95b182d52d8121e445cb76586ce653daefb6870ed8ef88dd33958ae981a3d1965c00d4f2890af0d637dd664fd9be1 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.3.txt000066400000000000000000000002011476434635200240160ustar00rootroot000000000000003987f32362d4bb4995078a0a6c3c9449ec65918aed02e49e9d1908ae6fb22f6acb2c56dbfddbad3072b68177b229965c441d85211341f46cd6969fba1f5545b2 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.4.txt000066400000000000000000000002011476434635200240170ustar00rootroot000000000000004d216428febffcc0c5c73c75a2f835470e60e86faa93d7cd5743dc51e4f0b68cd1da5f1e870cbd6d4e6616aa5ca3478fbe281b5b00a5913e21f35973228638fb graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.5.txt000066400000000000000000000002011476434635200240200ustar00rootroot00000000000000f5f32ea4acbcd18efffc7a8f076052d74b17eda16cbde21693c995c7428cddf5391579fb2b98c2839bcd11facb71edef1fa90e9dffdb309e6eea2672dcc99a61 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.6.txt000066400000000000000000000002011476434635200240210ustar00rootroot00000000000000a5dd58498ee11e2e1057151ba5e1c674f7adf4d6e1e47ad0af2a38ee7a075f5845a8b928620d63db8365eb12e669be586ec9b28adde8b63ba9fa67ead5080a82 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.7.txt000066400000000000000000000002011476434635200240220ustar00rootroot00000000000000d39669286b1f4e1f6227188755d59fdad2151ef67a6de3ca8deafb9c3eed69a12914d68005cc6cf9565c888e7cbd7c751db3824cc50389316329fa0723617ec8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.8.txt000066400000000000000000000002011476434635200240230ustar00rootroot00000000000000bf8de39f503f423a10569fa534ac06243e118c9d2039d6ffb9b10251b516de218e5998943ee0fe7b8ddb6f6aa6b84bdaeb87480397cd76cb6de37205fae40127 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.17.9.txt000066400000000000000000000002011476434635200240240ustar00rootroot000000000000001914a9f65eb64f67497e8a5315b65e8bc087c808a98ad77ac790fc0ab1fdef73c06ed277317cae8e210f992bd9f59915e7191d4a504681bf78e888b5177f1638 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.18.0.txt000066400000000000000000000002011476434635200240140ustar00rootroot000000000000002dcfda18af745b557c62c2b60c83c96dcc767ef4f09c4ec723ae599677571204f9db59f4617fdb69eb78e24e205d91e81f236ab3aca491b1318055d1ae933b3f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.18.1.txt000066400000000000000000000002011476434635200240150ustar00rootroot0000000000000034ebd540c1a341ab8fe8a59051af68c187311e407ec1ec9444ca5a2231c6b6c835ee13f9fa3bdde44b8b7d8439d2c8dce76364e8863badecb8c6fe67332ed93a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.18.2.txt000066400000000000000000000002011476434635200240160ustar00rootroot00000000000000c863ba11770864257cb232fc1cc25a2386c3387bcab8aa95a309e4ee67bb6e184f5e6281b45820cc1383cd80e3cdee8a5c011951e7ac6d3397c3925879017777 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.18.3.txt000066400000000000000000000002011476434635200240170ustar00rootroot00000000000000c0c009deab18661ad98d30c44f1cbb33a44b6a21c806428bfc6b50f28e0fbe67c7b2fd4696b8e4992b9d1cee3d4c71d7b7b4de91dbf0c103715f1542ba6a39c8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.19.0.txt000066400000000000000000000002011476434635200240150ustar00rootroot0000000000000070775118ede3e9600778bb92cc5b8a56f731ae133f820a7dd2e5d00b279a79e4a1e0be9e400b0fd092f1a8295d5cf10fa4b2931f834542998ede4327d25d9d12 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.19.1.txt000066400000000000000000000002011476434635200240160ustar00rootroot00000000000000aeff5df888036b4ccd8d7523673c077b2819015a0e1f27091b72de091a7d0cf609940ffba7cd73f3f48e0195c61c5e627fc86886775313f31beb31cb2d21e196 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.19.2.txt000066400000000000000000000002011476434635200240170ustar00rootroot00000000000000385d1311d4c5e41cf0c508b1ba6ee1edf16f29f010d913311b87d3b9f3bdc844dce970258fca3d606fc7d472f2024dbbd4d797c775c3bee455bfd158cb1e90fd graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.2.0.txt000066400000000000000000000002011476434635200237250ustar00rootroot00000000000000ec288ac8a27c79d493613abf9ae867a6ba3af28a029166d100c1153cad4c4039e8ba94beadf633d2889fe2af5c3bf91998c4ea67f00ac08150e9209b52c49938 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.2.1.txt000066400000000000000000000002011476434635200237260ustar00rootroot000000000000006362be7dc25124d8cda2f4ecace1b54088c8af7137f0a8789a80df63ace77ea25b5b9f09c72abb6e73deb204224d38f6f8de6a314fe255acca8fb8de3cc379d1 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.2.2.txt000066400000000000000000000002011476434635200237270ustar00rootroot00000000000000804dc71671ca8ed64f2fcf5fdfc7913758109c59b78a724dc6d6926829044aa08b354a862dcbc1bdb403def1f24e1e34eef2115778b671f87855cbfba62fa2b3 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.2.3.txt000066400000000000000000000002011476434635200237300ustar00rootroot00000000000000513828ef6e829ff917a08095443e51224f8a7dd5cbd2379e350e0505ad412d36fa9e151dc7ec034c65c4b564ff41ec4e40c9bbeb14501a245dd4bbfd61a64c78 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.20.0.txt000066400000000000000000000002011476434635200240050ustar00rootroot00000000000000813aa497534c608234e188a4272266cf2f2e14691c626c68c0ef1b05291dc305db1c274c57cf2d5bd83568fb7064bb121f8fecb617014496d23a98df3660486d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.20.1.txt000066400000000000000000000002011476434635200240060ustar00rootroot00000000000000bb055c688d9c427a9f0535e22041bc7c414c74a9c40bd626a7ccc59595303afc7e3361c644edda5041350f814a59dfc64f403030a32b948e707e6a1dbc72872e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.20.2.txt000066400000000000000000000002011476434635200240070ustar00rootroot00000000000000002710975d063bb3301eec6a2b52b068dfed50349bf44e7d7926709ce357cc70108bc05df1352929c8989b0425988e11ee2bc187967a0966211f30be5a0963b8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.20.3.txt000066400000000000000000000002011476434635200240100ustar00rootroot00000000000000d75f4a170d3763ed3f01c241bdb01e21bfb5fabf607c13a45ed4815ac7d4f98ff44e4e6bc2ab7b91d380d5a510a21c4224d95ca61ee52e3d10992fe022605570 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.20.4.txt000066400000000000000000000002011476434635200240110ustar00rootroot00000000000000ce98318463a74c268ca618f46cf441859c2a374abd5908f1f25c788f934d87a9ab97377c3422882e8ad86da4d926cfbc30356856f6d2312c793a168abea8069f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.0.txt000066400000000000000000000002011476434635200240060ustar00rootroot000000000000003ba6e99c5d9ff61a771f8e775027470b066ca8fd80b45926cfc5b9a89f09ea44bf616bc07099dc39c2f096bb70e0d331aa648de0779012be9fae6abd1e81fa5f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.1.txt000066400000000000000000000002011476434635200240070ustar00rootroot00000000000000d3cb24e0212471783cb116ffd3aeac56007692d9107fd9f3d5b50b65057865b321ca52c049ec87b196129140d044282ae807aa1e14f36623b72c6c88f2b90956 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.2.txt000066400000000000000000000002011476434635200240100ustar00rootroot0000000000000061049f9ecb5e4124486dd881ef942503988413f24256048a7ef93ce7ccae2ab9f16a8915e5ace0f04c84d4e75732acac93e36af23e893eba7d3959cc4787fc9f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.3.txt000066400000000000000000000002011476434635200240110ustar00rootroot0000000000000040342ee99d7aafd5de610927de407a3d736cf37afa3ecd9ecfb35abade00675d0f4156ad47f81871d63658a67ff59a3fb46660dfc28a10d21223ac40cdd13bdb graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.4.txt000066400000000000000000000002011476434635200240120ustar00rootroot000000000000007f47e41ca607a330a0dc988a9706fe8fa4065ee9045512ef0d5e9ebe12f53040e761cb9162448c1625af7d230baa2d24cdd3efbba0e4852bf855a075c2857bf6 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.5.txt000066400000000000000000000002011476434635200240130ustar00rootroot00000000000000d128ce5bcdf09b67de33cc8a32f3e8e6f3efb602f7c61bce7001ad7f616299923b90ada691d50a120a73478673cc7db49736822013556de86dd902209a5c5b0d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.21.6.txt000066400000000000000000000002011476434635200240140ustar00rootroot000000000000003cfa5ee53774a042aa9cbdc2a64747265b359048db0087b24d084611c2c1a97a7f3f20547612d5681a338c32fd45c496c21a511872d098bde50ba8e66579c456 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.22.0.txt000066400000000000000000000002011476434635200240070ustar00rootroot00000000000000f35ee054e97240c089f1f56d233875e6038ac264facfeddc0aeb2abd5d1c7c0844e92f2ab3087c45c2999f4fd7bec8720bcfd4794af19dc5e565afa884e1be85 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.22.1.txt000066400000000000000000000002011476434635200240100ustar00rootroot00000000000000fb7fc671dec8dfc0528b42177b380ff7997340029746c367523aee8dd5bb2b335cd61b6663f5b7a198df5473fe61b31b2eadfb61fe93ca13bfe18d8294e80329 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.22.2.txt000066400000000000000000000002011476434635200240110ustar00rootroot000000000000003e7421186fdcc7ef7ab87f3a294b84d3478f47dbe54a5cf68ad739625c4cb9b58b3ac83c900039ee65b2e789b37d5faf9a67586c29045fc099bbd3e6f09eff85 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.22.3.txt000066400000000000000000000002011476434635200240120ustar00rootroot00000000000000300b9bb439118289cba69db994e9402697114d1b262b7c2879e49e19266f7b7b97d0aab45f54921c5449847579043867fbaf451165d616da78d945927415fdb9 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.0.txt000066400000000000000000000002011476434635200240100ustar00rootroot00000000000000c87ffc9ad12f452c013e996f2c4a792869c4a6b333c01091cb535ee62ccdc8f8546ae09738d08bd284bbfc27719b6cf22730c06678434e807407ce3b601dc090 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.1.txt000066400000000000000000000002011476434635200240110ustar00rootroot00000000000000cbcbceda448c0b31acfbb5fc5ac18dc1bd90787009bb9b17758629b3fc1b1fb697c64666bb864d2a3cf968ee14f0c7aaf5e9887c4eefded223689b414c0a3b71 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.2.txt000066400000000000000000000002011476434635200240120ustar00rootroot000000000000008006a4ce467f9fe0842757075fbbfd6dbeddafe5dc1e3de640a8fb3406126155cd782b16128fdf056d79d6681eff83c5f2b1e86b49460a7a0b23b2b231ae781a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.3.txt000066400000000000000000000002011476434635200240130ustar00rootroot000000000000008bbab870128ce2e9283c41ef84959f99b15f003f7b0d499c95b2e40c0f1b706e65b3d9046749a56ba241046d2cdb5c9411c1a414efd87b7a9961595074c39983 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.4.txt000066400000000000000000000002011476434635200240140ustar00rootroot0000000000000084f8cf2aa279a63440dc412b7ccf6338df48b4b3f5aa21af99ad15febe667d277b725bb924e1ac48766ba72d02dcc0170f4d5cdb2c280615ec60a98839391d89 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.5.txt000066400000000000000000000002011476434635200240150ustar00rootroot000000000000006adfe5c8e153b43bd4fdb2f0dc79c2e5d035d3fe9e571c2f97255edd87d2419c91f8495bedaab3c3761d4e6b7e7b460c091d5189a184e6e2ed785ea06f204597 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.6.txt000066400000000000000000000002011476434635200240160ustar00rootroot00000000000000f910abcf08a2ad51e46f68f492b2d651fa5d5debf80761464ce1809d2672b1db1064db304f6f5bd19c2587792b160c9bb5fa526b371fd3a4060e8a0841f5452d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.7.txt000066400000000000000000000002011476434635200240170ustar00rootroot00000000000000461c5c8d68892208c8fe457a51a6c18b253ec32cd37aface4fa2eae81fca94874c654a95a16f21571134476970a66304b261063c7fe2cd8a19daaba9bb90389b graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.8.txt000066400000000000000000000002011476434635200240200ustar00rootroot00000000000000e6f77862bf426401c4b86bfc92eae792da5ae3cc830c03ae6f90e80efa8ff1e324cfd4a49474832b0f82d32c2e568018b757a5efcbd896ed4de7ee5dc736e9e5 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.23.9.txt000066400000000000000000000002011476434635200240210ustar00rootroot000000000000003b93d5b119ca2b21784fb6b151f5e9f9b9630c8cd4f26995d87669097e2d19048588d4055dc230f2f926aa248f88bfbb4a4ec547c2f5ad6b801ff43b05c5c391 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.0.txt000066400000000000000000000002011476434635200240110ustar00rootroot00000000000000325cc56c6cb2773eb9341963371f1c80167168a76601a96a097e7d60f77bed0715ef8b21e5132555308295775d061674cf87f47ce390155496099fa1e49bb441 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.1.txt000066400000000000000000000002011476434635200240120ustar00rootroot00000000000000b6934de96b0d4f0758d0e84671d5e9d82e5e5790c01d3dbb53db1793aa4836e216d0962386542126f3b2dc1a27b35bec1ae802e644794a6029ce38314269d5f5 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.10.txt000066400000000000000000000002011476434635200240720ustar00rootroot00000000000000e53374f59ccb74dae6d99f2d9fffff6a932f442d31646aa4dd7a6725f434dd08925dba18e4c049432cebb48b0cdabdb72a0653b0c8eeb32399fb3a8cecb9380b graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.11.txt000066400000000000000000000002011476434635200240730ustar00rootroot0000000000000026b5a3d30b0866b87dbcc800f5860c92f982aa04fdf8a855674ffec3bc8ce1fbe041672df9d00c4318040c44239b9c3ab829c5f99f6568d0d37f3cea19ecdb81 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.12.txt000066400000000000000000000002011476434635200240740ustar00rootroot00000000000000019ea12ce24507d58afd8ffbcd4a8de990f8cdf354c7c273480dd09eca211a709dcb7e41cedb99813cdfc680bae8eab1f25826246ea85c5860898f60c34b3af8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.13.txt000066400000000000000000000002011476434635200240750ustar00rootroot00000000000000566d9dfda5e0c39310f5c54e5c3cf44d3f7118224ac8a1aab2ccd2c9a46f34acaf29bf99a5baf2e2454a83c0b894cedff316a5b1e85d577d3c0721297afad78e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.14.txt000066400000000000000000000002011476434635200240760ustar00rootroot00000000000000d3e3134b7e49c1f0c7c368c0fb5cdf0c27448b7527aac4cc7df012137b52919962b41ed0c3c503298232ccd7ea03a781be798b310af654906150b446a5043671 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.15.txt000066400000000000000000000002011476434635200240770ustar00rootroot00000000000000f2b2eb456344075b733c1fac38c258ec02b0fa492ecc124d6d5a090b7b949eed1166da008b90182db9d29f106236251b3a2cc61fd2e99fe7dba268e20c3a0ddd graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.2.txt000066400000000000000000000002011476434635200240130ustar00rootroot000000000000004b1fb57d91cd1ee1f94ecfdacd0b67585c3638c8cc1b06bfcdd7c8e050f15bba37c573fb1a394451ffcf5cc2077f39c24a77b2f130943538a3c01098d487228e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.3.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000c0dc667c78c347daecfaca74da7e7cd230cd1c3992dc4c0b30014aa6b8add65ef7b110eca420495b93ee4f90a472297bde81e0758b77675389e3d9dd7edc0faf graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.4.txt000066400000000000000000000002011476434635200240150ustar00rootroot000000000000002a89e5dd34183dcbaf9474a26d51c51b7f37ef39aa09fe9e28de310910b00b81751a9d03e40b0c70dee7422d6b10bc92e6adae7fc480d8e0c943ee6ecce05761 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.5.txt000066400000000000000000000002011476434635200240160ustar00rootroot00000000000000a179aa5876ac3dc84d516ed73c08fdae2879b7ed8a725beb762860f02f671061d9e4f6569828bce0754da49ed3a1f46d134444a71ffd1a60d71078ee16260391 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.6.txt000066400000000000000000000002011476434635200240170ustar00rootroot0000000000000060c791368edeecb40481e573b129851090fc11f3b5f2b6a6a05ae1f7a64ff53e113addc456a4df469b9bcdf84199bc52381df6fc5cf03d65028023f5e3520be9 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.7.txt000066400000000000000000000002011476434635200240200ustar00rootroot00000000000000a6cfd98894639e5fbf7c687b60efcb66c7c0c7a9cc8a810083a2f9cc7d87a3ac51df3c3b6b4d49e4ea95cc7bae2e839177d83550cba992eaa99bfffcd85168a7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.8.txt000066400000000000000000000002011476434635200240210ustar00rootroot00000000000000f684ab732ae69ead5c4726c0c7bf2ec72127061b499cfc7f144348fb7b584b3cfdd02f8382158f849b6d0a109bcf1d0597a9eaa6dd427e254a784337b57aec7f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.24.9.txt000066400000000000000000000002011476434635200240220ustar00rootroot0000000000000006f556766da9333ddcf1d75e04707501e9f36420870da5df72e4c3e20d757fb8e157d3ea5fae5650090efea305bd7fce790cc5829ec08d322022c8f3c0c4ebff graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.25.0.txt000066400000000000000000000002011476434635200240120ustar00rootroot000000000000005cefd636eae11fec8127d2245d9cc2d630492311ab55ccae464058ea83920f24342d64d22afff325a1132f5a55f4c0721cc77077311b2ad906a39d4a7a6ec425 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.25.1.txt000066400000000000000000000002011476434635200240130ustar00rootroot000000000000002ce7d2337ba0ce219cb8c8cd80f39b6db3a523175ff1ef09410b01a4f6f19139b19af3b54174d8cd09ce6fdb0e7eb526825fc50433d64c3db36d2ddee916fb2c graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.25.2.txt000066400000000000000000000002011476434635200240140ustar00rootroot0000000000000039a4910d324054894d3b6695b9651040f7fdbb418450827409c6b8361a628a16a5a7ae6f1bb2d57b9b3784218862f7428572a0baf9d18ac3b9c0e5267018e479 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.26.0.txt000066400000000000000000000002011476434635200240130ustar00rootroot00000000000000c5e6955c2878ed82c168a6f73abee8e6ed5a79a6f57bc25131a44603e88c4bab17726f1f6c1c856ad3aeae96f8a19d38fae864019f6d2b64306f9c4e5433a6ce graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.26.2.txt000066400000000000000000000002011476434635200240150ustar00rootroot00000000000000c274b2993b36849ef08083eed7b9712d88eada464a893bdc123bbb7dd46e90417e3e3206e377d00a23a730a04170440375c6f2198418043d2fdc28baa0463e28 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.26.3.txt000066400000000000000000000002011476434635200240160ustar00rootroot000000000000001913e81ac6a1f6055b023b4e33d0188f801c722e4f6f18a410f2776169e1fa1dc87fad1e89416305ff4e08e352f9b7107a61fe9edf65d215ec2547cc4a78eb81 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.26.5.txt000066400000000000000000000002011476434635200240200ustar00rootroot0000000000000034218544d143f790a5424f12ec792f04b2d4e0e4244d549cb9baa6c3eacc1b68d2f0960a678479507c893050804d5cad916ba3ad9f8caec9c9958c7c02fd0dd5 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.3.0.txt000066400000000000000000000002011476434635200237260ustar00rootroot00000000000000cf7d74176481decbc4b8eea28a1133076fd184856bbee95f583f9c0818771028ec65d55f7f711df439faf5c1c85a47f22e7f4165665261922aa8b6e0d0d950d4 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.0.txt000066400000000000000000000002011476434635200237270ustar00rootroot00000000000000b51cb1d642400b5a3099d0b9194b82050d812d4f1ba782c707f7f3090931439baf241e4eae745452399f48c60de64082689069cbf3fb1b371c943f2fd493dfe7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.1.txt000066400000000000000000000002011476434635200237300ustar00rootroot00000000000000e565badaef42bdcf0c9dd679947d46bbb9149dfab40ed481f5195df6e24df4844df2c7fc333df52b7fac05d1008353d99d7930591bcaace4055d41bdd1358e08 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.2.txt000066400000000000000000000002011476434635200237310ustar00rootroot0000000000000026805a034902ede828fbf023b88aa7a9c03f0ee6dfc8e8dafb1404fdc1c64959a5b952d794d356ed08f05ce1e1bcd2d1be48054578003885ba91ad002d94c470 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.3.txt000066400000000000000000000002011476434635200237320ustar00rootroot000000000000001922b4218dc1549263354398c358fd79146de111b8e01d32408d0bcac382f4b90e27106e6338e36e9e36175761e8c0a55a2c86b01239cd64ff7a2ea9c2cba5ee graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.4.txt000066400000000000000000000002011476434635200237330ustar00rootroot0000000000000065231e030a03fb97fe4bb9d57d3c6586f8a243cc164dcf9e9119046a1ae96ec527d498639a1e434654747e236f412130219a3513152e9190974d2e0a8f00af2b graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.5.txt000066400000000000000000000002011476434635200237340ustar00rootroot000000000000002934e5e305b16beca9e37e6b55f4969e24877c86e68c2825ed8b8fae1e84c3ee97ecf3e9450c5d34d1bc1410a32c045f60ca65d544a9c960ab1d3ef5fb5ec18d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.6.txt000066400000000000000000000002011476434635200237350ustar00rootroot000000000000002956ec0ff31daf1aa6c6b7078ca56807ebab9f24cf8be2cca2bb2e0da164ecb0efbe6f8a37751af0179ffd84c90243e136363a47db18bfa0bcc7acb1a264d903 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.7.txt000066400000000000000000000002011476434635200237360ustar00rootroot00000000000000f4d1816ae87e21d2d1f8d7ab1ba75606f4714259ef8190e8d24fe9dfa708c15311fddad571c6815b8bb4e324377a0634983f262f3d4bd6068625d71401f0d850 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.4.8.txt000066400000000000000000000000001476434635200237340ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.0.txt000066400000000000000000000002011476434635200237300ustar00rootroot00000000000000a2b3e35485b4cbd0656aa0b6a6583809e8db9364a913ef94415c40d26fb8b55201f77d91cc8c411f45e4ebf949530262478a99e4655a4573f3cf709f82a43ab7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.1.txt000066400000000000000000000002011476434635200237310ustar00rootroot00000000000000a7a844b3e17989a6a4b7638627bc8045b0ce68330e683c03af4960470aaf53c381490bf16e1fb216d79c1c7db528e634d1c9559a1cc9635a39cf3bda84cf4502 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.2.txt000066400000000000000000000002011476434635200237320ustar00rootroot000000000000006578f9ddf46edd80fe156ac11799f1e979c581affbddca2ecadb4246934722666677a88e648e3d4799008f9c482f5ff4477e8cb62b4838196d07bf6665f64074 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.3.txt000066400000000000000000000002011476434635200237330ustar00rootroot0000000000000026d1e4db3e66dc9469053d424c29cd5a02f9d91d16d5db2eede9fe6c924640dedf27e304a99b38841550eaa1d0a1343b3f17cea0f40b6277d0f94a9df5257130 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.4.txt000066400000000000000000000002011476434635200237340ustar00rootroot0000000000000060c95c06fdf6b106683cd87ce993f2b8f6903f85050826d26bd5d579144c4ab71068166c7e9d6c47577c38cb93b2f4095f87b60a69e8ff391338d4c935c0e5e1 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.5.txt000066400000000000000000000002011476434635200237350ustar00rootroot000000000000008657b73725fe9390deeaf37f5d6a902cbd2939f8039a9ef0e96d3a7000ab5f835023aa323dad399ef8a00c8686dca5397295b8aadaa9367105976720c9dc9e61 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.6.txt000066400000000000000000000002011476434635200237360ustar00rootroot00000000000000928afc8c4526b1869a9e88b2e62115a97d9a062e5bc36a3393a85d887a038da293a08e9baa828d6121a1d0a03974aff7b91bed7cb38d30ebe5dbb5e3e8f96cf7 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.7.txt000066400000000000000000000002011476434635200237370ustar00rootroot000000000000008b59d45fd5bb4debf257f4215fcc0e304ac31bcf814cb2684fa49fd1696fbad35d88f6e578c502f927819bffd1efce4dd0909e59a51ba6a4af53618e5ff20165 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.8.txt000066400000000000000000000002011476434635200237400ustar00rootroot000000000000000119e593bdb84bc500b61bc4f4712d5f15b33f5fa5e6860f9e0ecdc0215ef7e61dd53472f5b03fee5ff149649712e9cf1ffc25141875c9cc4eab0cb2c9ec626b graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.5.9.txt000066400000000000000000000002011476434635200237410ustar00rootroot00000000000000d82db8271dd1bae9d6b49b0e3c9c9446dc95cdaa1405b99af6bf65c551dc8994c445137ec98e11c8b61363b9490ea2fc22ea98f966dbf645f0dcbef2419f3220 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.6.0.txt000066400000000000000000000002011476434635200237310ustar00rootroot000000000000000fa5c3a16722351ac3b57f54b2cab3dfced63a5cdf1b90d12824e6b69424135d4ae0187089b6b7cc27e5a47f1bc89e2ee111001142789eb1bdab062e048d466a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.6.1.txt000066400000000000000000000002011476434635200237320ustar00rootroot000000000000007da501aaf5560330a34d52d8df2170b2dc1ad307b922abe2d6a2da117c3dad8281385bcd8a092a4ed6642e7bb91e114d552c859511523e8f719fca5ee50e8171 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.6.2.txt000066400000000000000000000002011476434635200237330ustar00rootroot00000000000000804a6575faed960eb1081ba5988fcf53d89c55f68b251397aa5a59fed1e9e0e4272bf18a27dfa03ea15d72271893be93e40423b54a8f2eece9a314ede8a5d517 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.6.3.txt000066400000000000000000000002011476434635200237340ustar00rootroot000000000000005e8d1483abc2f5e65e0cd7dddcf826b0fe2dda0166ae2e13e107bf10c0268c0267b21b851e4f9671877fb5a0cf9a71081e2536eaa76d3f5ee2adf9e899b6d751 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.6.4.txt000066400000000000000000000002011476434635200237350ustar00rootroot00000000000000c4cbd897270ecd150eb59784d7a568c61e9d9f7c9ad9a99cd7547be857cfde0ca268f1d6d0ce726d4f6003882253ab6a63d334e406a1fae4cb55b07fc9f61b38 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.6.5.txt000066400000000000000000000002011476434635200237360ustar00rootroot00000000000000ad004946d3f432fe8c3694e8a8410d6ce6f29a7fbed0d785d05e488e6ecb8ccf523d2d73514fcee8481ceb66053d9eb88297f4fab25d031938c08ee2dcc58968 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.0.txt000066400000000000000000000002011476434635200237320ustar00rootroot00000000000000377a41d7a5831d0d20176bd7ebc7ad4ba24cdf7bdbc2879eb35844ece7e48390ca598061b58b6fe60b965af7f9097b353dc30b9ce89180ac03bc54a4fe3ee59d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.1.txt000066400000000000000000000002011476434635200237330ustar00rootroot000000000000000c22a1050619fc84f024a8928831c8b1dcc733b05742b249c2928d4d97f7bc4106839a1038e92671b42ac8bf2c0647dcdc09a6c7cd8ef240a67f841bf7ed3ffc graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.10.txt000066400000000000000000000002011476434635200240130ustar00rootroot000000000000009cc3900fa9b04a667146595141586592e1c7f72d4fb88d6af8e95b74d50cb8c096e5abd00d35e295a66dbdc7774b0afd311a20747e6051d786da36564282aeca graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.11.txt000066400000000000000000000002011476434635200240140ustar00rootroot00000000000000983b45f0aa5a7905d98cbe6571a4028fcbc115eff42deaa378edf5591b546a6b5e5cddfbbb39b5d363ffc16147dc72e056bd66c8f3e8adba97e58064f2c12695 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.12.txt000066400000000000000000000002011476434635200240150ustar00rootroot00000000000000739bd0c352ef4d183426504ddd41456d05191fcf3230214e9e84d7011a84c65263ec3299d9ef42a829e49d370ece69d15cd3f3d913bf63043fd95f55b1f6ac48 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.13.txt000066400000000000000000000002011476434635200240160ustar00rootroot000000000000007d9aae99f424473e4e07aa6e119645027d8da99f062c162cc7f67929fddd886192745383b70e053e2df6ae2176208c5781b92203f8a7194c1390ec13dce36a12 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.2.txt000066400000000000000000000002011476434635200237340ustar00rootroot000000000000005c8539eca13d99875f50d8a77c27327ab0c14eea88e31e0ffb78a81990f1e7bcdb34b2cc2ba0ec647cbc36ce74c4d990f6483381388166d4e6a9191c77c69d4d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.3.txt000066400000000000000000000002011476434635200237350ustar00rootroot000000000000007c1076a9065fbb7fe69796e8bf33454e1ed59aac0fb967b08e7756193d3a9aa41ba4f92e36ddb9e7d50c73cce42d516c0d0d2492ba1ec3f1af005597513c6f1b graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.4.txt000066400000000000000000000002011476434635200237360ustar00rootroot000000000000003ab8ffacb8cd9ee0b22695b63e8ba4a4602007f54928cf4745b2d6a8866bc8291806ab492526e4ebb2c297ec27a7bb605afe674d440ee3027d4c192dc06978a9 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.5.txt000066400000000000000000000002011476434635200237370ustar00rootroot000000000000003ec4d622f1a1d2efc910872db03f79ca43c35294fa7a93adf5283c68faf21cb7b2a9116d47a8b0e49f174276b84751d8e9e16a1a51bc20ab5ea8a40df2b41183 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.6.txt000066400000000000000000000002011476434635200237400ustar00rootroot000000000000002f7fd5c57b8564bb9af06d82449a46957c6adf723dad61092d6e2fa91e79966fa15f6dbe49df90e99f56d51c9808c1c5e6f25c80855728923a69bd77646d894a graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.7.txt000066400000000000000000000002011476434635200237410ustar00rootroot00000000000000a3625b161363afb04bbf497ae1af1cc3c580753c95a2d281c1788f162fb146c92f4b73b7faf053cc73df6db61e90b4eb37d3cbe7247b9bc6edc92f217f6e691c graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.8.txt000066400000000000000000000002011476434635200237420ustar00rootroot000000000000003aea6af5e7ed2a5ebab879d3ea65fea6b60be60d7b3f3663d03831797b3970beb896a82a035b8782c693b0f9e36c9f3d3b937ea09b165ee1478f393881f5ba18 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.7.9.txt000066400000000000000000000002011476434635200237430ustar00rootroot00000000000000a9098604f33ebd469909ee7be84dbe8636adce7e91199c442d95ddc35da4773fe7117057cf1a0449611625c902de03a26d015f82b8917c97b8bbe5e4f1915d4e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.8.0.txt000066400000000000000000000002011476434635200237330ustar00rootroot0000000000000066ee5869510364c01668107c6343085e9f2755a07fcd4fb65d251e57773fa317c24bb2a3da091f796523a30bad6f6d310bb50d0c57d3a29a3ff4bc65215e5c2d graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.8.1.txt000066400000000000000000000002011476434635200237340ustar00rootroot00000000000000924658f0f8cbd15afa5210d46c2e2c13a768ad0bb49e3bef00096b1d8836600a5e00d42ed7b3f04b49870c4042f97916e13f1a51d3ea367fdc5945fd4402df64 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.8.2.txt000066400000000000000000000002011476434635200237350ustar00rootroot0000000000000078623f5ef2359e170dc04eaaf1b5397b74082f962b35bda766e2574218ad55c2b490884f1f76e7bd66acd091f68539b6ecf5f8eedc63190c26aeedb976278562 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.0.txt000066400000000000000000000002011476434635200237340ustar00rootroot00000000000000cee3e8dfe8a1c7016ef9578edab3c81dc0ad7f87df1e97a860ea2ef455fb2ba88ecf8120529b0feea683ce623ce2b7e6172e97bf2850f73ba32b858410661676 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.1.txt000066400000000000000000000002011476434635200237350ustar00rootroot00000000000000ecde6cf51e0eb328ee6185334acc9f16b1ccae2568a8ea7f45d501a14a66ea2af76b41e240dd255e4d05e8fd5b8f3d6866c02daa7f30a6e0b1053ced95bee1e4 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.10.txt000066400000000000000000000002011476434635200240150ustar00rootroot000000000000008a7526d55d9dd3e4ba042b4e950105a9b99b09373fdecf8017ffb002bcb1aacbef6ebdd213588ec96558e49c2f23e9136f3167c928156a815045176ff2161b70 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.11.txt000066400000000000000000000002011476434635200240160ustar00rootroot000000000000006ff8342db34dd6f7a7a7e11b2925df9205810ca4ce15ba2ec8a2ec79eafebdd0c1d7c706f82d6b5fe536d7b19c4ab4eda2c0129f1d0db9d6f597a2c92f5b38d3 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.12.txt000066400000000000000000000002011476434635200240170ustar00rootroot00000000000000ce8e9583918491e341bc47eb6a08712ea310d91361f7324e9e10574e60cde062deecf58306204267a08bb8a8ffdbb85fe73fa8382f91fb51cbc6f3ea7919df9f graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.13.txt000066400000000000000000000002011476434635200240200ustar00rootroot00000000000000074391c2bdc294a1ec027d043a6bf172f70bf5ff75f0062821cabd5ce7469c88a3b60a0ed644e8bae178fdd9db26acef20b0721fcc708c1825da256ec5f2b4fb graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.2.txt000066400000000000000000000002011476434635200237360ustar00rootroot00000000000000a2370a8984ef9acb0664cc982bfc22fa2fc33df631b9e5b80fa8a2a9e8ef68d0c296f1f297e405c44b123445de67edc83dfcd6960b53063b0add7906202e5fb8 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.3.txt000066400000000000000000000002011476434635200237370ustar00rootroot00000000000000f70be733046923edab517e4bfbada4dcbc9f9234d4706bce59a8d9f23100545d994e03c49ebe525ad4435d33b5abfe25191c690fd20d8e16ea0aa0979438f21e graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.4.txt000066400000000000000000000002011476434635200237400ustar00rootroot00000000000000b9b8771b0dea2b4fe2d426ae0c84a522884c2b36cedd1095ded49fef9bc90c0373d70ff871635223f07f12ac9a067978d91d4b42292070e44f2a5f223e5f4d88 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.5.txt000066400000000000000000000002011476434635200237410ustar00rootroot00000000000000de80808d62c6f49cc391ad2f0badc6040ac174271656649f0837aa11bb9188a5faa7b33c6a7640129f4b2df4cb1a00c823efcceff4f96aeac77655be4c51bd20 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.6.txt000066400000000000000000000002011476434635200237420ustar00rootroot000000000000003e14feb6dd6a14d26c76f3acce640845f786961d4718730d4509e3b72990cc2a184db8e6d17f612fdcb1cd5f5e85a02f72a8bc69124b7173f0019664baa5cdf2 graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.7.txt000066400000000000000000000002011476434635200237430ustar00rootroot00000000000000aac0fdf50112fb896616ce4dffe1e427dce076f7d13cfa6b6f62ef97c26eee56c3ff4e8e2e088ecf3dee263d841c294651dc65514159b3f4cab0cd6e53f1e0de graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.8.txt000066400000000000000000000002011476434635200237440ustar00rootroot000000000000004da4d21bec267d782224732143535a3d9bd210c1b5ed9df56f9b21ffc9415ac04687b2ff01c4c6d8dd0c82613ce323db08e96b727e95a62855c47112c49d07ab graphql-ruby-2.2.17/guides/pro/checksums/graphql-pro-1.9.9.txt000066400000000000000000000002011476434635200237450ustar00rootroot00000000000000055f07c55bf22cda1ac6ce58b8acb74812ae150c3235ba7769b47933f827b4956fe9c036a40d48a4051554d161d9274c21dac57694daff1a0a8ee2558b838f2f graphql-ruby-2.2.17/guides/pro/checksums/pro-1.26.4.txt000066400000000000000000000002011476434635200223630ustar00rootroot00000000000000d1099e2f64e9ad256d86a624904a6a5fdd28241bbfa478fcd25bc32af53e5200892ce8e4808be0bcdd8b42a74e5463565002eb8810928748d3390c5c7f4eb736 graphql-ruby-2.2.17/guides/pro/dashboard.md000066400000000000000000000051621476434635200205730ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Dashboard desc: Installing GraphQL-Pro's Dashboard index: 4 pro: true --- [GraphQL-Pro](https://graphql.pro) includes a web dashboard for monitoring {% internal_link "Operation Store", "/operation_store/overview" %} and {% internal_link "subscriptions", "/subscriptions/pusher_implementation" %}. ## Installation To hook up the Dashboard, add it to `routes.rb` ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Add the GraphQL::Pro Dashboard # TODO: authorize, see below mount MySchema.dashboard, at: "/graphql/dashboard" end ``` With this configuration, it will be available at `/graphql/dashboard`. The dashboard is a Rack app, so you can mount it in Sinatra or any other Rack app. #### Lazy-loading the schema Alternatively, you can set up the dashboard to load the schema during the first request. To do that, initialize `GraphQL::Pro::Routes::Lazy` with a string that gives the fully-qualified name of your schema class, for example: ```ruby Rails.application.routes.draw do # ... # Add the GraphQL::Pro Dashboard # TODO: authorize, see below lazy_routes = GraphQL::Pro::Routes::Lazy.new("MySchema") mount lazy_routes.dashboard, at: "/graphql/dashboard" end ``` With this setup, `MySchema` will be loaded when the dashboard serves its first request. This can speed up your application's boot in development since it doesn't load the whole GraphQL schema when building the routes. ## Authorizing the Dashboard You should only allow admin users to see `/graphql/dashboard` because it allows viewers to delete stored operations. ### Rails Routing Constraints Use [Rails routing constraints](https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-constraints) to restrict access to authorized users, for example: ```ruby # Check the secure session for a staff flag: STAFF_ONLY = ->(request) { request.session["staff"] == true } # Only serve the GraphQL Dashboard to staff users: constraints(STAFF_ONLY) do mount MySchema.dashboard, at: "/graphql/dashboard" end ``` ### Rack Basic Authentication Insert the `Rack::Auth::Basic` middleware, before the web view. This prompts for a username and password when visiting the dashboard. ```ruby graphql_dashboard = Rack::Builder.new do use(Rack::Auth::Basic) do |username, password| username == ENV.fetch("GRAPHQL_USERNAME") && password == ENV.fetch("GRAPHQL_PASSWORD") end run MySchema.dashboard end mount graphql_dashboard, at: "/graphql/dashboard" ``` graphql-ruby-2.2.17/guides/pro/encoders.md000066400000000000000000000103041476434635200204400ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Encrypted, Versioned Cursors and IDs desc: Increased opacity and configurability for Relay identifiers index: 6 pro: true --- `GraphQL::Pro` includes a mechanism for serving encrypted, versioned cursors and IDs. This provides some benefits: - Users can't reverse-engineer node IDs or connection cursors, removing a possible attack vector. - You can gradually transition between cursor strategies, adding encrypting while supporting any "stale" encoders which clients already have. `GraphQL::Pro`'s encrypted encoders provide a few security features: - Key-based encryption by `aes-128-gcm` by default - Authentication - Nonces for cursors (but not IDs, that would be silly) ## Defining an Encoder Encoders can be created by subclassing `GraphQL::Pro::Encoder`: ```ruby class MyEncoder < GraphQL::Pro::Encoder key("f411f30...") # optional: tag("81ce51c307") end ``` - `key` is the encryption key for this encoder. You can generate one with: `require "securerandom"; SecureRandom.bytes(16)` - `tag`, if provided, is used as authentication data or for disambiguating versioned encoders ## Encrypting Cursors Encrypt cursors by attaching an encrypted encoder to `Schema#cursor_encoder`: ```ruby class MySchema GraphQL::Schema cursor_encoder(MyCursorEncoder) end ``` Now, built-in connection implementations will use that encoder for cursors. If you implement your own connections, you can access the encoder's encryption methods via {{ "GraphQL::Pagination::Connection#encode" | api_doc }} and {{ "GraphQL::Pagination::Connection#decode" | api_doc }}. ## Encrypting IDs Encrypt IDs by using encoders in `Schema.id_from_object` and `Schema.object_from_id`: ```ruby class MySchema < GraphQL::Schema def self.id_from_object(object, type, ctx) id_data = "#{object.class.name}/#{object.id}" MyIDEncoder.encode(id_data) end def self.object_from_id(id, ctx) id_data = MyIDEncoder.decode(id) class_name, id = id_data.split("/") class_name.constantize.find(id) end end ``` Note that IDs are _not_ encrypted with nonces. This means that if someone can _guess_ how IDs are constructed, they can determine the encryption key (a kind of [known-plaintext attack](https://en.wikipedia.org/wiki/Known-plaintext_attack)). To reduce this risk, make your plaintext IDs unpredictable, for example, by appending a salt or obfuscating their content. ## Versioning You can combine several encoders into a single chain of versioned encoders. Pass them to `.versioned`, newest-to-oldest: ```ruby # Define some encoders ... class NewSecureEncoder < GraphQL::Pro::Encoder # ... end class OldSecureEncoder < GraphQL::Pro::Encoder # ... end class LegacyInsecureEncoder < GraphQL::Pro::Encoder # ... end # Then order them by priority: VersionedEncoder = GraphQL::Pro::Encoder.versioned( # Newest: NewSecureEncoder, OldSecureEncoder, # Oldest: LegacyInsecureEncoder ) ``` When receiving an ID or cursor, a versioned encoders tries each encoder in sequence. When creating a new ID or cursor, the encoder always uses the first encoder. This way, clients will receiving _new_ encoders, but the server will still accept _old_ encoders (until the old one is removed from the list). `VersionedEncoder#decode_versioned` returns two values: the decoded data _and_ the encoder which successfully decoded it. You can use this to determine how to process decoded data. For example, you can switch on the encoder: ```ruby data, encoder = VersionedEncoder.decode_versioned(id) case encoder when UUIDEncoder find_by_uuid(data) when SQLPrimaryKeyEncoder find_by_pk(data) when nil # `id` could not be decoded nil end ``` ## Encoding By default, encrypted bytes is stringified as base-64. You can specific a custom encoder with the `Encoder#encoder` definition. For example, you could define an encode which uses URL-safe base-64 functions: ```ruby module URLSafeEncoder def self.encode(str) Base64.urlsafe_encode64(str) end def self.decode(str) Base64.urlsafe_decode64(str) end end ``` Then attach it to your encoder: ```ruby class MyURLSafeEncoder < GraphQL::Pro::Encoder encoder URLSafeEncoder end ``` Now, these node IDs and cursors will be URL-safe! graphql-ruby-2.2.17/guides/pro/home.md000066400000000000000000000002611476434635200175670ustar00rootroot00000000000000--- layout: guide doc_stub: false outbound_url: https://graphql.pro title: GraphQL::Pro Home section: GraphQL Pro desc: Overview of GraphQL::Pro features index: 0 pro: true --- graphql-ruby-2.2.17/guides/pro/installation.md000066400000000000000000000044111476434635200213410ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Installation desc: Get started with GraphQL::Pro index: 1 pro: true --- `GraphQL::Pro` is distributed as a Ruby gem. When you buy `GraphQL::Pro`, you'll receive credentials, which you can register with bundler: ```sh bundle config gems.graphql.pro #{YOUR_CREDENTIALS} ``` Then, you can add `graphql-pro` to your Gemfile, which a custom `source`: ```ruby source "https://gems.graphql.pro" do gem "graphql-pro" end ``` Then, install the gem with Bundler: ```sh bundle install ``` Then, check out some of `GraphQL::Pro`'s features! ## Updates To update `GraphQL::Pro`, use Bundler: ```sh bundle update graphql-pro ``` Be sure to check the [changelog](https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG-pro.md) between versions! ## Dependencies `graphql-pro 1.0.0` requires `graphql ~>1.4`. The latest version requires `graphql =>1.7.6`. ## Verifying Integrity You can verify the integrity of `graphql-pro` by getting its checksum and comparing it to the [published checksums](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/pro/checksums). Include the `graphql:pro:validate` task in your `Rakefile`: ```ruby # Rakefile require "graphql/rake_task/validate" ``` Then invoke it with a version: ``` $ bundle exec rake graphql:pro:validate[1.0.0] Validating graphql-pro v1.0.0 - Checking for graphql-pro credentials... ✓ found - Fetching the gem... ✓ fetched - Validating digest... ✓ validated from GitHub ✓ validated from graphql-ruby.org ✔ graphql-pro 1.0.0 validated successfully! ``` In case of a failure, please {% open_an_issue "GraphQL Pro installation failure" %}: ``` Validating graphql-pro v1.4.800 - Checking for graphql-pro credentials... ✓ found - Fetching the gem... ✓ fetched - Validating digest... ✘ SHA mismatch: Downloaded: c9cab2619aa6540605ce7922784fc84dbba3623383fdce6b17fde01d8da0aff49d666810c97f66310013c030e3ab7712094ee2d8f1ea9ce79aaf65c1684d992a GitHub: 404: Not Found graphql-ruby.org: 404: Not Found This download of graphql-pro is invalid, please open an issue: https://github.com/rmosolgo/graphql-ruby/issues/new?title=graphql-pro%20digest%20mismatch%20(1.4.800) ``` graphql-ruby-2.2.17/guides/pro/privacy.md000066400000000000000000000041111476434635200203120ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Privacy desc: Privacy Policy for GraphQL::Pro index: 7 --- The following statement describes what data GraphQL::Pro collects during normal operation and how that data is used. ## What Data We Collect And How It's Used GraphQL::Pro collects the following kinds of data: Kind of Data | Use ------ | ----- Cookies | User authentication HTTP traffic information, such as IP address and bundler credential | Authentication, system operation User-provided profile information, such as contact email address and company name | Business communications, newsletter delivery (if signed up) Billing information | Recurring payment for GraphQL::Pro license The `graphql-pro` Ruby gem collects no data and never "phones home" for any purpose. GraphQL::Pro is not directed at children under the age of 13. If you are under age 13, please do not use GraphQL::Pro. ## Third-Party Services Use of GraphQL::Pro includes the following third-party services: Name of Service | Use ------|------ GitHub Pages | Website Hosting Digital Ocean | Cloud Application Hosting Heroku | Cloud Application Hosting Bugsnag | Application Monitoring New Relic APM | Application Monitoring Papertrail | Application Monitoring Stripe | Payment Processing Buttondown | Newsletter Management Google Apps | Company Infrastructure ## Data Security GraphQL::Pro does not collect ["Sensitive Personal Information"](https://gdpr-info.eu/art-9-gdpr/). GraphQL::Pro's systems are secured with strong, unique passwords and two-factor authentication is enabled wherever possible. You are responsible for using a strong, unique password to log into https://billing.graphql.pro. ## More Information This document is managed [on GitHub](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/pro/privacy.md). You can use a GitHub account to watch for changes or subscribe to a [public RSS feed](https://github.com/rmosolgo/graphql-ruby/commits/master.atom). For questions, comments, or requests regarding this policy, please contact [info@graphql.pro](mailto:info@graphql.pro) graphql-ruby-2.2.17/guides/queries/000077500000000000000000000000001476434635200171735ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/queries/appoptics_example.png000066400000000000000000007616441476434635200234400ustar00rootroot00000000000000PNG  IHDR B\DiCCPICC Profile(c``I,(aa``+) rwRR` L J>@% 0|/̲Jy[oZtQWJjq2iE% )@ryIdd! v}&$Ⱦd $g$`|d$!#Bi JR+J@s~AeQfzF#0R 2332 1602 iDOT!(!!Ov@IDATx\e?77BB'J((U,(EbEEQQ^^UWQbAb(*"D@J %iB; {d7as;3=}7gfxB @ @ @ @&5 @ @ @ @' @ @ @ @M 5M  @ @ @ @G @ @ @ @4- p4 @ @ @ @y @ @ @ @ дQT  @ @ @ @ p9@ @ @ @ @@GMS)H @ @ @ @ @ @ @ @M 5M  @ @ @ @GK,F @ @ @ @@TbR @ @ @ @@6xhʔ)T; @ @ @ @- p]w8jVE @ @ @ @=8ꩠ  @ @ @ @Q 5 @ @ @ @詀QOmO @ @ @ @ZU @ @ @ @@Oz*h{ @ @ @ @-$ pB @ @ @ @z* pSA @ @ @ @h!jlU%@ @ @ @ S ڞ @ @ @ @@ Pc* @ @ @ @ T @ @ @ @ZH@ਅ[U  @ @ @ @T@ਧ'@ @ @ @ BG-تJ @ @ @ @G== @ @ @ @8jVU @ @ @ @=8ꩠ  @ @ @ @Q 5 @ @ @ @詀QOmO @ @ @ @ZU @ @ @ @@Oz*h{ @ @ @ @-$ pB @ @ @ @z* pSA @ @ @ @h!jlU%@ @ @ @ S ڞ @ @ @ @@ Pc* @ @ @ @ T @ @ @ @ZH@ਅ[U  @ @ @ @T@ਧ'@ @ @ @ BG-تJ @ @ @ @G== @ @ @ @8jVU @ @ @ @=8ꩠ  @ @ @ @Q 5 @ @ @ @詀QOmO@XbE˻<}7m]-0}tm1cƤ>D{7͝;7mxF?~| @ gN?az+w_y4hРۦ:( :w]) {f;#M6=:r! }_`ҥ馛nJf_1"m6iOq{enH<@z'ӎ;g1[}@uAiWgJoztt]Ш66#j/㏧O}S]mo{[{サ\oWK]tQ >T&^<\r%sua 'kC@h/yeN;N?r+Oƍ׾7?UF(a7G8[G}t2dȺO@o F4 g̘6}Cp]NZ T [o5N觍1r_Ƈ&@O2%=YG)&kO|)>t9rdz^W^a~{ A@.$ XoσŎ .(d&Q@ਣ{PNou7QWUQÈxSqe)n麫:܏<,X>:t 7 >t=oƷ"xU[V^~z+_?F77#LGuTdɒ|Ά^?~>UK|Seٲe)c7/-@վ S~㏯|d6 Q?yhԨQ~ӟ;hG. P;}駟"׿>J*Ho\,\0; OtKS^o=#g?+KVyR|.txНuO%l4>s2lx"Ko1-oI{nDQ|q!DE>{\ ćTw}w n-{\fΜbcS:ofƇo}~?~ċ?y2}r>bO'On/DE/zQ:c=[+ s[mUr]/IU8?'~y]l_#q5Fd/| ݝu@~|%01JM7ݔ~bn<c:~QM9f;tקN8!7I^gqmӵ玗]vYYA\gq qM_k׼&̱8w Q_mE@ \uUg?Y&aY .30ʑ*z?sM$.ą^7a7-! 1 #Ho7bTK/aFBŗ%M6-}_կ]w]QbԲG}{L}|]܏>x2޸*C?@SN9E(W@ (?7ߜ=oo z.&c\. -l\8G:>y uϰ i1 Z"GWŨ+Ç_ aw׽u?_^w8J>.!/: " es:iM T1u3=}SA#U. !Ɨc)>+d V}XG_!' rz\w]AI@Ǜt"~K_&M~"p\AJ$e}Io|7ド#ľ#DaX8tw72Q~رJ? ptw3<3[e֬Y7b x,EML?.F<)o>#%ooy7M~vy)zE iԩ5B|(N >D8~7S?Xt]әX_QSJ⽀x8Yo]_xo6̧zM1ĉ׷#@``1d_$H5}|1I,կW\LwuC15k|I1X;@3}~єNz ĵ_lq8z]Pb$Q=U/7TAu~|.pn!!F^^#wp# mDl"@o(?q7 CĘ"Xַg>qOV@\ E"9?$:蠃R.R)h%c GA kz科#G)6Q ?OE8(2h|1^l6I1Q|}D eɒ%T-1\/9vC|s"Q,l88׌:/cp1>h%>?[/Y ob_v|[9NuAmR'N` j=Gk0>(ҿcu]LjqqQG#85Eql "lX-<ŊCk60;l\Gy(1F&E_(6!L_ຠ$sN;oYuA8 s_`%?Ƒbݿtg|t"9}\@ਏ7#@w !F"cڢ@L"hӨźѩvi~CGPqB|@7 }k_˓q1o^/GVjm6@w!>)Qb:ƥ?1%ZbMӬۜ{Tj{z;Q<ۓ1?#E2u-/| ۿM^/A(e˖=3fS@7;o+q]N tDKKPcy+^_޼.(x55^'D B@ {x?dӜt&zqxۋU^z?x@_Ш6 'L_Gw] <7tSĽ+xYuA8u]ӻ}Ǎt/ya2[b/G,,.G}qV LibG>ϓ0!chbkҏ|z̈́b]q8uloOw}E/LE a O?t)ъQM"\a%o 5*}pw^bĢ%Bѿ4b}n"t3Z]TL8;1]2--¸sd]tp@m*^L_(*F<낕+W桦]9BNķ=|::_;LgqF1޻ӯS!/: t#з.\>糡ĿŔq1Aސvm|Ђ?񏕮 cJN8Fx$l)bi|1p5B!w_8-1aK̓.t(>."}!cY71?DܹsۃC͂٤Xn~èf$BDb3M}Db^ @w ;B1I|S9PĬ#7N:x0._wq]'L_]=?b6OS=GL6a„N:z P|Q(F'7||]_r%OSjX_(6+n_]M\tc-PYO?p> AEhA xUG9zPHZ86@ 8`AW (S8\r<Kwm%~F CoZذ݅ b4߿dۊaO"8*y0qSN)VM@/tt[mUG?t'f'ƨ˗/td]~@}Aw|4 y]_sL~ݝu@\HA^1I?S}Hƍk{_:ꨣґG?/hq@/2 ӱ@[:;ٳg_lJbuAwo8W Q_i A{쑶f~F1cF?~|Ї>Q 9q|r^{m ia@YȠ.)Do 1"=#_zZtiўg'[o F?aSL<, pԇǩ ;1zI97rH %K;"`v{3p}--4M1'k߬>Cq;.d|̧~z3gNBfΜ!,7!{ "8z81#ߦoMzO ^ӟ΃1amPC,/!CB w4N⺠;)_*}A| 0p=Aq˺"uwܑ<#0AO=C"!Μ'}, BB1bQ](Neqmٲeb!f!? G:K)UuAoF͖ % pd>#6]}'h?x.>X8cRv8oG]uU8asҗ4 @` t2(X<oG׿(ebptg8#Hp@qj~ K1Ot-0X<0ËE_PHM D0B-(纠;-O*}Av+O:5#Dc$cǦ;,tAF+YfI"dԸL4)"~[W\b|0ewL(~F!.(Ra L}A@uAnKgG`Ct8c5{][#6D go  g/^x†^"TߒoJŇ^O%#1+VR~B'P oR_#NuAw:@s#lTtk "%1 :k~]Lg1Yec6Ƣ/ʎAmf-4 .hpZ @ @ @ @*UR @ @ @ @@ 3@  @ @ @ @T8( @ @ @ @V8jg @ @ @ @ pTKQ @ @ @ @. p3}{_׿di@ غDy}AVJέn4o$: ܺFy}AVJέn4od8jS2hK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0Qm:'N @ @ @ @` m|sG$@ @ @ @ olMT @ @ @ @@|^5HGMB)F @ @ @ ЭQ<)-Y$/1eʔ}{QngG @ @ @/G%-%pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTgWH{}v~v ?_xw=Ӿzbm&%YO>NuѻFǻfWoc; @ @ @ нQ>I0`=Nn{E3amھҎvKz͎'t͜?f~'3c~Ѵzv@[\$\28$@ @ @ @M P .p4 vǷ #wG9oQQ ^cKGnjt}f?p*G @ @ @6Q Q P]Gt6G絾x9wQQ &و\'4fȸ4wAW4r @ @ @ @` p "p4x!靓?O{u}ꣵ8ffN/\nwm*G @ @ @6Q Q КՃ6 ʲ2-_}Mo2}rfZbAqc@ߨ(30v҂gJP;m>tS=N7tѴ! M1L,qq-;8xc"x龅wuZ|ĠiqY0ixZba^iu{٨߲UK9,_È6[ ۮ= 00u_zyzbi'x#LjɕKW![dvs\xճ|ACΏ9ټR(,mf1?= e]Ozg?b|WohZs"/ŏ<6$sѣqNl<{~6Br'ِZϛM @ @ @N}5t/J./dY)*ne2G/8CJw`mg4.+͏u>#.>#8Fe鲇.f_m> ZP~E线ߺV]n ۼ4m?bҺv'9?/ 0 [Lk{~i{t{:yoE14tݟCF1N%xq+g^%1E0 -<59z7FL6odG.INMcθ 9bp7pρ5h'6i;~AV-7TR<#u/qP܎T,~o74-mqźl7rtѴ>= @ @ @<G%- pTfu}~1;bTb禩Y#GbU>RMu(,8t71qߖFux3ɇӏF~QMxS>QLKVv>O(S%6sE*=.M;apy ,Ft^C鲊`Mmf,/+:[w~}$f#+уƤk,+Q~t7Ҽs14t[ۘѤFYP\"U,FM59?n;,FPzw*PRwu>~ߴ{l4FE+?7{ĺb)qǮB^YU{ǖͶfhUhO#P>7ӵsL @ @ @ @/ Q КՍA%9 kX|>UË7L{6 dnN-+lDPg1JGn|1wbn;G[ӵşa: "[1=Y%G;9r[f# -\tdm3z^?3`5u)0Ec;&G6׈<{H@E(?Qv'тbڮA 0OeՑǯO1JRL᫃<,=+1*=lbi cq^1Tmb/49 Er?d`%f#%mwP+q)ww~M{lO~7F^QFӨ)E 8ѠƏn\c^>eeW/rv}?knuJax1Ӹ_=x~6׭lnc+4qfSVȧ(3'gvܨ{Eh(F"FZwyהJ#Nsι_Ӿ?7~qGM#aGy䏤hMg}Z{l_>x^A[8=w󻿞tۍNB[e!uƑ"Xͻѿ?պg+ 7vh,;ߘ~='\c6 n_W܈ZwɶžK[0;?zU @ @ @V8*Z1hUale-FfS 08}hPV|@~%=ln~;3ibi'pl-btƅTe7wUjҤQ)֝?@IDAT-iپ;xNcGёVeKcl 'i޺ZbD8cEratt[(I)PWݳ^M:wڹWd!m}6Ggo~H~?bg߾_Ӓw~4pm 8vO3dl=h4d6(`~XEЪX˿|4ۊUcJ߿ogSModby)FԊSaT @ @ @ @8*8*Z1h#Ĉ8-pK2+.2:lĠMzG~ĤƩ:8)hZ"?5.^I(=F9H/{}W'O|ģG]R#3{,o7t7*p8\nwmf*ЋGCK'e#KzaYM^/Fb.pSŔk./\6Bsϻ[s94Tݶ8[3|*+M @ @ @QI+Y.pId#otQ΂5kG8z/Il|FYs*-"rtNlSej"`2kPYP%i{ҴES_6/=dz6΢r7 Ŵ`u=KvLGݛ.GiIhѻcw8!5i毟r܃|qzVvhQ&oH=tQF?zwu8lTޝ>!߾qĭƿ;~tj1Z,_iŪm? @ @ @ @@_8*i 51?c̷L6ڒ4:Gj 3ËHFMNh nqNEzΧ ɧ";oǖ]U׿"Xθ|ұ8X(e:Lq4O>~t7:jѻWfSG1wR6cy1(?>G)BM$pm|CRV,Hg<81 mqCvhIGnw\i`뗦x~~Q<7?k1[OVl 5`y,iv^F  \7K0rXs6مѓQll=by`tKi6y>Ѯ8jl<]/m=l,4j*/+ƿxʶ>N=G{ܞ&@ @ @ @}F@ਤ)J֬n Zt8yT[onիo"ӎݒ7EMaCzķGoxqtG˶m}>w1"{wT$yW硝B@˶}mލw#yex3(|6Ggo~Hv˳i%3ҼpOAn =辴} u3;Ը5su7p^P6TD('t,#Mt|Uog5ؖZ5$;ހ Cn1ZQw+:[^<=dڙFnj;m#F|z.tt @ @ @ JZDhƠEw(>* 8Dn( Gxv5}xMճ)MmnljΖG_>x^Ŀ:+%p{3˵sMyvc3E;-ӓnSF1ԍO9y @ @ @F8*j;8=t):jncIN7ͻ&h䡤+F{/?sɌ|嫖m:^=m:2*֮\W74[mc=>'-^ b9iŪ]  @ @ @ @. pTG%@VGxrb6hN/~=tl)׻8]kO%@ @ @ @}G@ਤ-Jy(\ɰ4xie(HKҒ,`4sɃ56֘!cɏ;cK$8'GMss}xȝ#&i/ɶMDA @ @ @ 4 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs%p4ZM @ @ @ @\`̘1p+?Ç7uxM\SHਊ @ @ @ @u8*ia  @ @ @ @ZJ@ਤJ&@ @ @ @h)8* O=4x56 @ @ @Ғ޳^;8*a8* O~qvOҀi 6 @ @ @`VځQ Q  @~* pOi @ @ @" pÛnBGU%@VΔ @ @}#gK(@h%Vjmu%@ @ @88Z9GO! G-P} @ @ GGGD  @-& pb  @ @ @@#Ou1 @Z՝ @ @88*+8*%Rh1kp%@ @ @: uxBtvG3 @, pʭ @ @ @Q_Q) @@ X. @ @ A@H;GxheVn}u'@ @ @J J @ZL@\u  @ @ @GG8Lc @@+ r; @ @ p$pTW pTJ bG-K @ @t88Qg*#@ZY@ਕ[_  @ @ @#ҿR" @8jW] @ @ @Q'Dgw:S GN @ @ )@Q5 @ @ @:dDJbiJ+iࠔ%͛umc ڶ @ @, p$pT8*%Rh1 8:m~=tsJ@J:Yf6?k4hvHilɇfWc:¢GSܔuil{~J[ѐ)=v;"E5.nvV^s=)mgJ_ҟҋ3]^D)]V*rzUܲ @ @ @J2GD  @-&QGKҏtWR<|o>ҽW^ٵ"dX.bJw^^FZQBtWSdAhئkGqNqn.s"44ᶑY<+S{mmQG @ @ @@#ҧQ) @@ lQ.])m GnOi5Ylmz}hFROL7)ZRzw#/]5SeǏbHikSzk==ь:#{6ej3?}osjV(, @ @ @`J8GD  @-&G1%%NiƍՅlm㌗ɶ}OhgtmߑM/U?*[tu?ϦNA=vxJ"]8Z @ @( p$pT8*%Rh18)]M>Km[脔?v{}G1U[/HiԖY~) 0˔nI6ڜ9m:ز14tSz88/ifu/m   @ @ @@e#'Q) @@ lQ$# uls[h ):c͐#(Kn"|z~EJ/xw Vt)ҰŽ!{j ul6܂/n霔6MA>m]obOMk_6uٲmFX&]pR6"Ѵ,h }%YhE8OϾ;oJܞY3IiZGm{KiR縔&gJdnj%Dl?gASZs}7~,`57p^Jn'S:8Z @ @( p$pT8*%Rh1>8z7h?)=1c+Km#ţ8*䔎l4kRZ8zEO t}@VtCSȔCJ3iۖoAhS:ijE6}1)\V{PLϟtmu)ޞs 'rTIG @ @ @QFਔH @D(5GfmACA4o[·O=%)m2-S?B HETTP@,RĎm/ްkY]]]{D&`HGz/JI̽fRͽy9S?g.ɳ{DfncѾ>lA@@@@@y*<@*@JjYHJCJټQIQLUi7Tܝ"[׋h𧴭4 |:JԦmci@@@@@@y*<@*@T*}‘>7U*     Q"@Hȓ@@ 8wW8"pT@@@@@*#G?#O"v@@J&@Qsi@@@@@ 8,8$b@d*p U >F@@@@@@##O"v@@J&@m"GW]7å@@@@@y<@*J6<.      8"pBB0! @e pTGgG@@@@@G:o/7Gc$ yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [G2=XHRZNٽL+iJAM#PCUHӎgo̒?.=Uy-e\OUƻpJ82(Q(W$N GGT<yэ   pۡ-Ӄ΍ޱ=g(cUɺI؛.J'PjU9{s 3TdP~_ ;/@ *c(o;I{윿^ֿ21)9<@@ pq!pMn@@#mQ88GϗwIJIT" SȪ;I^fvne@ pR<| yэ   pۡ-Ӄ8*_TܬwG9ѿ˼wɟ{ $HoYV?j=ptuLh^_.I@ *8$ %qT8j'sJƻfm~y@@ 88&pD7  VoLFL|Qq_&MN8L3Vf8<*)Zn+s=ΐKeG춽+b7…@ ~ :@I=,(kqO.  sG`G 2ǻ߇FP7Yv]Mcy*7@hKnRךgh0WOݾkB }NSTGNx&Ӿl\&^vO[|yrΖ󥴟9]?C4-;GG[f(q=~>/ 1HW_%ov3پ?k ?ԒI P%:lqB4*I.o56^ěݵnm 77h_3FK(ZjUƵ%oK*s ~qR܋omB~h_=/h[ϡohj$ʮ[$뾿Whm> _V7f<v:6_~@@橒|~xGCDn@@*Dj^X7I;[5ilvU5`|q5P T(]IJ8LqSA?]v4UY[ g,s=M$& s-#fK({:@Hȧjr (;6 W 'e'[F瘤Hz+nRJ4su[;s=d5? }`O{Sd/?sC7~r}igdwNx>GjR#%FA/M nP?__=?sZ _d+^a&D7l#+  @ 88&pD7  VQهΓgt'q3%C I=i" ]|Z{x M+T0UxZZQHSd}eV97HZӧ5|*2&pA3'=>>Qi9a\m ih6%Mzگo?,9{ZG b% 8j޷1OUL#M+i(s%#~%;>w#KS ߠ:NGCT(RAi>wH| JӴКGGa-k IO͗͟& UH azqGZލ=%v3_%6 &*&@,oT2Ak{:Vt:a.ڊMk5`_4~Mwi돋dSrQF)&u|9V^O7(E6˄wreKNftԃɚg۩4 ;i7\u &hǶ:[eI8} :ܷ ?9Yۥ /h hO~EW}cZe%nt'컿G6*Qz,Z+xj[F}y4Z_|uVр˨!/utee6DSAZzEv0[|mYdTTPKq[/Tj%PNپʖofFlӇD#ATxjiۆmpIyڪBn_ tuGikktjKzOoƷ'U TbF)֪*HJ0l#>[ c[JVN 噰ާt wwSu  Et8 yp8@@|+@lC.I=bLiإ{ڲo bhcկNQVҦ??d}yА; 6om3L~74S,jKۋNOk 8S9w}@c.ŝR gGt:t;&K zTvzjMGpWzǻ66 Uwt_Pu:I 8ˆBtE-݃6 k[8!4 T8rNSm,]ת5 meq =WuxnRFz:hWyt3 9Yt}ɰ29]R#{rsfNm_4~bsSZi)tj8mz\|A@b]QDG7# @@@6@{4=V;ʱϷ;QX'ǂNbrLӓvd 2MeW *Qk!Wv7 _jV(薚l*]hmYFRZ5Z%I;'?Nm> dں|A/i5!mjPZʫwਬn^!&(ޠ@ r8'm4Mާr;P2PKhHCGKJ ٳVew({ JUݤMvwI Ɂ6՟Afj(7t&4z^nי Tp=MZZMiԅ2WBg. hv*5=:|vSmjUq>>?;M;6eG?s\{]w 꾍;2ߟ.;_&YZg:]1Qᨤ=64pUjEmgA9}׷2nlp;,l;T@Gw F6\W'Yt4TL˨-/LuxNem96)TQˎ1S5JBZQ(%R\TPg3$k\A5"wqzR뤶vӺgο6\E`mu ަ_]ָ4A%mjLui7Rf5fT|tV$T)Tx:Eܚtj8'T9SɌWʆ   EtL yp8@@|+@CyTtY\Y9wI3Sk5Na>iޘ;QaUw)pl]c]zF4q4GX".z!pW-AΜT0+dyw>?ߕf UTz49Z8Q{8r*`p{n/^8J+hZSMʂjJ};vBi+88JL}tT|S+1Rm.fTw:坻Gd8 cVTG|kJp[^ۧ=k+h9P#3_z]:rvॕh Pi%Qg*VJPsdF PQjmӻka^yy4sg픆8*g/\HmO$`,ٖ#GrtVwvm-#\%/ 9HO^*_ϒ[WFp6y4ѰR* (5O8ך!ɮ{t2U~>.[/A&3OGbR6S˥'n5CFIrws +ly Zl=[?vĥU?W3[SSH60t<<5Q׿4QH|-]@@":< yэ   pTMeΛGZ(k:Qa+s{'uoaW>a.p#_w8_vk'=^ʮFjD Mg=Ɯoz[VѶ?G\G_מ-+߷ ϓgvs-^S'EFzJ44/`NW{xjOu׾S~v%-O"G>7{xiGeu w^yYGj{KVrrS3 WT׉̴ 5v^E4{r)vp0b3@y$Iϴ dlx־LgOĖpXLhk즜Ŧr uj}Խ&l}}O=L֫`Z/Ok>eo/[u0I3a#N-/kkh8=GJCyTr#tߏ2-oKdgԻ6~4ՌvȖs$YT?~UB/:  k":b< yэ   pTNg)nDpʙVkwnn;ӧNNz6OPu{2i|avӔ'KOt6p'8e=R뀂)DFNUBV V*UZ.;d?+QCWuVj揲-Lީj&L)5d׶lvࣁ*F0_t|@&3{Xvn_@O 09 `mȊ8pBGiJojG9,n %pSTu~x8re\a'ݷ>#АWϞ 3*[/)lsMAh776y&'nM=Y6BXG 8Ҋ=o)g&YӰJu=춠)̖TSq֞C[+_tla+n_P".ߠ4z ˺7?]Zh5&l\YuZPmMFCA"Os;I;kzWCSs9+2{hߥJ+ ~W@jx Uii&)}Ɏ$i5nl7g#  pѱ%pMn@@mׯ]M[eImU (6΍Yl ɬ W9{wL2FrvJ:;HiZd(wNS Y|3ks^у$# niN)մ]yG->l4Ԕ-\LsU՞${9vrnG>yq`J5?Α-HRҰO-YVҖX;Y|~*FzŽ9HNxI ofensz֒uJHIl'!' ӥ.[/?7̄VJBu1ᦏme%ݩífż?3V0iy8`?>aկ&Pnj\9̘o}qsY^yYG?Tp 3eSjb4?'4ptǷNAmֿ5a%ьV \nqtǒ|dž%H/cDSW&JckQh۩6[)c/x~֓֒>_e>5lQg?)g#p0  1t\N#}(d3Ky$.%>oh(v4zhG3>uo1S%K E][m&.*5m%gpA=ȩ;f :)זԳ:Hb4̡3e˷s~#=3\,5+Vo/E=GˎG=k?9!w uMl.5:7MN;Z=ALv >tvi7pS<8^ k׮Զ!PF?@ۈ՛cT.xsY]k6'?݄${0SH [BFin6rC}ZV)iEzGjUZIݎy|G7~(dK %p=L9!š;7m& XQAݥgsZ 4M=g*g}lhH/Zs9KZZ{z4%I]o68&M@C {pkEc~Yf:)f''haB:fϯ_n}'p-]hrRe}{zuf7n:TzҰtLu>Qдy7NwҊT՜tZH[T*XzNՎx7RIܜ{^ ңӅ59cOsdڐO8w3Ux~}p|8 W3o9Mhoa%&˂M[|yuL訣$4K+*ݰM2 9SZ'$s6Ӓ}Qr.*=0I>hZ(k<:a]GzO \W+m7\O{=Z9X4Nwe6mWH?p;65^}߾_- J_ve@@w":< yэ   pTҺdYM](6pdNpjl :-K4T1 E%k!Iٺt}g=;"6 jQ_*(fv~ݦ5/Z;֛)DLjCGvc/ѪIf/0uu,t;[*}lnmUvF&SiVJnZ5 lnϭi8ft%LuР\ts_U|LU7+b6.Nd >Qy#>NӼwLlC D"pܥiՔgwBC;~az'?+['B?αZAI:pNV׫SÄe:3քbt kLyx k}# 0#R(""< yэ   pT{x 9Lme)Ӿv z)KNy7w!\pOkTz}XQh^49q/ ,QYC@@ Etyp8@@|+@Cc|3ٶb>' )ǿuh8)sjuΓS9@5JӻJJR^m{ [d-a"YL+Jp ={9+dU+Cu}"{~9o. rv"(͟2p)(;  @ p'pMn@@#- 1"@(FD@GGvʝeZ} PEΕv,4cQỳ@@߻Kk%oEf F@@ 6$KUG]8@G@t#       T*GMn@@@@@@@@J%@c yэ        Py 7# @@@@@@@@*#&pD7   SČ>}V|{V؝rwʊ2cİ#9.Ezt|hܠ  +-Ւz&;?_Vd/YlUoFz$6l؇@@@ Zy # @@@ ٹ\ߧOWc'43\v' -ݱ@e|vvԎOA :\Go!oO  "ri -}!6o}cz8A>@@<^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SGbծRE_V12d/#m&:slks!@ EpO   PGz pTyGիKz5%=lݱKV*Aoz^-!N%{gn`ujH)xelخ U~Z iP7YvʓɆU#Ѯn%yrri,UoaϽ͘NnzfJ2׶y0r]l{αk{hT]V9+\F~\Կ羗is:C+sIq<ήo5mzfj%'RsY;Cw)tj*Ҩ^4l1S22~+mIv5D{d콎;^gq^wA_ݺsFqN\p9 p%D?  Ě##pD7   SG/pT.?psL (>O$a`ܮ\wNG_p|1n=Ց4S{nؐfk&3sowhhȿ#tv-pؖu<ߩ2aJyUʡyuݻe؄/~m&\48!z[`LFyL{ȃo,, 膒l&w\rw6eܵ{emr1v}rc39;hX:l GG_+6;.:J9 9f)4p4 dY!3zjp. ǵ[/=e`ByG.NUJ,^K.|@o# q >i֠|0>%yh+;:_Q^G N1_B/+M?e`\}a pT\)C@@X p1R~r9fo4rץ]lG6c`զiUkLhʽW?!g/wsbB;^w9vYsEيWOx^'9v$8t(bS#  Ĉ#"pD7   SG+p h{{LYvY'Oxt1ht=#diNAuI 2l_6S6hC`-r,]Li)Ir驇Ƿb[9;4flU~XL'Z#t~&VqwJM7ClrS&儜2Jt:cI,tZ_A5!$} \з6 =gGڦ??.;_`:®jY}Ky­](K਴+s`~_`xp}y&&@YEc@@@ y # @@@ 8\8Ft& ' hT]}NWPFzɁ,|b; &>g64p;44/}΂VѪ;چ0&}NmU%۹狻{ʱn{}6vtav:#pm'ID;aT9r=Ze7=/2zRw}iS3Oat;?S}/ZGU iL;?D"pTg wϡ.%pTw=NE}.y~u,uLvG b2 (q,  D#Q!pD7   SG+pR*Fw]E]X)6hMGiL'ۀQ h4i0VlzBϟZ]x4yʬ5r ;pL=TaCQPU[gj: GHAEC_[/eHva; ڧFRT5Ӵ"V m_nf]l˱!}RkUf['7=SP if+NAzG"pTg)~ Vw,Ҿ%\r"LSi:`5t}::0_aFlC8>   KV3b65uW^Diٸsg6$!jYmk8YfJ_;Ǯ.XI.{;;6\\U hp᫻k5p*V{Lv-]7{A񳌝\H;9Xpi%)(M)dqdGg|AAK+diER\{YШ_Ȑjo,\zwm!^^Lo~Z,Go(vfϲ|A,ʢDZ    pTGG@CbqJ4XlXU^`CRb|٢&آS$ѯY(;'O.fMPJWsLe H+~=% h# hD[!!Vz>6,>-z{pn{+a:\зW}p}6w)>9TQi}E-]Ҥ^M;mݩ7ϸJh{ Ҿ\s;9fZ?{j2MVݻ@@@@ Jy # @@@ 8\#K!Z8A/N [|dB cl{lV9vYCCMi(t?;۟e%pAO?^?#3[ιsXPIpiްVIGzlFe-6<;8sw<_{r^~o}5S1K\]n{_d䥁th[Nf-(>1V fḎMeȵmf;c0LwSin2JZ>7u4-4p4q^d|2z]v)QrY߅n9A:\Pd+t_wQi߉|.p@>Sp\}DQI@@bA(8@@KÎl\YcW-@IDAT*>&^tV> yrOP~Z 9#^8SmK[Rk -Zir_8 8X 4 @9Cc|R'%ɞ+p=;W^xLN]ub[{ڌzޯxw&ӺiN;,}K8'!-M~!9jfj>OM!O̗_`BUO7ar!u=|͋(UMiMrգ혥S TYZ-Fqt{cģ_\-o~,Y)UyN2Iϙ)tiX{zA)8Lawse/=*]4@6Uq}_9ygc˜uTNҦY/# iL[l2j0E,:<=_.\oSLpJ>Ç͑_% Lڙ) @CG}'˷dߥm;vIrghe1CP   M}`a3]jϘۆtAw5LJd3]([Eٷwre]nya|>NEh" .^KڊT:]k,OXP9] V9Q a}040Byfj> :i+3zY  :V5<㮁_wB)Ru].?YKzoJ`*@ਬ  &@cDyэ  TQ iUsrlDhEC;Ze^7TV`33MICFv4yTpOhX6fڛ*?4]Z5N6ǖtJ5N׬A-ٕ'vJ֝]L S1s+yjE+eNW!ZYrdg>,+?.A@AE A1%Ę3X`5jDcL5XAE((" Rv^{vpgv=>νsӞ;w>yb䧲e_RvfWےVz؎Wz;O{Ӆ_ t" pԉm @KG%pT @]* pԜџ?ypfykQj,tpFw|Q(EB9ɳK!p4ȡW>yK|_1*. q= @쯀QQb @tQsGq+tWCQi>M6}KOm6p^h8T}̘g**7}T-c=p}?1b3_lTmUVNQ-. @ 0*IH1 @T@9qe6ۆmb:(F'߽6 PW@ਮ  @G-$pT @]* pԜqӏjۛo[6WvCY(p45\c1pO>!Gݒ٫ 8*I!@ @`UQb @tQsGE>YfϘnߙf#Y5];COwu߼D?,udW.9]ʪ]wX =nA:Gɓ&e6+~䀆 +V68 @G&pT @]* pԼQve"@Q p4 % @ 0GG@  @ ХGG]ڵUF@@hН @a88RL @.88ҮZ 0G# @ QQb @tQvm"@8t$@ @`X*x* @ @Kk;% @* pT+pT @]* p$pԥ][ @`F)  @V ^  @R#.ڪE# p4NI @ U U)&@ @@  uiV-@wJ @U@ਂWH1 @T@HKj @S @ 0GG@  @ ХGG]ڵUF@@hН @a88RL @.88ҮZ 0G# @ QQb @tQvm"@8t$@ @`X*x* @ @Kk;% @* pT+pT @]* p$pԥ][ @`F)  @V ^  @R#.ڪE# p4NI @ U U)&@ @@  uiV-@wJ @U@ਂWH1 @T@HKj @S @ 0GG@  @ ХGG]ڵUF@@hН @a88RL @.ؾ{[Z.]jM7!͛8twUWO`޾cWsƴc9}  @ >z6eB5޵+m[$ڲoCMM @V8RL @ @ @ (8RL @ @ @ (8RL @ @ @ (8RL @zڒn|}mԓNL:{elj@٩gzwM @ @*ZUH1 @XceʲJwe2gF: [hMi\lLSai8*'RB @8hb  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@਻xm)>0=5M9aa~wIX}`N,ڵӤMowk:Hl<#Svmޞ6_sH\˜:јj.K @a8`8RL @z+,84؃J/$<4ϻߤ>#HJJ %k;oݧc^v4)'YO|`(mqٽ;x6Z>-2!m_>-}Eҷ @8hs  @ @W@਻Xx1iHkL>uUڱr`u 0NG4z&G- @ @G /pT @ uWWӼHƈiMI1N޺#5).Xc v8 yٚ @( pTѪG@  @ @@Qwu8:i7D|ڴiMi܄lljYO8>M{{ktWOh4QN#pT @FV8RL @zRj9hz0oZڽcWqƴ{ˎ~}dז)ڝqi)eXO&9/\9m_X8~OʷivoWdܔ iyyMo&>'K֝z_ʮ3+ȮyR︉=ii9`ոqi!3S\ﶻ֤Lja.һɠygܴ--ߥγ4r㲴?wh](+˷V޹N}-Ob4 f]df[דڽ}WQDzeI{&OVϜi׆my-;Ov3 0.lmGޟsDmڻm?.cyݾt}ZCFy*^wiiB/Fڹvsڱj׿׉Ffȸ;+ @FQEkU)&@ @MEXdǥYg? Eݲ_ R؈_ʦ{iSm殴?~*:3uLvQyX$[ +P6䶴 ?O4aG8Fߦui=noi t< Qliv,[_8xIْ8t_f=4e%칶~G/{LTj.VV%k;zfMɷXK'ڰ5->pMw{W $M;>i"S,/]Z_VEHgoa?bIJHNO|/>(櫶޲"-Z~cqT[}e!ޕ\֥];u>[קuY6qem|?~7je|BPda-7-KוYX??{QK}:C8anelo=-%#Th @ @`*Z@H1 @hrhޟ}sdS?pBO5iwo 40Ѭ75{M}j!|DlM>"RFŲ񧷥UYmX2vؐ2{clbty>}wmܖ~Ur=Q;{4'c엏ܔ# x #ĨDnDj9{FY_u߽o Ӈx=B-?> 7"E{̊u[<-xc#Oũq7\vk'ss=O,mⶁ!?;MOɖE*WŲuiٻ/wکK?0p4TMWߙ޼<}ËK|ffy-1Fѻ&<};D+€1:X#zf 6(GwI65_6bX{/}T~5+g0\[,8rX7ҋH @ @`*@H1 @hjhas7=>Wȧ)>(/Q}6쎾HQQ) +bJlʧbc޶3mtK1Wtߜ<ʃE>E # ɚ/_6y==Ӻe钴{GQi7B灛hf=9hψJ>yy{k kKkh۩1m=KonM"KLwur?>)n-Vݹ:?_ ҉Iiذu<%EQ|H"CڟeکG^pZ~߻1 8^k4p4Gcde0^qæ8p4ؤ]e#uYޣyщ(&FvGo/L߶e?߱MXl$zQ>VN(nF @$ pT-pT @ 45p4# 6ĴW1ZLbܕVǽƎeoNH&osȲEh4k#@ @8p8RL @z8ibʥX2Z@1-ҺoW ah4n?M)(-37dA9,(4nbOpFX2Rk cyYgӶO4a|곻^)Z],zO0o eK>܇TTXo}r?=5̜Om5Y6$2-O4tK[myvyZmp XvؕvowIW\bi N_V[<mS-y7Ү8q4Bn;VnLwa_[=F,BPS[&4=̝bʻqkBit1-` lֺN9rpG qM,3?v=Ӝ Bf1Z,}y`~ˎUl Yxps;5 @FQECU)&@ @M E[EhF1}ԴHMFEْ{i-}500ѷQx4'bBjgOLK]<{ȾC^4y_=0`}f~Q~p3{HGGFW}-?/Ȧ{&bU~ e#6Ni_.K[nXoUS+nKVnp`(rݣB3~iWWwڕ&17t(GN/95ťwK ԯ?F7wɠ1H֗ǵnY<[ib眐OϋeiNX5jFMS @QQb @ +Q)E6UV0lힴ HXݯ@O ^y!4@)bXq,8sJ]u|/1:SҴ{t+.+j  ( eG(FՒ~Obk(T/F~e/4׬i7VZEa L޺gB/ڽ#NX'p$sьdzY/l&|v玲]'?FEJw_.mF*F7zpܟvZ_ ?%/U}خ?fA%ٔj-E쏇)GSB[ە Q,|cGfyllTXpps*p4tD @* pTrG@  @ @@@G1 /yd>PV@fsھ|C`KO}PCٴoO=mT[h58f8QU]ןb,l*Se#3H?}hx-_"̱O3]XXo#F,z{6\ŔpE(Rbi Ŵf1ҸlNv+Y'pyIiYO1Z\cfb4-7-O+J'˔/{t+i>Zꬼ|ivjW}mfD1]\Tb{cJFFj]8Z3bi [U"FI  @ 0*CH1 @hr蠿:=M;>ieY0: $ӎ̕W~|tNM!vȫJ{PGr[ZٟO"X6^N>}o*.p4y'<:?&mKXY,U.ڹ>k˞dc(=h=(1Gc2~tߜ"if_qfr܂|=Ӵw+?ՏMc/pmPYlʴL|.3iYi[u}-i!(58ߦ5_(9L}aTj.uGSRS؅O3kJZMSvTo+yc3ε[Rڶݱ*4!F׉ѵ U(UDU}4K>]?f#%Bp4(O Xǘ.]1Y@,XxK38~/1"vwE~Үm=9Heqv n3~@_Q~׭so@ @8h  @ @WɁn )",꿯 tH&?, nٱl}ZDb곁Kt4nLjO=R@3Ϻ_Su-Y[X7pD} Ew?]6}O|D:gFf+.Hwʯd5949*rF"58YToy*<꣭G-N-qsu`#ubte@{ 㾍' 49{Ve-+P{vRG\ @FT@ਂ_H1 @hj(B) C"9PG,1RL3@L_ߝV~N c秹{x>Vs]Pb-7,M`ޑo>iϯ3)~tK~/UaQkF6Jάs"K)6_8iѻM=3'h2YK3<6=*F8za<@[>mLsElp֜g?4M>M"᧿K<# 8F:&5;/˂*[nZ~ӭK(I;=OFNPYKmats,M+2Sϼiiso]7#bE>!)צ?+bA>:p?f#B1.o~ƈNbEhiꉋmwNk]?޳Ştocd[sC>Rkhx.p4Z5 @ @`d** @ @^Giiib K6S3g!#˴ P55mϦh۽uGM~oV52# [G+1D%X1\~ A6<;) Thd)bJg7CX!-=a~敍0Od TlgNI15X#$CbĦzQacԜJ[ogOL)6ivs‚Qb4h\bd :QLklĭypmE۹n(Eh6"@ @88RL @z8tAW?6Xو;L-1rʬ'=RLv ~~)@s塏N=6YAzaykSY\#}__u߽qVu11`. @ pT*pT @ 41p4Gy;9:QB"!3GcemM˶ݓiqن5j[So H;{Վ-GҒA @}88RL @z8|wM/-m=[1֤#:&M<|N>=}etMAC^wv>]nҴc; ޻5S_bJ"nCFqqk.eZM:]H u! @** @ @^&3}LlZٜ]%u[گ*m%[ j]OOVZuLf47ִs?7[@h @_@ਢ* @ @^=s鏸ohV6r\di-i-+_.Nw:pj>Rq.GY:pgo:+Ϧ8qƴ~cԇf<SX!ml[nZN8:NH @Q' pT$G@  @ @@@G:$ pԤVW @ . p4KZ> O @ @@[<  5G]Ӕ*B @}88RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @z6ܐ^wYm&cL:{ִ7wm @zR  @tQE U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEsѸq*xu ^`- @ @ @ @fͪ޽oFM7ݔ}ԩ]Ÿo֕ uj# @ @ @ @U4h u*FD؍ﬨb[&@IDAT @@N\<~4afU\m 2'0^꾇k7}ғn^1~\mެtqGU6؊ ;wcWNg޺8/޲iBy[#ǥMJi^Eݽ"J|`{i=wMLiqӪgOrݳlWZޙdƌt1ۧǞ5>Qى |d׮tsv6 #֬N,Yld%iҤ[];ˢ#֦2m[Zں>in6  @-+M"Ϳ{Y3IgN9aв7ͯt}#uGOtQz QY8*8GA @Q]37Q @ NLz4C@~; 7 @GuJJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}}wU @@X,dq{w:3>1kju̎[^)vM~`^eٽ+qKzȲuyӒ릦5K'Ҋ'Mk}VLgWZU=!<:{ҲIi-k:|qi¶zNߎƍK'MOW>Nm)nn;t׶8?ugvmK:e4w{WfϦ'M-;vLJҬkܽ?ٖ8isشsӼ|;_ @(?qs|k7PO`{OOO귮oq Mwww"^ǾnwM7N:CkEi;N0mb??mos|F[ Fq#4@ ;1 ^0%8)1H.p/8NA` Fr{@SyNDun& *h hdU$Ё{AH6!4*@$h{AY t ^M4@:p/&@G8*F8*1'k2L`X A 91d. sc\0ap/V%0 \`"^0,]yPf8RL`4 %0ƌ{i*J`X 1T.  +3c\(ap/Vޮ:QEsQeUlJH 8/*L`PAY$85 T`P+ 4NqMp/Jp/h\0A er"p4HQUN CPxM7G:ujGGwhލhٖ,hnF 8kg"0 Fs6NY;,^0[ǵ8pڙf:p/8pcLG-8G[ZW"}C*-W@@ } ok5$N2p/hN[)vth{AsZM p/h@s jJ{A;e$и dz#^tG;p/_AhG +h!^W`O; Ղ 쯠NJQ-7lFK8҆U-5 jٜ@ tiê5lNK aU@M`6'Х]ڰE{AM0Y.n Gbٌ@ xqХ Zj 9.p/҆U-5 jٜ@ tiê5lNK aU@M`6G6]:IJ.KVp/ fs]*^Х Zj 9.p/҆U-5 jٜ@ tiê5l>f:l)ue3]*A6j)^PTKVp/ fs]*^Х Zj 9.p/҆U-5 j| utqS8fT.mX"PS& t{A6j)^PTKVp/ fs]*^Х Zj 8M  @ @ @ @xo @ @ @ @Ƭјm:N @ @ @ @ xsg$@ @ @ @ 0flӹp @ @ @ @^@;# @ @ @ @1+ p4f΅ PG`ƍO2%+ @`l ڵ+m޼9?>M:me֬Yt衇Vn@ uU;w-[^iҸqJ 0nݚ.]&Lϟ&M.hˣ^m۶}Ah%F`iڵ:޼yiɥ/[,_Kn1#1Ӥ.{q1cF8q^`/+. p a7U?\cW`Æ 铟d[G;A+swۭZ*/P^xB^_2}(.RF7ߜ%KoGs9g7.@{tzpk^Aˬ$@`t _>]r%+CFSOMO}S ]y_jxDzӞ GcL1.~\s5ST~N'xby]Џ7r#O 9Xpy{^:A 4M}O-R8Zxq?J|pi[o5E@a…yHiO24S} UN?Oҗ|4p7gQ|O` \| /̯t(uug> Ot@{A! ] ~Gx^U>*C)~ӟ>)^ 1A,yCL{QGӧhvZzsSl1$P^uj\J`?}{ӦM y]vjn^#@ ~ߥɇ?|;ħ1R)xm>ϧ*}/}ic@>s `SE]'P^~7]tE餓NJu*D?tgß+_G/ "PuAS{zw@{G?#"=H|#G4F DG9s;?я~X'(^/1cto7)tA}!P^5`l$?~wD"Hh# G׾. p-~_WӢEk_ڶ"1Z!Ho?sr)yx!F>oߞog{"N3śoʕ) @r!mE!&@7̓?^y8G1QAWy7//&~QPЇK/ Gl, !BL1%ˤI1:묾?f5뮻Ca8#Ss֬Yf :8\.O۞ѽ-BcB΃Fq{Q .M" `8??01Ie(_ܹ3TP#~?>ɓ]2Q;?tӥ?c>r|$!E(F7*`c|1 |ғv/(t< 6^6u:_iG^t*i& 5ՙ@a#𒗼m# =!Y~GpYzVwVXѯlƌyp%^{ڱcGDXN>gg C'Ax3>}\rI79h |;äǧg~qqO>w͛7 .OOxR x@z_ܷ~ַojFh)Fd~v:  gBŽ2cC OЇ>Nuc\%^[ޒv<[uA>nO#!7. oH(W~^'(8(^0` tK8+}~3 [G8-^}(CG B1rHXC(O:E(VDp(b`A,/)7sCI7O{NU)FW7+b#F|J"H.ް 0=`PQLSGw]'7c nm޼yhFj*=ayh!h4g?ˋbُ1RS9|x'o/FX+v)hw/#GH8F55@/^;ezjD ]/| +_ǔx]0vӕWq;/hC{~'D'F;:n^ĹR{l>CS>?1w^ _|H(Kc:by{^:*M,"T KG!5yMڽH`tr/x]0vԕ+?XW"_.+k& 5Օ@C.tZoobW,1Iy+_EY s?Lx3՟gW_>%Bh2LbTiӦ.k֬Io}[UT+o ]Gb-!qm}S2D(_zu6:sx$@` `^iL Sp?<@W*9낱ӆP "t#p4riN*ʆuAL6#DBSG<()[x/΋[V! N^tUࢋ.?0GGO|b~7y(5pu ۯ GMheu$pH(_i[lI -FQak(>b*bH՘(Wj %F5ьb7/cXb#qFT }!@`Xڅ ~_O~z_+7?FDjSTk`7C/iz#'Sk%FTyЇ;f^ C"^'شiS b^~䣒n ^3B`L ĈGSN3쫇}zv|L>8{^e]&~c1cLoD!~G- @qqɒ%˖-K6lG&Sgӟ=A/|a QgP_^Џ7M{Aak&0~{a 2P?Xm*'G\'ӝŴgFFbkѤr!R1[LҺ<ހPڵkR1[oZZ~v!#H?? SR-FG{;QbbQ|b:؈?\4 MIO>9NݽY.41zY, )#0?>ִ*.h@ T jƇ?*ƿ0F8n]=a)ՆwVm {Aٽ.(q|`# 8"^Q,hF7(G8jcQV8 1ŋ]bg=Y Fv!+Vbję3g]I1JY L,xXZPlQQ#j_~y>7t?^q, @{A(G'?>#SF`t D Bn{cE|O:uu{bXuA0SL2yty 4j񁃘*嬳".J]#齠]zL~駧g? uu>.O!F0sI1b3G`iXǥ'?jB#-0^Ю6^QF`t Ĕlټys8|%/yIu`ZHIH/ @bdQ%Foox;H1ki1g>O{#bZX r_ րӟ|zvuA;eƮ@{AN}CB[lI\sM0T|Xa^ć b4fXƲe&= ^ફJ'p}IVԉ%AD@Y =^^<=sΨgVA@, 9˲U۽{gzfg/V{^UXzuu:lZ-JCzw}MaoG?v!;d@'傾R$UF](tƖ%H*֬YRwuW^8=#fQz)u˰^{`S%6dTJ@f 2HHto뮻.W}BJv1=H|^}segzC#H4 %wqGx[ߚJNԽbTL4lvʕ+wHklJR4+ L$ ~|3aժU9`hh(di8C+^I\rI>.~ӋL酩RF]UՍ@j4l'Lelt7t?=aᤓNn/s@Z~- ^LJ/(U炊O/ɹ@S t[YrAmo8Z*98h*o; bj8^pތ:Cj~2GuT >U *{OC*N8`VbzX]r+*t ${jI@@wf:. S c]tQHWxG.xep.H$)Yj2U.h&e:\pׇN;-0 Ґ LKTV|u\=I z.Tw''@`ubQ K4)cADŖTOpꩧtNH/' Sk`f炤e  #O@7}(tSڶ ,z I+Z<RPuSAeX2V#RJdjd!tHR1ag ۩+o!O:,Bf%6 @%,!&`B ii)amY.ı^܋oEViK_Z~!炅PZ/\\B@Q@a| 8ZO @ @ @ @G`Y @ @ @ @rp @ @ @ @@:( @ @ @ @. h? @ @ @ @ueQ @ @ @ @]@r8~ @ @ @ @8ˢ @ @ @ @ p @ @ @ @:pE  @ @ @ @,wG  @ @ @ @t , @ @ @ @X/ @ @ @ @@@QX%@ @ @ @ -_'@ @ @ @ Ё,J @ @ @ @` 8ZO @ @ @ @G`Y @ @ @ @rp @ @ @ @@:( @ @ @ @. h? @ @ @ @ueQ @ @ @ @]@r8~ @ @ @ @8ˢ @ @ @ @ p @ @ @ @:pE  @ @ @ @,wG  @ @ @ @t , @ @ @ @X/ @ @ @ @@@QX%@ @ @ @ -_'@ @ @ @ Ё,J @ @ @ @` 8ZO @ @ @ @G`Y @ @ @ @rp @ @ @ @@ pA,J @ @ @ @@ Z֮];֒ڰaC'[ @ @ @ @>8$ @ @ @ @K5G3ey @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @Co%7%r$ @ @ @ @`6fp3ߡ @ @ @ @#?uea @ @ @ @pt~H: @ @ @ @zXXH @ @ @ @`-0xNQ @ @ @ @& hn~nmG.0 @ @ @ @F@Q /2 @ @ @ - 8:  @ @ @ @J@Q_e'V @ @ @ @S@baI2 @ @ @ @9ߛ:h`_]Mn_QC  @ @ @ @F@Qea9LNM.J-  @ @ @ @zF@QdE8jmd  @ @ @ @p4]ݺr @ @ @ @,mVpXK @ @ @ PpTs4^X6Nlhf)*LNM{5|O,(ਆ @ @ @ @Ep{~g{.ǰᘝZ}@X12\uϥ+|y9y =և/wޖRpR >J~Nk qpӆ?!uw}.8N5Gm>[VG]9 @ @ @ @@ 8ja@~=cw5\׬+k0<0põ믪61~wyidx`8*CwyZx W,v'D]7lu)[C @ @ @OG-p4:1rԬ[6s}z` YurD p߬No6Rpȝڗ~4{p4¶O @ @ @mG-p gK7웿νBa9[ӛTZ7quW(-} @ @ @ @zW@Q}_ yKJa- ;%l=&: 6#ۅmGp-?H˾R [H굟 a"L^pft}  @ @ @ @ȮJW~,\wM>hcv~Rjx0693+l-mvZ[Mꡭkxs0>ҷ5nqF 8XN+ry(oqGk @ @ @ @p" 8{3xI4_}qnHKC/055u[duf#.ծ^wY8orpU5Wsiպ:]QW9m @ @ @ wZdY;G];ΛpvG״pzo]m>t;]cwL[~B%(utJobC[U_u[ @ @ @ @% E~p}[c ׮fP gaǕN 8z/op-o~zTy)npM?O4Zku҆ @ @ @ @}) Ep=_^}@? ?᫅-#wz\8ji.҄V^ϟk?9-X)uv}qɩ~`xaZmU{_vV8oe돀] @ @ @ @@8jO6{InuW]RVYRQ}Q;>.cS[1l5uea]m>--((MzxMnE)|*\qoy  @ @ @ @F@Qj'(mk jp˼M]yb+Ew1p-?/S#;Ri Ͽ{p7\fGi}z`8qSƉ W'5ztcn( @ @ @ @u1Rۆၑp[bDm}4֬1;;ujaG %m? @ @ @ @pԛҳpԳY#a @ @ @ @p Kg'N^: @ @ @ @lFm#hgC'@ @ @ @DG~ 8 @ @ @ @%' heu @ @ @ @@ 8 8  @ @ @ @ 8Z`~ߝ~A'@ @ @ @M@];` @ @ @ @@_Z 8j%d> @ @ @ @`i 8ZpuR$@ @ @ @ʮO) @ @ @ @,} 8Ld @ @ @ @@us9lJreH @ @ @h. ਹ9 5@1 @ @ @ -ơ 8ꆢm @ @ @ @W^  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ h phGh @ @ @ @t]`ժUmmvکܴІ :Yܲ @ @ @ @K" @ @ @ @$q G%@ @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@〣 6aI" @ @ @ @Zd! @ @ @ @K[`8- @Uw]޾r28&5J"@$|%H!@2pY &p5iM#2 @RnH Z 8o6 ' moXj#K-Gp4[9 @,EaSZy{tV$@I@d`mDudd$@D @%7Bۭ,A,zRQClVz @"~Qpޞ5  @yP>'X%@2pY&0 h) % @zI }/冴 @vk#K @ +|F&:r 0[G @_v;%@gMgE @`O f  L\GIF;LZ 8jId @^pCK!-h- @ (, @` ,u<V@lG (nN  0kYY'yY,בeZY @RnH Z 8o6 ' moXj#K-Gp4[9 @,EaSZy{tV$@I@d`mDudd$@D @%7Bۭ,A,zRQClVz @"~Qpޞ5  @yP>'X%@2pY&0 h) % @zI }/冴 @vk#K @ +|F&:r 0[G @_v;%@gMgE @`O f  L\GIF;LZ 8jId @^pCK!-h- @ (, @` ,u<V@lG (nN  0kYY'yY,בeZY @RnH Z 8o6 ' moXj#K-Gp4[9 @,rwyg{½6nҴ4 +V-"lVam%cMy  @@ (PfH Pu3M Gj @%\no馐zQaw:Z'rޞ'>%@A@dPmHudeC%@`FG3I kKCH 64 _F Z*~_٦ @`y{ 蚀I(mRudYf&@(& @R뮻W\gqHݤm-ܲlTY&_>wvmʼ6ۄ}7'@B ,B }"rmJLf1 @ u].udɐR+Iiai,R鮧 @` ,w4pLt @Xj7UJSQJR8C;0/zk TQ <2 @`!y{!X哅q,UבNu*fy @EX 7~{]u#v[ >vmWn%ec @(, @`,tU@\O Kg?YX~}v;n _}Kr?|Ak',op^9  @P>Yzy # m_Nsgt]akB)_a! oQ{䍿(>Vn[;ooߩ.1C\Q>mnߥ U`Zw}򼙁]DzMNPGiigէ]PW[/O ;j2)aߢ0uKFd핥~`y~+el 5F @:\uUk]w tPGp>ur\T]gɟ3r%n!/{<fhX֍ev\8=~H0ySt?TZo͢$ɚi۔[Į+%kmYfP NzIM,PbT]H9pG\su6#ݫbmN S!LvY<^')_96] ilT3m9vokZI-ק%yx/m%^3V7N9m՛5X:)]c>NsC塡nay#]%no'~sS>TR>}XVS]_zN62_Wu3ׂlnqPv{+wg!Y ?'pn1+v0y!ko<,J;b/M)IAi۽7._OuuY{غ嬞-α,8ս7}nͿ@yu/ԝWj3ݕVRqms s5g6XvҊZS<ڥ6}6c2W5\D @|C?>>>011QG;YO,-j&ell,{!epp0@%Ag5])owK `g4߳~V>KuУ! o&L^ȫ.I`&.H?TkBzݥV>&/;S_Ojiq^O&M/ύg-*k >ua迩|LK 9y|0xث_BOihެFRĹbP!]<. :  θ.mtXǧ||2oe 0uǕ|0|ͫ?Қ}C4 |^.<3dik{+;w xy^>G?0S?8m~ >L՗OσL?s)M\rj_=xlޙg?Cx\]z9gs-KU#s{<$o^jΫ3xP^Zr |qqӥiLk}\c6J{SQ9rEKq*C09%S1mZ1*df :ڋb̏* {F~^O_4_}4EaM鍮=%/~橛׫ O1F^Z= mZ-?2e䍿/q#zZU75tGdL|su⛷P:sNj+7uoo&yIu^|mLp-_֒3./_lM#)xfo&s71#/yJ+K]tp4DŽW=[ozF%5c_~V9NT- @@9 ֭ QzԬZFQj謳ΊEɰzpGv?lqAjʺ/A[=&ݺvj1uͭ ŖY~QM#(9ć:uC6{p3בSxexSSւb%nzZ:yܝht9]aw_΋ ==a`Ë[tԝtƿgm.wa6Xg,oy鞭AK3A^MBֽc8VRe)u9gS:&oy҉U%پ5qbFoᯉ-0vٹwK]L^0Ґ<^SK+ >Tf|u'4b_ԴLlv9< ==kzV<ϧyRn/]˄pR ~j6tZo30(m5+uIZZ&-K[y8ժDugҞlo'\O7R?_7^z!|ȿ݉VRn^RG'\ׂxߖZ⛼ t N7:9Vʴc)^VZ9ۀXAz6x8x~ψ]/̱5R֦[e 8R^^(WY{}؍7{į1ɬjWn~b16r(}%V7W5*l߇>6V:f+^{Y\}zS_Պ\aصXjh*7ܬRTK@i|kc8*15ۘH f )Fv2?L|xGŷn͕F \@kNH/e[0Sp]#rk/V5NtO_oSQ 8Jî:Yt#(K. 7pCNC 8JGhn[;\qnUg˝=/G'_zFy) i`ؒF6~`0amj8(qkL>Ҍp:ŧ%sV<9?: Z.S8TSRPR7zPTn(^S |?|ǗYU cߌ׵8 /̯|)Kfiݱe>erh9|R%LN{n.^w0VZ0~v6soeߩbwp{( Wa6Go}vl1p6gbh>lԌf=4_'{PRPr^MIYgY L޴2t>o.V?C^F9y!Tn妝 7M;bWyk#S~sn)%5AX I] Қ}chs7kՊ?Mw_=5 5GيW^߹էTbMM'+U~'b|17ƇzS"4ZZ FMRjA*Ga@_{cpr9䐰; [GrK so*=V v˺4= pw8A9w=tĖbO$9yC&- &ߟ?a`ڮ? 8 ] 0ͭp2P2񇇩زm҃@2TDLJ2:U:!w}ffUe^ /,_VΈCǼlj_?ȋNRncym(mSl(fPK!sR r"5S]KC}SM=NV6ܒzk_lR xDyV'Zm2-DqIa$YlYYIK1B`~0[KoVyu[Z쑆bsq=V!]0wgp4@rpgHviB7@pg L'_jJuҶf*ugǺĺ{7r?1kh>>#਍L0rqnk٧ś=Z/F>Op~V}Pifm󾙛D.ƋNo ֔{v="7)*+ÂH94\ih߆ϧ֛>֘ϩ// .xp  ?ߊo?<9츑?nahuB*,Z-:6+,~>[|Ka]m4 8jO'_OG`{&|GY|q"8ihvܢ?+}`l8tp[Csm?vZnn%਋;)K5Z?k_jJu7f*yit>o.?̤_ütz(՚~A-͵᥾s S ,UXxo3&L\<NSo  қXMсE+:\J*GVOˏ{ 㧿16<7EOvI|C{LySn4|b;-|(rSlXLAh#/ID @`zCSKݪ\2}ѳfVQJ9ܝZVпŲnt/1NÊ\k p_=Eغ?kc癈 ύ ֐=VQϏOW66~y冗Ӌo>WDT|1h(5K# qzoqm!Y<("aWLlƿgaw_Λ+nx哙~XniR= <7{ꌺVׂ1CjY1Xr{`s[72DzU誻Ӏx=rN7yy7bgfh^lLf4_'{PZOr^M)o~a>ST^Ii*zn8f{ M6s)jƾ~Rk!/+#xSa?3o S7MS.MM+&cM\b?)h8w֌ @!Я75ԪК5k>knW wqGnm)d_bYwȪqbtMiջ7>k/T._Pl=#OgiJO#[E-w'4Ӎt6'ߗ5eZK/&a~GӟI|!WQ~Cx`Vu tFX XqOWczuJPMOpC̺/Fܛ@⃿E7} ߷Wne)m]ӘfqJ;z\M ;Ey&vxO] JcPSo]ե`PVl[j`5z>?>L=+Bz0{wCR#aiBGן_fVgvt T)}ԃH_|Wy~x̛;ܞ-vR*kdJa/ TΫ3~x^M9|jx>c^&/=ړTyߪv>o1kLν4OQ'KOG!yG}j>>K˂0!Į4bECeHu]_W|#4 =q+cA7O@~:3=|uj; ۂ}AR+J54W*hOsN-Ssٕa+觏_c0б2k&~OoFCh~6I:)o@4]  @`zCgĘ;C9d̀ /0r-T*;ni ˺-m*'z|5^7+cbC~}eŎtyůBiRS+Nֱ6酗W]fЯ`/w~aajm+`x.yb+#/M}M@ԭdz9+餺ۻrЌ+gVo#RtC*Ohg$Wb&sO!Y]ug>[I[u|'[W>i7H/L[ۚOz"Tz(kAXݼAIDAT0t~˹ ?Λlli6   }vk|)1px}z'06Tpr4 YGgrh^lp3}n䅔,@:ZogE'/םUGJWʱYP* fsWgBQ:敡/Fl>0xğndYSb7n'Moxc>Yrqŀ觏o/yja⷟ ?뼍8O>*wCf)Moԥoo^pCbk_xR}5X!}u B7 Lri%TfPU''9;[Ȼ7 .2ӵⵗUljLM-jW42^kZyr<$++?CB%83ϥެ*^dj}έnWӾ֝MHaBq{]>mXmQǧ||R!@ L=#^'L^wvU^a$ⳝPT?Sya7 x/T7[w1(b7?yoJ}H|妶j^4zdfhlT'lO.}@弚Ël$8}ڬn8 lZ\%wbs?VWL|k䥱~nk M{7\cL*[FU ^ŷZ&17 8Kcxil.h0q6wwO"O͆Cտܥӛ-Q3ΕxVloNCǽO @`G?Qo}8ᆵ)-ЃO +>s .v[ Snf o[=Ы6۞["&_pDZՊ4=TkbKyM&.bRmj2D[)I;xr?0qI|aB GB,@ 왺BD!QO]i:M^tX".Zw,^&oy^:-'-4붳Er!Y#fMI+m빷to7zb82M*یsmpu/_w2t 4q闪&%F9+ݦU.^/+syIY^tWӶ[?V_<̦^TZ _ * Cʽ7mge.8jWh^& 2&Ҧ2^']Lh[C`rki i~jiG_ҶnکI7[im {MZaʳS+֜b &3c7ћ ?a`7ي @@7s 6lWGټU\ 6J9ºuªUQG5Ĩg [֝Ŋ}VCr?4GezjmS,="vK]zQzzTΐp^+gp2#c+Ij~i7?OQce(>8J}i]1Hj2ZpTIb Lc 1L~y~ .Rk; #'/eB+-_7ԒX\^vb'evza~;i_y5l Z~sڟĠdԥ?ݬ+gstgqG]ȝ|,965 :jշI-.?Y,M+ݨP0+SEvNoZPک@OQ+o4ocwt~lS.mX~ Zo*P|`GS^F?B@N]+ g~n000=ضdftnQJg&''vm;찶dhv]P4]WA[*O?&teYV3ۧ],$zܻ۠֎*6 8i=Ƕ{WV W~+}U{f1PPOݪ]Cy? 8*Ǥraj]l1/VnGNQ|sw[VF'v4xe|iPNB }%sh M?pmx;#O&o+iOܟ[J:-Wײr_E糪[걀ȉ ?|sNAϏ-*4R̦,եky3u=lw)t|((@弚Gi?3oZn_4ZW=^T7nsd矘I}>כ c_~Vledu꒠}7*(VmW. pTH_uX ?&aCbvQJKMa[O @@w5+ k׮GqDzۂ$訓`;9 {ggeofN5uűŃ7U[RbJݺj}WZPM-Sw^F?xC1# +ōjӑ~Nʹay t aVQѱU#`ƭQ75ru >n+ikpKPPWp9!@`&fNyR@꜒+oʲad5oKK I]׿h0mkRkY>T+p[Q“sWu{z0KfJN΁!f;CD;(.j{ Y>m4Tǧ|RU8e&Y]bӍk~(*[.LZ] m/ׅ{ʾ{b1ajMq*㶹N 'ؽZ ϳ65J؊ӻUJ۬t S!%Rd*'<5|(ਆ}+P9FQL4}X9&.tonYw8"Z ݬ+ c @IDATEg#,9'TTPQ)gϬYO=1gNgT%$ə Oazwz{j~Lֺ5煮YfUH SqqYzJah,ZR>?c?{\%ס~7ȆGJ雧ey濼iŲf~Nm|-)3`G$׫ڦɆG&RwѼGyeÒURXȬSx;&~}!@ L6Y]W.YDƏo}7ˤl«qw4>Μ1c?/m۶ r;F-EkՊ3̵9xKո iYRV](%)n^#-_. KٸEJטz]5>DrZm.'1z$֮(^,% )zl򎹶bGIOm ܇ߋMIwm_8UʿLG*)x! Ӧy#}@|RޚߝAR _+w|w\V\f+Jכʜ`)wc.{.Qli= to8ZTGpOWރ%_aB_} G̉n[7~ qϫ?-z?{{KW}l{!J{=2$jbü/DfyOUb mu <פc7/^zOoy_0j>^h^)( xTrz+jcon 7&XSR8ޤ=w{ t}Rww|哟$j>5#_ŝUŒ殺GGxٿF;(\{̻v߉G+yֻ92ѯeXps[ӤwiF0xטx E^މuTq)מj$|Y \w5Q b Wj ]{eQoe˼Zo0`lҼ? x7C{|ZZ{#bnW,IqK.13x+ckH *!C4dOFޓ;MOzV2su06Cz73~*v9\`T58+3Tz|#5PŚ^]-x}OHY%BheGϡB f  uĉeѢE@{R4tSoNuUÒSo߾)o@T2v*eeY@ O28J@ GZ3-@(lq  P'LoЯZJrPyyy;J&UxP׮]k]^^n}7o<B..@@ j\DF8@ 8dV}q >Ge  ipA?c :pf;찃AMVTTȸqDui77~[W @z\  %ICɳ_@ #n#@ 8![@@Qeɒ%F]vү_*N0A/^lѶm[߿Z@+m[~" @ p}uH @<Ґ$@(J  $pA_ZZjzZf)s֭wҨQ,~z2e,[ִ̬iSӣRAAA*aY@ +" DNU d瑌.(@(l@@\j@@ ڠ>}̞=K۶mcǎ? C-YD,X`~nk׮ҳg؏x& Ի'N@F@@64-Z$s 4`&H탬: @ dzcc  vON@88]R p" 4@65苋eŲ|rYrȯף-ZHV]vRTT! "M" @\L  1Gb0xY-@(# ' r:ҟ:!' Ul:@vO7#u@ w;zu! \doG&yn~8r.)   4賢)$8$m*  'T$@HH[U   O}<!v#B@ >ʏMHX pN]R@@ +hgE5SHpHU&EA@OH4g 9G@@ x:C' G @ p}瑺6#@ȝ$  @VРϊj L #\8Rh # n@ r"W%  @<tDOѫ@l$(? P7#ucmpG;uII@@AL!@!n;T@G>q") @G"@EJ8 @@x40W' .IP~@nG #w꒒  Y!@>+B"Cv2)  p}HER @84Q)6|Df>   ̘1r-k] }@t#A@ llM @0#X _LW@pR6 9Y  p`R$@2\+ exEr @ pi xv p*@@ =xJC#@(:u  Fۮ$po @#AX\ pbR&@@a۠'pp%S4pJSIa@pB+ 9Q]H"*@(a!  =#>E&@(j5  mW8w@ j. 8rV)  ఀm8r)8%@ȩ0 8!`ەN .y$trv pъ@@l"Q px@@+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG @@_6 ) 5GQ@l  A8QcpQJ@@lÕL@)GNU'A@ ۮ$pDuR@ t#CV   /`}ǃ ]I@ G(@ZL  AOJh #  mW8r:) !DTQD+B@ zG>|DMQjA@ۮ$p @#AX\ pbR&@@a۠'pp%S4pJSIa@pB+ 9Q]H"*@(a!  =#>E&@(j5  mW8w@ j. 8rV)  ఀm8r)8%@ȩ0 8!`ەN .y$trv pъ@@l"Q px@@+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG *W'X".@@ AO(! 8W@&`ەV3 G28JH1@Y6  @± M+  #N pqz) @(@F@B zG!ó;@ p iJGi#f G^ )8JE(@@lQ(@( ! @lQڈ0 !@ `8J rKc߫l2=@m8Zp< #>E@JG Wd#\{;ԧ4{]i cۃ i@&`V3O& lذArssrii^KQv7E-PQQ!.2z咗M8Fulۮ$pXRidԩҾ}{)++vA㎰vpI+/G壏>Q&m{rטI8JP[4QH@  zGPYb$.rN?8,Y"~{:p˖-eʕ yrN]gM|0hSO]Voxp 2a'7x (@1 a;eU#0_W0Λ˥^Z<ր.3!8Y/?_ΝkkF3Nx\xᅡwOjFlӦMͼxy-ZsBGDlQ|'"Aa[oU>M8d+7w]~{)..6?j{@{n=XG Qf8  aGPYb>s袋L&MJ*ptWѣͰ%'pyM6=}oys?me뮻ҵkWӧOonƧPlݺh5j /k?$pTgB6dO>iݻw7]J@8O9{Z饗L>PS"_m^z!=&n}XzI& ]uUg#50t4|𸇡.\sYs1GK._ƎkzԞ!S-)iە\ .?sܬY0j _f8S`Ĉ:x㍢ c{JNr61 %*G BM($hv @/ e@eqi:Lǚ5kK. lHD~WLڳ޼evJxs)a|M | b}zH,,,ujQPP`[nQZuAld~8zg1A_p5Ѝo*F!T/bs #HΝ+I_}U`_|EϯW/V^mʾ~zszvYgeO>ha4d4yd3,dkךnu;/ ٨Q#i׮t4iRLzcە28V=^}&(CjXLGP#ԗhgqfW}@/]&8JPC8ZvLT:6/:2"k֗ɻ͑YKWI<ܣlۥM ޓAekdފ53إeݹtoXח++e5ҲP:h";u m6_2e̅fF-+ #`&pNRp}YԻl&T2^}@>ӪU+](?S{0%QR#p܎5@R iw}L:Մta i/~a=6I6r ekh)6\}֞; /Pcaf̙3$oXtXN:?Ii $:׮cj0]tz }k'' 80nRvJGQ! 2U@C:<C~gejr ^k|f5 \~>\Oڳ}@o٨~F(AdC{i ٲq.2a*/ d nޮ-)?&oL%VTvKNU~^ūgOҲLxc]7>E(pTZA.|SұE<| 1! {aG-%Jޤ;c )oIpz衇oVR֭z#QT($pT-G.|d@#f|4/^bݴEp;ۼ@{Xk/+C-CUHޖdon O77tHnݺU_cƌIޘ4v:4䭽[굥2uJGZ7DY@CG'.Ch@beaui;0QZʦLtԧG:/wҳ} Y^\"{Ǡ r}#eɚuݦmMGjfCg3w)WW?5i&vn#]Z5ūeepU:T_|QPz󨣎2~= R:}gM43|ޚt&}C=C9ҷ&]Oövm;n0>}=`^'5mTsO~ժUfx;GIl1v%G8r.) $'m ~;~b +dذaqLG j%G-J|B>6HFM* {no#4; vҎxOuk9cmLFPkT~T[[7M%z;4qWn  aG[x'N>J Gz#LIѧbz wTG:ү_?iܸLմpBܹsMQtdD(jcE+jzeA~Gg!/"!IZjl4zA$?!ky3kB;w XPsz7iEzC~~~7 "[o?j(сhʕ+SؾȠmQU+@(cG7|hOᄏ\{qr 'G93M(D{QjIoiJoxU_RemYfI˖-Mh̙رceM75iPIo>{:C{sMm۶l @n>xʼn<G ha=܄ A= >wyk7,W]u{W}yoWچ<]9w:,}k&̩jޜ2zx->7E#4LO[Puz-K/!:u~&}hە28VTGZs7.p\|q7CGys9qLG j%G8%luh#\n;|ѳK^,g ];5pOS7s1GyĄk=hG{g*S}thO :iHjƷCj+.. hѢfcޢvm7Gc:4JQQQgQlj_ \=h ѧ& ҷo_QICF7pFЧ> sA#GI=iCN>`e'v?֭3w{LoLءb:mڴi&dgyF#mYEn{-J{0a "m&<#*v%ǁ. 8rv) q"L+! 'RyG]f,Y%7'oÏEvټcM0@_ 8rB)F֬YcnB?.# L>OofvӡpvT߁#b1+ўwۏ#Fn &ɩ裏UY)8ʰ p@4 8so׿{dzPiz-p5XF{׿5LjÜi(X4>m&;ch/B:TNO<5/Ǐ7~zm{̛7\z衾}CiFr :?Sf7 l[n%n\?JG& pHER HZ^~ry]O*fI7jg[E TD֖ɳʼkeʵRRzG4E;ٴUSyXbG ^?^JmձO{J^nNm9 8&`&pXRzA޽{>;i@/kP.~&^nnnb5^0`ְQf{={X.***Ljf<+][=z{)ojڳp;-}Io2n6d̘12n8SaƶC1i{@&O28v@~R k]_&} Xϧ ӦڡǴA Wt0 QWҥKoկt?ڡy5뮓> v֘hk7;de>\|Qq]v6cVݻ^W>s&վ}{3_~)'xm2v%V 8r2) $%pʇ~h w;dC"gD(Ae[hE^ⱼ]HCG:hB:h"IFRT'l8qҧsky %; ^GR|Cs/9om! @ / exEriЛed|2S˖-p%]l@oiH~:_PeJu85=i@hСUkGtT8Ӵl~hKo&_ gGRQ& @O>D z;t_D 38S{>-ݶjvi'5iD zCaXG!IC/ 8PtȹF=i0[KzM駟D55sL+oӞZn]P"޶+_E98@ Cehq X@zև m7$/X@=\ z`]9QD6aՆl9f ^" aG0/iӦ@^>P&LM .b}Okf2Ch(hF:ȧ~*Omr-&0 7`78!E$j@o ?I{qӧOުQI(ʵñ! 8G)eee&i?|A=WMr9jh ~=!i Шb'?3`ފ&ѤCj/} Q}>|Q[a&ꫯ6Ci/Hz=uKKA'駟6׋0ĉ+{zYm˴mQC pK"y/R2c@\0̭Ct\p G?x'  lwuR#v9s6.ID9@{*Tכw{SYr(*{ìiRzô(vV_8|q @h4\!>X.*ǪO kpg6۳o4=cUØ`|\S{ʏM\ؾ UBJzma' Q 4|!, cMW]u3Cm#4$qH}&O,:I'd6Q\\l<5ձcd7lQ$@9^j؞ZzĈ yL4ޮz!իbQ!pTߖQzE{:w_Z(g??Ƽ8<fm =zuFcG;Tv,D}}YXF>n{ v&@pW~1L:dH6pOٜ}>=e>= 4 h"ntSVYsΑ>g\sCۤIDϔ6zhԡC߲DCGQ _ H/K.DrrrLhY{ЧuUӑ_t#8BGL4IC>gumݒ1Q:dbdoZrwVG7x MCnk׳>kz|GL(\xwEuu5tR3}2[Cݶ]曦'(=>/ iJ4v%L52UQǍA=$`ڣ>O؏3'E*{/\!6i+_) oӟ-,1+V釧<5J&[*}:o/If8Et_g>혁[&/@@5Ű۵Q)k5J4H _q׼yso*ݿ\؏9rdOu8W^yž5?^sӭʇ \|f9xM7%X@!ByS {cO{]ymJKK; 3g{}qf$է2i"pIű"+pw{5裓sܫ=LϿ:׉'70}2X 0pf64hBLϓ& +kGOk{BCv\$3,C63gάܝmdz!?|!nW !R{ u1޴vZ MpI뮻4atLڃw#DwtX>@V^]elv%LJ.8r)*C=һwoc^NKx34PQG;;c^8JPU]Yû,lQr~]du2 ^/C9RRAG.SmҞ;pG!V/Oatk/56 O|6M5zY6 @2J~1L(!" IotArEُ*N>] z凵fmL׿̮xܹwo,E*@IDATJ>rZ^M6 I TSPMl|jGzNqUOo 2dPeF!p!"iР&& i@&^ozújv[Ve]_{i/٪c+3?QHo'O Yu7iY@H(`&pM:QoB{^vی2~74X7Xf})vLyM(SjD/ÙihYB^v|))S^z4ptҞ ۵khu=kFWb-d^- j8hܸqƢ_~{З mQ]YHNQrN, L4 @G"@EJ8 @@xAO(@8N]p$  Q+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG @@_6 ) 5GQ@l  A8QcpQJ@@lÕL@)GNU'A@ ۮ$pDuR@ t#CV   /`}ǃ ]I@ G(@ZL  AOJh #  mW8r:) !DTQD+B@ zG>|DMQjA@ۮ$p @#AX\ pbR&@@a۠'pp%S4pJSIa@pB+ 9Q]H"*@(a!  =#>E&@(j5  mW8w@ j. 8rV)  ఀm8r)8%@ȩ0 8!`ەN .y$trv pъ@@l"Q px@@+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG @@_6 ) 5GQ@l  A8QcpQJ@@lÕL@)GNU'A@ ۮ$pDuR@ t#CV   /`}ǃ ]I@ G(@ZL  AOJh #  mW8r:) !DTQi g?7@@ AO(! 8W@&`ەV3 G28JH1@Y6  @± M+  #N pqz) @(@F@B zG!ó;@ p iJGi#f G^ )8JE(@@lQ(@( ! @lQڈ0 !@ `8JTϳ_4[FtoLM=o! AOȅڤ  )# %`ە28Z@ *GR45@(Pُ>]7y\guUpiܸOVrT RZ'dgr~UB6~^F#q:o*6A@AOPo?JqqnZz-~t= iXꫯ믿6 ÇKϞ=Z>Z`9=oVN@͛gGySN͛'-L,@(K  ,YDy_b9^\RԩSeƌAM7TvuW9ꨣ0.ut}姟~KJ-D7eIKm|I8qhIZ}X&l]ȶ+]ɓuH@ 3vvm'7tSfx< `q-'|RCD01/> p% 0QSɳҽ>[ulmn'p:ݩ[Ulx l^gBKD&>En:VeȐ!v_|Q}YN]w]ucq]w%gϮ?\=*fʔ),z3N{7Ҟtjժw}ҹs6) 8J@*4 WcN8w_z!БN.4&lbnmּO4@A! s7m\s,ZHqAzsM14Zv,\lw-4iҤIdLۮ$pͿ);h}ѕ!pTI ?<nI57|#}v?餓Jb1mUT(N9f2QU҂ S68e*$??@.`&pT]' ބҧ ]מ3O͚537kڦ .@&M$z~nwժUvHo=ҭ[7@nћo`]v-[_|!7| "mr) 8J@*|=# 6̄*gļS5\cz+e^y謳2:=kH{mMgvNHd3W\an,hGy9=8>;g-t9YlApi屽3u?E% i0Vw ;9RE8Ho!{>wi*;<{1_^^.#N:܇>vH]: w}7?Ǯ7OFk|=:Qu` sc-@1C;r9ހj(hA~â鐰_DwqT?6;~e̘1&(zꩧL_ءC3DM2ԗ]vYUgt(T& <鵒֦MUb>lo! X@üzs&{gn8 ʊ `:ߣ|ɾ%ꪫ䫯C=T03O?MGR&] {GzQ-Z!K߅#! f.K^JoAn:hL{^_Zs}eޛk0_$oM#׬\ƾĿߺu> +Uxa ?k*tGO7oz8l'i텐RFLYW&uشUq9x+9_woe쌅5ֺx5Vາrv;f(;v}H@H~1L(*E@o"qfh4 藁N;3Jnn1 7 .@v Mছn>H`:6Ȥ$&Ҟ!55]tE2ydOƍ3àiBzso!f{9sFT4#Ek8)l喕7:MfV^-.JG, LQ07BxL Q騣+WmdD(AeSEiR/yrm74T&̯g!dG@Kw%HX.{kYziY rGZF@vp]{ @ϟoaEfqWV[mN] W_} S{zE@Jeƌc`s3K.Ƙ3g̚5K/^ln GIjZh͓"رyB4)uQkcC2Wk*_~yhX{ss߾}[o dWZd9ٳgN;$'xt\Kf7kkvA~eرf?һwoF-zSW9seذaYNy+АݎnժUaR::,lەȪ @b1pF@dxwev3=+:w\9S#h$8JP-8R J5O@U9{u?4=flߢr8!HӞ>ki3T[-Vsn#GnY=tX5otדNՇM#p'g _ 8i'!bnr52=6xm8駟SO,jݍI?|Qp:D?lnMD{I&?!Zva3Dۧ~j6ѮԧbC_?`F:MIo@f@^ o3_+owpRyJJ$`V@"`p| s>9` * &T2HF})qvv9vv:U}kϞ~֭F>^42FUJjfZ>i"3Lz외*{ _{0vm]Xwܱ49 |QHb!@E9 d!)Gemxi8餓l„ ͎<ȴۆq% RiC6]t *WxH[kM0Te|Gыv- yfk_/m#3G f ůִl .8wgg;yub@@ bQZ&V"T@Ӆ{''իgxF[ҝF5x/w}-Zd Gg4FOhG y֢E w n)6ТQtq 6p7ܛ? &-^؍v+ʥ(' P>QO̵ tMn7r M1rt k߾*ܡ#?G>k[~(Bunm˹w rL$IpͫLSng;e뿂B:iZ0#tԻklc8Q@aGiX@Z%KJO7v tO#9ҽ}eNIZje{=~裏\)1Dm~i7uQGeH}ݶo}Ml#_VS$:^{i M6dȐd{]_͚5˅xcǎу|IqmYk4/\GS0hV5">i}Mٖ )|뭷܈Fz[yիiXw}whutzMGWnW8* Pʱ(3D@O?vyg;clnTQ=8Pn -Vi#eɴ,8/L^M FiB7P~8ұ5xQ2k߼ڳ )BW<֍&6NGaWCLf#>޾5~Q.pzfv~}w o?Vi:G1 ^  V1L(-+JγW_}Dt"/|VE1p릣<9s )tGN#/QN61t~n7MQ4zg:t?\ٽ&pTvMF@P zэ/P&ב|F r)[뮳#Fئnj{nSzOX+^o+v[֭[n&MTN63F&H efКU:u긑4Fw7NtM~̶,+ Z#°PhZ(,\GЊ uN;'z0!H+ eKCzudx˩v鋟¥:UZ5vZ i0Xzu2xj;p~?g=>z FQҢs*vPխN\igs$_WNԬBcklխV:wZm[I}iw>4'+/@H+;2}-.Q  w7VZi%{衇rF>pԳgO7B>p;X4Jn4}MbM7:T9a~M(̭C@;v^A]ݼ%P7B p# CAA)MeQ9*SwvGִiS*K/^{vuWɖύPM,ͳ)S t.]֨Pʧ.p;(HO˹JWfe @ 0Q G\`z ~g;m]vIi(8,} Y0ײA0'qnByт&]lf`2J;tdO/\؎xmˢ`ԡfvjnfբ7[xq^:n1'pAQUG|b/~1V m>UF[^vKB4з>޺xwՕ?QH##=znd;t_4EKm <%;iG{Skw#@my ~" ^1L(k: 4H}5H$pi:X4݇:'%Q:4HC~7]sg"pn&Nh|pN#9T J"еZ!~8 ɦѯ/<*itAMEPMͫK4f~47t5C9ąS>Co,M\s֫W/;KSi:}6 l7Mo5&OR2?wSZ-Zd F:9 HK1G:_3Mk5;w]`zg}gݵdؒidtSimۭ8s0w⎗XNGIx@jaGhx*z3ϴݻ;lnlml2M_iQfw&#=qUOǿu#X4 5r5kF+ 5.3|M7@#p_!dTbRLөiSO>ij1==XR'~0aj-4e~{EӲf[ouSÕW^i}}Qo 69'o^hݩFW_mL}Ri]{1r-M*-R_tSխ[M0<7> ~c}߯$p֠  uGQoa+hDٳgK~w~MΝc&0o88y#r=72=}:u[WN6:]ۣ|m ֱ'oM׭ۭoa8f{;wCoΗv_FBz]?nnh#)  Y1|Gu{׾K.i- "W7@Ӕ# ҢMOMyɦqϵ^0O#dэ#GίhD_O ,頃J:}HJgrR4Q4z.Kmf̘FXM~'4ԾKJlpC1 @ >lwMݻs9YO/t>ڦL;6Z,SJ#4sFS(գcǎ6}tT 3) )^a^4Ul裏lԩ\!+]^ zk C6 ߯I!@$E(>>7_veC̜94] à~GZQUKр Fr.ۦ]Ħ1)W?4Pm&!'B:m6U5-x:qhz}>m[T-xF.~aFHWgypߨI~8‘vcvLߵGA)nzsڹvj8r @F0TloѣG`N.0+ʥ(' njc9 nwmTTr!T?;L8MM{oW/䵯KըK?SI!t~'tf,~ԩ)dPyPO=ֶm*QXJɩ G 4(a(DBQ$J @tȐ!n}Z#àϢX߷n,?SQ? GU~M![5i`݂~ЦMۻ_O&\kI#iiٸo6md rL1MuӔL˯4i<}izul-N/ku5CF8z;k\b҈I]Z6 لE<޳ÊvюZFQ~A@ bQJ"V PM@Oh3<@+S_hHat) 4F*Uy tStm K˙r =? 5]Yg7a U+3VV:ִi\Z#n4BQTȕ )LcFF͚5K,R|>)& Pʸ(:H`ܸqndV}vgQF:/ׅQ#pTis٘6M[h$UZ57[6&hb0}8pWSmV:qшFlmLgVu->kbLю4JCƶ˺m ةGM87vS=6f0{HSPj۵;ڑF{ x ^# Z'pڈ5 @5(  H+ ^Q9W`ĉnS=<.Tp_SkTyPZ W )WkĞf7Iy+` Bh`ZTB?!!MJfnI oN?d=6%I$o4  \1L(b(s@E+ ƶ ^눗'd+YCnģl v2 @v>pԽ vQA@ ="sh@"&B@ZJGA@:RqMN@ 0mG^jvj@IDATa +;jו! pT(Y  ~%|l# pEWf~E@CO਀EGɡ@@V|Qpr@T\SaH!@(g:GtX @ =uh @J" @_I(_AC*[He?Ge, @(_@(8* 2FjQQ-br(@߯$pT+8#TR8J"pIolͶ-6eWE@|QplHQ9- JG)X FHV!@E 8榲  @ =oKj!@2ڙZ" PN_IਜZ":$ PZG  9 =(sZ@H)R@ ב48B pTQMe@@zGߖ*CQe3D@|Q9eE#u$ MQZ  W8o@ #DQQ[:! zGnd p2 DB+ E9 Pt#E' RG!m  \w %]@ l"@|  pG}@ ت @8p#S5H5'A@ _I(I%@ p):9'D 8 iP,@@CO("a p<  [@GH>jQ pVN  @|Q!@E9  JGhN*]H9!TQHb! $zG}xA@߯$p @>\GQc(*uB@",;"T "%@(RIe@W8DsR @\GN @ B0 @@ 8Jû @E( ~%#@: @EU  aߡ'pFj )GjN* @$|Q$J E:RtrN! p҆X  |QrE&@(l-By@@+  ב|(@(J@@ =72UCH 8TsR@"!"ќT(בsB6 B@H.;. 6Gakʃ _I@|> EGQlU DXw EDJQ   ߯$p @"@Ha(  @rߡ'p܇w@ 8 [P@JG- #u$5A( 8bR'@@ CO(L@ R"՜T@H~%H4'@.uB*@( C@@ =>MQZ  W8o@ #DQQ[:! zGnd p2 DB+ E9 Pt#E' RG!m  \w %]@ l"@|  pG}@ ت @8p#S5H5'A@ _I(I%@ p):9'D 8 iP,@@CO("a p<  [@GH>jQ pVN  @|Q!@E9  JGhN*]H9!TQHb! $zG}xA@߯$p @>\GQc(*uB@",;"T "%@(RIe@W8DsR @\GN @ B0 @@ 8Jû @E( ~%#@: @EU  aߡ'pFj )GjN* @$|Q$J E:RtrN! pa~:qX-zv-Ή8   rߡ'px S@@ ~% ACHyD 8`L(@@" ="s:@ OGy± L+ #:r X2@(8*2ȇ~hoi#8!pe깋ڠS͚5ˍФرc1 UW8F @;M i6̍GƴH@=Ԡ[<6tP]w徟__. eh)Gjzo_O}`[>caW@bQL@#ƙn`i 2ݐӒmH~'%>)|ζnB@ھ;A:ThHIӢaw74ibwyo>FЍ-[c=fu֍EcO@sӟ5j:tPkuSm6N/tabKPұW_}=\+T4uT\z饦Tc9&#MyO?Y R`wM[W8* Vy5F5 /t)T'pV\ P:&Lɖ38{=c=LZB(CUZhڼO9 l/MֱEۤk۬FZD4q\8}}=c{m5:UZ7iq]Yֽvk&]Zzu{7!bS[U)b;u0EZm[ؚmW& R(`ak8ض;/eA:aEҲ/^f~;ӣCKkٸ{ogb Mϯ}^]`v6jR[ּa*,Z?g^r\Ӹܩ7j+#'@J)&pTV*o(]}5?RPM#w}N1W{ITԍ k `C/Ϛ~U.Zk-O=Q7o~eZ**/2WJ r_{߂)AA櫮*f9Ȍ-١׶|ߞ]ju\{b`eCu~vs79\v9oMNzmnڧ8ձev뚎ESp6{ᒴ'>%8zvle ~m& v-Y]h6k ۿж^󯧼~wm~:avkƶۭϛBPZ>l@PN}xz[~?@(bQI9i "p)4FN2dH.{+y4; %. 香~['nFiHIlMljFNrbc@z![4zk{I;-Zȍ{_fHjD'0iHS _~4vmߞBt}M/_IZ"@9(t_4Oȃ"/#؍7E=մ3gt#곽 fB(CUJ葏&/|b-շ-Vko]Z55paa'`& #6MVS{hWv˟F٩ڪ_ԍxIF#n{6s"{싟Q֩1hب=G4Ѯzq;FAڤk`&> F)ҲӺ]6r?+uN'+`:'ޙHku;OtU/1cwu?g#)m:k m ş_?7Uzum+.ZbN4kZý?=.$vݞ}b_A} cnTTG^8aG7??@(bQ)9g vHS~nD뮻믿Y_c5;uDzhm(֢ckW}O} ݻww?7r p3; d!0{ld~tAIY4i6M}(G i:gBӚu5te{ntWTx=}F|4 iϛ70V{V˶+ y(DLQ @F= l]s5ie]lΜ9nA6+ ehJ i wэѾ;?]h{-\,(>?|LlLu^cӚSlUV.pA`u^# un]:d*!tCu_Ά i/~wv[Ɲ۸׉Éve0Jy܎Qϟ,ptcLu(S7M-班rX/Laǎ =`ںOy^>q'SoCLɶAJSuP';z0ڀ;%l^# P0s2hfͪNMl뭷W^ƍ'le]chm۶RaÆUW]ӓguVeܸqӵh=qӘ>hꔿktԳgOkذnuuMQwlִiSwSO/zv7Pf*m9&pTD@(F?؞|I[xeQa|` Pr_ϥi8 $typo),Zj8 -^{mw| :~(,PE# )S=6>A/MivQNн7xckSbiϖS]R;ɔvMSi=7\ML/#y4}q_RXꚠ.>N%̱ۻvizm{H;;m]*~" P205'.c)4",f^ߍfXSGˤIO4tE7t.qQUu뺰TzM˗^zR yҥKo=zؔ)SEn`foFouNA%=Wa/ZjR˖-MswGao!ʇc=fbH;gy)ϢJH r( N볈zy.pB Uy睱Q@'t뮱Mj8ғغ)Z0g=}Q{vK:Jde+ IQL(kGe|ُ0&y{FB-Q#p]NFM!Hsp[ond]j8d}(;}.lܓϵnѭN(>ptٮZ::L6Evl6XӺ]h7v ,M9FrKh(n'xsqntGo\9R-m-#9-١x2I+Ha$ʉ`ʶOig#Drw2@J-&pT(P#sϹ|4;lV((@{;CM:=uQׯow ?|TdFo:]_v7m}oX*P|7h{… EhU}4el۷o4}.5*b @ L6qӵE#)pݹNOH5j*B/'N7|xV\q*L)T)٢FeرQ_4pђ4N| a0Qf3f " 3O=߯$pVl GQiI h &>贻/6_WmøQV`>t4ѝLUZ5SSE-wAuKKMG>4i*0M nڧl~0Rϙn`JlTkm[b;Qͱ#cGY'z% ϚFxMl:<7PhH!M6,ŏ7TuaTk/Tv/ӧ咑L^]M5r4sg>`z[~4[us{m~" PR06'/SW]S {ӔaC_]!/v7 45܅^F8X`{>ȍ&J6Fұzꩧw8҈D)~ Q{6>}įr*h5=є۫Tf8* @h4@>3}cLUԲW\q ,i 宻2O# ObN9gK.z:}MGeI#)kogύ?XΝM*W߯$pTn-Gy@cQfFb=zTi[q-hHSu2WRh_?vsZNb!{m8>LݫvO";xlHUNɎ-sCvMN!A‹&#i^76> Eu/Rݙ߳? MSM-#lvTT:<.mϟ8:CeSvFm_;wc#iD#-~6p=kl|p=ִA=Vm_={ @Jj;p$=~W;<*PnA׮]ݓ|vaԚ$# 6J\y( Gz_#/*K juzpAh4NIxMTԠ4z@9/ʹ(; nM]zBy. \~.lF6̴_13!NrM (:jE{L)N=TYچ)QӸ󖦪jJK.s]{i>Sŏz+ Y( DTQDj!@J; <묳Rn F?H8b8Zd;iNyzZֶy#kٸ :OG#}kg?Rm*t5 =7GSq.ְ_';f|7bq&?|.pG޲:/g(i9unj4}}Ov_Tf[T,#: iw~*pNBbm&mOsEM5FHW FCbA@ da02 fBf͚e@S~4ݸ+r-QHdi 曭GՊtM+>>pЖ[nYe38i,V,'|bs74Z1\U ^K8%H T@]kuהdÇ۵^F r+TES<G#0իJRG.uUWvsܹsmҤI.B /xom5 BPStlJPND^Q䛘 "@FW6K/4am_{o`㏷w߽2QFŦI0Ytc[tO4-XboQ6L)ò]j8z'|k*dS;K/䞌ד>kF&9֔nC+ؓjɞ 3MqQtc-4[55p)SU7NFrfmIl"e)kB1'tǏ;i X~w]YgSev1Ǹ(xL4… JAf-ѩS';nK,MPFzDVFdJG5 S@E}k!T:alѢEv9XSm eh%ljN>Teb~;6jkfkHXGӒbӼa"~:wp27Fv๏1_&A4󊍛:l-ү{|:g&tjs9G;9hJ+Ď.GSfγ=>`um5qH#կ=zjӱ%/)t_ ʰmZL8"կntZj]hLJvC@aGT\Q5p4gӰ!}5X.]b )M6ĮغT/4 ޏ_to}+}Ɯ:׷.6xj2= Wy,^ؽֈB/N{ĕi5y˩m۶B<F4^fLSZK{Z^!a_4 }Uwn >(LQg]ve˖Ut`ʔ)O**A9{qӓtNI^fHdT͏8[B 5i~$-[_Iਤ@BUHCSM f͚vn͍(ڵkW`deQƊz/)hf;93ۉ#GiT=bZ7mxk#40/0֫tt[gwX0Z&ϭΎu Wz~ )p5w/~mό*`dK/X̍@5=m:ڥniSh럶u|jkUrӣϾF8Z:t֦y @K#P||ɦni --~=ilܸ[QXS =^Z4:ѝwޙ(EW_}%>M[/ *Mř3g hє!~SiD .ƥ{'uۍ7Xm w4VhoQfp@FkjP5AJ q$!}VSg6}VxWiUCqӛ&[^6#>Eaؘn‡z(6ʬS ӄ lƌpS9NE缆 p>w^>>/3B~hS!RSȮ/yo_I(-E@ ЊrTjt=ܠ%}?ꨣLjZe=YOO_傧4M֋?A}zHJv^֢_Ϗήzq/ $ _v:f/\b+5od)۱G ˂)ӬI0ʏ דkn-dtEK}xѾ=:hbmS>wД}! t3`Ԧ ћ6&N\4EKd:qߺ:h_љ6-[-6QLԹ -a<{;c{.`6Ax!;Tێ7@(bQ)[sF9Mte4hythRW^qGndW\1~U *iJ5tpv)ޯxPw\ao߾vW Ko쵦(ӈL/;wveoGisԩS呂Ks-ʭ(/ \>6l{:8d ,p nJuwntma~Os`ӧOjkԤC >7N;U. }ԯ '͛۱rZ :,4ou)ѴiLSnզ(G#GtI2O߯,`K@DG" @;0c~C{=}劉 U'VfG(CD=p_{pWNB:rSxM1~VmN٦7S~B1l?b][5 Zb?]Jg^O:8Yώ-Mh/I8~ϵҢP*OA'Ogqַwv)m6#J ϘPZ⏙5oOp-bi&6'6oN݆z]Ǔ?]boBS 7n%n T1Lਤ+P@|駦P+L742:~L^2MѱcG7A6m2v1n8l={tuJCWl ,߃niڒr]kQn@ <Hl}1 ((QKi֭[\AOO:w PЈGO=|"PHZ!r]|Q Fr pTNEY@6ƎkgqTn곧c*35k.2\%v;LalM(5OY`dU-z@IDATH!i 6F=nEۿ.ogb>fZmn;_l4L#8kb Ԙ)6/:/͂T \ύߏMoMH۞(7^LOv঩#y]8J;n5;5\I/oCG2N [oLm{Q0ZՠuJM|=x {@CQyD@|Q%:uER 8*TF ;4FjՈGQXehJ e`5   ߡ'p @FG@,(.2 EGQlU DXw EDJQ   ߯$p @"@Ha(  @rߡ'p܇w@ 8 [P@JG- #u$5A( 8bR'@@ CO(L@ R"՜T@H~%H4'@.uB*@( C@@ =>MQZ  W8o@ #DQQ[:! zGnd p2 DB+ E9 Pt#E' RG!m  \w %]@ l"@|  pG}@ ت @8p#S5H5'A@ _I(I%@ p):9'D 8 iP,@@CO("a p<  [@GH>jQ pVN  @|Q!@E9  JGhN*]H9!TQHb! $zG}xA@߯$p @>\GQc(*uB@",;"T "%@(RIe@W8DsR @\GN @ B0 @@ 8Jû @E( ~%#@: @EU  aߡ'pFj )GjN* @$|Q$J E:RtrN! p҆X  |QrE&@(l-By@@+  ב|(@(J@@ =72UCH 8TsR@"!"ќT(בsB6 B@H.;. 6Gakʃ _I@|> EGQlg<(>*$EATD9 3{ g8ys8A% (Y_^;tSLǪz~_@L 4 HDu' $@W"%;i *'yʑC@ G hP-@ ha@p͇rQ @ +8 @ G2:@ %Wi @H00G8Jp'4@ Q՝4 a\p*G!%pC @ =Q4B5G# 0D8X LpɄ@I$p^M @ €(L D@8JTw@@"q%Q"F@rG9;rQ4KT.@ €(;A#@r@W"ZP@ y$D-!'p1Q@̆ @UL < !  @F +* 1 hGݽ4(4b6 @ba@pT e @4a\pTi0 Dݬz5ݎ?8~\̮wz j{VF=@zcΞ PUZjE'"02$rJSy몤1)/LJL olĉ֤Ivm˷r.y$G;jA  6i$ZjY$\"9\|r.1N dC䙾p |}ߊv`t0-F=^)6dL 2FȊyH`Ŋ6n8mȑ`{'- ;ؗ_~i׷<@kݺuS7o=裶|R쳏m۶/^lG-X»Çۧ~j{J5wֱcǨY&AHb6om 6-?pW^7|{mر1R͚5e˖뮻{akYʴ,ƕIɓ.@B Kv=}:LDK9$i*~k>*/y'^{Ul^& -4b:;$\KL3jXzl#Zt"'?Cm"r';'>9,]ı ׮\E&gEͺ@zgGaÆu'|.v 4n6dn*q#eYV4h}ёΙ3ǮjsN|BQ,@8"L6@ 椤FڣnݺFTn;^`MV*-®RL["|\uh݈UK+ [l$믦=AݦM XN6¸(T"nf B@@M1cvaӘCеQGe|pl :4b:;$\KL3j)vߡ=lud V2K'|4e]vj.lZkVN8t,b̄r@a(纆 %Kءou?=G8z/|ֿ[pq[FLu- /`OG(W]umQyܹsgF?"ED`d#/l~oR  (4b:;$\KL3j6cR:_(pV.]=GT&0QeRfI"hJ0T*N=Tꫯl}7=Iqvi~YYՓ3ό|_Tz7==$1IQJ* gT,*]G%t,@dw}_z&vm+'4bA8nJK?~2P&OlgqO9SZ=մiS5 f!r(ӫ"0͝;3mܸq:K.Oc9RW"Ev1!dL@Bڵ3dMu"e!Ѓzࠃ*1_l#G}N8 (Mp4fR]ߠխV1Jm je+l ױZkU~5{}|?qݚvVf}Yլn5m[hljGPO̵Zn[os~ ?dMVYӆnZ&DI> f]'ץy:kꕮrYcOԷ5~ nް/{vuRSriĢui2IffE)u/?-u7̶h][;Zݍ{Ņ+cC'.،Dg]{'O<:rlQqӑ۴9C}m&.y}tH9r~;QL|$O\wWS}CѪxޝ .~unNklkBH +b_*;nfܩ@ \"ϼ"H{x`Ok\fU:mG?NQ:,*Z2w6^hW:ܢʱn,g:T<Ĕyq<긁ӧGĥnZE};=g2gQeE1!j[ _F$\9$Ө%lNΪE*wIߩw䅠uS?澯w}pXR&~F8*̅O( L|tCUVE˔)Sc\I7Jo?|Jnᨬ2_(sv @?~}~~H)Ǝk{챇r)$E%Rt$pUԈEB#GJ9te-Ç7sOS4&ER-[lbĈs_bDsMW"p'Q5@ $)_(-ᩧB8 @xO DjuˢZ=`hvkl-bpA8RAN*QEDu/}"<1j(9۷7#D3 2u{uOα"#6EAҍE"dEQDEi2o;QIQdTuEI-bDI'\ \49.37sI ?EK:$MIzեEdD$iJT$%i~ѢuQFIz o*K8 Ag?Uc0~oU ت \[g5oL:H)$GIT$E(^^L WGNz;/v.ړ.FEJ!? %!=G}gv'Y͛7oOѯ_e*s+b7pO-iΖpj*EEjժe&Lt3yf۴ic` O{jذa>"9Ӭo߾QqPBCQ؎xba^WF1C# 8@8SoQW@ [oe;찃]tEnc3g5n(Mp4إRD#`7zICqRԒ*R@ѫ}rvՐ/}4Nإbd%dUrQHQUnwQGRKqtM:?@ @eK$}u]URl G7:YNSf H&TKG+͓=v<.f=G?I9~_[}^^-Mҕui.} /=>hGEhHR:$I?]4ct^iՊI,~5Ks>E˨ۙ.ʋ)V8 GJɈQf'F>Ie|ޅ҄#o9M."-e쾝[.wNQT{"'tn@Gg@&‘nzM<8p`$ \E7ƍgy'S%Xo ^l4}tiz'|(#T6۬AoQƍmѢE^@R8pTе ,P0=7x)onI?3lҤI~;ZΝM?ܾ+ZI)JfyK qҒ,u$M)zѓO>YtD75GyħjS# GRrR 0P#WJ\$Q{AQ.b9r)*"3<t}YK[6!|vcKpR7E=SJ]Ě]5A8R.E@*&j{ME>ꎖpT4 $ (yG<Ǘ\tF.K(Rn^Ôya~h wM\P$HiRY=stbr.}Quu4$EˮDv<:o6Jv6)J1w~ݝ2ܧ*)ZEwMY![NQUr)!H.إSIղC#&vBnB)3 yw-QP9!O'mѢgOiJS=E S԰7H$T$⸨(jN.]( G%1PRSNT*\7t /!? #KB D8RE U4Oc-{1D-RiBF_SS-iJ"(_Hu;a?S/]tc~v衇jUE@7to?I˗//fjX6^rG xl>Q֭[ڨ?CZ'm멧zO mw :| Rkԝ,[dr7T-2uv{Rz)YѢȈiI;}YQRСC}Z7mKLڶKRj_?-˕a\p+=B= $@8Jr6@ 駟!ir䤝vG/i\pgA8R ZITJb$")ӍnhU.O~+ۅ.M@)T(IIJ*SbG>}!TLa uHf*E),W؀nqRǖN7w\jm7Z׮siJ+6~qʦp DVA҃1.%%)Jګ1%'Y;g[?\SںtXTcwh[v>cSdk)ʖD3 gQE]]*QY܄ rdQ4o}iOS\Y=QSTjkaܥViQzB#~F8*79G_z%/}iEO|>r"E1={T!UYån*"d[8ZguYFO?D*.Qԣ%Ko_G9-T @, H曽hR7aTõ]tɮE*G}7!w}H=!%W] }0 vKR,^#(y,BSuKrbGO'xT]4U6^=҈TJN|/q;ϟO#[ReߢIH"FTyF̒D%*Ttߊ"Z~NJh$H+ki״InH Ǧi)VI%GEUJpt;n^?=LMk G>UꆋrKQAi*U(GE6G@GȪyK +0`c=ώdgJz,:r LMGpt饗Z޽˽l GV E7㴟Ӵn ~fajJ[la͛,'W#jP/@"PT6ҏ}J#*LiٔTMeI .Tu؞r)~S^jҤyVnBHr}d~B(nHVWIKժU)GR>Ɖ?a=z\sR{¸(A#p>E@߷>}ع[º8 JpD8J:#E9OM I)]Gl<{择>meoi}ڮ_Xvmmo߶nk{fT/rTq4qT_-HZ4=In?|(?sB RG4f G!UiG_)5q] I JObXTW"BIHi?]N]Z͵EGgw>?Fh Rg$իQ2E'l7痒zQzln8)dK8ztwvǓL{aKyT%ԞA]6F."aB˅1M ̅Nn}1sK߬PJgHCկV)}}%׾=^u)UM(@v ˕L#| ]__ ŋMQtPnyp T`"(R)mYZׯ"QΝm A8߿ۥ(QA8R4=$r馟"U%ҘwQy< %s 7%hE6:SMl%Nkۺlm?lG|!P(ZB=*\s 6g!1j 6)cݣ)qMjPFPTF1ӲN RƖƕG91TH$Dv+J!t=6JYԏ ̙c'xesqQ^H/YnMt)Զ,R6ҲA(pO&] vSS J4o* Gth闋OmwvzZRGiiNlĆ~.Յ% :ҥ+2qV#}9kIdRye%BR_g>1 =$mۥj[RTtB$)iz\Ry(͚""uQŤr=Q. u Aw yȊFԵm/SޖBi fΜi /L뮻Δ,AUtED@zBc9ϟo]tQa7t 2ĺu2U=IR*Jeeȑ6eͥ:>lδl2{Mu]=3_UƕGUI}AJ(_{vC  4JQ=AdƏog}ڴif(MWpt٤9?=loj)e_9`+۱u7o҄mZ5]mObk~⮅vU(M8 њX˞;f'^ܹ@IDAT a~V6Si{DMS!ExQD\t2F[5͛?&*o!$D1F~ݭKhQIAgOcN.%]TPJ0)jL+뱙 (D:{;{tT;J)2Ic_߁5*kJ5sw*1SHmڦĹNP 41aXʚV-l[:~39csߗ;FN|c&M{lE)@ML#A'FٽkyVW_9jz%?`&nVMT;hJ9 /X:uʳzq>?4E =Í7 PN:C*#h,GvHG*J e…c-k[o6ؠ䇲' 7S%jgi'NN:M>7|zG)Q 7/s9t-o{ϧ8JZJ蕒$T(oSISLŢ]¸*h@ @!4VPDE\`z|Ip \&'"|H9H9Jq;(Δ/WX9E&:E(*ZT#$&)E"0rْV-Z*u&)Jd :g;;˟}QrM(ۊUHSFjP%J"̽6MN^;M΀-]:sՐ/ͯgZFuCwիӛ'c#'[-'Q` `#AEDr{/YQ*BnUiR^9W{oVQtCY6^iN- \Q='\]+L#ΓԦ(VfU*iݳ]䢽S"I:ÉsJszoC Vp;!JB[6?w -x.Z.*Sb>NT2!a$@f̘oDit$*B*zFrtHOք M+cƌUO7Ӕ<(RuhJ_ne-IK. EipԺq"6ʱ~饗u=cM&'B'SC-t\ "F@ ,X>(͹NЍ~E7z^"U(M8ҾآSGС&"'ÿq72->Q~{8iJLjY!ahk",ַ] RI;kk0ٿ*uR5w>Rdy p7*om_OkZ.Ґ9mM:TW\(tEdRP΃NdRѱ%i^H&1D)tG1W3r`h*Ku Ea[ob֫feZc3ѳ_L[ޟyLKtN+=L i|<‘3{N E2UCcY.qvi~_K[go*= }I-~٧o|TS&(k(rS= Pva3c%gu4 ѢE%պzo;`S`?p3`b7dtsnw6=_"J;$ 7oa>R^I"W6#qV.QT G +Q  tn=sj]1uYE:G^IZ?o߾?K`R%5kf|ׯΘRG$Xľw)F'"!4箏&B'HiոP2e/^"6I2EKYt‘")I?FѦNRĔiMꤨ8r+pv'PH*Z$V/y,O8{R}ޒe<&uk횷z[Ljb~nM?\oKiGܥV*$cAJ٩=7A~iFnyC \ti 1 VvneKUp4YvTT$H\RNKԇGEBT3'S?滺K4Qyܥj6~B̆p$)}G"/\w+}nuJ&LNMm \T. GFɔ&9Q"Q Jґ '֥SN82 q%,)R*?]NTn~5}}߇;k(0Q4E`ܹ6zhCOתp+*T)ǹntuGi*nJRͼ})U?(*L A@"M?7nF͛73(ɩ,ےDFJ5eEӽe%KRt%b@U ٨~E[/Kj0D8*gGM@_*⪮UUµ,TvE:kA8Js IP* Eh$.xv͓xrΝ E)p%nh?8 Eؠ=L*M8E Y ׿3Gh"iD7rF$7DJ#m[Oǂ8#)E>N~8 G}4eNTAtL$$J$NH}KⅢVݬyA}h v r펍HoEyE©WԣX$;l7*‘|6}vK~i״*61FIhکcG[nd/arFI‘{εO\;9idȻ̶G|-"Euu4WRc3‘c\͋cHA.m'5H8ҶFTQ(T4cY]& | oNS ‰^GS}5(ZH+oݭ|m$q0㤽[Mǘ5 PJ㦿QE 1L"/* #Ȝ@G8ʜ!kBJGUI}A Pa\pTZ,@b*Ə!x|tT=h.)Bi QKp$^'\nRq)6_a B$䤧?7ᵬoUH"H@P *.4 7MA:-R$E9QIGsQv~tmEnRPx~n R8 ˔-j_b%-2EYX]O_WQ ΫhA;aJQ$+o Vf6qOjt:gmw}׸N ?f8aK)Uǵ`"$E؜|d(dN}8FJQR8['mZ\7Du.}+XoT_IB#%۩;ᨴccU@GVB P"0D8*3 @GJ,@ "75jXEFn|5ND8J?‘-/g> Ni8B *z{ptKm&U9gUD8ʙFP&A8ص}T v 0G8ʥ^.J&pT2@ zq%^!ĝ瑸 E( $ GJK5=(3!_.cvLCrf#UW]C'Ͷ.RyJ[d<Gq9 #Gǘ-C&ld[ d@W"e&ۀ 8_b@ Q4IhwHնh݂v}Ug>}3gоKTg*N*H`Jy6Q}j/"KON@#KBXV'} @@0D84@HGH:ḂQNp_ҠVu;G;۱M3_"iޒw?N- حlkMLCrf#UW]̢rF8uVzV$![C8JHG d@#e* P*. @ #a\p>V 8!@ I\e{e>D-'=T[c [ʂikw[mۧ fT* rr@#XP@%@8* !@Xm¸hu; kGb}T",Œ&Lӯ6E5Z>Q:5`E_iѨjoA~qQ-sW{R]$?,~.Q۴)*,ltnen1\ X#q0@x 0D8ʧ^GHX%@  0G8ʻ@L Ŵ6 @ ¸(L T"#MC"p @a@pı@ O O¸(zB#cɖ x@8wQ{@ w€(ﺞC1%pӎ Jw2M P8T"\6 ĊQB = x@8G?QK@@>J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%Z(ӦM#bzJ\ @ @ @ @@QtX8xm @()S6mڔ 3 @ w|2;}BM @N +>#CȌ̸ <!$QA DzDv/H(J @1'ƕG1H@`5<[@  \P!@ =Qi@ w N_P@$ƕG #Pc@ ث  $@#%i (GN@H0D8JDw@UNH#g@@8юZ @€(S!\@@W"q,@@&8dBu $@8Jb&@ `a@pNi@ %;i  @ ¸(I# T9#UB9J(G;jA Dzh>L krG @a\pı@< 5ցH(J @@ =Q;A"p1 DJDt' P8T9rv(@ M 0@@8ʵ> q% dBH&XH"$*m @ &G d$Q@A +ѝ4@L krG @a\pı@< 5ցH(J @@ =Q;A"p1 DJDt' P8T9rv(@ M 0@@8ʵ> q% dBH&XH"$*m @ &G d$Q@A +ѝ4@L krG @a\pı@< 5ցH(J @@ =Q;A"p1 DJDt' P8T9rv(@ M 0@@8ʵ> q% dBH&XH"$*m @ &G d$Q@A +ѝ4@4b6 @ba@pT e @4a\pTi0 DYFm @`? #5,X:([pwy֠ABuL޽{'v*>c-]wץk׮"&C=N>q}ك>#4ߖ2d~7*q(bi@(LxZje;wрϛ7?Kz{A2gKoOWsI'MTD8RE5".ER*JKƕGqA *EHK|.+yJ7ՉpF p '%;0;d>u=ǂpM8=cMfjXZ4cukOGeuA?|<[lmӪժ^;s>~^֩[ӎ޶_[icg-Y?/n[m֮S&FXwV~n?T~Y&^hWj6_5_;V5JYrۖҲQ]ե~Dnp7s~kݤulZ]p mhXZaQu O'BxmNk۬mnbYdKD)uo쏇5]7ifz-Zʳo~~Mj8SEƅҝ-v}cT%xckSS Xi/|1nzKY;wCiH_nڮkճ`rBw\-IkmuwS%|/kWhC2i8S.Y{`Zh+1XTwcz&j*!< ~F8ʃΦ9A@7^}U۷BhH/N+G}d\pOoG,3f^:DQvgmD83d   G:9Zrk;gu^g˼k>%wikS}M0GvӧNSIL##]zH2H=PyS-/q%Q{:CL@Ҫ=zΫCr M<&%\nf׸@E׃!ȅst"QuCP$~=2IG*G>}9m.~y 𽟧kP^>i3b|2zI(Z$S}HIiيUY{N>ϊmGR`c{dӭպvůw&,ZkXԳeb„?j𩗞´FM}P+_elޥ@<ԥk;ZWI8{3˲bԶVXX7FLm|j.N +.nu;侷g'֭_aL]{3v/-c{;fv~2o*=aBOdLrcp.~$3t .%6ZhN dWM12o5ltg6׽\6=]%VS>侟g>lݸvv3Is3c1}:fuV[s {~m$@a(Krk=OqWΗ# 6ؠ2U1Aaov?Wn]{^F:th">%¾ƣS*N  E‘iIъ\Q#i_v}w/0G-i <_7p [g?qh>)eE?f͚埰_LWbtv|'~Jcq-a\p@.tR(sp $"vm>DT? ,[um;pA8Z7;޷|-ֱZ5g?أMhnٹY$D3 ‘Ezvdk"lᄤNRwP@Zl=ql"(L3'%[̆8`pr.Ij ‘}:K'ݝأ:"6&:Jm߷p>DMOMwGҋĴm֭_f)H' JrtM 46qUeG/|1Ů} /trQݸ0&^7G1;$?8ԧg.ZNI;.#w*'}<餚kE쎙M\d%pr]TF~4#mJ&k"[q\M*WNr:qa}qv-lCj;F]NLߟl0MR,ߊ>cs ~* wQ.?:̖9ɰ㴩^mi'eYjm_z%TfWEg̘aGu>@"z7Lm*Zreܹ[󟢳  H‘$-S|u?_{еn8l&v)n@֟˺ Rhp$y\O_JxNZJOzԺuV5' J*$Q:@i }.]M7T{キO7K=pA8fh{nJ$Q&+K8hpruNT$@'=JvA?#R]FnjMv$Q $~F8UO@.b{~uٔrCQ4^j{ֿ*ҟ1Fӣ)Ғns1%9sr!_~)HjQdIzgM]{16lgSV-ꫯLnݤk=о__R͛7իoF BziΜ9eU:uCUԧDKPD8ʡΠ*@‘"AK/-g HE٧z*yg}֫W/^fܯٳgϴUp"(HTtCXohi_{챇[oH?N[_ +r GF5\czQ rGO8SѸ4= R͙}:6-FD2ˎ.n+R is K],_j]Rlw";gYhTHΔvԅuQHQg+uV!x'Dw]\4\ ISŐ+fNqO*YuiQJ}r5^ 'LRZkyIJ-uqTvO(HN5E9[u ;hO,Utckۋ.=Zu'+U-)RIJ{ָnbUrP>og"%Hl]Ϻ}F"4 T%UA8}w=fV;"NΗ/&6j?h k/13&B'~F8}WҀybnذ]q'vHJ@'."[]V:,/*z~}@*yv":hž|I,):wa;v)YE7Njv?Zl7TpcO3|M4i:… Ըqa,uʕer' d(pVs96rH_ RJ(?U$Iѣtd%)3ϴ}٧` GuSDQT$Si_K 1%+ޓ8uI@1> ,mJהbCwpi>03+)ZE{EY錝:mnu Z8:s|$KoQULj#]zS{u,6+ړS}g;qOKI7FL+K8hS#I}J*?j梜Ezyq  G{ 'a7pOCRtk;\tV~V3̢HG$h@_E :mҥ^֭լYoCy^{m/l6emDnm۶Rh&bF VxkРAAEPla vHS;n(n=F}!6L#I>.8KyYR__}q$7E^Z޽{d=4:c KTT8R:ԲڦSzue]^Qu_zIv ukW"ťǨ' gGq=dB@ъ&Nl ïtwJ7-`b׏݁X"""HJ(ݍt7go6\9f=F?8ثW0QPۤfR8 ! u@IDATݩ҆-Sr?Azw(Qd /A5?*@@T~OTP.(dI * JP@R]Q gQ#EQ,9uVZ?VORH,*5\vo+JMȚnHvV7^D: (`쯏() J*r%}Adeov}<|\: 7yHJp~Я qЅz?3fc4U)R"-H)b(_l ^_{{hoD-Xn2?%dl#B-}hNJWj]СC:2#ҸB.j*YX1P|7r ~_Zֺ["-Yf\Iȩ=z #7&B$ 1hyw//4wpJOh*ҽViU9H  KJ_SKSєhJ( QReznHH8zV60^zk_2ghG]b5=۶{M+۠RE3&L(cm#euH3BO:%A: RiR٘LeO$@G8B-T8rVxᚚҮR@UʼE[ʽ\ZOT}Hh?%8RP3["9[Ҩƫ(k9-i!;pJ;(0 I[zi[ovOFAJݻ.cj*AZ<9߼ysS~޺L$N^ pҡa_h#! Sp"y W82E P]bŊҢE 3;n^)MW$@$)\:8WlR}ِ ,XFJ*3"+&doDʓ'&;&'GOw:דƥ b '`. S8J͏<)SOaܦMϾ!ټ[9o;6+ 8P`3Z~}y7G$|!75iD(J/4sPٓlnR`@? $!bS%#X1ctMn..)~H\G nj ʡ dk=wע @۶mA yV{~()!+F'3ڲe?^GJ&?  IS1J Gk+ @p=z DJc]F.˜E/._|EQ8 ?OITZhW*T@cfnWNz7d*̑Sg@R-}lQR)gǪ65, Qz:)n^Yz.拉Mc$RU#O*(7H{6R&_h7ULơSN{9#  ZoU)c4hEyTDPJ#%:{^V2]Jʱ+&!lԳ#=YKVeWaiq鉫4S\˘^mD9#Wu7L0-H?TZŻTZżY3ʳШ9 0l.z @=DAA0>x}…c=4 "o@&Ol>JL\rzF*U*ytς>o Eܾ}sQ,pzNeʔ\.Dܽ{,X@G_ٳGic?R8rlװb$@$BlҤI](xh/SҧOEDڱ+u@)^7 ͛=! Zƌ=D bQjժzX mJ駟t(2dп}݈7 ÇjM{ +W,؄4oe̲N5J GN)֏H ( p ȑ#B ҿσf;s=%6CX(DA~ lV]~餀\.v[B$ \,S8'__VU:%@̞I^o_G*]~B bLUQ%@8=r^a4ӂJXcM~f DK8¶h2n-`. S\aZ+l*h=٪^ l>IAd*;ׂY2M |dmz~1M3^yqxw?*h-D w߾;@w,vhGf>U(S n86bQi 1~.HC`~J׭ViMĥ~#jY+=ѠTߥuw5jF02 AiQS6 Y.;W(%O!OT G NJ$Op Z@vK $@$ x8 @|pZ @"0J Gl+ "@R~I@`ݺu:(4@ZeHv}!PH@(9 G[-g2yh@ե9u_,nj^Ir dGOj'$_ *5Zf-橳ѻbwkIF[T$DA4HG ߪMC-VтR]u"kڸ@<{NGWKH_g /6"}#d"*B5w(GRZTgc{(KT"o#`UIG(6-C:F32K"W]Ҹ3bޏ?T"̶b~Թ*Y愨U,$@$`   P8rvv$@$@$̸Q">L$@'Hr $h%;ҥK#CdPQJn(YZ|cc*&Rv}c@)#v3*}x}["r6u#ŬQ\G8WY |N L _RWiﱓ2}.~FvʑY>Df\̦p2\mN9f$l#mɘ6l;tLޡls*i%OjnH̀‘+ HD  3f\I(:% y!j \rtۅ?Ηv{HTɱg=.Sj'ϴ#όpcܝc )ç_;x["VOҤJ5HH̀ ⣟XK  H$f\I(zm% y$z,% &@(/j_렌_Uv>.{+y2g_᜙R$g/ A~jJy"TL{FEZ\[;s';3c}}[sɒBdxl+dֽr9w&,_X # 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8#v +F$@$@$@$`G )4 p GֈHHWR8J#' <7E$>קl S8ru7q$@."@Eɦ Kq%#t(A$@1&Hsw$@%@ȱ]Ê 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8#v +F$@$@$@$`G )4 p GֈHHWR8J#' <7E$>קl S8ru7q$@."@Eɦ Kq%#t(A$@1&Hsw$@%@ȱ]Ê 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8#v +F$@$@$@$`G )4 p GֈHHWR8J#' <7E$>קl S8ru7q$@."@Eɦ Kq%#t(A$@1&Hsw$@%@ȱ]Ê 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8@̅#ǒ`HHHHHHHHHHHHHHHHHȘ1^ɓ|e[l7Bfi|O$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$_`d| 9X+" (lۜO$@$@$@$@~ ŋ g 8͛uex=9}š @0J>I#' <7E$>֍p侾eHHHH̀‘+"p! G.T6HH✀WR8dIHynIG‘㺄"   D )y$@$ f\IG @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJ‘C;"   '`p* 8#C$@$@$`ƕx, DBHq 7p^eHHHH̀‘;M#p GN6HH\A+); 9Gb;$p( GVHHHHS8é$@$4# WR8@$@$ G"uHH‘{m"   0z G.d6HU(;  p3pd#HH x9rH(9cX-    {f@OȞ P8rZ>$@$@$@f\I @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJ‘C;"   '`p* 8#C$@$@$`ƕx, DBHq 7p^eHHHH̀‘;M#p GN6HH\A+); 9Gb;$p( GVHHHHS8é$@$4# WR8@$@$ G"uHH‘{m"   0z G.d6HU(;  p3pd#HH x9rH(9cX-    {f@OȞ P8rZ>$@$@$@f\I @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJ‘C;"   '`p* 8#C$@$@$`ƕx, DBHq 7p^eHHHH̀‘;M#p GN6HH\A+); 9Gb;$p( GVHHHHS8é$@$4# WR8@$@$ G"uHH‘{m"   0z G.d6HU(;  p3pd#HH x9rH(9cX-    {f@OȞ P8rZ>$@$@$@f\I @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJQ9 Kfvc#HHHN )9X= # $@$@$@N#`ƕ3 G⣟XK '@(c GAq6 ĘS81xH"$@(Bp\HHH q%C Ά0&NHS8JgGӧOK ./'NzJu7W(Ο?/SeQ. G@*$@$@ EMhl#q%DuHRXnYF#uֽTՈ~y*Nn\M׮]+W q bŊ~R8 rR8 (NgG[Ih٫? z\UU%_ (pѓ?ekשJ1ݰOfv g DS8"Tn$0sLYl$_^ Gr/Bf"_|ժU2g=hܴiJ~Zj7w^>|,YDwB R^=}/p&t6HR={y{ҥʍ7(^{mH{eخKN6m**>b3~[2f(͚5;Ӯ]>޽[J.-+`܁ gqn{$ 3f|r9=N7\:G\эl 8gʛo_ nKڷo;+>S8 ]ђ|hi^L7(TJ҈z"eIFG٠mwD󯚖VoVQ-&pDC8uyJ*:WѷD\R&o5, Gg- 0z G|8R"[2}t4i's)ʕ?&dЈ#?DXB9r䐢E]w%n߿_z!ٱc^э vɣ>JHÑ[! D%Ȁd^:t/{MlgUV}\&%fKZ¶K,~~'4lP0GqmMiP"fn-ώ[++Sz-,۶rWw~[!Ξ iS777XXpd-H$̅a G)$pg7 tMZl)ٳgVg?~\^yAD3ҥmV)0;dӦMRLyǥXb2w\}Oa~Ϟ=C NQpF\HH?HƈJs}ʕudCf G8e۶mҢE ɖ-ZШQ#-I,ZH{9iqʕ GQ0>6P(+3p  p q;^qv2aF8 $'kƍ>H?K:u3j(0%&4H_Z N>P8 Qɕ|uf p |J)/ g]heF:ele!ыn>Sv:۩̘βoyrYbaӢr҅Rh ͒ $!`. S8JH @:Oe]q?Q2e$T^=vO<2d,x1ѣw "8$3HH @hhӦMH8㏥D2/F$2eymKpz!,$,̵+'OOcX3p=z 8ŋ^+ʗ//Ɏ"3‘S{"H Hy%zlwpcǎr=.P Gvɮ#'%X f$%^-ÅA;Q%7,[2IuHo߯ʜ^H:?ėj_{i Z\ÏT1o^-ט(/h֨=H+vFEArJɛ%޵|<#+U=ryLRUǂ:Q]D*;T(] ,Py̶/ϞQϳnϮ.ޯ{DffI(f^%<ҴtRү;T6?&ӤZEx0wuAllCA?sDGc*)Ϛwϡ[JİҕJ iɤ/x*&c69Ѩ?6ˇ3WZPKS#ms ү}ֽK;tެ bV %[iX9T: 爵oQuvP d9D+;/kO8|[KKS_@7?*+ߙ=GOImj? saQ\t+&Mp믿ܹ#.tsw)rFD(p n!{֭'ZECzƍ{P8" '<ь >}-YFyiҤ^QQ"hrTM6J5y;nWXѾ`WR8bI={tzWøh+YY d{Wo>Q6kxpQXw=.uqPvVϐn 4e2u.=ۚz>oWEeɷ@Ni[IZ} r3WV:T.d𷒥T4fyR_rM oys_ҥjqyf~5uX0 Zk^/zOYSaTO dѶZ^|d Z w|[3yZ (g#/&-^|Km@IDAT:ܺOnfYPw:֖_ԛ}Un\*u^!fuOZLnRk9f#]4wh]1rYiYryuUcF%e[ds|J4߽-IwIW|Ǒo:ϥO8B-^lS5 Gľc1FSlYYR qd}f+z5,_^:^ KF\ptɺ;N0tM7 ."ҍ] hRR%Ap "܈+ro3gJf9|g5HHppt #uia G@FHVr#xkETI+\C.\(8wYRJoo̸QK$p/oQ[pi yR "@"4i_t-@m֫:ڰ[+x*QPэfe Km63 * *ʸ{ZJVK$h GMS&ޡDE#"@@ti1#,zl=tRu i:eӓsQۛz_k>TmK4)Di;hƣ*JқIg'{A6y?$X,Sx2@Dڰ﨎P[`R5CG}=}^B1) vבZCdt*Q훶pVUuڸ^W*w69tPQoLy 7E PB0`@q^( D 9ҏ9rDK:THC, XV-?&NSvUnv] G?.]sO\gΜY4h1213=c" D @(zm$@ID@(PJQq-Z։(Dmkb76L+%WMT i :# :)[#+嬯y WlfĦF͗*Ĕ>Q]A'LAfVQ[6I"&! Զ): Qbw-Sly1'(KvZv0`>J#AHI+7oE)3/Zќ{_cv5dW{R1tߠd>lO`'H3dm*0lMfRRI`_/FǬep#dP oz )2Ԉm嵔9K"Uj5'|NEAJgF,(UUt$U:U?WBp>kRr Jhk$m@DJOn7pߌLiH/N-QScY =xRγloIJ\ j@Fw7ߔ>H{"Ee˖r7V޽{}w2rH7oڵKͫo!Tq֬YLncܸq)S&3o~O`ZΟ?/g֩E2d ݻ>.r}3ھ}lٲEۧo-[V+B޽{H#*N֧D8=#'F$@G:HګWpw˶m8Z jDA4iXg+Wc=&5jԐ^{M#\чLI0i+!ÕW^I0w\dԪՅkKHK ]v)&5J G} $* Gl7 $.<0ydiذ<3Ay睲c&B(H%pdJH Vi0G/#aZgTYa}h8HUk G B]UEQB"Dbk]Ep]YF+5|"<;n8Ip` E=rUH]eW[+nг)RQ:UoSiSE =y%Z4#_ӑgIE% !/{xK][ծ79eURI|F-AXORF P @ @`J)_FEB',c^/ k+'W2^a P섣T &%77J`-Ut3|oq켬R5ULrpMu?3j_u9'_0C<H\u]*U_~)_]"s(S>(qPD++&Zn&b{v,/gϞO?TGF M^Ϳ[ӧM,YhI Q]GHHURȋu{r%0 GG ? $@$C=$k׮/R ݋/+VԫW/ө< 7nԩ̘&\hРA2zh G_}@8`Ժuk-K;VE/q%8DVH nP8bEID瞓EI6mc@i׬Y#:u<(oQKI:+?R^ٕkaDbB/Ju@WT[tH(]S‘cx%\OsW ɑ1gUe,l?t\n6SOBDD1kF+UYT>'"P S5~ @Iŀz;UʻUd*SO` JZja ѪP'JeW&)L\SuAF xj!EbsQ 89z|tZEp#U"e#ߢџpWYwc߿J.1-{ы*d"W3eѣ?-E*z=ٲMHa3dhVI DAQQC_#"8 0l.λul 8˖-C 5k% &nx;wNu&sO5(KnăH(LvԩSW_u+ϗzJ4"F:0yUWy"@B*:D;B .]H7{z 6LWo?IBMgs+#'@$@!p(sttBBDC7Nz])ZIS|o<#z "(8Pצ~{R ~;A`Rkjƕ⡷XG x'@({'>(֭MdR0rcS8 5 A LEE(- JE8Bԑ@jvzH>uFNG6LHÅ2Z@V1*!"*+s׳*Ky#\EQ2r'S:_) ]3LdJ%y1h G۔ ?7BQ L8rꬴ|m!j>OTC N=?B$_G[Vj+R!mҷ%zZEp1˷ӖjF( \_*YE3ZlE7~niUBXh`د]#DIB5m{Km2Y5._Le+Py U:D`h5 h 0LV8M7ݤS{HBrСC.ܰ˝;edwyG~ }Y/xQgziӦ^aҥ裏"d'A2z7$GO?%ن F8B!CY˒%K"De&YI )RpӳZjR~}f=:VHI (XC飣'V\YGK |$qud>LGm4 G8/Nf Gs D9sFF%kֲit9Řp‚s<[3po= # Gk3 @r@믿}rԓO>DTQ<oQK$0I" s4:5o 3#\䟇CL)e6Y}};vZ MH8zM uVA$g2…]/ GTJhfldJMRU`/'=j4 u#tz(/& pʛJܪ.S=q,UT+ WU1ULsJl vxVy1keLTc*aT>KqÙl#G]<4v k`"ye]]%&%_[HXC-! b-fٽJ6_Eh2Q:W-SQяXH◀0L(~5/xp O:H3 Du[M{HMP>b)J +o?%K L2:zu=܌CTiXJpMPx DRjUx{O(z% gH >c9MJj߿N+FܹSP6uƷ~;I]vii [MtDk]?w-j]${, 4'NB9RnԿ#VB7NJ*ඝ6ӌ+)9gX 7p^eH5ҙ3gJ'NM}-\։3)Dv#0_ T&%H:ς-Uʬ斔YBz*%w/k&}R_ZYRZhʛ%Q2M#T6H(`i 74xJҾROp,Y *ᤩ<;΃Qy5CX=;{ny%zUʭJ[M]1JYYR{lM[Ky R*mb9C:peVQ&"C~l.[?/~s_2nvAJ| Td+=vJ:^_t$^Q7%Z v"T%O(`@+x'? #`. S8c@߾}7Ȑ믿m#G{].܉6 モy/fۈl<"kf7p L*VL!թSdz?1Q*UaCoG Qy*!}B*;XM?D<+(9oX3 G)!|/kPM>H:bĈ6 #} ~ P R.~mٲE3(Viaz7e!huٓ262ߌ+)9GX 7peH 4HF/KvxA2ڳg~xCQJ?-O],kNB`:vG p V㎺eSbI" ҼS%mC:}6Mס-vDE  2C[M)K4neQi  N8D|-!TE%ifݭMégp-1b 5aՊkij$ $XSӱ.S#s`6!zᔇU'D^8ݕ^zcU/T*6H?~0c|lsGֶ "P[q e[{ "^D,}q '`. {Hb$1DAB85-&=zGHԠA߄XBA8x+X_ p6 Nk0 3k,Y`TPArEğ@ !M?>bG+VHLttC6+߂"ɓ'}gϐP>dqlMNP]R%Y|jds]>|NvT̸Q<J$(kϱ$@WlYyn:u$ONnܸe:QIhg"٭\WwLpR6_$i֬‘B iJQyiҧ-/LXwH8^YUR֪xG3=Cg#)+g~E ʯvhIHz$9y|ԅoBRP@;}sP9MKK6*UuD <{C8_Dm[I4rαekF:~tAl762Y6D@EJ %}Zu[SAZP FՋnX^G-guƯ.}LNfݖsʌ'LϯfփǥGM%O5(g]-8l'd,DX^Eʩ"_ saQ|k ěsɻ+ի{v@ T O`@m " 4j|}v~iBZ(P%C2͚!$NZGKfݧ}JGCڰazZs{ GN!֏H@#\ܿdǎ?.+~" *DH ~OGunF?Xc3gv̊H˶i& #,=:uJGmkmO8Ռ+)9wX' pe{H? ʕ+Pzeҥmq)D9G9"v/TZpJRRuza 55q2o [^ $)G_Ao8>z]%-KE DA.'Ti׺k(]xA.z>ADd㳸#Rm!ʻH"=fJMJMZһ~ZW&? GN( زnj!% GLZScLlYw'ͭٽ1H &mwTfN?xەe+C; UR!75N" s!yp752RAJ$ֱ@˅DPeH.-saѥ=i5ޤ3;tC~zq7ڂE*Odȑ^ի'4n\ t3#-t\T K"+Sִ:RM%AKkY\=ɟdYb'!jּ{%֌:TE9 gR͡ǩ=vP ҹj1RBC5 dD $PDB!}lzeܵ͒z֋*͝]*PlϛU4%jV N8Z1a qY<$wA 2 @|0)WM`i-[H֬Yf͚Lxg rG2>}~Jъ(XA_|Q0B5j{lݺU~mkԩZ\ڸTRЩT8\'ZX)(<є3gN DiJݽnjSQ8rJO$@$J ε(xȇ{3d!;vL^.\O^3p~]vNOm ҲRzꥣ.e! Xƌͤ^)iMvYdJ뮐1vX:t^0 7ЌBڐB3fڵ́ q7Be]Ƴߘq%#wG$ \эl @YG7c&p\ ;wΎtU"G R鵮ȟ]\ %[Z)n #H!v9)UԕB/>ZzkQ.A@ $a>"@P >ꂴpHEAэFD LE zoJ-z?5$T,lv!?lE F‘Ux\3<[ p<؃y=_NKtnˊm2nv= f>ZV0sʄT{%sg+b{[IK6`'PѝnV) eɑY,/%8!,%StV\αeov|H%%uP(Y;cٺy(BY2e;=f2G/`ҬL%Nlj?6\Aym5PIϐ oeʜ{ͩiO#,O}q o:/J3quJ0%Gy:DjUrm`>{YM$KDb!/p- 9LB xPB ?po߾nժUKɑoz?ɸ駟g9g n=CҪU+3)Iө@̂xph[gꆿy 4ËHkֿ.%}HR L0+o ?#e+Exb-GG\P,!0}s\uZ׶m]ߌ^{M GTxj^g]#Jv-L:"~!5~{IkA0"z$@$8% @ ܹS?Ԁ4Ĉ$u=EtuDZ5!Coj`p`Jpџj1IZ%D G 4˕tMIFBӻkc.NH8zR+Y#-AXyec:* "@isE$M U&!3h~IRNoQmBDr9pdaۈTʮ@aRQ)L*J2TSRٳ*aWdV,ģ\J`B8/軥c'a[Q BJFUb9z;~JK*޲|x)[N]>MޤtyE L} 7A\7 QՓ[;HPѶ G>@:oih>4;Sz;77JR3%GyjtZ-6/-nV F97ה+5H  XUW@Gd#DAt Қ!bR$ɓzjM/S>n-;vʕ+K Ns+ƱkP8 FIHbE" "\t/V "R74&zYR A@ӢW$p$S 鬐2 IH8ܭTB dvR9ϣM+MCpL8LwxET;:V)$b2gӞ#Xi.[TTaJذl;t#o` GVE\[ '6 w/+5 疗CU BIQvE[{ +O:*Yz\f~ ز)GBD TB=}1iN`Tr7f+WIwt6ïpdT8NFf^ %5UX%"h5{5|FxhifD*5"(Y#|%8/֒H(   p3p䴞a}HH‘{m" tDR<8i.Tkw} M(YiؼwpCT$D$y\@$Sѽ͓5*:%6N)^ i Nb4TGjɭWADIB⹲h)&ضRb>_V"Uqɤ~wYpw•# BA:B'ABphS;dRR+FH Vw2O@3S:-AvA:;D󃒈Bn߯^fSǬu|JE=BiR)-K$zfI уiݎ9F!A̩ )U 3!jҡgxGɧ:s;$@0)ƋK &@RO$@$@$K+)g  P< %.C$`%AF.]:INU6Q^ppmU*)H:vR4[ :i&$A("gQLz"W4IpzUrW/%5Kuuthl۩1l()6,j^$@$@0z G*$@$p P8йK   ̸Q@LI$@$#~p2 @p,!Qϯfjcj!U@)^N͍-b(EvJv儊NdhPn,37g-ѺpʞɍdHH a ==p 8#@(:%   `ƕD H<TnH . P8 mnᨍp7גzqYynwpNUiH*(""ȺXAAQ{mUk_־(EDQ UiR^|sfϘ$d=Gg&L9GlH۾}yjrom#F*A{.v:ڷUk!A];'p<Ȍ8s@(W8*{")uO C p>|D+-h-]W*KnA`~Z lL1 &*5dv_+1S~d'x1iզRd j y{lT,SJ>4FSΊO Ϙ## pJM JG @ pɾc.@(K)N&/XrKJ= rm΍k}ڙБ=_ eZsʷ&ihR% UgڴV@;'pG=/@C@&`ǕV9ڋ!uč: (~G_'Z0}Zh"Yv f5ڱ3.,ML1tҡAu'5LR~_#ѝuxssV&̐ٹZԮ"'uKT.Q@ Lv@O(LU @7츒U \G\CL 8ʔ4A@H8J 'A.@(Ĝ@JG ±; : p3@@ Tv@O(Te t@G츒Y \G/C 85'B@H8J&@/@(Ɯ@JG7 @  @#   zG*E, pŧ 8*`Ǖ-B:xhdLQƨ9  @*쀞Q*49 ~G7   W8J̍@rL@rL@@8 Uh,d,.>]G@Q;$phh  D@ c2F͉@@R!`R1@ 8J1g@@츒Qbn #ug #@g  @쀞QFc@ eq:  q%#G D@8^  p1jN  ;'p M_Q9 $&`Ǖsco@#<@G<@@B%`BU6Y,@(O@pT+ 9Z 8.u<ȘQs"@@T=Thr @ o@@ 1;$p{#pᙀ8♀  *;'pXbGY\| v\I,@q#!@e! B Bc  p~c΀  q%@ G@ G@@P =P"@ 8u@JGf! pq@42&@(cԜ@@ v@O(Hs@HL+ % 9\Gx& 9x&  J l4XQ# ਀W8r@4 p\y 1GD  zG @ߘ3  @bv\I(17F:3 p3@@ Tv@O(Te t@G츒Y \G/C 85'B@H8J&@/@(Ɯ@JG7 @  @#   zG*E, pŧ 8*`Ǖ-B:xhdLQƨ9  @*쀞Q*49 ~G7   W8J̍@rL@rL@@8 Uh,d,.>]G@Q;$phh  D@ c2F͉@@R!`R1@ 8J1g@@츒Qbn #ug #@g  @쀞QFc@ eq:  q%#G D@8^  p1jN  ;'p M_Q9 $&`Ǖsco@#<@G<@@B%`BU6Y,@(O@pT+ 9Z 8.u<ȘQs"@@T=Thr @ o@@ 1;$p{#pᙀ8♀  *;'pXbGY\| v\I,@q#!@e! B Bc  p~c΀  q%@ G@ G #@@@@@@@@@ ˗7߼yjף=ϟG"'@@@@@@@@@ \6`d@xH:p{;6#  Pܹs}{wp ;i p&@lJ^d3# pI΍G!vIGՖ! ^ =#/K@CG.! r;$pB|@@GԂ  @W8 בdx (@Ǫ'@@c;'pqx%@ȫr@JG^N :qrN 8r04 @@ 8í k\A@;$ps@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$pE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGf! DzG}pMk=  `Ǖx. $#u$5> 8 @X y\d^ 8t@/츒 @d"- B@.`p+ #*B{@@+ \@HFH2j<| pcU  ీ8t  pU9  ^q%#/I'@ p89'DG9Z  ] EV@5GU  W8⹀ בdx (@Ǫ'@@c;'pqx%@ȫr@JG^N :qrN 8r04 @@ 8í k\A@;$ps@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$ @IDATpE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGq 쑙/mqkfNY@@쀞ㅢy T@@츒k= @8N/@(18@܍ dX e!I 8J! @츒Qڈ90 K@ Gqn@@ v@O(HRQp< @&`ǕF́@x]^: 8E(w# zGt @a  6;$p6bx-u9H@Q,GqBz[?̓Og,=kU+Pp- @(8*"!,@ਘ @@W8*@  E:R$vA p?*/~5K7!ϞrH¥+'G@v@Oਈ`@o.KNÑv%J_<>3#K@vK?} Rd:>2q%'aOf߿E׵Vڃ:}̙2m4>}/_^ڵk'۷ "Q(GqBzK!rl@,zGYVx[&L?P4lg}2Ҿ^{M~W7mUVJz]mcƌR~}9sC-t_H\Qf<@ 'NoǎQ\L۷t萙п yW몝;wJVk׮r''c{oٲw}I'*7q%#O L@f̘!~?z蔄ntu$ŠOCv7@5py%qW0@(N .|\jKHI@L eYn \Ru֙ ty7vZ9ꨣΒO?=~v͛7Xt˥^jk C 얀67o^ctIx'xBF{(5R߈MCG7|TX1|3rHyGsw}wRJ?MA;$pTІ[@4i\{R\93{Gc}ݡ rUYCزe 6L;;W^c[~+!P~qȐ!E#ptnB>.% Y&`tn;v#СCͧnjԨ!ׯzH>#s?,;vL[;WZ%:ݺ)wF=κtGK/N**W݊=@#pꩧʂ  Nʖ-+{͛+? W\q9ADUB;˗K=o/yW^-rhpF_F(>W8o @"7Lȏ?x" վ\GBU.@5|AOݺuӎW^yE۞yi֬Y8"p(wq-!-'F2;'pe&puיٍ9~xvgyYL?wkʕ+￟,\мѷ~?>sR" z @lgI{(Vt]jK.rUO7뫧~Z:޹뮻L .swGҥKECXjFæM̷ fǕZAڍ 蒡4:;p.#M^@g9sY]?]s5W_~/8Sl MmY^(˔U+HŲ (ۨT)W>e YetmZGʕ.ini*`ۼ]jV,+ChY퐟k66Kr~p΍kI e 7 R>8O|mغ]F-Y-[wk׭&ԫ&u*7G_ϪM[ejО-U}Tܹ|y5y*ҮA iTlݰjEi^JGkKb0w:-kYGkp_*u`ѢNU)SD#YӲJrLVxzZV/]-o=kVuJ?)fl-U -v|SA*8FzՋdoW@>{d)5k̟?_D=qمy睨tj?3FRAਠ dVΝ;_~fnISAg?Y>'6}t 7xr!!6n(ol۶ʹS8p=/9/-Վ+ K"@ ␘K4ti/|f"/Hn|g|<}Ow.o7s_ΔOg,wn>uԐҖ;Lieb>nS"EϿِOAʍ~#cf.ʿT-_F.Vvڳ}%65oN2'{5=|''\w ۖ kȳbZj''췗\XLojV{*KYe|<[-G~ao-+PwrsCi^(+Cb<9az.JAH٫Eq @v =~{w '~?T*[ĉE?E4`+g%pʂEQ" Eɐ!CLu]j&.P^=1bDG!AHgF>|ԭ[wG&LŋOXoذAt I5m4_~9I'$DJ(a 蛹',vݝHgO_rLI+V޽{ׯ_//y涰ώ+ \/.,Yj;w]\GvW#වSN*ߌQ &vX]QdC讏Sʞ2`/GJo~ :KoEn6p &Bq:R=g{l:kXYz}nT=l_)&]?ʈݺ&c~lHԙtUMC34Vf! vv%$.fѐUJ<rR:˾閮Z0ӽuϝyJoMۯ3 -SfQ4.fVP M LTOZ V<,vr>a =SaYt% J}~?3۔} _@[ e޻!oz뭦1=PZ?cg8ODnfOG{;+9#v7pˤ;V:t ʕYtj `Bfn|"VyŊRR%^mٲef_ AgY O6GamE# ڵ+O'N8A:무/apETCѧr,]ܮ3LG6 /龺iH_ovn.SOIϞ=uxDߺu̧!W8 qi:8)p%uU+W4׎5kJÆ I&fdhב,뮻Lz1o;OtѰmT,GCJ^tiQ@D)˘u/ Dn6p  *ߒa^ 7KU@c< rgT[ҥc8)EIg Y9| fQp c`KY鿯S~+% @q/g8eN2Թrq׼9+TwХt¶t+}<<t4Ȧ58˪"Rm(3;Pc:[qO~l~|̾ReU]ةfIʻ.K<hmm6=Fa_cї]Rq:%B-ҴsZwy(-G/GkR`AgS:}8tٻ¶[Fޛ̾Y} ۍ@L eYSvԍQ3ƙd}:o^r9B&L0|~y?r-2zhLS_~Ҳe·4Po[:x`Vi.sI*Urg)El?蠃ll \*_|maQX*E;@lpF6lY3#"]ΖԶm[s~f֧}0κx衇ʏ?hH:S}0|oǕP-ڈakWN3.ƍMpuڴi:߿!8ב0V6#9̙3on8Sl,•re03bov씞-65QfuAkߑj U)W&oϙ G @D{#M/8ٽud&9ϏSWϙ9w}3fLhGY$&mW\q|wfݻewG%4j[:H1n83.)SNy츒QE@[OoQfp\G\ B .L:;襗^Q)S4RӝgC3IU?A0'L=vyLC\tx% G'tn.Wcќ,o8OL4IJ|hX{ fiQ8C|`C#fojUWc̜+ptF0 `6:ozOxr}G-Aqhs1?˓} ŚJ7yrp)][e8?UNf/Krgf8~F[ ]gzE6T<ҢE g >Ko.b*F~љJ{wܯvI5Ξwy, >ysYMCqAg4%՚7onf¼8 sh; *Wlٙ.O˼G۷7F6̄uxb"N=k\:,pUW% _fQqݴiI$ѥ4 پ+fZ]M,xhncjnWrNosy^߇@O e_q kmȑ#]vfjٍEEzkG "T~ Kגjzã1vGSJS{ޞHHCS I*rSo"GoWiT-lz 8+]a?@쀞Ek ZL;|C FӧO7o'#]v5ݎ :Jٍthۂ D N8A:tmר.)ҳg<$8#o..YDڶm+J6GaEO_6˭6h@x.X4i :Ygٝ;)Q~}yta3mMhԨYJ6~Em۶m2|p|PuV# ?S2`ua?;$pB5hd~Fgi9p ]h0x饗֭[e$ ;><#d˖-rM78Sl -9>Q$gZvOL8U9YdK$p  괗\sؾQ_cK6]L="{֬l"pt{{StfX[ޖMv-pTD x5Х#/,ɝ67Ѫ[寏gp=`o"GO %6Y#3i<2vԨPք ݑ;@J eUl1 'S|Uy2}Ic츒Q9> PP@˗]vC#5kv#nԁV ઀~@?Ըz(x͚͌5lPfiJ?QE[@40KK4Tl21c, ˋn7o9O6zY$^$'t1oiRZDM*]7֮]+7KF.!۾}{3f2o7v\IB<d/4*`ٳ͸^aܸj <2biӦ1>#W\)w/^F~7ǜڿ+;4˽{?I3/BsUsX0бB퇽MiKA@Hq"DG;7aN~v TlirxW,reߎf52p7ߤhHv<\$t)g5'{?_@[ egF@8l0xgg}((`w!ƍ33]wT^=)~Gّt6#fZ: Fa"*C-?@e;#O{mO߀ٳg}tGK7&L }x74oBFn8=nj?8r*  裳7c~j hk1 ps͹4=cf||RsFGq!pKW=TtԨXSJ˫=X.l]4< w3O"#}虋̒\K" tlTSAa]JK+piM`VUGA5saF]LA t&G,Mo6zM~ 촗 ]Ɨ2% "Z)8ޞzAʈo~ׂ.HEvі.K&pՙ8Sy,^1|z.A f>Iﳁ#-k7 ˃c~]:N[`.Mk٘4ņ @8T{@]Gֆ! v\I([!j*5j Ͼy [N;PVO\G"+ K@gݦMfGu.H7ngkJ{y"= l/}= y|4iQj0P'i۠zԥ4r拟ט}_>"սɳ|`yM,6傥*Na#1xPJc3$m bY4tT{'mCŲ~W˷A&ڒjکi"}.Qg7 ,D ȥy4 猶IW4wLHɓN4lnz&^=X!CY7VowG:aGfjuL8xՀb eߜ@ uv@O(u Htrl@HF+ %c@@ry&8r'FKjХnGMjT79,麓Q^YGy= @ ;쀞Qvԛ^"@@츒o? @fdƙ pto. Xe\8b\FLnk>ez4VQKjW*G.=w8J(?!d8ʎzKא  W8Ȍב8sp_Q8ںc\I2i\KI{Ȇso#nu~L( C(/8 v@O(;M/@ _Cz ov\Iȷ@ 3\G2Y@}G(#-yjyYv,_YvԪXNj5^Qi,WH[bDR'<喣NjݙfxݾcI3Iz<7 mgNƹR%Jt@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,Ϙ @@@@@@@@ʗ/oyf{̟?x;EoOy#        @lzt^z  *`l֬Yp ;3Ԅ  v\l&@ 9#ɹ(O`ڵS-=B@zG^N!<,*]B@ v\I(䅤 @1 p)&xN 8r$4@@ 8} ;ܩ-A@;$p3@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$pE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGf! DzG}pMk=  `Ǖx. $#u$5> 8 @X y\d^ 8t@/츒 @d"- B@.`p+ #*B{@@+ \@HFH2j<| pcU  ీ8t  pU9  ^q%#/I'@ p89'DŠ@IDATG9Z  ] EV@5GU  W8⹀ בdx (@Ǫ'@@c;'pqx%@ȫr@JG^N :qrN 8r04 @@ 8í k\A@;$ps@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$pE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGf! DzG}pMk=  `Ǖx. $#u$5> 8 @X y\d^ 8t@/츒 @d"OGfn;bDfNY@@쀞ㅢy T@@츒k= @8N/@(18@܍ dX e!I 8J! @츒Qڈ90 K@ Gqn@@ v@O(HRQp< @&`ǕF́@x]^: 8E(w# zGt @a  6;$p6bx-u9H@Q,GqBz;S]֨$O¥+'G  6B v@O( H4rH@-;$p[<Z#Y[z:#"~wsmj݋S.X!89]9izGiO`׮]uV)WEvٱc,Y2 sNӖgT#GYZx8.u>q+ͳJG) @ٳg̙3VZҭ[#amFx5k̘1.ԿmkQGqWn.%$,xN =#Bc<7nf/Q5矗ʕ++Vȳ>k>˔)#7~ɀD>6w,_\.}h[ڴi#ݻw9c_a?W##F/K.֭[ ,kThl߾]M&SL1[V^m^sip;m„ O?_ի˓O>)*UJ?Ǝ+}xbAo-O=w_;Cx#V K@_}_M?y1S0@(N . \jKH˙TZZ',XZ-w] =#jKG`ӦMrnPT)C}UVr:S7|#7xlٲEJ.mFz<}O?ӢE yB iʕ+.Esv:snGu\qqHL1 _Gy8@:M/^lZZAҗ^zi t} lȑ#m޼Yx?~9YTVMZl)w^"ϝq%l>}FL cǎ=\ A,7|fٲe堃;&Md~z駛KQ8һ] Ԗ3f?:~:eXܙO.MW^r-=ᦳ:KΝkBNږMĉͧ'Dz)RiRqBw8'G^(zM{MH_+V8D_ s۸qq&316jHz-sDG ,[oՄ5d4h ӧT߹RC;$pT-An˨uE>f8J@ <#7tt5OOhkһ+zV4VHUuF]rVPXd p 8rΜ9fo]j׮&7Ĭum:!XF;*nJ^Bc  |%Vҥ&~jTg?TgϞ-'pA7ImT(G?,Z%Vm%6K%~ RL?ȷoP]+m~\Zo.5)J35•I, iYkRliW۰T+_zî` R.h~k}6ǙlL_VifiUԮT.1ha՛ɴݳ5miPt 튷biⵛdϚMҰZE#x=v}Nj֖֠,i [iLӼVE֦Y^D[RBɷ%r|5?n߹K5iD9{n|o PmGvg.wIul&mڶ =}EAb| ו>7 fwTy'hpSzJ +6|>'tj&v=V2mЯ?-Y-5m;;?Kss>ۈz!5?iXG}\GyDMmP?C"7ϳh>4\vKdy#'M!f4sCо^{ד~&y^R9"NFzYG y8V1|EGaG>V>eJ@(:tu1]n,M}͛r:UzM)?#d׮]f?]2$Ixᇋ~uM>n8ݻteSq?,a k^+ 6LzY6뮻!;s8]NKW_%>htC=,f1ydkLM}J^r kA矗W_}QG@ %:3O>ifQХnm[z 2رcݜQdChmAD(eN/iR f-r2y%x]gshP4@3ٌlc/-7\̌T^t$IgЎ3ٜ3;:L`V |tDn6p B(/%mK0R`6eABItӰ?O͆ 44|ڄ^48w0#`!" MiC7 :+eZPS%h_J ЬF`V8z4,IצLcnE3Ii͞;Gٚ򬗿0ˊz43!i|`]==A3Lz0>g33,8sَ~ZJ?UnFgѠ^2Sd43t, h'ڒuLlQ0U`ja0խMP2mǴKs~W}8ڝᰎr/ Ep]Ud͖m2dL72}_vI BLw!d7;ߛ?]M3<=NZh5!<7Ѓ  Q2X΢6Ig N<<}5ۜͳwf;iI^tPke8/_]a?~ `2 K֎|ZQx1Q˓w}vb6CJ+5Ԣ M6fɔmI1 p{'E $# ID9#(b@sΞg8sx@ *&P@[PklNSL&tM? {R.xn_(bdj~ƹ*&Md${6}=?KL37?qGwsiؼpԲцs)ie[~?<;>-_.'Y6E&55(#>|Q!f>-\[~z@% \ѧu:$/)=#6 Pj(m +7SeJ8hTpMuo:AQ[ %e+1L1THNdkܣٷMC"ElcWmѠN:=ȶuv%t(P0J7/ G e<0=~XjZBǏhL}:G$7IQCZkIn/EQʷ^i"rUp{HZիjt>]$T,ҬI;׾Ê^37K"%鵧o-i .V} P ̞=ۜuY@G߫W^1wq}衇RWWG^jѢjfΜi$)dϞ=!<.ꀔAip}Mwn_̾UEby%QbB &p@zW_u)msrqǙs16uGc褚Sw3:nQ$l3]W/]/iYiR {/+2K6UT#I O a֯Gvz @Mwߥj/hN8QkL.yq{6֦pQ (\WUϰɟu nسVӘm&,d2Iel '-`4uw癡gGS-Q\?FsMϊ/%۶%Nh7KV= Z8g!]N2zlPPCQZ,r?:6Y6Kl5¶6ϦS) +J8p$zрhfaEcq=KgI6Ҽf2%EڇOZ!is% +[J`f)=/Yqm)I24ͧ ۆe(aBq"tM楗^2mڴqi3,Y.JIt?ۛo1m#m;|pEǯ 2=u]}jժ|X~A3fL0~}6u X_jх<^qFn~3gFe˖9~(uy@$#{^m:?_\!tG8L֭]Zv9+(]iH0kŋ}'?F+<B8Gǖ!$p饗ӧۜyi;#t@p iǕGFĒ,-BϦQJKXgX# B&?vT/A(LKVJ*g%_l/ue6 ʃGt)Edl'w]7\a}}) eO+)*{7{nHOSrM659i潅KRޯ9wEK]JTkٶ{5EV:Ŧ:r-T3ݦS$ { b+4&`'v.]b}}}gXw>^*%e{AHѷ$Em¾}sSG ^az}S:ǡfmAET^\5' ixb(Rd3E ">ΉmFw c$a(#H&|&=z0H/{׬ZzOҎhԢQY^usWGX?)SE].*5.8KuK}\HQ~aӱcGwץR+W ׿G:2 @a7nu3τԩS_$+G?͇SǤH]vuq:ϑ 8Rz)VNQ+Ut$a[KV+>4nlVei"oJ:TaGUxp94@ ga>ssc=6tI UI+GF#0&JpP+IDE8ڱYskߝ3 _ڦ?U[Q T? 3JQ%}˭F֦aQ6H }-NCVh%lq=CQiD ߗ G`4[2ubH1(=:l*O%+Yc˶o~ xpNi>4%!ھkK׶+^|dCJ%嫥ˍRgm75ZSi[\QQQhc銄960wt(K'$IgGl䲨t N/i&1-uMm)))G̱=K nc0Q!6XQ:utC7 ON[*(Ov<[pa@ A8zh3|f+CCj"vjBwj 3Hi~4_~7?^`fب:W MbV*WTW^6آAM#U G'wsk̜s]?Qi#̏RVHzzjTIH9ߚ^zh$*Ta%۶5HsTFXJVKhI"Hi6m-tV*iX{Ȫ|3mPQF( b?Gȍ/p$N.-lf͢`;(_G~A(  .4;XUϚC޻;uu=/p/^HzB|Wy7u;],=zQW㒖BMVZ~ڝ'6l|FⶢboKWuޭJ:TAGUpP9$@ -:aӳgO>Ƨz7o;>ꨣm2 K!G6s)F I)vn_lT_{w0=[6.A Gwʜ[S,{_:CrRI8ihժ^袾d$t`vۢQjEu5yVߊAF6ж鄣i~*]ӞRȧhR?1A9Av$d#Bi%4 PmfjuIJG>;Ljw#4EE VFzYF7 Ҥ^ !Ycפ6yԽL *A+(S^Ǔ*3~~T^VZ%6=VolKπ\m.z]'Dko#մE8I8z|'aSiSSC?0ĤHO*WQ`=<@!? #hsE7cƌq". GC)9RUW]etBkbVt?h ֣a"M6%咎tn(>:JTXPVHӔ*Ew7)Ś.)Q/B@ {^$*rn7LJ6t;䪔G81k."HqI27|SF ӢE wcƌO")y*:TW"%i+ TGI9 no ]dE?m2 K!GB  mDԢh'*T6Ҷe)M5!o%(Ҽ.أ٧MS]:pnE8R-hVH˞6X\lwo;]޳Q\*Әi#R]6"U6Q8<‘Tqs@"ݻV9FĪu"? Y, P-DLRe)YN8cC$t͞2G&x Loۻ6s9f7t{D0pTFc5*EzK4/E4OW^%$xfER7Rrݡs'tRק>Wz6EQZ1-פ|}};Jw1W>Rq%kAuVlƏo>^ϞJQ}I8 P(;裏6 @*J!;uT'0u(OYKy(yMrQk#F0}qT?D8Ak@!pTeJI'0kٲ;"o}5+W4]tڵk(p93#u"ǭ}w)1?bm9wɥEZ)N9=~lPWӴ^fU^H|d.s[;l+66{jf/|x$)%&a)TOuՅ~s߂땢D&5w\ .^I] PJz==JL_:Wr-M7hҠ'[y#Fq:R`tE:?z뭃xF|H$E"n{1~MRJ$}J(#G!2el];nBgCw3lZ GJvk&$C G;hhn> Z!)CQQ^>ibid|Z6 ʨczh(D *q Go^sbPT)6t6qQ4=g]n+ޤk"u IVTQtlQNkv6%]XQ!P:)l.[m#M\$mS6XX}׌iڭJ{yF,mJ5mrJ/w`Nc\ioiKgzi")J5)-SǦe+Mpo:>L k(rCtm G>R/ tM楗^r_$ZlH5gۺgaQ&M8uN޽Yw+u믿nիgQN:E(ȑ#u]͵^[lYPj7e/JRJ8IG]tI"%:e~&jS) p$ vXEomD #:Gй¡$gQsIye] ?rwJmRG8R~Kۼys֟_/؝njmvڏbZBR)RUVͥ~=zQVjvW"1@ @8Bɡ@""w /t-tzz6Ăpa A8F9FmEP G15W626BQjQ_|-YH GD.)J",cϦRZ` q:HQz$I9um̀k&ީu2c܇_!sRQnla 7H$s6pٮWҤ^-sa]"Km4<:}YnAG>mӜji/bwu G{kT +JᦔXβe Fֳ|5^=浧e‘)/pSm\g}sm?n#>Ipqπ~g[mm2ga‘R)-~лF RϽ˦`2xyFwI&F8J(| dwl`vawHwk2HC 1ZEGzرct J3m4h"k1[CVjԜҵV(Ir]`j;c~GC3:qԑ/=‘.nvQFfҥ. to/i\tԯFW$K]*`TmQ PtD荒ht΢dI'R6J-VfM( AQ^?/.{^Et [Q%u+Zݔ՟)eXЎ,|7}ݷhիX.ZVpW"@=@J@8A@e$(O=;o֍5j(V~<3& eBQC2~JloʱfrK_lbxC'"+p$)mKjR;_.?r)VZD%pnX˦*nӿRO)VP_]tǎf6]{?'U8z{wk4%y&XV6$ ͷ*QMm)ď-bZR)fv'(I:ѷ]>l_llV~;E8N[(TJȴCE>Z#Ȥvpu>]X]J<ʍ59.Q6Em|`DM}:ܦ^$M l:kDv3w/Vpl‘iЈ{X)$ IW vmZ;IfJSx^rgߘ;Ĭ+}N GFx߷4'w@u,JqFFERd$yξ:oOtvY$? #%ik"-:uj.6i\r%nB\lE] L#J,͚5sw18.`)-.\^wI;tmG)r$BzE".L.ҥyHYg^ΝMݺ%m/Q  Px$K4m\}fM£Qt}i>}mZlGb+OT]weR/hSIRrlfJvyŋҾ>N#It)ʥcUs|?@ 7@fSO==yN+]OW]uL sjs݄uӄ[o{ǖv_‘Q96bD%"E8 4jG$,X,\ M*!R GhgѮ/}\ڇNrp'6$[ӛ]zlӸh=(o۾EjFT$MIﭰ![C㋄5(^JnЏv/H۾=.cF}0?R8R.RQ(KU vAJc(fM"!j+5wz[b7 j v.J%~TD-16YrOh?5YۘKF g#=eӑiӒ]E"x} д^%stgfpվouB%mlŗy1=;SX{,@6r7 ~ 񊬣@KEw+mW_}e4IV`E (K-ASzE]*O/_mڵkgt%:mJ}͍%}nŊm[>TcʤO J@frp6mDݍܠ_7ÕlyG?`ee$<|,mUԶ~^Ńz!T$K@>oJ۬sgeFiѢn,=6(p$g]VDI+*ڈ1jN{+ɧ,‘IEigEQęm:"+ptߡ/7#DE҈sѲцbЦq=sO] >$i[E2ogFgDOρV|9 Geֹ=ژ5v"<2m"+p_)aBQzL>B1':9gMow\Hz;/i;;˯߲WHbTJ}Qԣm5{mzSɛ:Nv?Z`nGoZ33׿/yϿ1Ju`X}MmD-B6ȽXEB'츑0H+oNje_8K}aSs6$tٲ9s+;5M>^ҔtiS=z%N@ q|)xT_n ~^pᢓ@ %|> s診I\(q_)O8r,Ub(Ъ.Ifn`γ)ZoZEH"N6m3m[mEaQ *FWKۈ7wtZJk(I7*(w6P$r`? G~E( V՗6*ˢ~\neR-h%(F,o VöO0Y>^dP"Ejn_?^̔JXJ(J;XQEOAGwn[l굝7 /B:D'P(MAxM'y "|#%mI]s߀x;'#CEK z ]@R y%'dx@!Hi(  P2rUni#ԵZ fLFytPtV: CK1xo@zTM* G,TJrhs9 ^/#G墕]l}oˀ''GŹ _Ǐwz葿R5 @"GN # QBUhoHoGGPZ}kj..rۊ\pTt;.>>ѽWRR v,=4u>sၻ6\Xv+z)IDATrMT%f jǏfֵQ(@q~BpT +|@(\~^pT@6ɆBUQѬ‘_oްXۚ[ob6X"i歹ߙg?,\lVr.a2GÕZ%0g1ў635[T%H-5W3_Œ[y3߬Qon&I.8G6 F_.EeFrE6cz\x@ _ /@@J} p!H6J2fUVZm.=Ljq:*Zo߳VQ\JON6[&XvjE^VţH1Cww u,z#^A(D/!@D+ i9V@{$>$Q/O."adj"bQ`F_iRVl핷" RFvۛ/oU9 Io>6;#~mL'T?]ZbI.1+\eԭmljzϜBH$~^ /y~[6%v<8rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>t@΅zNE @ @ @ @@YfW-noes @ @ @ @@„;] @ @ @ @*@#UP? @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @Hl>r讙IENDB`graphql-ruby-2.2.17/guides/queries/appsignal_example.png000066400000000000000000004760231476434635200234060ustar00rootroot00000000000000PNG  IHDRoE iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 1048 1466 1 3@9@IDATx|Te !!tBW)* "(,u]w[\MkE@{o %) 3LLHf~83?{g瞧Ɓ⧜>l_ 'OZA    @ l߳_jHTTMY3JjԨT} S#@z ԬV#      ͑     PtW#    @@@@@c@@@@@ts     Tkz     9@@@@@jx@@@@@d,,L     @ ТhY0     (sEە'yho@@@@ph4Ҫ@*_i` @9m0OSy#(    PQ5jJTtM)'4rZ-@BJ]B;4lB@@@ X;6&g@ bt%*ㅊ#   @%XVtT%,E"!P@    T0 &P_PZs\    @i֪ 1     @ ]Ev     ܘ @@@@@";@@@@@M t`r#   Sǔs1̎D-#nSa@@@@@ t{p{ S@@@@񘪳El  P-h]-v     H@w #    T b7     ta8     @ ]-v     H@w #    T b7     ta8     @ ]-v     H@w #    T b7     ta8     @ ]-v     H@w #  @ dc<@$ ˁ#Xu Y:E5 q:s֭oݴYa@@%yu[w tZ [+a VvLt붝ޝ"ɇSgwTmWQ޵][?   EI  Tcv:[_/>yʛKsr8w&}իl- SoiGQJ+%f8yJ4GZL+h$? YެA=U /sCz!u68ٵI?7jel0tZ(hz}O>[JE][6@L\[^v ;y9'WcͶ ?>ekuKx: 䩖Lp QϼݓwnNjaƢ%W˜lr2w޼;ܴM&\6B>Lm!~=7 lzw ߿A׬ϿFR_ƫ=Q߫rL;vz?y_/wV~4V9췌utgvƴigz[VM|@. Reiv^p._%z=gj34؜nsӵac ,ȷhj6wQQ'n[UQG^_n?2^q9}V&^s\3}O$f * f&]7ιh t|w$ZwWO5OZ޹IOڶZNk)VvrCee6ӿp}Xo6-՛4nYй!RNtk'U+S6.vNȖ[f^0>q6!}zJ3&J'd$sѤ>l/mc@*C WTvb.2o/U؝sk;V.wt䶠O(sFO# }lfg/Y|E-p^nVf͆@w}4]fh!Oh띚x.+wmhkiת}(nr͂u[orO=7%k.0o=͔?^Hꅗ`pZY(GhϵEFvv/|w5}hf{?u['ݼ9xI>ok)X[|ҧ>m'N̹iܥ2j@g{t }3y3[ieʾ_SXn['m8i.Ov\QƛHF[trxE@!hv=Ojתeѓ_*?yc>AnSjumNK//(XKqge5r;#yD_|ܱypb)?s=|0s,\F4\q7,6=_Z|Wʒ ] qR6r95FK<3]5oйp:3=>QzL6wǵ_jE7^>ڒ}|O[_z F@ 6=g=w+GV2@/j8-'- 4nmvh45[ʒ[bٝt+W,]׏ rtn+[ιV+:i!]dZ#V,d,qA^܋ <݋X f/vSJO}t_]::1f;a  @ب)9tlӲ(`c==wUHy<}Js]'''fC}NnT{XDZ54Hwp|ݡOX kd)1 uBkSJLRk ` X [t|n4kZxz>"@.nP5?<;ޣVעo,$)^ANY,>ݹMU\^W_=ϭߗɳ;^j8 kw;M6׀<[2wwZe}asf=)NOSU>:yZn*zӠ&be # p¾Ew(I2-vN<ߧݬԊVR k֤WmX$oi;UyJ=|3iitU';]CmPZSءs >i0Yb5\! @~:i+};k#Q|g[LKTKՐqMw6O$wu_5:Jʥ>nB9Fw4'IMgb/7fAx^ϱC-5vQmA05kfܤr=߯陞ߚ`lw^@8aߢ;m[Bo}b\?R'lA%^#y@^^iPVhY>dz\U|¹zX$@@**QSz嵏?sZ2+klZф>=̬, 쿵e*RVk$nWw^4[_˝\ק)aQ:R_r&jZʝ])C5 2+-Vl)~9ҮȒ, [Q}r* <57mW$t C@:9;[{ӲժNons[6FŋHx+>]e||n˫:MmGE㞓CwgDsE5\oܳO,@[`v=)Y{X:UԮ-ևC$P9x$ziڡh?;/ z$Mgs9Wn;u|2Ѻ{z%uԼ@@=$_X+*Z.<=mΗ) QGu+/s>%qG ׂdmE*uDԩMKgQZ^i^_+b},WaOߖb{zҡU ӹ剓W{:`aa?G>=6w@]NGHw +P|QcO}b)V&ϜG?_`nV20bP(r(,l9 (ge>msֱh+ozG>'.-6g?Mu.T޹K>tX6i^&R5%x,^VpvoܱĦ2⻷\+3}M4(Oi'&\%4Vm~YG[к&>,;$;3]LKc( TUuz#J?Tlfm=iqB; 4Ի\~{ۋoy} "Y_=V[R!ur]ޤۗ|惮eCV.X"iZvr,;IgZt cFSҺESI>ꜛQn7Zw;;@^wmwN\w>LQ杉X<<6Ya4F%VZ4m"w]?g,,TKlҮS p297ro@m#/'c|F(O%Ke򞔃sJrڔPXWig]ulֳ|r\~슸v7w-|v{qň #{R K9iǼdC< e @u@wyvdC{[\l\ؿYwN KW9A[ ܺerv^3wwPZ`ۂٖ_1).7>ϝXK"ӝ>U ˹ fM:%߻u>F8[Vm&vlyvϢ[OzIs7@~z}a~~|M}s%uwɐ>4gŧv3ұm[taGlֱb僾kxiڸ,>y{/->Mh|zE`褝 ԙN]{S>T=\.>iѬI#'нGӜFEK4}߿zyYi-ujK.܏zIJy}-awuBرdϻؓW]rs==C㩡^Р!ߒ#f87{P+vOli-$-͔@?PgkrUcuӦki bxk7۟[쩊Ů6tb}vX-T5c.1 n@%P#HzukNCiֺ$?@;e<= .\v6@@@@@@7     @ ]w     @c@@@@@Z ֻG@@@@.+A|\    @ؚWjHTTԬYSjԨ}b@0EwT    DH@@@@CaS     I#ioSW@@@@@ tNJ     @$ 莤M]@@@@@0 ;*!    $@;6uE@@@@P@wT    DH@@@@CaS     I#ioSW@@@@@ tNJ     @$ 莤M]@@@@@0 ;*!    $@;6uE@@@@P@wT    DH@@@@CaS     I#ioSW@@@@@ tNJ     @$ 莤M]@@@@@0 ;*!    $@;6uE@@@@P@wT    D@t$U"Pȗ/וq}W {e-/k%5jEDV&C|戫US.g'dsJ =J\LT @@8v\׍KN0#oSc@%@|ɳ^|"U5;EjԔi(1Q+{$+J=^ux<$ ?v? P.͓>L֩nnr'Zw#G _o|G /wxuЧˇS+OǛNhf  @t@@@ hD/PpAN^MɁ#i8ؖW8,M5׎qh`_:w8+߬(c.3zVrzi[*Ǜ+ e' dY-C̔ǂkWDi N'%9duP[;f7;2Ktv֙+y\zGdiMZߛ%t+:ztg?QmUjY+SdzÞ#'yl={}09. 4傶c˦YЉW}*_RO8ߋ ^p C^u[m}cԾ-~5Ij%.žG;K ==Eqj@E?ؖbuٲhKX;SCY @$XO,M:3 ڗ"WB^n@2,`Վ~=%o[}.yV;-~&_G~<8l]`nd̓5H-_O75QM#^y^__ ^xLs\ܥ|P/'@)cN4_nl/wh BonhU0̤͛.ڱlV/>c]CzGƶk$lnygّuY'^\oNl lj'݂l=;ד=yoA_}(s6fu۟4kp&?I93z))5}ƒ;5'ˋTIgfvMΤW^G+?g'-n`cy;]'}YʺoyPT& YU4Y͚5d&;H\oJQ];K~_Z5$pQ9'v<@^Ԏ{e3(vՎǯi-ll[YI㗫IZZy[2$/z<<<6b~[7-Itp~s7o7}%_?/'OdL>}GG޵[j7\$ZnWE[bBq( 8BM՛-$69Kirr$0Ry} w$P`]LKm[wJ߮eݲ`d;. {oXfV{5Ҥaş<$+íU٬''yQ#4Z?.Zvjs.zrO˽mYe#8")GOͷnqI^:.^aH%f(e f(421K(-5hwyy_%IC\?ԉ)s6guGe~?SY9 1/l=-Sh ꃾ\ ~2mu j/ԩK:㣺w'v9r HR_F;}ْ-k;N W8׳Xm[ֺٲjO.Da`Kْs>ˌҳu`{lٟ#vLyo a]ɀN|Ӿ=|{[>E6E1EV)}JXU'If%1}ש4|7'_W<=G/]4=Nfm)sv^X"" P`TsǂuaG{~;=r>˓44yrVtm+_MLd?ҾimY韙,Wz#^BzvV\O[4$ͲXY.>,7=E|7꼷2߯ėk֣ٓ%/.:(Itkg0,Sf/{妱#˓&Ow~s{r͓9΂7nTyYR?>NyqZvt/Y^Vl&zvqRzʳ@w0jLT?W[Tg/ǷǴ*1MXŝ48,@aA.[Yd (ZǗiK(m?[!90y]KSQp-mH+ 4pV<Pc[˷Eg eqz`k&͵}|7`;:m^'=AzmMkA|O~E*vÂӇzJ> $M/qWiG7M^^-<"?Oi溴 ;?Qۆy?l_֦uڃ=gA7(hA[|ojkjO6qn9ylM9!5}RSJ֒W-C7鱼]3ǯU5(EYmߋm;Ԙ%cт|oT"5=lsbfBqƌ||_wGCg6y},v\Kc( !:eO f7,?MrԗIŽ}$]~mQv3[X{۴^^sY=3g>ZNuDt}ri=-zcy+ _ѻSeq.ϴ/lsn}2c=gq7 n*M7~Җx"Xl#",nl7ҽC[pU2{?tD=\:)y w&@YYE E։+x\}Mzu$q'N)oi0nXEV7V&7*̹uy?f N ڻ5j{>'>;y' Z=2-Oq׸,?lAa@Zj70B7;3,[O`vlXpL,mAT[{3; o?|6>6"s&#T3|J˾n݆8ik[o;^u kEkKiچ{oV= >v/v-u(qX~(OM;wˢ_2og|@ƭX)jSrחHgLtWKQҧfg7ͪ2wL}_շC"Q^(ܤg+//:$WsX{ VVtnUmBZǶ(6JF$ \o|kQ]FgMjks%:mNevuӂۣ8w@IDAT|ݲM;uƖjat/xw\: *N4 uPSsz[٫Aab' [ ԡXRY;ۦKbu-_Ye|MJ,>>ն [S-[Tk(ߋPmEmٖc%\ׯߖםSw=8ۚ]bqkn gTPV~Jz[sE7d.qu|^/rU&;`UZIg~SYۺ>A{$jjObJ`*,N@Oֵ# KC8yJ^@x#[€eu/@(A\m%by_xfWǒnqkKWɆ2oگW]IsZ~Hߞ4ye6 @~Vbޖr> /_Vh^_& K-c3sd#--s( JV_ ajlcޫ6f`C[iDvDpZj<&*4׊u{[,po@wyDg?['֨(j), N}]NmHYmMn{~_;wryCy?Q_sUu˚嫯"}[^6ͱnM)7E–~dwEsBP)Kw³ ,bq-ԩQ|-IC$owKzsOfYnCh4nnBضi mZ7LXWŞ YV[{K]]JI8 pF`զvnݥ\2x^,UG9g8owM`gۖͥW>fwZ^@(ǁ۱ ĩSrHۮ-Mynےۦ?)i'"Wr"c9zn7^wtY4C%+(l|nתics`'-+qsX4 g@.͝{밦2yYX.c Bw^&h~V&X}'D-RM5j #? Ń^ս|wd}^O;,^,_󶿉&8Oib-߿Ȓ<Wۏ9֚y^|]٭-{eͧ\eeVV/n`Tf9] *T5MU&Թ*yY|f[q|~-9@b #mW^/Gi+/^M9n41 rҖdk`5/I䊋IXKvC teӖyצ~^4oOqՍ-O?s>VLv\=b-X-ɇMݵ}FXI äCN hߚutGIrfaT봶+g:udvVZG-;8w۬E(n>mH=$3r{S!abӵT| w\~Jv,q_Ҏ%wh>iĮmks vvozfRZ'zkc 8"O\߾BSZVxpx$YέSP[s N_wT߮D KWU]-}wUk(vC t.MSU:kQg-q(ujokHT+mߧyL ?+ g()<#4o ]HE 칑߭cNKkj=E:m%Q3iu88v3$/?_NJwYَ!9#iδqjEoZ{ V[`{`ϮW,ؽ!iӼY0iF*GAo7cCx ; ǵKsO{4 P.z|iA;X6k^.}[בg/_78un=(C}Dq\9 ,_gxS)PN#۫<;g2ϺWmf%ؙl?wx3'm}an]?>[Jw=R<*0vu_-k^yuL^䑶J᥅)GG܆dGZI;y.^uߺńA ҭes-Ş,DQͥi}YsM}2auSO_g%;K nY2Ktz*DeYMĵVvO{ظ؝776\MxY**:N@8|pZR,}zjMs<#:wׯVȞxBY~vyj{rr[Kx \׊s) P_' :ݧzMOUmh@fmJْP] FoAlQ), Z[iؘ!0ٍuGǶ?~O2MKai.f4=[i*wנ`0-H) B]y`T3+4P;E[ߩZg-kg_. xvҸ`{fjtq%ZǮj+f4F~`m_Sn`Q}/JCuYzw;^&C5mm=ښs_;si- [';ވثӯؓ%s6j1CdL3l|@8an0F~`K}vDYX6ǽWi45{Àܯ-4E 4ip/t捵 (@bEo+~Riui˵@+d;hwYV{F6G.o-ȬD\~WonH;l\UX =3X0_y`n'hf3+55 ٭Evikguy?٭-3<8]$ 5ijޥ<5m:.kDn,`Z {kkǶl%]o|LV.w zǾV2u'Iۖ>zs^-k^uߺF^|7\rE>n>TSܯq{17>g)ݽZJWлs}'|S i.^J޿v?3>>MU#@[nr?yZo~㣵 ]>  ?oΈk>TƬ*1rn{P#%Y4G3 #uu$GWesd8W.{}9)1hEVjoS{R.֠& Z6`;s zHOqlG{u;i:&5~0GrNKkͳ޶ILXN9,fHiL9ZJ =h૴ r0Wk=kA+/h?#?vo%X&A:l<ߋPm[?ߋmo34f]-]5oߑ(h{R!زCʝh(V:" JWf&5u8[`ˎ:>UomRۚ-)}oS1oVf[@@t<٭? knN瓥UcSsRͣz;M[c9K]rDV'OIfBR'ηaٖSoI+11R3*ʹkV @};\vTYKS_uG@@@@@@wD    DH@@@@a     #ySw@@@@@ àTJX%]ܖ!i%kU"}OAʸ##"?{C{Fҷm0=?@'p,+[Nl-.((Ҡ^Kv _7y L  P+ޔ%i]rx4-mf5zxEkvԎ);Pbxݢ{%9'}V2Ƿ: <@yyy2kR2q"h )2?[DgH.egCd7+e-so8+5kJ3՗;/]%ۓJcN(qf66oM97 YҫsGֶ{dҕwAi1 }=cZ/yec}"FP}kHcxQսaP0G`f-fk}iݤ\֭AB0Ek nTԢ:c(uL Pശx)j CC5qlF#Vj$X3k:q2X7ΔE+I\ 8-IZzlޙ(I)m{࠼6eܗ,U&KqQe=^(%1dPn+7oHRy;N\l\v#ɑcҪicR @I%M[Oh@vYwMHVlIΑTm _ҥy[heLX E%}Τ97D#<@lx w57Qbjy.-n.|}z\?t&)4ܴ]jh NmZʮ ti}Oeɚ? WJkz4g\m>][X.ZɎ=>!B9Nr ֑$44a(ye$yI Nԉ    H`{>g衃6NkTK@= w L.tyek7IBݷm暖I#XO,qZsx-|!2B9>9m6 No|G(=Vo)Wo*_\/  DTrD[n/柖5I$F4Vg;[ HΩ|٪ 0]]>|BNڒUów7X}in-HpV{i;5bogk7Ͷ딜'JFDu, R4PIV]s+CUIC=lڟ%?{UFz#t!z"UlDE]Wuu]]]m] XHDMzBBza9wr'30F23̝;m{'$9-sV іJ{9/BK#Õ+f} _iafFvqv︁F!{;;)(v,$<1=Ʋᡍ@|ȿ'e^.sk\~\Oo|F}ZkFUM },L ӝV%%7H]o%rwjwI#k.+}]9z˕U]W]e֪mt/WZ4/B% 4a],dK魻"HJ86 nay"/Ζ޾3ˊMX+) JG8s!ݕ=qEvvh\o7Zx?->UAolJN+Mx}`ڣy)IEsitu<^7Z'i|`z$dg6sO͹Ə>ޞAPj^^~icڣu<]'ۖE5ɩash)AAN#Ԓ{ XLnۮ|.Fo }-^1]sԓ?wωqK~1-?vhާ|?t`nז~-=礥/7n^"}s0_sH ڨV|sX@GGŽmԷ7 +kŹŻ> E됲$Ok;lsNh][ ;@"mMQksݟ٨ gkXzv6+7XkhuTVYMǎ97Nçckr=gi7QS[K+7mNP-mn.;Ň*`(}?:Ը+p @m/`<5n[AK8ׁC{xP4l8:` ͙ŷƵtoβlT~/3ssix ڃ9%6 8E?MC]ubߺ(ΤV䮏aJIT!r`Q7]TQK||s֡4p|De{mCwɜbCwT1J+g.pv㩆?S{иHwqS9VA%0uJ~n,bWGAtɘO ͧ%oyM9`9/qϮ.!X3x"_rt}̃Y`^/<:׃ԏǏMst8h:ؚMuϯMVkB:ڗXJ2{23S:~əi͂׻dMI_^7 y0t:1-\j{8WlGx9>|cɑ.ݓK/:7龱ƳSv sV-K)5l1=E"m{ֹU-̽0< msp{mZn, ԣOHT̚jjFjO 5G@ L\3%4 rhW-tw#~BV\7Ƈ^-m> %`σݚSugVKH`ݟ?ӳ]&Y rGt OqA78X7;\2Bz^ rrs=>v7FEM,g6G,C_˨  jn:Y rhڕ4,\ r@znV^ɈpΨd&{ r;qi OA.z]^5q4osS~VWr 8j6p~rH}O׃㳽tortcr|dI.REnnۜr XE\Vg2 )kR7_%f@M~Nڀu,xoRD:@.|MFÉ2tntzݱ<%-Yso74؛oVo&F7siu iF'^%t7|Ό3-,=9%=}'-{(Jst=OO]ד>/X.g@$ \{>^^9~wG}7H;;>}Vv:N&A!=Glzwh/s5m?xT{IYCVo&W.3_'Zob=4a@s xohƄQt)ъ5MTZ^|.$@'NC퉒hnǜet;b @殥.wbMN3BlN M# w_ʔc31ayȟUW iA<:RA?.5_t'Qd#=6E?6;yS6&>PBj%z8/-kAM?Iv:-,`?!E Anl\V(o}7zP+Aғ5)DΨ_7,${-9&K~.dK]olJ(ҭ`KZL5|YFfa & ӿ)oXwqB{gm}:j(p Z<0zg zzu I#k7ӷ?PJDrZ{$ظ Gb/P5o'G%ངU58tiCM~4M}pow7ps x9ʴ{}Z{Km<^P /,T(j^}g@hy}Ml_J|x2d)-;f \b$u&C}‚(.)~hhYSx"z鱿E} ?hU/v#kٟDF7o[q#' 'i6\^I52I O )]0 @pV}?,ro9VK-vصэhj 9:aM D_WAks/@!.S- sD&%ȓmC ϫۗYD \ZY6rI-*V[JȺe@L:2+<{=9;&:M`#,dFaE'3%f0 V)GS]}oz,:OW׶[egא示~>*ͥDT;3 krf:w1ܞ;wJV.Z'U;v-Lc۴d؟).9&>^@93Ə~\b2׻Π?՛BI2xiyrQ̑3紛,. .8<mIhqP6-m2'O*4k{K=q|)=@WCݦ@4`%MS5t/ bpӼ]/-RZOCi th_Zu0O}qvd뷅ѼjW}`u?ۓ (uj1o qwF%M 1q”Cŵ-ijYj*6z0wZZO KzVKd6oy^f'1iҵlCjު+)sЄ? 9ix $[{7Ĝ۔ LKg̡W+\r ~{48OmѽIg @LD@k pFS\*etdzٚQ+z}Zԝ3tmj==kz0q,m ]QFn}ɃT߱~˵஝y98iks4 X@ t/c0週9&΍-~=ʿ?1ݔj ev+Qʰ 4ډkmy)śOs%Lqk\PRB)|s@j5/ޝ1K[szVyKekr$#v2H iISxkh`o|Vnd`Х{5_/6NF_Uܜp&_ߚn|5Rue]eujnjh~4>K?W똺Bϟlm=@,%faH/ypm[&9lBR=WZQcsB5ݠ5/W_"OdcgE?96;%_gGnQ]fzg&-Sѳi=l)rS aZ#G 4 XeQB+0U}O(&UYQl2qVq Жi5X5cԔMQmqҠp|p_xH ڴ^(gypzuæ˧\qe![Lʯ Od\3UI245]^H-t^XVt_/.K#>kW+n=4q?J`KxQaHzLoͅwf+7yrx% ?&Xd2{y5uL]s1(ԕ8LZh `@Ugfo  *xསڕojYS)o'gf햘hhKr:|:xS-ɉD}PHMQhԺ/? 8)UÝ؇MwS)F)utXx0՛=KsFMoqR 7~ Up7 񡙃 hgnO{ !VD?[~K(o?~֯1sq%\䟋|PƁ}0gWfQ*ȑS:E4S lK'rCScLm!t9>(0ONW-ує{pϙ3Ro4Ψem}x$$^^&.wb8OOѾS?52߄wO=u<ٺ@3&SDpq*)2 H7#](ٲKBO.)qi~.^)]3xHS֯R3Ieijq Cyzx;%* =GNPum- hQg:W@.`Ձ4R,|=7Iu܃v۞HϯH-#IHo%ӰI}OC[L_qrb!s|Pd[IPG?v3s{R*"1 9X%#DRRDNqOxnj̢jLނZ%'T# j=Hʮ~A5+o t^d5igKׇ 6V~~VmCu>XR*4ɂ~^tEt'_6ow )d  y@3' tCV-@U^ `EV+:=8@rG'_4Rg?N? K*x@/4_Xc8Y  .t 36@{T->| @ho"zOΖ}^`{ @ .Н8v@ @ @V @D @ @ ,@wW>8v@ @ @V @D @ @ ,@wW>8v@ @ @V @D @ @ ,`gWV[C @ N)Ю]XVޮ X2L8 @ @ tQ3]wQ*6 @ @]Mdrw [[nݺ)sWBg@ @ @f m:A @ @@G@ @ @ %@YL@ @ @Uzf_ @ @ `f1 @ @ tT;~A @ @YtńN @ @ Qg@ @ @f m:A @ @@G@ @ @ %@YL@ @ @Uzf_ @ @ `f1 @ @ tT;ĮYa;fC @ Ѝ;H /q @ @]!@ @ t~;9@ @ @twӏ @ @ G@ @ @K ݥO? @ @@@C @ @ .-@w>8x@ @ @_q @ @]!@ @ t~8@\=qETZ}&DyYTwW傽i`< @ TTVQUu yyQnݮxuM-UTV+ژSS[KٶMW<trIsuv2(8H@t[/Ism^E"Ur@]CwooY @ d[vǍ^!=kçclB2UqZ= nN!zp}w7{gb"#J:\ oVs:v:iYo!w9IӮ=]];nQcϤ97͠M~?n90@*'jhݶ=q~ݑ=BJF}|mEE=gq/n~1%wt׃i@@W= @XɉD\.3R%-AB^&ȁv#FSnՎCI[/-,L@wXP X)[6$3֝%a^f;RA;{.ΎJfA1JuNAa9Rۛ]mVUSC|7|Ӣkr@VTUs "*))'W g.@hE:^[Tqo/7DQI{[BaTveҟK_(li3[i4z_sExjrAp]@$ 5R{Wzgs=k.ksqAA]ɁoiOھSE-?j?K?ޞAoIeu&Dzj;ZqX~@)XĮRž6mˢ7 Ӓ]YlU];hrCaٞ,z,U'@/ٙM_6&rFH(oH#o7{FA)e4x̿,vJg:rZ+f,xngC7j9Et&zzwcV^kd|>rԗordЮ ŁycǤn t <@ Ds-mzKVo̼ڷ7R8@WPTB'[v6c|z꺞zE,ŹJznE%>OPbƙi|'穎?ՏFDhv_%88gJ;mx?x6 /i+[yA`omg g&~t+VK8%M2.Wyg;7dO~$MӔ1i6|PzxbRcgK۰h9J;̷;} n]7VZvO=\Ozq&2 d?u$ `m|#uV7B~gX_ʙ6pR/(jDxvW%ZPZK_ rf L=^@RFEmG,,o snkts37}/gg58ݭϫɭnyf?3S Kp9T/LLM-ɵ"I>7rZF/ޒy}[}gh`ˬRJgٷw e~5k9[2&x}Sr=|^[g@e˽JI)g޺uuu%{ۓ]0d0й{wfpa~ zoi߱4:A}-Oj#fB@σt 77Ⓒ|q 457e>i*+|r[hIzi\Pmr%%,n2qz @}.lbr ֪듕%d\nBW:qV-Q*0lRbD~H/pV}4MʔYC\mGRp=G*#|KQ\!sWytsY5 |.qkKw}Wg+AhK˕~;MC)O'c[mp0o!ۜI-/C~ɭzsYSnsY{N6SYu piiqxsT? tw%ܖuw>NsOC590=zl9RbՏUwnt͐u'32魥( N2sKu` XyՋ䡶BrÎ1z{~>~nHM![[M{Ҩ\\Zu9-5G8G׍e4[w9LC&ۖ:ΥWҸ&I52 ,'>S7]?+\I򋚙&,c44v}:\; uoiJgly\CӴ@psr@]Mޜvp%ZwYDp ѣɚh3fЍoW. @};R @0ΰvwq nGђ7Q璗E떺׍I9'bf5(F٭z{r?̿ Ew{.~亞wLV6* w| h!MԶsnOv.sd\{ܰA%} @Im'Le&M7z7'͝v9v^e+|$QPUbj?FPo&8lpSp7kxz;w7-&]r^.|<55X To|񠟭ղ -i[N, @ڿ5uut>eN3WpAܸs\ v:P rK 1+7}oW(AҊ*.Wr'ZL+ZkG Qn~jf%[ļaXv(Ly2r Kr=[',j6LAu |ZvU? d~Em>Rw{_Zy-NɐlfAq_.Q2myۍ>ߗKj9;(YMʋ:)bZ 5=0ԅl&ҝKm%\\YZN u_yO\rF45h?|H5bWVuQ޷d;Ǔ5!-^1lP_y@o5 @V/ ٫;T:MNR%y蓒$-ivvԝ*/u}lrn񽳦РlM=nL\\٠Ԕ=ڗ#{i52+p\Je H y|uygU W_;`@"\5KƷԞrҔF7d(2U+qY',9O% WV @RsB8+ũ[EU1i y>(lů xP> rrEvTYpuFkc 󠻝,exPU rZ[K" vekrtuq5C @: ՑSsQ S); cys)_λ/z@e]s53luۭ\'ڝȟΡGtR%(D>7+C8{Poep 3)W?+ >K[m!=4-\;\mEY\K~MהXbJ곫 yPD .?<.d_O)PڽsfQ5A,Mz$ Գ>.(eާq%M4lHP^I}%ӆ4)ZsA><#88>s?č߿gj $g d'̇ @z9Yd|ZnEOz󳯩wHOTf - aɌzfeځd lϡ* k\ICEe囸++;͵7%t,sw9Fa2ͷRh "`g*KT/Ϻ}?^FE5d.oY(&șpJ9m9SDwQr^Er؋"??8+Dtr/gz/wO';K q oJ5 hRw px˃L^. >IUE44+tVݼUk"5_%X9w08fGkOD<5.ylbR)t!spkd~w.UdArW%'H>-ڦjlة-Ciq%/5uѓ?YGG* e)Ma>ù&EKV\W۠Pgzzj p0UIOC[zI7L[*诮O#quON^9{ZSZz4_ꌯM4_O|Cn pE\R_:HryUW^45M97 &q:̴J0C\H=7Zf6ymJ?t @@g%Ҥ %M 'cN]0VekަS{!) )锔]k-S'75᫢r26K]dY@9D*%l ^;?o-<8uN^|ع Cw{.\Cɣn騈?lɍ9m;M51~Г @Rސ~;y˗.qo j^흷Kx.!y;Zԭ$`.539ܛKsx;[CFbn%7p'|62%qu[Ils0좔h1OIƸԶ+= ʚ=PB2Ȣ~QMmCJͨ2x˔rvhyPYgɅotD7]\Y5?8X$ݗ[anʡtZrSDj$p{6qG͟R)z @ Hi^MESjRl8ۛkr4JVz[;53ҹD`Ur{o\6 g9><٫drw [[fz?#l ۘJ;ϓ`9d~K<,i8j"\ڢ%MjÜ&5RɌ:ثpOg{(kJo9]u.Slo W@6 -]R[~ @*'- AОʣZoAH yA:2 @;o@Hy{{@@k H /$_X@.͊@# # nl9 @@ ~Jvv"݆X5 @*tv"6\UP./ļj*L4K?Ο4Hsp9ٷ6Q#nFz\=ʇ) @l1Ɵ$W @W@+ @ @]TKaCN1v @ @ #@&!@ @ @  @ @t$ @ @ t>;9C @ @: @ @ '@w;gc@ @ @@[ @ @ | { @ @ @:t`` @ @:ݝa!@ @ @@Gn LB @ @@@31 @ @  ЭI@ @ @|tws= @ @ u00 @ @ @Owΰ @ @ #@&!@ @ @  @ @t$ @ @ t>;9C @ @: @ @ '`Gj|(6s!@ @ 1@"nk98@ @ @]Tvqn"@ @:$rw [[nݺ)ǡ>wʃNCBdt[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jv8Zu<>Ltl@ @ `u15Vbu `趂C @ @ Еg@ @ @ 趂C @ @ Еg@ @ @ 趂C @ @ Е^7W h>ft.=qETZ}&Dymϧ ^40ĥͶC `K+󗴼gEeUUאu` @ @*Ok9jz vh}莑cFxqM ˨}/n@/m{{4 @(p1-gQv~榴]3d͞:llZR l?OOلdUz}7O#?{873kl˗iJM30fvIOV%nj׍ECv+vB555T? c& @ݸT GwZPvKθkL@ftO(vd@ FU-{Ҁ݁b9IԌ,[wM3 $_/O*(.q(.)NTDv[ydFݩYٴlFJӯ~։y]WsTlh KI?-߆ش{?ݺjݨ_S{ hƕЦNv4sP6v+@%& `uj$[ n-& @WA@wLH&7W=8Yo,%d4?{ҳC1L-U"E߬1@k#/)T[WG%̍ IR2 \Iso24]$IL͠%7PZv8u'  @yR @@eCn4GpI%#:[9ҤÔ@kÎ= )(TiqkR~A:eKngGzhlo>,+ MwϜF=}Եޯ0^Rye%s9PZFTQ^Q+M<, @ h{1* Ae{mYYXӨϓS襛Ôݞξ6r/kiv6;Y3`s0Mi"tE3^aqV]ʚTϝ?f69}~%賽t!J;hkz8TJ9Kc#'s^\DKkkۍ}[}輨DkcZé=i"?ޞIʡ2͠R:#cǤ>!@Ҋ*#oO6ɪi!eC#[ %p!m;;HJH{uTVYMǎ97NWypZ\ϤQCٳTS[K+7mNh뇫ozЂnA}[Sٖ)YtoAQS[n @|KA$H= r:ڗXJsk6 ^KyutorF+[3#p_V_E7K%1םvS}8Xtݸ]Oory{l O)!ue<`ײjjׯ{>pRٱW ΢9@9|]héB|Y rm~s8>XJɫ/t_tk.7hk.bB)t($S!o롒e/J.#lUIiO%-XAHV>V^X'NǙup@OD]J)o;&u{x @c $gҗ~RJ7H)lRXۛctSNJ%NmXS肘 f ,Mjr ѫ-նx2ZfŴ,s$M3> ԣOHT̚Fo~'uyH3<(JHJ%gdw&An Ki;˙~BLn˼:l,A0ł(ِٮLw)JyLrPvs&q( vMUG`oqug|0UV7=ڸ>ONmL`{:}apUv~^HHn5$e1fsFYM pl!=]xvWYw~KJh/FX3$zx%}(]dܞMǤg@:@AQ of_6Qm|9 u@ '9pI# ǒ:h5=GO+7MG#כ/mC 97Fq8IPVa 3@IDAT~ƄQ|TU/H zGDƪ(ŵֿ[&tpܺޅֿ`._׽3;F4ͣUПmҊsZ{. oY}7Mw 'mF@^,w%d,Y2wVF~?U;H~M<ŝ>yxʷ% dϑc{&䥵[k:,09ndc oDyӯyCXlXt/v\]]=gLwSHťNSSי>>ΘsxOwT͞asfix 9ssyN)o[!s..zFTIJOs*7 tt-BخhH'1'=|R{wn6[[g\Oӭy=RzԪ/.괶M:#|Rtnکj S_Pea Mi~4/*r4b.ng?G5>y1˱@έ@Mm臭'AW? CUF V9St oڸ8N^z9*zƷWc G]ПѻJ}U#nMZ lrۛ+blViN7Bd} id3 ZcHm_/HKtHn*(Sy{ [gT/3[G)0>R#X/b7֪O4œkVJbyy|U'cܙwT\/c];>9*V} W_tlWg:@ߙ69@z^@}QM<)ϟݭ33E}w*]I{ܖ:ƍW?LN@en^#ХzԱU{Ҷ;ۘyfwKߨrzأ+YyZoɽ*w!-P=rs@)}2>:B^5IыGvv9ۤog@7=pZ, Q6mߝ ䷹B#J\mNJkL9_TiYm>C\| /(Y${~$lx]j-Rxu3MiS:{)8=nwҨyUic\Powim# ت҈Vyč]m]1f`^-}F~r5_|='׿ks 4.ޞ6@{k̓ݞc^U/hJ -u!j#t>L9T$k  @N7G4Xu~iK,9Uz۩2$47-F*,)эz?}HW Oifc&ۘ)[?~ˮ&idlR=\]dP_m *t7ggԞa>Չjdz[gfP,!C7-+[p7vOLc9֟W/W$􅊝Q @΁Ng Jj*v;RFDu~cUJzW"B:=贀^T[C:x6`ܦCz\SZFN5h,`Я) }S@w|^*5-IOU~M Δ0oUkNf74g|%QGΝqFCo;#Zfj6u/ 5;]FX6UjQD\f7-j6;nnx]T-˟=jaP[nE_\t{$P4l t2ݕ$IR_,,7/~^9s8y9fO5 *p<+[*%,$H";,^V__yG Zpp01Pf[UznZN>ج3nZGD +IVwȘg&!sQW)~*7Uw#9y =W%sǼc}J̈́H-x!TOU7 h,32E(Z8Hg)?EmxK\{ܨ;Sˤ@kRy\&\m3xb/7uNf_TBSKB~/nطdByeSGA2s YN}YN=]e'7e[F5mK#oOIR^ $D-cwqnafz1+'J̷(j:%#zɳ7 k 2vag= #TVߜ+\11X5N艃*u w }#\wƞi95Jߙ'%Hʩj)>-Q!2]-ytoܐ!#ýŃ{|+O>R,yjqJ  =~.rȴU?IfD  @ 9#   R E7@\^@@@@@^-@W_>:     @     @ ݫ/G@@@@ g@@@@@W iaBu6#   }N2&bA2 @ 0_B    8n@@@@{%d     s v@@@@@^/@_B    8n@@@@{%d     s v@@@@@^/@_B    8n@@@@{%d     s v@@@@@^/@_B    8n@@@@{%d     s v@@@@@^/WV#   R`hDD;G@"r%     T @@@MPrx{H~ y0^@QW~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@Qt;ꕡ_     v 趋J     *@Q B@@@@K@]LTB@@@@pTݎze     ]b     v+C@@@@@ m@@@@@U@^     `n     /@@@@@tD%@@@@@G W~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@Qt;ꕡ_     v 趋J     *@Q B@@@@K@]LTB@@@@pTݎze     ]nvբS N+%5VcvwqVxsn+z;2H\3|@@5RQY)~bߜUQUm˳:8s挔Wuho@| &;KZFԩϟ.2&6FV.D|4 @ ݷoFc 5jq U`2zX%^juVnO}!)'kZ #So'$$O.1IO Q$TnY`4o:#IjfDSKSxfNN՗;nfxwc@H@]l{:5ƿjnu p>Ns( )0{rc?=5d}p4ذ#|fr C7 nn?.`m[O$b@ =iqRfLqi7dA;(M@-`_b;Dz@@VبfAn}Fˀ@iV}h1eɷ?kܺ~6RX7tP`3uSjf qFǛ|;x*C@)t;eAV֞'d[Bq-{7XTQ+OU~>W}iG)3ƖS!Uj?=JNu׵lGݞT"E,N dWjD[wơvه tZ2,!vcҳsF.r[9Td]rXjNm~3].pBM7e:[~6~+\u #eribŲK-Rœz 竩6|-[vz%Ar՗ɄQ͛9=;G$}0rD jϡ;N@%@]\TnKrם*(Cd|zP6,KTP{':Z~~nUU7[CW߬]_,+/Wd|RV3&JNa*-冗$Xo=U{;m޾`J+SVfTԲϣ+^<*?: C7(7<%~2Yj_Mo¸>K>RcHTy&_>;Ьl@@zHN J`B|qU;_.nͿ7wɞc–JfͅjQJ %PQY%zC\h3mOG]TRo Ԍ٦r_t4yAGEHl#} U ?R/jqicGIL )RÏg娙9g:p~M\9O@w݊7G 7$ݨaxzD'+7rzJ|q*$y*%2g)bA|F[Ϧ~28qf#͹үeUu/&놊R#ݯQGUUo&IY:qrC-gc[%+Up\7_f}aD}&K:GɼѦ?Vfc5N[C aNT4u12@Uxs鬃@@YȤQ=(X4Bya]i1:/LܽLu?Gd yJy)ti:dReJ㚥tBMgΙXpD/r dEm|*%h˝<# >7ʙLe?mvq'CUꌤַ 1VPz{ C$4]­۶=Peu3f~Eg؜my@8}.}.1܇N"n*Q wƀ;\vU<ƴ@/_|܏ˍSc:5r!MX ~/on>eC|\_0n7W,)Fݛf^Hq-u96" =,s/=]#`ϿvcF7\jy{lZoM7ɤ#mܰⲋ4}@" TnL/,@Gp3rM98*:6GX*ǥbZ=`mZݍiM2K.UN@iVv DjVwM7ԇ6|"Iyʷ3:GE@5y䮬1ӑrkVk`a1ƣq9 M5Q=2zGͳ;V{i\UN.-أ+YyZoɽ*w!=9ސ[nw76Emw; ЭV^i||"*ƬݾKjr$yqb-àENkU%W-zn.yjTnl[os9I5 >ZNO^9ZS}  @W W6j#ԫ?=h45kȴO}W"zq֫]yϑ=*_'ja 17td[y53.uJe./$lyHNa?U%1tH66[86Pd-y-   Dv BeZUJ͚*/ 9挜*~XoO+Uʆ1y@.sFƙkJohA?\jq9SɾDn.3&p;Ѫi  )~dzOOu;?g}[Pe|k g]_iA<Յj@V?/nNY\06Y jOˡr̓UsY|=|wYUIqEL3ܭ5聟I4)]4Zג#͹صr\r׵ǔ  }EŒ.fqܡkwOqi䨙9CDȠvN]FuuIh`ZzN_6c;*^򔻨@9o~g| @o`FwoR>2l^؀6OQ:hflOjɒ٢f_rl`Ć0_~pQ_48Mwrpc t@@5i|ctJcvU}N@]ݎ~z3r&CcY}m\@@Sy5>'$}c     }H%}bvPr8\E*Wz0 %)ZJk%,]zMj̅*^(\_V]?#Ϭ.Ѯ"   @eiR2@@@@@>gK     = @9     @ >[ZF@@@@=)@@@@@O@w2     @dN     }ϖ@@@@@z@@w s @@@@@ }     {S     te@@@@@ Ȝ@@@@@tw--#    @     '@li@@@@@t2@@@@@>gK     = @9     @ >[ZF@@@@=)@@@@@O@w2     @=    @ѣ>U\ңd @_`Fw_@@@@pRvtR*   &p0)UU\\\_~</ v+C@@@@@ m@@@@@U@^     `n     /@@@@@tD%@@@@@G W~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@Qt;ꕡ_     v 趋J     *@Q B@@@@K@]LTB@@@@pTݎze     ]b     v+C@@@@@ m@@@@@U@^     `n     /@@@@@vm"   }O2&bQ2"@ 0_B    8n@@@@{%d     s 9=%[\#{ʠ OV/?~ l   |5RQY)~œWHuu7@/Rbޮ-?G @oثܷ$N/<ۆr"8O<{0vy N"p$)Uv8")Y{JΨ'a!dy2wDquumS\|8eKxm:V=9ٶCyu(^>rd*0Ђ}dJHa4G$YḎG}@࿚Cs$0~Gc m'e˱sN "Q~A=,JF//O͓=I/$D~6ǟcz7\Y*~ l۴SB||PVV6n!kqSw(7ZM2;#g_~Mܽ @ mK8wndB|{X@T` r J@~~:ٺ,5M"Z|]kr[n:zPxb[m~>-c (S_DRTiHZ+E%]]oT/*-?<$ϐll! YQ@@@AAnj@i-?XK |<=ZgޡSÞ4)xvW?\׮UtfȟC՝mT?}ZfLs&7O9V3G@`SU_V+c[ߑT՞%5u3*ˮ:%a(gHQyD{IT]9QP-z<>n2r3˟kHZ] qo̝Aׯ?m:JY)jvg۹*qNĆ{Ej\2a:IA@,\T5;XXjxyڻ[Rl6f6 !pՂ92od_ʴLcWFfU4?eN ܠW?3k^K$j`V+TQcG fclD@tw#nomٯ2O2F3HF1wy23Yq~}PWAT|dC yP8^Cui~Ԛ?Q* m.iyUia/:ȫs=1<8Q$Oyby,^N؝Z*ؐ![J9{ߦZ@BN<ف"9MK?)} 9Dr(O!']FHDP3P/Tʔ=ˍc|]l(  m$gHJfxYã۬r8ZrL=B`eowQJ v$:W936R«Ҍ;&mw|yoU7r~?RVY-Ϟ&+Tpk3Ϙ$^yi5ކeR[_߰]ۯL&j\cǪIΑ F&*ڞ tkڥ#ڠܷ,<-T2e dן&6 &o~z~\t0O/:u"`"+7sY&oΕˇ F˧}WZJ|x3;V%Ho;@F:R,$;NFl2B3~ TD&$_m1sN|Y7/7\|IsM3Bo\HHPDߖ_=nE 4),I ]Q@IDATV}ݺO2㎆HjFlS/:o=<*BOd/]r& ?[w?6vD?xV/LRt[uѯ t+G?@ t5wNj/dy1RnT'jyZ5CFs4BVfHʳ+bdІ}3Ik ._")ݏ-0[ܧӁn=S\(71}qd6㖗 ",0WCr,|PR4lEJSr T"&J7Vj<jkzaCYUܴAKX.u ScQ}iCe,SprYrP}Q`}jVA _ull_`8  J #驗,|Vt|[urKEݶWYU7\.p {U Am*ko/e<|7w``m2wxYqb\U>T_$/]'r#]Tھ}+ fkwuo0u7,_nqz@.?Yp_@fmP#ȭ{g {6gSWG7>ΘsxOwT͞asfqF@\ @kY, uo6,W/A:o?^fޞjqʆ =+R]쵏>j@ GBMaGceV}#К@Z`̸ a1VUu/](Z-˕ZTyU R9^t=GIPm'oBժsխvwȡr !8F M}vۨNܯ \cj$if_ݚ#kw҅7Gɼs  yH+_|etC jILXgUWe|#u)n|Bytqs'o1By؍:hC@@ҳMw6i|ӍG7,״srEʪjy^/ }W@䗲*}h-,$H]T~{/oZIu^}W@GJr%o8%i%\ϩ- %F`uPceg ydY1||߻&_%uF u,-GiFzy<]$UNm9gU[:p_K<,m˓#YPZֹNVb9^!Ϭ*-ZwBͦ8-/o=)Y/ZYwRe")RL( ifeE(Z8Hg)?t-:xpĩ0=Zn|L-:dO!j&=Q!ۓK򯛆ԘΥ-n:3r񗫣%ݺ^  @},'|>U 퐔l#rْNǽe:d`HzI٫R5y:}/5!1(\Va2K9>j1K͔϶l׬IyoXM2nl9U?c:w93Tz=󼤬\/jaqcsAN 6PdPר P;}IoW]o&w* | wU0 1fWyHNaW  F}E0}Գtܻ0Ҽṿ hջɲ#xw GmU*EZYtSL)>-*{ Un=@)*7ݢ),͹Iu,_#*pe݇2˭f1k7K17"I$~n͡\Ns뇹qaYg@@ n'gJZV`uEʲsb_[UMhPS~ xz2)&xvnE/0ٴ:`:{ۜܶSv>f<̕}<ݓF7ȡCdΔq/>Q6ngbIѴgA@fL͋yo <]0bXlS~)~!$B7Pt @ӶR(?!m*7u T*G{yT:`_(jfH:5BC|Jlxp\k۽r^LRm5p 9D޿{ʚܼ0՞=l9W?VaIߦ u@@%!-]J+MͮQ9CE/וE*S3多ͪg&Gq]yBJT=+yED ,1LotWTZ&5'jW=#ݙ]\\]E/j𛟝ɂ",vґ{OߺU@u7Hg ӿt/>:;#Cc+g?  )n;cs<rz@@@@@Z uIk:N/jaY@N %nqBg9w8w*wJ^<*C. g/QKHV<8C)_:zަU]*.uo{@@@@ }^F@@@' u]lZ%y@@@@@|@@@@@z^}<     n>     Z@w|t@@@@@@7@@@@@^-@W_>:     @     @ ݫ/G@@@@ g@@@@@W ՗#    3     ЫtG@@@@@t@@@@@{          jݽy@@@@@|@@@@@z^}<     n>     Z@w|t@@@@@@7@@@@@^-ޗWV#   R vpd;G@"r%     T @@@M@bxH~ y0^@QW~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@QΜ9co ~_ um6F@@@@ XDH/G3eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@n}t\ Tz:~ lֆr0UŮmYvRDJt]P1d*+Cwߵ۝V*'Kj"キ@ONǻ+y;C Kqix{y$B@  D~JR>{0vfmmx Y@TڻlUkڟNsk喹:J!yҖ\ybVxw|-ZU&m@8"9sFn^C'[ml /]جΖ{=%Dԫ Iq#eقͭɩՏ%)=C>2kt!B]bo(6duw]wu-uwbbETD&"@$@H!7;d&II{>qfxs=ON~kN:K]姷Pc;L@uٲ7VR3Jdx?j\\jˋӧψ7l@_ptlxEXN庙:_~7Ny0wxT2vTJL<&KLQ*3+':ظslz| Of tw zfev3*)q2r`?YiڸUf]tAeVd:LɯF,%*Iü!`U.pa^ݢ$^351=f@˘E%e#u>>@@*Ujbbd6{@ogOjمeSxJB;xim͖~ J*뮵_;FFE7]m39T=r8ft|wYw0GlI(ЋZ߉'HQiyT-Sj<<]={dyrDҎ;{sóN͛/iΣr+qrZW.2{brſ ]:8-Ӑ7 uoC9r%\~ś3\l`ܳ_MzPs__+K>]8=?$iQ:.Wbbe#mUj|c6@hvZ F>.3]tլSǶlr<5ݪl@wV^^,'3ltZ9-)$L~=Kn@}eѧ_Y5̺̭E3sK$D3*[ a|YPzt#Sd]2qcId+}3ZۛK[vJxH:Pz ˔`IHJ R ri5r' P?jsy=Gʳy6lmHQm Ǟ(yok:/ynvojTՠ){'Xu+2_??*^~ r =«n' ª[1 r7tAݪͯMSYI}%D M[A2?XBl_}֬7 oϓ/%u1奃rpy>bΗ yV]*9Ų2Z/mmfn9ReϱBU=ݬ2 >$)Di?'  pL=+f\;I.rlk i)/F} &~.5V)z̀~#u <)`y+ڔQCs }l kT[ aOW)c/ՀZԿgrؿ{2 tlرZOg \]1nZJM_O@tԦ{J1mɔG qu'9gs߷0NJ4הp r;-|ޘl.;AּZS9H[%@jAnWi&jo+_͑/oS4̼2+C䶯犑@{^W2=7q]|9$T7|p8_``eaw Ʌ5&m2 O 6K^L:3 4%J|VF}^Vy ^=`#'V.-mq&Wu&Cմ]oB58֝pr޿/E'H~{ʠ[ rɴ cڛOW}'qIVYatku̔kf^hv23[~uluHE2-ho>>^RZ^.Y:;.<# pZ]{_EMsz;o&d?B3v?o}8׏泦nm؎$ҪֆiVwKI*]՚v? qp\wm}:( ƞգo%sP@1ЮLn]},-8iC0@M Scm-T˖["kilw흮m|H6eJO-+jKlte|L?Jʲ#??Yz|b<0zJSߖ 钩z".@wT0Q/4uNi&@{ձyle~œ&v\ KV>i?&ҷirn_6ŪI?LjG 'Z:v7m:XKk]sJw:mo(SW}%ֿUZdî֣[d|l]3@Z#ù[rCzX=~3CIet rLۏ};Uսv10Lhq;+Ͻ4ý9)mg/oHX[ƴ84d{u>:i iܗ;A@i?ʒMN*{y ˥TJim$"(nJ0ivxR47ݺh QC <0:c~oI)l nvreii$tU:}o+WvfRsJeVۏ!Swi[{}E.c}D`5aю6uCM]SiY"5C{l` Yl94,  a:uIvk=zPUq{3ztl&ݮP넛9tj^=S"g֎3\z,Y5Ek5s^<2tZ2,Ձ-M`dgV7Ȯ5 @u 8j^5P`Q[kRȽ:XeKkv/Z}iZR4a3w'muRW{_;(e5޻bT'x'ުInҷ.[G-aZVEL'2VȩkF殃F8u\9"L]u:>C@ qcځ#Viݭ-m;GZAnSY:m"+'W$?8rXgSƎ^KIS+?I'R)]t7B6m(/WZ D;.ťV7Ww9gv{$$0 vO%N]䪩[ :t${^Me]xHtˆKNU}74Rks]\lѼ_=]LjJnQ(քlu],nZ([eWRecwU,3]`}h 9mvU}=S'ujvNe[^՚K. 8~nK2Y5f2]MѬ`Fm=gޔ烹IpIk'nn|gO>G@ ,f<קWR _;z\}DZVFk=hmM&!A~L/ cPφ[QP~ӎ˛Z'Ot7$H&*(pW%=+ۚ5@]zN=qeX}򋊥{>iC8" 9Gɓ˓%%({s~L\=+7x@րxBFlI(;$((r+z\׫ߦU?vz}/ڒr~sduf%AOe5So buL: bOlOBdfM/j-na:6W sXbXۏ{sd_g.XdGLb`;Z(ζ7e6,{§WEVWJ?^pϓ:؍umOy5z?>az%<#`eXTR*uZH[yӼ߄'}Θac~SLٟi&Xdi  pNJqYbg5}65N>%ťe2RM6OKN+AVyӲx0hw)W/@58&*EjcJM*#ZW*&2% I3W:p;Ô1,5's粽aduRÛ5?H?I^q(tы-gڟjI*U&}x 0y#/F~6ZWU;!99R^~J.];pZɘ5OJrLEg ΙR)\OHJ2mEId8|ֺL, C3ֳD!A)4][Adx--I8^(%݅C     4X@wX@@@@@t{Q      EG-3@@@hux ;2[Ɂd7@@@@@*@y@@@@h%[Ɂd7@@@@@*@y@@@@h%NEO7ώ2 @@@h1{U(;l2[     @ O@@@@@-@e?z    ym@@@@h[     @ O@@@@@-@e?z    ym@@@@h[     @@AQI}a~@@@@E yVuV@"@Fwk9     FFm@@@hk;_iKk`vA"*@F     n @@@@@St{ꑡ_     n v@@@@@t&,G?9*'d#5=Ruvy_sk@h&+$7@.4AEwo"~}Vm*1}dʸQ.=ncqǥL;>2kd2LD@]0Czvsc*{4Ys =/pG`}+37ɋSYy@@ |~|~c~B #OțKWȩӧ%( @5WcAy%R~{Jn]`1ٲj7.]j.@p)@% =U $[.ѱ{cs4]%pJM ݕ @@Kdھ|5[\mႢ"yϤ\~q2r`kVsWk7ȓ-OSj  FK@@@fؾR).A^~JN̔.j?wXˬ. r_5z[lQ=Dʞ%!)Y{ (@O<*'t^.EY"Y5"mVA m1G=6FN$QVjir- ^~?rEO{w0P_v\>ߕ-]D&9cry.o&ڷ'忳-;{gou~O[LY;%_%kFUPwʀy~N?k_ˏ˻3tpصC,I>SyT9R\978~SakB\>,?4DRя۸>8TurY8OsLKz^\7*B|-?_鴜 ּrO& S;^d׺ 8ڟ+;˨s 7eJ`mnalU=pDž[ Ik#C}嗳zJd|;KΖO6<:B|j. w^gkg+j`K_ˑu= ˧dh]dsBl[c#ǓƐϗNaa5i o.Jn)!A2NcݽdKNyQswo&oy97 @ΪwmC6vNk6 r7 Ue6YãZ?f KNu^L>4mx@ @ 01]7վjŽEY$HM$ysxKv`=[xRLf$֘הø5p{]}Y_ 17kMrO:& y m:o#>U%$kAsHM g\vĥ?(}}^g].K;ˏYAu=_{UyaUTߧx T|b$eXu n 쌍{?rux rowF;D2!{ݦNf̖Ano=.ڝc@uTEZM ^ ]X-}՛I^t Y梊`ebs=}~T$ ,#WE;ϴ|~!-qWt ϙT|%i|+fhe;YMafd= 7& ӳ9Y ri wm4|0&#jaElx=|iwksػn[{WCJ|1Y2J,.;!~zq-6@@79*_,]"en,, 嵏>_{UҾf7rȺm{4Ǻ\˻-xOw(1Ѷ-&sV.0+76g@$Vw/>-!}{5ʧN ׏l|yyN_ٖm_&GA5m_~{y[~g@h%2_k+{nZ2 w-Rr YJp3e@~u-ɋ?>ŖG.YZWHPҋnm31,7]"œ&nfX@&hu&i+ۖhxק_"]\рi&#r%^Syy޴.šTbg +˱,.ɝˎZz%$[?FYdIۭ~V%=b2wj61Lkor͠aڄaWgؓǎEQqd&T8Os6~rIr6A]N̔E:HAݿWFWks4@%ݭ𜛽,,6\fF Wd@ۃfq4{t;eudzWy4Y#SI-dYy8rd/޵,I˲S3C>@+W&DŽHH@lxJx`M'ʦ485#ֳ7(~j٘-Uꋷ~|<{@hW]4Y.?XYme'hPVndt0 ]';tDٸ]F #S+/N۴{& J̙).-]PT$&չhXH\TFkowPZ'GSN)ͫsYO E к' ׺'Ru"|D/މ8̴{F2p2Jt𭪅h v̀MѺ;ߧ˿'F^n'NtɅ2CrZ{mOrTG'z }廸R˿-;nV-sjq>t?;A@JYUKk @ڠ@]Y&'rL&cj:r]]:EyjEwv^sWonʩZuIXpe64{у/>&=.κ<]y@Zn^!0uig4ԓ~DL[OdguЌ/))=-կ3ۯ'ûwai%ۖY=8Pm(ttYo;Moy~νmszj{"C~Hƫ<`+'gMim:01*b>a7 Ւ&]m@od)sCօG˝%s|\8yòK   G O#I)zAwԷwm*ekdXV]Sk5R>ZZ WSc]ycu@Bd<9^"t ձorЮ UHM:O(+Oh|$_2u[]5귩rm 3_2xm>1vj1=/i=9_s0W.Nx6sp^/Qrm'ľbJ?Q$q,dg^qBXnt9tXʲe~!V;%ݍ9v&ʏYYIj:.:u goWc0Ls/..nΐX jxrL/W_fAWfPL%h.,.ا  @S R Z$,(HivdCU86ea shL_~IӁWp5jz  #$D_ 2kVe =(xXL]4|qlJ]; "娖0mMeªTY'[|ˬzk0[b4(m{ dh3sM {t/.BGS43? s߈#i%cDty2uPvg;&rھyc dw2m(ҋ~y @C:jlnW:}ޞT&4t[,LvHLT@ޔ ?Y$$VwsmWZk߻Lzض)ajgivqP_1AYws 8chx4:s{=iG6ױ:?UK\>,  9/~%RYI>9^ ]跷?`]Hy^rהƗJg@@#PZVYՙr2CrKdGӣu=٥!{𱧤D4{;S˵|wҀddHv^JhHt m q?q&ehrͭGMqsw#-Trĺ;1ӪV)NFjl6j0b~3m Y[x]z:=GʡNz~Cz4I۬7n>e\Fo~@@$m xs4cLrMy6_?;)@S:v~݃{JG3D|c3U"в>5Ho{A@@ڢ=?ZNrQksGFwk;J3߫*wB@@@ BhǙ=Dпwdtsvڭ5$2GF U m\N/#up;/:h2 _~~isJі;eB'P!   >Z̋;Ξ-#   D@ Jp*     h-y@@@@@ݜ     -Z@w>|t@@@@@@7     @ ݢG@@@@ 9     ТtG@@@@@ts     h-y@@@@@ݜ     -Z@w>|t@@@@@@7     @ ݢG@@@@ 9     ТtG@@@@@ts     h-y@@@@@ݜ     -Z@w>|t@@@@@@7     @ ݢG@@@@/AAQI}a~@@@@E }VyV@"@Fwk9     FFm@@@hk;bGڷ/خEЮ⹭y G~!    %@-&fB@@@@Tݞzd     [bb&@@@@@O G~!    %@-&fB@@@@Tݞzd     [bb&@@@@@O G~!    %@-&fB@@@@Tݞzd     [?2tev&#   N?d@V"@Fw+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vꎳ xu%L&A^_a-kX'SOip3iZK~SXT,%,ڵkɻB@hV oavӒWX$AFV^)p)+?QA/~+(s(>B@u-'G%DtdqX4n?~Zk'[}R\ZfGzu"w̺DzvjQDg@Xq8rT)F5_$'P|}tw6a>vv?(v1qBXHzLI G5ˁcT:Ez-ϐ5gB0;Vm&qǤžG{Ȗ-&pmZdx?j\;co|CuzVf>p=%kOuvKBRҧG/.;B&iS(;7OR|ԇ[]Xv<"ϿGw:7KL+k7XsLt79tD;i7^2UYBA|[@6QNL7^%=:;?'/_B=ﳭ;/l»#6@ZSl@S,QKN8 "O ]ïVɉҥS}Rzuwۺ r7rHe۬mgH8.&ӗz%d{򣛮9i鹫]P#m4_nrt.`B 50a`kD  vϩE͕UP&se\/vkܔ;bi-۾.Snok+泿u vnRCbeR7 vLѺZ۴(oV\Z*I'VYPT$G븀cJyF&Z|k6YãK0i;~$-=.朿o?LiU_rwQѮA^~\]"%VFdD}$&*ǜ=e07wr@Ζ).>^ZuB*Y SsI_7,I6nδʫT_jUeI<2E99J^["~*R+}^rה. ^.EejE0ք<}2)&X>~hĥɣ%ȷqyֹNq}/kNqxSTzJI|=pƐPy0U4|j68mkko5>Pmgzz"v7 @O0W>X*>|ߍW!-Д-9-Z].Υ}5:CTgS""=;rp_# >l޿;"{7 DuhWXYݝ%",ԭ՚vұP15ey2_T"L'wXeZM%w_{EJdo􎉝R߁[Xpf_)#GSd!2mĀ>Z7>@ nyeM\go˒2W6!ҚT*(gT} {aWt8_>y,{xtFnl~v\"};cjw͗_]dw-kSտ&TjMGr4#c-O.*c{I8w鴥d^ieNab_oR ;zlxXߦ?9&Y=%2[>>`eOt\}5~hzt 5%GXoN_>=&C{v|٨QffF2}p p m'>XcU#.%5@y?AclFK9rvd7OY|D3˘m?R\llx ԋɚPh`>ٷ\ϯTv%\0Yk,Vo&X1մ #k>L~tY" &g_9Cdwl@wdG[ar*ޒ*'eWؼ'V cDhu$sE:iʍۭ 铭@wfm^6xF6Svdѧ_YTΙuYm9M7w<ևV[.HW9\7ǂOIYuL?oAnS;j/&  N8 v`knNn+OJ"{G@x.{]53M[_>(嚝ge\`kEZ٧ɔ1e՗j Yi N` {Ȇ$ tlرOg \]1ߗ[ZM_ l?gj 0uM9|\[81Yڗi : ͤ]pom/oɰJb<(^NjV..׼P3;UbdGbʨ6-ֆtk;x` /t:yy@72-h}$[>Ln{5#m->& ӳ9Y ri wm$k_` k[>dzLAn3VO@rfz;߶h1}ﭽ+fڐ%_4dۛ#NX[:gwjj r_>o`f7uْU߬mɯ%}le|ΕE@.By5 jmb]Mp͔vxշHK~M6Ț=о_zҲr-+ZwI,ñr ٶx-/DVk{v}i٪S<4h؛}~{ rO$eML˧ogs?2 f_/^Z)zǞHa_ +Ta1֘oS{XxMOօ @cZ]1-uYSٴؤ"vպ\ Hhf6Wͱʛ9+L3\kXlM9%ZŴ}!_khoZnD냊@޸a q9T &vح_|`~-  ^mso,iV&;An#ֺvn;dԦ rm /ۨ33KfWi$}G/3Bc mVn}P={m4=wJu\aJ ʃ,[?i=E?T#jbxo#>1n75t o]g\մsӋ%W cV&}iKll 0J)mL۠5M\+1leCZim̐?tj{ /ь4M%z@S Uˈ=.7ey3Liژf2 h='YˋIKi^sM8-GSt^A{Ӓ5pU&8Sӳyt^J6XA&{I\%&&m껍2OOy@ +ngږ{vmjگܸMko,A*^'jz6[cž>>k @=RٺǗ!3 W3@O8dݾwJ,ޚ%_ˑgW$g.ࣺ6?݅$ޭSaVw_ֶNzPR(]!$$]s'w2It\9we2T6SRdp57>ޜ =ؖ4,Mpr!Sea g'' RmG"J^I naWMnor|TbWTV7ehۛr -OoPzwmџY&QJS3fORF kw~u#϶ (@ Pc$jV[kQǎc=W&}2۪ef#61E \di@z0awVAM4Ͽk̟ea&WWXPT`uT}ɼ\NN׫iUиs uMΞ*DZ&T ޺ݫo©e6 P\0ǶsՌ'ɚ>Tݔ$l)ĴHTڅכO-fUo߻ȤgԂ~MͮI:So8nIO!;:8OQ/bf8dUIZ7Mֻkk WǧQdM\쓴@u%~}8j(vEx T&|e-5^=6p(K[^}2O0rӷM^^}vebL(DOt2''kڇUK oǩ5m&tnRE^&H)]*YYp cNvWusۍtׂݪ=^aqތIڏZf(@%@w'n^4%D ^^4U0u!-KV~IAx syaZ_kdؚNL7i*(l+{J1Z7H 3><ЫTcVAl 4C7Jq R+7v?X귂j}bQ2̓jByØ~얉 @/g_^lEݗޏ4ofFoc O q_.f6H dSObw]?ewNOmMX ' ; _.ąKbof^!M&(ɪyud⠣X}03;)0,Cd)HL`(p`yiM؞< z;a[ien {C>3I^oÞ A|}]S_?U/?u T^[wQJ8iZ59ywm7-^2J(@ P3 Unӂh$8W@IDATطi?kKmI.U00H2s J T+/%uèN>9~a!pws1a- 9fP\u͙@Rer&ugfs;ly f g`~ CHn~V[_j_q\mV탚w],^.P-/~ž 77c&e Z`]6L5mN7c+@~Tsuq;fh'޸/:j_X5_-7$\kn&_a=$PG5>W>C yP`-\U߳%Kd%;꧱[c,:Ât\0NԖNٕPlUqi5V1Wi!/7듙wqAMB͑X$Z >7]3in[JkS?z=W潎1R;{ r[^VI&؀-mx74j (@{pɣ!>`\fo}P1R'k/EP˚!q|J:~`iӲ'Λgϛ/%%|Pu2a U [EY l}|@7jk}-wl]{TxI"er̩g 1Xe3Gi1o(@ Pm$MfZW5yU_WRYi!um4&vBU"FJp#ܠʀZԭT\NL)3AuΧaeJy* զαDmok m2̇STƸmJt󩠬-mP,/z{CP%XRJ0\_o^gåAeJ.<jTl9/Ea`ȗkQu ߗɵL*@]֥zi-/W P:*geu:@o< ӌ LSdHy82;?UU3BHlkKLF|Kj50^8@gP%c=L Tdgކgᣒ"Jo}#ɴQCr=(@ 3ϺΤͶ[: 2OS e!cEK~li*<-4V2JzYʆ-̑I5*V,Q2QiS zlη?G P ӳRbR6A4@~M9ĸ:v$\ϟ*O%]3+(`O趧eefe-WQk xa޵x(@-2ySrqE=zJJwJ س3q(@ PCU?þƫ{!S(@&@wG#MO+xw?]3͎LSrנW3KSB1eFZZ؞_P7cAY,ߙ2QgaLNF P@p怅qK$|wWD P_Kߜg(@ P(@ PNXNnI ty./@ P(@ P(@ Pkq(@ P(@ P(@7?(@ P(@ P(` t)@ P(@ P(@ Pn~(@ P(@ P(@ Pz<(@ P(@ PhG'Ox* P-֭[;趃!R( ?ޝS@GPmOGC P:kn> ܅(@ @_Bn/y(ЀI"{ 2%A\M P4 dqk3Qgyuz[KP(@/PtC)@ Ph@uu j /kG-@B&N t0e^2wlQ1(@ P-ȭZ|(@bܓ@P(@aq)r Y&3$ט;m6SqG P(uAQniv0a럕=R(@ P]E@]]oOwdd ΒUn((@ tTVA5'7Nq\(@ Po *ˤ܆TQ6nlr(@ ر@]6Lĭ(@ P(@ QU[v ۩_Ky P(`M@ϢPdvcQ(@ P,!*v[zD=On957]vOD P:5%\y tw;K(@ P@puqH۱n {őP(Х inU[6F P(@ PԼ@'ktN'u1m7ç(@ P~YݒI/p(@ P= ?C, tӧc(@ P _/dC P(@-pǹN7Q(U*wI P(@! @wXq(@ ta= )@ P(@$`Yܦd tjp(@ P(@ P@0 pwnH P(@ P(@ P3 85K˛z(@ P'n^SSjTTVۻN|K P(@ P6 0&&D P(@ P(@ PUݞZ8. P(`7*8;2nnJ P(@ t8(@ P(@ P"@wS/(@ P(@ P(@ t8;-(@ P(@ P(@ P) t7ER(@ P(@ P@`(@ @IiҳrPX\KK}Ծ~s+RdYyyYuMMZghʜ(@ P*@9(@ o/k%iρi i|=wm_hfEzަL+keF P(@{`^M PZY@; -zUc-s(@ P(@"@wG(@ P8'OĎ-FP4:x As(@ P(ХRK PN-yA;d gd確&4[o-lyj]gBkMO9Tl1VUUA]JUGnp? P(@ 8(@ PJW`ONCLL=(@CQVUҤoDwm[} I 9’28;:bpq6{~=nbCn+? G'bTd?+M6fZl?xť߮6M[eL0 ڰFnAl(@ P= 8}z5ǫ)@ P'2c!?{*e22"НW>e8W>^Xm7oߋ| c_>%[L|: a(#]2kR2lkEE^] S0aP~G_w_㾶G?@[ ~ R1wO qWSƯ-{$>oLk5T65u=()-óK>CJf"tڸ1 ڃr>Ӧ2uF'wx橮5!9{U vӃL P(yLcwAftwޏ*(@ 4I`!uv t=h֗*db]%پlK NcMy:vŷ;ꨖ߿/W,?d bj] l_6nU5g<>g⩻?o t<\߽M^;4]} k1o~\2ʃZpZ @e+3iq\hn(7mKU r?s2.;ݘ!~"56P.S(@ P X~(@ Ph o)1rPJ钄T*e2ypIpdn r;6H[<#Y&njxy"#'O zyxCͪUUטΕw^{1ȭ=qzAd9я-zx׵6ktLc[PoN5}&UYglV۬Xcv.7䶶/Q(@ Pmwc(@ Pm$0Iڻ`(K`Z7|d0;clB ٶ,ɈV-UߨjFu6ׯ˭^j3M'klxN8kY;KV:Dߧ-_UvvJz6<\]dq*NmS}a\R߂z5-]q<9v3ʟ4?S(@ P趷;R(@6%gw n%.2I}+뷄tmUJf6oFDHd# P;GQ&M!p=0icLשnBKИe P(@ س|8v P(.N?b6ܧ|VQ0t LeYA2#;G멿M>z{zI%P+(,QvB4,xg'_ӤLr'(@ Pv'vw8` P(жF dZrcDZ=>⓬no˕kҺۣ.}:kU*3\[3ssQ$>z x[&fRf}|p_.3&o(@ PNne0(@ P=BCwkO7ceM60}Ё4嵻7>(߃Z#qr]e=Scu#ʌ@jZ۶G nTy⊳@H)s Oap3k_4QA -W^%~Ҳp% dyJ P(@{v4!xɨU?kF~TCi49(@ PߵOTUU^q7iDP/Tuk4eSTZ^fYܧ꿭ssֶ*)ue`7wؤffWxՉ)W&'e"Bul(@ P]W`wQI q*URLvx8f P^(@ P(:[]Fr)@ P(@ P(@ Ps09#(@ P(@ P@`z^8(@ P(@ P(@!9.WYyq*Ą~>jIhPX^i~rsl9{aDO6;';(@ P(@ Pm!@w[v>rptwĔ޸gAF$Wٵ.Xw+I|>bC4н8J@ݴ]31/^t)@ P(@ P@`ߢ3Լr\a$e#2 Şb>?c 0J { 3/;(@ P(@ P-@w{NJ%+߉ւܗ k@ڒ%oGW&ҷuGn)HWT& tA@ P(@ P] p2J]3mZل.x~ :1g/ʤY3 (@ P(@ P)>NWLүGˏȕgi?ԺٟjNi%p$ePMij#)%zj̎ТSKPZQWYqbgsTrXScw1i7/Cs+]-OuV'c9 JڭEJ*Uj5,E,7lj2T7rp_'G&ll_sqm9ߤWF<.nxL P(@ P:Nt;tƷpvֳF[=aZ2-R{1~09}\Otp*xef#] N|98X?ozX/ƾ7kyu)xdE"f 1n?3}qńmzvvL/oE`4rOθ#WO'p 0V+>M*ҙxeu5Lk1AxG]Σިr͙H3<P"bX85lHyxs,̸Ochc^cSs-9;}Ao]8(L=LxHs Kzk7ނGW$=(*1<PߪX"ű4(@ P(@ Pv(`k!@yU1HoTgvLrsG2{y?'r*px~yF(RrqQZwC}WTbk^¤j*ڵB`ZSɟoρҤuϯJĮb yv(~c= by8ŋk0;L|)S{nyxk17L.UGڏX[Wg! 򰢡cTe'UXC|q 1yib3pC>XyP-}/DrBb=B}6*}☖:Xt(@ P(@ PNm'7!I{5vRFapA5K;7ꎫ@,YN"Qb$cًdv0ɸK:gW%cKl!^/d^5Ye%@6YX)0>Z GW2)c3{l}^8"g}%b z8`#3~em|3vSc K&⇽)oD4*uhLoоߋ{3 .ߒM2 >]4#ɆT-nRE-Aw=La:1Nq籈I/7~'`s\VΎ08o\uFjSAiYփP+vgUM))ool r[Ԃ*R*!iۖnU 74:FAW ?.%yj Zw-v2@^1ȭ֫,J6)R|(@ P(@ P(pʮ=^9*.NG޼a׻y6pTD!0[.uU] k'Xl閴ͱR"zzXFyW*Jt`7R6bB.TjAe}6-$kY65⑔R sgjA H֭^'[eߚp_mTdwѴ`s}{E64LXW+UZ}"|'$Jk9ml|,Yҟl|"cv WT*}@;PW%g׏9ի,6m@=(HuNv۬P\4ֲ2~[K~J P(@ PQ&t1y8kդw%ܮ?LO*V%@]_?e7V 8N/Rԓy_Ke'>ޔhj&<-f *baJ\ޯdOoNfzXX{\WXmG=:"SܯxM21IM0=hӷ^VUPW]&!cvמgL6APXUݢNlvr䙦ǩ8Uymk_)@ P(@ P= t@wkg8Mm#*)PeU}ZVmɒU?zKzXLWk4Ne09X<Նߪr=.ugIjN W jMgnIZw_djY0rM6-yUjiJŴekkƹҤ&B̛w{94gl<(@ P(@ @ t5XWUꦣUR2Kʁtvu6ddk-Uth6V8Ɋ[g?3b\-иXMkO拧Ea?]fKm]'Rڲ{ޕm6Ee[:ݛ`oq{9r]܇(@ P(@ @i=ly,cZ۰ST[MjPVXnϷdMS뢥p;p?W\;!P$d,ओdA_9r; xv0daLI|1}q^^ѡdTw>A9i5+$}G]`0KF'U;0:<*/bfȆ5^-V%i}^ ?[w<(@ P(@ tliOǾ5U_e;db¥[OP0Q2az ~<MV5,*lۚ|Dz]H.eg"яCˎ7 w:L.*|{B hI]랁hy/Vf(5˩yØ~o46~}]ql%2Y]a?vCKjHj )cWJm~D*c8&GcT뵸tp(@ P(@+Д=۲wAr|>a1q@r155X4=*;ZoE|w.~0.C2ƘB\4& Y/w7r×#qGp8؛}$ %@dZ/QᲱfgv &Q݇W$og`DOCcY1==%[_I7*<< ǰ$ʃ'ۡ<\1>s^Ϸf`CtҚ|xUΏ;&Tc >.Ñ峵HV[]j?\4;ޔL$C|0;DV9^~.XR۾~ۗ\? Oxa?ޖ6;4g6 zy1[:R(@ P(@&@wG#dVԠF~+*0vhž\yT Exk}d˿EbSgfh 7eGU$:9J pAw;uTV%X%3zcJ*@.&Iv{πtiUrC));ބ *#yVʄnbVDx[5ɢ7oYe &kEzݟyAZ໡m] ǀPS&n=(LZ5r->.ZS̍ 9<"oKdzZD3(ZF~PYQ ooC9.C P(@ PvENUAbxUڲ҂=6ft]k1{8bjV'~ɺ^+*ꧩMŇFxj?M=)`g۲_[ܪ?geBHSNլ}Y2C$ڜ>㱥dF< QApUrg|6M<Z=ZԌ%t6?3sߚCCr=(@ P(@ P,,w#Z'vC t6/2;uz(p}y^ P(@ P@ tN(@ P(@ P(@ P 4\g?MLhI@2PT&Va~u5wD"#{x݌(@ P(@ Pg(@ P(@ PZn}<(@ P(@ P(@ 0(@ P(@ P(@ صv}8x P(@ P(@ P` P(@ P(@ Pkq(@ P(@ P(@7?(@ P(@ P(`N.@_^M؝)@ P(@ P(@ PNLcz,Nne趂U(@ P(@ P(`?F P(@I&߱Xdf.>t @G(̘Jh*<U5pC-ڿRSqt\\\cO7/;u7;#,4'oO~''p.(@ Ph@ť-<%(@ PP'OjAjTTVˣ"(QAv֜+{_3GM>AvY{7|$~֜U*mrӺwqtEEq!g`sVT7n1s{6oʂ3v\]Cc/AQǦt&jc/+mrnO*OH P;K(@ PI`ٱZ{dL p.DjQ1]zU0_7 r[M鿴X"lkXgP/@w\qUig=3%mc٨z2{ߔ7AAW&DU;߿)]qf<`ތ7s<=<=ZzlؼO~B݃f\(@ Ph@UUK6 P(@ lnIFI "h?-eG" ]t\|8hkI A#ZvSݔ$ՂdEpfȚv!gǴ@w7 J9tu<.oYp`bN4ONMC\b|-yPZ^4͎{QR(@N @w'f^(@ P@=UZ@gҡߪ+;3viKOB$m#$0P jB$X/wg2 \N̔ 89,JsWcFfV6BTnxTߦ--=eڸƑ2t J7[h.z֮gV"#;G/$(r̚U:(@ Pn?k(@ tX pjKDavv$z~bmӲn  6{!4W {“}'r;hf>#=<:cIY2" _[vA^A\ppKWQw~u"7@x1j}W[ƣٲn]\Zk/^W|<h5u > 8s sMlٽ??,}Ev/lu݅%%xpXWKo fG >h O<!Pxy@fj69;|Ř5iLGK_1o6 P(@`}y P({6T6 v-g9}Ά_cƆ#gae6PmȊO^;%ydd_ظm'\\\g(se2J{匧e5[s (@ P0͏(@ P& K\ Pa58c8L>ުRjIbgm\Xedϛ9UnD o/ ʝTʤELj A3}x>{ҿW~jgݱ,z9QǩVV^7y5%%}<?kY CO_U>3Peޫ )U7sc P(@v`]y P(`?B!Xsw"ɘsE8;XWAhl.F/oIyvCtUS~0nI]n"H^M7ieRJ2jk]ҿ~ݷ,B^jKղsݚ5[jNK ߾TvN^qyxwo?`ƤڃN|y@$c\<'(@ PJ twk(@ P6 ;? 6O_kуc7Gu–}}Sus?0ڼ I-nj$9-֏Sצ5O/}Z"5U+H9яnϚ={D+inxmɧ8g xֻl.R(@ 0mw(@ P&#O=j5mi[{"8=OܫGvsMiS#Y&TmxDuR X7w1#80.P(@ tNiR(@'Ϸv9GBe~x~|gk lĐZW %LZ_[Yj۠υz ōW1q($Ʉm>(@ P twˢ(@ PMlpǵma s7x8y`zT}{HmzA!DjYfE8WUu5R$빵۲}0{D <}[G]-nn@IDATз6̬xZPVV.uϗk ?K P(>,]>< (@ Pvb~ňʎ†v3- _bZqV nN t?=bdt /9!ƅE@T"6/5sivg|Q~U˵rqv ϝ6ŗjݟP?8993ꂻZݖۻfI]{~vs"48l}s 7.s(@ Pc tNH:im'O֮OyE/l(@ P-~ג߳jjNJ U:掬d5#oUC.Oq%U%H*LFAE½v'.)-Exh_aR s $Tx<8::"!Af6(C(@ P;]Qpuq&vt ׵~sϞ.n{[+(@ PF#; ./(@ PhT9e;P(@ P(@ P@G`#(@ P(@ P`Q"@ P(@ P(@ PY|w86 P(@ P(@ PhTF(@ P(@ P(@ td;(@ P(@ P(@ PQ%(@ P(@ P(Бwc(@ P(@ P(@Fn;P(@ P(@ P@G`#(@ P(@ P`Q"@ P(@ P(@ PY#c@S K-␡wXUV,Ǘ۳q s*%$ɇC7% x(@ P(@ tM}WW>;aqm_X*+^YhF>_W.y%5ڟOwGy.!ESjgwx8yhKJPRYjܷ[nput;9_FRQ2 g^RW]Y{V>%t񀻣{6ûC<4jjjp«1h@{֝{pjvٿYr<(@ Pt?yM/,ݖNJ}᱕I؞P/ꁑ A[iP_l;^F0i(ЕYBˇƺikvSlHSd|uvpa89pvp6W ee$3$%¡d zz¡ͣs/7 Aۯ[#8LTM]`\%|89m꬝wJLI^VVgnٳ)' P(@`$i,ֹSzQ (D!8~47F r 硛O}#<s F0IāCR/c;*0T༥\V 6|}[=oDq3(@ P@ m!ܙAL5FvQ͝s+QP&f5|쨮%)2)ɓuTPRNj\xM]E^{{$~GS_I%P6=9^1ḪҤcgU#!S>iEejgT8ع蠑ef/rG.myB ZBԶحgӷ뚻",$A[P?Bj7Tr!ٹ WuSdž;hNj;-=C*oSƣ7mEE̖82s m4ǿ~|O P(@%n~M`L>E\:b@1ձM(2baҽaVkyUx |#Gk|>>j\Q)DI)3S{g+ΗZg¸s# #ߺ wVUtsG%A^V{Wfr4Nd{gqD ?$׳5T  hƂ !xy8b!EM(IUk!E~0~U/pRSXutmz+ |>3/i:WwL:#x-zkKR @g Wчm|=՟ N\(pʫ Pm*X1Dv׷Ld馕#1EJq JJ&ڶ}5)TWe!4fρCx7Dn^^|m̧={㡧ǞC1y<)L'e g͞oZW'jIxwXi+r $PQ͚-l |j .~6؃,m];]Ҳ2|XZTT:/8*}Y (@ P`T:jΑdL LQeFIw}3L'= YW/CUI sޞK̕MR 4=UvOc%}<0{7zJP3)dl%3C+RN´^mdv;$;Yo:vxoWdJ>@tXE}W' 01ϯNG toxɆ~-̥dyaZi3zPahdKg<]w.!ګiFf4\>>P231'KvvIMSꂵ*VV^X6 ֩T)=w_HDHfjlȲR*; rDžBC7[oj\okiƴ}/|{]=U}L1|%ޤn)+3}C\qT*J2Rl*SR*?nVro rf(;@YlU+)W˗r݌l37lZ.姕3yoaZoTľx^Zt[X_i۰&7X۞~=p[P* Ty'"",XD2U[j͜u냤t,rx{I 0 4:6dRKݬqM8^[ܾwJ{mJYxAnjcМK r:88?{UIB iR (T,mooZv{C,*v@) M){O&o23$3$$?kw]Ţw Ʋ +ɶB@!  P 6|Jq$O O_l~K5Xp]d+ f:ypByMݧzQ?:vYlJ(1)Uڴ̼>`.ٟW _fsmL]67wnlum B9Z2ޜݭѿTA}l9S@>$Ƈ?:OYLo 7.n>9qe_SV7ې=_gKTezG(|t|d]MAßMP8 8YvqL*#t/,q,uEh/9 ۮv#>?^X6Zzة>/<1r8&D^Ժq**VtuvƊ?BzgF׵zМ:?8a!Bޚ_gi6?jezoRrLd)})_gNÄѣj]Y ! B@"%L( yR,&Qo;Gy)!.\ɀ ?eWK2sP`9(XeJ_H ldGk{@I(ú#9ud?o'i9;jz`j? E* ɖ r ƕE+9D?'NcIDȆBOC=4*1dGSpL{+9.@!` +Xu2=a(&#KN$mpnWwCQ\yb3Vvd_B5z [6Ԯ%Zm /nk+fS'gwZFVZ5}!vYBa4Z7(Cc~v ·?Χ˱x ̘<:nVm_B@! B:Zɖ}1wuxCHYlVwgEVUt"YauBĮ-S5jMmm:kxlJ6)s/elнlO6n99{V,ϣ>۔xv$?"S-]eK&=|g[+nz Zc(6BIYB%C縵ؐ=a_kUH$RV䡪ޚ!oݨB^yRZa0} 1pʄnunJ;]ѦL19 מ7"~ c֝Xi3l܂CNok3! B@!`!y.XdҌ'H@OzpQDԗ֧"2/VV+I`5_Zi?jW+Ir"儺~H(3Ú<H"QW,}OcdZ_ }:gA篨z0 qȧsV^Jۓ_!>ٞ^O粙<&1ɣZ3tK,Ʃ'-kk=WƯ{svswxs+/ŖB{_:;~ǹleg?wMaWtLB-T“5~~Wn ٗ(d4dl!"ƍ.|-;.;'zpEOg徉m9=i9;A֩gK p5znf`oiz'^g;H Y^9axzU"%gডK>$$^T25o?)+_St7{s.?&vo>#Y:Ylғ,3\[GZFa!)6 it/yI׬&D }Imdo(< !=)ӿC1hB}yEWB@! B"لn 5Ѷ!?cNpuCcjaf;Yax턇[.17xQ=]̭5fv3ynLdsŠW>f5"m ! B@! BDn)A(7z)>JPmk¥]s 8҆ B@! B@! . @-B@! B@! B@ ݭAUymvݒ+9uYFǝ/US3//BMM 7{\]n<'܇?6=Niۮ=63wm!J;iix1jP̹~9Sη`%lyڢnfV6~߹O>X[G^~KJGC.B@"[4-B@@`sV:LJ>J¶ ؕxbE^8y4rz^*'$D0JO W9Vs]Wp/Eٽ;/LӰlbW1mm_[ь[:y v$p)5#'?M_fEϧ*}/++xm`k7}>^5OtgeROS:ꎿa͆M9;CH`ƎoSjTTV'+F+';B@! BwpVB@!ai)̲L\/T#$ ~N~zay!|23@֘zUU[P!wr(#l_+7}ݺM{{z*E5Ku<<.[Hv-E?ߴ}umKtt?YOn.N>x :93#&Ɵ)?ga ! B B@! h\ GFI?RKH=lLXB(\s^1֡wq0Q駯^[涯W g'8P&O}<G–KpK 6nh;2ܼ<|pqtKS ?"vݤoꩣ]5B@M@&Oz/B@P} (c#$C͝V'm5[ٸ8Z;b Mx~F|}MޞR}K(+׷ 5,;)&M~לdd mlÜs5lABR +* m7^Ml}昖 &nTs55M-_UU\cS˛S%<鏥'&C Mqpٰ1"7ՆB@! ZN薷)-! B@t НGLrb 2X3aۚp\lP^C|yMc#]E>_8; aGND>ƿgaEv.zIw #1fPJTd l?]&!mI0~x,w%9X_wؾ{/ri<__xlˇ”Y?y1taρ#3ixx]jqaw?bƭ1J!\5 sg׷1XiNż2srGvƎ^rrv=={i¿b$<{&&%#4&g5?(.-QC=HތuoCNn>Jϕ+w{SƍAfV:|OY4X('?LZLwaI .zQg*"=O,Ͽvb@^?j|}<?l<= oo̜R3Q)cF}ZKs><9 Wttp W-bı>4'B@[)B@D6V+Giu)Xf"$v~y>pS+m;l'FCQSSI;SCJt̰-i_ۈVq  i}#Ob׾Ų^'ۋI- 1TccT2{ _Փ'Btl8T sW,ZboD^EM;waݷ+Vx/?z[RbۮJ|k1j+c!J|N4$(P4ٰ?^z>E~쾻qSrᯛٯlCG ҃t&/ۉO@KX:QzW6B@Kݗ̥ ! Ba<$gpgND苲2B*ٗD,>[ukB޶|~.eɨkFlLجDS A sۯ ptUn8r* >"99jN8##:,ФݬjWƓᓧM]ۜն2hEnSQ5nYy"r :aQe3_zǺv1>Gzk\884(rs_#q۬kY,zyc؋Z7NE*XgCHT3ӾZ#,Ru—<9tGmng5C[V[n7,Ţ>kci0zTʘ?wUY*˪ڇqhZYGcUhVԙ89Ͱ ~P:f?x"7%lK o"M망oniB@Kݗ՗ ! Bv >>4I^W:)T#,0F{FYؼ38kcuiX}KQA^֔%j,fS'PsZF-u9T7i_=D].K;#&T9QڥqOrB۟bxXlfL{n7WwvTW]j$}Rb42 i 4X5Zc4Bwkhb%CM}_eXZT);/=G_i+ GY,B@Xݖ) ! BCpuU@ܽ#uVqAzI*k*fĺD>C縵ؐ=aoeVot;K“]=A2Mѷr۪?lg@jz:6n݉56+9:v_.~[}s6u=)9%C}PbGz} V)}G4MtjJR6&{'^~gb#BJ;B@! ,C@npVB@!С kĠҪR9e:HQellor1c/la%q[R^>6ܪEdtl k~]v^=oo}-^~=r&<iԑW<ݐL(:x@?PXlwpm V\:B™e$t 4Yk=Ide#;+nj€>MvR(qiBJpGxX"tgfjzYY9V@=d=7q+ ݇*tĠ |OnOg*77BvMXz&,jXB^{4^{4־ߧ[dR__ĞEEX%`!d[! zE^! Bek Nuª2kc*#}*twc|1.h,HځugSSlKWi%o.|<-̞1MJJ_A5^kQ~\}ȢHG~=Aw8o")#ylЀUXps̩CmeL:x߬^rX-߷bP>֛pS/ -7qt#M;v>4-ە'/߮ĊUk)',s N! $$`_0wu8B@c:mu1(O! B@! @g$-I! :1lWT#B@! B@! }]pB@! B@! B]QB@! B@! B# B%veB@! B@! B@F@vEeDW AWhѱGor`ae$< nc 5ݧ+T{na$!{O toRD;>e$vO [K#$n_//gzCedmٹ=jC>7RMmg(5aN *((j8{WTVnhuO_UU\}ݟܲ<{mLآ0[ËU۰6q~x=Dn2.B@t|񯡌@! -"%gȳ֞0P60JrL^,5K ˗č~Nng#44?朏37,M cMۧxGfR&ۛ_Gh1w{#]5?_8;95sD$^cGw?[L'-/6DƘaC*ӯSD-i0}^P[a0?joVϿm~ !ݰtwsҳ"7]3 ϿK-Ҳ2,GݸkזUWLᯖmݺJ}VXTM4elo 4MؖdzO wI7LbxSzQ1æ^&.kDv! 힀IB@!к+ͨS% !Pϭ[׺LoG?D-nQIWtY9Ͼًb$#$0@ٗ` g5?(.-7^777cېq/Jh~?o"3']!:6{ΜJآ|[Rv/zclڹ ]לx/?z[suPګd3g(+0FT]2[~H>nwzvոZ'4(t7fK$H8m(]\v5+HIdzN`Fa^q~eR!B@t"tw$B@! @(,S  XYZؘj$1݊=ZƔUVT^% 編% WAKG{"/rnxWNSvOu]tݲ`&j2=\1g3 *qgq&fUVǙÇOnsWoyuWOQn\hNԺeeXˊȭ3\%(}7=:iasF{msXc>фS.=7 ,7Mf~{fܙ8LFv.Q^U,yN ! B^[B@! L"frsFeCQJ%N( 1Ɋd`V7 B ]/\  cmǖ %o){M9:`j$x,Ŋf]ea#U^Ժq**VtuvƊ?BzfQu4}GX넯ۜv$j+nӏ[n7,Ţ>kci0z @dߊ: bu3څ͝a>%׳"`AuW]^}9qPv/62{C*ded)B@t|"twk(#B@! @E{u7ŕNSV-߳FPd1 Y < 0'U4gpWۖOC'`[Lrxe; YQ׃=SM@ia[KkePלն»ןij<9hI5nP@}_fc~v ·?Χ˱x ̘<:n~G\dQC[](Ǫ  ]G}=zۘGNenaJq7We^kA ! ݝ*B@!ȓ#112 Ͷ?ǭņ FPt_OEliKEKcN8}ޣ ef{(M#6;w&MLcr=o[oD8HMOƭ;ffٸėfk;AWbkdĜ7ߚ]Q] .`яNՅ6cϡy7| zbGɍ*twǑFZcB@! 糃J/B@!`: daDƢ-ݘP!N6ElL- a.V1\scR:LxCr"nu`>J#5&m95wӶup!F i4aIm:Gmv3zCIK۟,mIwBwsMJ7}S-ϖCfH< eE! \D\׳Cf4,ٚBg2J8y H"~h[lll>ӹ*ÔvPyp?MEz\J (ٰ\8uLhZnjEp"[#ش;J%/_6oGdtUHgK_6br( ShqGxLfe~O'\3I96^h-APT¢b,|=5<)Z֒K` 7Gܤ~r(*D%M|P+8 p7NU|v2'zLB@! @! %ZvĦ J_Gpu^/[&a͑Dۮ)C_|?=}ƒjܽNȳsVFg׳/Mw?ĮEvDOT8xc#i֝ G>ԒTe2,bδ,ei~5٦p=TjwMz O-I5/GdiT_nPߞ,-\,y%<[=cQwIi˖?h˱y-ۏ6YTVTA?7Ibk'6^yg1ƍÇ DetWV4e<" Т߱˾n9uzȢ] _}eCSW'qt۴v삵5|h[+7^xt-ڗK)JAaE7a/{/DTsqzHƾANBE4S .02`iD,ʃ^(,E\YTԔIzudC! XDX׫=Fpɮj'б]gL|=<[u{-a]qah;:uC9{v|?4vp=tAf[(ruJ2 j In̶D- $(:A~GݣFj?6]mNb"p |}etWj.h3un7٩{{|*FuC)w?oWbŪI & ņ(쯸|{h>2[KFۍK멱1V?Mݢrۍ~;rkz\cf،z<w7@AAZwHB! Dş׼e0D ZVԠ>޿῀4!BCt{ ^\sOMēW߾4 [">˭]XbcݣU:kgYwqV!CݍMtuߵSC*%Ź] &L,8%56_%ډRĞ"r% ;ew1ӳKoXY^z&uB'6MҲ2$$&!$8i;M G!`ޓ4咪$&2=Nsɓ6ٸB@!Ё2UWrOC2;U> ! B ߫}]pi: t7?6̟ ]k|05HV] H˯P` 'y79P L%p> p0VѸ&;.~S͹Jh\)4.k7y럱m)GBV<;HL0M(W"\5tLz 7"&gS;{6v91s\!F髽Mÿc<~T c|dB@! B@FF!mŭcһ#rIaxsߗ!> $`[T>}6$b=:Oͨo1e_9^=)ue fMm6'=Y(-Vwk=+i6]41Müd^887~Kb5>6j2se;'q,QӮZTK[6ϣ׈;Ž1s?c8Q}Gjp^ oGTJ)06xUGrQD2af j_-͹t$?En_fFsmAV̽"VAxlZ̏gn򶿣\^0ۏb ul91gD]9fN}mrx]@6[;f; =Q=s6,;CBgX{8 lz0u-M !uޭd[ ؘ G=ˆЖ9~' b7" ㋵a:WS@VGe[! B@! @' Bw}]5~:mL KN# xRfx0ooJ I{0N8+GLZus@8NKPNj|F"_nxkc $8]Pε4Hrwƴ~%XVP\8Σ~2] l9}u۞tDO_{\w7OƏy+|=uC8erSIc$fO+}؞.JFl=ea_|6? /]m1槣Ð$xyKz7qGsf╳\ImboW8G_4mu?7_SakU?EogLtWRIy]'SF!=ږ9 Ψm qL YLJ7j%׺&w=\A8J33??Gvطqv_߳\iR b˴e,y]i#߮Lܸ$ ow1\L>n[ox;(z]oFp5=߽?X_|-B@! B_d1e0X"Ȩۍ-Kk0DnGفMggW%`l=eƲ͙kgT|}"*Gc`^|\U"rsVw$qF[e1xQ+d. .b92@1$XZ-@e?zxY$Ibn]u4RY,b]KOZg;F6,?w2G6s?Ó+)"C jsg?1hc$R]=+:fuzHRטq%[Ceo벎~悁!8Aƍ&LޓؚF?}c/x<>w_ݐw(3U2ہ<^9Pcݑ,EA$>9~`O=`ڟ&83vɔBu䁷n Sl*BX促{vc揯nQQ)K! B@! ^T52.ZہdRB'hEn>$EL@Bv9ۛEվ&U'ocl|? ~XFxu;bɝzXf;2 o_M "i N-\OّVO讨mEw+޸":=xe}.6H'?kIMI1}<_+(7ޏ2C!j{|@]ZQg~JT}vgz"7B/ <7~7: s{)G[S[/zM?9d="^!B@! B]WՂ ꧩv7Sg:$M ЯGlXZZy'rX)W迒?2dbV9ƽ~=cț[corיզ˞_ǝ4c*6-KA޾\ݰ'%gN[l-qe˼7$wtjI=2s:t%(Կ.s?3[pL#JVA9˛.;[O=xV31a8Y.gx~' wazƿ+ݧZoq&à[o$Uk1i; 0_<[$>koنMj,[=zCW7%gq'7WkJ ! B@! dt:r@9ԘXdC심ky2[i,!_aL 林+5g}?As md⺆ڛMk[Ʀn~EHLy{ 桱0ֿ`@/q[ֵQMpڒhp9'oђX.r?~M)[W^Grys|Ei(RZhjG[g ^Ǿ?[z*ńzdb̫ܰ2O] z3| J`?i|!_n>.!B@! BR&e2/囫-n쭊 Cx/N_?h-T>QF#"g)J!4ekzsX14f{ \wOjv%y$Nہ'I$+~x~x) r8Ao $ě Q ^XVwr&P{-RW#U9S$B@! B@tvS>J%M'bV?Gld=j_Wţy;¶vIՍ|cYb' W2]2i?S#gW& h]hϩ޴-24D9x.1(ەD*#t_~E49lA=3Mbl"`o;LUZQx[tnً>9SL ~C$lqK&y8(09Foh!v]) |6ЊB@! B@!997{q8^3!!Lc>j&S`jݨQ3y'~zlI z9E<$ EcR3ӈk k)x;{;+E6 L;b`# ؘlg38R)\GOc_7"q& wœe5JCMJhJ a0a_b-];ۮXKMIFH7>qcke{Q= ooe1iya9fyr.F=دDzJ]^_<҈Ժǹ^VauzZ̈́~MB~/+aی#Cu"<g{ B@! B@! :(.3tgӛ쮹J!m)[\V9qX=&ԻS;6)\odcՃя%ft2t>{}iM΂z sK,Fv`n&d@~w7 3x(ʸ52sY-=Ś,]k;$OL 蓌džěݾ4E"Um7t, _w6%QX0WaS`(_k0qn}^o~`Q9aSҏ P@62gDb"?:5Ȱm`^ ωrX1knv%??k4#Y0;[-~~_q`x'coV[FQyE(>u P|kxnE3~/>5toNJbqDy/ Vq(VOܥ1AF7;IdJv(,MՃrenOy'wF=S{9qxzN3u7_ߜS譊uGڦBtgb/G&kKI! B@! ,M@nKL&3e1E[I.#_$<Ьϙݰ`q1~$&ky:g^A^$z1+>#Q$MTO !}(z P0oy 5ijĤ?4˳Ksȫd{`>;%Y>IrKO= oY&p&v􀄳99xRBn`1ВaL~x0 &*$ʥ>F78ӔKƢ'ɪm$i⾶ V{Z‰8KE1,U&GlpY]FV1(bK{+ [rse8Hv)ޅƢC<'t ʺhADvY8\m]KTN42wޞ+u/~߈0F|rqu4  sr~v$4ڮ A1%J&okl؄Ș3@AH`Ǝom1}(++W-{!L=R[^VB@! @[8H vwӮ]m˙[sGU8" ,ІğϦ3[G7.4FsdE}òƶfMɟ 0o+aSCw:ּ̍nhyVit=DT}VDrV97>Ӄ@@ Z[nGy+s|?EI_^# p* mV'rIBLCaLIbD糺ӧWk$5=ܾs:v 4$ " TYU^I^'y@@TU+_6{m%MNMbb䂱g1_>(I:ehv<$@sZ3?  !@ >B@>KD-UbL:v N[}= mM6rťr0-M#gcZI~~d׾?!Wh9fB@@ 讘s! TQiTSKRzKX)! Y<22R~|T +? @fp??Uk䮛/s~>D@@tn  pk+[@0k;99}sƳGVIݥYtlE[4tG~+Z9_ʷK%}-A2L@@@ztW'kC@(C :"ZùYZ$MTu}znwuo[3?/'dr@@j ]@@, nG6t2+kְ,^3ܖum#21Wn/ /@@!u,E@U%Aɯg%!b;8u0琬:۾ckA %!&>5Pbn߹[N=}Hn^I̔+/ >˻ڽȟw!%N5  TR_0\!  Awמ0HjN|NKlX@wMD˙0a2du*i9rYbrse~k(,1Yn{k7" i  TBA]}V`9NB)\'4@@ w-{#??_rYتkM?(MRkl@@:MF JDDDCk9-W۫u1eM ~@@z'11Qn2ٯ^wIv@@0tI,  P7tL&_Y7:C/@@@@$@@@@@ka}<     n@@@@@ և#    @@@@@ka}<     n@@@@@ և#    @@@@@ka}<     @TGs#  ),,˗ff-   @3!    U34B@@ln'[%:9xt@@QչCB@@@@@B "    9u!@@@@@Ptż     uN@w;$t@@@@@ ݡh1/     P4hP+au, Gq'@@c'Nx{N c@A_PP}B@@:&sLKۥ%J Ժ[=`   p ffпGJ&}1  ^ t?+uwt@@ќI9*'Fx t  lnR2#"##G;,}G@@ Α;vi[ ѿA3?Ug  P,q2(Kf9)%5ƍITTϗz ®!  @NȔItTDFFkrRONlWJ  (dtOhgy}옜(,t"ϲpv@#Htt ƃ/ho*2Tcw:{>ԻI#3$د0x>env@Ꚁ}<*RY,_FK~}-D5m@@6"h' {@6 ⸵5yDѭ3՗vOyV@@}uBz`d-UYV/Dd]apw ^  @m D7@wCg, ']?ڒd; P8-n&o}6[ݵy-@@ @xz˧RZ;BODjwp{-  dtۭi gn&@ em{~oy2e@wNF@, vLO |5MC@(a;%0ti4Ԡ@(omk+Ro[dei?6 n'W;)q uR. rmnP(NN!z9΋5Q}>ƀ 2q@Ϟm{Y%>3 @ m16Ǎ!+P'7y}y&]_$ Jw:  @ X@a|(:@} lP݁"G@@@JpFs)1@ި     @ ^Oֆ     Pk!    Tdm     ,u Nٙ@ |:9/JG 6ec2W iSrL-rH&!Tȱy拽2KLs{TvJV5@-)IDATkl8,r!k77 ˗|iSyyrhh+Q0KHئE8+Ǐ:) WGw܍ut 8JFNyS-ڂ26R{~m~~K۷\zR竉U OȾ'%2/5їZ3w=927O@Ȍi!%G_mu^låo'Kq0g~yn2GBgLOW-HV hzz|(UN*seK?;yOexҬi7Gir%'S{ݖmߕ d6 Rgh2K&9̾a؝_^`lݓP^v1۴L?ߔiA5;xH'm.ҫsGwv_]P5izBV2_R?$e @tq&f,?%6L']&4ˇ%=h l# з*pZfޮ/a @ /䮩ns`3bH7 z]~䶠C?&fxU'p6\9bck.i]g&ʧg ~ /G_-9sΉe׾rwo9:K );rU˝(wg7ii]ߺe A/3 rɢ%xMvVMi]1myF@rSʙޗv\l݊+W;@ 6dKVNn#m7t?*99㸳v-yqDE5-y?yqZitKht`Η}G%QlXbVO6߾\8'Z6zlE\} eMuc[֍%"S\&Դ\8>Γi$RD_\۷zL[WޱuϺ RPeiUq8JNrf]:㳓ٯeIm)1ȿ]{eޒUN1Hg8˴o!_M;pcHC#؞ZF WB3?|-cme5}⍵=)gRV%yIs@Fuώya>߹rDn;wsM=?$tPQ=d1Sv :\FM&,rCf˦|FnAܾm`aw OL*G\k䆗62/;:bJ.^ϪM?.O#o/I< ޗ.xbuo_^(ߴ79G䵅%??e?AO ~Z{ou"Z7+>.l{7kedX/l=]' t';vqw<,v:i-O/mZ_8 u߬)zNtoɾͤoO{!|U4/ ޹X.A].uۃ3vW2h ;]Ξ+|(yA5]䖳yWY/ץ^YnŲɆgGINnaH?ngK:CǝIv䝻9[rPm@,dfQt,Mb?[+~{anٰugݳYsx>3_ĵG(mmC_zظU&k6owjng-.tnN4ؽM˓XcF;zkbe!;9_qXw>#93GT|Nfʏ&]]<7/OKF/`35k*j}zzSdNW&v h G@w9uzOm[&fxUײ%x8 .hoS"*?^.sdȅ[/\b? )2L\4T-뻃N Ԃʯ,_"m/lZh6F|:]$q<3`ސʠ_:Ypf EȏǶAmO^]o ǃ]Ęl:/Wz_^? -ur&9u[eީDn.'}49Y\bŜ5ir˛.r%ֹka{K縴.y^!F:|9K6͑ w'GUmufY2Ҿu t/YAfH|\ o\t :E+ζ wcžZ;p7iD|fZ,MM%9\3 ﱧn@]w8t3 eq`?@k wţ%в;T:k-Ȃee#yݶ!ߺ; hۀz@ޘ5W/]%-ʈ}Rd} fAn [_@lei5 m4(f1tml,ĖҀoibzvt$1KkMc-!dVb xk-/7 FIگA0 Y{M|W353'%z348z?79A5U,=$}W}r -IS൲.n`UldN{ʨynSW;lMӶ5k=gdY0߂^ PϙiVtk-:=7vLOƬP_[&-zk錒W}X^וݩzZ)gwm⧒]H3I/LU}P woyul*yoXG*P2Ósv;An˦~~eC .[< 3tk[HyvjWthwq-u;+}V<ݥH@wTF9ղ7,ן=<2Uqp3 PVmt$Yݵ =PcGKƞW8=nĝUR̯m䢳O6:e^Yo~4j(x@ޕ:bwy.6).i6}bq~rҬiIв7)ҝ@qgZ^g\S)mSg*H<{ U_1[a2Q2y8y~ Yj>z tnMM:耘~Wcn9@(@m m՞SFzQ >tL@Lvj r\1x Nq fպt@$4lflt+zz3H-#<)ƒYhٮ58mˌYfփ^5xfV  rrMs!ONwў|oLw?PKX)b rVwlY,[>KZ`*O]ִ7e.'zΫ?Sb5Vfr~{of:O/ Y۩WV+ڂtS;+P~.nf箄G/vBYwgAn;==m˰w[UeG_|hKbx>dZ[/.åQk.MКm]4^(/3Kh3]$3H^atʔ=LzusL3hv&s_H~deRhi>f5~UFGMW\]t0㻟}%_e'_Yuxe12oܻJ8_/5dsG si Xe#n4]b1@]]Ve\ݔ%ok}o$-l .x_~}HkA[ Njy}o,]X4pGim_Fq<u Z}ꉃ];ZТ,]2=uFW+ҜG8p^\?vjRdc6gCnZ?RLͦSE7;׷ymehk[eh'3ڬvjZmv=?V۷UwyLvZWO\E񝧢?vzz{Ϻ~C lEvLVUw=<#uU?hlˊ mvudtfͪ=8fAiP1_^|w#60m{2qDL59HRnid|۹[5mއ;,OwByuA[V+Zfsq{n#`wMH~Z_ڀm>>~w8._VZ22+YQ =!E/`?[r(|ԤCiupàAamөWG'NY]):@e {͑c2KS4meQP-}8+LۅTFZʖd]VϾAo#h:q< Z9Y&O<̶LuJ;Y~殯gY7hMoZ;& ^ {uTurzhomn{MC_.]OۛLC;;EA5S3*BҴdH6`25fM;dOA&vVf:` \Yz93S/ϧzlZA+EթN{no~5ػ*تR0I:g6*nEk/lߴkey{Ay}\3r[yO-;A˒$kmZ&s2"M-S]2vFvѹ\yg'(.QdKu23rŐT}MLUu߲]\©Mw2S ޝӎ]RtjYB$M/E wgu펅/ kzI6أ NӋGW?VyђA4@v+a=wE8{MC_%î_1f1Q ۋ.[Qݚ:2O^< +%QE_m/5sxO]L2!mi- p^[]K&J\lX{}^s{g^7֬qku)K{oM<W_g{ +Om#5'=Iw&I8_eWơmuMܾ1ſnܿ_$5+YEUkc5?E/ \w *.2irp-xm-qCi[_|6j^g^g[NMc` ؇Sܾ\: ai K@w:a7wAvNNܡ5-wȖ%Z5s%٫dbN_PѬsˌ~}OFP`8,k\Fudꠊ6Hڑ_x[FKbQosz7oAQZhIv- @Z>%GK[kTv;sYm3Ss<gveV[m֢ٜ5iwwer=Zr$M#sNwe)6ʚv%J꼮^V՜vOy%U2n_ٮV2}q̌, yrDvn6Y?+V*ZDUj Cj[ E`zg8$|K)yam[u3/wIy7%-Ćͻh쒿8$|8֯72-&oW"E2|@o j-ڰr&X&֣hSwʼ%n@tl9ώh:r 5;ݻjymN,=ɱ1wregai"dpfޕ-LV.e_ַr2×vM Ȱ| o% xhpyƙd[Q͢GWmu[-8XOwIZJ<%g 69:f`r+W{˵m?+HQ: i%$fǸݞ8-=ihK@k_lLRioҰxr뛽rC?}sQ"~6퇎ˋzw?*Oߨ;Ot՚ſr;ňG.$`\tzVk\C|V2~@K:*2ZXs3gYt:YFFwm&_頳iZ~+ӠX9'?Q.Rv KuOO?^Uj dcEWء,sL-t^$!APg~)nNN1Aw?Z8MOt6 -;ҵIr$ C\r߲tȦ{ߕ)ͅʍ[ByEm=Iw;_e瞥`3ݡ㹞q:uMp[-iW^,OH^gԞ%/\\XCMb猖OY$Ͻ=S]'|wYd穞֦\نZV;!K;lPV<س]wM!sK=Qv/<D^ maͲ{o!7^e{ h% Zot-s]-qbnOL} w y`&hg;ۗ%93OǙg\:hdW騃3VY@ֺnn%O\ h͛H.\"hk9S-%vgƢg4ňV6)-7v(6𤮲14oQ rLK|*8Ҏ_:o>'H,;+,3WcK/}[;}rg ,]En=zn{v< 5An|rk[efòퟙYD/X w&s7t;6{X^wr2ꅒ\ ]jbuE](=kzcA 0vѲ L ůJڳmrUݧnǝ^GM8=jz./8%i42u/ke}f%IzhFH \G^P-e*>Ce2 ЫTzKO8u jҚսl=*7{]+OBxt @݁"V.2@x~2JK@p,?XIG~v4),[8'}G ;!{ݧqf/@@33䡧)QQiSrי#CG@@ .,zaO:U3u`kerPmNJ"   @- P PI.     @ CG@@@@@L@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@?DDז7IENDB`graphql-ruby-2.2.17/guides/queries/ast_analysis.md000066400000000000000000000103701476434635200222100ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Ahead-of-Time AST Analysis desc: Check incoming query strings and reject them if they don't pass your checks index: 1 redirect_from: - /queries/analysis/ --- You can do ahead-of-time analysis for your queries. The primitive for analysis is {{ "GraphQL::Analysis::AST::Analyzer" | api_doc }}. Analyzers must inherit from this base class and implement the desired methods for analysis. ## Using Analyzers Query analyzers are added to the schema with `query_analyzer`, for example: ```ruby class MySchema < GraphQL::Schema query_analyzer MyQueryAnalyzer end ``` Pass the **class** (and not an _instance_) of your analyzer. The analysis engine will take care of instantiating your analyzers with the query. ## Analyzer API Analyzers respond to methods similar to AST visitors. They're named like `on_enter_#{ast_node}` and `on_leave_#{ast_node}`. Methods are called with three arguments: - `node`: The current AST node (being entered or left) - `parent`: The AST node which preceeds this one in the tree - `visitor`: A {{ "GraphQL::Analysis::AST::Visitor" | api_doc }} which is managing this analysis run For example: ```ruby class BasicCounterAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query_or_multiplex) super @fields = Set.new @arguments = Set.new end # Visitors are all defined on the AST::Analyzer base class # We override them for custom analyzers. def on_leave_field(node, _parent, _visitor) @fields.add(node.name) end def result # Do something with the gathered result. Analytics.log(@fields) end end ``` In this example, we counted every field, no matter if it was on fragment definitions or if it was skipped by directives. If we want to detect those contexts, we can use helper methods: ```ruby class BasicFieldAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query_or_multiplex) super @fields = Set.new end # Visitors are all defined on the AST::Analyzer base class # We override them for custom analyzers. def on_leave_field(node, _parent, visitor) if visitor.skipping? || visitor.visiting_fragment_definition? # We don't want to count skipped fields or fields # inside fragment definitions else @fields.add(node.name) end end def result Analytics.log(@fields) end end ``` See {{ "GraphQL::Analysis::AST::Visitor" | api_doc }} for more information about the `visitor` object. ### Field Arguments Usually, analyzers will use `on_enter_field` and `on_leave_field` to process queries. To get a field's arguments during analysis, use `visitor.query.arguments_for(node, visitor.field_definition)` ({{ "GraphQL::Query#arguments_for" | api_doc }}). That method returns coerced argument values and normalizes argument literals and variable values. ### Errors It is still possible to return errors from an analyzer. To reject a query and halt its execution, you may return {{ "GraphQL::AnalysisError" | api_doc }} in the `result` method: ```ruby class NoFieldsCalledHello < GraphQL::Analysis::AST::Analyzer def on_leave_field(node, _parent, visitor) if node.name == "hello" @field_called_hello = true end end def result GraphQL::AnalysisError.new("A field called `hello` was found.") if @field_called_hello end end ``` ### Conditional Analysis Some analyzers might only make sense in certain context, or some might be too expensive to run for every query. To handle these scenarios, your analyzers may answer to an `analyze?` method: ```ruby class BasicFieldAnalyzer < GraphQL::Analysis::AST::Analyzer # Use the analyze? method to enable or disable a certain analyzer # at query time. def analyze? !!subject.context[:should_analyze] end def on_leave_field(node, _parent, visitor) # ... end def result # ... end end ``` ## Analyzing Multiplexes Analyzers are initialized with the _unit of analysis_, available as `subject`. When analyzers are hooked up to multiplexes, `query` is `nil`, but `multiplex` returns the subject of analysis. You can use `visitor.query` inside visit methods to reference the query that owns the current AST node. Note that some built-in analyzers (eg `AST::MaxQueryDepth`) support multiplexes even though `Query` is in their name. graphql-ruby-2.2.17/guides/queries/backtrace_annotations.md000066400000000000000000000065761476434635200240670ustar00rootroot00000000000000--- title: Backtrace Annotations layout: guide doc_stub: false search: true section: Queries desc: Use the GraphQL backtrace for debugging index: 12 experimental: true --- `context` objects have a `backtrace` which shows its GraphQL context. You can print the backtrace during query execution: ```ruby puts context.backtrace # Loc | Field | Object | Arguments | Result # 3:13 | User.login | # | {"message"=>"Boom"} | # # 2:11 | Query.user | nil | {"login"=>"rmosolgo"} | {} # 1:9 | query | nil | {"msg"=>"Boom"} | ``` The backtrace contains some execution data: - `Loc` is the `line:column` of the field in the query string - `Field` is the `TypeName.fieldName` of the fields in the backtrace - `Object` is the `obj` for query resolution (used for resolving the given field), equivalent to `obj.inspect` - `Arguments` are the GraphQL arguments for field resolution (including any default values and variable values) - `Result` is the GraphQL-ready result which is being constructed (it may be incomplete if the query is still in-progress) ## Wrapping Errors You can wrap unhandled errors with a GraphQL error with `GraphQL::Backtrace`. To enable this feature for a query, add `backtrace: true` to your `context`, for example: ```ruby # Wrap this query with backtrace annotation MySchema.execute(query_string, context: { backtrace: true }) ``` Or, to _always_ wrap backtraces, add it to your schema definition with `use`, for example: ```ruby class MySchema < GraphQL::Schema # Always wrap backtraces with GraphQL annotation use GraphQL::Backtrace end ``` Now, any unhandled errors will be wrapped by `GraphQL::Backtrace::TracedError`, which prints out the GraphQL backtrace, too. For example: ``` Unhandled error during GraphQL execution: This is broken: Boom /Users/rmosolgo/code/graphql-ruby/spec/graphql/backtrace_spec.rb:27:in `block (3 levels) in ' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/build_from_definition/resolve_map.rb:57:in `call' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/build_from_definition.rb:171:in `block in build_object_type' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/build_from_definition.rb:280:in `block (2 levels) in build_fields' /Users/rmosolgo/code/graphql-ruby/lib/graphql/field.rb:228:in `resolve' /Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/execute.rb:253:in `call' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/middleware_chain.rb:45:in `invoke_core' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/middleware_chain.rb:38:in `invoke' /Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/execute.rb:107:in `resolve_field' /Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/execute.rb:71:in `block (2 levels) in resolve_selection' ... and 65 more lines Use #cause to access the original exception (including #cause.backtrace). GraphQL Backtrace: Loc | Field | Object | Arguments | Result 3:13 | Thing.raiseField as boomError | :something | {"message"=>"Boom"} | # 2:11 | Query.field1 | "Root" | {} | {} 1:9 | query | "Root" | {"msg"=>"Boom"} | {} ``` graphql-ruby-2.2.17/guides/queries/complexity_and_depth.md000066400000000000000000000220021476434635200237140ustar00rootroot00000000000000--- title: Complexity & Depth layout: guide doc_stub: false search: true section: Queries desc: Limiting query depth and field selections index: 4 --- GraphQL-Ruby ships with some validations based on {% internal_link "query analysis", "/queries/ast_analysis" %}. You can customize them as-needed, too. ## Prevent deeply-nested queries You can also reject queries based on the depth of their nesting. You can define `max_depth` at schema-level or query-level: ```ruby # Schema-level: class MySchema < GraphQL::Schema # ... max_depth 15 end # Query-level, which overrides the schema-level setting: MySchema.execute(query_string, max_depth: 20) ``` By default, **introspection fields are counted**. The default introspection query requires at least `max_depth 13`. You can also configure your schema not to count introspection fields with `max_depth ..., count_introspection_fields: false`. You can use `nil` to disable the validation: ```ruby # This query won't be validated: MySchema.execute(query_string, max_depth: nil) ``` To get a feeling for depth of queries in your system, you can extend {{ "GraphQL::Analysis::AST::QueryDepth" | api_doc }}. Hook it up to log out values from each query: ```ruby class LogQueryDepth < GraphQL::Analysis::AST::QueryDepth def result query_depth = super message = "[GraphQL Query Depth] #{query_depth} || staff? #{query.context[:current_user].staff?}" Rails.logger.info(message) end end class MySchema < GraphQL::Schema query_analyzer(LogQueryDepth) end ``` ## Prevent complex queries Fields have a "complexity" value which can be configured in their definition. It can be a constant (numeric) value, or a proc. If no `complexity` is defined for a field, it will default to a value of `1`. It can be defined as a keyword _or_ inside the configuration block. For example: ```ruby # Constant complexity: field :top_score, Integer, null: false, complexity: 10 # Dynamic complexity: field :top_scorers, [PlayerType], null: false do argument :limit, Integer, limit: false, default_value: 5 complexity ->(ctx, args, child_complexity) { if ctx[:current_user].staff? # no limit for staff users 0 else # `child_complexity` is the value for selections # which were made on the items of this list. # # We don't know how many items will be fetched because # we haven't run the query yet, but we can estimate by # using the `limit` argument which we defined above. args[:limit] * child_complexity end } end ``` Then, define your `max_complexity` at the schema-level: ```ruby class MySchema < GraphQL::Schema # ... max_complexity 100 end ``` Or, at the query-level, which overrides the schema-level setting: ```ruby MySchema.execute(query_string, max_complexity: 100) ``` Using `nil` will disable the validation: ```ruby # 😧 Anything goes! MySchema.execute(query_string, max_complexity: nil) ``` To get a feeling for complexity of queries in your system, you can extend {{ "GraphQL::Analysis::AST::QueryComplexity" | api_doc }}. Hook it up to log out values from each query: ```ruby class LogQueryComplexityAnalyzer < GraphQL::Analysis::AST::QueryComplexity # Override this method to _do something_ with the calculated complexity value def result complexity = super message = "[GraphQL Query Complexity] #{complexity} | staff? #{query.context[:current_user].staff?}" Rails.logger.info(message) end end class MySchema < GraphQL::Schema query_analyzer(LogQueryComplexityAnalyzer) end ``` #### Connection fields By default, GraphQL-Ruby calculates a complexity value for connection fields by: - adding `1` for `pageInfo` and each of its subselections - adding `1` for `count`, `totalCount`, or `total` - adding `1` for the connection field itself - multiplying the complexity of other fields by the largest possible page size, which is the greater of `first:` or `last:`, or if neither of those are given it will go through each of `default_page_size`, the schema's `default_page_size`, `max_page_size`, and then the schema's `default_max_page_size`. (If no default page size or max page size can be determined, then the analysis crashes with an internal error -- set `default_page_size` or `default_max_page_size` in your schema to prevent this.) For example, this query has complexity `26`: ```graphql query { author { # +1 name # +1 books(first: 10) { # +1 nodes { # +10 (+1, multiplied by `first:` above) title # +10 (ditto) } pageInfo { # +1 endCursor # +1 } totalCount # +1 } } } ``` To customize this behavior, implement `def calculate_complexity(query:, nodes:, child_complexity:)` in your base field class, handling the case where `self.connection?` is `true`: ```ruby class Types::BaseField < GraphQL::Schema::Field def calculate_complexity(query:, nodes:, child_complexity:) if connection? # Custom connection calculation goes here else super end end end ``` ## How complexity scoring works GraphQL Ruby's complexity scoring algorithm is biased towards selection fairness. While highly accurate, its results are not always intuitive. Here's an example query performed on the [Shopify Admin API](https://shopify.dev/docs/api/admin-graphql): ```graphql query { node(id: "123") { # interface Node id ...on HasMetafields { # interface HasMetafields metafield(key: "a") { value } metafields(first: 10) { nodes { value } } } ...on Product { # implements HasMetafields title metafield(key: "a") { definition { description } } } ...on PriceList { name catalog { id } } } } ``` First, GraphQL Ruby allows field definitions to specify a `complexity` attribute that provides a complexity score (or a proc that computes a score) for each field. Let's say that this schema defines a system where: - Leaf fields cost `0` - Composite fields cost `1` - Connection fields cost `children * input size` Given these parameters, we get an itemized scoring distribution of: ```graphql query { node(id: "123") { # 1, composite id # 0, leaf ...on HasMetafields { metafield(key: "a") { # 1, composite value # 0, leaf } metafields(first: 10) { # 1 * 10, connection nodes { # 1, composite value # 0, leaf } } } ...on Product { title # 0, leaf metafield(key: "a") { # 1, composite definition { # 1, composite description # 0, leaf } } } ...on PriceList { name # 0, leaf catalog { # 1, composite id # 0, leaf } } } } ``` However, we cannot naively tally these itemized scores without over-costing the query. Consider: - The `node` scope makes many _possible_ selections on an abstract type, so we need the maximum among concrete possibilities for a fair representation. - A `node.metafield` selection path is duplicated across the `HasMetafields` and `Product` selection scopes. This path will only resolve once, so should also only cost once. To reconcile these possibilities, the [complexity algorithm](https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/analysis/ast/query_complexity.rb) breaks the selection down into a tree of types mapped to possible selections, across which lexical selections can be coalesced and deduplicated (pseudocode): ```ruby { Schema::Query => { "node" => { Schema::Node => { "id" => nil, }, Schema::HasMetafields => { "metafield" => { Schema::Metafield => { "value" => nil, }, }, "metafields" => { Schema::Metafield => { "nodes" => { ... }, }, }, }, Schema::Product => { "title" => nil, "metafield" => { Schema::Metafield => { "definition" => { ... }, }, }, }, Schema::PriceList => { "name" => nil, "catalog" => { Schema::Catalog => { "id" => nil, }, }, }, }, }, } ``` This aggregation provides a new perspective on the scoring where _possible typed selections_ have costs rather than individual fields. In this normalized view, `Product` acquires the `HasMetafields` interface costs, and ignores a duplicated path. Ultimately the maximum of possible typed costs is used, making this query cost `12`: ```graphql query { node(id: "123") { # max(11, 12, 1) = 12 id ...on HasMetafields { # 1 + 10 = 11 metafield(key: "a") { # 1 value } metafields(first: 10) { # 10 nodes { value } } } ...on Product { # 1 + 11 from HasMetafields = 12 title metafield(key: "a") { # duplicated in HasMetafields definition { # 1 description } } } ...on PriceList { # 1 = 1 name catalog { # 1 id } } } } ``` graphql-ruby-2.2.17/guides/queries/executing_queries.md000066400000000000000000000143571476434635200232570ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Executing Queries desc: Evaluate GraphQL queries with your schema index: 0 --- You can execute queries with your {{ "GraphQL::Schema" | api_doc }} and get a Ruby Hash as a result. For example, to execute a query from a string: ```ruby query_string = "{ ... }" MySchema.execute(query_string) # { # "data" => { ... } # } ``` Or, you can execute multiple queries at once: ```ruby MySchema.multiplex([ {query: query_string_1}, {query: query_string_2}, {query: query_string_3}, ]) # [ # { "data" => { ... } }, # { "data" => { ... } }, # { "data" => { ... } }, # ] ``` There are also several options you can use: - `variables:` provides values for `$`-named [query variables](https://graphql.org/learn/queries/#variables) - `context:` accepts application-specific data to pass to `resolve` functions - `root_value:` will be provided to root-level `resolve` functions as `obj` - `operation_name:` picks a [named operation](https://graphql.org/learn/queries/#operation-name) from the incoming string to execute - `document:` accepts an already-parsed query (instead of a string), see {{ "GraphQL.parse" | api_doc }} - `validate:` may be `false` to skip static validation for this query - `max_depth:` and `max_complexity:` may override schema-level values Some of these options are described in more detail below, see {{ "GraphQL::Query#initialize" | api_doc }} for more information. ## Variables GraphQL provides [query variables](https://graphql.org/learn/queries/#variables) as a way to parameterize query strings. If your query string contains variables, you can provide values in a hash of `{ String => value }` pairs. The keys should _not_ contain `"$"`. For example, to provide variables to a query: ```ruby query_string = " query getPost($postId: ID!) { post(id: $postId) { title } }" variables = { "postId" => "1" } MySchema.execute(query_string, variables: variables) ``` If the variable is a {{ "GraphQL::InputObjectType" | api_doc }}, you can provide a nested hash, for example: ```ruby query_string = " mutation createPost($postParams: PostInput!, $createdById: ID!){ createPost(params: $postParams, createdById: $createdById) { id title createdBy { name } } } " variables = { "postParams" => { "title" => "...", "body" => "..." }, "createdById" => "5", } MySchema.execute(query_string, variables: variables) ``` ## Context You can provide application-specific values to GraphQL as `context:`. This is available in many places: - `resolve` functions - `Schema#resolve_type` hook - ID generation & fetching Common uses for `context:` include the current user or auth token. To provide a `context:` value, pass a hash to `Schema#execute`: ```ruby context = { current_user: session[:current_user], current_organization: session[:current_organization], } MySchema.execute(query_string, context: context) ``` Then, you can access those values during execution: ```ruby field :post, Post do argument :id, ID end def post(id:) context[:current_user] # => # # ... end ``` Note that `context` is _not_ the hash that you passed it. It's an instance of {{ "GraphQL::Query::Context" | api_doc }}, but it delegates `#[]`, `#[]=`, and a few other methods to the hash you provide. ### Scoped Context `context` is shared by the whole query. Anything you add to `context` will be accessible by any other field in the query (although GraphQL-Ruby's order of execution can vary). However, "scoped context" is can be used to assign values into `context` that are only available in the current field and the _children_ of the current field. For example, in this query: ```graphql { posts { comments { author { isOriginalPoster } } } } ``` You could use "scoped context" to implement `isOriginalPoster`, based on the parent `comments` field. {% callout warning %} Using scoped context may result in a violation of [the GraphQL specification](https://spec.graphql.org/draft/#sel-EABDLDFAACHAo3V) and break normalized client stores, which assume that a given object always has the same values for its fields. See ["Referencing ancestors breaks normalized stores"](https://benjie.dev/graphql/ancestors#breaks-normalized-stores) for details about this pitfall and alternative approaches which avoid it. {% endcallout %} In `def comments`, add `:current_post` to scoped context using `context.scoped_set!`: ```ruby class Types::Post < Types::BaseObject # ... def comments context.scoped_set!(:current_post, object) object.comments end end ``` Then, inside `User` (assuming `author` resolves to `Types::User`), you can check `context[:current_post]`: ```ruby class Types::User < Types::BaseObject # ... def is_original_poster current_post = context[:current_post] current_post && current_post.author == object end end ``` `context[:current_post]` will be present if an "upstream" field assigned it with `scoped_set!`. `context.scoped_merge!({ ... })` is also available for setting multiple keys at once. **Note**: With batched data loading (eg, GraphQL-Batch), scoped context might not work because of GraphQL-Ruby's control flow jumps from one field to the next. In that case, use `scoped_ctx = context.scoped` to grab a scoped context reference _before_ calling a loader, then used `scoped_ctx.set!` or `scoped_ctx.merge!` to modify scoped context inside the promise body. For example: ```ruby # For use with GraphQL-Batch promises: scoped_ctx = context.scoped SomethingLoader.load(:something).then do |thing| scoped_ctx.set!(:thing_name, thing.name) end ``` ## Root Value You can provide a root `object` value with `root_value:`. For example, to base the query off of the current organization: ```ruby current_org = session[:current_organization] MySchema.execute(query_string, root_value: current_org) ``` That value will be provided to root-level fields, such as mutation fields. For example: ```ruby class Types::MutationType < GraphQL::Schema::Object field :create_post, Post def create_post(**args) object # => # # ... end end ``` {{ "GraphQL::Schema::Mutation" | api_doc }} fields will also receive `root_value:` as `obj` (assuming they're attached directly to your `MutationType`). graphql-ruby-2.2.17/guides/queries/logging.md000066400000000000000000000013111476434635200211370ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Logging desc: Development output from GraphQL-Ruby index: 12 --- At runtime, GraphQL-Ruby will output debug information using {{ "GraphQL::Query.logger" | api_doc }}. By default, this uses `Rails.logger`. To see output, make sure `config.log_level = :debug` is set. (This information isn't meant for production logs.) You can configure a custom logger with {{ "GraphQL::Schema.default_logger" | api_doc }}, for example: ```ruby class MySchema < GraphQL::Schema # This logger will be used by queries during execution: default_logger MyCustomLogger.new end ``` You can also pass `context[:logger]` to provide a logger during execution. graphql-ruby-2.2.17/guides/queries/lookahead.md000066400000000000000000000070411476434635200214460ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Lookahead desc: Detecting child selections during field resolution index: 11 --- GraphQL-Ruby 1.9+ includes {{ "GraphQL::Execution::Lookahead" | api_doc }} for checking whether child fields are selected. You can use this to optimize database access, for example, selecting only the _needed_ fields from the database. ## Getting a Lookahead Add `extras: [:lookahead]` to your field configuration to receive an injected lookahead: ```ruby field :files, [Types::File], null: false, extras: [:lookahead] ``` Then, update your resolver method to accept a `lookahead:` argument: ```ruby def files(lookahead:) # ... end ``` That argument will be injected by the GraphQL runtime. ## Using a lookahead Inside your field resolver, you can use the lookahead to check for child fields. For example, you can check for a __specific selection__: ```ruby def files(lookahead:) if lookahead.selects?(:full_path) # This is a query like `files { fullPath ... }` else # This query doesn't have `fullPath` end end ``` Or, you can list __all the selected fields__: ```ruby def files(lookahead:) all_selections = lookahead.selections.map(&:name) if all_selections == [:name] # Only `files { name }` was selected, use a fast cached value: object.file_names.map { |n| { name: n }} else # Lots of fields were selected, fall back to a more resource-intensive approach FileSystemHelper.load_files_for(object) end end ``` Lookaheads are _chainable_, so you can use them to check __nested selections__ too: ```ruby def files(lookahead:) if lookahead.selection(:history).selects?(:author) # For example, `files { history { author { ... } } }` # We're checking for commit authors, so load those objects appropriately ... else # Not selecting commit authors ... end end ``` Nested lookaheads return empty objects when there's no selection (not `nil`), so the code above will never have a "no method error on `nil`". ## Lookaheads with connections If you want to see what selections were made on the items in a connection, you can use nested lookaheads. However, don't forget to check for `edges { node }` _and_ `nodes { ... }`, if you support that shortcut field. For example: ```ruby field :products, Types::Product.connection_type, null: false, extras: [:lookahead] def products(lookahead:) selects_quantity_available = lookahead.selection(:nodes).selects?(:quantity_available) || # ^^ check for `products { nodes { quantityAvailable } }` lookahead.selection(:edges).selection(:node).selects?(:quantity_available) # ^^ check for `products { edges { node { quantityAvailable } } }` if selects_quantity_available # ... else # ... end end ``` That way, you can check for specific selections on the nodes in a connection. ## Lookaheads with aliases If you want to find selection by its [alias](https://spec.graphql.org/June2018/#sec-Field-Alias), you can use `#alias_selection(...)` or check if it exists with `#selects_alias?`. In this case, the lookahead will check if there is a field with the provided alias. For example, this query can find a bird species by its name: ```graphql query { gull: findBirdSpecies(byName: "Laughing Gull") { name } tanager: findBirdSpecies(byName: "Scarlet Tanager") { name } } ``` You can get the lookahead for each selection in a following way: ```ruby def find_bird_species(by_name:, lookahead:) if lookahead.selects_alias?("gull") lookahead.alias_selection("gull") end end ``` graphql-ruby-2.2.17/guides/queries/multiplex.md000066400000000000000000000104371476434635200215450ustar00rootroot00000000000000--- title: Multiplex layout: guide doc_stub: false search: true section: Queries desc: Run multiple queries concurrently index: 10 --- Some clients may send _several_ queries to the server at once (for example, [Apollo Client's query batching](https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/)). You can execute them concurrently with {{ "Schema#multiplex" | api_doc }}. Multiplex runs have their own context, analyzers and instrumentation. __NOTE:__ As an implementation detail, _all_ queries run inside multiplexes. That is, a stand-alone query is executed as a "multiplex of one", so instrumentation and multiplex analyzers and tracers _will_ apply to standalone queries run with `MySchema.execute(...)`. ## Concurrent Execution To run queries concurrently, build an array of query options, using `query:` for the query string. For example: ```ruby # Prepare the context for each query: context = { current_user: current_user, } # Prepare the query options: queries = [ { query: "query Query1 { someField }", variables: {}, operation_name: 'Query1', context: context, }, { query: "query Query2 ($num: Int){ plusOne(num: $num) }", variables: { num: 3 }, operation_name: 'Query2', context: context, } ] ``` Then, pass them to `Schema#multiplex`: ```ruby results = MySchema.multiplex(queries) ``` `results` will contain the result for each query in `queries`. __NOTE:__ The results will always be in the same order that their respective requests were sent in. ## Apollo Query Batching Apollo sends batches of queries as an array of queries. Rails' ActionDispatch will parse the request and put the result into the `_json` field of the `params` variable. You also need to ensure that your schema can handle both batched and non-batched queries, below is an example of the default GraphqlController rewritten to handle Apollo batches: ```ruby def execute context = {} # Apollo sends the queries in an array when batching is enabled. The data ends up in the _json field of the params variable. # see the Apollo Documentation about query batching: https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/ result = if params[:_json] queries = params[:_json].map do |param| { query: param[:query], operation_name: param[:operationName], variables: ensure_hash(param[:variables]), context: context } end MySchema.multiplex(queries) else MySchema.execute( params[:query], operation_name: params[:operationName], variables: ensure_hash(params[:variables]), context: context ) end render json: result, root: false end ``` ## Validation and Error Handling Each query is validated and {% internal_link "analyzed","/queries/ast_analysis" %} independently. The `results` array may include a mix of successful results and failed results. ## Multiplex-Level Context You can add values to {{ "Execution::Multiplex#context" | api_doc }} by providing a `context:` hash: ```ruby MySchema.multiplex(queries, context: { current_user: current_user }) ``` This will be available to instrumentation as `multiplex.context[:current_user]` (see below). ## Multiplex-Level Analysis You can analyze _all_ queries in a multiplex by adding a multiplex analyzer. For example: ```ruby class MySchema < GraphQL::Schema # ... multiplex_analyzer(MyAnalyzer) end ``` The API is the same as {% internal_link "query analyzers","/queries/ast_analysis#analyzing-multiplexes" %}. Multiplex analyzers may return {{ "AnalysisError" | api_doc }} to halt execution of the whole multiplex. ## Multiplex Tracing You can add hooks for each multiplex run with {% internal_link "trace modules", "/queries/tracing" %}. The trace module may implement `def execute_multiplex(multiplex:)` which `yield`s to allow the multiplex to execute. See {{ "Execution::Multiplex" | api_doc }} for available methods. For example: ```ruby # Count how many queries are in the multiplex run: module MultiplexCounter def execute_multiplex(multiplex:) Rails.logger.info("Multiplex size: #{multiplex.queries.length}") yield end end # ... class MySchema < GraphQL::Schema # ... trace_with(MultiplexCounter ) end ``` Now, `MultiplexCounter#execute_multiplex` will be called for each execution, logging the size of each multiplex. graphql-ruby-2.2.17/guides/queries/new_relic_example.png000066400000000000000000004704141476434635200233750ustar00rootroot00000000000000PNG  IHDRnJW iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 842 1134 1 @IDATx\TW&"UDPDcM\M/ll5)IM5e;1լI6M,FHQ@4A6lH s{>;chx 8}z\!        3 7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"       w"8[XYQ[^9     8g\n nhS;|}Y5kNI=.=wGSERkEtn9!`1юIq٪M!     p _?V՞r:KLtfN1BsBH!p٪se8R     8=.xZ#J\/˶kW'$[r+Cw      pY3`"*]퉣|mh:>Nç_玎MF#&xXG lj6ۗv2x^}͸Z!v;܅3boCEJpoU-IU.nLIljL|],v(e&AiJH-b'0蟧gM&wѯV}}Jw;#lvspvLM1HHHHHz3ӑ4:3  ,X%VFlZVU'~HoX_~/6G`.N>9RO?rڠ'GuT]];95%GDkn¼iZq^&U{P!,M rP܈Vl0o73LkcՕ׭_654q?v h^c8~'1l#xpfmx/h_&OՁ|+_8A<47\Oxo!Smb͚PigいqӍsO̷n޷+>T7+/OfyX \b̊ %(Δؕ#ܥAQn͘;Lü9[o~D]1[q뼉u봺.e0`O} ;q|ۯ1wBK13 ~gOih#a ;F=V]cA; ²dx=d Oqp|f=?- SBXXgDc;%ɋOࢠ3c SE̡e!q߬3myǤKÔ+vL80"t%%PF8,1a`͹aFppDy[jS>9Y@D [)6;m,}gޒ%Z6d˄V_?G2b<9箭pbى6b.tzV텫oƲvjF$@$@$@$@$@x]F qH>L.9w<}$BĨp?eDz`~OVhNnIT,+jwNen-WuG4̾qÄ[byQ'J}_b1V ˟BkƟm$U/Ҧ78?c5oWVZNgͷo2cx2Ncœbx t͈MORV>_9!TΣ8BgO^@9w1GƄeq4>{^_C'1O,?`4wo˳] $@$@$@$@$@$K&u EuMe9ގK=>T/sb$5.a?ߍp!H=tF@0h{B`yxu6l-l4:)[xpq^7 ]|Z+UKDz &fRӁ-XU1/}L'HM')v emR xWXW,mp5[{TkxDjwe:s"ҏKuhm,|Դi*=/K>Ӊ6ROK(XL4?N; %7Kb.CAh#e|GZǯk/@Τ8Kv{=tNRs%;Ioƚ!v8QS2O\mDa0GFA:x ,ܴل} X󲜿.X]r oZ@i=6ߩŀSŌ#yj 4¿JP-4rޗᥗowMt}Mt)҉;&qN`bO[TMbv@W`֊VyjP\NTZMZq;b#e_(%) ,M4ǽV@ vr&9n1!ƗN "bgޤM8(M\T&Qzc#DӪ&`Sfa5KZ UqF_-ZUϷIHHHHHԧ쳀dh oq3@glQ/SK;iWmϱd՗m<+a=F/aMT²hJg˲Ē;_Rb9ۥ&k|x#>Y]~āS f5Sdس~z]Ugky!>+9x|NP[I zR؟>uYc$Xݴn'R.P{g:quNTl9~Z76C$@$@$@$@$@$K tOֆ J_ O=R>1_5U4UcW ˃)#`r؛WŠTwXS#4XD%y3vQqu3"ks%:1Mk06y}.ƈMRaEso 0&i,^{QqB6:IJrIkg"0j0&E)ej7*SIFd+}:%q[xx@5i& 3Gu.'Ϻ\w+{: !-ۋ;5gEVQ,}F      ^Ng㖖,}J Mu=rRŎe/I<2S}@W5бQNdcr~VN] ]3{3TֺE2u2z m XQqUˡTV㝕iJԙ kv#yf]6t\c"R-r%VA!'x?ܷJd'3hzb)_iM/eti8`ΖbLk$6r$dah(Ūig|HUa QڅڙHHHHH.t?(UaSv5b:JƆ#ʶrwk+ZR)v"ǚ!&:~x竻\.8uP->~?D/L~b][s4U5~H ~d;JcXT XO0+KqHdٝxC:BĞ=+=N*@Ӂ x/8B12x0*󄧗|p+Wd7>QTj,J~.¼h4זE     Κp# / LZֲ]in|BGw"*|KpHҨK#־^=߀GJWDcJ3w%Xw6Lcљ9<P!}1 ԅm<; / u % /OSMe6G!n>.?읪E$;o)߶m7^Q-JK7\?󱹎%1:- $@$@$@$@$@$@sNd0m9ܕݗ:lBzS;,,D hv:~0-h#9a>7.uY՞^Yaڱ7Դ fV$:O˓|GbmNQݱ&LЉ>݆LؚS-T`f)ĉ#vaE5j qDihnibFtveu8\9 7$gNHwZMUVK~d;ꮩF0rZZ$t[j~psNF\y,?rܴwoDW+lݭj.O{$խ-9S3՝Z Q-8_5zcf{"\`uaA.ۿWӍ`,Z0Qsn4 n]٘Rh_ k5Z Fa+xM0HHHHH~Ϊpno yO܄$7zQ%"[X-]~κan}aE+^ qXOGAʺNG'#ODbmrhލU%b}AJ[4|\r~$ ۶(و{QMU݁ukewb)='W2uSg %=2Fuzޜmd3GцP|7FM8VOw0IHHHHHBM_ת/޲/1 .n]_6t&h芨'6QIvx?Ew5;\]Ͼ19|U~H7XYb) bIL[u˩⽭L}%s'2r^dIH3˔c EMT-XCqI26U@4&w|k*[6%\z>Xaԉ-A曷+|gq2-HHHHHH.ܜlES M&d49ۿs߃痩R/_hY]՗4_{5V7>N~6<87ӷ'яv* ~Iy >houSo|E]E]3gP؀L|ϧ?|2 /Si?ݷy)k̭g~4R-Mq+Ȩ@TT@hHTUS64 qdja?׊H*`Pxg 7!wNfTm1'1Ei/'xRג%t*'O@?iml8$ɛ)ۄ+"1c022kn>[(;26 8ˤ,eݑ0s>+ҋ~0CN?gc}wq_֏1b5T~JfϮ;L*k?cDD ?q7v::p]GxLF$p?-MJ?2W>h߃wCҫkPT$++/$jC}K;D1# \D ;nKJ&(zz8[$ ,ӑ+Ծ I]K %Xs][F\ɸco,8_uzfO7jHMl|o(&4aǟµNWl{:q{r>}Vm <I:وYU.?Ig$eJJFi ;V9V7 )LEHOҜ[([|];if#xvuryŷ蒋6⻕_c߿VMӕHoL<~>ZT"!Q/Hڔ*      0pA;\ճ@ht"nZ(|o7O_Ёꉫ!7{ux!C:g@f^7{J{H0u~j4A2_KC@?<|U^%Q?q< s @nxaIHHHHHHHЯI-8HHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NnzuHHHHHHHz 7"r$@$@$@$@$@$@$@y]9*       ^@M/ @$@w^WHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NnzuHHHHHHHz 7"r$@$@$@$@$@$@$@y]9*       ^@M/ @$@w^WHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NnzuHHHHHHHz 7"r$@$@$@$@$@$@$@y]9*       ^@M/ @$@w^WHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NsX Fl>.m+n~:JS21@^xQ9$     KgUt#`tL~;tv ͧ$j@SK2 PGԝaU0$JlHHHHH؟ %P.^[w؋ʡ@ a[~TU4 ;5v_R 7ͱ U?#RTsHN:-M"Q)pF32'     nhڷmȳ) R GOYksWK)tHHHHHHUm(l*B)M 0G* EF[{`8b:'v @gsUg}(ˆ#Qdd!"IqKnًV'!6e4ܜ6҆┭(jlI~kg KWqyoP ބzi;`k}RSv87{E l݆epٽG ;E{ch3 en/9 #O1ؗTlfPıivN+S2&\XFr iW~#qipֺRlOMGf{yaPxiLZZPg(     -J,;II 2!l3rO&Zgꅃc3@1"VclbSZ7H 7@{}>>_[xUhqv h]㈂-+6.؄Ɔ:=YZY駾`#2y1FK 'w >vφC(&{n;,wzZKQ!{ĄD"TҊ1+J+4d#2{}(Ns Ap/A^)m51aIB ?_]|u @o Pcց !a GaLUHqV3hv"cT׊6}|+ٳ#."@.j}wGdt4]4>6)Nh?kЈMnܠӐ}04( aAzkmk'đh;aa70.͙:@lfщJmyiJX^.9ޜАF' Kgtoc~eqHv>ﴸ9|Y; /@S:[5 k`M291ynHXg PBXjD&&&5jxbh=vt<&mk1s\u뺣Vͥ%*Rd  PrGuu09"bpqyl,>NGliZ6+,bC: [n[NTS D]|,|,o;SFK{E#>~(KyE#awT#\¸`ÈϩqM<b4 [>zY 1z m_MST= SXǏVƒ*.' 3,bWkvcZ判<Sch#7Vc9`ᒓ"\m<"e+![qÌ$h#9ɪߑR' Ka1O`$.})dhc_DYdmPY 6!cՙ7 3,"a S,K[jpP^>h#gƔ)Xy=]x }aHHHHHH$p"9J $j-__02ɋE,1Kx,ۣ1I,چFBͻ'BT ur앭X#5u^p@xU`[@Yg-$,TVl\lk0Im Y_͖/b[#%D*,ٝm',h\Od 589@JA=J҂JUqYV,Zպކ5eؿ/iؾ}Ak+TLW^>VoDMcy$ eٝ'b ,GhDyZXK[.Sz?$ԥV}}Qb(])Isy"|8lFɶIg M$@$@$@$@$@1Qg* K병S?4b9v앖Cu2Ń0cfkU[auXdaiLryb5VgG3X}ml΅Vae3#rlb{Uwˑ^aN58}Nø0%]gO]%А+ҬZ-WB{15l.>^81N9hY&U'G6ͺC[rU+Dn=abaɇMKl܇[gZu%Vϰ5n?p}v,I$@$@$@$@$@h.ڃr*VdPfDi x&^>X|1DY9]Ae8tB8) WtL>ˊ,]:fq _pwBkm9A;vQrݯXBz%Gs])*Ejxq%%:J&"S֥MBLDl#JKNÄrI ;"~(Djt΅ݐ$|ؤ#tFd.D(-=uݥQ묘HHHHHdŠ}G7PRq+v<ʳP֖pdʋ-ƭ;̵H<1qx,A|bˮthKi^hCڪSgcRxkvZ65ƉO`,(^-DZ/m'R2di>VbѵAճH7Z,e=6!fk.mS_Y: vQFΙpXb5{sE:;[Y7Z$~_R/:=na^l~2̟=aSW9qrvc$@$@$@$@$@$ЫcwZ;k"ɱꩃ_ub‰6s}GS|a^ c2JݡiT/6>t܄S6chpM~"FDp⸎:Bw.^EΉ<[!yT.qZ1Jcx,=SڣT7WvXh޷m(2Vff(܍?f}>(OQj GwH(Dy^>C;C}=0D^vHHHHH P+Kql9FXj wgbKtːF' F,GU~.k|wLp-V:/fsCq\aWFsSj^t{NX-#iR^} m.+JON3p;xY!fyuiv[ Ř'&$vHk8 a){depɉqbd_P-)؃BهnqM3bǷwbgc4FJ]׉$    q#3WN{8,)AzXZB( 5N񰜟,M“D-ƕ"FTV.$L9ĸJ+6}=ur;J>9A޲(ҀZؤ 6`xτEEU\ƾ&4F?i/9g@þ=FTi3?J)-gWMN jlւk+\B,W".iqs.(      ^G,nON9 6q$%Yi7-B} )~< :8j7o(Ήg٭G$.VѱaNѽDD sTtHtb1.zcK1syfIHHHH.l9r8_ɠ ċP)Py8&Ic-;a&>X'5RPj}o=Qk[;Jמ6͙{nZ0 }EYV Bm־ Gɗ'aW-gjN,Pw,Z^sNP8u/gGK\xI Qۓ:$˜1r(EfvPlP̨`:NI!R>xzi۳)z>Nhm JjG_kCE1* (_,kB`ܚO8iG,1Ȱ0WDxܖX7Z*tpv&D8<Oo:ZΆnCu<^z[77d] KU8MGó[Q:tg2uRvnO\9`q,D S%֝'e;dWr-{$2" AmߡԪ㉡]}6M$@$@$@$@$@$ GVS}tf:9#>ݼߜ#,q9ܥ?mFTWF[imB/ ^_Qlѓqy 6t ZD >h?ymD9lU85_(Ϻ Gk,wmODѱ;.m;K]X/ov i8P~H)=NE)Ӡѣ#&6Vx %tpưpbuR?ꈹTrg&^\-3^kpغ$ J46@OV9.3zL      5<5OPxv]1$У 9jlS *ʎ=1<\z)^ @x"T;M)㍣75Jbp( W0u{}R W|,ّg0Bzcq1HHHHHz6xOU!jģ qpju wNShF@pkz"L8;P575P!4U5 B(}+ 7?'     8oؐ93LY#pLX|'; $Ϝqa=Ƿi 72 I@,ڗ o>Va@&y/` -;}&ԕ!;, UCcYV6qS`HHHHHHHz:'A]!       - 7Z @"@] vHHHHHHH(hi0L$@$@$@$@$@$@$@=t1       p0 nz`WHHHHHHHH@K$@$@$@$@$@$@$@$ЃPA]!       - 7Z @"@] vHHHHz:0^$phCS}xK](5ۊܽŕ8z\gAX3"k"05 uF؝\ zbhIQ#YHHH:%^_oEV pUl/Zѷ O$}t{})6ս_Z=1*RDP?!pG 9˾_|1l_h3Ӧ%8/;h:l w}^Ǻ>B <'7,R{!}mJ)wx Iĥ z({9#xxݯP}%Jrq%qPҵxx&d܈?aŻxguÁU #2Y=aH?yydDax{I  83-hTW8,;܆hMs6lH3 tM΂ƪ}-,,tShnFkVo(1z+Lx̶sA QXX`n*~w#E]iKt5?;-.Pj i.jESzM/c>tbxk0Ne,k QMGrq6Xy~'X~ {B@[}>OcÔkOq#jI oT0O,<ޝ~&jAc$̞~2'!/y$bϑEk|q,:߰x}}WZYG cI3?#p^/c[8;ǜA0Q_WJd2jhd2}swsXԉ]dR*j/>y®mS^[hY%kuxwXzf^6&3cٻ:{¼ 8r y;tUX+`WBn;mbYZV |;Ghfwv)>7b K4X{ctBn󣨨jFD?RX +SB2] ;񂗛$nnb[9BIܕf1y[v!L YLJVsq* KշkBۥ 9t:)5aGj1bgDufvO`4Wo=*vP> C/8+9emy`S~i$#lx$EHJfMf` ؔVN 4Jy<;m(IG7#0lׄCx!fMX'>\W^:1@hߦ9sk-Rׯg~1s@{/>gX%>[ǴEXr WcN(8|pR<> 0Z CK?}2 y~)+IꋚߦY}?2F\ ׎ǶԅpnDaVMG'Bm# 0.Ƶw9 >A6S#ǟ}pS-\qvZ&k(ݍ\J1900 D\' h9U{IwLo>CbNw+n7LO> U+A_cs)wk 6W[-mbߌiôI36ю]œqMhs8~^&VU#G2V{X4-ܮ   FM1> SEP`&UزOs8\X"Pe0&̘[.SF;weH-ݼ08 c&LBl;vqە%3ZCD{тtlEU(kPrhcB4D/mI~IvשU8gݺTXv$2wFAa9MVqHcuMԡqs]!oIGImb=#/ͤkhulD-*pI =SXEH5KݎERlD|I\ +W]x$;SsU=$vxiJ+*T`x4&MJFKxlLF^x"XlCqV5]7ҜW?䴓2ɣf@c24,&"Ktk}R2PZqyb^Lm-Q8T0&9!CyqɒuP9083s2C`_]ѵ>")LS'.fl`#~A?6]=^w>Gf iguHj+5**-MUA)RImSʹfҝJ0UK4뒁PYi+Ei*ي??`#$WYTzEB ä<#U0ۑΎ"?^phKQ|ZĹåV#Yw ;Ԓ@~ 0Dmy#,։e؉x/!UcJD,~!WsBAMT11o$[7 CZO.YBqp|;z;fru6 l_^ĮOr%-XJ&'݊=x9s&m[/r-uO6on^sv}VTRYZ) @&ЄM 3YE"x`v\e.=#(Yu?ҳl4YJs}uSeAfayY{M5C8ݲb 8T+0nmzN򷽲o=allK~;=Ǿ{-q7 ҉Qe yTrX=cp31PN+L寒',MAԈ5e5N\:%x&+'x<U-8l-?6\E%?+J&4lo5rmC8N.5<'1-sEi˒>'qNQ `ʯ@Vc:JE7jƃH53[a>rX|Z59<8/ nt! z\_HR9\~=4hBKG1JQ q!$%6 1%7R*N>~jDYz]W"6ѡh#W$&\ XmL~6rx7QaY+fg6"q`?:}xC_S.d4Dʩg#7{]}7ⵇ^t.h 3#>~e6jN@~vڅhPo/\Td÷[ZmK;mٛ>#oFم:m̥xScmZd̓HH.(hg,VSm0o[QyЈ|Mzn>MԴ]XD"2HWkm#,"C p0~fWqM;h#mm| ,4 jr7IsU*\ HGAR4ъ\h?C v-Qf ˖ZbAdj=|ߵLj[F-xF Y C^>?0h՘*?bĕ_67k{m)J~EM_#FPFHBIQ\Yxwܸe85ߜ,嫢^^o)Չ60hS{mD>_m|{j9}uBU5?"ơ`Iags~Z/ Cdϣر#Cr49$<{6Э$#f߆ϟ AO1QÅ;5bϬpI9H ,ñJB"E0>V3O?b5DXluٺyOg]xbI>ܡ{ϳxDY!~$4n<ld=xt{9^1a9}W`J:Grxfzg^;.9R􀾷Gv ]M^̟HEy_/C8M ap{=6)r?ʄˁ؆6gY~phez֥\Fy!Ϋ 6KP|xTIa?7c{\!p9hj(‡[xjppfi[QF`|\d&r⩜5(h1BΣ00I֜y "E'(]$@$@$ Xx28=4C\,l.4]AneA3m߁ll;8ߣ [nD}Xk)êA'#Ua"c5|LAikuʏȗP\{ & |!Qjs7#o∳T_`agf̿q%]G7oϑtOBxlX/iYWa^R|mX&/Yظ? Wtb]2"y˓TDAI݊$R6 *H:e_)*IGӾ§d.>=Nϓ}|1 /Zr7lф1oFY7Wk3,B,ZO/EӖw=5>k16i5i0 T#(/_hmKhzYfEUQ 9$%ZJx/>WGŮ>T@WyuѽXT}u1č"dPS kb?;:n=tuxm,P\ф!"y'גa* m|Y+BhE @"<\<ʋcP#֎z"&jy/~Xu|| fsY&L3 wF}85xcFt[h&dt&9Ҋ`vgW+݉BQ-Mi p[AS-ҦC)|m=5p0jvǥ߄8˜A p{k>cE]ɒ#,u/W:F뒪e;1cLBYwzrj@\{ExW,Q5u2q-wbR+wb|«|al8q eNWwfS:b5>.V&m>1{L!V .Zw-8z,]a< (^+S_\uU׉p0Q+uۋ.ì(}gMFDFp҅F[xvK%YâƉ7?Jրp6kڇMiZFz/C@ .颼 ҈I0ZJVa^GaƌDIs0Q-d-|5B1 )QĔޮ@Fp=3XN kꄳIJpC 0!&"]t9Bl oP׉6e-~YغݦEL)ÿS_M@[kbG݇Z u#YYD㒈:Z<?ڏlGuO]C5*HASR "H'Gp8g{\~xjkk j[Psn<ZE);w$YB&=%lN͙x I8"?^v% FH>LMF͊h3tL2rK& >A/Xcql=;/%9A%m]JނZ7:yxxQü-8T/}O vAĒlkH@h#g4~"GSQ-XH]<^C]ȱwÐmtWbDɸzl?(n<&it+6;`)?6 rsW{?;T6$"Ja Ӊ6Rt e8w&Q d !&\ (r((ʺx*^*zA7rW & zf{L&H|TWWvMOW2t*+ժV.:5%] >͕21zX[ɳ1( ƣ)%= K,m;}BO\q޹Wv}2Xe1u y9Pߕy%DAWrm4.oeT)S~S[7~ Ø{<ki,M0L&otK[T L ^<;gxu ? sYbE!~d\,_ҷlzR-Ea(O%~Q:`mEh(v++fϛL%.ܹY}ceg{wzJ/A v6h1!HHH&.|g݉h.~GbmNѳkV10˒zkP?,sޖ]h|Q>&|,q> 9d9j շK&r)/Z:$V `U)eGZ6er8rJP?xۖ/w/u-{b]_jhԦ,φ=F}c-8j:z]"˯\^IQhR_ ɘN5W)RS+NrXw:[{f&"M۩~+k;Zaqj 6;cDȭ@oⓠ?2-t|1y,(m-*粱0UhTԸOTgd%Ln>2 񅙘=>-T -e s WTDw+W>zE)˥rjXhTlg:e:wb,[|(۱{;VNy[/O;o$EP5nR ]K8fdJj"F;/pooŶ>~uh>w1sM1% 2Dh׮˼F$@$@ Wb9αCmxmJ}O`3ABjR4iD7܊]{hRHKx]_PJn7ęu~/;-L **O+yIXu&g Ÿ8 ڼ4$$@8թnUJ**$WIL*EA| **%iƜa`J!*bDy'ل6~y74soׅ?A}[GhGi&Yk:4!ئ"Ζ~(m|E3̕EF~/3M)ֱ&̯҂0դk^Q5XY.,cT/ćrmCB m$m[ MuuwF]7ꖤSN߆W}¬]V~]M7w{ǽ缤1DpR[jPIa-ZHH.m S6'";*l#Kzw7;'Yըm_mS_ԉ)((ʇ㏰j崿4 ĸ@[]Fx=ZJz|ySP8õk{ >IJ=gDMa:wwJ\DVs=[_D#ٱA.9)4Ϲ8 vsńF1ScW#_,*ZP߹TQ1*;x3>4^vOPo"F̷gcڀGG־%m閯'  PUFCSe%A']+ : ׉%,& PCmg(>-C!2zNk0y/º-6uÿr4Ā=X/;n=kZO v]#Cb؝RZ+''S+}W%-}y  aX!GO1Dqb#WQɞjqhа@dK% vGe8'x胞ݒ}ѐO-򹠖g"3&  ߋ#U#8ƚ,*Kʛ(R@A wEJ ].}O*"gAHie¸MW|ǝ ۨ;1gf>u)׹(ˤR.LzrN(^IeGE~,2g~; \y۝_S=LKv,\ &?~9=eײ-y. u=sx)؎lNhb%ޘp+^|oϝp؈!\qc):u|nګFRmW,Gp{`sc=+78@VbͨG!$ p:)HT(HY># x;P&}Dz/6JCNnǮ\{_~T `غBJe{mL<SU=.FiGdOgu_}re=_m=2 ɞ}.!I1$@$@$pA l_D m#iG>i{Ӽ+"/j ^򆼺6GSE`;+Ka&_{eg 3/[_#Y+cT.y譸et?>g߮|S -?8cPS?>Wj`]+-ׯBV`يNUbUm͋|Sǡu47!&KOH(4|[eя'5Bf$E a੹9Ss:ps$VGc8;6 B#bڿ;\)9S ?JF1x6ݺ]Bmz溩; t Aw!_Ա4-ˁ ɷG'3Mp1NuMb¨@]-AA'|ڰ~Lsl=G7L~y+烬jg w,~}p 5~?m[6b9X[hZo_}vGH: FѥS;i)WaiW!1*N`={ht֜ld:mҰ%CFclΜC^SO'8<3jېaԸ<7XH$@$@H0714Ŷ]3akjliU]NjSjjcһR maeoA-q+,1<'b!bEԫ8ƉGe\X$&zXda.ﰦt;BEcع"^{5oeIb(kIFc1gg|ܖf!Nb쵚JY_q v߅ϗ9\^-nֵ5:LN[Q O`/TmQ]%G1k/iDZۑb9U=FUzK@&(#B K-"sy(@Z[U(թSS/L16.h cw%:Fۡ)I̟!櫉ݓ0{nc=6u'&x=}V̘(yfc@֢0dþ][aҳg܁ycW sS3]=)7'ŪИ_Mʕ§-;}nTnqH~'M4.KěKTOa5E(\MoXYSOR2P5\X8Ja-qk"#-,ϔ\֭>gnӟ*"2 _0tͨh)[)g_nF9Ex4|eZMŚ~q?#/Rкɒ)dfCG`C[B+j)7όbܲȗIgn@v8Ҵ;Bºim4C֍e( Yѣ8pՆ}_G  N]-4BZ>R@xT+zAH( %.?NwXZ`kY+ŗJZ@ |\8< ^|wyJ'$լ ŽЙSp~} *$`=C|(v-sos~%] [wa"i_Dz QF$ EzM ^IS]zx=>]F凮 (&7~T w(_ɩ>xpe,r$^_5rRD)LqhLS.::Ƹu"NoA=%sf31gQiSWC!lk>L<;7VpfFb`<Ꜩ? ~r6mu=Q6q޳Ŕ)n+n^v 4J^KW^7}ѩ 9UA0q,h?zθIk?귿caȭ&eR[YNd  ho 9赸ϋn8s?Ɋj1 n{)n}_H- pҽ*xFUOQ}<Ҫr5$@$@$Pl_YHk-MDkqmcxxDcjw%z*HIJD%~8*it4;XW{(܂A)[؋ Hvŀ1C"u-Ax :\y brmwO2d\8 y*;[4jzYW_X .e3,wp9gW\}JE\p7Տk J+WRv^*Zu,zYQק2*]UaP1RJmUAr)D:/7P,s"hVR>abQcFz?.XQ4WSۙ\ S>b(Y^DdjXDVқ57[t抿<1 -zDՐm:fylKgdoĥ]6雹(0 su,` {&8_Lx|g~˵hV7arO|o\1,?1L<f2dza=[|?y6nEwł0oҪQ>&tϘHw\  ΜDFW"r⢊9x ( 3W&J+&ٵH^rVSQ K"q̵Nq՘ @x.u׶ˊcRpYq ªҒ?+VD,˕]TD(b{Ğ@pT:/{X7}fOjn/y\+|5)deGXUe9g@)+|tNmjo~^O)9JHcPÏ6gfyPʟt~zrCl\\1p[EHQxO!廴;ݱ͐Bmkq)`PQ]wp}}Cz'"2&W>sq8}}[>1tvb<5ˤ?Vۏdmnѻmu݉-uYkg+ge/nnWyW "P%ʶ <dlP:nߐB 넔uXo_~֤/m1YETyej0T, ru8.ѾSi#Wv0/*_ªTڛ. fle'.Z+jW~Ca)ɪKQVRrq-Qs\xeg Qh)/Tvy0Z"S7C)BuhH(\j=}C6 vI @'P\G짌W"8i%MR۵"f2jk_9"dl *J9Lt[A~0 qϦ 5ԿK& 2QݍQ<+Us^zK/L be`Ê͞ RkKN$@$@$@$0S\Wo v*pO`e{\p/'6ќ~#" HX^T[ƶ}g<(NW&bOkѪ)FH}͚ CX%x2O*$߂;_@ P \ԍKRu^1(Y NY1p."HHHHn@X=WSeV^5*Me%HPI+m9٥G,8o[@U՚B|P2W^¢5MQTRͿfqcF[[J-.q{xuֺ8w#n\/&"   N|h}neZ֙~$p&;{t6oۋY'a=#/GGE&Ih֬ǔnDz 7SꪴR7OAI {@rs{V䜕tpq4X7GnRY Y͢bqF_n.3UfEH*nȍ`5HHHHHHHHwr's       (#)#7        wTܸ9 Tܔj ;*n܉HHHHHHH*nȍ`5HHHHHHHH7DxN$@$@$@$@$@$@$@e7eF$@$@$@$@$@$@$@$Nw"<'       2B2r#X        p'@ō; @!@M ƝIHHHHHHH⦌VHHHHHHH PqN$@$@$@$@$@$@$@$PFPqSFnA$@$@$@$@$@$@$@q's       (#)#7        wayzz' yPobIHHรBCx? 8.K J$Kf/iEe@nnn+F$@$@$pqฃ" pq GQ0JE$@$@$@$@$@$@$@$qB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'r@ۑ_1opU0dXiPi9yq()S*Gvh?6(&?u H\s͋kF9l>AX&NA:~Aa+*lvJh-zgKad&_; _o4Fqm)+^]Ygܮo*^( z -i J,>5F*oB}-e=3KN)۹,맅QQ~|]Q'*%wGc_z*mD*hcOq;#xjFIRu`TsY0FS9Nba##1YF]]iKVX}#0!\% }β_?]OSMi\$h}s?tY|@B HnҘc@MyS@ѯ<̽;yvl:'XZh١z=ԕy`w–5p:2ED~C}jپ";W-ê5RMbFضso얌HoBD5knul.`ǩ]',AG)`zu%+IX1MwK9K/Asz"맕hE6bظ,WR1``?4k] [OXtRc"cϿb,sp雭RSqYj'ĩkgfY\Mg@Q%GH6̛#6:H?tRW ޗ?%jCTB'q"(}ߚe?-~G-Y{1H~ _9MiMW]KZy ~LBo*mڞU;GS{8[1zٯX/ev-$4n-'쵍ot݅ cSp8p&-Sк] G{g?o?")hK$ke3aRW]#/`k;.җvykJ|Mh[Fr?۴gk:DS}_-ZE.JSShխ7h+<5?7`Zs7s9zoހ+VC5_8j'[ Y+EGHQ+/ 7ՊFOLp @[Q~ʱP+G4FHv4=wwã-+~$rI{yyX߶9D\V̗7zV]>lBتyk^CSu"S s⋽y"t9.nMD?.F;Ek92%SH֌ bw}cuI2*T13o4o#U1% Z'uaUoO[r=1cޟ/k?m݀YS1gMζ4!OqઘxRmQjॅ_-_Y>^OpQdqkݱaf7+zֿܳZ@S v5޹'!m$|U3UL{қ:@R_,tw7Ch,YT;n(CO;ނwe+67õ~]O0@vOG%oka-)r;}c;^QTT^DK2&uzi|2 @o!ښ9`TdgŒZqTgaO.gNO}2BC<;uJŹyzb#ݫ,c~;wrAi׌S[;󬊛-bP0ա5u̺ʲQxzJ^i#ۧG_RD9i;^i4ad0Ai/O{Qk^!uRWo Fנܯh ^46)m?wm7@>)mhOgaǃH.iU:Uib%0xCrb yv&O[]g N*ԤXӷh1CsiuOHsnS!y$:D\Hym^gѻnܯr< &C!ӯx Aa<˄% [e!zto)]kiGV'ʤtAdEvax;}8@?rH}}m{N[qnݍ>u~9nҶj)|L%30(rEb;oL|[1f/ށ#u Τk~kо$yXI<'Hel_TJ}ƯfYB7*U7I^Ocjޢ-npl^簘X?oX[)#ØuE;K+?;?x&oBVȲ{_ʙ~Tx;sYJ/&3(_tf4O?TbEkVEFbJgbŤWX}!<1 [U8q^gƖPYJ@ؔD%{F)VNy<W;Nڲ&Ly>VB-\o/?_eIƃЎ;# @ `Ft@oc$HyZ>M!pbyJhEQux/i,:'mՅ{DXn÷_ut/飑2j K5{Âk_Y7բ( 8^™jΟزh6a g;+W^th^9uzT>eؤes]p $۟Wđ41<%{VFGJPKT%K8:bSZ4}yAi$j1/?4P˷%\t^ƃ/>;MMp?]2Nd둇`+ b< 3^_,q&:pW˪ f;S1 hrp Kfc=Eb;1jq3~v(F%)=S8RXvĝ$ oG;gŒv.!~oP(#Ƨ12(܊y m[wQ ߙVka5DZ9O?Xq;XT+g%su5(mdx\1 Gg$FrEv\*$itWYosM,H{@vK< mՉ#Y$}ޠhuع7S @ݣ}C+Rf\/F{7:u7V4Ne+˒2ߠť0==ՋG6j+~Wt! |)K*Xmz{׊N] WozFټjZ1y"~9}1LD$@$p! 4>P{_r)4doǔ, U/jp5VW [YK ;㦻3'Gh/Yg'{^V}sG&g# 7ԾɆ <ˌ.n#cqc1gV+>«?΋ IԆu%8"QS(6dxWtSy5pM<?c9Z;+o"Lz2>j9ޒĠKHXD8,wZOJuR*3TR} pi?` k7_@kEQguɽytmIR+68%6Wv3}$C(E痱.=<ժ7.p,e#|g77>^LG:LCֺwѐ4gtrS| hб`n_w'>Y`3ʩ/a嗢:ݠbUC*7y] :xpܧ?O/ UAÙXbۢd8.< 5qQo]Sl+M}7A|/Ûe쐥[O6 \8x۽ >#gl״qօkR_ޑe{8S1?E\ڹlLc (0B17 ,{Y;~^YCothf$ jwe̽p ( jOX욥 8__+J-Z2$Y ɭwjլo2gSXeYE6ba5Yx, n_!h6շ-&<;CiSgDٌu )S'?Սr,?>ɜ%<+^ia#` Wr:vĒmuV^|wנv2~[S]m&L|y ٴլ@<3<>v+/]ﴎ;o@_%z&{xߣ4,ʜ̲ݢ3Xqu,lSM?ք*qvԔ^ <ͧcd-$@$@ens @BYrR+wͰ(ݤcv)8xx&~# <nmzelo yi\yh?fsq,MLID96Jexx"rc\ _9f b4Yɻ{y-1†ϖ㮜}-Q.kSu ry|>EvSmݡ7<ߟ])TJ'xcςo7J9mYT!J4R-U(R8[6j01ahgF @e ɡVXs<l _DD۶>+\[|gArF>VueFTxtTNUi;|+{_96ՓU&bs=6Q/ e=(HH|_/ԿrwP. ޔ6CVa 0Y#\2lYvC?}eeM {8_~ju z`/ȨJI U:`kڷ=@S} 3@Yt~} D#dT~b8~]tRR14rVyxw븯좐<7wWp6`,M卌 hú#D`HOux`û>~?tk_o%JoS`[2zp^s:V XvvY$϶LAkg.=㭙:js\; hr1c'KR-M0e>W^6~CR?Y-ñͳ%Zg!w#uY/wӪ#N\3{5Cn) Ȓ!븟\ʹйԮ$1/ @pl#1}òO4.FM0*mqQ)c: ?_&Zea[ӵ.ΥcUa)Z/f݇jF//3W}/U 7+;/7^Y%daSlWٽeWLzx {]۠8Y"s̞6G +zx^G(K$WP"ո*ſ5, FͼRa|1)i/' ,C㲖Xώw_+vK.I]pC\S~ plB8u&B[1F]">/m #A`E3Ox_,V6+6/ ,T]i ,`ۍqOP_thؘ(bCXq*M@t~x^N٢݃[ Ej-X\v7& onOgPT;2>m?o 0bE ꛇO?Y>]SZAV =i3!!/msTi=#;~T;e%ړ=,^v\{㓾3~:>bc#pt"r$]RGRO$@$b{&pK^nc%]vÖcӰ6"#eo;[VfCwcyg;5Om2It5CS縱i]rozb͗~5 G`[k![{_H8J\O8#qD \1\6([v-/ #x1Rv3ax8+U_ż/`;+;s,)o~l-7ב4~<>܇Y2mؐ{|l+.!ōs4lu\+OLmy'8 Ob/IEŋ'uM17=)fO} ayhkζer8ĩ+qț''Ls(⃉c=R:3Gȧ}o>?5CЌmxxT&RjoWX,?nho 4Wb5S=}Y_9~kI4;q@cO;!m_on*'4.pgsϙPkTQAW|%c< \j=[ah~Kd=a\4kM_No׊ܫBo!EiwK#~ޑ"[^q)~"M;)Kuq;|l흅 '_h4 es}!|1])s^UGgyY;yRAצc0W{ѫUL2̈oCbn{v/&?L[2eH\9#~O7 ŏ2q?jý($ .piit9>O8xw(m|Η$=ce9եrUv6{M;zίED_tKbMsh/ץKTtհiP7ހz޿#J[}nE>͘aqll"U9Ks߼<"{n޳fn%bO=>  ߚbp8~np/|M"ˠxqUv:*F.f_h,pEOIݦG'{ċqza"lGv!͹±/wt(Byd<}@cH$Le2yb*# ٹU*(&V """`PYAdP5V?%f='0957[v:s5-{ %U|LdKl,{ۣ  ƥM2rOE?OEZ)}JB jF r`[gkEG9'mQmX<'rr٥̈@lt$UK̓ZY)3Ta<1f@8'}(/Y"ﴴjCEeE$@$@]ȑx+{=-ʘE `.@QS_ ݲcƣza _ 2,y{c_yx^qS/M٭z޾_3 S9β֌HHHHHHH8}7\LrabB? yH$@$@$@$@$@$@$pPqs 4]*o9HHHHHHH8_ 2^MqW{[e_ WoFAΛ_2N#      {䖿{ii1,gIHHHHHHMTDHD푏p4nLXlv@$@$@$@$@$@$@$@JUTR8 $Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"KNzzK'2G 22m5 \t8n)u?K'|*nB]@i-MM$@$@$@;4E󢸍!kCP^(pBy &ޢG$@$@$@$@$@$@$@$@ժU}\ .>n\$@$@$@$@$@$@$@$P)u,HHHHHHH#@MpܘHHHHHHHJ7 @p s @       7qc.       (uTܔ:b@$@$@$@$@$@$@$@&8nE$@$@$@$@$@$@$@NRGHHHHHHHH 8TǍHHHHHHHH PqSY G1 :*nJ1        Pq7"       R'@M#f$@$@$@$@$@$@$@$*n\$@$@$@$@$@$@$@$P)u,HHHHHHH#@MpܘHHHHHHHJ7 @p s @       7qc.       (uTܔ:b@$@$@$@$@$@$@$@&8nE$@$@$@$@$@$@$@N KH e˯̅A\-eHJJ kL4g`JăOߊad~     K@h7y'gvl۶32gce3 i&6Algsqlܳ+v֯^+7`3XUjavH b(0Y,[>-pE"_—mgj,^VmݺuA t{dyM*1ݙ yq6|{yN$@$@^  c,] {JrztF-KI^d kZ;2&N]{sy'm+eA аy{ IK6S1ƭ;kEla6Dvҥ3V7{3'cfl9t罅(Vˑau9q~ c g-Is !fhSeB̦-gMC8:-Pv\2e.r27m[疿F3_UaS%(aǦg>,u$%G#^ڟjCMRUjKKThuG$qE,6f,x>3gy}]b-AGre4\w7*7N[ Cxg'YL3w!>\,xP8`5|x[锍8Muv6~!P"phwXէy$Q]d@&_`L#`qSՉ8kD.}гcY.sitq$]7 z ~ 5s(H>2!nmbo]И^ۡuONMعh~^ǜy瑸06, ѯNn毴qLUHc_g<ܟ=_5EXh$DϦ υ拈7m׈Ft gr/wV˙S+^Orl&r?|pG Br$CMpԮSj~-a X}"M8 $v 5u2~L 0&@."@osڔQ:6(;ӷ'e)NBܸi4j(,~K09 TA"E6yBۧۢVhA!~dL^N{H4QhS[F#ij^װdxB6i4m:W! >>bi^͸;KY|)] ]vf’?vI=hT*+š3Jh|W~9rrqc>!۽MkICX|DvDuH._b̡/m0L>H tp` yUw;9)t0MCD*D^`zv p&`o.:?mٽ񴃴iP&(8>&`L#pllsx?{53(<Xwx~1}$L_r#;eGD.k W):{`[ZA¶tvHǨTq,O!kP4mP]严Dϥm;~,i#4?LCPGa A/l;_Z1l/A 5:T]1?F2B8"vX#WBW;^ .`ѤO^ΌCA1?R=bZԏVK#mgo2(f_9g_?}U/гumX7HзuU",׹`(|dXJ b $׳ZGTn" *,̚376`?39% 0&x B!$(>rHtBvA߶bx;21xXq`^yQޣ}_5GPLEhc }Z9d4 "|f 9y6WG ˪ f?U YF z 8 mTuKoh@' .b]h馡Qɝ%`1]uxyZEgK* O`l[h$ʷ:JًhIkNŽrΦ!ՍR4Ou5Th#e97QiTB1HN=(̆]%%ۊuq E%BUZ)"3! >pG.&"5զWk4FúFiE6'2P{ n޺ Wpb״հof|R؆L [d|-vc4c _C~0ƳʼnjXĶsM^w=dv(qLx;rr&IԹؖM-]q9D5h=Kh oUvcx+{7a톭J} jfMH0Y!Adڂr"șhkpFF"v SdG$gnGI(Y+JT.,i']zm(Պ+33w>c)ط} 6܏KẅaP.S֚&hdUh jD&3qkg| 9/.Y [5C-doӱ$ +gu;~j\]2V! !L6L]wDU+k{Ѹn#*9RYt5K!>#ФE3✹-/MTƟʱm֬O QRx,3 tEi7ƴfvLÏ4үW[[ zX)q'~],!:TԈFk޲Ȑ7Y, u;-BjV4䒝b؆2.\&آO~uZb?'X5@yjv 12@;!܌l?:3Ww_.0|91Cv#}6bi<^ű(ܡklbb> ߵҸ1]j4k>O~Ccz'[9/T)ZVVN] TtHچO>a[̍jx%i^./I;}&/зy| "rk'1eJbR-QԲ*YĆPr!},)$HFMa=!B@9=U[>ff#1e(גowPuJO} y3֡a-Kq (@]DUjzYNkѨD'_T:]#f s>Q׿+ByT{~wB}4!Ct]hM|thSp'YoҺ\ٍ冭zq,[[phVpi<.:5:];jg6^PHKib˭޲ jС$nuNsm꧟};!iL ݂OEJT>$]xL9A2Y#8=Df#OǪ6qbU*H\&Q'-LMMEb!I;U@lIN<ȨA`,K'~d&)%үP2۷FJԛIv7.R"ۑcIJ`T؍9m]bs|ԫt#G.<0Ah#]2,^WH· v+B"]D8)H쇳Ͽ.WmWQ,1#!u'2?QJSz+j٣lmvҺQ`(cIh#('1T,'{= {Wm 8qDLtuv]Zn( mla|Q=d,O-rr$_Hnq|]J*];m~eWJNdAhcXObӪ{*ͯd'0$Ƃ7GŸt]vJ7^hmjUS|RWGcnrkkh-H.>^ (ʐ%a(2GfTY]_E(e녃XB"Ty3Jkro ߫-ugFo@rIBΆi՛A3'ώJrgWeS;28jɸ'FM'6t:P]c`Y~j=]@0BZHNڑC=JNRT\VɮPL'eL 0&%tu[̀qzo _ ع|M039q ^rXF E|YG%S.zRl 򖫅ί;<è_׬ sJ*RϹE)]JҒ[G0`DT#)|Jʔ\X1m,&w›v> tn$fn,J5tZ{`r\K ߺ3EzĝTqnk8D6BuKCN%N%ݪݩ+: NJ i1xr &dis`֜" ΒG7]W}֟$.%.CݪeAQ -\ r!͊tc墵[tpX>khB5 i{A-ds9+rO- wr5QcNˑ;u͒x Tl:P׾" 3O'J0([.Ttj(zO/Ƃdeb|ه 0&EֳzY2n8~!1m\(Ez|Sl?:لX`l1:4:AD20y5;q.fC+BڴGv{G\O8]:\؎9FhlZV0 EBZc݌Y&k&N[lysUԡZ\_<ޙ2Gۀ;&qvXzVk}ij^o΃*:U!֋u^ éo%"iaQe-+aΪ.C5ULJ׼pVOKzXx12t9E!"$,;]SmA$iֳ*"ZڶܻNʈ-'U2ڑNu$햦:uu^fo%kU!~9V ZANg` OV:ã9oւ%3~,+t$:BN4;$ ^/kgr} 0&2>%?}GyZhSzG6|?c8K$;0I37-q=d|`+\ϴ.}^'ێ^BB?i:GԮQQ# (68]I ޙW/ cʵVr %hVLSlzBL].J\ݒ@ #=[flW|8UΆKV-2R,}dom[]j[ [c]P@*y=:itY'*fPbEDfC)eeә>dUWH3U]ʊ3e-YI2&oiD+p\U; .A}̓Ui +&Ihb4ŐwcxYAfy2󧝼OEhcfC^SmBMߒ^ l7[ C%}CJhJ;ZLYwa UkX(q+m5Of5Ƃ%mn% 6c@kmv~n,s4Nl |}U[&c]Vg*/zS06JE͗[!E$cL 0o@ ӟ![eߵPnl8 m Z`ĀaK9j'-!;2Ȏ0O۱r'ڽ{m, GOfY5o nl)(*)bW*kcŠUQ^̀܁#7%[3a;l5kh)8#.;~mNQAsd%3JczzڽNHCvXb,A7('` ޞŊ_X̓;UȍB*ꠎI(WR/ݡQO+QRA,pvzAI 9ܽ_5OKJbe![)iw' ̒tܯe 0& 7,Ȧu nPfG\k|26.Čܟ? yHp]~Y6>lyr(=آpgpI)TI*%”5/d/E#7kZL'1Wg f4BW# ݋l n@͞k"r;,#=1^3Vt@MO8sHٴtH3lR@s/։FP!:5cE4t_?GPHWq6<5DA4c>#iـEm-$ӯ*/iw\صB]k%([^Q=p#BF,۰sN6?V2v<)fn;m'ysfFY*xxR&pյ`LWvc\AeBBc>ykkVh m3DutX ̔&e5Rw\fN'Hpc1aBW=1"]61yA0[p|6qX; uG67}u'TZ7apM2,sqsoqoIL֜e?z:-kP{_< ?|u[5Z<+Ҷ$ư~?D߄c=:_v|yV!vۖlG6v,@q7 _W }ZQ,o YioKA|ԅ#Qem cTFv=wR]G"G#_úƤ RnDI/*ZUDҖkU4dP^&⽱Qŝ :g|)bGhP1A0_OCrb.B凤`^Q_$gciX*WNDsMѳCcax0ASNwyPC^9Q"$kB٨6h}ikH>y:jl Y^wcb`[U1ڭt$xiXƭS1wGXT. ZAHE['Ǻe_-_U4vDj∠%XY$|3`4*G2]/k;Vzr9p&<! 8&Ӷv{)n/Rg=83XY˅Q9V>e#ZX?'6x2ϖ! Րh5?:c*A:ۄg&싿d?c. sg1=KG0Ybr6:p_P>aL 0@HW% F.st{` ٤QYi.ïZ;~ld`㗣N1/~ztK43N"X:.|R0`jBHK؅_fP?cmX7J1%n*«]B`/v%8DuRYҤRG8 //ܩsU}hSݱ$?dJ,V:g{ IXENֈF\24jC5ڵw/!&$r?)Ӧ~k7Eka효T]Nu GYb( +SN|?_!hְ* ƃ?OTw}Az3-nFkx뫑XӗX$7vz4\f}j&|bzn-[#Vw>ҟ!h9ZJ~z3r1x&%qK=1=Nꚗ0j==DixM|{}1[sTfOʵĐ>tF "浧j}IX8uiCΗU+7`L L)޽Mj/o}T5m&lmv1=z&:&@ӧ??.8?l"a\σՁ пQ%1?MSP(=!.H/;hc(EsU 1]PM{bi|&Kec{.xûD=4~%Awi׋3X4Biݨ;R"V !)N"IhtaXkDacї6zR7M+>3lO}" yҖqw45 %|4gBP\N r< %Uq&CNddG|1]${ ./5pjtvƈ6MBUᵝ~']0QXը< C˕Ҋ@W4;~c'NtGa~K} ;9r:ѽ0"aJH6 f]*E* &`9$`?N)W=U;ݟԟAٻP-5SnN o`%C0tY7v*yRd ae'}mm?uƓe:Q_ZdN:%|֐f.otC"i+f]g_fEjevoyd+Rr f_xZҕ`ẁ.,thuu-2LB$%;8|`AJOjGIp3?C4 ̎߾zeӵ+BC+㑂(y\+pv@ɘꔈy!,mžӍ\KIe]QIe~HUE0-Akdʻh|#"5p!|:-o羾ƞœpP։HW(I|:\탂dv~,B˦<]8sihpl}eD,m9SAܨ0`L 0&`LC,Խo5ZUc oTDܹIq@_vL>-7 ʖ۹ `L 0&`L 2YxA>{}[6{${Afq=~_$ɽzqc5&FqoY*ν߾‚(? `0KM [`L 0&`L"p n̞lۛ;+!^])wfM| fq ]u(O% m?'L 0&`L 0&<߀r]\;]y`|ѵ-nHD-j!Z"+8m-z'0&`L 0&!P*uoPsL 0&`L 0&NwzП0 0&`L 0&`vW&`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R>zz%g@!qu3`L#|gNK!m o'1;I͛w2z 0&`;x^+|(#ϣ>,tx5F 0&`L 0&`L+ƍW0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>d 5f,|A ÄtQZw5=N 0&`L 0&p?j/ط NM@M5zC(R$UCcP>WKQd[Hy^E-n(9nڈ6nőQ@O @uѤIj$.vE c@UW>{ryͫUf3n6x2\% irK?fkk4^W:h0aɨ0oS|+(VgN:U[OX3V~ST)y}1&[2pv&lX?))[u6Bj?&va瞃8v"nK㎛@ ԈjUQ~pܭt/ҹ!muB1Oi)v#=b(n RH4jrƜ%w&͝}2sϡb!C4g+GdL,qV9k׍xqėhT7UYᇕ{r~*k 1v7J~3fN"%:|o!7Bt%H ^=f<5鸈k,48M1Ih#cHmAI5жHMԫZWlB6`L~"~~;&}-0E>.'Xү4.=z€:#NXDO؊Sh=qz-zO|'gvUP;&j4fX Ql_!5zѺD I'`ߞC8|WT! )*݀ZQUuf\qذiڤ∊io5$QޛpdKlۋ׭hYNzP8 ~L0$31q_kF,6a( ۳6V]O%jWL 0@^<! Gu D9Y2s-> wvvUfuFiIJh`=yH(D=eʸIs#~j9.9cPzwy^d^xK{͜Z2/P6x{΄6`z.̚'JrZM_C^c{yo AL?0tԫ$C[P%UYJZE`,M_Oh$`}z3'J"xqGh㲜ԃ*W]tiبcx1ƮdV!x@H=Z{TyˊȕDKB:nI;0f7u 8wvm^)C\ԃOm?Z"A9ub?\HK^5 K$uc֠& ԑ{a58s:(Ӓe~LvoZ6,Nf}4+ӑ`{&Cpg|+ԔI{e,[~}" )~5Q'،:-FCﷳ I?_#=uB:Ixm^U;c\v.)MkE[S3ϫS7d$qV,GEճI?,=YӾ+$av^_ls(um?>v˙LHk"!xym[S`Ws!3.~ ʻH߀Mhۏ`nl//4{ovJ<9K #š֥%=VWEN&Ol/a E]"\ǝ'@椑yqƠ%#Kړl-?,1S],jHwLjUB#ʅ mh)/BM2J֎4U%KDZWJhYf 8L6K3b1d$Kzc uد$CXO:G'^K<|P,̨sBѵ"fv@ڽ@DqY:D]r6u sG|QA ڑZh]{$ &{V#ղ' 8kU41 䗅cFmgjj_{0L-V1P5,X֒6EVCWqZB~k(*aᐷ6ykUǧѪʠ \zcs9T)k<Цj5kGgsSy../?Ѫ,Ý6FT$Y?2j v 1n|!}|lDs$o9WgmjOȴ^{U%!Vw2xJ@qҡ$ T%cjCa_3.Jh& 8a/kBû 2vYjd_2_B4,^EL 0@:MD6O첡F U0w +ۄDTR[d%c$ mp1Cd;Y4΄SF'a>ԡ]T%wg? Mę\=.<Ҟh2endMiamFgj K(\(ēR-:M[ ZhtH,v΃j0Z>73Iv*w;#X]t .xsvQRk:\΂헖=QW^ko $Wqآe39lzᑞ w7"1KfDZۋ0op,1c Qs\caZEZ+s%H8kҳi\I6.-IdgIP%f2iyE@2 O&p!=Ҁn#7eװr.|^=G BCY֥$-rof=0F"vļ1ǥ[OVw*dL30QjPoH>|ݐLYz#SjS,sMܼ,ϓ=RJX2 j%6vNw+L 0tsY+%Yۆcg8u&, 4[,ByH%+~ؖ Kҁ^fuAƪw&XEv;nƫߠ>[|=[ZT<F/+j="-M=֞dK&@&;dHM._-~HiEO`Eeq(w?خ =}8R.aa ɇUi@K3AKRo;͒7 E XĢ1V8lmsX*h$a`zpX>g(wqHCD2"~o8үأ1qCYG|QZ202 ͈|5wy FM6d?d0E ̠/6{izQm2\ZoFm[@[yݽ%:3@V加-d\\- 0"*(P;T9|K%B"?7D~Jhc+s4R"#E^\HTSSZ'49 $X/tMX{,68ɷ.T%f-'BlHw2&UÉ1ЦMΌ[MBYZbޣ_} mDoxP(C>]$[ry@ZZDvƧSԜ Y$Ąo}j;F^cG%tnQe0EVꁞ=_ū=o M3;OT1;:gytƑz!L{|C[8jD8&W"`qpVQղ+:'BY2f= Ņ Nh鲲)`)~ߢsXrN3oFq޼eGY4eƤgh1U?Exxkw~a83? [$!C$>ZGI{IY¶i@VbT˫B"; lWh&9?!Z:EG=:TuHA$֮x*vG-Q\ڎ&6HArɛ>TTxhʹ$d9{:_zyٹZa1M{`(ZO vl3m@ǿL 0F=_$.Gߦr5>Wq{.eUFBަ.v?!CsmwW(^8LXxa񙁽rl9x=ɖ-rCEFhFj C vӹyU-qe97صs6mބu!Hvx'ߠfi755w*d1 nߔC;Wnw墮g EŠ9H/⍡YջόKWzP `D0JvƼ:NYgi_D,??ūF`pQd0늀+Ib5E=#l("iIiz8N-Ӧkt+ 7iҥ)u-^FG[EՌdv{k AnmQi*AV\:&\J>g:l\j6-bE<.jdw^ިJbL <8NKv GOi?٪Л&~'_Z.#*iG$uO=(WTiϮoLԺ$K*w%TT6 oN)IG.+˙ߋ[@y"4~ bK{<''E7/R/lţ5];/"aWY"q'vhCr__"_}ZwZh=zM@|,iqND+C?}綒&vl[ZB]#%t7HN:{c Š͋9 >E wYe(zb݌SCI*8PKT[;)d%?hزhDu2˯IoNuH;9ަyhN#%a߂ؗI^:$hzy 0&p_H2.M4!|H b?Q>si0i;ǝ~=֐WR}>ވHm3ެ]lfY~h¥p0cSq]8JWܨ{0r_ۗik5 Щv;MZ1$n[+ +LBf2T?b;J_'SX/n7k 0g2Fo\w I@귶'E1781uYNB~Ѷh GvÜispʾ1X5$jNfgñ!!%钕KZ|wfN[r;S=Km}K4d;-ڠKRMGfF+PLJ69h].߉z{0&33F Z,(ߢ7.3=zڇٶ:OͿn+߯)viĈ7{E?YV㮜?2ɦ=as&0vdm"8nBw$[7n,4Ù</^6_Aj`G]6YIɝ0Ʉ;AQwv7+ÖJhY-d:6ЬĎH?Lɔ:Dv%QK2st K-5Vh=YNyR}QۧrRڽJ Ҧ >ݬM3lT xHʊq4#ṃc.CzKhHzGT"#Cfo4yivh'UemPβLlv!}-rgrXsg8WL 00)D)w$GE"wK.dA@?G1 B?DrW!ugKpDAgc;#{DN MC-\h8Y>+r ٗJ̠W:d͛tGpfLf-ǕO%¾M(U/\d]w?Sz %&"95/"G5ݝT JJe-td ˅m*0FҦyą=HD_D JK 2gg \nm_J|ž]/kW_mXiEHDF[אm1O>O@ wٱ *~3y^O1}{{iRNWﳔfL v9maT^e#!A*̃dG@oIBpo zL} >]#Ee(Ew$p*/aH]tz,/v$8{,N:'̩W0Ib _^߶LHnE~,v~'JH㯿>_zn!Ĩc*),2 )?_9MH5ib%HR"++>_= $>InC?Mi|CQKU޴ Ɋ;֨S{-J~ev<1y|>a vTlSE)$<~$v֮]-vu3fgq0-rhٞTA6mP]ݿQ.IVٶw#|7.?~/WێS:C{/cL K~F3w|~3JQ)X☓Y${$rOƨ9{SԩQFqЯ4Y%^~.e`p^IET WV~L[*_Dxy)ʝ{W{9A|4-=Gbj<ƵQL}-I س=1~ {M0蓼(JF;igfwV#:X<oǠϳQQ#)9Ln6o׫@n0NCX4/HuÙY`MFtQSPۍ1޽yo͟$Ĺq%lyfϺ!>'5L? 5.!AѢrڀa-Mm06Fb;?Þx6,,R}˴}>V.fʊ/0Z}mHl-T{8"Ql1~qbW.5ri}fT-Y q)bTC(v;PKoDamW,7gT:tt[=W*2;(s)@B &-4")RDŮE" !$!rE?{wWs\Bnnv晙<3  GsnUVCt~,.o^~k?ld\BLV(B\ц>/LI[>{<a!bl&o|mI1TEkMU5Lk&m|"v?ɃFp.]/|6羊 R옳zs?d.y©F?Úa(IǭƎUoS;[8{j9QaGg28;5|+T_(S]1/ ta"t42 ʕS|HPHq#XJrlo$ oJ܃_?ɖMs<پٶ_Yc̲L1ahV_;FŖ<[c?4p>=Q0J:5{#Nk*ͮG$6O/FL5; ͞$!Par(sn;8IQFw"33ƼaQ9Xi+rIݟNE?vL_+cV^VҩS䥝j9^ם,wl%ryIl/Ɵ5Iu"͎Ijϻn:< Ir]}p$.wy1s]Qf  "IqOOciٱ[ W>IJձG-!6,IzN6址c[ƃr;L{0|Ŕɘ(f P}=z"J`!!P_'^/R`xvޝ"<')gZh^Bb7ɪ),JG`~ʒ)(Ab!Ho]ia:k>{^_Ē2,+'uR;jbkbPڸԺm2˰ ˩ïTNdccG_bdJW?i{lC/=w=bvُA:*kS>A&6}*:9F}]D,_ٿMNr0h4|>u܉uCxuzriQ@V%X5{ڪ֍3JD}?Zp,_k.I˫@CZ߱&.& %=l<ՙbZDqm_We/ޜ*UgNٽůFUX8=@k-G*MCOQOlEuat!iv/>\'M*^-a  0~w-5^z621 Ϟ&_^|3Te P K_ÝB}Z=,I#b‡ƣgSK⪷KèDΞUi _'8|Cz#H&5l)Hڤt:uj)5l? sӿaMQ'|~Ybx4eP18gYj gI n!!H^6,?&&݅KE,u=kbdGYYЖ @Hc+S,~䇲bLKc?ȼ,ƲĈ60 yg S\N:dGAqd ?9똯Bw0 '#!p= b.wR/f -^# 'oauSs9_*_f]OEf4u-KS] y$~ ڀ._ǽBI$@$p Hؙg q?Y(%2Nbxp8)CzƂ ~G ŸfIQ8FXٖ`cBSt?"0\)n oճHeITn#_&Ux5#      ׂr ?.^+?qMxHHHHHHHU0ΚF*vIHHHHHH"P0,f5p5oۏhW;L$@$@$@$@$@$@$cwƴ?I8i/-X $pW*nt媣eƸ(-oDw;e #rW)G !       GΉ _F$@$@$@$@$@$@$@$`"@  RTҎaHHHHHHHH       ()j 7$@$@$@$@$@$@$@$PH PqSH;"       *n8HHHHHHHH⦐v E$@$@$@$@$@$@$@Tp @!%@M!VHHHHHHH        BJB1 Pq1@$@$@$@$@$@$@$@7cX-       cHHHHHHH )*n iǰZ$@$@$@$@$@$@$@$@ R~ꕘIq[躆"  ;;.eYp<ݝ_?Rq(@fff~l   PCAqG؟wD7>CyW Ǐ1Ùk׮9f Kvqƻhs @&       7qc.       wT;b@$@$@$@$@$@$@$@;nE$@$@$@$@$@$@$@N|GHHHHHHHH;TxǍHHHHHHHH PqY xG1 ;*n1        Pq7"       |'@M#f$@$@$@$@$@$@$@$*n\$@$@$@$@$@$@$@$w,HHHHHHH#@ōwܘHHHHHHH7 ws @&       7qc.       wT;b@$@$@$@$@$@$@$@;nE$@$@$@$@$@$@$@N|GHHHHHHHH;TxǍHHHHHHHH PqY xG1 ;|/-o;F!6m)W9iN/a_(UC\$@$@$@$@$@$Pv~OljqQKJA %([6kEV\'H!ڗ̛Vصm+vlݍ/)_9TZߋ c͸`m`oCzOƊQ o6Ί 5 Ӈ4uU7d1̓0O^ع3 ܥ)Gi9  -궉E2 lȣM0l/fW/6җG\F\Źx6"y   7ekRs-FTsop@.d>@ g才x湊RQBqS+O:3__ȔeǙ#7Uw,y'pa_G>1}p |6'(2lɢwc7Zt6T9*6}&~ؑf9U JwMVbw6DGG#"%쑄"2P)|‹Jh4iFX|Oo;`LU5(M Nd aRB~:3F8ԕ   pF@W:YtqcFѮ}6*ԀU^Ša*'6R£ȓōܟo6Moq hAvZ)t9yM"OK?2q{t`xF|>w~~g"'A⮉pBx[6 _2ZBtkĖM/ Fz1s7),@ԩ R%r k^[O1t9򹳱''2? n/ Be\]˯a |yH[oM.lsM!=](Y?-ٸe5 <ZM0J;=7xZuE oaX& )Tj4h EØXdpnTSb[VeE@pD4Z6 ޽m0>A=`IHH|I@ pzeYKXyZ YsJTB{/]OR?> cQ/Z6&4m*YJ_9Jo _)6H4qTV[Nn6eƳhm1IU@gWߘvE\&0<@]T3ߪLkc쬧S 8ҔA TJoC24xp$mžY ?*>'EX d]$"4>~q5A.C*yh1"4v7jIq2_ѕ:#a2oI9uu˭ fΓF*TK~^)Bծ~1)SsG 4( {V*CۊKwtBe-,!`g+bri[s;yk(3a >xc 83 be7&eR M#i(IF?܅'uQcUY bW-ABq#ɸtoN813>"y:|*zu|1ߨ[y[IUuI5|6#wAm7VD7u:޴]XjNqyFj$$k2Ac8qH   @ yF}.2X/eAq1"ehog7 ѥϽQOSZ߸A5hlI QuYV21EFcJartlMj Q\ᄨYspǥDJYXRTn 82*#mfU}m=iت AR=e`;bo q'GG@mFǾx6ձns]'%E׃duK)ʙb>H(G$M!M$~vvjGI{6JUСglm߆lPuWD#SYamr:-L4U4&}FiT*#}Ǐ_   UO^S96xdLfKF*Aződ)O.\rRw=E])C8mءcCma!w3dbEu:ۻP+m$^]2k>w "֗jR@\"⺢yZI}RW1MfpzgӠpYv5QDv`<S_jDR1ELAiM)99 c2eePDu!32('KVt pìݹr?JetصOYJXS6ӹ!D%vc$%U:! Ns6aC֥= ®% n%{SwmF10%<7ä Ft,cZu$zh4^5g{!h;m$#IHHL f,cT#0x? Z<2 Y+Lin)SJЈy1}+mp3O!Zic> mbw{٩(m|:bNJ('QH4eѧ22]ֵ|ٹ}9pX+*os'9ѦiL^WbO*ǥk-־ֹI<[Fic(Q)$%=*kv,W&ǠhƛͮR'YuQHu {LZKՍ $@$@$pcӲJk lErm@rnj)H;p]VJɢL@Vƈpr3K[";n m6{g1z$LGW߱*mcWТy> DW7 l+]e65 9/x{ahwXS]n`_bzƑzڔXgQqUv6׿ZaV;TP*]Q4'Kzr^ %>m&Uk'Ž :ꭍd~[Jqx.tlФKg,e':H~  ;#졶\_rnB(F)*Ur˸MC*aYדjWyGWlH4%mGHv]Ϩ hjOd1 AJZU/PftbkUHy:lUYC ߺKV9.K)~0l A@H1VXvi,UvKyPi&`5'Yd'9-;nk:E=SqH.vI W/bo_GkV:{u_a'|ͭ\V.jN aV0ֺG(n~rVY-ROۙAiQ5:!(Um5C9UyR3WcE8F+;$un-+l:XNSݬM5K~xqZe`d4:K Al?oGKe3$@$@$p8/򁮨셧Mɹw T+K˔JkHXSq>-A܀T~7_85b.9.8mv .7Yv𻚎7:b':/gIA!Cpϭ m {%5H'3.[lo^)ntJ#n GHk[$Qo:~TjgtEtuP"fvqR)Ӎ߃Kd1LJM mz5Ⱪjaޙ{&~kki poMT(,mv;|[Ji;e<^Y)Xios]wMpw̞qf"  (ROҤI:B@HS(%f1nŒ ߝwL4)7o,,"VHs8*vv w ǐ,lPϪF}:Nɮ=Dy;潂9w{kJS^('4^(nKw%^躅 9Hw(FqLrP2YY,]Hs"N:Ρ"v ChXq[ݩ$mז?B *SkOoKcl}nz$b*8VdfNf[T|n[} QQr> F,7J[;V1$@$@$@w1+/]Kk0|kFIT4ii >DE*CI@ ϳLW7ŚLïuw]w~}C6+9AH%%cg'7u ٘v^+g]" ȋMװe?{[@P6.TUвQ5v6/2q8&(aS | YiQs]m`ڭI삵q,{R 9w~*qVLЦ/i+ Nyd!Z je>Lj0UslȆ|`E 8xݤ!l  )쭫'.*.4N(jOjPX5w*(vꑖ8tŜOz]$w+)6 NX5֤xh".帓/K#?viAlcZLN )K@]K`b" ^%fŧ}wwmFho$ M3ԖG=0p1q\eXlIK`5Z HX582^=o#'A4I[:ޤ-{SؒZ~zA$@$@$p}DESVntL~_6Zu"n ԨF}T2?i*5F)mhT-f"3Gxj)bӑa/Hy֗` y:TzZՕB5nR\ͪ[ /6`wg@:'8&L WN(>FKoHÄEhDP?dߺõL\HN=syM76ᕷ#kqWؐ;S Ø֧„2OmIu&&9&=v|e^xgnTms7 et0d_ŅSbfn/~h5u=нn‘`Sd疜tb?! !!e{T[ ]&|>} : ym:l¼)EGy&~ :ףL#"L:􀆒"wG1 fæ6}з9-nr4nߌ Wql͗)0HHHv߿Nݻw pRFuzz9)N >ǃ<8?nO=vetm ֈ\þˎBR :"% Zla{>_dm4B([ߓIرa)+5U|d!>)UϾ;kx _&ܷfendFxΫ*SՆ8*} P9;bVC;K<:tf;o,P3g6'ei}dxȠwT K'CUV]LŬcܮ[+6,)D<0|*^xA4p_߱ǺR )/1xbl l,$Z7=VL<@I$)1y6+lZsZq$6)Mi\qtcLj^/|\N,ӧ`wZI;Zy/^6o3ύWWoL)`f_'·$@$@$@ų')v B.er .ouZ1?<6_Mz:eS31siEsb(]OKƧa7S/fcg^M A$@$@$@$@$@$@"kō循2䝀p]͛_g]4% ZTڮXk*z{7*" E6 Xkק=_C _vBv{m|X}X @;7O7W^ #j{HHHHHHHRq & {-K~5X[       JUvE$@$@$@$@$@$@$P pW= xLJy IHHHHHHH` PqSY xLQ1! ,*n 7K#        Pq1*&$       %@Mfi$@$@$@$@$@$@$@$1*n!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq%S?1iᓰu/0A [,ђmD-ɛ~l!    ( |ZɬT=r™sIHa4ה,HԬ蚵Q=|[O[0 $ݸ"^AMxȐmֿh5*UJCPAsƶAr9cx Ǡs9{Ngً{ϣxqgm6--W:Q1Y"c+1bNgbpJ6=,еk?8KW )ǰfISŪls#'`H[Ieg?MaB$@$pp3'pṸ#x577⊦/@nm۶A`L67zVyٷn|h)Yr{q`2=b~#CƮp%ܒ7ahئ O@=ThS]>s cǕ{P6DǶqQ6ouqQ I҈ V;ut{E`dҎay9@qvb)› eE|VirHJ肗F>Z.&]Yf"R&ōN ٿ8]q#[`hzrrB*nnd⸤qd2X6*4_m5Wu+7jVb2  :$qIrc1w9x~-R 9+2Ys Uz-{Aaca5$5[}3zr5-|-ŲQPW [w Aʮ0_Mu0X5 qһkJ>:vV#3_OTtθ.?Ekxoq#.ޏ~kks>ގJ)Tj7W>$5:slSEqo`ÎJ)IhNxSi&hUJqwy@{/1/?s=(?`P~s4oR:aI3мUsԪTT,{:K㴸EmZ@o)ox % }.^3O'>bqo^fO*m,"#Zz-_7/c6ADKV<uZO:xՒƿV/9s>)mL_=̇12}Qo@<0u6C~$Vicڤ*_/zCYYjuFǣs?-٠(ǙQ9}3x|4-MC.mLJSl5kp DupNU1GGapW M; L;k $*lۄ?7oGYFWm69 yѿ7p嚩TXӰEGĵv K(.:԰xc;:p;>b"Ęvl(2ӂol䇨qOsQ_[4Ցu uGALzG^ ى_*T#]dQ۵._<m:6v )HΙ7>/#],9LvkxIQfDUԉzis)G3mӟq* N, uP<ٿ`p.W:uue%U+ (Vn2=3N77=-KG4Bl8&;kN5f˛M}hT[!}&>#GNrCB`Jh)H\(GKH}qlێW 4=,Mh]ӱŋ6PlݧqT_QcHy87VJ$aX ]bc}yBY\9{zC]A (2eM؈`6yjx z+D^ p2EexZДC^ ;KtxVnT u_p+7Į W|y?;r1QZϚŊt tn15؏73 u:"U.q5_ow\ju١ySYI`/"Tu:7_;^;e*X-E#TM&U&߶#俱HK2NeB[!JYa'`baM'tJ'7i pPi :K SѩCMH1I|f' ~݆N#:)Qy%i▼`fH5g0{UEu,#[(n );1kNx0"!)j$!3J)NӿPkYO&[8>KZ_ad(nZQ!x3/ T7bXRB2n4t ^ NjIo"^>*fzes}u=F(XoDYׯx Fp2&9zW$Yhzǎ᳥lDH_[~GxcLO.){M*!] f{9[V㍗@AƑ`!7N͢c/0c%wvZJvt)?bߔvYs)YQ7S¼pvwq)?[Ƽ&c)NklC$@$@EM+/M F>O_\..w28u<ۘU -Xbh/sw0Ϫg1wxۿC)l,ĸ qbW夿JT6K{Chy<ʙB2~Z*e<̺G7)bӆP)mU7Ll#.:AAk4Nzi$ڛo/w\Juߩ6M[Gɮ}śxy##k Rh|&(p&9[I[ϟY/!tD+=8r3&\rl,y$Rd$H.,V(GKK0⩗J1Y[rTgHHcZ.~7<-VCz.L`=tX>}W+]e0XehnBޓ,}5՜L7̽Be]UuIL َܚJg4s%@a?fMNkfbN`+#1p>e3!6)[_L]@`^hZ2 [0q⧦1q N_KBE` 8R8T歶Pg7ubϠⳣ?<ӛ,xd-ڑa\ P!A 3IS儓R^hho%$dj%33۱bAP\ȼ1Ehϙbhz]<3i[QY)qOBm(i0h=ZS,:whPDղ؉[uk:CE3+-۷DtSϕɛ1903Šq3]X2s ~791`ޛ?mC+i|ҮMS-]^@;ԗbsʽ^h|F`p,$N8HLܸ zfa_뜿-=Jw }I8tS)`܈)s??sj;8&}>gK1*THH 5l,YrҲ8v~ZSe[I!6:wS-?5k|sx-x|7:KLVRc⇒G%ǪV*u?w[g%2xrpEpWrtj|zv̉)YƨƠj*jjbs1&YOzvJ`lIq˨|)bMHIVRfRrv}ΔZL3Qi;:(|C^nq8dCh׺c{tR?Ugn7F(mdh]o˱>NZ6_bWf&r8C<:ƃ;h??kvlM;h%kϮ: n4TG)J˷L<=8Td"`x>fK+[Fw,ej7%ٲhԊXn )C8^,C;1+^"'Om\Pl6S6V {?a'&[ &Tڍl[f]ZHV=q^\n~{`/XLԳSik4Vq>˖'F:'U-ӡ)[9˨\0S -JJor>tOJ̝ VEؿ/tǡV:I#d2=hYy(U斏#yuw$Iqd"z>j]¶yYwqvR46Yk!jU$ڵqYkǐ W8*9!vrvGHoVqFq$@$PD  Si ѢeGɘ>e<YӶ|9ucQsuרjWUBRbnrJB: Bas^i#T_U9AQJb H/%u?JJ9}ݪ̹VW zKM @3/dgp U"' b?\]Ēez0GV掙n&5㴥} `/1ypTSm+ +c|}вE<$0jQ羞_ [Yp\*/;{$TAO]١+4:iv| .,/㻷&`Ax̚:cK9MHS[->rN/?mI-\)hjE8Ռ&"FXX&k:;?(vJ)^a9Vy`zS&HPlZkll"s%dv˗;) {_/u˖P mHHЪG m6r}'.7oHbx P|N@\Ե+bob)S8cL\1'}&bVE&E\v!jlb[?N H tjKIhd|Vi UTԌu ōفMB/5_V`,,c.vr(ؽzyB|5*xi=s-Wym>tu7 .QYE9X<ٚH%U#>GWa|?)1`M8dՔqpQOaE8(Hoš/+cش hUJrV⑉~ :%mU:[Nqt A`c]ip8WPewӟdErPƜm{-Q["Zt{CV6+7m1. =yQ|SO =N§s#Nv-~F狷.?gyxip'%YڪE8*c.l5H UF˸lOƖȨMsʃE!>FB+P(}0ŧU[셧 % Wbdžyz=\>s"UQ~7Q\l" v2jIuKg|'uߍ|g^( (!/=E((CՓ~,_U?+biKq&Ymdָb01(oQ:t#¯Mٚ>Xɀ/o,Noh۠|NYx  y(9K~͂e_>zвe>QpKKy"0Te!_WU4ZaM3('*Üi` g@үi)\nX!\+{v!X w+ui8ݴ}tX}Fr#ri%G~^Kfhӷ|S#c'I(yN^"mJ+}Q׉a  F@T X풋 473<3w!].%,nXJ4{ (rӈÇUn?Hn/RżƇϳ*}S Rq#nn_k~+3ycXmTWlmL:iqOgbg4GDD>1ӳ+DIΘ^x#Ks5} bJ{z}JWʽZ3ܬXd5_IA6zpϛ\&\T,5 rj묔uz""*ZIpşa:Bg;(Os {o/ AE]?lX Wł t!JHBIH9E={w[] jٙgf~3;;L;qW9#v4U7gW*d9/+CYuy4UM:R~sč=LQkl Q_*}_Vc:ɺlui3K6S(R?H ]?WMy<)k~җR65O'.Y2,™#dFzg-'}xu O=Y.^_[o؎T?}J:.R3 "liOJ>~ I]PϰL#+VL{īS#j>W#F 95ŧq@TVWhw0k03rZ!G&cx[ˋߢruOoDM2aO20w//7jW2#[p6FoK̦ꛛz]k:Bnd"e6̙K|]#T`M)|tfrk1  ܞtZ2n^mI$\CSs~{b1_P. 튖)Bf!yF02,o;m)0MNXFcs;qc:Sdlݺszz+Z~|g =״CyR"{ח1aqD>ڣ^*d $lc.l{pR67ڷ#$-68 TSW@~-G/땼?b;qcиeci@QW{4j+8f. }pgu[{7bgh{xc՜$wFGqmۚPdF@dxٚ볠'G;cE)bxjhSx #`\tG݃ {bb[m=˃W',.t{W zuqSҖ|{{qHٺ-'o_M$UY6/罇/?>N S Z#iهoCÛwB\hPKrqV/ ֧i܌R-0Q)wBDUCXLڱNCVv\a̷iG:eж5Y.}k<#vY)F[!;7U^91gLϱwճzQHP+&B|6u}=<7qј\`^(5]7폡T'ëfb_f*lߊ8>,L?_e?v*J9uϟɟ/_fuO/ X/^c1{0Edžngyq y9|tˌ+4etD߫jCmj6Ey=uMظCr u罤)gVgo7F\;Xz C\U1oGE %Kݝ/ά'0mzdHu~Ną:XPkvKw _K;ɯgƛlWϯշ풾}-fɟ=W(Y!HaϿdRHXmq~'JL_Cx ȼe~SXG`#` ѫ:uїG3SWvV^X u8^:mɔ@1TŰQoGn:G&؏v A-ok_>mn%WQJ_ 8+ƵԦ {T ;5Xk|cGa.Dž[01jXa{1D*OyF{oELU-4ǰG_ï˝l>xW]-{ ~n#&tǧ[Ks0z| : *~w yc3@u7W<r 0w7:.ǜ#q]ޠF)>zZF˛ i{5s)RozKC뿾ʵꪸSxHHb$`A#S={{F&y]^ڴƶ[>zi lSIr-MZK' T.O%>#rS>~⪷H7Ę߆J[~)kS_&v#dkHo?%uSqU _eπ;ƇS1[=pS\EP'8sy3ӳ6 "jfj*";*GW AdE+a쏯yC\\ yB8\sK^IgĩGF"y{xg^-EPk.h(MOgzJJ,=̆b("C/j2,dۂɕ)aR&,>v\d, 9SCT|հ#='`ٶ0 /}&8*{HdfT(;B!gʱ )#R>%zeNC?%ς$/kX)oC/  K}mdvE|RAtiHi^}EQL| ヒ"P,jIUڢJR oʲPT%OL eˬ}eES3 /ʯ(SoQ/*B/xM qER:EjSMW=(oh% /=dc$SM_u9ϻdt %K&k#0hcFqTH$@$@$@$@$@$@;e\R $ccSVAٿ†=k"aK"L Bl*nl9_!0E4CO6" @TXCmrdWhv@$@$@$@$@$@$@$@%JL*UD)HHHHHHHD$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"KÇ}== :PXnK]0B$@$@$p`R祕M Cq mj 4@Dy$P233KR dd~ tB;5KV#=zf!\6,YTo.1}!9 @H; [7z?,m ݃Z`U+l(6+7ErMG@Xl6D6뉶1Lc5Xr#f'r]7kMO% mn]U4GWItein;G"2 Evmйs' 7{dKKҵ5?6ٵ=9?Pw_}]+dڵ ij GL}~yц{v&䔃?ZVl%XÐ#8b;乻wF\CIlKۉ9s0mTژC.P؊>RgFLl 2lbʯ2m wލ~0ᙾKux!66 & {@+A /%"yGDgc- i2KI0gܳXoF} $c hxkolmԇٖcJ~ZzMAA;y_}9{O]­b.Eǽ C>]Xp‡/|}5Ka_%tc;^2ǠIDV4aߖ8!^ O/rҦFc5aW_M.IëcThfs+-]כb)nNlMi?]5Y,ؿψGJHڮ5tV_u^s7Cy(mZ 7lpt:$IJ_Bz!q{~"]bR3INݳBcDǴ2U*G5i*>Ӧǝc0A1sH{&j=dO.2bY6$Eh8,+& WJaB˳'[y1瞺&K4{澴Rr40> o;#}y4W GV]'J뗒&|ʷ"qc⫩n-qxT7B8^.e02tN$P*bӝu.sB*n,~1,v*m .-RP c&1oD'~p3No1)]icİFbʝ?pdHC~7E]D!ċ`jAZW>Eznz,YocUOQUͭJZb5aWd_%y`C=eWjՐ8ʌ,O.ԏ!ʅ޹tͦ {T+W+S/ۆgY/fh{uFmle Aޝ7o܆' S04:zΨ;}kvkO^Z[V> SܸvYغll~z;c Xh1vC(2e#XSREuH8WҫPJ76/Ym pm޽{ؤ,Ph_|4FƵA_Ψ&đd`=Slo-)Jhvͧ3UAѵl%"ưeݖ]bڍHvnnTAr{|_)KvqEǦG}3?cm1Gerj@_ԯ̽k@&*VmjyIwXeɉ/HܲKZAm+m7/OFli˺o#xM[60;NڇE+lvl~zlǰbW=W]{|ݺ*ަm^rwY*uN6\0ԫ͚D$\S?VQ69z( z*>\iI$pQ܍5*tRƉ^nCԳJe!o6e:#!vh}8k/nMahLZ&!ƖyEGj:7m;NōMU2*TԪbv\N \qzu},6ZXl/=,&xYцtxS5y ~L2$^w~9݋6Gwa؋йcmk:,! fPYaLdžhwE╯܊iJgViu(״?<8wV;+VL%yrpC]=.oQS^neObwLGMqgM# %@z[84c>1cX5t@ Ӣ;?`57 h3HX(:lړ]?.`. j*i륾I>fަz?ާ >aTmzziTfl;EicFnqЧv9w2N[mW6aAiS9+Z18a 3f!ؤȟ[`Ӥ9 Ss'qQTA *$ÝTNcrQl}vM*k;^3GڃbXX\\8 0ea Nϥ&/e/6rV2#VRO61RŜ뢴Y%)TuMY(nV~4(mjnvڡe^Sngߗ |cT:UiS6z l#˾Se-۲Z@߂ Y(Ӓ~J6soFWH5LUϳXvuؤ0p͝ Տs^F̑S*^}N46S]jO`u}'2wN O>EI}p| J5Nsq] xOqg7Yf;/G_[ϯ%߆ƼQ3[aXU9V.h(YqӧH!`Ŏδ{S(UJ(,ug"MMC[BARl(^96s+%I_pKZxc@O'˾W=\puf^<Y.~X=}NMx2k{g݇)^ LpLn2Z) :gb,: os=SjIg/yv7k+GpɆ=h!4FKaYfJ¨ieUq'. ^_d 2R:rx_=,_[6ALw|7I᱗ $#~7g6Y{Y։3:E>" 2zϖ1[$tbt%ZOtʭ$_ /jf=ѧQKA)W%/}tO- 2B.ޙ,g9:yua5Ay_F }2 )zLӵʺe ڏp0q/5(*/t!뗍?L6nx!9Q(X7:e:2WK1|/y[!u:2+bP6Z 'L-JB};tgaXSlN m6w݈V*i:_݇{ٿ,r 8|[bj;>A6)REɩRYk+G?qW9acd\:؊HKT<ҽZ%N:,,/a*Sssl;qw N9e/Oi?Pn՚GDmFuDHUb>QJk~=6S: Iq/ȋm&Khx0; qr8tF6%}9(:Joc}Bp_Yrφ%cG^+mQ?w>Kfs_w4 7p7x?L;2S+nawލr6؋_'fHΟE1%kZ1UWЇ=&SU#f)## JŅb neZ%%͝2mעAoxCjZL}ԏv WT CMS ,n\HR ,(-{dQ6J:sI`W G&RkK_Kץ,^t=fɾ==Gx JV2 /Shs b4u>є6 -p'TŎ߹b9څ`Aa9~r"y 0(u .ͿoU-_].,_hadz' mMSW(!5tu[iT_73qݮ፮n[)eFGb6EmUJ vyq'KG#Bhl9ө ( z.UcE;#nb賏K6߻C f]|]jqY=ge4)h{NEhS.drtjmTٍ78* e{Y_Z/رաY&ܱաU03" uм[4}~,'8zq/NjnYBsuq^G~Mt3Jg8Q|~O|0*X+ma|HB@Ycoqٗ[ڐlhz $Ǝkja &/D(k~E+|Q4'4\R2=_g/1҆!#]~ݧJDqE~gU{k)/))FM-ۡƌW61Q> ~9cQ%|#Uk/[rth}ؐyR]VѾ:Ҋ';M1+{Ѡ׬Wo\"Dc<[4!Pm&q=-vH&kf4۵kK+J_:5Xn3})2IV˥D/sH;ziYZȖ^줣^QU7i8w1s,ŏQņlEY>*{P.1w1j_YKqL%vn]Us흑&GO2Xxc ôz}@Չ.3x,RfI* 3nz/N޶i%B"`bTNOR]%߸4yg.t,7>0x~ aI$p> 3d0G4׊N-gƑG| ,yGtYѳUX<%B]HٌVkM@X"o7,J/Æ̖~k fNy?ָύ\f3xmltT[vmpY5oF˄ )~(ykA,O;O/1clk"Cg|84ZWoX;+\DCd h6FF' ݉tq|5z%mJ*E؏bփcgaV>E>X)=մVn}WtY);;_V=zk»W!~_K7 #>Y 3T^:]+ĥzէT7moL{bzM t. YYgbYfUـ^D8 :b׉Wމ7Ob@;B'뱍A= KBڨrkȕCqWj @a\ xUi*z`P#If7i 2ⴧ/e.1mt\\Xϧ̴Y\\9/N=16|;Ǥ/t'eOٷٗLUs}EiD`aCfIhaw$6Ђ* #;eFbj,`x .h׍Uqq^"7T} z[ (2␝-8 cx2Xt tl0"Wb]y¢ EJ /7j#u,ΗkM6v^dYxqVwړ>PY<^ʮ`9^J]? BlkN-?IC] jkA ۿٟzS- {p \^1o"ʒ#}Fא:a \`ᔛ t n 13Ʊg|Xɲ ,Wx0~Pâ,D:iSfn OwO1Fs Ou򗎟&LՔ2$U5]7#Aw~*M?3߆dɔD4!^hǚ9uم_9Av- jWf"Tѝ)ػ2ubȧsоQ~f0gP\N>fEL?#@$PLt-aߴ6VC +jXWX۬&xX"uH[j-Dpʭ:\|0Uk+Mw.p] +,〉} xz[bh璭Fjm$][f9 gXj_GߖE"(t 8%[ gEΨaOޢ{A静ʸ~=hV-vbg1eb(n]S#-o~cK?KS {Sqgg|#ہMM!UdwyYiNڲ~/zKɧnst!o\w! *S1#*qcܐfrQ,A'1nR)oE8ubѿ1MΔѤIUuD3sK>H%߹Zpy2FKElzY'%QIZA>}  Rͥ,lY*V+K;i̳L~0\ݫAeRC Z vDo,[1l9`3EQu_DޏϰxD-eXr }uW֚ۤE9Q˂:=۲>=v)S"WSB!hZe?WOFǑGUY P;pYA_nBZ'bDMamc-+֯N,PdW>\/QUeJʂdauO>ƻ=\t YTkF79+l勲]) qlZA2Ǿyoˆ|n?ðD`txkMƧ0|h RO#o?}[Z-h֮Άop{V] k*+jOMi8O^rqo?+^t94 \ZnRˑ3+0V.vRG˥_VmiQ&E]v,;hݘKJ].ێavP+| M(BկOC9 zAhaI{æW}/ES:^?r/Sj咚6m7᠏ hJrN z-?~ˋ.>ht`F8ml8uő5+˳Q)mfWgݨsʋxRV,rWq/KX8T蓻̽aa<{t_;C9xe ^Gg}MԷGF^n|5S9yh.d\~| 7m9| VDSԟ*ӛfDW>Ч=}5?ԉ;^mf1wuW*ȇXIX|URUB/W%S9aN7\r5 IS;+:Y]oW̒e~w껝-{]"űx@$phY})xDL]ѻZF-۱dx2o'sG0p᠁-/1LB_y0U>.&{ssƽE\(4lKHǟ>n+SݎcXZ;e@qeLG84M[T./0ya0Y_G8%k]];ҫ_ikJ̚@A,6 6Cv0voߍG0pwmn6-݂:&mzj- &GQNe!]C1͗eZYB0hHV9$g@Ҫ!C$rĚSHMNv9·|+G!Oop9-,KY(Hx2mីPwԒ<"g>ui]vU(72G |>~ z0!X~r|6sl-p1 WÅ_1~XCf䊂z2vl[g`Ibri+3 }>3ˊS'f#hu{xKH̬FO[ i mzFcwuU7vMT\/35 ;6r9|ܣrB#B2H&8܍^܏εqFlڅX舗LA9ñaeP]mKeZ A1LGCeЭh"35  16I.n6K?[& I%ch?nj-3"رG2QKd;U,WΔ/ki{;5UNqԏT+%AyBoOo`Ok89 wk%˗Oa_ ߺ$p",Ywk]6hTVD¢rpXN Y %Lθ )_^Xqؾn{Xw4rKҒ_`68ҚL6kzuFkI_0fEde$~U{ft h2Gu˯E⋷FU~6B?MUS؟(G޴΅`B7O(;b)ԱCO % DфޏMQ^24!voy'B$}CZ1 33(Ścچf}xdXLSÜ8۰%~Oq\;)Wxեnwyٹɟ.>(wOs4lXD,qϽoe~ Y7e+_.v!rK~["j`Nݿr_:Q;#J=65l ހ7ƽ ox w& g1g Lc>˪⁗/|Đɞ[j7 r~Ӥ4w0#+ebdc$3I0*>M" $@V7M6ziZw٭]\TRg|p Kflv)5KJ5_D+ݥJZ1˟UCLu `C7oнJ T3)!XD̻q[Vzn&]z{YөyX }{}捞3jm2c^uk1) s ZP6?"FuP!_ػB l^T ,1?Q~5nVh4֭zNAxwXgxa45\c0Rm緽JEx£H#h㏺%s{}+Mѭ=7V1(,x˖W[Šs}%fR>s TJR|Mܣ>2c]0vP Y>㦁ݽY4򣁷7'aH+/}|?xs6iœM5\qu:㦞QՔg'`JKCLyry ԩSެ󵳞NGVATŖ` B*ҖBc֜ TpduI"L`YA! ?׊t+d)\Xb`$&}" W.ٓiM2`Z13r Uyy(H :c+x?1of1֬tOCda޵,8aE!:-i`'޳2M^r.(F_lr4# AJ9Vʴm%ud8Ks!Xݩ ĜOՑT%G./Ww>3 c^Cw )U?1>4ˆ`fr-pڑ \k$\I:z0Xdt}hYaʃ;tr{fd>etife~~ `Bi ?gN+lҞ=#1 J*y.]7E = %KqӅ|(Dq,B&a @AZqS1DnHHHHHHHHA@P        (TܔT$       @&)[AM<?:>rnGB˧$@$@$@$@$@$@.\l+(pTN&`C@GG.u 49ISJNTnPTWݳݦՏeq}|< G܂NQ/8y l&19{K$ % +fm:kFnB}li`gWAt/ON'b_;ClQضl))e өؽu#6lށ=G!Ȕ1Qк[wul ,phٽeb+jv=v!u Hgpmވۤ,<;r(g ÀFfLO51{qD{) '(3A+(:MwC=pE,8*M=FruΪ6s/4E4!̠(W>ZY)_JSxW ;7-j 4ClQ܄^4tËHH@ yTo:& QKk;?EpQW kR8"tv\N7M£>Ͽ)YtōE񐛊o>ôxQyN$a<kW=ohW'$̣X3[FɑEb̏1es\7Q)\`k1񅵨^<} y 0w<,zٟ/>+[)ݨ(nlYOxhUK lވkP^z=.sɠˮ/:y`yݿR-ď) T"/A+_ȟA$@e@^*Q(qC1 uP`tX~fwqY0` GepMU\}npV 8>_35o:5i{d>3~!iv! Nvo?<}E۶Y)Z#+Z׃B1~h0`( js_2Rc_Qťtqly_4a.PeҾ+g|rhy=w,84\"BRs"'8߂&i˔_kѴSxB 蹞Z%)O>tvQt7}vJsLq Y!Q:\C<{ MiGr?>|QhCŦ& (Kd,fMv1Õ|mtw%T~ٮ.q @ͱgȶ塜נ},hLpףs"9XnDזA@IDAT10٩g0١x|y` M$KM9_e ?uΪ{xcCxh'1]4+ƵqQ\%ʢs) EaO@o4_n|=+FEAxxl@+R_0wS4W7^K`/0_f(]r}1^Fv_,߯]HC9㠼XR<L}HwtV/9h]i?:Y_[(5/W]ڣMZ^ҊHHB5s"(ݼ^raZ-C袜 PiS,JSsxz(,Lz柚|m^~]_{'?nJ{mpc NCf#K'`AWqIpU؅Ըu(P؎,ƔV51OqƇċxj\|$8^x/Ll0c:Xo[3{6ڕG"*W=zk(רk5 gD4phmm)" S*OJnlݼ;SI% r54k q]竅uLє K6";&NAF{ q]ai/I΍UG:hڤ%ǰzECz ~mqH =E2x,KƊu0hQi1)l_K>e#RuթE*>ɥ% X|$ v%e Fвy4r[/5Y.Gń\ݛV՞+3n܌?ZNڡiR'nd•=ڳ IxUD?h +cϢMnLl\7~~ Њpe}}H|hLV\UOhs2~߆H:FEЩ1?T)િ-,BM+#M/]Ivf :6Fm?.Y {N ᴣaPvnuR3aVRIBTŠ+;MsQ)sE)2f^Sz')2TjCHCF-؀_Q+؄< BitI? i.f,9Z)ׂ.; \:l{R5;ՓVC0@)^*eRf˾FˬCb y^yqV6Dn 7] J&5| @Y&LwЎf z9ޮ.a)E҇/f duJƏg*Hk~&d3k-1"k FZaf=#mC]%zLST={Wzmʜy塑{ب^,Us1WށD]Ӟ u s `^ W-\Feuicq[l͍6)nR6qc0}?_>wơScO&” Ӱ9r$G'8l>­ \tj"e <ﺅfJ~af\ͰdqM_,|$IDt/3^9~FNo6ָSBC))?2Fy Ca߸5l,վ 6}98/׌vmP2@ ٔJ5 UAѫ}mxy2NZ\ AS\65%]}fԘ)kC\i*LNώ "aDz6袴V9J`۶hG8iJS)]+2F3EiӫN5g('veɊsW6CTm "1Pon/Na?(m:Bz+մ[nM2mjF֓(u:HyPʹW*97ET˯25ҟe(mLk*bw- Sf-Pᡗj++cD`fuZ @#W.=c߻jC:7yGQw $ZhI0@! A4A.*XQQ;v諾" { B !@9E޶ۻ\;|N<3:3R9[b"t}qQc,hUxA^R'Rtyvg9 `L!plj]cTxOrȖ>-*c&'QJ,6m-룂/]\wj?S9h@M'`6E(`CC;ɕA@UN %Z"jQ3U8JG8Z%j ڄ+\BzkA qfl'Qh#~R:l6EL-(,77}6vH"0"H=lcO8mD*1!jvCY'Sjz !8 sENd׼_ԊB m/Nciׅ֍ deljb9Jt(KWn ,j"V%*rvۅamUtls#/`j˽ɞc2Gr*GFu׎Z^.ܩE"ߋ=rFxt(Ղ '1 m;Eq{)xF.נ>hM'ץAue~k$gps]u|َO>_`oS^yXpTI֞ܖذL*@fw䰇L?\K1K^d=-HYoaUwofة|{ՊYS 5yP Aˤ8̙NI5w56>:Vv˥X*y25Ő_Z/| &ڙf *4ef؎.SjC{qx^ڡ/9_vu^t.* Fڞ{T-4xyT4#-UEw՝OO|HIOVeS>-Gn.JhRH,Sԛp ">A98i֡\ &1s!vZJ;e]0gniFF\M>8't%B]@6~N jV2eۮ~!iHShv +8v Rpy8BZ.#؃}E@@ٝL)K6A:gTh=='ۙQ OQ _?+QIm6&ُ{RU܅-MQzW,yRЙB7{p&$!#|0&W 'w!ݟǭF`ᄠý/]Yq՛?BSs|} rVmPZ>Bg\ϰxf4#L‚س'5.f21E5\>wN}eGwoJxNQ!^m3|ȶ 3f)e+v1-8YƜ0{A%i;ڴS.⋒diT\:>gcx/T!K֊Y@oc(z̺fJB1}Yg|eqţ }b&,>µDliV j,ia ubuWvZ_KZ>56$œP=墨!k_hwnNy6KݔhCM{*k `ZXqZ5\C紻lC%&-Ҹ=II4|ftxuޜ)78#4\vA7K/>WSo)6l^=)_L6]v02;2&b=f&Ma \n^߆CBBY=1|\??fi gKN`ìjM Կ?IX uTrgyc'"j͠ \ *)G5Qh>}^^rٲx,$qԿ돫DAXr3UaIplexx/~m7>LˆF UP68aOoLÖOdWKvݧ*WFhPl_ ?  %?.>_bת%cTlS;?G1lKhﴫZ2'-MBQOb(Icx`pa҄4 .5a]kC;I[g uۋNa۔y|`mm>/S&x6( CNnN?OBMlƖmyQ`1R 4d8 A]0_۵AMibѯ2 Ur9,[!&7OE:mt!<ZPjF j!STd/w0-`dMM+\B OIjj,^q:M.w`-Q+Q8R,S$~ eL5̰.K#ݩ<'{D =4"g! yS1-u\v˨Dz;FxtQ:P a1 hQޮBZJq5 }F|=7f؞).H N-xL}ߖB㍡)~LtѷJ)c@';^D6haw/͈lg=^[vD](S+cTCBi;E+, n2R֌T.ix >HXxc]럻:hLFPOs(%VmZ3 ͵0YG0xK꿆8_[@$ko꘭V~1brB[~WhR` A]øw/sȃOԵBɼnd9~P}9K5Eh#v@:RqlW>$duڸp֢U`EvPvy;@ghT=u|"k($&$Y:@v<[ZY[%S <-} .::ďUefCD0tXu zXܶ>;+s'z&\^牅Z'4#ҦwO{+Mж:ύjOS[M ֣XcjdTtM'E?v4Vn_!\p_ 'ECy>}cBP}g֢P 0&p//simN6.o8wO `j$n)4]E6G}rB nSɿ$Tw(sJt:+ {ڜ 1Rܬfި3YX-lup&}^S\CDֿa괽.y q- vA4ETѴLE~O]̑pB$*RPZ&Cim f>[_]utZ jWnttc;ɶ[MWGH;3 /H`uJ!=qʠE<)]i1YpqO}g ǧSz6z=Ȁ}3wfWrxaN '@i꘭tޱC2E՗OAHhsk<.iD.ՙ3[6b)*pi\ G{*f~f7VńU9߼LRm^Exzh D4eҳږȉC06tV: MloCCӷSВ?S5J롪]}Zqrgi/588.l؏-WN[pSۢ.ILlQ_9AĂcg,nx4}(z{؞)b/;7Pom.kyTpg t9;mRrQ?hYZ6 r 05\\;A.V|Q(?B.υmkgsY%mI^7&G0)ꍶH-_]ǃ8\bO9Au"pBs6/雖a!m1iJs`nۨ?1Uw`2I391v6>O7zjf`9#[ơEjV9˴7o+&VJULHOƇ_a;wmDNxʠQ#\J#NBAJx8kRH3'[q&/ż;ͤ&͘xQ~_(@Tsp=)Vr<ܪ&ZX#VxdGiv$^#SKUítiv{u:2_+׺=?C6l|O]nmb 1,842=A?uhOȋLIiܛgGItu5 8j5 VJVvld4 (bYk)^b+k-RLnyW||G[4\6zSMZX ڔi]: Ytiپ)YHbmtOjQ1, f:uh73V ?75gjQs W _4aɭ !d rbɘ.OMTtvo'" BqtկXk3-8oFׁCPO^S\iu#؞wwB(ފI,AɄBEn4'T-C04kIƒ-߭;s"h`†mF7:6Ԧ0~ 5H+ڊax[ci[jƖoVoxE3Np||05o#6}tQ*A<:c`,Tڷxޡ?CaU;^ŐwN YkAIڎ[?t&'6[q&{{PtͦcNy('2S=\H:56"(] GOߗեdy=NbWz0/>/奁G?'sϛ֒w.O:&Jè R̵Z^wVt|K 6\WX?^m}?r)~%է?wNG(lm?C^No+/6mܟ{*X'?Gàw5꬧.Q߀ܓZ]t L 0&@hL٪qt|?AqG=z1Ż`nwT8M­'g-A \xF&֢*H֕vвR?$Ђ8AۡC=M0PcKL5AcHs)9.װ:T-T9 Iu : 1 G݁=V Դj?:J)U*kGKYQCnkʫIvr\Nq6i!F11uUΨt Fhת*䖈 )۫{Ւ)L\25x9yΧ\63 N@0ሣNSNϙ`7UhހtoBol{SZHlg/µO.[ .`轝zvʕ71Un–bq-GFD6| TUť~!ΤJq+Zp^ԟ7cik߾ĪĮcaxw_ 璪yY̩*DIKJR#8MԐ +pCT({@rBG -(W)?_Aiˣ6^}[DhN!.ݛ  0orQ.E0p.Xi/NT/ ˙0NgQTlJ7M0 L7lX9Yg|Wvp̶Hdi ? MpWIM:ˇEYbp q&/ !*!R~M'r$({=nt}}TCbmNb9Ozi#e`FN`L 0&`L`eΙ28c%Sݺ}.k4^&0&`L 0&.;\G`&@ˡv†8l93d_bPnk;`L 0&*'`L 0&`L 0`i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A ]"YYY؝ \q*T{4\ &`w\[\,;RMqzL <`L 0;T ?k1&> uRǟFQgΜ1rf7&`L 0&`L 07ʕ+7gq7`L 0&`L 0`MsL 0&`L 0& n|Ʊ`L 0&`L 0&p, 8b΀ 0&`L 0&``o8`L 0&`L 0&N7G0&`L 0&`L7,bL 0&`L 0&@ &9&`L 0&`L F7qXL 0&`L 0&81g`L 0&`L 07n 0&`L 0&`'# `L 0&`L 0&ƍc1&`L 0&`L XppĜ`L 0&`L 0&|#߸q,&`L 0&`L  n3`L 0&`L 0&oXp7`L 0&`L 0`MsL 0&`L 0& n|Ʊ`L 0&`L 0&p, 8b΀ 0&`L 0&``o8`L 0&`L 0&N7G0&`L 0&`L7,bL 0&`L 0&@ &9&`L 0&`L F7qXL 0&`L 0&8pX-A"#kaZ>>Q%.5w o( 43jiXc{_a4J!eC|}F+bw:mߞVV{ecz`|#YXM*i~ \9+QPt9I5RH/`L \G|X"ukfAˊj-оNQ D}X 07@疱2~g'0<4,ec`=q _" &?O-1}ڊaoIJ8vR\q $P,τ f}+ʛ.c)- =P^7U|Z&眬Z͈\ŢJE 0j )^Ơ;:_ Veeԉ 7P3*G"FԈK[s7c|ZADsQ5P%1k*/s+V._m{қi?ePF-oubP3%?}5ҏ#rcsiM=]b~?ߥq[jnQ:I*‘Xv v)՗Qh6 [I+fbgbhZn lY +o@橳R*UѼuW SzWc<\lٰ kqs| fTo ڷF\e?'0`ʢ}-]sT)ع} m߅}Yʷ!aH~dtֺ阳ถ! |:dzE0kAyj`cu lrgo !Lsu2A9pF&aY JGzxB`ڈ^>oC)H9:Zs4Ϙ\![p7 D{ѣ6Ht{SWIW-Zvt,8Ab?@sװנ5o/~j<}?e#3c'֧͗aQrE<0g41 \5>ב30gI]y]W3)i,?LZQ޾' nJ֦^>L5ɉlعW ~͸2NӗӤoQgHpS Wpp~4NBa fN_#~Rkjiӿ/3 o1;'-oa8^8ޛJ2ISP?`wId. \1;[Ve8pB gpn2w9{Kݙ@ va$WCs"K4fFx{,AoCUYj\Ifoq/+N'-vצ+=ܣ;/]xy<|.}oݿ\ѷgz7G/߱kZڻMo_{_E[{~c>qڄ!:6Q8UN߷4M'":%:uRJ?!K`~!`#&kB_c&<6:'Erwo}ϥ ɧ@bNh#rd䮔MAhzWnC91p>K~Bahɝ[Zi-a/cMhC٘H#qshՃЦt5HnCJN>Eۅ+lAeE+H_%ATc +iqKm~E?-3Oжֺ%񆛍fgH3r^*㎱ϡP>.Cf>&>"y fl_gCћ;3F;^Ner ioT۹J1WA{Té4: +Yϟ[/`qhWXCΙrwVyx=oR%/='\dlY'd?->}U|޵ su0 zb:."}2,ٗf9ݤ)Ʊ'g=}i\] UaW@IDATtES&@N~M%vE&En~ :䡧`4sv }ˌc |5sw .2 7/0:ѳ{wʻHm+NEѽ&`@KlF o[3p(җf&)CĮpx.`a9E' ?ʪrr3*q@-8+۹ZW_ v_ %vTA|hr._3h~t_")w:UixDZVwK R6 ZKaݴΒ-/|hc`*1 ykkZ:eUdV4EhE)+N"QhI:*]|uM g)~CWAwG5 {um[J-ؒN\63$ 'NjOzh1'-EG3䁍5hSGOίvgz1ao&P{f}%z|L b: /a=Z8mDHs8 [tF-q݋ aq* ·./e>=Ku/dK⛣kΈqY*'2?iiAOo 5aIz/XoV#f ">^ :QQ >y,A=y-d#[fphR ] ЪWȞH]]]x׬v]4ZD{gQ!Z׭ =akvqjxJt=I5Mώe̠5I!ZW?TuYh)[p\ ʘQR4E: P63?kV\ a݋;QpJ?Vk{W"oi(gm-{5, VuYdԦbt]4#8< 7GÆC PzYһ,k$Sǎ鴴UxR\˗9-ZPGHg^sbDd҇A0<ܧۺ(%g_y _Ðb8u6Imd+j#)CcUݶ"0K|OBPm{DKL\S=67I3{{YnXhϛ>i˰QWȸ⍵ڎIvØчXS0>!aÉK}Q;DT>uOΞܾO$Y\:=-soO?coZ-"dPzYlXۡK=h9R2F@-4hzsV}m[DI(Bk,'ɓ s7,7Axc;vTmNUk*1P`y`r*(JZFL[nWm"!N1$o*{#uħ–vaTv:0WFN;cw1dqW,zZ'= Lvv,CM4G}&HIs E.ɂO@c`I[;ޡuUAM[zWCkt2`, v;G5Hf| ."7G0mP뜾.~Y+^dcd()ykr1rUIƲw5Z i3oNՆƒg:e?픤=K0i\C$tNcW! C6BpCi2qXa,z<@œ2Ѵ%Df6QL6SzfGÛ /0E ~ +'Ƈ00F%jI풂EXb9AJmHp#Ÿ~1$skKds(kTG7ͺx][ɂ2:K6Z&6!O>p`O,Ԏnm#~p 1iv]I,t ]"yp4~#k5Ĵ%{3yR  {֣ ȤgfR~OSRhY:xküEdnI>5PZ>vjr0t]$rj2zR]d ¨ٳ0S8Y_a!L9m.J71&;fJI]ةMܩ+dh;ȨgFܿiSh )`ȃSmnJF`gb'Q' ʜq0 rs^U֞GL~260^<7 <eDF55ASo.arhBˢI1QB-{œР?rQ5w+uzwI{;⠱Il\| axdT3h#ywiZ6]iK< \i+}l"]wF&5sMZK}͍{G\] |} YŴYԏv祤"ktEW'[\ye li0\<^WkLlyaQYN`rDիHl}y}M{[/FSwRe_ nrja W10J/!u^IۀO^ԌeVovnF:r_õgb$نjHK5[7MejLRur45+Cb709dpV/ 'HeByfٷ mM2k.M_-r@9w/ZOԧ-CFaeO6qxNtDI٤1b #z&#JW#F/ ޔ]ދ IkQ%M6ah!EjE:㋿YZ]RHTfwzִr8)$# Nd, %q|oMc$_>ϯ mJע=j=8]K#4Ac;m ъP>Hu@y1Zf{fsژߔ_]C]Ť<;M'1~7DDatEptC}uTțs"d2 BwKSd,}>fVRT3-LQ  ݪ>TI$c]~?Mk?(j5/Տ61B6Et{-r ] mb:c#ē B#Z/ } Ak0){0ip8k3P^lnXbu}mPF/D5Sh,%vNrڷ!.}ʻDm 5's0g6͜ H]|LSU{7|i?5E`H)IbзM Nj4SKYh#J#|9'umS!5˥rq ʗ@~)5W_7%?5Ql[﷈ -v2@\E-v*m>Xa ZwܥhU aܠѫ"C.3J3.sp觨+FoY8Yz% 1E^ gݻaB]t'RRr{3 @ C &'Qg ڊ4U4#N7U k&֪k9yg؄(yw|c6Trww(ۺAHD@4k -_C躺T]OM£}Piy|4U@R)eԶ?Ҁ>}ao|w-Gk@˛[٘qsH|+%ݻ4&f؋Lr {ahB~q3$ ÞDJ&6.(#_z@]bEOCr6zn>Xl zhՏCX?>hlҷK"V=iЪ<ǰʕFu=|pšpyj\Jo꺟F -.tnxbi]ݚbKD6ှ1wAAlβ Nh8=Ka[w2i#KV EܕpBG[!"fRմg6=־9f̠瘤Bhiv4.h邖REdqraAktu} EK9LwuA̸Ѹ6!Y/`+~|6MSD-j[\Kz~|z?$ɗ**>O_ϐ[XIi&NXu5QXAhf٭c ռt`[^')FuITfvXA'~{B~y8-/SXIڸ?ڸ'.ARTn5Z ̯ؼ4Vt>tk靝..Q>]pdILK~N *udSbY sn,-7O~z~{8 }R½h6{i*õ&W!?.rq, TJ}'x&U 3{uM]AǞ05C Ww>Q&~Q6IѺY,,9}Ht@mCBڥ/Pћqd=TI UmKoY( m쩅DQZNN%{=Hȅs]EкLI-.&}oҦFD$>۹$Uazp,EZ@Ag>wavwMk!N64BjnU:\wIҮR&\r>3@/P%:q5 r# mܳx0H#Wm zE<+xoWͧ_3~ڧ ,b{|Ah# :([U-߇rw颏2IHK!`:]1$/./ w+=(0Z9>zz^/`7AhxeԳxvX|0*ݔ}ɮɑKIcu[ Uh 慣7m:7uxad_>r~BжBucQiXbxɧd//ǽ>-vTmK~x1xwH0Kz.DX Vm^|o?x' @(%)vv^ͪ^|9C@ƈI >FdZ8ߩ0~l4phWzp>gz5O[#Ah#Oa_h nD&B[AHNm%%r'TV4)փ[Tvv#3E%NBUk% tu _/ij gMN^f&*Jm)Ǝ 5@b#j~QU:4` ,m tft˧fƊL!ڵ=hg}6 IuٻGnOڭn'?)Q^0.r 9ˣFt 8vVPruDWi(>>y/*21÷D_5gqCn!qѴGC!(\V h+:- ئ1L?D@$ڰ ȡhϓ'Ǜ_Qq%r}R o2ȆDuPet}^FU{D}U[1 hGŶ-j'yߏZ(ri= 1v%~:/:4KUۛ&*@ݎX N۽/Zc8PetM ߫k07sO`ǬO48yZ}Nؗn&^ݦX .v%qW+Yl ΐ\uvT[wn-Tw$^PPe[sVTM:/ͨS7b䨺urm^%,#r Y7U$<Cێޱ YuT>4O@HSرx ,$ U+TӦn:4owrsw;_R<&Ɓ\Umt?GrXqꄣ^ 8Iڞ3s;NZU:3ZcFB5|FJf Ҵ\2Dh~XsUI@{H픛`0 BO}ǴwwǖuZ$٨jgFQh9>Ǵ;V,#kފ\~O`xW[C[˨R Sd#;dyh?kq9 HBmDdH ?WB۶_CC3 !Nòr/jB kwRcGgr!+RO'{Q5H KoXR-o9f3b iC%d㓓_V\zd¶/S7IBM 5 䲛XAfp;}LodA">:yD/s4*0(ܾ^h"F6O]6RZui86˨p -]+-]kHm>-/KJԀ$3Qr7oYxDMR4YO.KVEHU0=Y8WgιC@ ^kH_^|W#/JIB5H@ik[o9nF{$>Y3~u8XM`TV*HyQhƏ@ϻ7~\v, n':)U|#Ж^"5֧0 6-msaf%RK_ZQWq GNGVR(m[ޑmd67K,_ o_!mC<1Q#'ôHKQz]JrT2 L;XX)d_L“ԥ=B շC~]g<U?|ʳLM\O~%FߧO'tDWAvɘamqKC͸p~[Mvs7ektm˦_K 8wIz;kHwOF `7$q@'&s$'Y0/4-4ˬCp:",YBR>E/4~ߤ5E T}V\SpЛB7D-]a/'>1u@ڥw4AO\X gG+Xa|D{(jFYmp^?>V ;1U!4["ҏta}I SFSRuUS!F=ɱ)R*, iaEȏ^BN=YpR>YX(gNt52i;Fܫ)M1YK8SFo4=f6[V.ڌ;(FY[<\hOշ߼K~x=} I7mSaߦ$0$g\IUNBS0ah8v autfMTMNݑ5'Cۄ7}[8 m̫茯 geF;<96dh)L`@=:RaShap(A6a_tE@6Qyi1U+ł/9;IɜBIֳۨc NK ~>8ՍVҧMq*_:՞*\ѷeF2 hZwpdx.%܌pmDU3izh`EǮ*j.’G]'K{Yp`:| }i{|_Xp;A*_Zk٭:5nPE=\'l9Ѯrmb}.liZ$uC ֙c4m׫e@6i@͡bR5,ڧhЖ3N_؂rvq 0j0mb_1dM]cpKR!E4pligBoCzN'оyٚ-=Ѫ2-o^hXMԭ_`'@Gݰ%c LvӶzOAhy: tCOi!}}L!hEfɎ"ʔR4wRF}Toh ?͉bGƎF}&Ҍܕ2$DiW߼K~zϾ瞹KFB)i=hms㤮PTٿO瓖q8o7]7v YhHv54n&ŞUK0s\Zm\pҪ]b H nJd;ƏW ֶ~nTK݌y5 shZжrGS*M!Q"b 6d7^Ca_݋4lad+]">.֞mfLזtv0N`XFNӌ;`vȥ^H}fyͪDZu$z]SD4tOIeAٷ<ś& AFޝUQ}(&B.[j▩eiu߷+-oݺ[vgYf){(*vV33|gΜ3,$H+mexE'ˍE~ە(M?i)jf> WzXhb<wMX)i&LmBr8%*H)m\`kc+ TqYGIkj nB}"YUDzG].*FZ<_0F6;KxJo_3c1V9jnTAm#&Uz#֍q,0sZh7+m-إX n4h{AR?kv{5]O|Aetu_5M'oܟr寲@ΧKez2i7.zi}f "ȸOׂ);Z3O[4P>nNͧ)ݙV&zص"ѮUеRҦ33hs\'̦xX9[iF6Ӫ6@7Ύʏ2S|e:qB cՍbH?=V2#{^;=W٩ˢz)H(;* & a zַ:M&;1w۳&c)1#G_VVtq p4N/P]Jʗ4銵CɥȔھ*sm=8ڙ)AD)N SGv]CNIczp3^6,-v`.Ә^}̧;R㰇Ì9kfBېo1 U})6oܭ:rxϾ>R]ǦGbךUlғ?w2iX} ߚ;o,9¥s&VZ#guMiVLf;UCl e=~#߿z>_x?C-en߆Iwu*04c v+';i_l&ҵP}8aZ繿F~*ū_~_v;bN8lAu1ჷl.-h߯n{ع)Gcy5׷3_k^AuHDNսG*yR?$h󅔴у6-ҮK g∧Po q~H>OUdikThMJڸ;Aѹ{rJ<hu'}gTe^Έom{F XxߜG9߼1tX^Fg;Ɛa1ȴ<4uQz<\?b|;ue᾽q5"ǏOߑ^hК۔{]RJ%WYCzxzrm<[ȣ4Zy7du>aR<(^/bxI?DJFhiWDWiuRN }*&+ժ z>{!ޡp@Vl{ !B.>"TzXhy6Q۱\_=/?/>J'Kbٵ ._]a5\͌_gvG=TǤl_غdB?iW'/4uBdΫO 3*\GV.#)k5-/㑶s̴_kѹu0*J#C qsObݗbѴYs^};1rG4M=ͺ.i6ڇ Ko1G aG/=!s'o5<xsu*Kw9{vP\4,bHUjlgbt~>_5H{Qx-NUg܃w*G_tXd5#Z*tI֞? b`{$)Q&rf>G%;a_^yKo?J#xʃ|n ƅC!^҆9`=T/h@oK-X3vTut<5þ]} FIRoܟ;$ƣ8|u^4.(ΔjZWY"zs˅XPQSw2?]3Ϛ?]d(zQgc?cӤ}W j||uv0bTrš% ?pN~\ᕸ®.o?4++ ?U"o]Cʏ|Oyh.3Ue)ϼ݀V~dǍWCt w)0R>| [X2,=G3?/~n+fY||۶bNJ&~f Vۂ^QQ?۪$:^qn.8پP ᄎingCwi3h RyEZ,Rfٖ&ut23@ `:u ,9 Sz~|(ZWtqͣ)S+dqz.l׉Zb@b5eJ4.ތ~Zג}qj_s2>+jf5efLQvk7^4ֵ<0+,׽ -Ǚ:שLp"|ٰ15~Pԓ+?}D8aZwizW֒(ײ4zD|L<7|s!F~X"a A?oܥ5,[^x5Po|F~\)F\lA!R4G堣g⤊zKɆ*@mYR>x &Oyvᬬ~Hζ?PI{qxA%6@ҤZ4|o׳<5ȩ Kkjr[7߅w$5Wc >_psu=]YzgS37_5$] C7OG]"Z3msuDU^b7q~*J:$y9O?R7瘪οsΔXbL- 7MZm7[ۤlh)[L'FEHء}$\Wl,XM 72'jsr9.'=Ѷ!i5hfŧ-N B4OfFRFN+#U4)c@B ҇ Gtot ow?572Ye7.a:->jsۄrMߘa, Ƹ2Rr|@>Ʀ%إO[{ =B"ռ* y^07:r._0S RIY~GRrs+mR@1ʼ&ׂQHwzg^]'|{pƩ1o4>m5:(e*pnWLA_Oyǣ̦W_C,ﻹ|67\y̸g Kʺ-&<_gԗAXSqJ\F;4?O"1CDkO͆#RAͤH㈈jFUk BR"Hƽa[.UtVW˯-#KkI%q+˫+NݛGI\ux\,B,=Tzc?4 E'_ݙ34ܐJGPyz[g?V*(`yH{~B!C)=ڛOjUB[U=^Z,cMl)x) yl8>4 *a0y߻>~y"Pl#"\nS5ˇڕҵEX{T2ڎ" zTcJհBi\]z9~s[#}ѡ|1_*)ZB> V{Yy{&Ɏ-'?z< :?kحjQOzziڂb2ڹ)WFwnS)[O^y05JCsSޯAAޫT+uPlm[xkW3ΤN+ :tξzP{8Zp;27<[:GIK(@ Ptn3Y*P$VY͎R&vC߿<&M(/F "#IDATWQsT lqq5c9us.7L P(@V faDaSs r \ TRfk!눬S i&Eb s<"=ǹL P(p xqbnNËY(@ P(@ Pq x;pƉyf(@ P(@ PE@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P|[htPx6S Q8~8N)u>z4*xvo_?VpSرc<Ӧ(@ PwgqvFyV&_?!!!^=~J"(@ P(@ P۸ #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P~L(@ P$P3Œ g籔_'67i(p 0ps_(@ P%ElDz=GQzb"NHQaI:XJu٥Wã(ǪȒ͂J7~MNQ`>]+V!3_GQZh"7k T2sމz\_ Z 1Юԑ½-Gi U$.bּCXr32~ۋGNLwF6w\^9r1#pTXqN+ٹn5EȖ8u3y;sUKv\ߠQih}i&~ڴռK-1X$$ n^kvcѐ/ztŃ{"*>cS9i{80ꂎ@>"ه|JEnSż9mk231?t @Il3v¢9 BlG"˹j><2 vv=u ܜ:{(@ Ph %ڢȖ؄c؉ϳvM|G|v[4s^C`aR1祅eسu1X5 Jvǐ(G"|FNM/%s *BAb tP'5ߋf+8k͡$#/MM&ĔWkTʌ< sMLvA%^xy~\8[ ^yZ8{`B@BƯ;(@ P(@n#Fb#qQMxr,R5iƲ1&JBkj('aa*v31& q'<\mq(a=IFJT i4#\"][6%_[b.u[|U(zW1c֜zӴy+1h\K5"]7AX7߶:,)3y׸9%tge4 틤0"sS?5=F\!]SM;M ~+nmm }z3.Xq 9HP_c(oӐ (@ P(@"W|,2\]Pv]VYX =a#tRH Gm{jʖw%ڦ;X;ER FtKFɋyGP8P(@ P8[clrUKߛOK~fe?lzc.۳ķk'`+|6qPK&}5Kt'mW-[TH`.\3Mݐ)ݜ!$FM쀡bxK='1}xSZbOz[%ȗVԑ~pGnRLN/Bj]jʹ=^9Somamg~~?_n='_1$:QBq)hw /(@ PJx0?tC8\%og66IWnu leqp!]n c+vH07{ZVj'-=Tђ^!s1cV9-ɖNHVkޘ Պ4^})_㌖m^ҝ=cbAT^>Ahp5FjZD?ZbAnVjwu^+'a)n8q4(@ P(p c?PpSɐcXEV;& 1j{1^FSH%PYRuߌFpcC&y8_XjsmsO0"WYRZm){O$Y-oE "Y39lvۼ@Ġm5.xh0m11cf\pyUOHݏ+Yݵc; ʢ2s`Wѷ F]Ǯha-_ 3(@ P7`ej[01ܶHhһO<ܚI]g4zK6,TC'+{!M0*bmJ< 稍ZJ DGYIid0Mض.cլP+f}1K+c9rdIo\{svTsի+3-&tK@-@-@L}zy.'$ sk?eNm$ֆmԙs0UƔƍhA1eF&4[}& C&c[Y`GE1պ?P&Һ%UmC\d,դMB:G*v@(R ڌ+sW╹)(=_]R.W֘޶V)O\o&'ʟIB2؝G !xykUfrp(q@*;zF^?P!f╥[1+ѭg{ifv+y1ZҮ%)]vÀ|um+Mߪ^_XSޏg ڶg{=n0~Յa@,|5>'A-7t=['\k/xH<wwSb^ښ'7c٥qZ6345"7X rBYQMJ?z X Lٺ)$M;)yZö_>0(F-S) P(@ Pg@y)-Gٱch# EDD(d9J PcJ>B[ sFs:mrIX,*S mf#EEj^B) @0ky'Prl8f9,¨WJI7ǧA0+!BSj>;FGM w ͠fa(lJQA`HZWeZO9=sI P(@ P@d|[.M==^PGHN͹.Ne|Ev8(@ P(x7{?=VE}XmMHk1hxNinNǬS(@ P@ D7şG]oJ.)Nq5)#9XU4?>(@ P(@ ԣ\ { ... }` Hash. By default, no `"extensions"` key is present in the result, but if you call the method below, it will be present with the given values. To add to `"extensions"`, call `context.response_extensions[key] = value` during execution. For example: ```ruby field :to_dos, [ToDo] def to_dos warnings = context.response_extensions["warnings"] ||= [] warnings << "To-Dos will be disabled on Jan. 31, 2022." context[:current_user].deprecated_to_dos end ``` That would add to the final query response: ```ruby { "data" => { ... }, "extensions" => { "warnings" => ["To-Dos will be disabled on Jan. 31, 2022"], }, } ``` Values written to `context.response_extensions` are added to the GraphQL response verbatim. graphql-ruby-2.2.17/guides/queries/scout_example.png000066400000000000000000002264601476434635200225630ustar00rootroot00000000000000PNG  IHDR.E|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 558 944 1 v@IDATx\TU?1*!d,%D6>Bf1lպZnc=['7?djbankCm~"a af?ν~9|{ν⇟@$@$@$@$@$@$@>NrG$@$@$@$@$@$@*      VA lM$ -. /=c      h4 UU?⧟<ۂK IHHHHH\PFRozrSLO$@$@$@$@$@$@Sv BxHHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_ mU(\~-ߚ*^sUׅ5 h/{vyhh|h<؎B[oh>uk2Qn@DǍEܭ!gq99?8i2&)[18! 8NrnEqtg:kSg" 75,[Kwb6e`{i8zd+P;s s1׉D呯GK[PcNAD?]J)+q06qXwqQ*'mou/4I1̤d c 5Sy|/F4"7$ѳg˝<׬CfE6WC:(c_%I[z<w5{Oa$9yci{6ҏ['`Wd|gSQFblT< G+^jhK*w}FO2;b`hU?aQI}IHەI! F`YDGkP[js}#S'dp~{DDK?GwWF\Od\KAb<aRP~io Y((%8x]/eG&fCJQ0^ޭtnbA:In٩Ll Mލ꿠1]0܈S !x4Y aU1f!}0{؆+mg{8n 刷LBMʱ>'Q>.26 $u1#`Dg\@>I1n$7c4T d\TDLsGלh̨,[u;VDwQbd!_#g_2i9$!Jf{O L[,ʒ1Z, )$9 ྱCuMhn] q̣t&^s7̌9%Z1vXX-[]ۋUq9Hۚ J5Gxx%2! T[/iU/*ENWHlK yZ{x<:d)` 7A,zbWr}otMy}le%C@q%6\)9xr_{]ѳ z%U[.սC4a-1!,|+ZXzQﯚo\ uHzd'>oJ} ,n< GuZK?Z쇤AgY‘3[DJ[z,f[Xc:[3T F?+^e Z=!:ׁ(Ҟ7IicWM{ҐYG^;GDLd&`گ Xg?g:#jk-2PL 0 } 'E>ʚaìKJI٫o8G_E"⚗6băpv'8\Y]ʔV?׬Ǯ Z\`lӦPS$D#ڌ,}VAsW 0تMS^(TS^{Ir~"#yR2YU:Wɀ\BJBDY=B&ʑ!MF *ZKc~I%.ϑ-\2C[;}uܙP~(>Wg]pFG@)-) Z~g4;dyYUv0Vs>27YUߗKN|@w)Ʉ!B$ ^ 5OwjYA Pq!PW`ɉMD.fsioDf:A\Dʃ#E)1уG+ENDy52#ʔCu_;Q-N@Nĝ%֞OeCd:(N5ċ,~H7rdOatjP2n~$/,cH{,c#I?xRd@w92GM>[ݜLƈSa *>7SFH*8\E `ҟ6@GoMfƀE~D!e@!tO6iט*zJee3XRfM_'QwEz˴o,}d URe-I2ˬdȣ'Kz-Y{З~_GY<# ZYzצ1CVN#JLR#&NCу_])f_ĈB8Pr!q\9g2vʂ^"ou8(7nd߾ g9\KPD?:xKO/?9] EP4y7I_c'[ē̿\,*' Iӹf}+74CP={1Eo.eqQND[9nYNE"ex$"|(Q0}tgd2ZǪFc]'φpk CJD&GwA?CާGaaғTw[㵱@@ Js܍e,`om&ri~%KF8-EU6V %2Kx﨡(*[)C"9EU#(ŀpM /&{t bb V,|ȯ ;G!ibx+by诒mTOTTz 든[7Vj)ʓ@+=ŵQ?:*kѡC _KM[oZVb:erS%A;RYkdcRDkKiGoCMK_E>H]>d`d󺥫MkG֨yIfelP8N\扼^ {oUW U@c7bA"Bs "MtWtG7S:6;\=mu tI8wݒ'+z\ ;kNe r92Yg_Rȳ%%7\YU\uKtV߃Nd>jQSWhE#S>q΍*ٷ+>1*p =)G`%ƈrlбԳ<_ f!Dd"Otٴea,iG'dSEEmAvF HK\v< ce_(* "A,~L֠ k ˓z60<KMBԺ'*ULևȡ5t4T%u?YOi%8!Q1&WhʫYywOSW\T#_Ô_w%0]#{I#RZa Ϋk7>2q){UrHG5DSf#_Eo}`dm%$Ξ姹#DQc2~pu_D]un_6'U2>")5Z"|W&#PzDͺJ5=dCA܌(O[^ ;!mdT$;Hr*cWe4fψUR+aɱD^݀)plrS۴埛dlcᰎʞII핳چU2Ӄ6ubyrX÷Fe\G>~cT 9G@ш%mbz Ol{@5\W'G`}dV~}jQ+k ?!;Iw(aXU [|jl]k]˭<`F\c'5iU.FZpYbc wVݱA17[-?.!v<ۺXkG'-_ۆx=FL(,$D=\ӭZ/M:ZT]zwؒb(Wԥ{`_4qd9K8!nk!m8tD220s<̛;sw޼ٚL695:#?jW(9xW1@"[a`_0ELĈ6]KbѪ*[Rd oل)Z̴+M.mϕBsEd5/)\#am~lR.[2:*R-Q* vLd R|{{7YxR2`/AiqR׉le!K&_4y)9-{L(7D;Z[T_UGB,YE(M-YdZ;/+7]ױ[\WK$yyWG} JzGr<݋?9^K've";)^<L"[K-?&mV*oO9[~Dپɨ!J1|K_.::ʓ @~%db͖,U(GkW!JU|^vZvf jM]W/nb8/k,Jx]BzdW1erMǪ#yy= Eo4T`te$ݯdo!MÑ8Q.+XsCdY%6Xr2X#XdkuG||-zc9c3E` KFK 24[WcePɜ&d]c6+L-ѵ5t\ɀQk^}eg)0jq_2~ߕ1(U9ȯXv˧%t4"2b=" OpI]21P?*묎΅bVA.ώ9hdRv*X_xSvMO~)"  쬝Af&I+>ydǤ֩<"N@IʛٜNـ~LЬP$?#1GEC6~KuU& C:2D[BX(7e LvOT}QS#q:$[6DLMEȉs#SaK\;G1ۑJ_ųeYEO<@~xUZ5!TY vo?W>QLG}1=zr.o25*]V׍e91:B<`Eȸlx,^-%NZc1C) &E7[a"XyNFzGgYbޗwq :8VhMAಊ~6ՠC4aAe3*$"c-haX[2,V.n̢T+Zd2qGwݵz.5:ǖJ(3+@ouInYe{)uP\ rEN R!\U:sf# udlrhk$k:,XοM뺏FmbmCz?&`r~YEjkz_s$SLi[g+!GJA t"FV{6hW$<-IxYin!Zn..0g'YUL\V JmeMF6HHHH\Tmܯ:4=1xD\41VH2 oL$@$@$@$@#;Xﵙ% O-6[ @Y$ ԓzb4      %@et      z[OPF$@$@$@$@$@$в,N$@$@$@$@$@$POT` HHHHHHZ@P FMOY h[ NZmcg@N3?]c'wxHHHHHH5h> bEGWk0+Й \[ji Ys1 @k%L XSþR h`T_kX#2T^ -YVxklfUɍ`5[1 4LC ba16 8#@9sFZe%βE-:,P@3nO 7bB\`k&s+{5'3̕} Ku|^}m.ƺe~=zRxx l\,[2Lm+& :0cIV1Vl@B;}bc員5XVUoII3,^ U {WcNMƀeA֝H=( ;K^0?|Cz +6Ƃ!N2Q@\Q}s&7ah|?j!v.ށGjLmd[V$/ >QNM2m&U`k\u " dه\>+4` .R} RwP̊_%WOƘԘg^2w٫04Af)#kv܁n o0n0( [\T'g%mK7Z~$bu6`  '"p-|beI/o5k: Jta}?V wX2rT] {a鰥/kd]檩IX_銙_6]~\ڧo)4˲CݶXGr^X,MX*+;M gQ_˲bqkCjn18ٱIXYbNrj{@ǣVF@z ~UjkUoTq6Zf* LJAMb!Eh2g@@'P,{98[`,]A8qg%i';Yk4eз/o: Y![ F.@n^EvP}p%LaB9(YkS4V[V<~PeR屮/Fmguœ۝A2|: a椾>GT)VkB\\,ep9tې+Ň"WNXY&nG?_$Vdm)SX/طdmX17@q=][Bן3Ϣ^*Sk]e+ 8BmjGe.~! t* XY9Ȫ u &k`k"}Xl2, ԸiE·9>v.(^l>eҋqQMb:a e,}K޵gbʸMjkɔlBVa"[6Xj[m؊h 3( AB7qizR`zXܒ,k i%# lLDqa4÷zCWjo8:sjv,8^q J6Eb{k6-uXEGҠDѿ ;34A\AO Wظo+%:KU  4xa,ƭA-C挝X'pIja  ڙ 4I|%^LY>q ̸KSg##vvP&32!GdT‚w!4]n]]Pl4u~tiPΜs&&@lb-\7`JYط, }b](*oR_AkgF?l2W`-I~D'.$ˎ<,Aʊ+|zF];ye:NyUq 4ajc Vdx1۲r#kTZ]L/`/R^UMN}K010^S+רA`6k5nÉN6wLɮ"vm$M]a(vORǙv X0uHNfr}IEmw!vp6NtW]ekۀE4%2Yv=".ʫ28dL;ANm/4Xw.Cθz{/D=? o$>#&ᙵwȄyPq*U FԚLniZ4퍾khW2y!qO޳_bS.)}&d鈹-C A̠Op9~rve-Lz8k:]T6lrC[sA1,FQ&J1>̖w CS+cۊTEXdAޑۚ|zeyO'k𥲩IOx eòV- xt*)[t$ܚV+HV S.4)VsqZw> ޢv$g2bt5 sGY=cLP.}$]46rLD(Mg8baKm'b"V~7CO1V9YG~k+dR!N5l6iN%̓k`0xP/eS4fnEUKPҗv`zCϳ,.rXv^Ggba@Anѿ>mú9 5\tiu(_tWkf )&QB#GYv@A^! _8( MâxzMnƱZPdv$'W;mDU-0La@(Ss ywuI%3r;,TA^sG~Ҝ#Jkռשx&IX$VvoV1w$ o=0t_yݯ(r:c- 3'̞5d@d !8 !K)У֠ /kiWZS6We0~ ŝw2KyLf^fmnj g+T^SaILWV ICm>hJٹSP᠈XQg_ MJbQB>Yd y]beh >|A,+_lyI'#:?+~ /lU6l0 A,JBXGMo-E: j'Z_* +X{fCˆ;~);gcoQwo/h(Nu:+9Y;l>eoWq _~WU *¸Ec1 UZGYu2:` e݇ѵgsA=1#% #]r.l> ƣxYvzL($a <6rtמ,yK^sߋl;6S;bWaŵ=tlz>V:+"niu~ckrncL$ ]8w~Z6[2; # Ѣ}yѺ&mczڜP69M[=ٕv6D% cH,9NxGx> tּLϤOhVS&[[-~6`h>kMS>Č~/ꂷkuڤ_[^`Ċ_E(.yиꇹU]Ye 9~͸7PzlQxeedϛ1&U1+NКOPͷ]/'.\3?[A9C(FKj8=Zkmr>BePרL BHmn6MܿA֘h^Fƛ7 Ypd.MG2t,PN-:g%YܡR0_DaK˯3*;W=lcM닟;^7lں;fṚ 0Ї)xfZi uV$    h_>/ f`ϖ`U>zBEinj䘗q^H<tݶȒҡ#o6FAݶz)vUlf < @"4_l wm"bŢUi8YצmjD21cCC~^\Z6#2 vj~ QMByHHHHH,tpL6߲JŖA6u@aMzRAҪMd E2ZW!0_aj     n /fHHHHHH l7I3 @{'@KO$@$@$@$@$@VrXM     h5:p;i^y^=]TEO̢Za0ЩS'5c+o2ld)okz3GY[7d2Gl6lknYhN8T`n;{M) *~6S=oyy9ѱwQ&rU'"Fh$m#1Y P 7dfno ) MQ?wyPuG$9>RMRھJ )r'~=(kmR&[k6,4_-kr{R1uرIʢ\5 FfRz@b&!@Yk̤ 4L6a4,xʗUT ?F /ƮsgIea2vmw4evnRz rYA+o2pVomޔۏ6 ֮Y P֚/3ood菀lFkyh#0$- U.y lHHHHHH)xr lSA$@$@$@$@$@$6;b@$@$@$@$@$@$6EA$@$@$@$@$@$6;b@$@$@$@$@$@$6EA$@$@$@$@$@$6;b@$@$@$@$@$@$6EA$@$@$@$@$@$6;b@$8%HXr N|׸lSOÚlԉ-kR<&   h*@{$`J߂Ud]1IHHHZQM֒H=0DFN9{%J݂rTpx`@TދE?b?&Q9nyG< WF#n/$@$@$@$@>BX @>Rw@gOFSI>ΊY5?y92j>%ʫ?güyS;kW Z`}F$@(bU-< StGUqB4+m+s~ DM1T{kqK"pbO:V}aڥZ {6NAT/#HD+kE     !@ VH@|g¡"1)VbզVWm( oj`>5}CI??]a)!uJdqnu'IHHHJqY 7k%lsLzѶQpDt~F R>2wpHꨟGoj31պUEGqҀĸ-+7ye"XxH$)G@IDATHHZI YU;*A]#&&b=gو~FGE50e4jkkrfgbjc8n ֏HHHH\VQOZ D~ o0W q(jj.G'K`*]~UWJF"   h̄ l=A1 gJ$@$@$@$@$@$#ȍ`5HHHHHH *>r#X       ë$@$@$@$@$@$@>B VHHHHHH=** #7      pO {>J$@$@$@$@$@$#ȍ`5HHHHHH *>r#X       ë$@$@$@$@$@$@>B VHHHHHH=** #7      pO {>J$@$@$@$@$@$#ȍ`5HHHHHH *>r#X       ë$@$@$@$@$@$@>B VHHHHHH=** #7      pO {>J$@$@$@$@$@$#ȍ`5HHHHHHwWIHHHq|Ͽ"ǟ.5_a̙H(/CH`݋F[ \VQOmul x|~ V"`kw[o++ t p[1o\.ePx)=|;e 4Y:'=꾹8ͧ$@$@ f]:B+R5Oa;LEnE4LC ~cbF`yNzrMgTyoHLŻlRG'\TۓGSf׽| ϹkTSb%ޝp;6YuO4N$*>i(s~$yMdÈg8j\r;&> yNT;Wf>?bW#SUCY^a]H"Ϫӟ|"nD_8g`Ȭ>TQQhyO_9OؘJ;:wb0jZLO#pزx*!BQ: ,r˫ $b-cj-|^b&^L5yD$\ʾɋqN h5Z`Ny$Om%#5I( kxe+n EYy$?qݍҏXbJd-= /Ȗx|\D 7ag՚W `EH8߶5\#uyo_s!Q^Gr5AA]q>?(صa3Olf+ûW\?! V{h?՛ UawVXҎ+[RMyt<#R.a2i96/Ǯ%e(\8D ۷ -#b坰yQٹÚau 7-i'vce!7rȫU OR|$̶UdгV6#e@)xo<6Zjk+?bU2}b<*|ِ!a&nЭ.7W-Ɔ{ LZK[L wd'+Vڥjq,՘DގV88Chy1/C+i-+*/>,]V+}E*zaaO[ Ù?B;0n=d#Ë]f9~(p,О<''W{|簞2Z]c!~duģ6lb  _H@3֝1]A<Õe!,)FQ .[AYm~6ڃͯ>w[Բ\m ?oa9Flޖfy .׶F aB?-e8T(3ИhT+1Jfwe+F^\qVƝތqȒxe8ybzBaxe`ͻ؞2>Z}X}ﱕkZ}OT6tzҢi >aC&_Ԅ\*q%߼+_+ba~堲؄1cWB!/J thy].Hh^5kVW¾3@,_gmןGTx15]&bWqZ/IZO?NڊX %?2(ύe?EI&'5 bWf&,6J0W;*ρm'm&%֞C ў5U>z>y*yv.Dw@-Y^CIam>vf  6H"щ_ l:}FIC be'b(p"Qx<[^ ;LS_*n_LHQ>K=|5Q2}s{S-u EhçpKe3,z-Q_ű7(F3L\E+oEMڶ1=eV^fݟK]s,K,C_6VMZbƫ;sS Jܵ+"b'aFlL6p\OÍ*&8+uU7<K>B'@ؕ(Dۇ8ٲrݕUe<+~* iiֆ7ᡩP2^=-<CCD$eܳJT$@-Jzsz_!wN 'IĴcs60^\Կm7 VM66'&޵Y'_“/gHrsW5S.CG"C||HȨAyNL(÷FVDђsB /УFt4zU ~k`-5+l!W-6Iރ/l 2E[RJhג4^><+(9^ʲA{P:U-k7* ׃Ud[Vq4FZt:C>iyexuf>ݞ\DkMģpaxHIk8\z øiRkJY_ -;{/Y:w9[P풥Bޙ<}S'M(,sg1u[gJmUxre MdY82vniDEb(Ou 螒͒^٧ڪ}|ҶVO5m YDqX)5Q>wX~eGe?ARdfK[ߤ6X!lncA9hͣ^6!Ю=$~.~BAZuW⹝bqWө8l|EٖEP+Y{*;ew_lKވ=48lx,c&/{ ӴWreR"tO+j%mY0{Sf~TO]mEҥD 34.{ ]&^4!PfE$@uPn[ UT7T)3f))H\g(Y,ye0+lѧ6o&Eh3IQbu\ـȶ$Db96I|7q.5 V%)abCPʋ2LAM4~6Z+Wg-}l$eV]q~u[*:T܄!8Y&\)'k|BEfoÓ$'=gy'{b 1}<ŷ7A2YلsfwJ?!@)KĢpEt~q ph-nw/];kxxys-CἝ^/CH`݋jO  F⇟IHHHH#}oxGc{Suߠ1}O; @= JʋPpQ^[ϧ꾹-] O$@@R` aZ2l3?kr_mB=NxF(2co)\Gю@&&L@t1KU ^Lu/SlFj! 2zf֪lF븉 X8s=yjԝ ێ ?kѰ< l=ʇYZun\ ˇ }FAIܨ[!mRۅRi_gPu|Eھ_з%@\o yqT̘ L0cF!5w )CCIerTnXc#8R?59/vh526BbQK]7bw~$I]zt຀ʛH7bUV[Q~^ wϠ2T&9=ֿ[!4NKĻ05 uY O. <'qt *-A݀o3oHx=X܄W_GG@lDŖF4Vz{AreK&fZӞ7Q`ݛy(_=a61Qk_ģVCևHMcCu" ɛc)0a4XNcˑB׍|TmҴ}O+ԴJYTR _QWP,;Zm&.J]v#uL6̈"ueݜMEM=_pe jD #߉ vc4|FtۦLєxf(?TWv.J~Frm HY]׉uҸ S6kI"x8S,-?ISs6"$i`w'\4 * NpbN*VT_{)=} Ma_4uzFXau҂ryE>}v&&Y5y~uM~M@^#O˪ FDˀ_Yv"^oi3Xoc!& e ScXgIHFOe HyEiR>Y &#q3h϶g[<;W[5 5^zQl8w?HH> }.8dշyM]򐶈i\F(m3U\u꺂mp)|wnBaz$z#(ωAw"@b{V"p+&s&ǫG4s {zD4iG!' { wR|4iqLMRij;v#aa]M)r#5 o }MG3j?RpY_7N-D`o]#*Vx#c[Uf̒Sن=h0X ]](,&H5"աfsRnBVua0~="P^aSv0ҁc!"CCI4X֡]@eG!OZ%/Z]E/u(Y't>RyqcZ1+F&JR`:^ >, G3Lvd={Hm4w̗;;_E+ >o' ǏcoQX !x,xđ z//੻,QABKNo^+N5py3#l@4lמ~ʍVK{,eswҶ&u0gHH> hПve++^\4(@zIzrWVN;. "֌]ȎZ_]qZ6Y;a3/Aǟx &"}$+ުȨey@.|:,@%޽hQ9;qQ=X+`ϫF'`:$Eq7P( D(킕]߄ AlK5ZazuycU*{=D܃=t!k*)y*WLnDr@ng; 2AzK-:gByo߹q(9Oˊ=ʕkFi v>}J-ZẪ 5Ea_B|i%c;aE"W𯿹d[~#UvrE+Ur w ["TϊArq$k9?{f$m eɏF3W7RW gybG+h︂ Vmr<+0c?ʸR洧o bm_gLc°AO>wfbm&2?+YUn4WyWvR!  1HAѩ+"_ڄ"DVR( P?ruԼU|,KP~Q}^&^D{ ڼ DIA\ 8TI5V2=Se>c>- chQ ajx'<.[Re 2vhJT=, b@ ~dQCi?y_:D"2o+pI5nb6۹pN 5a0NK}Zbz}˗hdB)`ݷ5-{yubZ';BGf7`fz&y^g@xJUEÝ&L,Z(Vlo$G4BmNV6@Q,hcnt^^j:p. ٻ`mz-h{5X U-ޞrizVk9XMKQ'*NmqLuW> ? cBkN| Ɂ70`?}ބJ3Y|%ٱO;r-'E[1 }K>ͳ=_1ĮF{#ӑKwGY~/KўoO^9)^a_JϽf@!ȓD\Tԍ"_ʗPK!9ۤa@ſ:;.d_qֺDǟFS}yu:gg5}X%ޞǡW^!`fFXUkuvv&Ϛ`J,0kӐ$n`YRO(/c/ĒCWpXכ[a}X0Ί%R|~"'_~JDA$@$0dcC;93`ʑa\LFouZ {0#Դu jĔY)Cԭ^#u"Bڏi4Rkؑl9t!Wj&%2GçvĜLAlT$ oz)C MiohiqҦ&ln$r Ү-h/}I G#yCzz[Nj٬q&2D-rH;ȠV9=TR9I)M[1CVyڎvZ9Yr(K&]3Az䠤["=Q]U]b"sВv:|FLã0i  p3,9a3n1S,9y Z[])Y?ߠ//H&pU$KW^",M@say2u?@VY  }뺌whc=ҍ`r7}yh3R*$Z1y+ĢwFTco}{S?S '0&4"߈D;[qhU6iaMbt2*Cr[m;kjW%Da [- O,~%N r8F9; [V][q'M"ʐn@ڏ={X*g)Ws@4.,c\+w jsT͂KPQDX;]/tatDP\T:ChVMA+(xg2%nN&Lj²|$D r֋LLd$U$<ԚD#{}I}g җ¦d@t% +_\2,mt/i.K$okp`Oho|El3DCՃ 3(PUV*$x^PL3rXuDao~"ޖCJRwWqoiPjE-0w k{Z o u}le\yQ4?Uoq3ZԃFtFG,^ c_ov<=E4: (q"j2.м2 ./l(y^tUrOHZ0D}7$oLϧ[4=H~i 4Z8 j~ꣅݕ2F:/$\$w(rI6I8|﷞$1EܻG2x {&La`g,Q㦱W3WQPWN-8B~t`(! 7u6'!*Ő !0&ؑo7Y=հ]Ht$@$@$@$p5qR(5a͂   z'9tHHH~ &v4Xj^W$@$Л`{c7   :9d7{&A6lR{^bzX g`sm$@$@$@$@$@$0#l P7"     1 !HHHHHHG1 @ P 1pG$@$@$@$@$@$0<`ǍHHHHHHBLl8      ;3FYKpZ#c<W-D¼9,N3ѡ+a8Sy( "{_7Ü}`OGҼ޸}ѯ0kxiܿ 35 F`JK݂'s|--aپN-5/906ۡذ EUh8.u9ވ(_'u9b{=p*o&"=p~{ ѡ˿36"7.bC$^X"tdA%6' !dӄwwFɓ8P떠nŷxW^N(-H}g{r ,*ه:4vMT҈F܊_]l{(M5gGKeq$@$0 يܲ&$o>o dhBk`m;/GJO\ʣMТ&_r D]YOY^}TR _QWPx;ZBӌ&.J]v#uL"̈"ueݜMEM=~;3-QTDL} QߍDĴHD F+75it噭>kB+|4׆*pM^WjhKWl qBX5\g;_&sIa@w'\>3CNk *ףnRW >-(YmSڼ}!}h)arfoqU|j<ǁzo;3]NP^)UoaV0ԵM^WC[| @,kIkY5%[ :%A WUnXVQ0N6"<]Uu3^K{[uB>TcGm8TBQc)f W&d4"TVf&c7Jz9 ϜǑ 1HӾCG:iss4$ N!륨` Dxi\F4mmhZ:JumSz~wnBaz +BR jE#Z)B>eRˊJh^Zht5; /s~>WȪ\ir]PvrUZ'a Gi‘M3PZ).]#܃΋_tշdPyqʾ$R/YegI+6/DaU0j-e.¢1۳iɝҊB1 vMX|="h_뢏e/ދƋH?X6il'M/ =/Lw#>}/-%nwM>{pb+>9,ڋ/Pj)>ں* %ƎO]H<#.d,ߟ8u/ ܰHxcL(2dpU<4(͊eD> aNCR1{wޫj,x+=Uh>~{bxSOoSe#b9REhuL1SO%^S ^T jZS-]/^zchW Ow?jd;IHK`lFGQ BUʄWV.0@+O"W*R:a︀X3 偿|ccDj/Ӣflͼr9=ᣢ4"Uڧx_LD I([XExrmbk]퐫ۮۢ Ca],2l.GFji WD+NAKn:np<+EǑ,Ag w:U&Ӕw,|l-·g- ,(aR)k&JvCJ3Xd[&!"f15V !ZMtgf fȃ'nt)^%8X?f?p%.r7=y?̻$s#L|Os\/M6=Bh%*,tho^]U5gP:xI h WGns/=G7Z[]il9}.løKt{/b1z[׋z Lș ( YΈeOСM=7$ROZ*lS#R$@$@c` ӟȉDP`9ތ8EKرO;r-'e:i`}JriM>!];}tw%$ g!x 'Iʁ)}̇\jGyqL_DDP)> Ц"1oQRAqP+w 0/-2j NK &cJL5vɻp1EGׅE\M X'^kţMW>e= ZNdFhg@IDATPbB#Ƿx $Q `Z>R㟂1,%@Dz@hޓP9h,4Am(1-ՠLD5(.-CgZ!o6.} N\RkQD ^q'<#oOrXSLO:]Ǖmyk2D鲎hwbNjvbo yv@$ײGQPղ;J4?2.j_8vGkN '.'m:QZLe ~+uCS@i^{rD?+aK3D]4ng'*9?b+e9lK?i@\J$U?'Ȓ}aGjA%uUZ[4|< 6REK;L(Z^E"qu(GKZC&yY1 V9M+@ђAcf{2}Ԟ/#"f\.j*0a~ ^_苛.C%4GQrP& e1(X$ )ZX-XKO&SWQ}Ct O9ywWg=gc'uw!4T+֦ *~@婟- ujUHGטh{weu^g{޾RDwEM@ވ_5D+aь%Fȩ%K1 2rO۫4 Y#\q8woclhs6;ufj)\ bAT[] w2$?cglҷYv~sēIF?4gBFG?t ' 1}_B[ViZhN5⃭& +!OwT]]9-f'މܢ1DJMl]=Rr aAmUנ09h]cz+sCWaW!ZDL-^^$[4`wmO(=HD߮KU,h7S\:X63+׼c-R!j?}ܟ @&'       RHHHHHH$@$@$@$@$@$@!@64Y HHHHHH 4(3K!     x@` pf)$@$@$@$@$@$@Hdr      ,HHHHHH P}@LN$@$@$@$@$@$`CÙ <  IHHHHHBClh8      $@29 @h<bX %:9ˎz|^Ix̝CL7   M౯7㻉l Hq勳V-X \+5lgɽuⶣ@|~n4,O[E0K\_JkOzl.+~ѺۅRkh \7Wk23 $@&X6hck\d0;Ab;eD/z$*ay%̛Vzr\o x><+7)ۗFoƯˈnjHH]cf2ʚSa4qDݰFl}9r(+F֎vU!q{Qn)7Qg.f"*tI58Q G;*)B&XZ VH؈I'hW K9NOXh׊"pcXQ\y)c|d|7<%p10zD28~%3 1'}IrD452iqސhXԞ{ -B$@$0& "&pD0M}~ muՍ|Tm4!2}+%Z|[PPQ Kq'|EN\׼ /h9MOcvU QfF˔H$,C4o*hspL@6!)ԧHDLDRڈl"{S& \&¿ϜҔf8K&-jhKWl7st㄰jۯi8y~؜ORw'\>37Nk *ףnRWc_Յ C[|USo QTRlE],LVdrNHڢikD(6>U\nA oG˙ 0-VqGɸjDĬDd䘀Zh S6}t]8pu/똻.<{5vCobe}gDj,C[:YEA38`E¼=icf-KEKY=:ՁM˝+kK$@$N!% r%iUHnLs擨|UQס -+hȇx&5z#(ωAw"@b~_oK+N\M}9OߋHdUN]4hq|PX[䅣d dl؅jM7v䨪o:/—V,'yqXr<%%,8,i(_F EXT i{Z}rp_ OzG4\W1g=Ԯ`db'$h ]}/S^D+J*Z(wZ,DK(Y'>_xA 3V4!ʉܜ||YHdDrXVzҫ|8ŠrÛx?@2>UNH5"O1}]; F:jܕמ~&ϽHesw)ܣ_6ܥe$@$@#06LgDjf %@&H 0Z)gw\@D绐#LVWxqMFK'73(,iey@.|u97̇/p•\k\KRo\v-hbbh3 0as#rus_-8w-O˳%B$G^"K|cV|&OSQF}b!pB̮0k%qEʼ2- pFo7wB '&vK[Ty4=g *sE| ,(?E)b}5\=+xፙ};I]ȷ}j"^xԧ嶠(a'Z,@_+I,yVl4Nsm͈CoqBL{sܔ~ˊYŽQ_l}͕-L dQWEyVYeW^|Б gcB둙SWE {+`h@A5o"uj4K_0SE%A)Zoy~aaF-LoqUH;eRנ5GT͇%M"b Q(m{O>R]&JvCJ3XP.[E+9`k4x~_4U0c@.Tѵ+WR#o@DGLwYS=% ¯2V:3 Uߎ7?I`ZV-`=}Re"cm}r`pxk \[]Ws!Az ˨%,MQ| EYŵ X!z^Rv4q7 Y?jl .UcG X$0&XD(rٻsq6jԾ{ڑk4&:؝"1o3ff@jNb仿VWkHR92 ߟE{Ӓq?]0Tt# K?ā|,Iq0S`R\k&.y4>Շ?Od-p$#'b0 RKE>?ՏeЄ'?}qCm!wz|jR'Bh8Ƕ mA);N8D Sb?G0]k]{:fe}CEO}Ww \# Ƅq(ߞ[UT.n~mq☯ OjdK_ ΪLHn9Df3D4>fTaMr0Q> t!WjfE zthw_~Y;1gv|~7S<;@+".QPhUzW$\w fqϨ}JhDlN 'EҦQzLگi(;r.qC ڡHkaOngEx6$;Ld)rA$u;;Q@d\-'LѲWDO vQ~ϭ(_ ֮/ptI_Qߌ9e[sv[V1l2Y.9%g?$0 8>ٌyro}$ yrS+ 'H˓XbY2] u Yj~쉗L(z2YܽM;$$/TʩqWr+mkH6߁sͶ<oCMSb1HH`|= x~#~>AErbץ˫l ЭOS.2*吋\9 e"P* %z?oY'ȡǛ֠ |[T;y&J6UzD$LnE[u=oiJlSߌ=zXv6cDhfw^o/IRw 7sLzRcr>2L<VojLD."*j=R/߈BlCꕐulbIT[] w2$?su_h}%wHhg:<訶Б o}_kMt*2 W z D;=C>m5xeh^z)~wQu q.% ;[4=y]=4SM)?ް6kPZ eT!h]s{dR~_ziq}_[ej#(Cq5 )72VȫlfzU{D8 GJχR`0Ӿ5xc㐅X94G_FHHH N7n_gC`g?=ky1 '0aXνg- K`({ Լ>˚ 0a؇y Wqy7]vw~ze,~IP Ԟ^HH`|;#     qC`lFgfCHHHHHHKp1 @H P )nF$@$@$@$@$@$0\`KHHHHHHBJlHq0      ;\rLG$@$@$@$@$@$R`C c:      R,HHHHHH`(ӑ ؐfa$@$@$@$@$@$@%@v䘎HHHHHH (7 #     . %t$@$@$@$@$@$@!%@6Y p P.9#     ) !HHHHHHKp1 @H P )nF$@$@$@$@$@$0\`KHHHHHHBJlHq0      x| HHH<.;_|`ղc'";n}☞HH`xoO$@$@$0 c#|qOͽlp㵹? w  D`L︡@uq9j AG$@!%g{B\ C~I\ktЩ?+S4X scJuGy.$X>x/J &?ArFݩ( ˓p ӅaNPm[H.cJ h{ouي>eCfI`pvfx&cOVq pwʸ_g<3p;i+uߨ;v'iχET|]1Ҽ޸}iX\q>/JD$@$@c`EH݂'s|--aپX8Kۡذ EUh8.u9ވ(_'u9b{=p*o&"=dz[-ҶhBoCjBu25a%O?#X0"O"֣64܃ck{x=b݌C3g?=L]KO8Zuu7 ^Zd1??^svO% 'ƆlEnY77Ra2h4!5֗#;h.G&&4r D]Y %ZՇڏnAAELdU[+pjPkGˡmz$TSK֎vnoDqS.S" Sмȣi_pe j`&ڶ=HaaFGr2&-/N+򵥠&d@7mnǁue)M]bjQY]z3qV DKRwֿꋧ]'V]. Gku_>2L KL/M sՆuMw(_E@]$˪y(~Tqj"mSVqlԵI~I7w7i)Z#5>J{"f%"#GF1r JVx+j\VtKisׅ|>1Dƴ6wxpY1? J5`wr H܈&HΫB6qwBٶl˒gCreĒCVO"u`Hss$a$@$@c`7CKQ}4iFWEH*$O7BimZVЅTUocOpCt s`Ղ$z#(ωh"@b{V"pM/wp77hBωWO8 D;du)gBVua/R",*kMD:GCϋm2jR8^"5^d^mGݎz$8SаI[nr0}My D,d _USEY(»uP6a +Q x=l]aȚH3:Qh0Uk=a;USHxcR%2dp o颥񢴩q0[gsSZ)ޚH+k]Dn{߿B} G|YHdD2Vzҫ|8ňưx?Z移PR- SˑjD-BX3z+*%UJ|гeғ XpqAگQ6Y|ygۈneɖ2.Ms;i#.{HYJ0nY*gDާyk[(f/Vr}kd @œ*K]bE!y*tZE ʰu1) G~n-FgDBi%ϘLщ0?43"phڥ̯nT2΀p<L2".j ?߷N[#mD⍨͏Av]0W D*yӑ$`wrPI,D9۱ڸ?ߕaeܾyk, "XYvKӳ`umEġ7:cޓʪ"`F$ f`D8rn,m- Xl}>Qim(ľ|MU9#+y>1  1C`lAtsȗ6ao!",NʩßPuԼU|,KP~>!Xei (<=[mb@XQLhBLΈjn%C9>U+5a-B X]}LV?0쯓op-ڗжS?M5QUb zuAp~jLj5$m͢$RM ` z{bEyQX^̒붩h/C ,9eqk VqvyGS= ]R|s~, \"X1Lm{XIH-FF)P<fX;o_5ӊMM#J2C#S-ӎ,FFa/<d19 064P=61ʒ]pɫZېljcn}Cr~Yרʾ@Y!vZu"k&x}fTaM;2-9.DJ1?e>!S>W_N8>)H .ێt1_~;)S4vyϟӟLIKpg"C4yaƺ|Ke z O#@1g=^ D{ZB? ^['*sԵl?lD;nWDkIeWA}s zЦvK=lzo'X P)yWS8njElQb^& 'Tm\e Yrp]rܽ }d@Čg/c\q.*m,޼arzFrcPBb(S>uXVjF2jqhj|;ߛ$@$@Έx~#~>lAơ-IW[lyY2*CrF[拎X(q]=ݟ7jPZj~a*j`2y&J6UzىH܊z8ފ՞=r*=|Y;H1"5HlD"zt3~2T 3rqD W2R J(HkP[2ɁH/D/CT5Sc?>SF9ɇR_*~- 鿋b6AN>MKuZ}O^&GN7K8?Z;Ō_!wz,&6gMxɇ"lɐްwR/4qa 20 S$DjoRa>8p\e|}rދ##Qv=$"Ǖ.U3v, Q,1z1ҧFxX6*LχR2o?\b6vޕ2ZqP!j?:h|ɠqH$@$0> Iv$¶O CKF";A$@$@$pN7n_7`a͂$@$@`];AB  =ib`yb$@$ LdίL̛HHHHHH`P1̈HHHHHHa02o      #@vP2#      Iä˼IHHHHHFCɌHHHHHH& .&     1`G %3"     x(>L̛HHHHHH`P1̈HHHHHHa02o      #@vP2#      Iä˼IHHHHHFCɌHHHHHH&f̛HHH#pفsn|Յ}s[Mwcb)   I౯7l5 h+G\y3_ks2H$@$0I}ǭaa{rq׈)ߎQ D$vW`H3pOÚ?tL@$@$0>=6Eyڟٜ' _GL?{'5HUը;P~3WH ˓p&a8Z_OEr\ܝ2gN~_7W[n!oeV#k_g Vm>4Ru+¢H4N ~D;VÜ}Eޱ8IhoҼGxU-STz:  I`"n9ehh>l_ KQrkd{O nM XTR؝uR^!g.f"㍃8-^ Ia޽%=^_!s˟~lcv (94{6$lσ8u8ӥ(?};Fl]ݍ߄ CMh=c6_Tz #06Xg+r˚0vQLL}~ mH97Qy V]j])s}-CGV& WU=Tβ6=m납%kG;R78,R)HXY)hT4m'z82H^W𧻓 :(&i C[|*da6wㆵƛ&^›Lhd=<|mvZ=/Sg'$A@ܶ2j|y4nVѴ@ *|ץQ©"_UyA$0 g1eȝ-#m\[|"i-ðFF=I=Z߇yV<ό~>RA w{|`r Vn,AuAWfyu/Pv]uEoK* ~Fz*&M2H #2:>f9#yb3"bpt`D߈aǍJzml[0i\Y1uG-XQ(69Sg{өἕ77`9%gV,@#yxiM6Ȫ{aF@2g_<evې ov:'NJDgDQN<ڜ_Wb }be. s bFXz, wb?"PJC,{DIK0!1w(ɖI2% Z1 iŨPHniOJѼ.P,>YBJި  8>!ڥ1rȪjOsX[Qvjj 'f`C`.?*7F,qX+5¬lBvD,R J10 S˽ V1tE|ܜo8H<1L̇"a ucLb'B7t wNI!zQ8,:6dz\x_Qi{?&AH͖IF(a4TVD7>T<)]0c"{`|,|w INJ)'@i? vW LX3"4Ѐ]~d3Q:dbpHڛX?{OLc& 1"k^)NG^#Pw(}k"kDJ9gJߊ^IXYK D/٭n̄璁"jhQDa8O,sV"kX،'KoUAsm02EMȒE0&EV7V?(KуБ?U9pua$@$@G7Xmִ]4jv0O %-WWDϜG4^VYh{qs`u .gr{L?e9_|x ~*I" goҖI#8Z.E=~7-FGC "}WJu^:LZ&#ay>ڄ脕^K{4L%*V}%9P0ZoE^ Bdb yK(:1+6S Y xI?0*=k"wx%Ci{X=q'vZQD8Ȳfg2dlKY4oiHxlS}~^6Ll1Qħ䠃  sXkGҎKb>*{w1B`Q>=L娐SV7/CbA$)\O|MuDujqh TS) :9m"?B&"bhĿɩC659>, Vٿk͆9RZ`Cu9Zi^EƉ&;WI=&t$@$@ݏoh`c8T UꗎHH{qkdݬ6Z[yM؝r4t0ں]~muܡ#k Lgג\"˥M4urhD=NW%P֫QarSLT[ɯQZ=Htrؕ`["~K>P3Oo?GӐh4ڗZ^ xoQR qt# ,u㴾7^䨍pm k]ͼO}CB!G镺0  "3XWFkXk?]}],uvS-%L'&7Ĥ>~QչիQba2Yj,X{rmC] qhChIWUyFKjy-  e*(seeoes~ mO_LJ;3ay w4Ab8 ta>󰞮@u!-jKQNwf1䣣rȕqī5Llgd@yNۅԕqHHn@[5Լ8s@$@N X   /l^S]~P œA% (vf U֗HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHHzy=GfH$@$@$@ Pk'}3Dž׳ޏ#𓡱Z t=.^t4$  L՟/3^Z_Rku٘G~byC$@$}  b|Q;lٮtֱ$@݉֟5꒺SoU[[1>ܪzz\%~n7ᵹ\cS^s# |J5CQD}:`c[e[P2N[Y ˱1Mf|W s;:4Mҩ}ٰҼ78_?mI¸$@$@]dD'.Gy/%eY[_^O ƪRN!՞Gy6#r]q㍿b=(:xoGpJčMF'}0NMt>XsV|D`CJNWپ}R5u1kbFWLX13CmHfixjCi*rا7ؠ )M;ۚC$@$ k)Fbzs&"(@>aFiɇX1!X I[4$h57fI>n5,P~.yE)(?-Zv^)90כ9[QO\\_'[/?C`v}ǭu!H`DGH DܼHu߱kZ"֯>Gi=]4hksŊŒ$G% xOƑOߋ*7:D10O7S `ضal|Xz ㎝)iO2! b|B5P M4iS %d_]}j* 7.BnCHDIJwm`I<NJCkV AwL"@21%4;S3 )&7Pf!^6BMAnNg@BR(,ThJ]`F>|`9ChQ w[+M:T>=,kE8t eH.CHH`C2)?uO"'Wx" ="'+V3UO {4ucphoZ(_D,T俵mѡ- |HHZҏJQ(Q -Hc4ԢٸP,햾Y*!5#!.%ԇYQ8i{$.Xǯ܆ˆi(;3OR(FR&tgcp7|;f)LPB1{@2Om~#gkؕ PL/ETYoDϊuX63 a-y>jcp-t$@m#`~5O<$G#Wo-0&]'j!9Ps{y$O"dD"`V*ibbjjk?8K$%o=7^$k )z31%>.H_+eR",*w+|X|)3b:   V:[z V?N0[$ghCY$/CԔI(g" p"AEUSq\ZQS)"x6UYd-134a-|dZ ʉIb'hET-~])-I c{py6˹ٝ=+@,b=2)ňXf&C T]y9cTiZDۭpXe._?T?2 T&LVސ aJ6JHLeZXCE<{z>p#+VUC;+R\.ɿ@ߒ`d7dRz5~%j4 هhb] EV]ȷP!b6*vs}u}zs!% /p:mu҂7d9F 0GG0&]}n51J7G#_@嶉j§qM#"YnO'rt5 !f7|zPew ɑQ&/AO2rY駳jrLÞ[doAjdF5qI>DTBG'ekNnstU[%چ$}-P f-#H/VgoꈪChe+?Wj9lU<nQ-͍AXu گRjj)y7NKw(wN9 \.V4isw%-߳G6⤚ KG$@$@>}±ju*7Γ`:cR HߌPiMNɖ8Tv~ub~k8 dft2䊰4rPJoDTf6Ŋ!{D!{F%YS`pOt *Ŝ|(VaH'lZ/FXDX "]/Ӵ]&5{aʎ32['TiY>uEȁO" iʘ&8U49H*9 n| b yI+DΜR^#!SZ$c0DisP.@w&`> QХ2Xw@4Y?dW%̎V9:P͢u\XHG\ׯ ;}&b«&iㄌ|:'}UX5s7Az-P"Q6~Br+BVSIHY z|)?).V;?XyjmsLz2_ATB^a rx#gzחF1絉|iM5LL O>(΃pM#OJ'=wd~`SV7HMx>rĄH+FU cddč,qToWd+Âرcv֯+[.rf4w ?>wd=*N* w^" k.+;\0G;FbR9 / @K-VYbawc#'mb>CFΖkĞG#njg$H4s,\ ]>$5)IW߇ʁ{O6tɲ\INl)}K5.m-Ɇ"V>y:Ҝyr9LY+ j3^n _9K[PY{S=|;ӑ tw=.^t DKP'(_N:u2 /#3 ֡Uy5SV_^}#1Y&u:w;1IR_zZ4$3=L)ܙ+cUn{⊰з3g6Wz4qmh_ L@lW931s~>)n?oMt [MNgoO}cm<[- zfĥ6G/zS$ >|FHtQװ&ײT],uvcf&5S3O/«_o.Hz R/«gFn&y-PV ^UݒƍEkxR[>OҟH3ӉbLoTtFjoeeb Q꾕O n>;}?'_nu=cVbD |RGb=]^CZԖzNy xN'+E$@$e ?_g7Mlc Ҽ*BG$@$@B |$@$@$@2'>qc8.\G'6lR{^24[X: t:`;#aHHHHHH#i#     V(vƒ ώ5'     nElzl, . Xs     V(vƒ ώ5'     nElzl, . Xs     V(vƒ ώ5'     nElzl, . Xs     V(vƒ ώ5'     nElzl, .^[uּgΘQS-^٪ٳ'O;:|H$@$@$@$@]@/]jb{:^++?w??DM-u]åKa6Wa)Ķc G4!m!4Jx.n6k- @#@=["e64tM v[/ͬi}HHHHHBl[h1GjkG {L =B$@$@$@$@H8ScIHH r $@fe$@$@$@M}ǿUL>Ua   W4!vk  li+ت)ͫ8G7IHH@# 8w XQȍڐ;Qdk\ڍB+&i,dVR85혅M7v fKϐl:$ "[Kv ^k]YP^{kx+K_DG#b|"łM^kx܇'w^/ IH)2!>E zl(y{Mytu굢:wLً`H<2Y&Bpuc7OW CV߫gMG/j){Y`z?fk_0}qya>m-=Z˗/J;}װZ߿}Xmm|tZ:u߷o_-]seea !̋YG~*yN!*q%S;[Ujf$ <Ԟ_zQN$@$M dqÝc.VFaec:Jc+-09#۩$@d-J6a90_g0}1a=UTGqz2JN9E̽Hz qNB15 wT8,u'DO_գrfYqp3*뵢VlqAS[XBo=QT4+NƧU(ݔ= Q)yXUv G 埱pů/bطq lx6>~x D]%6$_˱{|{NgϞ& |P={v5#HcfÒʽ`* <[љ;Q{'j!nl9 G6#InDr0O-rQo% ?g_7l'Z}&g0.6 {z bFC.UmkX&e_̙l"k~ R~l@JBA~HNe,Db30;kGyTt&>K@^v /^Duu5}_$<)^$ghyOd& _؍(ܿC>̺ZEϊ5,`=+QyɈ݀Z5P)uHvDѮlMF*m7|l2 oVņ<6 KT =5pW!swZQ8i{;Ҁį܆lF@;_q0 6"a-'1{;mf&v!Z .ݻӁ8%Ƥcr#b,frpIy$MݢZMzoN :'EX| ƀj!q f[ϋ@ FT~cj+<`ŮtJ|$Vm_ KK9 ejmȚ?xb 12D8 wi 0NXIذ /-LlX 6Y4mrsb ]8"\׻Y\@gjϞZDFkD%QW/6]a~ùK*rcj?oDzq{mouKI]CPķ~; b~<nixC݇ U+rǪ㡈 sP)`&v8^iMYeը2| l#UHon\ˮ4ɿ@ߒ`d{ʾ85 ^;3!  o5_lcԏ!4XLI" 1}}AӖOĉpf;aoCjP.t2a%kem2 '7j` *}K 1rk0^+f@b9/CĬ>VE*iSD[+ 䯖6/ G53>D4vEbJ5'巖EC\iUY7ם`>>cw1h=-FCwJ05 P4S7]wݍ1ԩS:t( } j4ÙL4i"A؎=ZbkMm|ͩ5s%H@`{"`m04 >b;vƖt:(ݴSLYNFR:1VMMa#NcBZ 1!\)j93&7⬗SCQ:@"Z7u]}i-E8̜eϫEjحĔYʀ e喯"'M$Y]`!DO)t$L"f]xyl=e s* yH}D8M]C;J Εw׾?ֵYhmȓæ@y ħ7pE/{5('Ȧ_bh4~j-,e{Xr߆{Y$bevl~q۪~~ͯPvq|G!Lw}h֍ǟ'9Ϛۯ<3qʜXin^끁z&jEݐ*/uCNmiIs2VhI"/v$!-L,m䰽H;Dy$Ȅ1R5۶ԕqIHH# 4?,:_S5F(9VkqF8?OUkشZ0Kt*H<ők0G qr\MvN=H@LIG|4?G9x shիJy //sӅˆ7nP)u#aNT.avK)QtK,t ˤ GvN",OBԎpl-FHcP=(RN C.B z+zFo)U,tBI&5 (~6>Ҿ:ةYu?uFDD_:9ԟ7`CR$^9i. zIg찖q.P d`P!gg!_cPNiOG9A:(E9i_z9&% 6:m+@ xOV 4׏PXdR۞l/o͘Ls_OƘ>쯓_kTfnQKM{ oHHH@hPu5"y[+}v_qRX 2l:op to]W۽+[O$@$@@& wtƱ5$@$@$@$@$p PY$ @ Є̘HHHHHH{ H      vfLA$@$@$@$@$@$p PY$ @ Pm;3      (,HHHHHH(S `otI$@$@$@$@$@$v`Ό)HHHHHHn :$     h; mg$@$@$@$@$@$@[E @'a .p55իsٳ'O;|2 =.^td\IZYy ?}]?bԜRNͦgO?\xOb 0 UWW%sπ^;3WzE#     2VW:w/_ָ(>t$@$@$@$@$@v`?P{^;l6+.FhN$@$@$@$Х .x8  |5ؙ>9UA] @"@ lz tmJx=P \z 3Ado*v:  &@2  6l(4Jpm mj{  p#̈́T0|.GayUCcI4ZnՕrތ])'6Taqkbr?cfuh_їmۗHDv '̥߱UHnkSS:idJ6%86 Qh*31cGkcCNڎWK {rCOA'  쁭 ] !zΊw7 5a2 e)@_̋CEzHMo;{-’<<<iqu7JT ["qqMy,EͷH J|`?"."o0lbw_i"Ja;F9ݧQrޓYOJ?-kց~QL?Ɗ ~da0"LA,'$j,[&Y)1+>Sbc;JyKcECv"  NGgX}e6hL$J45P.EuHajAFdeu i_E kl]7.IlC(v3مВi|D~BlA&U𸩋P5)wb#H9Q:<^*3%DYY{PlG!ɴCqL xD%B|ik;bS9k'"sH:O Raݘg2JH}x"Ҳks ZbD$7GoD ʜ}]K(c>4@˻_mwoq߿ly'!::&n;pJK1.iwR*@곳`yD[rŶDɎ{w\dDn@TNd-M:D8oHHHbO JF5.<p8o-/D>&"'^TmJ-d@缶zJ%z ҉roCeRVݷ*hͻ'LKr"LYC2`Moo9y?]=.|~QK_A-`KdZeqEs\Lj+<`ŮtjRDLU(LCIJ=H&O"'2N hנ' _l_Gg܇g_z=0:oUJ@UZƮgY+.^ bɵ̑fb.|K34?i\I$Z5 sf'QV~!&" y :m;bWZ(D $C#J|V6C0{JT܈03ג<-ԀA$@$@$){C/*M=3LRS'k>!bիqHC). b%yfh#L1zoHkw,&NgBv@%S B7,,ň(SB1W6QzbCv zt(j~EbE3er=Rj7~\Ӓ{GEJ%^ƹ/#7ml ɶTMJ}?̚isY̐sJX>UcEӄھ}' }= _O|12 $ }&(~NZVUC;+b!dt*u,ʞqSGnȇߪE?/;GzƊm" @G)b}~BYf FtrhW?L`ĒwL?"֧_x0E)i,3ѭklw<䟐i'll*?kSޕS<4ӄ@[WŬSކK₭2FmGޢg"8icNCه:_VWR.>F>)sm/;{ 1b Km|y;Ѩ?Xѯ\DŽ vГe0 G}TgԩSaN)=pƶRU800={ԟ`S !AR}+۩Dx ۰dRl-نϹ'jau GD6V)}ƊfғHH:=+!02tT;"aM1o.N\ Yeb4x8FVSɾA2y 9(IfSEHxz%-r ڒ5VMm0%Yt7$,T5a^BӢxXV1ѣB|m/r:yC'RVEپVy_x6+Ϙ} *X756\5('?Q1yt8'R'[H|ne%LҨO!m(Z˩pOXq9G(..wy'm/0'%N4^@p}:mbZ|{Ƽ#%`璐)4ٮ`:"@ 0a_6iXmfV*IWaEdD݌ӲE߲A1eKJ_5Fy+ڗS @h~q巺$Ey[Hp V5O pC3ӇD&% 6:!I]>9Ti\=,lzͤ8Pf3nʡH<*7B"HUٓ d1/4'$B?r6.; Wﴛ}irΎi[?F/Sg u fs! $݉fPHZ]6qp  z\x˾fzyNclhuM<9gN<%g^)/IDATnV+m"m7mK&Ͷ0׌Z,[Vyu յfZ*&\3v䓏ŧ.kj ruǭyɰf~cP!젊T2~:ڸ߽v<2}/ip}lio}dN{nR   p!Рr*F•k Z,Oa] S׭(:qu(msn)5˖٩mjO:r͋$@݀~N/A7vo0՛s;9o @=[_c^ ,¯mM꯴O ğ$@$@$J+ ^ Tw !  ׇʻk7!.9HHZ ХM[h7: Tu/;Ņqg; A@ ֘gIb+HH:5x"ޓO_GqGb`IHHHHX7h/sj؎.3׳jk׸(>t$@$@$@$@$@v]5:|ȾA;9cFMͷzoT&R +ͫ^&ĬIHHHHPڒ @%@np     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -|- &Bސ 4&&ĶZԧ3юu  LvxҼ6^U% #  k>㣈;ZuRf;Ƨ%t` 2 \b3ao=fP:vB :i4D6NYtV$ kooU.}BK%6K2纎}P}lRXsG$@$kKFtrƒ(*ڏSySYYVx"7"eU .{Bl=gX"l$bGaZR>(LUzv%c" [Dz%bń`y6'!soDi&OQ'uSPXx#TSPc#xdsR5e^)1FFČt,ג[E0KL ~MmA)[`h|9ajSNg>6lY4lݛD+jn2),ӥHѴMܼ͚ίBd,FYO'.`viC372_10 7OL[tӇmkƶx;ͧ5 Qj.bEaFTXf$X \O&X M[ӤM*t6"~=vqmN(ܸ!z%+a~Wߵ /[%P+b^<9`ռ`t7tE&oRH]?fwgxjBP_4r1w6YF8=#n:DgD]9[^$qȚ9f`L*wm}^['aBPqD!/C]Fߏ ɡ~'c՛"w}E%!vS֡Ġbfk#۱DK I/mU2js?ÞI:;'>3yg;]qS1Y-}泲"=Zcϫ;^oc&C;1q _6Y|{w2-E^݀Z5oˢuHVwW6&L 1kޓ t>>!B 1V=vCЎ̃H$>aF6dsan E=K5+Ak>vAxb|7v%Qqsh#0EGL h@m"oFٙ$߈}X1?4YQ"(Ǐ E`#RnIFڝM6e#jACl~$ O"xDɴK] `eSa&ҊB yCʢ7 kbF$ ɻ(;ШѼ%+Ƥ#kDJ9gJߊn#jzZ۩D lR{4ؖx( #c0{%aɢg#}KMm ±O= ]K5 z!jvT8O %-=xҞ]=S8|YI8`e`}6W:Ӻ}d\G fg WqJҕLn#RiHM EF4}=KAȷCt.7["wx%Ci{Xhu%fA2 JҘ]"kAL?2#lLG5z\9>R K;\[]N(l욯KkV0{u`SH] L ϊ`0{\J @aeDOkOcvOc|-˵\AU @:+ E㉭WRs2eP2ܪ&0\.ֻڜ (@ PNk^S 7Y! hjGj1(ꊱ"6M9(i1%n C֜H-^!w$E{:mkI2PI@?,1ؖDD)mφ0%Uu۔(A= `kэu.|cqhF1kH{]͏dE##0 ӑYyBt2yƬ_[ԖCuIޜsFcԉxHX!]4M$k= f3Sջ%XdF6 |Z(КtF~<'<^dږ'?]`uk]T750ׂG]rPr &Ali]jZˬsݬxDyr"9a2 l}u(@ ZvZmɘlֺs \]ZU˺N+EHWa 3@Ro &{ik*]:7\%)7)P%ҍ ,w bN.vJ7KoO,w#JZk˭ɻw+/D=~R P [oAG]<5dM,7Ol0o)"^;J^L=6?R\|9ۺ:zK(LMzIN o/n^:W.gnV'+X4xB>Afv$noIܭW z9(,Sl6ŸE'Ysy|kE415F, ł=̋&nX5޹eW-eE0z$Nc[VC xXR|J͖f3%I{&W.mkZJ !u:W8JYӍ. P{`IMEDL ^gұ0s F5W1%f&2>ǐ&<#6 C9H Kuâ[ =u\8?7 z\W'm.\vqYּp`=΅|C\{ SwAYY6?x klnݒڵcYEE<tv{* ÖP "fzJ%C<*3{璘3ŤZp|\d]/c8? ybƼtC]VXN(@w[z F:w珃!P>+w#x5_9tZZ.b,5G `lu]Ւ{ڒkұokuKkq;׾N΂rjFzϔ`? 3Yطa-D||MZNK7rEG}1~I[&T&놏GL6$W@bʬZ ?܆(UVyE-نBGh&\ndfҘFSZH4ϗ'ߚ-Jv6f|*?}oc_"5u_ޒ+]YX*u+eB/A˶h_C\ҹq/F|i:{T=mɓ'pV)H^ǵϟǹstL"mjjjpaTWu_(6 iH5 z>k3Oܾ-tC~hc_ (@?vِd1Kk.Oٛok8he\8!m`)@ P Dka- _ [[,s1n[(x &659+"vu92ߗuob:AېSmA+0M52PZִX ؼrlyK"~oͣ*F7n$t=eπoG 59Ik%:̅ݑ FP߳DR@{p<} e0;92CX >N{7X%1%JQ;:]keqx>, MnIGīX6AZ'YonI **%_0bREqX0֮2h:\: NS ,[7\޽`Ĉ8q[\xQԩ/?|ouu0_}e޻w())?N{qe k>ǻ VScܱǣ*f ;IX3kmiŹkbdvL޻K{EՋ(KSb6#*2WE膍(ӍHd0'fүR]Z+60RYuS JAM"2e:le{n S6=>j\"#%c%$b yYz >)& Bςό'A$iI.%36cK\?yɓfNEeߢxpuCZ$=9 0oo^}۰i? z >YQ~{b۷U:bbb cǎo\Q]УGď=}H DFFl/xt,(j0ckr(baے,JU܋7϶L4tחNO~M铵-C&,Ǻn/?rr?(@w>хX7rNx@c@S#+~F­kw;]4_O !Mtؙ9+ݱ.! J;-HMl*@PRl/ӤV;~p7ƂM͗_w$r_JC?O=3+Okwئ%ܑMw䜆@!A**=bt%]o[}~nz?kWZ޿xnx׮]Vׯ֍Xu=Vhz ["kq 7 " ǢpKGZV7sEbymTs d@+!@ǐPܨ Τ˹Hx\ `yP[`<.e*e%̍lNW"-X%MfDM~Nx;n,~=RRV`ftNUvOlNpնS;ٛ'\m4f96SndГp`Zۥf2ĪgϞCkVA H&Lg+2 ^s>d-QzdQa"F%Oe0]Nj`jAlPqCA.3B8QZ@g0׆1ˑ^?$!e[νX9B$U71{+.2'aW݁-[9:UzԮWۙغԿM7q{W;nm02jX͐DzIݲyw|<ږ3&ަ~)/0:5Dk(Pw蛜[Z1N^s@un}cVDD3?Ptu o͉X qߥFijmcӑYyPK\Ц\,u,s1zU+NG6tVDbv`,^p>ԦO(@$ЮZ:KdñGF v=tK9\!E,ݳY*̨uN$W笐@1Vi56nݾ鄭iKuOgskyyY3嗗>27@}F Ve7¨rۛ#9y8fYW~,zy ς7qcG˚\ (@ PYgZ`3m 96 $Sng :xS91qnP#75tڗ}[攄Dn݈#vI_(@6-H}acڴ̃^,Q{qY.a*vv>9,~(@ =.Y P|U`Pk߱^U닃כ(@gL PW 0`LH 2 Zka]<[JV\\]}ʇ(@ P(^{.@^|p %Cx2ٲ' P(@ PO>F} TT&wl>|nêU?rG(@ PU1(@ Pژť(@ P( `}c)@ P(@ 1mgq)@ P(@ X_o P(@ PmLlY\ P(@ P*W9(@ P@k IENDB`graphql-ruby-2.2.17/guides/queries/sentry_example.png000066400000000000000000005332541476434635200227540ustar00rootroot00000000000000PNG  IHDRwNiCCPICC profile8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x bKGDC pHYs.#.#x?vtIME )V IDATxwtTEf{%!jB4 X{?błEJG齗B{ +kBS9c{wv~v&*B/i2&C/i2&C/i2&C/i2&C/i2&C/i2&C/i2&C/i2&C/i2&C/i2&C/ƒŪ8Sy&΢AW~J2Х<ewb*BVQٹ%8݈ yL$]zu`09d咚Aiq9A~ĵkItlZJ86}^y, +q㽗zB>0؟fHږЈ9_ЦC,c=י)-.܁5?5K7s pKXg>2x-&Dg!2:g_Qw"""""""[4~ʊ*z?;Z 5uT5EfS^V 8Ff x5Rg!+Vmg$m4nWRϚ%СSkglvSKc%'3Ʒr;jkw듀hu˷ \|H⻴FuU-U5.CFAuU 돮=;G1[IIO}\y녴n@UE5U'?[NM35oٌ6|:wG`44ϯ~'""""""rj6 ;AzMu-qZr%g9ZTnR|}Xb+6줸9shZFf&6IavrgŃ e ٗȻt[.νM|,CG t=IۛEzj豬Y @_`[tl$['f& 𥳐*'$,"ܟ0hxw$᫏~d2qcZ,c׾%CG '͡` CFk(-.u;̄P޹}~g;{wݧ3Xd]PQVɤҲUTVTՇ?"6 C/MqoWc5DD2t@Z5g^ l>|uQ-sݵbzK= f.gJ5յh9pN;V}`M׀Ml6/%Ee$oߋ` 7+_ϣƇNd2rCWf6a6֯FiQԝ;܊_ד%n80_K\zݹ49?/&}_6F u&^>jorڋmr -`^A!|5'(4[8daU=w3ӿ[@IQ)7{99kʯr8c>` a\x0rfM8s V<5GNt1_sfW†ȩqZU\X򯶦O/f6Mp<޸j;cʜ3oR8cH08T+)*`1?MKeEߓga鎻ٌ_zCgԜMMɤ|˯WNL[YJUKb7OVpJJٸj3oeo~Vйg{mɏ_f㗿af%_~ ㉛ pd2yǵkݍXVj-Q_L+r?~5]Rxk3nVZEop1әTUVcv7bf/)cUqpE(v'cS84qt 8}E4`e]J7ڵd2㉗'$nܩKtg؁?ƾ,W\PBbŅ.iSM}ߙN۱uN};OM*#+-PVZ_!a[ \tpI`޴$G$B _/oBLn&BÃ9l۰3q/K.|6;0!l&͡׀. /}Co7oVZ:v%iKi)v1dhM9!sWkxucOdg"ik mZqg`N?@VQxvw^T Kb6ΨCOMF<==(/RWR9M4c:"M4zSKmX9tOX|v[6;_[= 䶇`[Tz0Og`7s ټ63prbZGӫ:voGxd(7?_RnlpX cvxxO_a4F|rc8/g닛D~Py79նo\Fʵ\vwi``]ӣ_'(o|K,&>5K7owᑡٹpL]:AUXs Xi)grfز.ITzěN`MsĽ9P;)~ 4cŴn@X8s9;-1kxj8W^ @dt_5 Fڵ @!yED[?1b4@]iqsAIhÏ@_-.r<~$tk8\%qs I=ITU`2au Kp!<2Jɭҵw8ܩK#?q}=}f;kҙ>u+kph:Am.vmK,fQZRNzlT}Xl3h5 `MhEmʫɩ&>]b1 ;bX 'k#$/̴\:uoݍ}KhEqa)]{dzm. KIM˃H,zHܒB~n!;ѹgssơQ[d&!nfS{ʶ 'R9N jt':Ʊ;q$'] ~Z[|B]nn6o,)0zxeʟt&>_/YtU՜1xsu_]UOv-k~އToRUYc,Cvy6{es~wNۄVy;:QCil6lC׀Ӈnǀ  ˠ}پqӿ[uI ~ؔK.i<5K7ȏؿyv4sټv7b˺D6MK\|H79EDҭc};RRTF,ɤe^VD-HM`n pSعm9h_[IڒŠ_7 ;:9 kl7 GQ9='صc Zt?ww3fc *:^󫲢/$Q`$'ZUW䧖*hs]e 9GQ%מ 8=$T}Rs[ vw0 fa4qۼvϙ(-.`d]xG7'8<@:5z-nߊ^†ڵ`oZ=wۓs9`Lx*oGNQ\XMHXl:4w"""""""bͯsI3\x0F#mU2Am ЩG{6Md/+҆e ױwWB3O]ym"ubDӹg*;)Ғr,ur2(/$:scbwR r%'sY/BXs f!5e[ܟCQ͢f=/Rxj"N͚;͟tO;c F#%Eed`0гDžl"2:|8-ina) mO&y$mFHX 㗳 e~ׅPN_Vdž0 M[+]N_c-|$tmR\;C.=;MATGwh0tFZtMZ9HIQLIQQ-1zښ:.Ӫ*=5%րNl3hML&#$Cey%!e38m]i퇂_6btCz8G9:uف=Ҙ7u)Gewb*o$K.]EDDDDDDaNi8|.F .;шh{Nu]{;=d؅gRW[ǚYd 9ۡq܎.i~\8v(ot, _" 0L5 ]B/:ӱxqĵkAVZ['ѭc u}f-.ȇ\EyUԢU3Jtl$Yi٥]v~3&//\g`h2'gؽiܚ m:}kג^\{>Hߗŀ{|}QY`->J@]zu`ۆ].f\fSLe~0L0R}35K7c 95iE`$6r]|93} PVRNzj6iؕ ƌWspw7N]ť5VmB+Ӌu+|:ZӥW<7F`?ίW KR{k~VZ.YFmyxѦC /'5u {j%?0L&qh޺: 9ndS\7l6W긤 n}:&y·}!r55TWrR}`!a!?SSQ^Idf ?+yZ+[͢a7o؊' :NIúȦYd87~gSJޙ[Ǟg#Znoپe|_tm9{#KalǶ`*pVʳv>a vIzw!g1aLG~ G̖7qC;W>sVF6 |B47%͝ GJIׇ'zJ0,h4;/5m.sg'=-Vq1\zŜ=|s}L&.sK1廩ۓL۴a F6;lް穫{vzJЎ;6\%L᳏$yW a!?6_Qa Tu"'G}ޟsΔ=ʻĶ7oP޿/*s3uب11Iڱ%n=./{FRbKzeey <<=x'1STX<^ʮ,_N=>e-xyy1dtu.l&|;wRgm6x۵tꚠF"5slV.[dGҭGgj||YnϿ0{|οp_~*RA!`'B1=ۘ"р{L ':O=KWK]]7_9d%73g|mށ']{t+FӺ^qѼ8y`۝ǁJKy'dίb*&LGtB~Aj :Foް'xΐ(/+g^?{1ۃޛϾ?! _|[հDDDDDDDN) ~56c` 1Z!&}ے疇1A[^gcʷ/&|Ç`4]U1͜=1Jұ&G~{h4ε/\ƋOws?P[[˫oAgw[q Z\.E5Wpi,r_zݻCIq)Y+ lֱLF֯H]mKu q2LĵiEJrhܖIVF6;_+fѼQS]ü_2w.l| F+ _{'6C/ TRgqt$ooC}fצ3_q 7O|vp%pw/ø+Fsy'ؕ7eo]v;?̜ܼq)A<#h1 le 3!c.8{`"7cMδ6Ӫu3ƨ158Nuu5~gΝ]Fn{eՂvSCxDyyқ]:??ƌMVF6O<6 +3o~贡7z58+@nࡻ?7.r/巟4Sv2_Xx˾vDhyĠo`_^^9U8ǡŎX6{]mݼq 6%|8puӶei,Z\͓?`ߟUPu?>`BjβߞLjUZVn`a-9cer%75w _u\_>3u$9fW./WՕ0oItWߛKͪkx͏D:wȵ7]F%""""""rNs;y'7s׎'UM۸iq1def"e[iI)(3{ͥm#Ďmuz#s۶nU\ WzJKqDXW_BIq)'Ma5;jIuMuZ~vMsFrR28&~^-]7PUQɜY شum+$>ҀAgpw2Q#0 :vIfhb Y^'X=Ыo?HGG9)iNrn:U\;ՈvSW7IOA9o6og3/?dT9AEkߞTiy0Io[\({"}9eu2s=VVF6fȒ:z-YȔMЫOw&OB'UGmtgPCL&#$pUcxزi <<n05ȿVd8ꍎ)nE}P F^v7,F;~u-@݀ {ԟK !|8euUu}}|ݷfw_8ҬVi4"""""""r|<A."Ӫ%Qё L&#OX̐sQ__ϽI˘-OMxr*+OhՋlf9g23X,̟^}>|M Gdbbode5S?Cw?{AFF°` ܭS@i'UmE`ƽa3v(S9CeL 赿t]ٕ.eۮdL&}݄q{:%.?Ͽr{Oi)?%@>E%|\|z_7n%7'wuu b#/m'}晗;誾#dV,]咾rٚC{7(`o;Nݻ4:$#VǷ%%y/Gn%. e|ާ.oY;k]{t>z g~ tu7;^!9$Ə`̩sѫI ߰oO*|5_jIQaQ2ldݍK^䲭u8||Y|-YAi?1ld_fλp$AQu eXn5XFh X,5ŹTJ㎱̂{9pCԈSwl4߱=;;soVca5.urHޙBpw]'O㌁}yէXzSYJDDDDDD_yy 𧤸%OrΙ'Z)F>oo[LnNxsۯ}u1 {#$4o ׏eeGߗFLlKcp548Խ-~YHqQ Uאc-b7I3qN~':$ڛtٶifibZ?sKo͓? u=ċO=<[YdsKLLOaM\|(lv;-\JIq) F#Gպmfu< ?o(ے3kAwa;uM_{; oӪ*T<q:Znz. f<ȕ^@aA>&aa<£ nFm}I}*"""""""NY𫢼9-aiiSltuu7_Mn>5xzzpUc{ؽkv9378^dfWhO7^~YbZ1tމǟ}`g9o9=ʓL26zQz;pp{A&&b2ҭ3O8/=s_y&|9`;U׏ֻn8ꨵ#]tpO_JެOBIq)a(f!b6U"B艔XMHOrzF+``jxtwOSZRJmmaz RF_LDDDDR0s*DŽ{ h 665&O<{;<9i ~];)~yV{vnFf F6k=͈;w4S[~JK10`h4Ljx|>J$"""""""_""""""""""""dU""""""""""""T(%""""""""""""M_""""""""""""d(%""""""""""""M_""""""""""""d(%""""""""""""M_""""""""""""d(%""""""""""""M۩HPH%Xt37qQ ` ̛_/*ʫص}/^Ѕ>gvsgϋ)+"ExsX8k9s3Х͟m_7PUYMVD ;=1מ @qA) g-'#5_:uoոE?JBv\{}as[zj6$0t@u#3-ХWsaK7s8cH:tije.DDDDDDD崚w@bnŴzt޿[o#6w+\S}dr3{-|%~83]%Wr˩g7΁z"MLgR]g?󩮳79p7}K3+qhI 8hit?,-g]\f)ٵjReL8Vm[pӽӥW<~XDnf~{Kx f$]ř0QUmv}ؾ)ٹ)fO&U$GHX`^]rUù궋(/bXv L&#q1z'˔Hݝ@v~r6\zy4oٌ&!}_6WmgUEWo'L JX`-%h͢B7m){w:oOąW U,y+NNי&ͥ`IYi̘HF]>p2gNFey.J|6,_K7??{_=/oOmG9z[Onf>A,r-JhxKzHX%EeDDDDDDDN0Koxx^NT._w^Du'͐.~ȫ#5pp|=L[ҙ\kE{n倾[RHD^>w|Go ?๯sp_Kgڼ)֋/p{,{Zo hΆUHۛIdp11u{v1kӚ 89ИkFq066gUxzg~šg~&"5%_""[^n͚?{UqI$@HB J/ۢZUbT`,kRfJ{HNA`~KrsyΙ0=QBC3}C7LNW^N-&ՊRZw%4v?W+-Pzj囔W]ڑGڳ=MTX}42jֺӲu.%6׶UZR'weIq rUVZI1jڲO[YQ154pH %%'j!< o""Ô$YUgheQ嶯j}ԍѺrԭ!і%S6[ 7{ J"^8uѠ0Iϧ_+B5,=Stwwu?}~RnإmwJ;eݯaZ:ogbVvA%IR`pvoOSLlMtoՒvmKUbRt[m/'LQմe5k{dk< ?]]7[Iv nZIRV~url'Wv IDAT`EF+=-~%4+ut@$`aEDz/I_~Uz)jڲhuOas Mb؛ED`hФ֭ج2,>fȢWhO+t{u,lҦ4 9 6*4ytFr/.YTSh"B/#7|4%EyoΫ]c% ,UX_j{ "O&h,Iz4Yb`ݸOx>-)Pޝ~@SuoJ<Ɂ35[W=CWQ!j}7PpHhI_TaM&ӱ*6I $%&i4~8oR:g@8^>}.ߤK7g`WuJCNCxSR{N-!Ŀ.Ngα,ϕ׻_cC&sŪ2ۆ]whm V@iU:v2[LսCw. {Q6I!%37wB[YTV!8CyP %~j UGqyx}.^\}c]1r<\]&ӱFLFxE} G7v =VŎ ?||8O~:^ԯ}Z$iOy]1|_94qAmQd,_Vc $é*o1+>1VI'b/qN[5kHFQIZ: ШIۏ^/WיK4ouR Ӫ%ƞ'22:B+,U VӖ ^#DiHWiI3MU>+ gVd6)M$0ߦq#?=CsUkFTſRS+]A!ZyBV_+}~u_^.WvO{EAGtJ,Oٹ<`rbSh~ZQ?\aszZmJ+9v` 5~'>gsh¬zwX^w Yk 4>}hfצbKPYYJJJUR\*ݡ_~BSU4o׸AV^ʓ$oVvRiiw0UM[5Tt-[V9tjޞYXGfIg/SXҵaV5l_E?Xz bWdj_J26ު>#T7֯3(;fO,،Nv**ݺ3Q%;wʑ{Ύ&_:KӗCRrmCd+6G~F:K5Cj`_1SO`EY7:]RISH/դkݮ"z Va&sh⩾]޳2WQ%(NM'~6f C!j]Rl1תko#[XGN3_bx-f%%?pOIs 2(08@n^iS'~Y%ݡFg{&-56if:uo훸 5yt+,"D7sfҰfY#Axso͚2_U`\~]-IqJJNѓeU^nwWntaB?Xܺd_A!_e԰I8xi1y~m$iώpWmL& qP6.E]tPXѢVjک4u1 "n#7{ T\/åMe1T3Ԭnzp@M}4l%NE}۞^~Ǐūs*]yƷr8] 5_PMwXo;ko` W5Cznhr zt-T(AF4|@M;VRT*(۰;TZZ@j=ҒR\߉Zi m4oʴgl^钭HACd`_Jvt`4hsTZZ_ _Kp'].3ѣ{ 2S_U&I276֚kIֶjĦ> RkkeF/I@q5}ԩqYZ^cZv5$|ݧo`IһbI k _-IXQYjjSwֈ9Tr^/:x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x /x >] :\krrI"nwpG.ćlRphr5s|K!($:OW.KC3cVPH| PIq l*/fLL&c`Cd'2Ry]bEժ!g:/ᗻ˥C Sph w"SRՌ `8*s?Wr.K.d+(VIqkרcam2[_ TgVAM&|ɾ.Gk .\.UR\&[aXqq.K6EFq/2!a){3 m Ǫ>;AKS Wa~~I*/cn_d|̳ȩ2[̞ nd13jfqZ(z3pVH$TmKut\G\R\T~pp9.7Xb/\ڑصZkBp !:Kyy=ZubdX萿XIq7[ePHXZk,IJ?_ղkugp*cor)a]E~xܱ?){C֭_Ǝ@s~I/|N]z\vإ)&d2jzWuCwi銉RXDk]_. v7nU\ gydWTB85Hj IUnN\.$eVXV͖-X쌃JJ_i}Ǯ$Is.к+cb+(o}z bխoG ّ'붿_zu TZZVgiz[U]Oh4VZs.0mvhSi^|K+$}\ |5n-_Rjס~BBCT\\?5oLh/ԪM ?n[IQj0:cVZ`/\{Wl $5i@mn4-i(6>F]tP jufLY?k|X)[ߎJj~YWoQ^NjF.}VP>T#*\vTJ׿hǖ=?!Ѩ RXD_WQ7z\5wON_W^fUaM[~)E:7eۜVi;gUW/٠ */@FkfL/ 邳_i]@!ת Y}sO74ijۡz]CI=ʔ;U?LLuPΟvlu;Qy9!M#5ocֺW;koיa JJ߫u<{CjFGud}:| ׽6]35K鋴/w`EתYSkIo~QmuC/?/$?_AwTy~zۍf-j?_Ѡu7IrjO Աs{ᒤb}tm޸Uuc%INS/YuUgNj#<}C Uڱmy uoYVЍlejsY3I6ޢ:q1w{IUkjTm}_RQ15t7vhIR;W?сÊ)I 9/gQr}}bDqjAM-IO~7$o7@);Emq`0j+&koRRr_u/~=ԸErUVZI1jڲ%_-ɤڟyHoRU3@FQZ7RzZnإڟuH~V5kPfYnꭂB~Q.𫰠P w``xZCeWIq$IQKRwJvn cԭUjoV W=Qjhve?.e3fL~dzXN7HucwO\.[h2*;$)4"D~~ZJٹOKʎu4В$ӥХgYPH+qAiTد] !pe{¯knZl5mPMZ4PBú2͋[L&ӱcaͶ*KBúB::t]٠q\TM[6TrFotw {r' 01s^0o;-..$kٙ"Ѥ[+$$سïչk'=ڱmW_Բ}S׺OͲkS][ձ"իzHJJJupOՎsW4ږ/~[^Xur`,;.Y,K[|,tt-NSNK?b1p`0a=TX`Wи?B{3M/SaOMhz/'?U>x+0(P5=?#s~zŹ+&7ST\T|ՈVt;L_Աk+[Is.UI{\bL/?_l۟7k2{zY&[5hROiS`2u^0uWh2*FRw{*vf?_m;8 L&Qڮt59rҒRegTNͫގ٤6IJn|ƍLWnVm%C; 9~9k+Ouk˺miSojՍ7;\ŒtXOYzQyjzu5EOZ|w46N;c>3q]ٯkx髏f7М 4g6i||U\Tivq c͝PЁ\^6VbR$)Mvl٣[R$IR25jrtTg/=q'iJٱW%ei"^Q˪6MЦ;g^J42 %4;W>VvIRii&=-KUIQoڣM붫Qg/Ǜ7s>?EE6weaM/ę?gZeXd8pVUl#kГ>/?Z?N^ϳzŷ oJkWĆ يd4=m?~V=ĭZb-Xe ֺV Zo_QoSՍwӯ3hقGWHuSXD_I_}d.Fi=saͦJEE%r4AC(F{If b<& g 5yt+,"D7OG*Nt^ǫDM=YYvwXFmk놝3uF[']g/fsR֗s'l1`0kz*.K'q\r:ڗݵ 6O?bj]XCيTnWB+Cud4O m_[Բ} U^n?ӣO>͛hܜ\[Aw{to'}レ9W?^5Mn,Iz*88HhŲZuuVו'MZ4#O>ZQ$é3A?ϙ/բ\T~iIo*~ϲnШiT^nQk%j*Yf}JmdffiƊWWIM*3#!ΚW_4 *zڟS5bgHIӰG;oWVfrs$I24JjH{]qux3AO'/ӿ*ժ~J]vyG=pnƍZrϱaz]]_}}b>b$iҧ_뻯nSZfTmݴUoO'^|=]7:XQDdL&ƍKV_/>)I?fzm^{XRTIRqqO'g} OI۾厛3o^Q5%I3gUͨHMF<;R&W0Z\{;ٹ%o,fي+,+;Rm¿G[ߥ‚ `5YZ2o%*s_V^{ly@pHW^MNS) VzjÚm35_w(($P1G.Ku*/ҴIsV[_mߺC?^K>zuڴP6-No>S㦍dr=yZt&~>.d@.k߽W_&έ|Ul+դs5_7V{eֳjj׎=_L6;CG|R9N)+(mw+,*̷ۯQhя/ޟ{rcCGFE ьF"u{ϖ~=jҢeTk_<Ӳي'M?F}LRSk4u u URRo;Ƽ9NBrs/Tx* w:u'p8$I&7+&cn\lnw/Ym[ꞿ^i> X,*/WX{7~p8r9t:caZ{M/yv~=O{uȾAegW>4ຫfy]Kž1Nm}Rj^ϟ^WvWTt$^_ ԳUAZwL>+StH֫umӥ-T>Ԥe)Q]]u]7e)/؛5㱡&xa4rnv}5wB ڻ'Z"(8H1Qة~ unQ|Jj@v}Dmql7pVQr-~u Q^L-|vu[wN>Co$_6{vT95%3)o٬JMI,s:JK٫fUt_.вVhNkdU(͐ wY_->UjߩnzVyW3&B[7Ҁ!SLQUl+ц[u;]N핂Y3>:O#ݡ/>E?gt7yI9WSgiӆೱZdgZZb4xxbF[cʯ#?O~V|OnvZj@LM۴yVyc)+).r8*/G|Zi-/ @K-׼.U:wLLM(7'OcGn[^^gxA?N#*uyƼ9N5"ȰNKeȒ$ٕiXع5ӆ4cCVqucN-[RF;efdri3ԽUL 6Yk_Jf$e־L*lqP ,W]Yp8G3v ۯYQ[7d2)qR^PvA͝H 9k().[ zΩV,j飐`}i_tPV._} 5IvŜMhӆ-*.*9ڴan:Vsng^s WXeːԾ} :zWU~Ո ш1jĹڻ۝:&D{FTWW_xS~DAڱmgbէ>>^k`M\ 6VCvZ`L&z`0Tnh2d2zgL԰dT~>>>qo )污 J?·CB[5SϾ5Nخ{ͨH=cX}GuWkҧh4T+魕O`}޻V._!̈'\%@U|8+_|0Uԩ{pg)Ш\VqCémw+84u+6m2[*xs͞2_")Ŭ87jfs}fS7sff|= ͙P3rՎV;UK ulE{*d2)i#hgc?쓺'M ],Ѩ~Nz74~%l!oWxD})Hx^:4u~]_24WX2LҐA/{>9ÇrTi}^azUUH&IVU~T͚7ѰTnNd8a\.rsZmf+:i\\NlE ?wOVX$Z)(.((8{C' S`<.]_7ӭ`Єqk }r\ɊYC uO{xD) 8S6 ?.3i@umX}8 ~a4(08Z ?\8\_ ߩc0¯?c/kwuU7l6QzW+UsZ+VunظXnrA? ap·.> {a#B/ .'yN#Y ?|N#0b6ڻ'[[/J#C\¨7%/pQνt< :KEt)++n9= ^ao FhcՠQ"r),io(!Eq&DYz=ˆjFEշ^{t=?L3j܇'M}1#BpZJJ5oeacTXoZS w{̦y0~7XtWVZq6w>锬m_qfys\a9-ӷ(-ukEս4k$IdZz:tj*]L(6E@LBfONlm:c/ @%%*/T~~Ae=z'3a+)/'WTRR"pZ.ߤg,bV^n ׎{fMdlZڽ=M?8RV^fׂ9˴lZS阿\6c֪YӌJ?>\p}S;Z> $ys ko;vO{SG׌)3WO?F|OubգOW^V>?|NB^~ҞYz ѺT3*ۚ ]^/Y2H}LO>Ǔt=뿝rC5qn,oߖj$ 1GuyfE׊Wm۷$ycCuk6{oU|JMضS&[W.y]h&}vlߥZcnV^]Ny-tahnu+uCe0tHԘjM PW辇bql}dIwQ$@-t^)(6@D 6" ҋ{ RHH/<c$0uΝ;wfwf>&;shR6DXu*Wij#]2KFEFuҳ#zuk}j7u'iiiT^Q_@AOgwRSP^~7wWNXHiii/eޜ]Fj2f$ sϩܯ֯?vPvu-ZA\\%%Hɩ9{b3`C4iSMk[4VeFXYY1gT'\E:ikvqd)hޡ}r1u+ѸeX=|y}k0tՉ^qᇥ؊иYC\\]1{q(!!aWWR#$8~fR'}Pgͫ#gwi¯G~=Oș\sy|cvXm'a!a&..KW,V931`DG2a(Ɇ-lٸsgspao֝wtE>8+(Qo dD^giڢJpyƏ~ oPxQ>'ԉ3==+;ALx<|$%&ٗ2j ,y9kVq_^5]|G<7rX˲ſXdWZoI)Vhb*̜Gybcx}8b㲴kȵPye0޹=;ǛI Y7#9SO慗LM9+^~{Ӧ} KBC€^&QQѼ(7moe4l\A<Źyk{ynGNmɎmXt%}U}sfxڗ'~|%>4 thoǺ>Ն**A [RSlK5`vɁ'Lq"wC+&%95[VVVzij7J 9q,3>Z@tٜB d~p)iލvݛg!=Hs|Ul¨ןf!,^W t¯GP۷Ё#ܾ:jqfϸ AW#ZCB|R I̝o=*VW 6€!}ذv3M[6h>}_Ww@/lҭ^ NZj.7bpsweן{b}ko֥I<;rբGlZ2@̘;Ւ~98;;Y~H[6aWpus_a<~5*X|ʖXl.6Fdo_^ 4jT?ھCл_=qqqao@J~\ ZѨI}<7л;DfDiѺ)Na֝ԭ_;[}[nJ](Z<.=:Քo;╱/ҩ[;W$м=tvlu8r`Pf IDATׂx{*A  cokշ%]=ݯ;ήLzS"u?rvfs:&N9?_nd֝<;r}7}~rM7fm "W +~ZX͗d6މ#E|2<r,ADXwŷB{ R=Hl;̠=qrqlo&!TH6N9n6Y3sKVr9F~2޷>f}ϔF\ a٢ Og0yackiEDDDDDD׉ciulXCšG>˗_̫,]#vv& o `e`kώtّd8A3m ≾ÇOĿJEu:y`jld@EjלW9w^^M9{PZ5҅@=D@.H3-i &a4+b3rs,Þ3>ՙSgѭ]_9㾼xLxF#zuf}n1-Yd$%Tv{ }yBӼ^{;JMMk},uko߽|/kܶٙ #b^ ĉXXA^>?;4hCiߒԨ[tpgȞ ~5gӸU p'o6d2X,7`;ر~6>g6W/?ҦpvqHԭ_gG!.6'3HprrL{[ȭ飝]lx.__RSӘ?7WUF~W);ݧۧYH!jԪʆ[8r(}] ;;kVNYkմٙMN,q)#P|!0TޥKq,<7}8?_LѢiڲI׬JJJ*c^zq_ns'ΰ889S w-֖%q1h9R).. .ѩ[<؛41>2G[͛C=tFOj`?_ˣ_h2bkg}3ϩ?~\\̩ ^K9h7}^rsMU>v :i D`w1X" Ҩewx-9Yپa/}uyrgN3mv{4íѹKZ3#:bٶjN;ɬ_w> )@bEغOu4y(jamqp>Zam[9uV3]˶Yp)!addd|J%Z攋jGwkfz*53ȳaf|mbƾ=o$<,޳.p%~m _'Pa֮[ .fzظv3|eK\l]aÚ}ERbDE ##?6mc9OwVXC+$''g{zزq;&&&Z`e?{qégH 99s}B +nI['>^,W/]!"<{YuJpyvGxXxV F-Z@Lt Gc?(_K9ö]n7}~r&EDn N"¢FngIu{hչ1nwf3О̑43ACBlLiש)YGy AtF&%&۳8vഥN ;?6rusۯgMDE`ߞ̚1gg*ZC;s||c;9s&~.IpP0]KRDDDDDDDJU}9pb;>E<- =pM5dYԩW"E [b0lƦ-ov}G'GMxŋUQsz:ԯd ,^<vCVڵIS ގ5JФy#bsUWx03yf9__JLt ^}d/{m9ƻhhdǶݴiߒ yLl&S? G{noKWY~2}>|9ex-kYiߒ}{ΘpquU?dY 0鳉|X;F-e+-emcc`ۯ'*=ۿ`O !>)|IB|gܩVKL&Ziʠa-|8SFfYdzUOxx31"""""""P2rZx!2e$#)Mμތkk<܈<;wQݣ5)"򸋏Kd2e =*Q7pvqJ{ٜﵼ{3֩<&n^Q>zFMr{)Xr`5kՄf& kk#  coog =Հ!}~(Q5)"8rtrG˿=adN։G &xlیR^%""""""""""""9ȯWxI """"""""""" %""""""""""""_""""""""""""o(%""""""""""""_""""""""""""o(%""""""""""""_""""""""""""o(%""""""""""""_""""""""""""o(%""""""""""""_"ĄD5ȿFM tC^fO_Sp*c5^NO{\\<}:d7S} d+iи.#FVMң#;bܫW/WXl.. DDAٲf88S߇F-`mc}!Aٺv7a!LFJEdžMFRSҘ?c fsz|Qv>Uf}9{;ƿ;&˾d^>4s{wM$''3oB8B@ !Yk!|>.vp"""""""z %%%GrJOܱ̍{3LILH!!늕*01(YR^%i֪ ))=}zB/yh.ssRJV(@DS9|Efn^U*2{}#3OѸi+KF JWÿrk\\-b3ѵGGz?#{>qI|B+TϨ/PXat>d]6\ toq-y9XKhj֩Í;1h{xxum_yoElX딯ˈQ[ f!/Q%0/Q3}Lv&ƿ; Ͽȡc,UNӦC<~V횳Qao~F]xf`(_#yq26՛dYʾxo}k` W{.ԪSDutoKMMϿaݘL4jRg`Y |1w&<9هӱ3NDʖUƖmJ~$$:%fI M kk g-'991q4~csLZ~{UcfGގP\+1Q[÷AbB"/^al';~v=}`4f x.9BC3?IBCMDDDDDDDZZ+", fshY"y=C{2al"b31b0:w39wљ] f>y-QʫnɌi0/Sf~Bᢅy $'gfv5@Dx$/I&1m66彿 k3d@._ d,~ӭW'}awcok 1msfc'y d¸0өU:|^fJU~:1/~e4hR7C9WKXif.]$&:빸x7\͟Ovu؛bŋƆOOML4}{|^8Y_ΥkN {~0[6m8{}28n#ΕW勁.Tͅj5o`o׬B\\~YqNȒ@̘;d2շS_ _\x|-y&|8[[[7mYr}zF߁pwwc¸IQha֬ZO?IתFru\+]Mhڲ KOx)y1 }jK~^κ7RnMK+laz 4+1\p[R^%qqqao`qm^g30U,ǹ3m*js?ڳoO@>óuԦuΜ<~&\''GvlO ~/m;ԉ3CSnbןRZ% S>RXa^zm'MxLK}a>^ty*aO9y;qvqHsyRvО;[{3Ԕ4nDW etך**XnX#C#ٳ0Fő4+.P/C#ۤ:Jf9n6޳N$%%r-Mq1^{ ޜ8VS+ׂBXh}h22L!ajCyh*ux=]5 b^\;W^9[ʷ-^7/Ywbb%M*3 lZ%K9uնZͪ\| ;?S*cJMM̩s*ɢXL&.Ox.ų/ f/>U^^?{}Z(ɂ%s=1[Ǐё_Ͳ-*2K.?Pym'ҥprrij-V+h_8u,fs:DFD{3,T[]}"5T{.dyĪԬ8.'1!t:j~ב_GО4oHJLf˚]J;'GթH^)<״&aY_xaOM88`0pt!2@Og>jԪ=mef'n7dd6YF)ǔ7*"W-ZiJ"w\wјw[Sg=wiҲ 6xnbm|Xng!d X=..]f9{̾)DD VСWsO 5Pڷ$5VkR߇-jJ1-)ɩ\5%u[ ܈%5wps`mmU7`;ر~6>g6W/kf |P6a-.] 19 ~nX&"""""""%_*_ً{u_Iy.h"\82G^?{!|}09x,[Z |ŋYwIOOTIi;;kVNi\cܗ k61m ƾj(Y[ LIIfҥGi|+puZhĄ;5oKWڸO+\ WHJJ 9y-he+++2n=DՇS/շ [ߢy-w,wؿ3*4i21@k,T= jN;ɬ_w> )@bEz50EDDDDDDDr+_zrh+~w0Vy.xbeٜ>qO39KJLzX8fs:7c;k~4N{3e665h\_~ZΉcWSg{3iSSSD~m-6g¥%+iЖk\Cel^5Ǽdm>U`T?G||xxj/]!99*ol6~=O`҄x\T^@Dx$WobߞSҫ;3ܼkܞ;;6dy'¤ ձ?!a| ~v zu] ֧? ,8Ъsc<\,f3殴URȡ='HINfto_溌>ؽ '%f<'e(Wꖔgo؁Ӗ:%'b ܃JIIıS8vHR-3P_~\Ɩ [bˆ,a յsEfNďсR%  &== KkJy8jW®L ?YՋk|)̓C[Qo0idF {wܙMY{y 懴i ݯ; 0 IDATϲS@lߺ/|ѹG2XYYen`m`5tӄ\ aQX[hܴ!e|Xf4-ٱm7 eNԴe9@߮Oc4ȸ Sx~qQ|<}R䳏Sq1Yn2v[$''w^DbK}m^>і=:RP,둼Ƌ̜>a[ЮSk^yE>ys~~1oTG=gX|}c-_];F/W?_(UN]۱{羻5O1d-Z7W l2bN {o}D=(]ƛ>ygRr@=80S?_L'#f|ʗf9s".nm\d֯o?o`P z'ήN܈tU7j+Ҧwnllkkll`ccn6V/BjJ*ŽdY0Ν9ψl)Ra#F=CZZ?N\lNNЊ_nO1jee޷rJ<Owƿ;FMDDDDDDDUE"e*Sn6L1amm Z(} ۅ3-?KT \\=ƾ6nyɭm1L2TdKpp{_^LLt w]*.6{,VFT ,)ϱfХM`^C=qq`e>DDCc7ams{%`2M7ta6|_{}u:uLDڭ6jb8r#F ###LB)cvOngz߻i9?_]`0<Üm3rtQD^'_"""""""_a4tk$666˧'e͕jנ6%J=/"""""""""""0f3EDDD4I)%"""""""""""3 DDDDDDDDDDDD$PKDDDDDDDDDDDD DDDDDDDDDDDD$PKDDDDDDDDDDDD DDDDDDDDDDDD$PKDDDDDDDDDDDD DDDDDDDDDDDD$PKDDDDDDDDDDDD 5Avl7p9<<ݩ\fhު ]ztle F Y&jD\RB2[ڕPdee[hle]̚edHg:gveoK*mRn̜6NL:5xffKɤ Ļ 99yrf b_ wSW-\DמhӾ%7cnq?"Ӿ11$'dMBBQDD1wh 6[[bcIKM:2KSһe]stv*7n}^jկls#ųW0 .Zn<;p$jVW#::[͒6==K/c`oپrj\ط_e_Rʫ$Z5!%%ǼOVO(%"""""""MHMMeW ϲy릴m҅UРq]>!ByV\O]3s؍h|m"˻$O?uղAg/Rr  LtnD߈c݌g\Hh2Μ:KR%hll2o!ddd`euM#11ۍUX_^K_Pl& yҧuFyhZ>"G_rY.Mȵ )DqusZͪ؛5<=ۅxxk#2Xz.U:Sg~Lx DGp-(ypYhD):)"""OJ Y8w3 8u< f.cqtXLi۽i wt{ ήNΦU;ݰ*;4đhWӡgs +W בFh׽{6?9AR%Gib~ Lv,eԫ-;8t;NZl\3nlP>RRS>=)899Ҷc+KێDxUyhծ9}6  k6SropٺOK 0cT\ .P 7,U77wľ?0_ݰ*Y25U&*"ZS߇ZAFRϋb% Sο4# ¿zM.ZmSm117#0PVZl dd3ᛄ&"""""""Mއd;#=uαÀLq鎗wI/_ѓd%wN5]\NXbRo,uYxZֹ_#99(vlw_, `k-X"w_;{N.*.DGPNE6KT_my{wOaiܬaQF&Ll2tV]{vkώ$%% ֟igO>|"U*Ӊȯ(7틊A9/LoEMvvfRS'U˿ XϒڠS$""fmcM ȏMaQp0B ޴ؐdfpg*@ 7`;ر~6>g6W/?Ҷزqw鿌ڿ w%"˾"E ӳo,Ҹq#+PlıZ{wpBe+egWZnw`goG-x?/TVْW vlEu)Z(.9ݲm3{v#~mN=+Ѷc+5ȿ$4:iif(aٛppő2~^u?fDGd/(Pf|r>TɞͿ͉2 uw0Q«( qDEd.OIN%.6! *cHJLUTl&!> >6:wغo:cI֏uʃLe xP;)ÿ^GG'\A4KDDDDO݋F俿5Ǟ ~H ~H ~H ~H ~H ~H ~H ~HV7Wb:""""{QNjlԤ""""i/uѵ."kQӴ""""""""""""o(%""""""""""""_""""""""""""o(%""""""ѵ."ED\I}"7I9\W&s+"ԔT222wHF޿wZ3ͤzѩ(]IJJ tkC?,iN9o6bkў>;隫"rwkna /D0-XT:iIiߒY{ͫw`go֖ރ;末"c& h2p%~]G'{ЪscX." 2uA{]Sq7Y8k9>JѲS#KߗlK *Lק`0xt [dOz„ os#7gXy,=.BRE7 NΎl^:YgddetӒv=z-!1ܛoo6A&56`4RK!I5:jgA7,ےJINa_4MZ%*JQQ5*ġt 'gLJu gW')}{*ve4?b2)T]I),}[?=4KDDDDxûceeE}&㓰wFd ZZyW.ވ`͘xLvF˔+$%&ӭ< #>v++(YMZɐ2`kᔯTY4w%436DGg!wW*Æ߶7?p$&$QaUN9͘8hS Uy/Hu2qvqXsR0ήNyXj8:QxACpqsHOLd6s{]u/\.Xԓ8˾Ԕ4~v%-:6p}A׺qk`mM'[ATD%mpʔ/mfȷbi‚éT|4-ߊ訛(H6knFQA.":&eh޾ i=k)i nz[)%""""$%&|.$%&[_IɸyÅ捏KvO_MTD4wОzv=s B^!o;uػ8㯳;3S%B"E(""m(RDBd_oc2cfΙsa2/|<<̹?u9\M@EJ`_ΤV9z$^֒v/1%DDDDDDFp}0wWo}7{[wj?Öa(T,B!I[fܿڂkgɼU:3tʲL`˺Yf23^˅h W@R5&EG~\Iu1jPxzyMusK~ɼU<ݷ=Fx1>KŪe4EG9+it+WbZJe6;9oXƔdva܌rd OrExܳ7E=>J\;,^ޞ/HdU.LCsw%VFuϟ#.Ԁ/iHKDpH)ɩ\86]Gvcopb4\[<ߖ+Ɛ Mp/+ `PZ<րo_PdffVsNjL-Gu?0kY]')iz{u9oXуtj2ҭ8=FM(R 6k&?]Iq'2b"riLZs"{%.S1sǾ%"""""""""""" %DDDDDDDDDDDD価䗈7n~XZ@DD3Vk!r/ =ݪ@]c0cDD>sW_ DDDD}{ܣgiCo(%"""""""""""" %DDDDDDDDDDDD価䗈7^ХKlm bIOOW }ˋԮ[ooDDDDDDDDS3V.^ΙSgtΜbdg( """"""""wJ~m۲ԔT礦}NBDDDDDDDSɯ3""""B""""""""wΨJ~WV;tO%TZEDDDs||V!"""""""r䗏Z :"""r"jODDDDDDDD yxӸYCeF@DDDDDDDDDDDDJ~}C/o(%"""""""""""" ,<5f {ʽǓKi @  IDAT5^2m WjP|jǝ<Ma ܓrV DDDDD=_""""""""""""r0+t:y'2\a0d)""""""""""""Nf$%1tVݐÂ@_,`""""| co?&t:q:\K"%)o;J%'CP `""""""""""""B׹XlL|}rGZm$%FrR6D侥$b"~X,#b">>YꙒ`G=O v[rR2~[՝:yc'_ v o\ES[y-q\ddd`X1@æ1L""""""""""d7Tʌ/5@xPB‚8eC‚/hjq1.nmޑEd=%96:0wYܫ1dzߟ:Gfrٷŧ_Cd2kщnퟥukw;>EWkߜ | {xc xi1c>,ǽУ{ۍʼ֤PZs8~qp8|:q-FөSܾ;>k~Y c WpH7hQ-7x> YӝUiPkƍzkս}԰̜6sg/2DDDDDDDDDDe00Zn>q➟Frk] mo?VFpt )Ii=Nq8nw:p ɵqätm׎=T_yx&&SDQ../RVUʖ/g_}ny_/U!A,Yɸ3~?y. <ʬym?ܼa+l̛a5O-ʔ-ɬ/+,>s~]\AM<bxyy1x8#MJh{:ȡcvwt}۷Δ)Z06{#g&O3 '˰Y-&>߂Cω &#U_hIq:$a%'f9hT0F̞k8˞,Ԏh-bWro}_Ww'nc<<,d)V>yO0vdff<۵7|Nmw~{S'ϰ{>:΢KXj|i^HGҮeg|}emۼgE6yvZgHfxRS] 'ZL2cOԶ;\>vEty'VF}M|~Y{KL0ѯϐtGm;g<kљm{j]k'ML4 Õ5|,_ѩmw~^xzyRh! ̆u;k>|4@;}ۋ':eν8N.剟fN~ݬLp-ym+-%tO[6mJxj%}y$'p`$7lc|dpk˦x   ,O(moŎm;{' tْmP-A:NFF^An}r,CDDDDDDDDDD9 ?=>bd$]b5x{.xc0eMmR',~vIi\/>e'}*hoX{ +7GlO~9N2]˳=> ,O_*/ k햖>XJ/LyJ~5Ԩ] BE P~mrS~m|}} NZ*\DfE4o??_&})"g)fĨwF`27}9oGJ86`[p.[7mg OP<==Yryaawػ{?OEƻ9 ?N3wڱܘ߅SgK7f/]!chҼ!} zШi=*=XoV-^3–i҃<|01o˓;@>0%6&ֽ_̅X||0 \hgӶyGLOԪSý\{mvrw Dqzԭ_Zukл+lۼb%ȰrR:&Sso[v#F_d2qP$sv:;WGnު >ĄD.rt}3iilݼPԨUefú͔,JȄGe/1)u&)O>NJeݿoZMQQ]{' \1?_z @}XGhX(6J+@ff&a!ڹ'N/Oe+NGS|og/(V(o 6[&O'~NӖW@t"™;k>_~>h2ްwfúM̘uظ~ եUna?q,1=q;rv׶?%)-;.W @ϗ'2DDDDDDDDDDWØ=Z ўaÚ|c?oO9@H_=ts)>"yH8qoB 6;!eSjYCo׋%>!4}̏=P]-~b%Jf=!X(S1w-fu;Wr˓|y3E\3s @ˋ勩ܶ%JSn E 33u\IA_M7_v.ۋı?p8kfN +'Or[Tl#9vxחXEqlLӓ!9{8r(N'0( =7@hԬ>tֈq|,Ƽ&= ooo3g1Mc*V.&pSlް\Gm6+ae1Zl9k1]粘df`WlDDDDDDDDDD\QM+z7ȏۨzG4tB`͛scfsJHhrcxAE\"?4|Bh8bƖAbbZ)cn[gح!Dm9L3w5Ì͚jKnǕ_kf]x9At+لv̲$AEW}:ukϣZe6+F?%kn]" ˶nyByj%~] b!(XvZc-4 Je΢/o9l$&\gŋ l#1jfpdώ=A e rOɃ*ljS$%&u6ht TT-uHyusw+ޥ?,'whnj^^d6a2p:o\frR Nc\}+ʬ/!==  /.'qOl.C'{RR9;l&(WL;6(U퇵=h1a49Z0z[$mgw{Vvli>T˶g|c^wPr)?@rTi&q).Pg˫]ylV)ͮNoiѪ ;̩(Zjs:رw8iF[7m۶Br_ v_V|iNG{^~~99T;'p(Ƴ PQM<˗l2Yѻ`6z#ĄD>7 c|;; (Y Νy%<<=xShܬ~ۂnۋYY./ ΝReJxVg+<|3~j֩h$4O+19sqڹnvk""""""""""r[(Myʵ!b!Loq:vf烋=F&9mi 'qg BP0r+/ 'r ) 윾|UX+f~Frb*~>iFV4w7 ޕe,Whdk/7}x{(5s]:>呶-_е̡uVTYws6I*\b%0wׇ=KP@rҫ̙9wL O0^]'kXL! U*n̘6vnӨi}jXnSq]6DGCjU‹~tR σrf jR^D9KzzZ6uVf|{(?„w'iQV5^[g=|9wj:>ѹN{p턄wܰXfw(P(?m?ϿJFOO5kѺ]+F Gf^3~o6ѫQy{@r }UƼ1'Zu`0Ҹy,4[̘Y2DDDDDDDDDD-ri m8N6Ap|4x <̴z)X1 <صkGÑiwBGFwkV'jp<p~1/k0 8lv6ti/?ve[7iIoڻKtލOP|$>64Fy"BVbq8x3nռ zt ÉOO$'d^.c1ǥK闓,lNFz>>W ȶKKfSyeĥ 5%/5Ê+))xyym*""r9y,Cf)""""""r3[ZhtmoĖ]n/W@d[Y39{:?|8 fHNH%/\g?>&1$(c2n9u1&n̛7ו2j}or*KDDDDDDDDDDNJ _@ވ.%FJr;aw +&RXa#jP܁U`0/ɉ`wa@_,];$!""""""""""""w_wb&8$ʲkc7^'pO{L 9@Jh2f)""""""""""""?eDDDDDDDDDDDD価䗈7 Xma!oD)IilwXE6-ԃgt8:Ӊk?_/~S=;8qZm!Nb0}y@Cqmtr񿶎Nq<ʟ\ɯ_V#&טo/U۲6%i&%$s<w"f)e-"vCрhyT_055a~Z11/G7ut?`0 =pԬSA/A}YD9Fy3/Y!ߠH<ߧ'<߽/y`Ϯ}>y|y~ǜ:GxD^V-  G+ҕ̜6]gْԬ]OKVE;{r0`H_ιl̟]lҼ2/ `̝5ؘ8լB}!&44wa?INLZ ܗ@[jGO0ad$8w0]{vU'Lhq"|g^*-;̤STTn=;gbb?vCPpڴc0~2Qg)^(\Ip`^y7p]֮^Ogr -˯D er_+г ;{3gԶ;RlI֯ zQv1c8yɟ^yffrFZUYz=˔/Yr'֣ޘLFa|K\ ~}78yeK1`h_"aWg|CjJ* ԣ+a0Y9FRbOˠGHrR2O(8u_tqZ>ڌUW5uF#Og2yڇ2~ڱ" Bg)[4b?z%2`H_/kپu'F\KV>EΘ=q.<7_̝g[@ݖÞ'O  /XZJ/FVu8~s>UKm>Wܷ3]9GffI+=d=HGѭgJINa_r:p)53)J-ɾ8 oı-W4F#1!4<WGr)!1R IDATK|}?ɯ\KmX4 Rټa+GFcwQaوK L41&vn;o}@@`]zt# 3v1~С{!_t~=*T*!ضy|Dh|{(TQϾt47zAރi)QǑoH¼7fgNG̫}s!cͰ1lH@ȋ`NZTT>K޸yc*\O>sضy۷N:G".6QQXax{5l,@p>:kCPP uբP~8͂Y:;΄7_eʌݽݗcTTۋRx6ӽMi+pܱ3'1L4CPC:ys7E3x1x4.qR:_U*2m,@BB^p_GƙSQ8NFFat~ogWd~^Uʐ|mk#x͓ͨTuCPe ˏ$59->FLeyIo`/9y,@B 3&q37\8{};+8$K_u|FW/{|]ErRSHZ>Ҍad#v+e ϗիQbYVZGD|TCۻ{?q5n8«}r׬’E8s:-˶;p:;Ukp}>W JtfNO7׽3 p~0M ڻ,R_/h$B,E45SܳkߚMmuY nzܾc|l޸~x xiu7%y6*WHftx'Le慾`XxOO:~xG\X<,.Wkt\ {Ȱs04DԙhrH@n\RlI:uk vz5ңlrլBUHMIqxz(ۚ t|qBBsMe6J-ɰ mʖ/Mf llްygGG2w|"tiw_O^bow(Q8(A%nZ/?-ĩQlZZ nj߬ax|xC̹8zƑ}Y`5/;)X4F(GF{}2M+F|l" L^ƕW/RT1,vحаi}֬ZGbtG_~5S4iȎ;ٲa{ueپe'Ҿsl^. 8R|zt4^1RD5K̩(|ɓ7 иYE 33uup},/Ř7ޥwW mT{תJ =csf~KU;wofߞ̞ F+ص_- .\{1,c9^>(^^DȗeQ@)ZϸY_ b|>mg+Vqᢅ9|0Ӊ`oQ^rJa{IMMYR1_{7r<86[&jEv\Y+DZfd(z-왙XyMV+˲\JĜ|JINJhW߼p?˦G%i"w4&.D>1hZ6ݎd"YJ*~dĜ!33B `yI|\K~},IаP([ K-#:,M[4bL< {#b;0$\t-AIKM%55 __bλ:+8K$'8xM7xbڱi?uO1` @> )Ȃ_̟S&MhԨ] ϰ Lto.SfLi,.&2wxmKޟ4];2Qk,JbB"ăU+Ɉp'|1b}*.6JyV>K|F0e4|-d68_NnApxzzy(Wr\.Rxk^ ]d1L&^~㖮w7rl/̥Ks텷gDG=osLc}􊤤dĄ$nds/ ~E9eI"4=:>&0(.w|}} 1p=0( cܿgddHPPmYpy߳L_[uDЁ#$&&ɯOD|,ZQ,׵?zMPdQfϘǀ!} 'L_zzzy6Οz=Nv@JWumrFOb|!an˟@XO Ájc_dظ57ӣY3[L<67[L8N'(I|!uSr {|O/ׇ)Ii7d2]!U*e Xv/T||niVMJ bFRٶyYb-"kU%1ʔ+E j=T(V(ty'|TmyIEU*a;8²%+TTRbϮ} u? M7jo~bW^iX<5ָ\tV,/ %)1[jcDZfX)T V/qotysc=wL lp8 lܖy7<\Ǿ?;\Mtpa:!L&#E(\ӭ/_D>dѷ<|o߭QWRulݴ[wRR[b~m\  ."1~OH 9z80¿%0(r0o9uL^HrR +SRY9vͲ?bͰePr֣|Ųvq,_b fϘGJJ*dT(BHhn*>Ps>OW`аP?DzzFU(C|l{KWaPxќ۱Y6ݎ?-gU+1lk b#ycԨ]fj^lW ̔زqVѯKN3a4Z[nڬe=^\JKcq\8Crsݥ?,gkp8+QX6/1K]Bصc9KDDDDDDO‰9ǹ(ח*O&%)a<ۿ3OmϓX,fЎ .&rDMϿ{V.YO ǛB%WbY&l޸gڍ;[#ҳiFtk f>u%K9ud}%~̳]{Ged2rx+Qg<Ҷm/r;9Ya0z7l>'.&#tR\)1q.5jWy:ULγ]{@{Q+a󵏛l̙Sg)W .Sc?[#T^ uy6l|,nOwf䰷Ǖ8J.kLHMM|2<ѩYb1!~[vߗ5.Ey5o 8w.}ޝgfR 7f}<&yluhު ~hnH8fgWG=uٚx6ZM||5׏ 8gl64]\l. { &:gt7 Lɶ}L4P81!J]ƩbbbqqqN] e2Suزkj1&fs>Mete%99фsINNdJ>[cbb'w^%prr|6$%Pߏ]O9xbwFI))fs7>>`Ă%'bcpquIw>HϛKj)B!DF5 xJ ;foԏ" vٟ_ \Ʉݺ#.nNO3MEǣOѪ\_3\W(_TfSfA .vvvbݰzQYq|`ēh_hڼQf5:~OzU 8+'|O|\Q5`cYNLǞIB!B^B,jӼޞDEĢM@` X=+ή5t9x9)xŌ?"ؿS@bkXO:e9tx8Bd9\;i3z|֏|&]_qFBA%`ˆ\3'Wڎ2oBF#-4C4hTβ{wgߏ*o3`X?t؇roe~$''3Ѹ1z02w|R=~NEt sgZ@ѣ[2meMx:77>p AX֬&u?P(*еGg}ѱeʕBn㦼[mfL^@]hؤ/^e܈|5/UzQJ/ {r'oҮg,]vlǧ̞18ؐkWѷ+..8kʎ2m[PrxNg([tY?bbͷ IDATbm-Z}ͻh#V/[3l8Ÿ1b4m A ej5h^?{!{vՅ6|L0;&2"w@.Sܶ;%Jg 7wV*{hڹw>þ͂?R)h5h4[kII1sj 1h◹[e>f fΜρP(4П{~Bȴ{ww> ` uڞa_l6s>6B!B!x:FrY\ݜ1cN1gGN^``_VC$RX&*"/!s%isap'VCgWL>y]z|{eqz}>2nN$;{$2")cbisvmC`@p'6sޞ#!..1GPķ0'-ku&\]]hШ.<}mI'up DRȈH&NL}>OmGf.)W L0ZlٰC|jNZ}H||C CIHH`q$&$_s^8;h01b8BCˆ$v(u, CtT E| W$+Z*(J,6lۜe Wil_7obˆ-_Yh5#zj8:3u bc!&:&ͶOSZZqrrMGU)\Ȉ4q| 1 j1DGpIx q+MА;c:{rrc%4П1&1@O?U+ξllZKkO͸ZHPn3_:ac1o._ @ȭP6B~Ր>;{~GE:z# Gmɇ4mј~غq;pBTL~YϺ(W v%>>if2`x?l^Jd+X|=G0flXdهA׮S'>!>>wB!B!DΙfjӦy-&*?qڙnr3 Y(Y!7<,`u 2<6zfMXȮpUM[OӖs3z16Zϊ,weY_9#4@R໙~X(֛ҙڹmԩ_]dXFgq4!#cw2Lg`ٙ=3&|՝:kRN5u.u?Nؘّ8oȡc\F(^җ1 4$OkwcG'G-\Eе넅dʠqQJ%BRqrd2擏caOI>V?l?zȈ(3ˡjk._JB|z}<¿iլe눎ɲ$ZRV0oU*G?`9is3ܮ;u^W8#ՠlץRe7NOw K>nsi~ʼ 3moً;f ֗={qL&[Lzz\#_~y=`x?| IF[mz˗ݸt aիw`Oßmgnԯ_GOWh{=iؤ%JD88վSk6o|ܕؘ8\\ٹ}F̲ޯYO:uh4bbb)Y'_I3[o̤T,099:GZ>RF!B!9wIr_av m~j6xg̗YVp br.~m8zQQUbr!d0oų~7np:74 秖n]^Xzʠ !2%tZE^D`4HI~tQ1̙1eˆܽ{Ý~{Q/JfˮH PBʸ\l/ V.YÕKr@ nG(SOo](R Ӵpl?0:4fQdX0̈́çH!!GhҽA`W=Đd/Jeǟ0[Ś.%%\'G1ѱ1:PKwxcEyӼ?ax:۾tn.(X$?sR(WM+wcX_0~;Zd٢,_M% ~*fl6s'$1єnZijT*Wm/7W67gtsbݽO ^,[btT Gˇ-)>uQCƧ)JޚE=Ŧnh+gX'[ x̏lfh F֮=>X k8 ur{Ё?mQDQbc1*U~ GG"#ʓN~ߏ/VJeKe`l5GGԶEݔEAVex8>E?ʷxQVmZtqu!&a, qIII!&&Lɍ?.@2vńte"#"ٱe7CPPׇвq{޹Ghֲ)רbFy~4YkҬkWJA~ąYt-~D!f3{wO%Sݰ{!a͖,σXt-II9Px_!B!"#tnmѨiy3=܈ 0[= gR(m̀or qߍ"[wUTJ5K5z "K29r9M .&gWL:kbc4KqðYh5+v\<) jöXj0s~>ukXOGf+%k #O^/ZFf&45yX`% Viکhm 'ԷxQªk6i&yzоDG%4!PFΜ>ϴ3Xf>G|ɺhZ%|Y[ `Ԅa̝5_ с}RB, M7b?4l\6*Cێعm7G[Ӱq=n߼>ÉLRl >)ccd<ǿij׫F+J BajGY?3r8ޯ=):wHE#|RL>,1Nח((]֏\_ Fd,eKRԷm-]ڳp~ۺ(> yzJ_{Yzmގ7Sgp2RRR.ƠT*ӠWҮq.[jZhԶv* 3ptr`͖eʃum i/JNs5lRӾEg,3~elGՙ&Ѫ'$''S靷h,PP|?eWmDf/elB!Bs88ڧVNJÚY+]g[t&waD'{}T5 RHJ4Pv%CdR*(VT˙8jWABdIad  jX AR g0M lꈻ3nb-4mޘ%}}3O>J=hӡe%&$T)KQ_=L8\\l-*Drij6U+[dz}Oގ+XR5!w#B!x T/`J'2a /I{<^$3#BA.W IFLdn7&ѧhp}l W.7j:MىߎzJzWNEgfBk@=jhФ^;2Z;s郣,cʪR_+]֏~F!B! |=29X,X,"b3\Ypvu [wr;Yr6g=.> >){#w>:_g9ۅ)Z3\W^L/[X]R)P?w~c#x/!B![dxR2y3$X,Ldbm3F^ FB!B!oxRzy$kLPѨqa7fv  z|Q!B!B!2 `RL(B!B!B!O",B!B!B!!/!B!B!BƐB!?A_}B!B%B!B!B!B!B!B!o X,i"EBQ!B!B!u 7bdJF!ɄhzZ vuNh4j !B!B!^K7,-:R:ApzY,, QcYF5#n( !B!B!'טbNH8&c2N.9j^Dlt`\Fm5jG_Ь~k"qs4ӒȈ(sWg/]Oӽ6g|bcnt]pؤGiٹBмaϗ.\Wt2z̩sh֠5?J:r1[6k_GOdf鞟=cFP !B!0B)Ld,'ґBJf~0JMu3]kڤQTc'xhz{v>\^5,sr =cypvZӤ?^yrRcMl߶fy)@ɔs̜:_xQݽݪ)]&˶)Wϲ=RX~!))):vxI_\u.;łla h4jJ*%&$OTf}\KOw*s'eŸ\8~oƏm{ףV/6Zң?\2h=u4ǏV_B!B|&f_hd4ۋ/ ذ3_DTߓ֟5E6Olt$~4)))X&P(HL0Bf5^x!3'T\L<.5A 8h?Z'Gk Ęx=uC̟Dw5'2s[Zgnxyy2s^Ý]Ӵ?=>G e~ .Qү8#!’_VevtnГwޫdm7{!Fms`!4KO[gE$'PR9 Owv VѪ} 6ٌhçmM3:C(xȡ̝9ogMOA Q̟(M*WCݔ8u4 'SAlP\)^ V(>nʻUf t݅MqUƍWCRwڡ߫D/;9&}N3A7X`?-M{]Y|=#x ;;-:[e8 r4nn8,GtJJ ƽt*Vmd両,of˜_"#=`'7nnnDE>5gNϒ_VgZδbi$iܟ] a\qgoc-I3}˚6/-~] WW4˿'d_bbI7q'd 2"cb6Ӵy#~߹ Sۑ?f邕|Т1_ ˉli"# ʊkV*JkW<V(;صwuʔĻ`~ԫIz5ɛ7a!wlM-\`Ȩ+^Icqr@ϙ7nevʖ/MB,_CSV5:q11zBf{w里ӃRc>E R`0fkp"N\2 ޟO2u j5Mev8E9y_9 IDAT>jaJŸ)#([7e*[uVV#W+B! ? k0jxL&*;~ovl͕K4kІA}gTdoinj Mtmܶy' ܦزaꆽ(*]RȧLKTo˰/Gq3gOg ڇqߌ|޳3uԤB<ޣzj\p71Hj֭n EEF#|ܕ+M[.sΤ;oqiu셼St,B!B')_6Szp=&K&w^w^Yn\n[7Bm(Uޗ7ɻ5of q Dޏfu,i#Ggǯ9 '/&-&80h=Kg7KY8k e_L2ͶYYqLF[~݆#%ҦI1x?hȈݸ g<؇-%[=yDS~Mj֩FN}fWϷf8'gGbcQ#p xI_LА0N?ߍ%w>YpA׮z)vV?liʼ eFv86Kɑ+S2TXs)ܸ~|ҁ31j0vvxOn1̚6' Gtv޻{J1/ʴ&q- tZ>\Ʉb`zz}9kῨӠ&5V{ mls{`0h4sܺy[PłB@J~|jׂ76q&.N4lRS'Nz{磨oa~0őCXgyB!B!^![V`0P(0Mh+e+׃_utP}b1[|⥋PrW^\u. ͠P@.?w"=Wۯ5PQo6yA! + " ~tD8u4L`ݶ E+e,"#(PB +sI3ً\+n]E3}ON?i-`O~okpH¶6 OӎE|$.XR*INN#MEu/2"*[@ӧβsV^3GNAYprr˘f VhO46O룻1&:W7g~f5~\'99լ3[ OBt)CDХ{ -0]\\3;kܰ$b-4!Af$.־z2m?|lπ jT{)V(Rϳz.5M|Ҳ vс y(VY~Y>ǶQh\#\puF.XOBL6JIXA}P:zpS7ϛn_&gP~M\SfΝ9˛B!BTU8.c˶dwvX` 'AH.i}4s`>1:7[pO/wT*Ke߰X,x˞hԔ*_BEe+7f;!ᘌtRR 3ߎD ٜL@[ s E@*]!MYR,nn5ݽO ea|7{ OcԐiĦ)RliHmbS}4_VV.]))_zs[m3?i4#ãle22,|l_b%|9} VdR l6|jvZ#kWJ޷xQVmZB͠k)]sގJō7jRZcF^-f5i5(PI'cNZop7]ZRe;U))- Nb ܶTj* \\\~8.QQYs7g ;/>E}q=rz}jXXrl.X=?5#!)@OB!B%R0S-_9_=D?q? 'tu+E]ºi٩1A F|h4yteNwr`!E qIΞ>U5d癟[ET*ٹ}G, Ε{kߏȰ\vƯmF\@cѼeL7RU9{>''h4r[>o< =eVr:j׫ӠV?lke]3sY<<5e咵'_cKEH9Mjh4j 󞟱tJNӴS߻Nm뢛]wbohٸ=} P¯3WUi5u`֏1UlΞ>[ ?.l+]wbɂ)))mPu3et,sߴ@JB݃GFc9xxզ9Cz|3;L:]=vR/J]Xt-&$O^/ڗH6ը™6q7?6S)Mլ[N$%hظ.Z6`w?r Vd_0tWL= `6[(U$cSC}~6#lUiӡ%=XP)lۂ kT*1nNGT`6[>fjV[r369%O/O-ZZs1Z'gGըf\jU1wgNA Ϊu>i&ڧq#&vF(V4mR5!B!B<~e} ֨P* J OӖT*ɓϓ֝CΞĵ|e[sF}bl6m^f K欷=ߦrɤT*mӲb&t\BdBaX,/cY ~Cq}cX AR g2M lꈻ3nb-4mޘ%}}3O>J=hӡe%&$T)KQ_=L8:Je2HNNU9ڵߠ^8t'O+q5E߾ҶVOLBy>ifi%7C:OK@JJ FG:r2~9b5`0T*h_FSZ;AK_ ba<11 Za3LbBb}6uYOP!wO!B!^ł!ɈC{\fdS Z;Kk_R!M*Ŭ|l KpHfؿ <\1$1 ugG\ܜ:f4G:KU/\1?Br5]ZU.;AD4 6{+}Wvl6yvլD/yg~~T|t^<$d}WvkJ2ٟ{>=Cr|e*/!B!P(2 |^宦K_P^oO"b&KgWlX'vnMP4fjΗO5].>{M ޟ")djB!B!BkLPP(ps".&6ihj쵸h|=ӹҮSgr+{fUJĸ)Jީ}'B!B!Bd Fn|Q!B!B!^7ޔMzCd'XT)< !B!B!Bi$ "B!B!B!B!B!B!o I{(B!Ld!B!?@f~ !B!B!B7B!B!B!C_B!B!B!!k~ !B!B!Ojjut%B!B!B!" x[}후'&99є湔Q`l6g{_fĤ,I&L&cIbdN!B!B!9#iSv)G] b͖eoT9tg=~NٹmOK`ŏ1et~K0zp}ٰv ?..ȷfǓlR:z NN].ogN`Ylyz1v`ߞ?O`Oniݡ%wΔqߥPTL;@PR|)&|; Wtw3ǬțL&̘@բP*0jV`Ġq9tV^Tuxf#DIB PZ)P\[  ݊[q' qdžo|'O̽w̝L=ss7lؠXJV/_B6tGCF!B!B!I`ie)BAHKLF_k0?ӏ֩G8zV`\v?l_Jtt -ښ0m,ϟhcgҢMSјT;iױ {vg̅@u6ʒտ3gvZYfaO0r*O&~=uCSuԦoUq=̺xJ%z.]+E1_!B!BޭrP`iy{?rrKWHY.8ֱW+\\rd).nޮ׬&++xgDtD,I)=tE󖐚v9(֠}6̚2p|xsSt nn̚2{wpvqoҰ:Xɢ?|)Sc-{6Ff]8:9г_wʖԏX8o)rq_ahڪmEӮcZ} 0e2eGgZ6ًT*͝Uضe9?=Fſ,QTԮWo}+*2*+Qn |VgiQh!llT$&&qZ`~:wݚ<hڲ!#E6a6ǎ-{6gfpڿț/7QQ1DEFt &íwt2qϼcըULiZ5BPPͷ s+YksFP(ksY e{;0M8}N?G\-^8x_ObB"5kWG߮( "#"ɕ+'{ۧII$`goGTd\Hx^8/ kL/n e:A0B!B! z=6yzg9$_uk\|l"^i6|VƷaetZ=&.#oB?E]TD,qtzt:=1QD}}h&NoaS?<@`?IΟMkkk D||<#' #W^E0yVSϗ2wS8v$ui'%%(&`az{D&4$Mװ6vv̛kRBOX0wIZ`ąWo`VeOu6]UYr:E\w1O㇘?}VI 4ph=rA#)| {c0 }옿x%JclM7r Ƽ 9%+wg%v=_ulc36ۧ9/Q~[7PqLJex3py .[}.SNssN̛.ׯd:UjTͻL82T󭂝F~+}Iׯ{qz12L4f:=uQ0B!B!`RS4(C>ogF^&f(H[:ݾ[Vp`܍|9㖲׭|F_ƅSWܦ,_g20B|${`'hٷ+5kWZt+Ts-"g. ~]b *©g{im΅ӵ5a(s$mV.]˃ {VI?Q߁=RǷ}p"^󿶯9sfa`^BE2ظsߏgiڲG4o\&4$󖾶mڵX"w')cgQ/ܺFM뛿7 T*jKggBg߮ںAx̏k6?SDbie)CKzs~}غ9 eڏ}IINIm׶TRgwȔq3IHHu;ӛJO׭́_4浛iQݷ9_}Nh{G{.G!>xɢF4 ysSj* )ǩg1` WL>/LueF5YiB!B!B[j76-/a4p*^2~L`YOrr y q>tW,n>&*biв&[gt^ϒ(ZʛGOMʅ20B|$%޻/Ӷy;yPqksvY{6xzpE=WNkCijtțOy<=H;էHAU~5Y㿁+6RBz؝ܞ<xV÷Bhrʚ>#(%9{^3B20֦Uݻ@Ѓ`6caFIw\ə+9KVg8eu9Y?5e>g.dϓx}9Is5qEdڏ}yV݇6f`6\ٓelLAڈtya~?|S9{uY IDATm[d5y0nHT*%Om7jRd_̗3.pL_AY|=y(^S)VvvB!B!{g4ٶI)4mW7s/OtZ=ko딫Z\Ys#b)^ ]]"P(,F-nD<%Qy^TUNDO>A^1QFgyk=)ݞ=}NB^< jeYLt,~Lf>o"W.]g1ƥ+R2mP(2mE@!.nlL:8WJJ*QUR}p7alۼ ˡL s98@D9:HڵLЃ`_:4jVd2>/_FHJLb?|DwnK&~T</e>/d4 b%wZ- y\V9y^5{wgݪM)=7@/ZY|4G?ruJ)9WV;W&m6\%/!B!BFس("h۹)V/_Ȏ%$(S/pliYM[qf.Ipr] HT*NN }!>1%>'w x*G GV.[ǚQ*n1W[x&IŨQ*gδ dTwX㿁ppԖ{ZmAtޑKc!Tjgogoǀ04iѐʠj_W^=ƾ{pǡ[!Bb)Z0-^Y8v$wM6ڼ^4k՘3'Β- 'HөKBѭc/@OB0󱠀w~VgҘih:5664~0f.b֔h:a`/ϺUHO`=ܺ\fxÐQ;}]5u`!/"~FK_1L5ss;MZbg".ΔјӃf_4Ojd|ףSΤ_ӲгoWF@jJ*VV͜9y,2>-,T!B!B= [TJO@T{7ܸrѱG+j7u?VOb)Qw';Y]/R)/U+Jʗ/1+J*%vYm>$'Sr llep(O_(80 w&7~? iش> y(-gnnQXYY^z3*U) C7Z-:VlٿzRpÈ1C^IMMEaggaNCb惏&U`&?4ɒ˫bbk.μߕmF^;>Iɯ=)v !B!#Z ~NԔTԖL?oLVS޸2`BK?%3fu3;rUqMRKj5j<ޟaie*++,,,}! !O{\f kB!Bl=\`!>a,];F&~T{oƚ?Ozk?j$W^r!)B!B!BK$%SH[%JyZWgD!B!B!_$/!B!B!4YKR.B!B!B!XHK!B!B!B|4$%B!B!B!>B!$-O_!B!S _B!B!B!!/!B!B!BѐB!B!B!hB!B!B!;d'yKw %B!B!B!>"I H?Z-Q1o^MhHXeHHH|vccho-*2SpmsqS0(B!B!ĸЦw_GmNgFz|ۗEeZǞC෶@Ӻٴzf~mhے5[S&A~龾w̰zY?HNNs;|hn߼;$$$p70CE󖚏۠Ftw!C~?| غi;^o`jǝ[v_L:[ 3&EB!B!>ZVФjA#BB"F/g 淭!Bkw;22(<)FNwiď3rx X"7px_ i~XZYRAmV5o?0VnΖ6fy(S4=vsxd2* 0e%&&ӮRXGNtbI$T*t$%&1b܎歛aC\5e>W߶a?uME ~oSȧ $[l/^-ptrg-pR4 -Z7ԩKۯȴǏb%̘?r0v$ F#'c٢ٱ$J~V꜡?R|k3itPr9RRR4ͬǡ97k^LA9Vm"y$e+f[˔q3z:.t#5jUWwYF( Vw~Οb%p"t-GRacc J%&&qZ0B.(x`a5ϖXr# |٬#*%66g.YGFKV,,2>##9k|7o`z!!!)frUy{(Ṙ5p˞ҰVK $%%歛R)ɞÍkq|͗Y0֩F=PLEr`aiݾ%וB!B!#pgXZ1 ]\Yx$? ˑ=ietMiR:MLzf^L|~Z +STA7Rbh+^OL{(޻Xbtzb≉zZQL5@æ~y<'?6֌>xFNF>L?Ǧ5yVSϗ2wS8v$ui'%%(&`az{D#3J&< v.Q9x  V,YCf;^J7ޫ=3qƏJ`C5o@\l˽MgWW* gT^ ܼv QdQ g/2e̿=GNh42oLZm}BH} P(e &2" 50 lؾ0s\yph=rPxtۃ>֍;o\Z~5k=a_v;[%?+ èXKw7 @\L7,g2fB|$${`4{Eb|No|*}Rv5լL[n9j/SB\l]tμbr۝.=aoX9`uJ%J֭܄VE~ɸѼfZrиyZ\eK~tzz=< jK5)ɦlztBP`4ɞ G'G\~ֽ4jZ?C?vmK*qv1ey˃ZƧh!slnHM՚YZY+;|``̜4h4;mڷ=WN֭>?bï$''3w/L1&C֘Vlر ;{;5úU{gw>/O,O=b}:ײ\Ye)J"畗:'?Ev-qk6j֮ƹ)-B!B!Bjt?*|0 '7kR}Xxy iXy▕ay ۑő_gHz سcȓ? *A(O"p*&G@_z(_dy@y2qp7n\ܽ@V:~W?66ָ @y}H@zя<6Ÿy‘(]$sˊ%kؿ*Nz,ymT}%W.^eɚV W_yx\vܺ{eƝw 浩nݸCz5=e%JgUXZYзewmGqpL-xXZYR\8o 50gZYQ`<۽}.\6]BJh>a8Q< !&:`mm/KgaVLu?.\aXQ roy?ϓ׃&-RjF!B!Bڊ~Gjɓ[qEeaM(y4# % <;* Y,-*F#F+XdEKķae!>xqjo "Xpj)J.Jdlݸcg|`4KwQTT@V5{  I^#gv#+cʍԨU[7-XX\DbԨUYS3gTj_|) =}4L!N[S o~O:wȊ%ks\Vmi.Ya!/lи.vķnuؚݾځ1'Ѣ~[r{zS,-˴,h1J2~TW,T7 ,y9Lg䄡Ԫ[.=a L?9;2rxvT|Ւ"|2?s/`+Wn,@^p:GnwZgN%[6J)XQQ\rA#YXdXBeeOShZ5 1*m-Vm[(8mbqusei5i_}Ӗ ٳc?QQ4nހ92OJo {h䝮eo2z:`ۧup7oF\\<>E QLIy !B!0LNVc(Jz툃Iy߷0gt\xwܧ +Y_Y֚fnvmHINh)o "Yy#R%oJ d8f'&*Z%GCa4a|Mo4l}x6la,EȣPڷѺ]KNJFRbeen{ͨT<#' %>.B!B!B! ~ !B!B< IDAT!B%B!> ZN.B!B /!B!B!BѐB!B!B!hHK!B!B!B|4d/!B!B!Bsمd~ !B!B!BDrR2qnB|{h$:*N'< ~C Nz=6} cc޹?q !B!B!'M_BdbOSh\z~޿f~m8Gu]NÚ-SBi\t ^֏}.ӗ7`gdD?Bs/i+~݋hZfLKjhR jՙkWnE_jߩO߶nA`~ 3B~B!B!tZ-+d/!2a40`|oއ$&w=%럸F&k6Wڌp.\XZ3[\x2eKӣo׷_G'߀Jd%̟is'a^Κ-Kqvua1` 뷯-h jSͷyYrv׍Wo2h:vn'?B!B!Ve!-ݳѩ=9뱴4oث.YIJHfۺ?~`pɂ4hYR^ħM_R``0T*>h_#bIJL! YߩCY4o ZhלCRn wjì) ǧ7'~?Eʬ)w'gg- Эco,JGܼ~B>>f ٲg3hjoم=ulL8xhhѺ)GN=_~lJORb%?+΀a}qqufXVme64-nMMxpkSEL=<1iϞFPam,] wP/>q.\!GNlߒu10R`8::p"t-7כ?s!NYS-]{g-( 7sQ(LM;Q)5ŵ+7MNJf 5!}7]4o)[Rq] 3t|٬#t@^{^Q/YGO.kP|ߵjۜ% yp?re6l܎Եy/ȜOuo( llp`a5oV<cև%R!B!=S[7rtD,o&Afw|?)ɩ_[WQ3&O`zS@~DENGOL׊f⨩6c<=?ش7fI3r0yeY< 81m:||*sg,0/99cGN[:ϟF0zxRRRb¨i 6 x@DZ?^'Xd է^\8{E󖦝[ !a^*T*3{xX"<}}8@ᢅȕ۝QV5rNxsq#&sMG?r*1֤j'4cV]2v&k7`4ذ}%a<fvgOgܽ@:s(1_sg`Ԅa?-qhi',_h4ZF ϼ 1v0A&~8#ARa42n&;&{ͩgk;-uO~6Zl.Ԭ]s/dsFjx^tzN;Z&!!'''h>#ȑÍܞ@B^xcfߝ<*{syrE;[:u ~{q` k^*V)y[dDsd#cdmPKB!B!MIhP(h5ZԖt ̲9INN!o4nSJErb2n/?q[ADR7 Zdl^}ڢY2{EKy)1t\pSD|$) nDEĢIբ@_C_mo";yPqksvYMGv+mً$&&`cc{.Sp)_>DeG|t))X{R*t+f"<^hԏcGNp5<@* ^o`$'2exlmp Wb4`G{A/}eަx9z59ss^'y$3i8@ם1zB)`nbZҮm{ zLx2!C?(yIIո{h \z,6\k떫9A3g,0eSzLAxL `|ۭ_v0Myq&N5ʔ-' ٸ7"#hz_܇9sll^9]=V݇6f`ys3kTo S7Ӭ9!B!B*[$q1ܽoұGt̍[v|Eȓ77jШuҵU0?k.1Γ<,X>/^kk+JٸcV4m iNl  MG'G?3 `0%'L*V)#ǎؑiJU֓JB|k+yzФEC*U`>ZcsyFqp_ib򤤤$ݩV2UkTOTQٜBtdyMؘX0T hٶig2.\f׶=tSJv/ ekt ?eǍ׭pmឃGO00蝮׻Z~ܼv9SBªeg<\8{kWn`ggKex2vvv< z̈c9揿5%K')1GA&`۔9V>qWh!V,^&UCbbkWl\E9+YCLJJ*{t-w2Kʖ/m+鞃 ;Voie9iu7ֵ_M85j . Mei&W^mډ1-RmM[m.={߲q;a\p⥊Røz:#555]ҠWoe%t:ٲa%%B!B &RS4< x[,DG8 T-azQ˝kJLT'T֝)I)28?O2nWș?eXr#5jU֍;jK5/-Q5jUe̙֔3|nW2w x{adF=Mm-egW o~O:wȊ%kVIO\ٳBLځ1'Ѣ~[r{zS̯-,TV[<^dE:puߪZFT׮02{``ϮÛkuz|Tjk| }dEőDϾ]Q*tՙA? )#.xv7u2ah H:_BJhUt:>+[fg|YX6; @[f /OB*Sn'""??Ǿ]L0ūݷ#ۏŜd\*.FlL}h徲xv;?–x vɎ}ru7|c}猏w.cRbEDcf @sng=Y-Vf3e d ˒)n{򒞺H]}Ꙉf~ #Mn柷Q\ u??U,zΤy \\\pqqq)a0(Ju$&&UP/_ ALD#C&l8=LrOacb2;zV(""""""""3 _tWnHR_aIY /Rvu*T.KhOްÝ?q??LY$_BqOBE >~+s1>F⥊AY DDDDDDDDDDD$Ŵ<I-~HagqⰓ5""Lg@3J\E!""ό%A!""TOgW?s؈ӤeEDDDDDDDDDDD$P%""""""""""""YCnxꆻ[4"""""""""""QJQfUGlQ[ara֯ׯkpEDDDDDDDDD_3}d9qW ¯-RVblɒ-35^1h&~ҏ,Y㉯.UJh#czuG^_`]\|&F}un'!!ᩴCDx 9&BDDDDDDDDD$53j^\M[6%K,g &(_.>S0,2Ӵe{ɢs[|)[DO=sfc {mYθ#H!?jǛ?-e͞{Xaġ=+Z̔cɜ%>DDDDDDDD$YѨ@RzA9:L!聡be@ܺuqSG<9\]]H!KQV5֯\f0x.``|r4z?N\]]֫3Nw?ݱ|zgNCbbb2n2kp9~vF6M3e8o-Q""""""""rWZc۱3nD΄%9& 0ړ6]Z\]w9NL?qqKW4PV5|att ;bf=y\]]LrTR˾5 hҲ3MiFt! MZ8e=GVM_3,jծlk|ص=d4 g`YvNXWeVsXvǐyhܢAZUϝȲu_<<1LZ0=?d21b`z3z0Vm\°1ȑ+0nHlZ)^(o5xy?p쵶`l*T.Goӵg'ڇ9;w0Y^<(]oLgٺEԖc+O&LMY6mِf˓fO3e8 K ~h*o5͌y\" fIfIPp jÈ_3mO^8%~;eI,[ Ky>wM㫡7hZGѩ[/&&h,+hyЊbgXi)C 09ԭ߯ϒѶC+y6iȝ7!>>̘9r ]Z09E~{S㍪n~>vW*C/? l6n6L?c4m4oąܡ :$o|)W4ip:Gh~"GoTE&m%*2ȳ{PtIcy&VXK73wvCHeӰi="#㋁W3t 29k&4GDD$lbqG<<=jY~^ 777YLF~\j58(ރ2⥊ҳO7l6koIJehٶ)E%+d؁ r8^ w}9n!,v1n ُ@L;$4Cz8} G`ߞluv-ФeC-eۦ4yxzyft쁿%Jq3e͈smm?5{*Z93ן9v8rS8s,Ig0]^}?iˆǾ=ȕ'u`poݙ vȐ)z0y @׫Zʜ8~aԠÙ:~7͚X|MwWpH gރL0O/Oz!h\5m (Q>WPq]֨1̞#+1\lK-aɞ3+dΚt ~{snn\I:۩װG Ý-q`A8L``}ʝ-ɑ~~DEDx/i۾ 㦌=ȕ''}aTˠ}~w/~)VW7W&¦[6ϝ-PN\m! IDAT ::__d;Deȕ'_=p|{?.]Ltt 5#{άϐWWrʐhcܨIlΛ&-`6nTRŒ8%' 0/?Ⱦ`X9a0yooƎĺ?hڲ˝;vF%Yp>VRZ%*U+uC3Cm{Ѽ8`Bco=l>DFDRj ?v'Ωgרo-3~d._Br%>oP7X`)e],SB/dÚ9v#..ysuDTLwo9] pqt$+QWsx_}>14$$$wzo~\MKV(#g6.f6p_&۷“N\9چA@o%]hZ\\8ki!S+:ˮ]ƾ=ZgN%!!׭u"k,tԖSGdɖReKPl \\\Zz3g)rG^'Pרߨ...;!3mN2f1xnnn$<=[t!Ygժظbcba)"""""""2t=>L7sҵ{g )>><ΆgiTVu{nv;{wÝ۷^p 2peҦKKٵk˾_"9_ϩyDC%k,Cg=c']J#Ǐtt/۷m]H`N;E!g\;}˯b/{gO,p샔ln2wr[/Ecs)-Lfcģn¹ ZiB||hەȈHgy|,/u4nVaYPvurvʙSa 5K. m΅'2"c9޸QK.}hZ2g d2CDx$9{o,6ZoVR |Y_""""""""/,{ۑ6]C#Hv&]{'on:u م`Zٿo?;Z, 2V+f+Wy>,]z b4wn׋CGi20/- ʕ'/ێ߶ <߾=l-o5x_|sʆaJ_UD^SJUSb: vZm+ Azl=fT~Z n,mߒvnӰi= )ЯFhӄ=ܾNREy&_} bv4y GӍ@gP֢MJ-A  ֧JJL&6\sNoȡM5x o:qU͍Y2QcxmN*剋˓/"$mCϵuvB3Rbi֮p_ytT iBf1xx[5 kJ+d"..ӸEd1*28-ߣTgN\yf3_UskAT\c'hҢ3# 0 5;rWE_`0PZ%,M$fukT[7nqI}̊ڼۚLY31|bc^'$$s:X,*[v?r^ڼݨn)Z'Ϥ9s$cgF9#)zA_Cg͟qSGˌshծ)#' a[j: &4c(55uLjD>6ulظ~۷F_g﮼Ӥ#}Mfe78ֱ/A0}411L7{{nZoFٰf#yvtT ]L)S$5?)sFFN/gL3cǡGy㖳|Ҙi 3qV8{㷳ҶC+^]4iXl5W._% 0 6YYV[!Ɍs~ډ6Za2x}z|n ,[1SFJ|_:LH%y${fM8}K㺭;>%$ؒ< $$5yj/`9l Òa2_,]ci/;s|7bc8r(}ˆE]i^ =9t7n~r""""""""ɓ77+Wj2zc?D.|L(T _}1 3Wo6Ι:0zH\.Ol5F-11oDl6"nGЩdžu͑uea'/&9 ghllBZ0 &bru]s$['SoXUp)2f e?z z[oͦl;{۹aJff̷u[VdRƍOr>//O<χȈjtss#!!۝4Rd2BllS&W77bcbYbHգӃd 4,\ _ ͱ_ݠqI=Nt7pu' Es<ׁ6Rܣ%?{ҕ=_DDDDDDDDDR_ug"jn N4hPEDDDDDDDDD?Y]4)Ju Is/вꋈI-~HagqⰓɒ#Tw@DDDDDDDDDDDԿi旈 DDDDDDDDDDDD$P%""""""""""""/I5~HKDDDDDDDDDDDDR _""""""""""""j(TCᗈ DDDDDDDDDDDD$P%""""""""""""/I5~HKDDDDDDDDDDDDR _""""""""""""j(TCᗈf ȳ`Þh@s`4`6~j3f$(=юՒS3dKiDDDDDDDDDDKJԌ/yșyԩވ k'c4j5I3N~'_aO8~!Wę4o%sN4s;pgO'[o:pFɲ%y7L&c3c/Zi;sCp"&~d{f<Μ cʹ9¤7nҿΝ=͖HRל&Okb&goo$m=|l1(6x_f/FL).neC."""""""""KI>-[6mK\뵫;#8™SakX9}ٻ+5 Tn +L8PܱA}G|(-k=tcU?F#G)oEҮKc^{EZ9kV+քT9Ϛ=+DG?Qcbe$WWǞYQ`www_@mڪ'ߗea 3gw:cќ>yKuoܸisQ6mucβ1'ϴ9h-@t~skWg?rq*T*l .;3&M[YGQQ(Y 1C%Cؾw^\FuZk瞿ŕ(Ϟrn~\b[7o9]*rI9:aa8r|mP,]+Rj ;]K^kޏ{9*VO>ݺy۹<܍kߗHJPH?YKf`2yN?X,..cGO&z$3wNfNKW>i61xyy$[ׇ F#~~ ڕXr-^AUpw{cӨSHAn Oȿ"w>$m0[6mgߞo\ww7n@REEEF1HJ+yJx"1ɒK)#̚-11ڹ7nvcF M֝TʖnSQ#kWmbɒ-3YpFx 1W;u\|Q<rfZЪ]3=yزdĸصsGooKæy6EEF}oV w V__ }AuÇRtqL&&b%^#Ɇ_G%o<Ι`˕bPߡ<6"<OOq^[O/O6@/@Jex}DED@tt ;wd2mӪ{|p*Ǖxp2f uf1l z  #:::<~Ԙ[I^7u)uP%"""""""""BJ)N2űX{nn ؏;`'ӡ[VUת䵯~`??=g!*2C0f{^r%z8^S C9~8'puscܨIRj~e+~KL٘3c:fc%^iȜ9o&1_Q'S\ȈH||}.2gtGr1ͪkȜ93*P(˿_E˶ضedʜYo!LSϤ|}Ҋ}QC6nHd_l6˿_G:≏dv[~ =bqp?$cV9\ wfj=fuH.6ﵤb pTtV IDATt}ѫowF ıygxW=6` t,5Xl mпi=Nyi,{wExxzE~=i0f /]Ŗ?wu#̪kѻ[Y)_ {9b%_Bj.f9SQM0M(UϚmÞxzzPKf^F=,Q}=CЋ<&ASa'/%ǿ+a2kܱZ~$&&n\x_YlL,ww}ˏcv kOm\-,GDDDDDDDDDytWyQDgK%""""""""""8˨#""""""""""""/I5~HKDDDDDDDDDDDDR _""""""""""""j(TCᗈ Dh yn[3d24""""""""""By޿V%򌹸1h< F.羟f*`"""""""""""Of~HKDDDDDDDDDDDDR _""""""""""""jh/@B {]!""""""""""-рlz_"ϘՒKDDDDDDDDDD{D;VKsO_"ϐ-Aݶ/g(Q3DDDDDDDDDDKDDDDDDDDDDDDR _""""""""""""j(TCᗈ DDDDDDDDDDDD$P%""""""""""""/I5~ "#vzlܼq ݮA[̙9ჾfѼS\b9d,L%&:nvdFEE?J}Ǯ>!vSm꫈<=M\\B(Kt#~~ϤO gٰmo gh/N7hױ...i_Mдn}i6ɒ-3/ONm;9peڶHߨnO}AYyjE 39i5ނ dڤx<~_ b`X[G*Zv2vD,:=9x%OKm[vnOQ*~GO&opEMCGWJsfOo;RUDDDDDDDDDD>XڐY*Ο=ϔo=>퐘Ow~ZiJ̡lݼ.jq%ՊbM2Ax qq{KWAf̛Ѩɂѓquunp)Fcs.}І2Jΰi-jծ~O^.RȠ/2towҿJ-듇1hؖ ѓ$&R\72".?A=B')۫?A %2!ySExy{QMЁ#9t۷`☩6bbbɕ;>@غy;GMAl7^7M%!QmNv;Kpwy[l\g)P(?_Z7nϵ+IHHq3n$=v8ihn3f3UkTa_u'f-ԭK-#:*JUө[G\\[zM7vݨT<ΞgϟȐ1=vmO"S4~~հ?Ů9y4лڲy;Fr%yp5:fSάYSz:++`62n:[6ok>cJ"t ƠٺyYeWOȜ91<~r6}:..?z,Y3ѸECK OTd$,% K\ˎm;6~&oSRY>=n1cd|i۴}csΙaTZ=YpbU,ۍ`08~߱72z 6_2k7\vI.M8Ƴ=^v1&"""""""""Hux\ׇog-`ْU4jVϾa+ोW;b"d``Ѭ^6ڰq/ rX:&"o<)2;wbPL&^ZF#VLgҔX۷iݸ=sg- TUJpu΅g¥x5fŠVxRG^Sll/\b0LVUN }1-Y8ǎ;cYtʾ}c3z0*U.Ӻ_;y$fl-M[5baұ󻌞8Z9ۍ@Z0ЌMy-=t2]:tϭ7 NLVmlW Y yWz .8 ;sA}ѡs;OSa,ȥLG MPp0㧏&ml_{֍ԭѐ5β{MFxJ}1˝ِ7oeǔq3壆;L=m0!O}2_u%iIb+жC+L&#iLoc08s*3p`!n޸VtXp-.WWw~O9Q&>@:-ٷv;ؚNq5:tjcdU6q&"{l|ޯ9sgwףw7^)2 *پ7nkʑ+; ..5+s) ܙ8<sf̣b fH_Y@`11&TTqĭRN> >>F| JҮ/u@hѰ-QQ$ޙUAF ]-yEfL/~|M[yjEJvCXr=+H4hR93ӲMGMxx{wg NLӖ 3c2wLNky?.oon^E w[6of.}ZҥOKWvA&""""""""""T~P+ Ҧ /Q_bԹlu~$$8 JHHpϓ%Ν%¯lrZIs.9s_YtwbI~fZmJ˶M9yVoyi>,q=f˓@]krd3UDtTl>u7lbԄܼqJtt 1{b"MKW(VhN|i;}6& !}Ț= d2DŧiԬ>/R+7ȓ?=]`Ƕ]ZJ¢Wc6 {yK W Qsf'.&꯿F[׫w^j#j2X`)Vo }h:bbbI3UB:oҢd@y,\O>Kf9q_{w Dbb"V$e]di,S7ߪE\ywΆ'pU$$$~6Yg}k=$uuG Yw钕m~6&Ic9g=H<==a}Ȝ9QȠ/'_n7O#!Ұi}c$$$<4 Ο]]\DewC^0ooo)c """K5޸~o/@l6N͕D["??u\\\Z2S'OsuzvGYg!11DztJK.}dq>[N>C ;s Ŷ_#WdΒQg]֬ܰbiױu@p#FxHfyffg{7o+<U=JTdɖo?b|v#V[7o(P(+e=xvlE"0 F¾]|yXf#}ܾYDDDDDDDDD{+f3fWCיL& Si]gLGE80..&g;W7Z~g@;'mfWFsȷoIੳl߲6HƵs0ff̛'c&MAaȚ= MbPl Zk9ps+&\l6sN!dΚkнOCɋAz `8MݝY2r$Ogq- vկgug`O<rU˿͕7tnߴiߊ U*p`Z5i'9rfwnSr9.DÃ'O@g౬Z 9re˹[o2Ohް5o5!ϽN,J5غ+ .ZC>%K#O\4o7w7 .@H, ...ܛ1'}dʜQ73f')tYsfWe 8QXm/џvB"?#>a-|r||}`(zN|k &N0ן~0:*W7ׇfv"#>Ŋlvypxxx<̮OY\^ `4OYsu5?b럞I[_=_F_{\qtqqqVh>IĒ*y.""""""""""<jԩJYqRADDDDDDDDDDDesDb(ULyBFDDDDDDDDDDDD %DDDDDDDDDDDD$PKDDDDDDDDDDDD %DDDDDDDDDDDD$PKDDDDDDDDDDDD %DhPDDDDDDDDDDVK?dr1)""""""""""Bym+%3`P< FfW羟.T" 0B_""""""""""""l(%""""""""""""Ɇ_""""""""""""l(%""""""""""""ɆB ߳XmvBDDDDDDDDDD[sOU~,J|sngyj*""""""""""Bym+%KDDDDDDDDDD^0m%DDDDDDDDDDDD$PKDDDDDDDDDDDD %DDDDDDDDDDDD$pQDDDDDDDDDDD$9KeWLpߏ!Y_"""""""""""lvVYX c0dL 0%DDDDDDDDDDD$ٰZݼ_/qq1)(aX"fҦO)%"""""""""""ɂn',4_|$pq1@Xh$)|_/.Hp[@:BttClG| OK@;K\lqbq|v)*2 ≉}䵌Jt{wbg>|:q܅'ެOYh_L\"\yrҸi}W*'J7=:t~&¨V]wfw/Qtǻ}8y2J1z\&O-;Ue3fm]ȕ+G ~Y 1dϑ>s=l8GEG6E<9r*{vgpߑ 3תWS%I6m&X6k<2eȤ8<=pBK6O?"G8y4;IlϮ}w%ƘYh%W5mM&S|<}{0a$lL8<HтϘ-Kp;@ObЦ}+6 8$8%H2?Gjúܾ򵋰Z,tnۃZ_ʘ(VP~۷Xv&}oiݮcgTRrVZwt,G2u*FONLY+bXn1V=oym ndYyL|?hgLE ҹGGBi]W*K"t?BU^sYRWk L A$"""""""+ R"UU`ҾsBh٨-U8u Fϗ)Lף'0L+Y^Q=A]H&5f GeŴϾ`vJ)N=I-S$:*-^{2:f˦J~Iڱ@Ws'Xu/ #W򼚛GQ^ nѽSoΟH2%0/ng%GL8;f };|w`/ҦK8*~~6:MjLb g҄i>34{M޸ZmLt۽łً7,ʙ+;ԉSȝ,Y2PQmƏٗsPّJ>.f3QtuiB(StXdWǗLY2&XLEeHuwBBȔ%#j&Cܹh roɶUWq&/3fH$qOũYǑ(J&iӧuÄ2nl6k03gDr0LL&MnΪ^eζ|6~*w O\E8uс"e nOv1g|޹dbxxx0{Dpm6LFt>DDDDDDDDDcvί]_q gH+cOį'07t|[v0ccnA 446Zg-L36Ӷۼۭ=?nQz//OΜ ](IΘ{=}`ٕ8t:tiyˬX0, }{ V|{wBظгV]uӉe4k՘G;KZ0w qq,Y5#3g<.l7ߑ\7Wr/o0tb 8GWW":GOwnkۓWoЫK?bc@^z8|\r%opo&X‚KUzӿq #ԫքbw?8׽V2G¦~`78s*JU'oGVN8xm[~bv5 IDATǏY$qOv-ɦKW974HjM5l̆s;bZڳié߸wQB)j}5*SZ%v'`X$1.rJdk%g\|[wpp_ ta"~N0#Xh'$Ctoq$.߫.ȕ'')SwPOΟH\l;j: qeDDEF9_"<΅ٶ'>>㉌"22 //OtxOM{>77f"e[iӥ%$J:;׽ݮ3? db5l޸H\xVZ7`!Bof ,6SqmgۙSgSv5V+aq̏7+Iԓc2G2n̝sRdQRrfi|4n(rde%ζZw\9ib +8omK4ٌ[7o.}Z*U\װI]6c>c:.\ ,jAѬccgO">>fެEt%$tR;͝bY:Y9^Yu|M;~f>9߹L?Q: fd͚g|ڿOBX|+1Oll,'ϢG !:*8 Mpȋχ_a=߷smI>-fbԄaX-~kShAn޼Ś(UJU)ryN6,4QCS|)~9~ԩع>M\|úo6=GV( ^ "r9bbby7DTT4]ڽ@lL,';Mxhzw˓>Cvx xBWsen^Ia<=Ո?ɦ ?`xxw~fMq {`+5u^ٙ(ߑQw{ج6bccyŻ,\%RPJ9:um@DXX$Lxzhެ8>09'XDxs98mbđe/aO0 xxc0woۖ(R0)S8bOSlI|X%JoxYDc(Goo/rɉldݵ7IIYd3O0*N&w:wuu~_|:uwB2QtށEޫ~ {y:ZLAyx*V)땘GϿrN(ofaa|1s}8*T*˜ 8s* SfM$ e+b=̘7yR)֬\Gi٦)O͗]?(PU>7J]L&wC8f2G>ԣEkǾOcY9Gyܽxie]FtT4nn3 ,,S$\{7fL0ͼ^*?:s}+ҳs$ۖr1~<=`0Z>e$Qk-+Qﵥ[.mb<Ȩ!SJɆ3g j(\1gSDh#qǷcH `0`4X,uꭴҐ=gv] je¯{Ǒx{7\y^n˓u?r- H@EI"""ln( A!_o7;@^; 'ϫUdn~ҧ8%֟% cn b/:ۺ1qϗtyV.\j>yYdcX69dHJ%%s_9u6l6|9%9>>7+e$:W*{ՈqضEpMF G!}ȑ+xF MjURd6وj#$8/nYz@tt!hZ6Vm;Tmݾ%y÷켚?/M[4NR Y&>Gm0Ȗ#+ok<;c>wԦs٫_a1/Y:kұuW (\ ͓rje ݹ|֭\C)Z0۵b؇ѸvsR'H{>-5PRD$I~Jٳs?m͞pGڵc/Fd@/ZE||<ʔ`woo/=Dsd u~_>3O:,'Zy~7jիNfs B0*Zr9w\:w93''OlL >>P||:NtT4V oo/}B%>j [BLt >lvy[xaxx?|OC?v;aa>?^^FݞZ bbbll,cXX=GDD#?g剮$o6ׂ1KZ5VΦ[]zE|4=peV]Hspa&ݎfc\z{Aו3wp/F W9G￝v=ԵzXusX0{ {w'E?A!mXMntݙ_1kڼޏٱZmНFolL.F 08t꫞Ee]]͸^"5塉/ $Jx~h '?g`0<2XP">...MBwӸB|k&ON||s(Ŀ˕''Gcҙr2f@i1|1erd^|1e.K KM|=c&ΒCjo8GK}+p$~G2fjoiR4֨[DDDDDDDDDDD^6 ϗl6Slnz*۱1E1Q OsPW7WJ.Js*[_ll,gϜ'm4xxb_aiҦ&}tlߺC`/GRTQ8)0{_"""""""""""1TV2K&Qj)gtڎe MSnʹmJe1 T|<V\2L;}{ beT]fo5d2wPOt%QfziK9ߜKDDDDDDDDD${9g2X,bbb9MHDD$)""//O ט%"""""""""""_...x{?:Xb3 {(""""""""""""Ɇ_""""""""""""l(%"""""""""""ɃA!P d`4`'dX1Ź(%"""""""""""ɂ`ݕ ~)%"""""""""""ɂ`ϛBcXDX,VBWH~貊Hra2I6â*x0:m&SRKDDDDDDDDDDD d?_ 5#0nɡ d&9%ri/I6dC/I6/@Ɯn[/Ť yA`""""""""""3 ]]~R<*DDDDDDDDDDD U~H䗈$J~H9Dͮ@s`4bz/X|E/ymv,}?Y-VADDDDDDDDDD^(m%DC6U| y䗈$J~H䗈$. nnon'b``0$RKDDDDDDDDDDDlvBkf)(ɔh͌ o0EL/Ift޾^JɤY+Fxho%UZ?L?u䗈8vC#[IL&# FCy᫿}N8Ͷv`X}LL,׮^OP2y^@DG(""""""""`D}=O8 ꅯY11qq!k,(=:?-c|nҧMtodW ?ڇN]>h"gNb)R0?1sFgZ4z6 `U(Ͽd1h" O/{V]L&#V5Y œ2Uu{.}֯H} zΜ%I6uˠC93slSY2l6+_LT=wB~;v+ e?ZT\Xf#Gkv*<<6yn*][HҸYZiOGRDΟ #Aٸ4ߚfIĬV+3&Im]1̥^+i.*7v=QBh٨-_NǨ!xI{N dͪ8xBвQ[fMK]1ZvϿ0bX_h٨-nNkW3Pޔ2yt;ҢaV/_{zR9Mf]]R>.zr%Əӏ?g9H]xN &M`(93|*&OF-P&OʼY Oy{} &KLL&\]tޞb% 'ڟ_eִ|4cU{ 6oo¹|1u.oٞ8BȯtnۃUqo\u $s];ҽS$%wӶy'V}~=q-p>1j6C.wv;KYּդֹ߬nm4nZ??\]ͼNs~q'ulSn\'9652nݼ#"""""""""I5˗̩@&uim֞7og z_- k1|@a⽗ɫWeִyӦC+nݼMCecv:K|c'H!nnܺy+o!66kWlJR\|aGMEș;;JJxzy&G\\<;h&%Jcu|2s ̞1^h"w#>ݝL3ԩS`,J,3g;慻Bdds;!w?;b]ȴٓp+vnyK>x9*Wk'..< ˕Lub,YdLUODX8߯LͺrtLF#+k4cd;͙4c=uwjqMfMKVo0hddLlYfmm7" 'lQ  aOٿ+.ϏYSnl v6л#iuv&LX+fzdr'SqV0Q +IŝG;..}GPL -LY32~<>"""""""""c'ςb!666X~=}ܓ8q$W\ :*gWy6\I.V(Eͺpq+1;͑+;spgǏ07_? 69rfc/0 O"4vVqⷓo\(o'|qBhz9?>{?szjhѰ V I/0;ү:mjjիӓv\"EU^5m.MVmʔ/M+]^^۵*uv^{rZ ,QqmtlN?KtTZ];遇.f|}I&C>d˞5oeoh֪ wC9r?,>)Xv!>ބPhAgWtT4veʹKtr-"""o7ѲuS2f@ -Hll,f>fϨQv;]rՊ|4~v3<2ᨀWc=T-S'v/]IF=se Wx2r]PBi.q4iSxq\p~ ˎoȖ#?+:n Jҹ\w\s8Γ[wr'sY<9;^6_;@Tdd d26]'n{U9ȦE3yTS>h4Ram߰ U*}F-_Kqד)nnt>8{MXꕙ5m.vSg)V03}}>ĪkI IDATq 3#**1L[w&&Lb˜,TH폘^WQJ˃B|<˩d^PQm(J-AjӠ&iҦ~o  \ȇ%*R-" `0zR<<G1f}Ս $cJ^ːPCHf˞g/RhA.'RՌb}h|}xL3YGDDDDDDDDb%Ξɯ=;a4)[;be̜???OcY9GyܽȞ3'&&sX_4c qq\z⥊Rnu{ߎWҿzNr%4~7˓{QXAzy ?lyj֭Nޝg8*}6 5"&^|=Ramm7\"tl6s&ƙS -۠&Fby&W^͐27d8y4S}ּVKso&1BB0|9}}d~%Yfl6zͪ|(] JΎa~`>R;vjS\I ǰ9eNb,Mmkqss#__Ua f/a5IжH¸qfvU˾!Y,>]_)S%Kf֯ZJd̔'N;;;wllް9Nm@qSQryfLMud2qU,XjjO@@@]&= VqmqTL9nhшba߮pT~JOⅹt8\4o6?$fJ-ჿng(??Br5bc㈌ ٟ(AtT4Rd&ǜ>ؾu{v'*"R'8nb9}{ bK kCTDׯpu 9HB ٳs?mu$S?/}Þj\|w93w~zN݆5Yb-7luìZ ~x'\zPTQ^- nb7w#Lxx8 "{lL=92/zʔ/ő0i4&>f EKfO yil6sN( f/d29kfjׯA>]h&Adˑzb0hԤ';jMNo|7ogú/*7o-aшdr.+\ 6l+x{{O )^(olMۘ̓#><<;vmG2sd uUa_>=6)}mf=zxh}iҼ!fۓp.]Ǟ<}L31SFOtLWseuhϗ/}i]#k 1l6.&&8^l ?_{h؆wPOBCy}/22 77W\\\LYMPU!fnCl6c|K6zwybX|1i, aaOr&8(oua޲YLԮaDD$f nnn\`?mmOtT2ϜsEGϒGv|DDDDDDDDcڸq-(IiX E?K`ٚd̔V9n [7mcִymرZmНkUoҳ_7V,^Mf blOM4sa, .1ҹ ُu^g@!\8Юe}4sףV|0}jTh@U0 \V_Ls,"6E!s85E?ZwԿ7V]0Y2t N $E@ BC׮J (]$`Oؼa ?χ2zBCH&M7e^K箑.C*߻]NY^,#׹Fu$I ϟ`xlh4>=tVkVۙ9?ꫛ=$rq! e@NZ5Vꉝ%ӓu~t|-*v&D[OzׇE o||ɖ#+3=KCG gӤÝ|^e\z굪o~fM @2,_: jRH^{ 9K/Sp~LÝ%_ׇzLRg!,4Pd;:jԱ8CLq* K1V-/!Gl7Yrg( ?{t oW y:wHԩpu5 IUp]4K:WUGDDDDDDDD[+ Z"""""""" TRwq6W_w;f35{%eEȒTBa$kD=}_0̝w~ 0.~>~s=~777F#5Tslw>ld2.y/zyQ:Λ JU+`0Q;5*a2Rb}a2fExUzJbE0fvsY̎cnnXz͂ www6B۩xxv9nm@| MI%h5J.N:5|<M[v2I& H5Xpuף&Qam*V)Č'j/G%"""""""""_5Gtt ^^w\^X$%&X6()).r/m=u4|H]hܴqʏh2,3s"Eߵ旈=X,nX,nvfY[Fm{"ǘ_""""""""""""S2b )xy,W*xq0 to ~ɈfWG% a6fخ39rdg.~Zuk~b!t^Z{^Fd2O.]Kv~+ǏtX&X ;cGN`٨P<=>dⅿoaWAQxW.89S@-RsSgs,\9 wwwǾs v F vlO=6j֩G>`0g}ϩ}ӅxFĄԗ~ѥcw, v}777/^ɰA#z*H}vErrX)m, Nƶٷ{?f#W)^N<xga.~^&1KNt j5)W cؑɔ9_|9Dzuǂ9iݮKuOfzoosߨuddo6@eg޲ bcx(U$+usI~ٸͩy1}d2gĐ̜6n>1i6\r2tL.5 O N^p.-):ifԯńѓ)l :v~Ȉ(j.UWijřd5:q:V38t=-])5k<֯@9 w:/_#'flt~7lvݚw  Lձf`:Oܔ--ۭ."""""""y=~+!99FMSB(2J`>j>_9sKbQs̙1~R^e6-\I@`|u3`4|u*WR+VIy}i %`٢U4o*ΟAL;ֱz:ƍHe|`/zv[HSgh2k4>Ѓ)sDGFuoT3d42o»i28xy{K|ާXGZ?Qt:î]x(QtiǽÄѓh۾%O}נmnE[OL& #2"3TTLN>]0o}ql$)){ӠKWïjӹs9qS)ӑȑfvseel޸ ˻TF2zfa$&&-{6ǶeJtɞ#.[l6/YM[BWïI|| د ߗ/YE")tAJ*G Lf:lʥ8%bys:O fckխXj=+ԉTh͝'fN1gbc(_l26BE +3 VN;hTԆdNZX܈vAhhK+<<;ѸΫ4{kWOuLeîKBDDDDDDD XO{h6<p`!FKjٯϕ{/o/֭@Jei)'.^h"^GOBFf-Sgr9' )SC%ZSjV ~ gϑ+ON:Ƶ~ֱa0 k 2 IDAT4oӌ࠯8r(gȓmqs3hM$SLd˖XL2T>E[ٲyۿϞ%롣|5l,>Ȍ)ؔ+eqVrcΩܼq+bvnMDD$bۼgϮ̜6ˡ|>g23ӾNLF#4kBzp`!x(MA][p!CwæsDDDp />^ڶ\歱/WaIII)t ?`lݴ ???)Qԩatԃ*-[] gL: /:eqPj,Y??_yXJ+ť_fZJ.ɷ6UΜ9(X8?&fTf tt ÂFreigN눥L9|B/^I^]:oN)W,50,Y"E ԦRK-[Vתm{y~ف/IITuoEJS ~ !WB?{@?BBBc-nh`tx S2YL~FK@FZf&#'&{FOve%HrYF Gаz$:>abMrjJmP pz|9s|݉7|[-8[c"ذg&~7ڜ+WvU,7%-ucKJbd̘7wצ_7vbR~0J`q[`9/Yk2?j]{wFL?1&z `و"wVg֢i2adFg>gL8SfS\i34{.H۷[a4ifNs_`ީ=V^$P}|ȟ)3Ԧ5:ҫXr-:J/ 'c=@:d⹲fs[~ٵoMŗ_`ϋ|.g6$&&ÇP䙔u[Fg`oX\>Su/ ~~,];כh?!N;ow#ejIPL _NN27nd .r>_f-|>'F͏Ld 狑&22 @A+>|wwpa] 0l.-ރOttm%K#U;w;:^{zza:^⥊+w{js"iظjTN5\r2{yھ Wg2!*vQ)RiO=)OΟ˱mLKzz?/Pn)FKdde,7[Èr5N;E8RՊ;zs-j5*3}dY)[K<~$e?dnl8z?G3L?_`96tҒ)sfl6L&LfKcny,X1i'MϹԦro =>XnnaF9x:6GDD$?HllcGN䭖]zX.郷7 ,V/_KȖ=+׮Ea=s,z￐!;=;pI"#(\ؘX6ozO[wdrO5\g_(Y8fD|>uѠq=*T*9ڴ wcH_$''3et>R;rcͤ'OO gI\G-:" f.z3nXx/_lٳR{7%JSg8y+`ydz,NBBg-,\rpz/A;O\R۽Ntt {vtgd5b5? \?̅d͖ ҡS{TL]{vfqk.sfLgٵc#CvuT^2q췌5 ??~#Dҿ\sA9&o 9bxH[J`@d•!i>A}l ڻczO0 e\nIۊ+B!Iݭ?Y;79{wV,]8wо.[?퇸8-ZlXɢ.O~ <ww qqta)BfJ Xɢh݌ۼ7qM0MX9qN@@`ҹ}6CKm씯\隿`>l6/M]noȕPnSk5#11Tw-Z*6/o/ yߞ@Bfvmڷ$0Ԛd{2E=M/Q?m$>.դjJs.XASe?XxUrk.ʪkѧ+w]z,WQT1;ísî10v76`NzJDDDDDDDD$}0$':ՃuEʳZIscps3n e]記l"K&؉m̍i#*2 oTc|1 +f={fM1pINN&2"_;ܩܤ$~xrr211֘Xܰ^3ƚ`z%6&?H>OL$>.ٸ8 Fm;QQxxzs[6Zܭ\O//Lԉw@u;`>'՛-x} |ifܩ ~iܩ\Xnm=~}{r}o1qǬ&_45Zܭ~^ɤ*d~<5KD\ơS: lJXEDDDDDDDI(g~)%)%""""""""""#M{("""""""""""" ~H ~Hüudbpl߲5?/M% sh DZ*1[[k@B8m Q ?NfSbYRu"ti<]/IUNמyI"8=GRb ץS?z86 /tl9u6/=;ffɜ%I30˟A]̡nOft Vؑl6*T)Oϻb2xoFxUiШ.}7^iǕ!NmΔ9yfԯ`q Wݱܹz>ðQ΋eܱ{\Ί%Y||}pys_Ȃ9ȀboP/ ⫍# SG/ '9=LzTBԬSŁ}ə+;~ґKprA_qʝOk}_jO˔ٱux/^f_sQL&ϕ{?턗'Zt$r(IIIx-dfܷ#Z7ܶdU,CnA#T8ڼ6KɳK8ߏ_Zͪlӌ+th*Db3z׌%Oūrh;j3 ??&=LbĘ/_<!aȑݥw!\8;w2dt ܩ}b@0=vr z=Z9w.uWaW:r pٳoWzqܞ:i]_7jׂ >>#'հ1 &:L"}򒓓֥{'t Q+ϱȉdʜ/Hbb"cŴn¥}3[KAzoT:227wRՊ)/?o}D%)_GOmN̛s'9K& f9tIʕd|ңsqXIxz9^p.-):ifԯńѓ)l :v~Ȉ(j.UWijřd5:q:V38t=-])5k<֯@9 OuAfN9=c>;O?@~SfM$sL|\-#u'OnʖEV@y¤5]cu~fKJⅿ3s& js6=SN5Vxk~+V)0GgOxVlq3FOd2Qf h4RT`uĐQYvZnn#_EEF1PkFmYpcߢ˘4~* qW.;gqwgo0vTQٳavs3}P/ sLd͞А8~YTlBs) V-۩#VdQB9}rkVŦ ]nOdw~DGLf3jp*VNȓ/+QNض7w`]NB_0 cߍ *E+xF| 2dpazc[رu'w`P@ˍfyZٲihhݝ[;Q}uT>x+a0 gz\ؑhs%,dɚ9%`~"^KGOP>ɍhqӆٹqn;i$ENN<9X G\d&n~>d HHHh`Ui4a*|.VHnԤ3L{Vv%<F\7+,~BxU*T.[cw YxX8d˞ͱt.SҥO?Mٜ%wϷl,_{:]OO/O4c' Zd*Y20z +'X,DEY  x6֪[zvzDN8Mמ)R"!!NoAbc L"ȕZ/4˄]M?EDDDDDDD$IaĘ!Foo"#S2) .ؗS@Joʃys9ɛꎍ"b̅89rf#11Ĥ;O+u Xq+yayC[$Zɔ9ٲe8ּ*S OVvul껱&ޯհt#3z`Y 4m^wnQ/Zkޏ:sƭ)Opl۹}7ԮW93<5hjٵr9LUfw|ihfM],ŴI3ֻq~:d{8{Μ||b׶ݴl2o}KJJJyKL ?`lݴ ???)Qԩatԃ*-[] gL: /:eqPj,Y??_yXJ+ť_fZJ.ɷ6UΜ9(X8?&fTf tt ÂFreigN눥L9|B/^I^]:Sƕ!|1r 9f/?!aܟbҸ)f@welݴ Uʧ//Oy¤if~>>a{y0QN,X\_4ɔvΓ_?Tҵ )*. Bw>w3b(BBBc-nXUɘ2{A IDATGgаTTdR5rX2749y4۷d`rLL2$9s,_Pol |=z-ۼNxX8qX\]Wn:|`*0p5wg /S~r:&*2 k6a\)W M_kŒ:eْX<%-ksͯM;n7iU\\<.Nx;,Wd21~$nw}KulDDFLJno~XY1k4U,)?= ,k:2{>A_""""""""OtuRMi[*e-`ڱ93ǂ9KZߜqssyf||}(_,H\l"#X4o)qqܚTlٴCRha֘懕8{w|||13scعm: ..&Bk8Eڞ%Ol(ާeެ9}|x3)|1r &˗`0x=j5 ߦijkW#p<)1 F1am4h\һsse_ (^r縧6,ROPP,S'NzZFYv- 9%\cB dyx*'k,qqv+uq+w>ґ]>d6M7׉zV yK HYy {;eV@]kg(Kwun:;"Eҡm'ǶŊ|Czw돇G,n _vpqΞK84/˖O!.6eV`6(VKӳ_wB\\<]{~?;~eWQJy3d+Yv??}z;ٺif>1=g6>މ^a4 =\:fH4 vr&.`>l6/!WBٺy;SgOLy8oҎ$4zRKܵj5pha4{O3}{)>t #xdΚ٥iߒhRk-4Z GgH`~\xW+9]`5jWwd[g`UTVig(GX-wmkry (U$M^~CϾ:0w~/]$4l(V=}'""""""""!9Isg.@]y%ZxNtTmgnLKb̃wŀ`@KNN&:*Ś`c NLdD#~/&%%Srr211֘X9u6K$ދؘX,{Hbb"q|a0o܉z}_2))$mۚV$''{l6bcMy4h\iGyp,&꿈uyDFzM&=s͉hf^`x_@0w_ӛީ\Xnm=~}{r}1qǬ&_j^K|Ƕ?&鎟?߈dRKDDDDDDD /^3D5!a:k3@R&""""""""ΣC<4_""""""""""""n(%""""""""""""醂_""""""""""""n(%""""""""""""醂_"4 """"""""""Xyԟm+%&uכ@tL Ng-=}o`g&`ٓە; }?,-Y5+a2qswc~zV)㦳oATȧ:d)㧳so[(;Lb`|P/(!""""""""""ɖ)5\6W;rW'_.5B>vgOg౴n߂:jңS_=Ț= {u۠eDEE'o.D\9hݬ#AIHHYc]=;2W8;;pgq/RB)lfΌٹ5bwk R7ѷ+FV9ɦu[X<<;s>3aQ/mv~xV/jҭC/vj58x{vORn3aJoaf&η+醈H*S2Jp+ܾy;a OÐg~֭܈7^,^u6F қ[7o3ϗF֨7*5*QbY@k?{f-SL ֮QSV/bcÚ,_RRlC6xzyP?xRuϝH7K7w7֬C'ۭV&΢%@xX9s%.˝7?`- ?kM=@nc 3yf4!_MfӍ)5^T4@⒈/`0d|}KqrrƵdɖs/Q(>iͯ~{Μ:6ȯp)O߁9wg*V)OҴ~KN?GjdJLF/Ub1% ׯD|ɗ?Sժ¼-V-[K5$<"1ӡEgsd;(^([6l_p:Q9ysQN5LG+2" W״/]/#ssA<yظ8 Nsdc̱X,f<ǥs+wwc0 I@#GO%<4˾"{|} LFl6O 0ண@f3y\rc3| /R2;9`yz:Vаp@yA7e$`ރ 0߭˝[whݬ=YeNݚ4hTO7PDDDDDDDDDDq" yĂ+=|>_JԩWaVLߪjq=֮@Dx$*ɉ V:p"{ f̉~pr ,2fLN7L)fnNpPnvU@z_=gxX8nnPEl6axzytgْ$`{oy% n,~9zKuiޘ&&NV~& -H.m~O_æm|=ki]]kNT&;|>3ީ٘qqN<ːxp?C!63hx$l{2"/ D ^j0 1m6axx8:*NZ״""""""""""Hr4I$11 ^KZW""""""""""""Z(TCᗈ DDDDDDDDDDDD$P%""""""""""""/I5~HKU>'u(ݶ/Wh2DDDDDDDDDD$EI~bf 'dbJ4V$Q3DDDDDDDDDDD%""""""""""""/I5~HKDDDDDDDDDDDDR @KHb""""""""""l90ɾ%%(dn۩K&X """"""""""$w D^!f|H m+TCᗈ DDDDDDDDDDDD$P%""""""""""""/I5~H¯ǏBE;v;w(x$8F?$Ň_>wj4A&ԫޘCtү4k؊5߯sݵp :IuTܙ _7spukL)=S.Z/fz}^IlvGԢhd͖YNR;fηԮ[O  A.^h2QBV=;QͲI㬁!"""""""""̥+q(H)ޮ[w8z8A0LtiUW嫜?{2нOg({:~&gOϗQ̝9;AټGdvFV-[Ãdz-#-~HUY/Y+ٴn tohdƤ0E&66ifs c(T =t/Cڮ'UW;<~Y2)^XVrJSBiV}ckcq,]4抈$s)zK`ɕ;Gr9x2qE+NGpv':*w70g~=qVLd6ޗGO1e +ĈqqswcqܹHăADFF1fDnB]f1η4mЂ&[|JǶ k63it[62~:ZtG4o܆v{m>9k=:"""""""""""NV+F1IbN?@b0QqE.hԬ!˕UȘ)91Х EKfO+S||ȝW(Q5Lt֎!}ȝQ;o+-4˙+C4js9:tiC,Ѡq=?ߡ\O3˖bx +Z Nyi];ؖۋFM!cz7훷Zqy:ti_?ԫ_EǍۨ\"K IDAT//{wplңNg`aqqՎй ^^TQeıSl'~Ԉr{^^: 7w7,3 ?nԓbv=1=zf~=ETDDDDDDDDD$1 6v5T̙ W.J=iX(̏3zV++]⌒n;:2Nێ-pqrrl-'lc||}covHb5zb4jwA.Vg1H<<=4s q.gg9g&[}ґ-G6GК80N<$ȹ}qq$$$æm}2כlٲq_:w|ܛeغy;wr ?nYI͌yȆ5Ȑ1=9sd7K.{?J ʖ/ɖ?bڈٹײk7%>>'&iK\ҺpMEy&4 cl""""""""""3Kn^ ${c>MD4kВw};NOP\\\8[^|nnƿ游x9[ERً 8+d/2 {=C6TX.I 50Ӥ!k2{>v;ad͞/G+''fYa }ƟV⒆sg.MF|ef}3ys1k\֬XEKf1+7bR\~} uMB>ەE rqFE3I g/yAf Yeی1`0([4]?l&22#&qv(fzhjE!r2^*V`LhKWpb(Z0% =0'Nha#6~G[DDDDDDDDDR=Y\ȋRU,~$'&OpjIذot/Wq(nd,;w?7QQ[U Tnyȝ7K׍{flو/QQ8I:#66'yΈ\\DG1u4EDDDDDDDDFrL}ح- Hi_Esg~jԩԬ3)zq_fY |ϝE^^I2CO%_Vsv9rfLR8Ke]8{Hʔ++7RFgΌEK#KDDDDDDDDDD$HAGDDDDDDDDDDDDR _""""""""""""j(TCᗈ DDDDDDDDDDDD$P%*@:ADDDDDDDDDDRn[+d4 """"""""""$w D^1ńfH2dpl1%vtD`""""""""""" TCᗈ DDDDDDDDDDDD$P%""""""""""""I] %$XI NLdNy|HgىKHT% YIQm_"M3DDDDDDDDDD$I~HKDDDDDDDDDDDDR _""""""""""""j(TCᗈ DDDDDDDDDDDD$P%""""""""""""F b OmZm< y%HV3EGEc۟-!!g??q?sn'<쏟Ȩ֗i>}"HHHIL).]z:6 Ԋީ%0.GNًօoנ}ָi[w:`DвGפ!C>h ojѩk[j׭;{<~q&ܹ?/nnX6ξ=R"ꌓbҘiX,v;EE8"Oll,i\0pX?O]Ӕٹ}뷭|a[K~% ZcϘ %*2M>Z қ%.>6$6N=źUrWf͟&/b% ѭggn\IV]X<~3s>Yffq\;h"d̔{r1sgFE+hcm/XVuΟvSfv;FM{T&lf|b6QѺ}sj֩Ƶ[D^: &^<=;'){^[>|D6R[y2e}}ڱ%0k\+Ln :PR9 +g9d %Jگ^^թW+IN)fY dXn޸Mtt %˔Q0V`Cd̔]憧w#Mgh1s7>&̩FJ*F>]Jɾ1+7{[,RCz㝤-G%44ߡc6~rcbcckTYw џ٘6a6Ǐ ::BE ңOg2qmvHA~*֮)#pwwKϨR"W._ًd̔}:;fH0o.@Y1̈́ g/Уo7s<:~:jq9(U.ʝCpEʿYڱj=tj5@xX9se w\j{w\jcY#[(^[BRLq}=| Ϙq&UjTnmi{%P|:vkx=S 2g.h#vnͬ)sgެ/%q1F T[~8+xɢs J+Ikb2!0.K~ZF-l6"ps=n="#"#0.-Zw:6~;w8QHkjZ=̩sȞ3+9s`1[pŅ<--dU< yXbNR% mjUaތ:psΓLH M4q%űd|ڗgƵ_m>K.;M=_??f|= 3wVXGoc4&5[L&#.\E+tْay!6}|ݷxxz2g7OgZYf3 ߫A,"""""""""/H3L&LeEi/'fiXr|laܴ!||} `2HTd$75-_Jrz ߢ[Oh֠%gNj9^lv Of{,_% ;[<-ϕ˾ɉX9DЭg' b-\x奖u(RnlJΟH'3$+^8,coxBNcT7Xtsү9t⥊'MƎLdxY +ɸZ2&5+֓/oTUHn aCth @:o/re6Ldd3>nD|\<~d̘n?;CX|-B$T^ٳ7on.3#8},,/l¢o>ՇA9]{tJR~ {nݗFW:O7w7BC(V0kVj}I¯k L)}ѫwzXl b s2dLKf [N?k3O|B|sΛ;&*2wwGfDFT^93sqLfɟjݿr K,/99~g}R'5J0fx5|Z5thم" ;onZcѵ}OCYM\h4fz5m@9|Fo0|hρ_b͸)#)TdȀ,]L0r2ZwJ ||}ރ F-'AAA؞Ě=}ުj%4,;0t^m+@;ukGMxp?ȈHL&~ۇlBxLR={V,CG|S )@*fhd岵lݲLe$**qzz4Ҭ/K^p 3s/$~dQz[h,}Y2&ɋgX Ҧxzz۟wqqICYY ''':$#qʃ{}8,q"xzyj"Lr@??'.5-cݏ3`X_?zL*i߹ 0G0Ъ]sVlX p|W[  )@exwXuoV~17gTQ˾bҌdΚ:GFFq1V}V:0b"hլ=~n[##ض'Y..iprr">>O/Oei^$;"a퓎sޏ g/m,nLMǖO߳ }+j5+kKR̯ػgNQIJ}ñ_DxcMܛYt5%J#N Q8;[ gDGRDa2}1^ܹ;;ΙXz^lqnn,\لGcO,Y_?V/_#!k,;zys^'[qqܼq93SJy'ociDDD6 ~GVc DGRB^˜~pr ,2fLٺpNI;?hJW\#N 6 W״lضұ߅k0럔=ggObJJٹO2fʀd"[l_[6ԉ3$$$$? c ^0_~Ƶ؊H.ru~< @BBFҙ__G5qvv+o<:KZmhP//C:`F5lv5m՛qvJ,J%qBq,ق`x*J뚖_Ob̗9;&}?>h>yR7I~9fþldϙC`yr!0b8fMK2Pf%mR,Z0?nwpss^O-&)[==t<l&2"եhŽ#Ν@*V.O[dTRVMc2ɝ۟FM:~-WoPтL6ұ$O:zՅ8 $iA2dLO-\U;|"ׇ~ ymM\jLcִy9S~1;nnIf~S'2[wCZUxRT͛ ZƙE L&Aٷ,+"""""""""/rg ͫdvxbcb| ;-0[,]=(,syu*FDx$IBß:4kВw}#->.O - |\$'Op<<=ԸZym턅mQO_/#<,.L l6ivO1[Uw^)|Y,fL?3 ß^/sg`6?x+ߏ3 y/__DDDDDDDDDDaJت}sC>o/:vkK4EDDDDDDDDDD^YP$%+jyCn DDDDDDDDDDDD$P%""""""""""""/I5~HKU>'u(ݶ/Wh2DDDDDDDDDD$EI~bf 'dbJ4V$Q3DDDDDDDDDDD%""""""""""""/I5~HKDDDDDDDDDDDDR @KHb""""""""""l90ɾ%%(dn۩K&X """"""""""$w D^!f|H m+TCᗈ DDDDDDDDDDDD$P%""""""""""""/I5~HKDDDDDDDDDDDDRT~,ALLk\<wIHHpEGEuvΜG9C5SFddԿlEGǨEDDDDDDDDD$E0 t GNl6ҶS+zֿޖ[~r򽞇;SHݱmM`TZ>~Օ2Kp >n1 lٹiK.g͊uLf<9T(]ġGR"m;Z~P}}|Xnqq>SOdp?wNW״""""""""""l+:*{cXڳf72ˉ:B5d<^C͉ gҏū!}ַ@Ѓ`Ξ>O50w}}|Ѻ}sv;gNTZr`00[̞?ѨJ;rM붰9xzy2w|fN^eĸ!d5 {7 fٺEF=5+tCDDDDDDDDDD$Jd4jJU+!2fJ@.pss˃;&3Z4& ϴ 39sTY#1cW@Ⲋz&mZ;C' -HUi/o*RuϞ<^F <ɗ,Y3_o^Ǝ^jUH#"3y8 Pv5o [9.Τcسk?;ҍ\e2`* .bO{9y4GO|2|>X-64/ϻ#,R1Sc4۸KIS[t sX6߲C9>gK.gO{Ҥq&i߹ 6]?ܛ]71@8z۶DttL(ؼa+KC6KU\][ޏ?m<61ܷqq҆ZLu?%zj^;qi^<5qȻnf}M'Vyr&X Ȟ=+ggs'`` {lL91LDDDDDDDDDDdJ퓎^ӣoWN<Ǫekؿ'f!cz҂Y%jyp?qSOlJΟk3O|BY1rៃ=~ c!DFFqQf)$k,^(?qqٹjeNRJ_&@KW7W||׏\:W._G$M=1+ט>!F0"s.wnݡudɖ:ukҠQ=@ǥki;͚O(Q(9rfcfܾQcl6A ,4RBi O/ޮsHOY<;zv˓5+0t(nߺCN=G鼽zwv*R=;1}dm6~b߷͊kVor:q77WrͅlTْ{iY'>=ǏbƤ٘L&5|K7RDDDDDDDDDDQ)_3w·_/%oY6w3s<J*F@qv7_}/9Mvng5,3xl6O&jqә:~&Δ y&8(X7RDDDDDDDDDDq)zWy7uۯ`HEѺ}s~~} }04e+nӨi֯̏SdQŘ|lql`00ޝTe>3TRd˒} Y~dKDDvYٓ,f1 ܙ{pef̽|99iҮSk%'%i?ky #UZEIRn3zkHIR~zrrr֍Ml6ߵ&)Lf=LV=eKIQyղ {N)w[?x6T~% fxKn{Wd2P^>8 qlpGJJF\ g@%9SdzZ=_JN񺼓Dlrus|╒"7w7N689s2;/y׭曝Kw~gߌp"< NNNʕ;'wp647(1|Fòx>]epk qW/rDa~a~a~a~a~tvl%M6MpQ9):uRRRTr {Myxz<y@Rz+U&IΩOӳ|$1s>BkWoTJEpes>OUz/Y#ïk9 Uʫh"oZ{oTWa.SzHyy{it1 UVQo }ן9s~6oE!L4|Pk\{ɲe4bP˟Wt`!͚6G/^R eWO]s~zYOZ{%'lO.XgUp5kD۷ڴ]%ԧOUVQo1TR9IWHps]ٺ5 8j5}4:v=])=%I/_u?m*WUA GTp W+ʥ%Iϫ}T:ܑb/3~XLVMFM=yߨM~ʝKw$-vNsJL|nuZiO4{< oH>;zrxq5lBCժ] j٢ '(4䂖,\@rq1kuq5D[v_HJ]BE͟HkUS<~z *DIUYEԘ$I11=|BC.uЊoWHp,xՑC)h4mV:u"D?uczԌ)i[$Qf/P\>Zg] x^W\Յ0EEE @ I~RLھWumKy|sFw_\IҺ54kxp5b{tw2 jy ȷ(11Q%KPj$I%JWuUPA{ F?XzV$ӵm}{իS?iQ'JWsj╚1eZQ -I1e&_6m\\~e0d6g`/UZ~j͛=_cG} K%ImQWzlΚ҇Nq%BGԷ T[A4۵w 3u7w$ztf/Ѱ*%{Q;#z^O^JJ|\[vS=껟mݴM۵fA;~UZj>x}k6[kȀjٸF9JHj%"č ֝_ULto$i+Q,+'''IҤd0rwwm]~ˡM5қJ^;9bbb)88T'5~z~$wrjשZ4lgeZok\ y6m[vIF. phyy{\ڰfb=RBB^Jy}5|Tju?S!'''uN}S"$I%KЇFIJ}ӆu9UVQڴ~jԪ=;r'އ)7F8BԎ*9)Yt5"BqmF\SLLlc&kwTh!M7M˗|:K"#jݮ*Wk2M:uNsJc?}Ϲ6kDS'ΐ$5mXEQѢE9hmXI# W7zhʄZd yCuNUһo}&dX6] Ӽաs\\TXyxC[7mSm%I~ޢ+ؗ8;Mp&NRk5o|?a$I3䬹 g$}r}~Fmݸ]gW\\FKBr 9"E ?pB$Z~bct-fHŒ-1õi6kZI?{Nf͍ +RhA-*di{%IֲE+uq,_IIIJJNJ3v~7+N-˔xb 8W~)ӢHћs*^¯"~?|)χJJ.!I[>9r>Dt>(Xyڗ%,WU(+I:zi<#=ܕ;O.{rҹ33**2Z j5K=*DI2.22JGӒo>_5iX3|v_n\Y^J;ujE/IYfM{vQuuv=߰}=F榅_.KռK7L&=SFըUUTڵs$Un2o<~;U~i^k٦_J;UHwPWZyxz(r*V*g׮kw@>/O/\RLL}Oթ[;*ؠQ=c*T*D7o|;G;(g9땎$I{vVf-O/>K|XKFïPGOTdƍX5kWת 㥦[߶ݭ!ˮy{{޲SN't3Og_o_ P||\]s|`vG5kWv15Zm v 3ֱ#ǵpi^]/qFT3ҫ`e?j-*P0e.xzԭ]s2ytMpsº%$޼/n#rruU3pʥdv}܄*>.^..fEEE?*>>A>nT:a=rswU(+Y+Rühy{{IJ <<"oo/;B5jUmbŋܙUTNp.P׻@fSe*ϾcyV%J>rx䜳=<5qڇ*VOlR||ըldJ\\n1e~Bm;{q/*JU+jִ9jB.ZH#FMzXtkhtc_Ѩ\siĨ!$ZчSO7ƉUg3=WYnR'Иw'*:2ZJl6KV+00RV-ձ+}ymN]FVrOD;\>QhȅԠ7jeL~nM5iHsf~.mzCշK7jtc=TAsg}=xRѢE$sZo oĕ:{^jhӺ- N]6pKsj_e`PhH]\VUVMGSz>+W. tkѺmUW#ٗU|"$ggg%''kϮ?eSj痷<|]^*ziյ|JKJ}ލ/h5qMKzoTO*X(11QIII:|*ݻHJ]*sтly}W|F"u),#甒x6T~% >aZ|լb8RՆe4޹bIRbBB$i͚4vFG Oӹ6M1ѱho{} ûXtN(yyyy٭y-J>9o{j*!!Qv?k_dIm>ƎHAuS| z.^)!!A 旛kU\⯗2|=6i\]sjܨʑzBfL'H+S؝ ըG\7iϛ5s4ј&ɤE}}p:~U7}nw$ ~mUYM]^p{˿]Kꓔ1ב%UxF{6'Ѩ M`n=嶩ȋɳן#-VJIIY_i$''%YTVNsJ>^z3~Y>'I?59_JjJLݤIdng0p\&s,.yѿe+&&V^ĄJ"f:5h\_O@c~5|L9֌_x|d$!Z}դ˚'*DIIDlL<<=f)>>!~M\eM>|5*2J>9}| W.G.`<ʝ3aLLL&\\\ i$)%ŖKrُڼaf}o11rpO7>_N;Ng@V3<<5Z4'ww7ڻz~Lgf T}ûut9CASqi|="?֏ IDATd||q]_b~WX4k׍}n3M2zK=_Qi3Up/Y#;A:nqZ=߰d`@o٩ח_l]U+57mxߝb̪jfdcÄ_<*Y9   $)&:&ji+c믜8@_tyu`߯gUAN /X\YUJ999qi~nھu3WjCJ/u}z۾AA5dlN#KaᒤϦծ%Io zGQ$)%%EKY-K۞ZOijB-[R3V:ꝡ3tLqqq2 \mWrymٰmT2 *]F?L]{t aiXcTfU-X2W $I9sҙSg 8/OwwI5uvM1^}8BKYǎKᣆcv}PTUׯ\P'J*(༎;-]{ThAUb 5yt~u?f<3|,W¯cjӾFc1иYCYNC/{TJ볯f~BbzK~ެ:u_o/;B5jUTJmvҳP~\@,{<jԪsھW5|ڧR ]{$>kтlbŊ*<.^u6?v2SM(ӧpb x  {Μ>_߳{Fwd08kӯkW~8B>IRm윚*SR>^ҥKxONmz(G)ZH#FiCD 'pGgCW g$KrgLt<<=2Œ\\\2u>fWrR Eɜ9UE^IJA/I2M|Ih>_6m`CN\>|JLL4m]e      H 矣ǵx2=UIZZޞzX$ܙs1s=uN ׀}Uj9uFϕ$]i֝ң:uk'Iڳ{@"N0.fxht~HWbEy@nbgUf_$iSUVU\^jX|Y, <aUܹFֺ6H4qdޚt %Sp@ pG>9ծck竦-^l3?R.(!!Q*ӵqUE 7Q~$m\Y^JU+7oFvNeܑ&IVMVU:|J9s% V= >=jϛyxSl@ aVU'TSˍ׾^j߹^ЋB2ȰD%&&`4HvmMr}k=WCmQHp$pK/:krssU h4RXשj@!rssUWW >2SjφʯdAkIl=KEEf)>>An\̙͘__Et~x`f.ٙ xضe[V84/1|z:~bprrRph_chtV\|<84/13w.]F<&ը,ɨg)$80LzS:zF\ dؑڵc@&_c"( )Hph_c"9)Y..f ph_c\J*A! x l^U'+P C3RkPG6GpqqM$%%iGܙs;  ɤ6Zh[())/ )Q 8@1H͘LF%PAd3>9skt~L.mEڲa+_lXJ&)b@2w|ұZkA1:l&((XJQAd3IIrq1SAd#W*>.^E S AdK}{Kt)=&}:w\\\(w@|pw_p_p_p_p_p_( [m~hl%MEGEkwնygl]H 2&%%t=.9 f`0p1:wm)JV& j\KE>%?|><7 JF׬Z<͘|j汊ְA#Uha5nڐ L?S(B*V)Ŋh/iןjj`zQ]]O#S?ӑCd0TZE9l|rz]jڼ_3C7sh@!?'˖шCzpp`!͚6G/^R eWO]s޻[t .f-u,Ԏ_vU~jĪ~:4 ]̒ֺ6rezM~Ŋ "|ƍ>:]u_hq7\*Xn.607A: .C|r͞VW=yh{Tf:yxzh tEKP-E{wQ/JJ]r'sJIIQU47#]IIIv5RK$ʝKo3XFc-Vd2mVjkbj] w>(]BǾ#߼y4c,@?\gghL$޵Glܡ e?(::F?[mwhD|5h<,.iTҚ8I_,dIR5xq;sN3~.zR2| )$IӬOo^_ W+W||~۹[_/BFQrT6ʹe65nPkVә4t I5uq5Dg^}9{E^S5`p__q5lBCժ] j٢ '(4䂖,\@rq1kuۯ[v_H"EBE͟HkUS<~z *DIUYEԘ$>i ZC+^!cYS'Nݡ(::F)O/O͘6Et)?IF*VOkVe?J֭٤YƳUއSll\k+OnoXWArTuUܓA 9 ֮դy#%&&j:vێ.NAet_I=tzM5YZ7LjҼ&~8EoUbb*W򕞑lݸ]g{ВoVdT6ݤ5>Y˗$z&MO >>Y[d܄;oFdRtiIUV:(-^Dn>j:vm'IuoںifΝ9_ұ#ǵnFIaȷ>P5y*U>;>S>SbB\wn1#+66Na$I o[H!_\{jD$)0 HLV_N@-_^֝_ULto$i+Q,+'''IҤd0rwwm]~Jkރ$Z7iHo(Iz:~tߊM3PunYVj=WCuM;kۖjҼ}ᣆrj׹Z4hC]zt֍o:tk>߇EoPo^VERAx&'oo+cЁ*[t,Y>4J k7̩s*[)\t14LO-u?mc'z7$ =T%JSBB~B}U^zA ׸Qt RfK6ߪ,5hZFf/U?QS"%I o;v*5F/ծ%I^ޞ2Rro<}>^.}UAm\Ye*%OOUQfӿWת׬**h4j5=LKuPFQe_o^&ͦm۲C/\>߰~nuHyh43xgm\Y^JU+HujE{y}&[_ïH}3I*ZhjuCJ?YHҡlJ;z\ WRRҌ]1 ırfwH2%.^jU_ $)zsNKWu#T<[!C5$ww7ʓ[a%IgNUTdjf { x9#os/&Ir,DڦnډMZz_ W%Iiϓ'}.;O.IKJҵkQ:"KEn߭j6MzYz7ծckiR;iTQχh0UQEtbgM}&I N޺-x*Zd욘(W7Cg^my]׍σo ZOTdƍX5kWת 㥦[߶ݍE._J =o"}r0~9ïdxBB.7-Eo[ܹȫ3ֱ#ǵpiPhT]k9۫JV-&@}{O%"E S*ル潜|ܵ߯՛`0}w^ըUU^5vjԪ+=O?̯MjkQioKʗ?<䤟6}'7w4c&%%h4(?PK$ _5j6lMw>^j߹^+_po_+=#w\J_|@FM/_ U|\\\̊(>>,\?otz'nQ==*ŬVi[hRco׼:J]^fb꯽ٌ/dTv}2io٩IcbIRj%I/5o(i5sW!׻J47m﷍qsq&mp.۞>yF5B·/?c2t`!;K5wXsʩm[vjˆRKui{[յ|J_nvRwCn~2lT_|@5q[Gԃ)g'g9;;)22JϧBr $|c/^/Y\/$YV-f]dRh2%'']N _|^TpA PbbtQ|j=WCmQgܝ>yZn_po:piX ?_I v3un<~PEo;8K.MFkվeWul]˔m[Hz]}sSت6ߒkNDujC{Q}%YԲUL9Ζm+$8TmuR'/oO] $)rok+4oWzopI;\Gv-svI}V/W֩aujJ׬*Eӵ|nSYjRzQ6=O,ޜR2Y& <*jp\ZjV^oj2JŒĄyzyy}͚4vFG WTT2<7ͦXyy{jSiV}{_GFF3M3qTmZE6,N7ZrrbcK0˝jQ+(L/>.^VM ɒ㏎K{sb{'(1!Iڽk~D_.u)OO QNzKxM8]M5ɾ]Lt=ӽ~m6ҽE}Q33Z[~旫lئ\6Z)Zy@ ׿k%vڽ'' $?ߏ{}o43`O-2cΙ%C*W7lu{xz\>p\s5}olJnx%vomyRww7iu4뿬C/IDAT cƌ(>8j|r=\lVuVB|7mC`0<И9r(oUU;npuwUTjE|4[(Y\TxCbڲ-{@tje;;;+onN+weTfE=YG-+/{      DJVm x FEd+Ym/3r 99;d6fy9U@ SAAAalU-B,'g',?O:G,ɒLy\7 "˸qd]55,L/J㔕eyJ&YqHѵ\ sWD4J *ne2ltУ!>3*"Wu~PA~Eؔ6p `c*wۄ________ed^-Igd6_M9 cE1kW}i{^63Y]d;, P[t͚](A]u[ P 6@i|gWf ՙ3jpsy׶-?)t'*((Ш1ҭ$CGwiYsoo Zi7~UU_5;S[omآĄd5{46hR2cz5M=^4_ƨfHv՜P>}V[4OCuBz/;b0֫->~TIuˏ7/[%sY>Ƽ,Q|*_Q)zE3 񭯴seEDM =}U'+saФA\#Muh詡?y;X|mڶRzc({$iLz%>x5Ǧo{h6_'*)!IzUaÕeg()1Y,Zq]\\2aҸoڦOCI/+9Xo;(ݚ1c׭}Mk}?%IYYfd-ww-zsso1_.O̜$IgϤԉ-_R~GkVlPxJI5{UeEɎ2 V:?QM|@tdJS'{@~ҥK /;hu7YJKVX.[iղU۪IJL#ߒ4yt>ZOHOX6YODkWn1dg̱zfUBfK{ ;cC͐sZØQ%2+'²_++3KF QKt(U| 3988HL`EYOͭu}=3P/|Nt)ڹ*07Ǝ$=7E;s@YYf5%$$5*((^P.4>jzu;~Ziwׁ/ m?nZj%'ŨmrpV򪡠iWm%Iϝ Ο,s7|0CAQ=ﺚ6)XKYgG<-IJOЊoW@?F$Imm?3ß謣G5qjWN@eWjJ$`G}uYIڻ+Vaԭt8_F4U'tyM$ }J~%*MJjxUW&%Jfmޒ$7ʪVRΤJ~?zLߪ}<5!SJRJ=GZj[!gggj࠾][ 3?6N|P^5׫Bf-Uߍk7-$ICh%Kuh2ª_VY?:u͑d'Ʀï^%I W-'O޴OFz&MWhEDLU<ճsڥz8H1&Bg~5d]IYn_X$%$)b\\ϾTvvuYTEaX=Ф*UlTFVh(-D1a^hp+PiѼ57d ծ#V}>6GQ4mbrsԪMKIR^ݕ-Ѻx1[C5|KJ;߬[ߟH6|$ю׫Bgkw^xY6)w-x{|xEKҲvmnfg.+gDzseVaa 瓤ګR%ժ]S>ByjbKר&oZr$*((^^mK;cǎ[^kX[ERbBqۑq.Rդ:uЧ?W=b*<{1cIiTH9ԦÖQ*v~El[%'177Orrauהf)r}VDCyyyr+a7Z~~2͖[0ʍjQV%Nݽ_^~=Pkr旺}5qT !kF:|FcGժSK]+ErV_- wVreWUru՛cGYBiZI~ywѤStWTɅ&@;d;]˦VYQk 0/+ڽM/xջJPK*~^aҭwbͷcŷ"~w&QM횯~!]*&urZke˜e36|>7H9ׅxu{ `l:rwwS-D;[e=8TF )͚7ыPfܱuv*F>2U{'ԠmвEnsX}J X⮸I9k%2Z gs!`rk _.Ovh;RQԥ{gXT~_+9r     Eؔ6p `S*wۄ_=lt;N~NU@Cdw;`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7`7amuIENDB`graphql-ruby-2.2.17/guides/queries/skylight_example.png000077500000000000000000000476211476434635200232670ustar00rootroot00000000000000PNG  IHDRCPLTEĒ‚quyϛsدÄKN.ŇŲԦb1|"g)ڷ:˯ҤqԎǍxۮҡƊÃܺɐBx|VП7˼ٲ޽͚l\Esw{׮邆S˔̗֫nԇ臊ٵ|٥ʒuy}󳴶옚ƾrvzɽtx|#iM0IDATxա 0 K ?7@P(Pp(@P(Pp(0ͪPa^Dw4p-]CJࠁ8>16ҏᱯ]g'$e ` h(4'h(AC6%-rL( R@CߋP}5T ZC2V+ߠ @CCC?ϼP*b+ @CCC7"b{u:|8m՟UDJlbhh~?aL$Rй̭ [TLe-՟UTDB> 63υigFDO2n<;47.-S̫LzN Wr͙WqM<+Ȣ".kQ9z9ҝHLb\އ~mseiM T!۽W=uji}grg9q0]8s$>  mb u(7'DK86z7Кc-lQ ^JsO]*|T~D6#ppCɯݜZvyk=Lβ~dqz"YGCcU\0?I E _A S &S(u Q$lTR`2kXd8_8}oCr}{zk+t p۾(-X8*  U7M-\1FwS2:A{;#ovSf%@T=_\C#b̞1N*7ġKXCG r}Q)g;p" *P^h$rزv1"c_] yr|MI?Ufzqn .aeO8ԓ};")ථRmӑFb x*lXS[ĺÍrMms]:eZ3z+8' }g8q .<{* >(}dGq|BЍG7sލF@|5'ݧa1#oЫ;1Kz{$9f==N* ġK(^i35ߏ4pwcS >L=;cڹݤdJbf9YoI111\f='E|CO]tZ&O?Wh0qV١ͯʢЪbwJ5Д[6{s 3IJ;g{8T׈CYQ24zSޚ9P"rQ-vC`*=9Q=q ?!]'94-ҧ Fo,ɡӝޡOòӻs,.q +Ӓuj8A1<5EˡEeG.wYoNP6vXԡCQqy?sLW{MӔձ 1 0Ryu/="pi 9ph$s($}I;yh/8-ʡC{/8 #@C`PPp(  ! C C:$ABl%8@ٵִ8ƏA0@FOBA"HPRzx І]gazkܓ'',>O<Q"""""""""""*̸^93NZ+NVJĆ&Dt2Dl"DĆl(ʮgQ=J %bC2r%0gX$d7 *R?lɏ %P6m Jo.gw"Q@n$#'<2E,ڍP"6 zGgo^E~ǭiKcGm`"SDl(:uh4Լ~ qyN&0VyJP"6 }q [ >mJCet;@)8P{qxP"6 }q E;ltH4mtR, Džf8T{^ l%Pٟ^|8tzvYu]C 8pu!Yir:uBsU5n>;/e ctΰ/h}zJĆPWCCHfmo MZ4/]t76g1;Z5w'qgJ]dV6tnラOgCP6:/ckiJ0Xe v z-w$Xܕ8F܏_0ޥVA:ʜxhC/ݫ=omck;;mոn꒯'# &9BJSϱDl(z Q26PuH`)mhVmI7܉5""n3 !иVր.Xoϒ_bMmΆ~g] 8>ȷYnU Y6O(\%A \͂Xl҅B !E}sf<w~l7oO޸cCГvWΗ E*\>wUH 9ԈFՎu6zJP067C74Ye67 [n1p&O(ߡҵ:JJCt.zbc uqRe\Q摉H#z+ߡ0A΂E/Zis1`zPɺ(´SEtw_q(2^uˣC CГv:Cb BƅVX5Ysa ;tϡyh)(SyuW.C6JW(LHi4~TuѡСt);Rqr\>sh]2gNCsu.bˡhj_I9ߡ_m}4(;URؑUCH ~nr CvhX{M;txȱ,vN~϶hY 9x^Ё* T@{3 Hy2 8 +r<~!t(9֟ /liIJ_x1mRyʡ9Cݼ"~rj;1p"W%h[kT^גGBҡácջZmnܡg6,t76Z&C1Uw5q;|ðF׭I;:#`ϗ+\][5KJC (9|ԏ ,²UA>D$#pqh(?8ۗufΫDkk+Bҡ¡pOExI%CP:4%DCP@Cgt(JJB}Ħ:Q%C Qt(!СtoDBd@agq(8;M W% i>OJ n P¡P % P¡P % `ٵ0塇P06МRM衐KN mM{R!\[B~K d00di]M}~]eӟJl(i[VθwѕaY3x[!J6Pꎿo(V~̶}ςĝĜbӜwx>ۍP<6DP5C,j jz_놬PRICṆ"2fR DmS=lջmkaᣦ~r)9[3ݍ}yl( `5>~_]Iz.#2Cm411zMf=3 `9>ڟ@Z;yc]?57ebe?hxaL1ٚz͚yfF[.uJ"lE ȫ"쪕nldq }?S(^SDf6]U8Yj->ۍP>6DP~K1U#~0iz ]_uyPHe!𻽶Tp徆fCs㣸3E)>"$†a~Z8iAZT'lO.to֛1 . ^r_P θS'SǶP{$<:kkh;+ԆjSMA42#(7df4,Ⱥbhv>U{v֔E[/[swkh?ƥP{,fDwRC%@ - y,7L댺,Hېin83&ՠBŸf_^)}ڣFCLPfDbTP C}edܪB>L-YMcX, 1>i:'?0tU3nnܪ43>C g7'C2$JkCCP`ŒS{u ]C VO<62k6Ce;owg7'C2$J)iznȀ;}a 5sP,CQ=}x5Cؘ9UkwmF8qLq _0:=* 1TPO gP9χB3mh5F CWzp ? )sjALqy#ˊ-Mtgj3mC}ɽ-R)C%@ TǏchL~! C r Pݪ}6t r6 _6+R7ݵa.*9I;{fa׌wSih[juv{Dbt zJ*,JmC vТlC>*mhcQfizTM'ur hWTl:Nk>D :w"1TP C/.7UvR{ ܑƓ~({//qcMC%Ԥü, *l'֚jVOuiTzΞP|bOga'C[O 1%TUwwtCr:MᾸuڅ9liZ]^ϖگ }kN83g7.L*_b^CW >oC2?з/YK.JO 1r -t3&ߗP U1TP ]x%̜m%Joݍ8~lqNgk==P_*Vbݚ[&J*ub.J7?Z$"1T򵨋O 0 @[~> >8 CC8J8 CC8J8 CC:Ȥ! .Ƕ࡮\Tþ&aBh Vrj'P HPRzIC/ޤgaufLJ5=<9~AWBADDDl(0)FO ͉k֟E۞YbCB \ļ#}M nh,PJÄ3>>pBvn!}E_?GouˆQe#^VX*;^Ctچ ^]=j3V]B} lg'dh::<;X ?%*aC˶p_ |b0/ ;yYء 4P9qfqүHcA@#Ob'S@eΟwѯB~@v4TϦsPƄʜ7'(WCJ'ݾ17ͨ9#hTC v(IBγy\If| ſЈjIi6PC̙$!gP+ibuY,4Hv ١̿3IBءu@IɟwGςʜĜ9NrMF!m D%I*}ۭw[%wBD.=ǡ|ܽdZ'ebz:ZݨeCvhCBu2 >8 ϼWU'yW] 4g*ۡIU|c ]1vңk,y p+T$j;{jI^F8KTCgS=~4ʰCk H k@_9UŋEZ 'v)Fs{{G"QbҮ):t-fy@O*mDY k;TqHe\\Xy)iteeomC"[UTr~;DZPˎ m T AYC{3CLs*]T^ϒgۻ\ҕM=k7Tm cPbCOWwZ}AV3V5t B[WOâM`C5+@,k;[12=m^ܳo(R$Wۼ ]C쾆PcC =MMwwtmYBP K'(\͑)w|kYPK]={~|| %6/h:~X,fi媻IL`o6wdsˤӆnf;ԗbH_}h|m"Q#63VzLMLs~\+Uܳ=؞[x֢z+B4t.56j}=iPbC62l(z_nF ^] cژʿVV#JJٙ*v#Jz_oÕ g|$l(ePzJ7cCcCl(e!:36-F *'Btfl(JDDņd ` o/N@ " Pp( C`p(D8 C ¡<ثc``<y`g N>` CsǺiQUɛ%"1X2Rw/,1   L cpMې2{*qfzBDĆ?-b[ԶpX}G[KRQEqP""CҰ XV"} J荋D/uOCn!4T,H ȴ-661vz|Q]דl(u ^n O74ko1[< 6c`Rd3X_}IFh$JD†>>}Na+*64K[B@,k6t"1^[3y8f1'P;uC $JD†>h]w4:Kjh-[BH>,g0-З1Oټz ݋hK4&\"6)d!nFvC/g  }7VōE+R53;kurs'Vw(uC!B1. }<[͆UCc_C[$JDÆ>p}iZ\tĆb>$@(o5j$gmUU55Js %NaCw]mq_ome%UCUbD;\^MD)l[lDY~\ 05:uzݽ萵^ nhs %NaCwǺiQE'œLɖ!@){|AZG @݄Y"4}Nflu+qʸJxuڪ#զa_xЫ^Uy0.۩@h#vU.:.snq}e`1')z܎Zn2b:xXRT}Ze 4^D l͆P7;vltv7qt 4ch =ظRZCmtڛpr[ζL^?V7vqVDͷFv. :oZl ,,Ūlq_eUոL[?Euq*GTSi4ʹPX   GPh dX?9EާOq44Ph A 0A򈁊IuTBs @1z(<PPx($PHCC R76kSJEo4RQߪLy(y(pR'x(ǽ,IBFAuN;)8y)o>8p)q~jytRvKU7tq@B:СdCWjbaI6FקARotz!n}U}%Bv;С J:46]wA`ZhСa-N|bث6y@5uݤy~sW 'M wlJQСdCo2:ž)Ln,ǫVqi*G{L|e’y=jRPd~6}c]? kw}3S3tʆOkAƇ\.K.C_@(iA AK!~xM< =r-~̎}X -v2ˌc_)ڡ\9t"2LvPaC-zLPUʅl 6S&u0֡˿.(rQXZ:r̆Vk'˲ZG⹾$~ruo2,'_ﻯsjꪺvŝ7;իS?:Xug I'N=.i^嫈GB2Zwdy xd3(<׾Zw'+Uxd! AQu7wC֮7O퉷z\_nm>{&u%uD!e¬V!l (B9|%Uu;Ds`rȡK(*_mqdV7~  r1 Gqvg_91;wTe( &8H ء!g/gd&n{`f ee΁yU}ۈj]~N*] wcwxԍ׀k=%T`m^\2nj[cn3(<׾[١Ks\ f&8N\$rC@ehXWwrۡ [֊S>P֡ӤTw WWh|BK[͝{N׵/_;Л?Q[?K#F>sxOMgVqk. m].y/ JXrC Td#@ic^Q+ƎڀXM$kf|֡7C-I99y!CΥM ZV tG 1Uj#!1 H3Ji9{>kaC-IWhvsDC =Wq&$ `Mk}Iњ)OrGqv #iÕ]}G9ơgREv.4u$N٣PVI:\u><||Bo)}OGqzt];=L]Cy ʦ(LqM2 ;Jٍ|֡ ֡dE -]Y)P˜ @pe9W.64;ttMGqvIm084Kɳ[c&rR|Vdv(e7:YZް&Q7,T]ZCƸ,$XP9G"&2BQ>m6U~6:cxf5ss|%S<P־D>Jٍ|֡7C-=qmZqM]v؊^쬄>|(w]ٚ]mTybwb8gQh\CW9&wInӵ(|DbvXsTᢖ8/g^Q9gjbj9MK,w禍CqBQC~C7`|iA@ 9L]5ws3(QCx r?y 0΍#xfDZqi Z. n9C5UF'IHÎB F*Jmb}s ,dq ό8ZKb^sKQQ8pArzXWĿ/i9{f{|=,C-'sbl7(^h:lYno]_| O2jхՁOAB}׼zϣ΍Gyc,fbUHWI;e,BAbDgb26nd?p; սI 5jܳm Qs*!Cj\<_dhO<;?֬z@lng"$ͪ+Rg,[ػ +Ti uw|2L۳ixiXO,9]!a*m"C@~ #zY7 ^#C@@ 5!C@@ 5!C@gQ8SI.p-$@mlVkAXH} Ɨz3uN[~?qgK l(у`CP""P""DDD7DDD %"z7jĆ}n =$6JD0DD_ JDb?6&ki?N"$kzYobC!< aLL+r3Ƣ1g+woja&*+L 6|;P"z ;8quil_h/Kى/qT2YHJD`ӓpjd^5YVRDۺ򺮷ˆne+y+[2-6 |#m'G=q^Tĸlr5؆͉hɆP" ŨR[FCL:bxPK41%ٿAi+ 糃.Ey,;TZբ,繴&2I64l(d0tiJO5t7r=hODk ]Re?pb0uy.xu>Ɇ %aC;0v /w([bM(]Un&hszlhP" IOZD^/ 74Ş K8dK0.0ZC2([hT3PbO`C((ڨ:d^/╷˸g\%PSM{V:w E[7k!`C(0$auQEo\xC'G -u^~m Wʽmr{|ɭrQpc;S*|Фs`, 꽡6;mQmckǑXlhP" & ]q~-Ӵv7,k[}?ou`L"ФkqI64l(huoK|- 0[b-^KΞw_5{;Ɇ %{P}X؈uDtk4;$FCKϢ`K1й lhP" 3&m G&nVrU XC mu\w@!jw[DzsYsI64l(>b!ߧx[kۭٴktREzs؆vasUD;`7ԟdCQ@ θwyGfCZC hmv^uC &DFɎ/kkl•,>uޖIQ'[xf*#{v߁59/a3u?5ԛ-!`C(,i]7|B[OuP짭~MҺf'0?٫ca `PJaepClá] Cwr(4{I,)ĩY u ]|^uՙy$PIw{&evaC6A,dg#{O6 :A):zOOׇjofr*6+3P" -,5z-# Xَo6E+yEQ DQ & ̤SDAnU{ӓ̤_ Ɔ-kh:"{qhh(B&3 nuD%5Bsr՞4r;'"U Nu71F:-FW`CDDj["̯HFaO}{2:S Z+8f_{Q+P"e adU}Ɔ%!pI]F&uCyl(Ѣv. ij b'f7t|ݫ tJPdžP"z<:JS.oL]4ɂ꽩ɔ l(=!>Cp ޏuCGg2 ZP71i*%P";&.7l-|4.<4'BtCu7=YJD4'-\b+LCsR ge6A8[I8TIԤT*5{6t 6h@扜1LƠnh3} UN)jorR= JD4gA4|rO3~%cCS .P*uոIS[' B266l( 6sȧ3oVp0lEѓӓZZU)bl(6ba 1V[i0p#1CԈ,LnHݎ@`.eIENDB`graphql-ruby-2.2.17/guides/queries/timeout.md000066400000000000000000000054521476434635200212110ustar00rootroot00000000000000--- title: Timeout layout: guide doc_stub: false search: true section: Queries desc: Cutting off GraphQL execution index: 5 --- You can apply a timeout to query execution with the `GraphQL::Schema::Timeout` plugin. For example: ```ruby class MySchema < GraphQL::Schema use GraphQL::Schema::Timeout, max_seconds: 2 end ``` After `max_seconds`, no new fields will be resolved. Instead, errors will be added to the `errors` key for fields that weren't resolved. __Note__ that this _does not interrupt_ field execution (doing so is [buggy](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/)). If you're making external calls (eg, HTTP requests or database queries), make sure to use a library-specific timeout for that operation (eg, [Redis timeout](https://github.com/redis/redis-rb#timeouts), [Net::HTTP](https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html)'s `ssl_timeout`, `open_timeout`, and `read_timeout`). ## Custom Error Handling To log the error, provide a subclass of `GraphQL::Schema::Timeout` with an overridden `handle_timeout` method: ```ruby class MyTimeout < GraphQL::Schema::Timeout def handle_timeout(error, query) Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}") end end class MySchema < GraphQL::Schema use MyTimeout, max_seconds: 2 end ``` ## Customizing the Timeout Window To dynamically pick a timeout duration (or bypass it), override {{ "GraphQL::Schema::Timeout#max_seconds" | api_doc }} in your subclass. To bypass the timeout altogether, `max_seconds` can return `false`. For example: ```ruby class MyTimeout < GraphQL::Schema::Timeout # Allow 10s for an incoming mutation, but don't apply any timeout for an admin user. def max_seconds(query) if query.context[:current_user]&.admin? false elsif query.mutation? 10 else super end end end # ... class MySchema < GraphQL::Schema use MyTimeout, max_seconds: 5 end ``` ## Validation and Analysis Queries can originate from a user, and may be crafted in a manner to take a long time to validate against the schema. It is possible to limit how many seconds the static validation rules and analysers are allowed to run before returning a validation timeout error. The default is no timeout. For example: ```ruby class MySchema < GraphQL::Schema # Applies to static validation and query analysis validate_timeout 10 end ``` **Note:** This configuration uses Ruby's built-in `Timeout` API, which can interrupt IO calls mid-flight, resulting in [very weird bugs](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). None of GraphQL-Ruby's validators make IO calls but if you want to use this configuration and you have custom static validators that make IO calls, open an issue to discuss implementing this in an IO-safe way. graphql-ruby-2.2.17/guides/queries/tracing.md000066400000000000000000000131041476434635200211430ustar00rootroot00000000000000--- title: Tracing layout: guide doc_stub: false search: true section: Queries desc: Observation hooks for execution index: 11 redirect_from: - /queries/instrumentation --- {{ "GraphQL::Tracing::Trace" | api_doc }} provides hooks to observe and modify events during runtime. Tracing hooks are methods, defined in modules and mixed in with {{ "Schema.trace_with" | api_doc }}. ```ruby module CustomTrace def parse(query_string:) # measure, log, etc super end # ... end ``` To include a trace module when running queries, add it to the schema with `trace_with`: ```ruby # Run `MyCustomTrace` for all queries class MySchema < GraphQL::Schema trace_with(MyCustomTrace) end ``` For a full list of methods and their arguments, see {{ "GraphQL::Tracing::Trace" | api_doc }}. By default, GraphQL-Ruby makes a new trace instance when it runs a query. You can pass an existing instance as `context: { trace: ... }`. Also, `GraphQL.parse( ..., trace: ...)` accepts a trace instance. ## Trace Modes You can attach a trace module to run only in some circumstances by using `mode:`. For example, to add detailed tracing for only some requests: ```ruby trace_with DetailedTrace, mode: :detailed_metrics ``` Then, to opt into that trace, use `context: { trace_mode: :detailed_metrics, ... }` when executing queries. Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration). ## ActiveSupport::Notifications You can emit events to `ActiveSupport::Notifications` with an experimental tracer, `ActiveSupportNotificationsTrace`. To enable it, install the tracer: ```ruby # Send execution events to ActiveSupport::Notifications class MySchema < GraphQL::Schema trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace) end ``` ## Monitoring Several monitoring platforms are supported out-of-the box by GraphQL-Ruby (see platforms below). Leaf fields are _not_ monitored (to avoid high cardinality in the metrics service). ## AppOptics [AppOptics](https://appoptics.com/) instrumentation will be automatic starting with appoptics_apm-4.11.0.gem. For earlier gem versions please add appoptics_apm tracing as follows: ```ruby require 'appoptics_apm' class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::AppOpticsTrace end ```

## Appsignal To add [AppSignal](https://appsignal.com/) instrumentation: ```ruby class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::AppsignalTrace end ```
{{ "/queries/appsignal_example.png" | link_to_img:"appsignal monitoring" }}
## New Relic To add [New Relic](https://newrelic.com/) instrumentation: ```ruby class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::NewRelicTrace # Optional, use the operation name to set the new relic transaction name: # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true end ```
{{ "/queries/new_relic_example.png" | link_to_img:"new relic monitoring" }}
## Scout To add [Scout APM](https://scoutapp.com/) instrumentation: ```ruby class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::ScoutTrace end ```
{{ "/queries/scout_example.png" | link_to_img:"scout monitoring" }}
## Skylight To add [Skylight](https://www.skylight.io) instrumentation, you may either enable the [GraphQL probe](https://www.skylight.io/support/getting-more-from-skylight#graphql) or use [ActiveSupportNotificationsTracing](/queries/tracing.html#activesupportnotifications). ```ruby # config/application.rb config.skylight.probes << "graphql" ```
{{ "/queries/skylight_example.png" | link_to_img:"skylight monitoring" }}
GraphQL instrumentation for Skylight is available in versions >= 4.2.0. ## Datadog To add [Datadog](https://www.datadoghq.com) instrumentation: ```ruby class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::DataDogTrace end ``` For more details about Datadog's tracing API, check out the [Ruby documentation](https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md) or the [APM documentation](https://docs.datadoghq.com/tracing/) for more product information. ## Prometheus To add [Prometheus](https://prometheus.io) instrumentation: ```ruby require 'prometheus_exporter/client' class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::PrometheusTrace end ``` The PrometheusExporter server must be run with a custom type collector that extends `GraphQL::Tracing::PrometheusTracing::GraphQLCollector`: ```ruby # lib/graphql_collector.rb if defined?(PrometheusExporter::Server) require 'graphql/tracing' class GraphQLCollector < GraphQL::Tracing::PrometheusTrace::GraphQLCollector end end ``` ```sh bundle exec prometheus_exporter -a lib/graphql_collector.rb ``` ## Sentry To add [Sentry](https://sentry.io) instrumentation: ```ruby class MySchema < GraphQL::Schema trace_with GraphQL::Tracing::SentryTrace end ```
{{ "/queries/sentry_example.png" | link_to_img:"sentry monitoring" }}
## Statsd You can add Statsd instrumentation by initializing a statsd client and passing it to {{ "GraphQL::Tracing::StatsdTrace" | api_doc }}: ```ruby $statsd = Statsd.new 'localhost', 9125 # ... class MySchema < GraphQL::Schema use GraphQL::Tracing::StatsdTrace, statsd: $statsd end ``` Any Statsd client that implements `.time(name) { ... }` will work. graphql-ruby-2.2.17/guides/related_projects.md000066400000000000000000000137121476434635200213750ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Related Projects section: Other desc: Code, blog posts and presentations about GraphQL Ruby --- Want to add something? Please open a pull request [on GitHub](https://github.com/rmosolgo/graphql-ruby)! ## Code - `graphql-ruby` + Rails demo ([src](https://github.com/rmosolgo/graphql-ruby-demo) / [heroku](https://graphql-ruby-demo.herokuapp.com)) - `graphql-ruby` + Sinatra demo ([src](https://github.com/robinjmurphy/ruby-graphql-server-example) / [heroku](https://ruby-graphql-server-example.herokuapp.com/)) - [`graphql-batch`](https://github.com/shopify/graphql-batch), a batched query execution strategy - [`graphql-cache`](https://github.com/stackshareio/graphql-cache), a resolver-level caching solution - [`graphql-devise`](https://github.com/graphql-devise/graphql_devise), a gql interface to handle authentication with Devise - [`graphql-docs`](https://github.com/gjtorikian/graphql-docs), a tool to automatically generate static HTML documentation from your GraphQL implementation - [`graphql-metrics`](https://github.com/Shopify/graphql-metrics), a plugin to extract fine-grain metrics of GraphQL queries received by your server - [`graphql-stitching`](https://github.com/gmac/graphql-stitching-ruby), tools to combine multiple local and remote schemas into a single graph that queries as one - [`graphql-groups`](https://github.com/hschne/graphql-groups), a DSL to define group- and aggregation queries with graphql-ruby - Rails Helpers: - [`graphql-activerecord`](https://github.com/goco-inc/graphql-activerecord) - [`graphql-rails-resolve`](https://github.com/colepatrickturner/graphql-rails-resolver) - [`graphql-query-resolver`](https://github.com/nettofarah/graphql-query-resolver), a graphql-ruby add-on to minimize N+1 queries. - [`graphql-rails_logger`](https://github.com/jetruby/graphql-rails_logger), a logger which allows you to inspect GraphQL queries in a more readable format. - [`apollo_upload_server-ruby`](https://github.com/jetruby/apollo_upload_server-ruby), a middleware which allows you to upload files with GraphQL and multipart/form-data using [`apollo-upload-client`](https://github.com/jaydenseric/apollo-upload-client) library on front-end. - [`graphql-sources`](https://github.com/ksylvest/graphql-sources) a collection of common GraphQL [sources](https://graphql-ruby.org/dataloader/sources.html) to simplify using `ActiveRecord`, `ActiveStorage`, `Rails.cache`, and more. - [`search_object_graphql`](https://github.com/rstankov/SearchObjectGraphQL), a DSL for defining search resolvers for GraphQL. - [`action_policy-graphql`](https://github.com/palkan/action_policy-graphql), an integration for using [`action_policy`](https://github.com/palkan/action_policy) as an authorization framework for GraphQL applications. - [`graphql_rails`](https://github.com/samesystem/graphql_rails), Rails way GraphQL build tool - [`graphql-rails-generators`](https://github.com/ajsharp/graphql-rails-generators), Generate graphql-ruby mutations, types and input types from your ActiveRecord models. - [`graphql-ruby-fragment_cache`](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache), a tool for caching response fragments. - [`graphql-ruby-persisted_queries`](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries), the implementation of [Apollo persisted queries](https://github.com/apollographql/apollo-link-persisted-queries). - [`rubocop-graphql`](https://github.com/DmitryTsepelev/rubocop-graphql), [rubocop](https://github.com/rubocop-hq/rubocop) extension for enforcing best practices. - [`apollo-federation-ruby`](https://github.com/Gusto/apollo-federation-ruby), a Ruby implementation of the Apollo Federation [subgraph spec](https://www.apollographql.com/docs/federation/subgraph-spec/). ## Blog Posts - Building a blog in GraphQL and Relay on Rails [Introduction](https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-getting-started-955a49d251de), [Part 1]( https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-creating-types-and-schema-b3f9b232ccfc), [Part 2](https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-react-component-cb3f9ee95eca) - https://medium.com/@khor/relay-facebook-on-rails-8b4af2057152 - https://blog.jacobwgillespie.com/from-rest-to-graphql-b4e95e94c26b#.4cjtklrwt - https://jonsimpson.ca/parallel-graphql-resolvers-with-futures/ - Active Storage meets GraphQL: [Direct uploads](https://evilmartians.com/chronicles/active-storage-meets-graphql-direct-uploads) and [Exposing attachment URLs](https://evilmartians.com/chronicles/active-storage-meets-graphql-pt-2-exposing-attachment-urls) - [Exposing permissions in GraphQL APIs with Action Policy](https://evilmartians.com/chronicles/exposing-permissions-in-graphql-apis-with-action-policy) - [Reporting non-nullable violations in graphql-ruby properly](https://evilmartians.com/chronicles/reporting-non-nullable-violations-in-graphql-ruby-properly) - [How to GraphQL with Ruby, Rails, Active Record, and no N+1](https://evilmartians.com/chronicles/how-to-graphql-with-ruby-rails-active-record-and-no-n-plus-one) ## Screencasts - [GraphQL Basics in Rails 5](https://rubyplus.com/episodes/271-GraphQL-Basics-in-Rails-5) ## Presentations - [Rescuing Legacy Codebases with GraphQL](https://speakerdeck.com/nettofarah/rescuing-legacy-codebases-with-graphql-1) by [@nettofarah](https://twitter.com/nettofarah) ## Tutorials - [How To GraphQL](https://www.howtographql.com/graphql-ruby/0-introduction/) by [@rstankov](https://github.com/rstankov) - [GraphQL Ruby CRUD Tutorial](https://www.blook.pub/books/graphql-rails-tutorial) by [@kohheepeace](https://twitter.com/kohheepeace) - Rails/GraphQL + React/Apollo Tutorial ([Part 1](https://evilmartians.com/chronicles/graphql-on-rails-1-from-zero-to-the-first-query), [Part 2](https://evilmartians.com/chronicles/graphql-on-rails-2-updating-the-data), [Part 3](https://evilmartians.com/chronicles/graphql-on-rails-3-on-the-way-to-perfection)) by [@evilmartians](https://twitter.com/evilmartians) graphql-ruby-2.2.17/guides/relay/000077500000000000000000000000001476434635200166325ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/relay/range_add.md000066400000000000000000000005541476434635200210640ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Relay title: RangeAdd helper for mutations desc: A helper for Relay's RANGE_ADD operations index: 1 --- Relay specifies `RANGE_ADD` operations for adding items to connections. GraphQL-Ruby ships with {{ "GraphQL::Relay::RangeAdd" | api_doc }} to help implement this. Check the API docs for a usage example. graphql-ruby-2.2.17/guides/schema/000077500000000000000000000000001476434635200167565ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/schema/definition.md000066400000000000000000000152761476434635200214430ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Definition desc: Defining your schema index: 1 --- A GraphQL system is called a _schema_. The schema contains all the types and fields in the system. The schema executes queries and publishes an {% internal_link "introspection system","/schema/introspection" %}. Your GraphQL schema is a class that extends {{ "GraphQL::Schema" | api_doc }}, for example: ```ruby class MyAppSchema < GraphQL::Schema max_complexity 400 query Types::Query use GraphQL::Batch # Define hooks as class methods: def self.resolve_type(type, obj, ctx) # ... end def self.object_from_id(node_id, ctx) # ... end def self.id_from_object(object, type, ctx) # ... end end ``` There are lots of schema configuration options: - [root objects, introspection and orphan types](#root-objects-introspection-and-orphan-types) - [object identification hooks](#object-identification-hooks) - [execution configuration](#execution-configuration) - [context class](#context-class) - [default limits](#default-limits) - [plugins](#plugins) For defining GraphQL types, see the guides for those types: {% internal_link "object types", "/type_definitions/objects" %}, {% internal_link "interface types", "/type_definitions/interfaces" %}, {% internal_link "union types", "/type_definitions/unions" %}, {% internal_link "input object types", "/type_definitions/input_objects" %}, {% internal_link "enum types", "/type_definitions/enums" %}, and {% internal_link "scalar types", "/type_definitions/scalars" %}. ## Root Objects, Introspection and Orphan Types A GraphQL schema is a web of interconnected types, and it has a few starting points for discovering the elements of that web: __Root types__ (`query`, `mutation`, and `subscription`) are the [entry points for queries to the system](https://graphql.org/learn/schema/#the-query-and-mutation-types). Each one is an object type which can be connected to the schema by a method with the same name: ```ruby class MySchema < GraphQL::Schema # Required: query Types::Query # Optional: mutation Types::Mutation subscription Types::Subscription end ``` __Introspection__ is a built-in part of the schema. Every schema has a default introspection system, but you can {% internal_link "customize it","/schema/introspection" %} and hook it up with `introspection`: ```ruby class MySchema < GraphQL::Schema introspection CustomIntrospection end ``` __Orphan Types__ are types which should be in the schema, but can't be discovered by traversing the types and fields from `query`, `mutation` or `subscription`. This has one very specific use case, see {% internal_link "Orphan Types", "/type_definitions/interfaces#orphan-types" %}. ```ruby class MySchema < GraphQL::Schema orphan_types [Types::Comment, ...] end ``` ## Object Identification Hooks A GraphQL schema needs a handful of hooks for finding and disambiguating objects while queries are executed. __`resolve_type`__ is used when a specific object's corresponding GraphQL type must be determined. This happens for fields that return {% internal_link "interface", "/type_definitions/interfaces" %} or {% internal_link "union", "/type_definitions/unions" %} types. The class method `def self.resolve_type` is used: ```ruby class MySchema < GraphQL::Schema def self.resolve_type(abstract_type, object, context) # Disambiguate `object`, from among `abstract_type`'s members # (`abstract_type` is an interface or union type.) end end ``` `resolve_type` is also used by `loads:` to confirm that loaded objects match the configured type. __`object_from_id`__ is used by the `node(id: ID!): Node` field and `loads:` configuration. It receives a unique ID and must return the object for that ID, or `nil` if the object isn't found (or if it should be hidden from the current user). __`id_from_object`__ is used to implement `Node.id`. It should return a unique ID for the given object. This ID will later be sent to `object_from_id` to refetch the object. See the {% internal_link "Object Identification guide", "/schema/object_identification" %} for more information about these methods. ## Execution Configuration __`trace_with`__ attaches tracer modules, see {% internal_link "Tracing", "/queries/tracing" %} for more. ```ruby class MySchema < GraphQL::Schema trace_with MetricTracer end ``` __`query_analyzer`__ and __`multiplex_analyzer`__ accept processors for ahead-of-time query analysis, see {% internal_link "Analysis", "/queries/ast_analysis" %} for more. ```ruby class MySchema < GraphQL::Schema query_analyzer MyQueryAnalyzer end ``` __`lazy_resolve`__ registers classes with {% internal_link "lazy execution", "/schema/lazy_execution" %}: ```ruby class MySchema < GraphQL::Schema lazy_resolve Promise, :sync end ``` __`type_error`__ handles type errors at runtime, read more in the {% internal_link "Type errors guide", "/errors/type_errors" %}. ```ruby class MySchema < GraphQL::Schema def self.type_error(type_err, context) # Handle `type_err` in some way end end ``` __`rescue_from`__ accepts error handlers for application errors, for example: ```ruby class MySchema < GraphQL::Schema rescue_from(ActiveRecord::RecordNotFound) { "Not found" } end ``` ## Context Class Usually, `context` is an instance of {{ "GraphQL::Query::Context" | api_doc }}, but you can create a custom subclass and attach it with `.context_class`, for example: ```ruby class CustomContext < GraphQL::Query::Context # Shorthand to get the current user def viewer self[:viewer] end end class MySchema < GraphQL::Schema context_class CustomContext end ``` Then, during execution, `context` will be an instance of `CustomContext`. ## Default Limits `max_depth` and `max_complexity` apply some limits to incoming queries. See {% internal_link "Complexity and Depth", "/queries/complexity_and_depth" %} for more. `default_max_page_size` applies limits to `Connection` fields. ```ruby class MySchema < GraphQL::Schema max_depth 15 max_complexity 300 default_max_page_size 20 end ``` ## Plugins A plugin is an object that responds to `#use`. Plugins are used to attach new behavior to a schema without a lot of API overhead. For example, the gem's {% internal_link "monitoring tools", "/queries/tracing#monitoring" %} are plugins: ```ruby class MySchema < GraphQL::Schema use(GraphQL::Tracing::NewRelicTracing) end ``` ## Extra Types Documentation-only types can be attached to the schema using {{ "Schema.extra_types" | api_doc }}. Types passed to this method will _always_ be available in introspection queries and SDL print-outs. ```ruby class MySchema < GraphQL::Schema # These aren't for queries, but will appear in documentation: extra_types SystemErrorType, RateLimitExceptionType end ``` graphql-ruby-2.2.17/guides/schema/dynamic_types.md000066400000000000000000000253601476434635200221560ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Dynamic types and fields desc: Using different schema members for each request index: 8 --- You can use different versions of your GraphQL schema for each operation. To do this, implement `visible?(context)` on the parts of your schema that will be conditionally accessible. Additionally, many schema elements have definition methods which are called at runtime by GraphQL-Ruby. You can re-implement those to return any valid schema objects. GraphQL-Ruby caches schema elements for the duration of the operation, but if you're making external service calls to implement the methods below, consider adding a cache layer to improve the client experience and reduce load on your backend. At runtime, ensure that only one object is visible per name (type name, field name, etc.). (If `.visible?(context)` returns `false`, then that part of the schema will be hidden for the current operation.) When using dynamic schema members, be sure to include the relevant `context: ...` when [generating schema definition files](#schema-dumps). ## Different fields You can customize which field definitions are used for each operation. ### Using `#visible?(context)` To serve different fields to different clients, implement `def visible?(context)` in your {% internal_link "base field class", "/type_definitions/extensions#customizing-fields" %}: ```ruby class Types::BaseField < GraphQL::Schema::Field def initialize(*args, for_staff: false, **kwargs, &block) super(*args, **kwargs, &block) @for_staff = for_staff end def visible?(context) super && case @for_staff when true !!context[:current_user]&.staff? when false !context[:current_user]&.staff? else true end end end ``` Then, you can configure fields with `for_staff: true|false`: ```ruby field :comments, Types::Comment.connection_type, null: false, description: "Comments on this blog post", resolver_method: :moderated_comments, for_staff: false field :comments, Types::Comment.connection_type, null: false, description: "Comments on this blog post, including unmoderated comments", resolver_method: :all_comments, for_staff: true ``` With that configuration, `post { comments { ... } }` will use `def moderated_comments` when `context[:current_user]` is `nil` or is not `.staff?`, but when `context[:current_user].staff?` is `true`, it will use `def all_comments` instead. ### Using `.fields(context)` and `.get_field(name, context)` To customize the set of fields used at runtime, you can implement `def self.fields(context)` in your type classes. It should return a Hash of `{ String => GraphQL::Schema::Field }`. Along with this, you should implement `.get_field(name, context)` to return a field for `name`, if it should exist. For example: ```ruby class Types::User < Types::BaseObject def self.fields(context) all_fields = super if !context[:current_user]&.staff? all_fields.delete("isSpammy") # this is staff-only end all_fields end def self.get_field(name, context) field = super if field.graphql_name == "isSpammy" && !context[:current_user]&.staff? nil # don't show this field to non-staff else field end end end ``` ### Hidden Return Types Besides field visibility described above, if an field's return type is hidden (that is, it implements `self.visible?(context)` to return `false`), then the field will be hidden too. ## Different arguments As with fields, you can use different sets of argument definitions for different GraphQL operations. ### Using `#visible?(context)` To serve different arguments to different clients, implement `def visible?(context)` in your {% internal_link "base argument class", "/type_definitions/extensions#customizing-arguments" %}: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument def initialize(*args, for_staff: false, **kwargs, &block) super(*args, **kwargs, &block) @for_staff = for_staff end def visible?(context) super && case @for_staff when true !!context[:current_user]&.staff? when false !context[:current_user]&.staff? else true end end end ``` Then, you can configure arguments with `for_staff: true|false`: ```ruby field :user, Types::User, null: true, description: "Look up a user" do # Require a UUID-style ID from non-staff clients: argument :id, ID, required: true, for_staff: false # Support database primary key lookups for staff clients: argument :id, ID, required: false, for_staff: true argument :database_id, Int, required: false, for_staff: true end def user(id: nil, database_id: nil) # ... end ``` That way, any staff client will have the option of `id` or `databaseId` while non-staff clients must use `id`. ### Using `def arguments(context)` and `def get_argument(name, context)` Also, you can implement `def arguments(context)` on your base field class to return a Hash of `{ String => GraphQL::Schema::Argument }` and `def get_argument(name, context)` to return a {{ "GraphQL::Schema::Argument" | api_doc }} or `nil`. . If you take this approach, you might want some custom field classes for any types or resolvers that use these methods. That way, you don't have to reimplement the method for _all_ the fields in the schema. ### Hidden Input Types Besides argument visibility described above, if an argument's input type is hidden (that is, it implements `self.visible?(context)` to return `false`), then the argument will be hidden too. ## Different enum values ### Using `#visible?(context)` You can implement `def visible?(context)` in your {% internal_link "base enum value class", "/type_definitions/extensions#customizing-enum-values" %} to hide some enum values from some clients. For example: ```ruby class BaseEnumValue < GraphQL::Schema::EnumValue def initialize(*args, for_staff: false, **kwargs, &block) super(*args, **kwargs, &block) @for_staff = for_staff end def visible?(context) super && case @for_staff when true !!context[:current_user]&.staff? when false !context[:current_user]&.staff? else true end end end ``` With this base class, you can configure some enum values to be _just_ for staff or non-staff viewers: ```ruby class AccountStatus < Types::BaseEnum value "ACTIVE" value "INACTIVE" # Use this for sensitive account statuses when the viewer is public: value "OTHER", for_staff: false # Staff-only sensitive account statuses: value "BANNED", for_staff: true value "PAYMENT_FAILED", for_staff: true value "PENDING_VERIFICATION", for_staff: true end ``` ### Using `.enum_values(context)` Alternatively, you can implement `def self.enum_values(context)` in your enum types to return an Array of {{ "GraphQL::Schema::EnumValue" | api_doc }}s. For example, to return a dynamic set of enum values: ```ruby class ProjectStatus < Types::BaseEnum def self.enum_values(context = {}) # Fetch the values from the database status_names = context[:tenant].project_statuses.pluck("name") # Then build an Array of Enum values status_names.map do |name| # Be sure to include `owner: self`, the back-reference from the EnumValue to its parent Enum GraphQL::Schema::EnumValue.new(name, owner: self) end end end ``` ## Different types You can also use different types for each query. A few behaviors depend on the methods defined above: - If a type is not used as a return type, an argument type, or as a member of a union or implementor of an interface, it will be hidden - If an interface or union has members, it will be hidden - If a field's return type is hidden, the field will be hidden - If an argument's input type is hidden, the argument will be hidden As you can imagine, these different hiding behaviors influence one another and they can cause some real head-scratchers when used simultaneously. ### Using `.visible?(context)` Type classes can implement `def self.visible?(context)` to hide themselves at runtime: ```ruby class Types::BanReason < Types::BaseEnum # Hide any arguments or fields that use this enum # unless the current user is staff def self.visible?(context) super && !!context[:current_user]&.staff? end # ... end ``` ### Different definitions for the same type You can provide different implementations of the same type by: - Implementing `def self.visible?(context)` to return `true` and `false` in complementary contexts. (They should never both be `.visible? => true`). - Hooking the types up to the schema with different field or argument definitions, as described above For example, to migrate your `Money` scalar to a `Money` object type: ```ruby # Previously, we used a simple string to describe money: class Types::LegacyMoney < Types::BaseScalar # This graphql name will conflict with `Types::Money`, # so we have to be careful not to use them at the same time. # (GraphQL-Ruby will raise an error if it finds two definitions with the same name at runtime.) graphql_name "Money" describe "A string describing an amount of money." # Use this type definition if the current request # explicitly opted in to the legacy money representation: def self.visible?(context) !!context[:requests_legacy_money] end end # But we want to improve the client experience with a dedicated object type: class Types::Money < Types::BaseObject field :amount, Integer, null: false field :currency, Types::Currency, null: false # Use this new definition if the client # didn't explicitly ask for the legacy definition: def self.visible?(context) !context[:requests_legacy_money] end end ``` Then, hook the definitions up to the schema using field definitions: ```ruby class Types::BaseField < GraphQL::Schema::Field def initialize(*args, legacy_money: false, **kwargs, &block) super(*args, **kwargs, &block) @legacy_money = legacy_money end def visible?(context) super && (@legacy_money ? !!context[:requests_legacy_money] : !context[:requests_legacy_money]) end end class Types::Invoice < Types::BaseObject # Add one definition for each possible return type # (one definition will be hidden at runtime) field :amount, Types::LegacyMoney, null: false, legacy_money: true field :amount, Types::Money, null: false, legacy_money: false end ``` Input types (like input objects, scalars, and enums) work the same way with argument definitions. ## Schema Dumps To dump a certain _version_ of the schema, provide the applicable `context: ...` to {{ "Schema.to_definition" | api_doc }}. For example: ```ruby # Legacy money schema: MySchema.to_definition(context: { requests_legacy_money: true }) ``` or ```ruby # Staff-only schema: MySchema.to_definition(context: { current_user: OpenStruct.new(staff?: true) }) ``` That way, the given `context` will be passed to `visible?(context)` calls and other relevant methods. graphql-ruby-2.2.17/guides/schema/generators.md000066400000000000000000000066651476434635200214660ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Generators section: Schema desc: Use Rails generators to install GraphQL and scaffold new types. index: 3 --- If you're using GraphQL with Ruby on Rails, you can use generators to: - [setup GraphQL](#graphqlinstall), including [GraphiQL](https://github.com/graphql/graphiql), [GraphQL::Batch](https://github.com/Shopify/graphql-batch), and [Relay](https://facebook.github.io/relay/) - [scaffold types](#scaffolding-types) - [scaffold Relay mutations](#scaffolding-mutations) - [scaffold ActiveRecord create/update/delete mutations](#scaffolding-activerecord-mutations) - [scaffold GraphQL::Batch loaders](#scaffolding-loaders) ## graphql:install You can add GraphQL to a Rails app with `graphql:install`: ``` rails generate graphql:install ``` This will: - Set up a folder structure in `app/graphql/` - Add schema definition - Add base type classes - Add a `Query` type definition - Add a `Mutation` type definition with a base mutation class - Add a route and controller for executing queries - Install [`graphiql-rails`](https://github.com/rmosolgo/graphiql-rails) After installing you can see your new schema by: - `bundle install` - `rails server` - Open `localhost:3000/graphiql` ### Options - `--directory=DIRECTORY` will directory where generated files should be saved (default is `app/graphql`) - `--schema=MySchemaName` will be used for naming the schema (default is `#{app_name}Schema`) - `--skip-graphiql` will exclude `graphiql-rails` from the setup - `--skip-mutation-root-type` will not create of the mutation root type - `--relay` will add [Relay](https://facebook.github.io/relay/)-specific code to your schema - `--batch` will add [GraphQL::Batch](https://github.com/Shopify/graphql-batch) to your gemfile and include the setup in your schema - `--playground` will include `graphql_playground-rails` in the setup (mounted at `/playground`) - `--api` will create smaller stack for API only apps ## Scaffolding Types Several generators will add GraphQL types to your project. Run them with `-h` to see the options: - `rails g graphql:object` - `rails g graphql:input` - `rails g graphql:interface` - `rails g graphql:union` - `rails g graphql:enum` - `rails g graphql:scalar` ### ActiveRecord columns auto-extraction The `graphql:object` and `graphql:input` generators can detect the existence of an ActiveRecord class with the same name, and scaffold all database columns as fields/arguments using appropriate GraphQL types and nullability detection ### Options - `--namespaced-types` will generate each one of the `object`/`input`/`interface`/... types under separate `Types::Objects::*`/`Types::Inputs::*`/`Types::Interfaces::*`/... namespaces and folders ## Scaffolding Mutations You can prepare a Relay Classic mutation with ``` rails g graphql:mutation #{mutation_name} ``` ## Scaffolding ActiveRecord Mutations You can generate a Relay Classic create, update or delete mutation for a given model with ``` rails g graphql:mutation_create #{model_class_name} rails g graphql:mutation_update #{model_class_name} rails g graphql:mutation_delete #{model_class_name} ``` `model_class_name` accepts both `namespace/class_type` and `Namespace::ClassType` formats. This mutation also accepts the `--namespaced-types` flag, to keep it consistent with the scaffolded Object and Input classes from the type generators ## Scaffolding Loaders You can prepare a GraphQL::Batch loader with ``` rails g graphql:loader ``` graphql-ruby-2.2.17/guides/schema/introspection.md000066400000000000000000000145331476434635200222060ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Introspection section: Schema desc: GraphQL has an introspection system that tells about the schema. index: 3 --- A GraphQL schema has a [built-in introspection system](https://graphql.org/learn/introspection/) that publishes the schema's structure. In fact, the introspection system can be queried using GraphQL, for example: ```graphql { __schema { queryType { name } } } # Returns: # { # "data": { # "__schema": { # "queryType": { # "name": "Query" # } # } # } # } ``` This system is used for GraphQL tooling like the [GraphiQL editor](https://github.com/graphql/graphiql). Here are the default parts of the introspection system: - `__schema` is a root-level field that contains data about the schema: its entry points, types, and directives. - `__type(name: String!)` is a root-level field that returns data about a type with the given `name`, if there is a type with that name. - `__typename` works a bit differently: it can be added to _any_ selection, and it will return the type of object being queried. Here are some `__typename` examples: ```graphql { user(id: "1") { handle __typename } } # Returns: # { # "data": { # "user": { # "handle": "rmosolgo", # "__typename": "User" # } # } # } ``` For unions and interfaces, `__typename` returns the _object_ type for the current object, for example: ```graphql { search(term: "puppies") { title __typename } } # Returns: # { # "data": { # "search": [ # { # "title": "Sound of Dogs Barking", # "__typename": "AudioClip", # }, # { # "title": "Cute puppies playing with a stick", # "__typename": "VideoClip", # }, # { # "title": "The names of my favorite pets", # "__typename": "TextSnippet" # }, # ] # } # } ``` ## Customizing Introspection You can use custom introspection types. ```ruby # create a module namespace for your custom types: module Introspection # described below ... end class MySchema < GraphQL::Schema # ... # then pass the module as `introspection` introspection Introspection end ``` Keep in mind that off-the-shelf tooling may not support your custom introspection fields. You may have to modify existing tooling or create your own tools to make use of your extensions. ### Introspection Namespace The introspection namespace may contain a few different customizations: - Class-based {% internal_link "object definitions", "/type_definitions/objects" %} which replace the built-in introspection types (such as `__Schema` and `__Type`) - `EntryPoints`, A class-based {% internal_link "object definition", "/type_definitions/objects" %} containing introspection entry points (like `__schema` and `__type(name:)`). - `DynamicFields`, A class-based {% internal_link "object definition", "/type_definitions/objects" %} containing dynamic, globally-available fields (like `__typename`.) ### Custom Introspection Types The `module` passed as `introspection` may contain classes with the following names, which replace the built-in introspection types: Custom class name | GraphQL type | Built-in class name --|--|-- `SchemaType` | `__Schema` | {{ "GraphQL::Introspection::SchemaType" | api_doc }} `TypeType` | `__Type` | {{ "GraphQL::Introspection::TypeType" | api_doc }} `DirectiveType` | `__Directive` | {{ "GraphQL::Introspection::DirectiveType" | api_doc }} `DirectiveLocationType` | `__DirectiveLocation` | {{ "GraphQL::Introspection::DirectiveLocationEnum" | api_doc }} `EnumValueType` | `__EnumValue` | {{ "GraphQL::Introspection::EnumValueType" | api_doc }} `FieldType` | `__Field` | {{ "GraphQL::Introspection::FieldType" | api_doc }} `InputValueType` | `__InputValue` | {{ "GraphQL::Introspection::InputValueType" | api_doc }} `TypeKindType` | `__TypeKind` | {{ "GraphQL::Introspection::TypeKindEnum" | api_doc }} The class-based definitions' names _must_ match the names of the types they replace. #### Extending a Built-in Type The built-in classes listed above may be extended: ```ruby module Introspection class SchemaType < GraphQL::Introspection::SchemaType # ... end end ``` Inside the class definition, you may: - add new fields by calling `field(...)` and providing implementations - redefine field structure by calling `field(...)` - provide new field implementations by defining methods - provide new descriptions by calling `description(...)` ### Introspection Entry Points The GraphQL spec describes two entry points to the introspection system: - `__schema` returns data about the schema (as type `__Schema`) - `__type(name:)` returns data about a type, if one is found by name (as type `__Type`) You can re-implement these fields or create new ones by creating a custom `EntryPoints` class in your introspection namespace: ```ruby module Introspection class EntryPoints < GraphQL::Introspection::EntryPoints # ... end end ``` This class an object type definition, so you can override fields or add new ones here. They'll be available on the root `query` object, but ignored in introspection (just like `__schema` and `__type`). ### Dynamic Fields The GraphQL spec describes a field which may be added to _any_ selection: `__typename`. It returns the name of the current GraphQL type. You can add fields like this (or override `__typename`) by creating a custom `DynamicFields` definition: ```ruby module Introspection class DynamicFields < GraphQL::Introspection::DynamicFields # ... end end ``` Any fields defined there will be available in any selection, but ignored in introspection (just like `__typename`). ## Disabling Introspection In case you want to turn off introspection entry points `__schema` and `__type` (for instance in the production environment) you can use a `#disable_introspection_entry_points` shorthand method: ```ruby class MySchema < GraphQL::Schema disable_introspection_entry_points if Rails.env.production? end ``` Where `disable_introspection_entry_points` will disable both the `__schema` and `__type` introspection entry points, you can also individually disable the introspection entry points using the `disable_schema_introspection_entry_point` and `disable_type_introspection_entry_point` shorthand methods: ```ruby class MySchema < GraphQL::Schema disable_schema_introspection_entry_point disable_type_introspection_entry_point end ``` graphql-ruby-2.2.17/guides/schema/lazy_execution.md000066400000000000000000000061401476434635200223430ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Lazy Execution section: Schema desc: Resolvers can return "unfinished" results that are deferred for batch resolution. index: 4 --- With lazy execution, you can optimize access to external services (such as databases) by making batched calls. Building a lazy loader has three steps: - Define a lazy-loading class with _one_ method for loading & returning a value - Connect it to your schema with {{ "GraphQL::Schema#lazy_resolve" | api_doc }} - In `resolve` methods, return instances of the lazy-loading class ## Example: Batched Find Here's a way to find many objects by ID using one database call, preventing N+1 queries. 1. Lazy-loading class which finds models by ID. ```ruby class LazyFindPerson def initialize(query_ctx, person_id) @person_id = person_id # Initialize the loading state for this query, # or get the previously-initiated state @lazy_state = query_ctx[:lazy_find_person] ||= { pending_ids: Set.new, loaded_ids: {}, } # Register this ID to be loaded later: @lazy_state[:pending_ids] << person_id end # Return the loaded record, hitting the database if needed def person # Check if the record was already loaded: loaded_record = @lazy_state[:loaded_ids][@person_id] if loaded_record # The pending IDs were already loaded, # so return the result of that previous load loaded_record else # The record hasn't been loaded yet, so # hit the database with all pending IDs pending_ids = @lazy_state[:pending_ids].to_a people = Person.where(id: pending_ids) people.each { |person| @lazy_state[:loaded_ids][person.id] = person } @lazy_state[:pending_ids].clear # Now, get the matching person from the loaded result: @lazy_state[:loaded_ids][@person_id] end end ``` 2. Connect the lazy resolve method ```ruby class MySchema < GraphQL::Schema # ... lazy_resolve(LazyFindPerson, :person) end ``` 3. Return lazy objects from `resolve` ```ruby field :author, PersonType def author LazyFindPerson.new(context, object.author_id) end ``` Now, calls to `author` will use batched database access. For example, this query: ```graphql { p1: post(id: 1) { author { name } } p2: post(id: 2) { author { name } } p3: post(id: 3) { author { name } } } ``` Will only make one query to load the `author` values. ## Gems for batching The example above is simple and has some shortcomings. Consider the following gems for a robust solution to batched resolution: * {{ "GraphQL::Dataloader" | api_doc }} is a built-in, Fiber-based approach to batching. See the {% internal_link "Dataloader guide", "/dataloader/overview" %} for more information. * [`graphql-batch`](https://github.com/shopify/graphql-batch) provides a powerful, flexible toolkit for lazy resolution with GraphQL. * [`dataloader`](https://github.com/sheerun/dataloader) is more general promise-based utility for batching queries within the same thread. * [`batch-loader`](https://github.com/exAspArk/batch-loader) works with any Ruby code including GraphQL, no extra dependencies or primitives. graphql-ruby-2.2.17/guides/schema/object_identification.md000066400000000000000000000053661476434635200236310ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Object Identification section: Schema desc: Working with unique global IDs index: 8 --- Some GraphQL features use unique IDs to load objects: - the `node(id:)` field looks up objects by ID - any arguments with `loads:` configurations look up objects by ID To use these features, you must provide a function for generating UUIDs and fetching objects with them. In your schema, define `self.id_from_object` and `self.object_from_id`: ```ruby class MySchema < GraphQL::Schema def self.id_from_object(object, type_definition, query_ctx) # Generate a unique string ID for `object` here # For example, use Rails' GlobalID library (https://github.com/rails/globalid): object.to_gid_param end def self.object_from_id(global_id, query_ctx) # For example, use Rails' GlobalID library (https://github.com/rails/globalid): GlobalID.find(global_id) end end ``` ## Node interface One requirement for Relay's object management is implementing the `"Node"` interface. To implement the node interface, add {{ "GraphQL::Types::Relay::Node" | api_doc }} to your definition: ```ruby class Types::PostType < GraphQL::Schema::Object # Implement the "Node" interface for Relay implements GraphQL::Types::Relay::Node # ... end ``` To tell GraphQL how to resolve members of the `Node` interface, you must also define `Schema.resolve_type`: ```ruby class MySchema < GraphQL::Schema # You'll also need to define `resolve_type` for # telling the schema what type Relay `Node` objects are def self.resolve_type(type, obj, ctx) case obj when Post Types::PostType when Comment Types::CommentType else raise("Unexpected object: #{obj}") end end end ``` ## UUID fields Nodes must have a field named `"id"` which returns a globally unique ID. To add a UUID field named `"id"`, implement the {{ "GraphQL::Types::Relay::Node" | api_doc }} interface:: ```ruby class Types::PostType < GraphQL::Schema::Object implements GraphQL::Types::Relay::Node end ``` This field will call the previously-defined `id_from_object` class method. ## `node` field (find-by-UUID) You should also provide a root-level `node` field so that Relay can refetch objects from your schema. You can attach it like this: ```ruby class Types::QueryType < GraphQL::Schema::Object # Used by Relay to lookup objects by UUID: # Add `node(id: ID!) include GraphQL::Types::Relay::HasNodeField # ... end ``` ## `nodes` field You can also provide a root-level `nodes` field so that Relay can refetch objects by IDs: ```ruby class Types::QueryType < GraphQL::Schema::Object # Fetches a list of objects given a list of IDs # Add `nodes(ids: [ID!]!)` include GraphQL::Types::Relay::HasNodesField # ... end ``` graphql-ruby-2.2.17/guides/schema/root_types.md000066400000000000000000000025171476434635200215140ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Root Types desc: Root types are the entry points for queries, mutations and subscriptions. index: 2 --- GraphQL queries begin from [root types](https://graphql.org/learn/schema/#the-query-and-mutation-types): `query`, `mutation`, and `subscription`. Attach these to your schema using methods with the same name: ```ruby class MySchema < GraphQL::Schema # required query Types::QueryType # optional mutation Types::MutationType subscription Types::SubscriptionType end ``` The types are `GraphQL::Schema::Object` classes, for example: ```ruby # app/graphql/types/query_type.rb class Types::QueryType < GraphQL::Schema::Object field :posts, [PostType], 'Returns all blog posts', null: false end # Similarly: class Types::MutationType < GraphQL::Schema::Object field :create_post, mutation: Mutations::AddPost end # and class Types::SubscriptionType < GraphQL::Schema::Object field :comment_added, subscription: Subscriptions::CommentAdded end ``` Each type is the entry point for the corresponding GraphQL query: ```ruby query Posts { # `Query.posts` posts { ... } } mutation AddPost($postAttrs: PostInput!){ # `Mutation.createPost` createPost(attrs: $postAttrs) } subscription CommentAdded { # `Subscription.commentAdded` commentAdded(postId: 1) } ``` graphql-ruby-2.2.17/guides/schema/sdl.md000066400000000000000000000071371476434635200200720ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Parsing GraphQL Schema Definition Language into a Ruby Schema desc: Defining a schema from a string or .graphql file index: 7 --- GraphQL-Ruby includes a way to build a runable schema from the GraphQL Schema Definition Language (SDL). {{ "GraphQL::Schema.from_definition" | api_doc }} returns a schema class based on a filename or string containing GraphQL SDL. For example: ```ruby # From a file: MySchema = GraphQL::Schema.from_definition("path/to/schema.graphql") # Or, from a string: MySchema = GraphQL::Schema.from_definition(<<~GRAPHQL) type Query { # ... } # ... GRAPHQL ``` Definitions from the SDL are converted into Ruby classes, similar to those defined in plain Ruby code. ## Execution You can provide execution behaviors to a generated schema as `default_resolve:`, which accepts two kinds of values: - __Implementation Object__: an object that implements several methods used at runtime - __Implementation Hash__: keys and nested hashes provide procs for execution ### Implementation Object By providing an object that implements several runtime methods, you can define the execution behaviors of a schema loaded from SDL: ```ruby class SchemaImplementation # see below for methods end # Pass the object as `default_resolve:` MySchema = GraphQL::Schema.from_definition( "path/to/schema.graphql", default_resolve: SchemaImplementation.new ) ``` The `default_resolve:` object may implement: - `#call(type, field, obj, args, ctx)` for resolving fields - `#resolve_type(abstract_type, obj, ctx)` for resolving `obj` as one of `abstract_type`'s possible object types - `#coerce_input(type, value, ctx)` for coercing scalar input - `#coerce_result(type, value, ctx)` for coercing scalar return values ### Implementation Hash Alternatively, you can provide a Hash containing callable behaviors, for example: ```ruby schema_implementation = { # ... see below for hash structure } # Pass the hash as `default_resolve:` MySchema = GraphQL::Schema.from_definition( "path/to/schema.graphql", default_resolve: schema_implementation ) ``` The hash may contain: - A key for each object type name, containing a sub-hash of `{ field_name => ->(obj, args, ctx) { ... } }` for resolving object fields - A key for each scalar type name, containing a sub-hash with keys `"coerce_result"` and `"coerce_input"`, each pointing to a `->(value, ctx) { ... }` for handling scalar values at runtime - A `"resolve_type"` key pointing to a `->(abstract_type, object, ctx) { ... }` callable, used for resolving `object` to one of `abstract_type`'s possible types ## Plugins {{ "GraphQL::Schema.from_definition" | api_doc }} accepts a `using:` argument, which may be given as a map of `plugin => args` pairs. For example: ```ruby MySchema = GraphQL::Schema.from_definition("path/to/schema.graphql", using: { GraphQL::Pro::PusherSubscriptions => { redis: $redis }, GraphQL::Pro::OperationStore => nil, # no options here }) ``` ## Directives Although GraphQL-Ruby doesn't have special handling for directives in the SDL, you can build custom behavior in your own app. If part of the schema had a directive, you can access it using `.ast_node.directives`. For example: ```ruby schema = GraphQL::Schema.from_definition <<-GRAPHQL type Query @flagged { secret: Boolean @privacy(secret: true) } GRAPHQL pp schema.query.ast_node.directives.map(&:to_query_string) # => ["@flagged"] pp schema.get_field("Query", "secret").ast_node.directives.map(&:to_query_string) # => ["@privacy(secret: true)"] ``` See {{ "GraphQL::Language::Nodes::Directive" | api_doc }} for available methods. graphql-ruby-2.2.17/guides/search.html000066400000000000000000000045031476434635200176530ustar00rootroot00000000000000--- layout: default title: Search ---

Search:

graphql-ruby-2.2.17/guides/subscriptions/000077500000000000000000000000001476434635200204255ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/subscriptions/ably_implementation.md000066400000000000000000000263431476434635200250130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Ably Implementation desc: GraphQL subscriptions over Ably index: 7 pro: true --- [GraphQL Pro](https://graphql.pro) includes a subscription system based on [Redis](https://redis.io) and [Ably](https://ably.io) which works with any Ruby web framework. After creating an app on Ably, you can hook it up to your GraphQL schema. ## How it Works This subscription implementation uses a hybrid approach: - __Your app__ takes GraphQL queries an runs them - __Redis__ stores subscription data for later updates - __Ably__ sends updates to subscribed clients So, the lifecycle goes like this: - A `subscription` query is sent by HTTP Post to your server (just like a `query` or `mutation`) - The response contains an Ably channel ID (as an HTTP header) which the client may subscribe to - The client opens that Ably channel - When the server triggers updates, they're delivered over the Ably channel - When the client unsubscribes, the server receives a webhook and responds by removing its subscription data Here's another look: ``` 1. Subscription is created in your app HTTP POST .----------> write to Redis 📱 ⚙️ -----> 💾 <---------' X-Subscription-ID: 1234 2. Client opens a connection to Ably websocket 📱 <---------> ☁️ 3. The app sends updates via Ably ⚙️ ---------> ☁️ ------> 📱 POST update (via gem) (via websocket) 4. When the client unsubscribes, Ably notifies the app webhook ⚙️ <-------- ☁️ (disconnect) 📱 ``` By using this configuration, you can use GraphQL subscriptions without hosting a push server yourself! ## Ably setup Add `ably-rest` to your `Gemfile`: ```ruby gem 'ably-rest' ``` and `bundle install`. ## Database setup Subscriptions require a _persistent_ Redis database, configured with: ```sh maxmemory-policy noeviction # optional, more durable persistence: appendonly yes ``` Otherwise, Redis will drop data that doesn't fit in memory (read more in ["Redis persistence"](https://redis.io/topics/persistence)). If you're already using Redis in your application, see ["Storing Data in Redis"](https://www.mikeperham.com/2015/09/24/storing-data-with-redis/) for options to isolate data and tune your configuration. ## Schema configuration Add `redis` to your `Gemfile`: ```ruby gem 'redis' ``` and `bundle install`. Then create a Redis instance: ```ruby # for example, in an initializer: $graphql_subscriptions_redis = Redis.new # default connection ``` Then, that Redis client is passed to the Subscription configuration: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::AblySubscriptions, redis: $graphql_subscriptions_redis, ably: Ably::Rest.new(key: ABLY_API_KEY) end ``` That connection will be used for managing subscription state. All writes to Redis are prefixed with `graphql:sub:`. There are also two configurations for managing persistence: - `stale_ttl_s:` expires subscription data after the given number of seconds without any update. After `stale_ttl_s` has passed, the data will expire from Redis. Each time a subscription receives an update, its TTL is refreshed. (Generally, this isn't required because the backend is built to clean itself up. But, if you find that Redis is collecting stale queries, you can set them to expire after some very long time as a safeguard.) - `cleanup_delay_s:` (default: `5`) prevents deleting a subscription during those first seconds after it's created. Usually, a longer delay isn't necessary, but if you observe latency between the subscription's initial response and the client's subscription to the delivery channel, you can set this configuration to account for it. ### Connection Pool For better performance reading and writing to Redis, you can pass a `connection_pool:` instead of `redis:`, using the [`connection_pool` gem](): ```ruby use GraphQL::Pro::AblySubscriptions, connection_pool: ConnectionPool.new(size: 5, timeout: 5) { Redis.new }, ably: Ably::Rest.new(key: ABLY_API_KEY) ``` ## Execution configuration During execution, GraphQL will assign a `subscription_id` to the `context` hash. The client will use that ID to listen for updates, so you must return the `subscription_id` in the response headers. Return `result.context[:subscription_id]` as the `X-Subscription-ID` header. For example: ```ruby result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] end render json: result ``` This way, the client can use that ID as a Ably channel. For __CORS requests__, you need a special header so that clients can read the custom header: ```ruby if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Required for CORS requests: response.headers["Access-Control-Expose-Headers"] = "X-Subscription-ID" end ``` Read more here: ["Using CORS"](https://www.html5rocks.com/en/tutorials/cors/). ## Webhook configuration Your server needs to receive webhooks from Ably when clients disconnect. This keeps your local subscription database in sync with Ably. ### Server *Note: if you're setting up in a development environment you should follow the [Developing with webhooks](#Developing-with-webhooks) section first* Mount the Rack app for handling webhooks from Ably. For example, on Rails: ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Handle webhooks for subscriptions: mount MySchema.ably_webhooks_client, at: "/ably_webhooks" end ``` __Alternatively__, you can configure the routes to load your schema lazily, during the first request: ```ruby # Provide the fully-qualified class name of your schema: lazy_routes = GraphQL::Pro::Routes::Lazy.new("MySchema") mount lazy_routes.ably_webhooks_client, at: "/ably_webhooks" ``` ### Ably 1. Go to the Ably dashboard 2. Click on your application 3. Select the **"Integrations"** tab 4. Click on the **"+ New Integration Rule"** button 5. Click on the "Choose" button for **"Webhook"** 6. Click on the "Choose" button for **"Webhook"** (again) 7. Enter **your URL (including the webhooks path from above)** in the URL field. 8. Select **"Batch request"** for "Request Mode" 9. Under "Source", select **"Presence"** 10. Under "Sign with key", select the API Key prefix that matches the prefix of the `ABLY_API_KEY` you provided 11. Click **"Create"** ## Authorization You can use Ably's [token authentication](https://www.ably.io/documentation/realtime/authentication#token-authentication) by implementing an endpoint in your app, for example: ```ruby class AblyController < ActionController::Base def auth render status: 201, json: ably_rest_client.auth.create_token_request( capability: { '*' => ['presence', 'subscribe'] }, client_id: 'graphql-subscriber', ) end end ``` [Ably's tutorial](https://www.ably.io/tutorials/webhook-chuck-norris#tutorial-step-4) also demonstrates some of the setup for this. ## Encryption You can use Ably's [end-to-end encryption](https://www.ably.io/documentation/realtime/encryption) with GraphQL subscriptions. To enable it, add `cipher_base:` to your setup: ```ruby use GraphQL::Pro::AblySubscriptions, redis: $graphql_subscriptions_redis, ably: Ably::Rest.new(key: ABLY_API_KEY), # Add `cipher_base:` to enable end-to-end encryption cipher_base: "ff16381ae2f2b6c6de6ff696226009f3" ``` (Any random string will do, eg `ruby -e "require 'securerandom'; puts SecureRandom.hex"`.) Also, return a header to client so that it can decrypt subscription updates. The key is put in `context[:ably_cipher_base64]`, and `graphql-ruby-client` expects to find it in the `X-Subscription-Key` header: ```ruby result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Also return the encryption key so that clients # can decode subscription updates response.headers["X-Subscription-Key"] = result.context[:ably_cipher_base64] end ``` (Also, if you're using CORS requests, update `Access-Control-Expose-Headers` to include `X-Subscription-Key`) With this setup, - `GraphQL::Pro::AblySubscriptions` will generate per-subscription keys (using `cipher_base` and the subscription ID) and use them to encrypt Ably payloads - Those keys will be returned to clients in `X-Subscription-Key` - Clients will use those keys to decrypt incoming messages __Backwards compatibility:__ `GraphQL::Pro::AblySubscriptions` will only encrypt payloads whose `query.context[:ably_cipher_base64]` is present. Any subscriptions created _before_ `cipher_base:` was added to the setup will _not_ be encrypted. (There was no key to encrypt them, and clients don't have a key to _decrypt_ them!) ## Serializing Context Since subscription state is stored in the database, then reloaded for pushing updates, you have to serialize and reload your query `context`. By default, this is done with {{ "GraphQL::Subscriptions::Serialize" | api_doc }}'s `dump` and `load` methods, but you can provide custom implementations as well. To customize the serialization logic, create a subclass of `GraphQL::Pro::AblySubscriptions` and override `#dump_context(ctx)` and `#load_context(ctx_string)`: ```ruby class CustomSubscriptions < GraphQL::Pro::AblySubscriptions def dump_context(ctx) context_hash = ctx.to_h # somehow convert this hash to a string, return the string end def load_context(ctx_string) # Given the string from the DB, create a new hash # to use as `context:` end end ``` Then, use your _custom_ subscriptions class instead of the built-in one for your schema: ```ruby class MySchema < GraphQL::Schema # Use custom subscriptions instead of GraphQL::Pro::AblySubscriptions # to get custom serialization logic use CustomSubscriptions, ... end ``` That gives you fine-grained control of context reloading. ## Dashboard You can monitor subscription state in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}: {{ "/subscriptions/redis_dashboard_1.png" | link_to_img:"Redis Subscription Dashboard" }} {{ "/subscriptions/redis_dashboard_2.png" | link_to_img:"Redis Subscription Detail" }} ## Development Tips #### Clear subscription data At any time, you can reset your subscription database with the __"Reset"__ button in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}, or in Ruby: ```ruby # Wipe all subscription data from the DB: MySchema.subscriptions.clear ``` #### Developing with webhooks To receive webhooks in development, you can [use ngrok](https://www.ably.io/tutorials/webhook-chuck-norris). It gives you a public URL which you can setup with Ably, then any hooks delivered to that URL will be forwarded to your development environment. ## Client configuration Install the [Ably JS client](https://github.com/ably/ably-js) then see docs for: - {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} - {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %}. - {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %} graphql-ruby-2.2.17/guides/subscriptions/action_cable_implementation.md000066400000000000000000000014131476434635200264560ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Action Cable Implementation desc: GraphQL subscriptions over ActionCable index: 4 --- [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html) is a great platform for delivering GraphQL subscriptions on Rails 5+. It handles message passing (via `broadcast`) and transport (via `transmit` over a websocket). To get started, see examples in the API docs: {{ "GraphQL::Subscriptions::ActionCableSubscriptions" | api_doc }}. See client usage for: - {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} - {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %}. - {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %} graphql-ruby-2.2.17/guides/subscriptions/broadcast.md000066400000000000000000000106001476434635200227060ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Broadcasts desc: Delivering the same GraphQL result to multiple subscribers index: 3 --- GraphQL-Ruby 1.11+ introduced a new algorithm for tracking subscriptions and delivering updates, _broadcasts_. A broadcast is a subscription update which is executed _once_, then delivered to _any number_ of subscribers. This reduces the time your server spends running GraphQL queries, since it doesn't have to re-run the query for every subscriber. But, __take care__: this approach risks leaking information to subscribers who shouldn't receive it. ## Setup To enable broadcasts, add `broadcast: true` to your subscription setup: ```ruby class MyAppSchema < GraphQL::Schema # ... use SomeSubscriptionImplementation, broadcast: true # <---- end ``` Then, any broadcastable field can be configured with `broadcastable: true`: ```ruby field :name, String, null: false, broadcastable: true ``` When a subscription comes in where _all_ of its fields are `broadcastable: true`, then it will be handled as a broadcast. Additionally, you can set `default_broadcastable: true`: ```ruby class MyAppSchema < GraphQL::Schema # ... use SomeSubscriptionImplementation, broadcast: true, default_broadcastable: true # <---- end ``` With this setting, fields are broadcastable by default. Only a field with `broadcastable: false` in its configuration will cause a subscription to be handled on a subscriber-by-subscriber basis. ## What fields are broadcastable? GraphQL-Ruby can't infer whether a field is broadcastable or not. You must configure it explicitly with `broadcastable: true` or `broadcastable: false`. (The subscription plugin also accepts `default_broadcastable: true|false`.) A field is broadcastable if _all clients who request the field will see the same value_. For example: - General facts: celebrity names, laws of physics, historical dates - Public information: object names, document updated-at timestamps, boilerplate info For fields like this, you can add `broadcastable: true`. A field is __not broadcastable__ if its value is different for different clients. For example: - __Viewer-specific information:__ if a field is specifically viewer-based, then it can't be broadcasted to other viewers. For example, `discussion { viewerCanModerate }` might be true for a moderator, but it shouldn't be broadcasted to other viewers. - __Context-specific information:__ if a field's value takes the request context into consideration, it shouldn't be broadcasted. For example, IP addresses or HTTP header values probably can't be broadcasted. If a field reflects the viewer's timezone, it can't be broadcasted. - __Restricted information:__ if some viewers see one value, while other viewers see a different value, then it's not broadcastable. Broadcasting this data might leak private information to unauthorized clients. (This includes filtered lists: if the filtering is viewer-by-viewer, it's not broadcastable.) - __Fields with side effects:__ if the system requires a side effect (eg, logging a metric, updating a database, incrementing a counter) whenever a resolver is executed, it's not a good candidate for broadcasting because some executions will be optimized away. These fields can be tagged with `broadcastable: false` so that GraphQL-Ruby will handle them on a subscriber-by-subscriber basis. If you want to use subscriptions but have a lot of non-broadcastable fields in your schema, consider building a new set of subscription fields with limited access to other schema objects. Instead, optimize those subscriptions for broacastability. ## Under the Hood GraphQL-Ruby determines which subscribers can receive a broadcast by inspecting: - __Query string__. Only exactly-matching query strings will receive the same broadcast. - __Variables__. Only exactly-matching variable values will receive the same broadcast. - __Field and Arguments__ given to `.trigger`. They must match the ones initially sent when subscribing. (Subscriptions always worked this way.) - __Subscription scope__. Only clients with exactly-matching subscription scope can receive the same broadcasts. So, take care to {% internal_link "set subscription_scope", "subscriptions/subscription_classes#scope" %} whenever a subscription should be implicitly scoped! (See {{ "GraphQL::Subscriptions::Event#fingerprint" | api_doc }} for the implementation of broadcast fingerprints.) graphql-ruby-2.2.17/guides/subscriptions/implementation.md000066400000000000000000000033601476434635200237760ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Implementation desc: Subscription execution and delivery index: 3 --- The {{ "GraphQL::Subscriptions" | api_doc }} plugin is a base class for implementing subscriptions. Each method corresponds to a step in the subscription lifecycle. See the API docs for method-by-method documentation: {{ "GraphQL::Subscriptions" | api_doc }}. Also, see the {% internal_link "Pusher implementation guide", "subscriptions/pusher_implementation" %}, the {% internal_link "Ably implementation guide", "subscriptions/ably_implementation" %}, the {% internal_link "ActionCable implementation guide", "subscriptions/action_cable_implementation" %} or {{ "GraphQL::Subscriptions::ActionCableSubscriptions" | api_doc }} docs for an example implementation. ## Considerations Every Ruby application is different, so consider these points when implementing subscriptions: - Is your application single-process or multiprocess? Single-process applications can store state in memory while multiprocess applications need a message broker to keep all processes up-to-date. - What components of your application can be used for persistence and message passing? - How will you deliver push updates to subscribed clients? (For example, websockets, ActionCable, Pusher, webhooks, or something else?) - How will you handle [thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem)s? When an event is triggered, how will you manage database access to update clients without swamping your system? ## Broadcasts _Broadcasting_ updates to multiple subscribers is supported by GraphQL-Ruby, but requires implementation-specific work, see more in the {% internal_link "Broadcast guide", "subscriptions/broadcast" %}. graphql-ruby-2.2.17/guides/subscriptions/multi_tenant.md000066400000000000000000000111471476434635200234560ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Multi-Tenant desc: Switching tenants in GraphQL Subscription execution index: 8 --- In a multi-tenant system, data from many different accounts is stored on the same server. (An account might be an organization, a customer, a namespace, a domain, etc -- these are all _tenants_.) Gems like [Apartment](https://github.com/influitive/apartment) assist with this arrangement, but it can also be implemented in the application. Here are a few considerations for this architecture when using GraphQL subscriptions. ## Add Tenant to `context` All the approaches below will use `context[:tenant]` to identify the tenant during GraphQL execution, so make sure to assign it before executing a query: ```ruby context = { viewer: current_user, tenant: current_user.tenant, # ... } MySchema.execute(query_str, context: context, ...) ``` ## Tenant-based `subscription_scope` When subscriptions are delivered, {% internal_link "`subscription_scope`", "subscriptions/subscription_classes#scope" %} is one element used to route data to the right subscriber. In short, it's the _implicit_ identifier for the receiver. In a multi-tenant architecture, `subscription_scope` should reference the context key that names the tenant, for example: ```ruby class BudgetWasApproved < GraphQL::Schema::Subscription subscription_scope :tenant # This would work with `context[:tenant] => "acme-corp"` # ... end # Include the scope when `.trigger`ing: BudgetSchema.subscriptions.trigger(:budget_was_approved, {}, { ... }, scope: "acme-corp") ``` Alternatively, `subscription_scope` might name something that _belongs_ to the tenant: ```ruby class BudgetWasApproved < GraphQL::Schema::Subcription subscription_scope :project_id # This would work with `context[:project_id] = 1234` end # Include the scope when `.trigger`ing: BudgetSchema.subscriptions.trigger(:budget_was_approved, {}, { ... }, scope: 1234) ``` As long as `project_id` is unique among _all_ tenants, that would work fine too. But _some_ scope is required so that subscriptions can be disambiguated between tenants. ## Choosing a tenant for execution There are a few places where subscriptions might need to load data: - When building the payload for the subscription (fetching data to prepare the result) - `ActionCableSubscriptions`: when deserializing the JSON string broadcasted by `ActionCable` - `PusherSubscriptions` and `AblySubscriptions`: when deserializing query context Each of these operations will need to select the right tenant in order to load data properly. For __building the payload__, use a {% internal_link "Trace module", "queries/tracing" %}: ```ruby module TenantSelectionTrace def execute_multiplex(multiplex:) # this is the top-level, umbrella event context = data[:multiplex].queries.first.context # This assumes that all queries in a multiplex have the same tenant MultiTenancy.select_tenant(context[:tenant]) do # ^^ your multi-tenancy implementation here super # Call through to the rest of execution end end end # ... class MySchema < GraphQL::Schema trace_with(TenantSelectionTrace) end ``` The tracer above will use `context[:tenant]` to select a tenant for the duration of execution for _all_ queries, mutations, and subscriptions. For __deserializing ActionCable messages__, provide a `serializer:` object that implements `.dump(obj)` and `.load(string, context)`: ```ruby class MultiTenantSerializer def self.dump(obj) GraphQL::Subscriptions::Serialize.dump(obj) end def self.load(string, context) MultiTenancy.select_tenant(context[:tenant]) do GraphQL::Subscriptions::Serialize.load(string) end end end # ... class MySchema < GraphQL::Schema # ... use GraphQL::Subscriptions::ActionCableSubscriptions, serializer: MultiTenantSerializer end ``` The implementation above will use the built-in serialization algorithms, but it will do so _in the context of_ the selected tenant. For __loading query context in Pusher and Ably__, add tenant selection to your `load_context` method, if required: ```ruby class CustomSubscriptions < GraphQL::Pro::PusherSubscriptions # or `GraphQL::Pro::AblySubscriptions` def dump_context(ctx) JSON.dump(ctx.to_h) end def load_context(ctx_string) ctx_data = JSON.parse(ctx_string) MultiTenancy.select_tenant(ctx_data["tenant"]) do # Build a symbol-keyed hash, loading objects from the database if necessary # to use a `context: ...` end end end ``` With that approach, the selected tenant will be active when building the context hash, in case any objects need to be loaded from the database. graphql-ruby-2.2.17/guides/subscriptions/overview.md000066400000000000000000000053241476434635200226210ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Overview desc: Introduction to Subscriptions in GraphQL-Ruby index: 0 --- _Subscriptions_ allow GraphQL clients to observe specific events and receive updates from the server when those events occur. This supports live updates, such as websocket pushes. Subscriptions introduce several new concepts: - The __Subscription type__ is the entry point for subscription queries - __Subscription classes__ are resolvers for handing initial subscription requests and subsequent updates - __Triggers__ begin the update process - The __Implementation__ provides application-specific methods for executing & delivering updates. - __Broadcasts__ can send the same GraphQL result to any number of subscribers. ## Subscription Type `subscription` is an entry point to your GraphQL schema, like `query` or `mutation`. It is defined by your `SubscriptionType`, a root-level `GraphQL::Schema::Object`. Read more in the {% internal_link "Subscription Type guide", "subscriptions/subscription_type" %}. ## Subscription Classes {{ "GraphQL::Schema::Subscription" | api_doc }} is a resolver class with subscription-specific behaviors. Each subscription field should be implemented by a subscription class. Read more in the {% internal_link "Subscription Classes guide", "subscriptions/subscription_classes" %} ## Triggers After an event occurs in our application, _triggers_ begin the update process by sending a name and payload to GraphQL. Read more in the {% internal_link "Triggers guide","subscriptions/triggers" %}. ## Implementation Besides the GraphQL component, your application must provide some subscription-related plumbing, for example: - __state management__: How does your application keep track of who is subscribed to what? - __transport__: How does your application deliver payloads to clients? - __queueing__: How does your application distribute the work of re-running subscription queries? Read more in the {% internal_link "Implementation guide", "subscriptions/implementation" %} or check out the {% internal_link "ActionCable implementation", "subscriptions/action_cable_implementation" %}, {% internal_link "Pusher implementation", "subscriptions/pusher_implementation" %} or {% internal_link "Ably implementation", "subscriptions/ably_implementation" %}. ## Broadcasts By default, the subscription implementations listed above handle each subscription in total isolation. However, this behavior can be optimized by setting up broadcasts. Read more in the {% internal_link "Broadcast guide", "subscriptions/broadcast" %}. ## Multi-Tenant See the {% internal_link "Multi-tenant guide", "subscriptions/multi_tenant" %} for supporting multi-tenancy in GraphQL subscriptions. graphql-ruby-2.2.17/guides/subscriptions/pusher_implementation.md000066400000000000000000000260741476434635200253730ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Pusher Implementation desc: GraphQL subscriptions over Pusher index: 6 pro: true --- [GraphQL Pro](https://graphql.pro) includes a subscription system based on [Redis](https://redis.io) and [Pusher](https://pusher.com) which works with any Ruby web framework. After creating an app on Pusher and [configuring the Ruby gem](https://github.com/pusher/pusher-http-ruby#global), you can hook it up to your GraphQL schema. ## How it Works This subscription implementation uses a hybrid approach: - __Your app__ takes GraphQL queries an runs them - __Redis__ stores subscription data for later updates - __Pusher__ sends updates to subscribed clients So, the lifecycle goes like this: - A `subscription` query is sent by HTTP Post to your server (just like a `query` or `mutation`) - The response contains a Pusher channel ID (as an HTTP header) which the client may subscribe to - The client opens that Pusher channel - When the server triggers updates, they're delivered over the Pusher channel - When the client unsubscribes, the server receives a webhook and responds by removing its subscription data Here's another look: ``` 1. Subscription is created in your app HTTP POST .----------> write to Redis 📱 ⚙️ -----> 💾 <---------' X-Subscription-ID: 1234 2. Client opens a connection to Pusher websocket 📱 <---------> ☁️ 3. The app sends updates via Pusher ⚙️ ---------> ☁️ ------> 📱 POST update (via gem) (via websocket) 4. When the client unsubscribes, Pusher notifies the app webhook ⚙️ <-------- ☁️ (disconnect) 📱 ``` By using this configuration, you can use GraphQL subscriptions without hosting a push server yourself! ## Database setup Subscriptions require a _persistent_ Redis database, configured with: ```sh maxmemory-policy noeviction # optional, more durable persistence: appendonly yes ``` Otherwise, Redis will drop data that doesn't fit in memory (read more in ["Redis persistence"](https://redis.io/topics/persistence)). If you're already using Redis in your application, see ["Storing Data in Redis"](https://www.mikeperham.com/2015/09/24/storing-data-with-redis/) for options to isolate data and tune your configuration. ## Schema configuration Add `redis` to your `Gemfile`: ```ruby gem 'redis' ``` and `bundle install`. Then create a Redis instance: ```ruby # for example, in an initializer: $graphql_subscriptions_redis = Redis.new # default connection ``` Then, that Redis client is passed to the Subscription configuration: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::PusherSubscriptions, redis: $graphql_subscriptions_redis end ``` That connection will be used for managing subscription state. All writes to Redis are prefixed with `graphql:sub:`. There are also two configurations for managing persistence: - `stale_ttl_s:` expires subscription data after the given number of seconds without any update. After `stale_ttl_s` has passed, the data will expire from Redis. Each time a subscription receives an update, its TTL is refreshed. (Generally, this isn't required because the backend is built to clean itself up. But, if you find that Redis is collecting stale queries, you can set them to expire after some very long time as a safeguard.) - `cleanup_delay_s:` (default: `5`) prevents deleting a subscription during those first seconds after it's created. Usually, a longer delay isn't necessary, but if you observe latency between the subscription's initial response and the client's subscription to the delivery channel, you can set this configuration to account for it. ### Connection Pool For better performance reading and writing to Redis, you can pass a `connection_pool:` instead of `redis:`, using the [`connection_pool` gem](https://github.com/mperham/connection_pool): ```ruby use GraphQL::Pro::PusherSubscriptions, connection_pool: ConnectionPool.new(size: 5, timeout: 5) { Redis.new }, ``` ## Execution configuration During execution, GraphQL will assign a `subscription_id` to the `context` hash. The client will use that ID to listen for updates, so you must return the `subscription_id` in the response headers. Return `result.context[:subscription_id]` as the `X-Subscription-ID` header. For example: ```ruby result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] end render json: result ``` This way, the client can use that ID as a Pusher channel. For __CORS requests__, you need a special header so that clients can read the custom header: ```ruby if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Required for CORS requests: response.headers["Access-Control-Expose-Headers"] = "X-Subscription-ID" end ``` Read more here: ["Using CORS"](https://www.html5rocks.com/en/tutorials/cors/). ### Payload Compression To mitigate problems with [Pusher's 10kb message limit](https://support.pusher.com/hc/en-us/articles/4412243423761-What-Is-The-Message-Size-Limit-When-Publishing-an-Event-in-Channels-), you can specify `compress_pusher_payload: true` in the `context` of your subscription. For example: ```ruby # app/controllers/graphql_controller.rb def execute # ... # Somehow detect whether the client supports compressed payloads, # for example, User-Agent, query param, or request header: if client_supports_compressed_payloads? context[:compress_pusher_payload] = true end # ... end ``` This will cause subscription payloads to include `compressed_result: "..."` instead of `result: "..."` when they're sent over Pusher. See docs for {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} or {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %} to read about preparing clients for compressed payloads. By configuring `compress_pusher_payload: true` on a query-by-query basis, the subscription backend can continue to support clients running _old_ client code (by not compressing) while upgrading new clients to compressed payloads. ### Batched Deliveries By default, `PusherSubscriptions` sends updates in batches of up to 10 at a time, using [batch triggers](https://github.com/pusher/pusher-http-ruby#batches). You can customize the batch size by passing `batch_size:` when installing it, for example: ```ruby use GraphQL::Pro::PusherSubscriptions, batch_size: 1, ... ``` `batch_size: 1` will make `PusherSubscriptions` use the single trigger API instead of batch triggers. ## Webhook configuration Your server needs to receive webhooks from Pusher when clients disconnect. This keeps your local subscription database in sync with Pusher. In the Pusher web UI, Add a webhook for "Channel existence" {{ "/subscriptions/pusher_webhook_configuration.png" | link_to_img:"Pusher Webhook Configuration" }} Then, mount the Rack app for handling webhooks from Pusher. For example, on Rails: ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Handle Pusher webhooks for subscriptions: mount MySchema.pusher_webhooks_client, at: "/pusher_webhooks" end ``` This way, we'll be kept up-to-date with Pusher's unsubscribe events. __Alternatively__, you can configure the routes to load your schema lazily, during the first request: ```ruby # Provide the fully-qualified class name of your schema: lazy_routes = GraphQL::Pro::Routes::Lazy.new("MySchema") mount lazy_routes.pusher_webhooks_client, at: "/pusher_webhooks" ``` ## Authorization To ensure the privacy of subscription updates, you should use a [private channel](https://pusher.com/docs/client_api_guide/client_private_channels) for transport. To use a private channel, add a `channel_prefix:` key to your query context: ```ruby MySchema.execute( query_string, context: { # If this query is a subscription, use this prefix for the Pusher channel: channel_prefix: "private-user-#{current_user.id}-", # ... }, # ... ) ``` That prefix will be applied to GraphQL-related Pusher channel names. (The prefix should begin with `private-`, as required by Pusher.) Then, in your [auth endpoint](https://pusher.com/docs/authenticating_users#implementing_private_endpoints), you can assert that the logged-in user matches the channel name: ```ruby if params[:channel_name].start_with?("private-user-#{current_user.id}-") # success, render the auth token else # failure, render unauthorized end ``` ## Serializing Context Since subscription state is stored in the database, then reloaded for pushing updates, you have to serialize and reload your query `context`. By default, this is done with {{ "GraphQL::Subscriptions::Serialize" | api_doc }}'s `dump` and `load` methods, but you can provide custom implementations as well. To customize the serialization logic, create a subclass of `GraphQL::Pro::PusherSubscriptions` and override `#dump_context(ctx)` and `#load_context(ctx_string)`: ```ruby class CustomSubscriptions < GraphQL::Pro::PusherSubscriptions def dump_context(ctx) context_hash = ctx.to_h # somehow convert this hash to a string, return the string end def load_context(ctx_string) # Given the string from the DB, create a new hash # to use as `context:` end end ``` Then, use your _custom_ subscriptions class instead of the built-in one for your schema: ```ruby class MySchema < GraphQL::Schema # Use custom subscriptions instead of GraphQL::Pro::PusherSubscriptions # to get custom serialization logic use CustomSubscriptions, redis: $redis end ``` That gives you fine-grained control of context reloading. ## Dashboard You can monitor subscription state in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}: {{ "/subscriptions/redis_dashboard_1.png" | link_to_img:"Redis Subscription Dashboard" }} {{ "/subscriptions/redis_dashboard_2.png" | link_to_img:"Redis Subscription Detail" }} ## Development Tips #### Clear subscription data At any time, you can reset your subscription database with the __"Reset"__ button in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}, or in Ruby: ```ruby # Wipe all subscription data from the DB: MySchema.subscriptions.clear ``` #### Developing with Pusher webhooks To receive Pusher's webhooks in development, Pusher [suggests using ngrok](https://support.pusher.com/hc/en-us/articles/203112227-Developing-against-and-testing-WebHooks). It gives you a public URL which you can setup with Pusher, then any hooks delivered to that URL will be forwarded to your development environment. ## Client configuration Install the [Pusher JS client](https://github.com/pusher/pusher-js) then see docs for: - {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} - {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %} - {% internal_link "GraphiQL", "/javascript_client/graphiql_subscriptions" %} - {% internal_link "urql", "/javascript_client/urql_subscriptions" %} graphql-ruby-2.2.17/guides/subscriptions/pusher_webhook_configuration.png000066400000000000000000001236421476434635200271160ustar00rootroot00000000000000PNG  IHDRTh! iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 360 1620 1 QH@{@IDATxUHHtwt !v- *t HAlE1FJP@Z{v]g?oo̽{kfΜstPHHHHHHHHHHHHHw 7 TF            P IHHHHHHHHHHH $@$@$@$@$@$@$@$@$@$@$@$*q3 P{HHHHHHHHHHHB% n&           *Tx @T$@$@$@$@$@$@$@$@$@$@$@$@            @ HHHHHHHHHHHP=@$@$@$@$@$@$@$@$@$@$@$@P7 *HHHHHHHHHHHH *Tf           B  @J@L$@$@$@$@$@$@$@$@$@$@$@T            P IHHHHHHHHHHH $@$@$@$@$@$@$@$@$@$@$@$*q3 P{HHHHHHHHHHHB% n&           *Tx @T$@$@$@$@$@$@$@$@$@$@$@$@            @ HHHHHHHHHHHP=@$@$@$@$@$@$@$@$@$@$@$@P7 *HHHHHHHHHHHH *Tf           B  @J@L$@$@$@$@$@$@$@$@$@$@$@T            P IHHHHHHHHHHH $@$@$@$@$@$@$@$@$@$@$@$*q3 P{HHHHHHHHHHHB% n&           *Tx @T$@$@$@$@$@$@$@$@$@$@$@$,lظEΝ.GRH!>p֬N4L6.#G9e=P+U &IA$ fl WhBr_ *T;w (Zƍ'QM[yHZ=Һ%mP    ண P (!    x g.*U,.],ۙG`AaeÆ-vcNSXaI6v.$^ׯ_}eDyf$@$@$@$@$liѺ s 1LLC$@$@$@$@"g*˕[nE4o]R% Nh&V`&vљntq+jIJ*.9.[%KW \A2IR@>u}3$@$@$@$@$pH.tx]G1CHߴ~:ziGAHHHHH LxJ ټe ovʵk$YDgCGsT1"3 @֬I;x\=cҭ@پci߰ J\    Y L:z<.      Dh*n5U(T6wdZ@;*M.],;v!egSƌp"իhrYj?~RRH.s甆 jK2%=Z.@[eoL+&K4}eXIk*ۮ8W^sɥ>I<<J \|Ed,wr"ugvSĹ9|wZԯW;I.鞦 BEs"^;'    P@l+WeOֹLwW|yi?;ǐ4qk/z+2,:!a Z>oHɝm!   H,(=P˗X_`5qT0٢[RAG C :"-Hg?Wj)UVuyL*2Y6 a)ٲe1+U[oդ㝡:y2`QH!RtYL LI#h% ,t uVz~GN8Ҽ7+T)sLҿϛfDn!ΕÙ iJ~9X[=W;1uw_xLɑ#~ǥVjwMQkG:`JZJ}27L= Dh`Vd(oYՊ)bZ:|N( *pC(`aRH4.]i-] 4Oe ;>V + 1rʻ . $>qj|RhfUt>|$*ٲfGeTZ'}TarnBxGo,QLRJE67~̜@&LL-E IE÷zN^z԰kᆫ_FsL& fiO] 8֬ 0t$[` Pdʔ,c4ԗ_ }?Lƾ;lÿu q`4ר1;}{&P*ŧ\P=Vd{[hNwզq,pHHHHnbPdt|kGJTz~Yrއ*+.]r,Qw`y1rtX=^Ab)Mf%pֵӾ>= [Ǵ*'zt}EҧOgcؠwȪo.0pZo Z&od>GOO+2̝QjJ4p=ֳ{)R6kܪR+X0 3-6ú\;g FC=*N!   HB(]qT6O~q+ K܂ ^:!h`Xb+5,ܭL1_ۙx(o+v2s!_hk.΂kVl ;f PWw)؎C4~}X8G+1k˰]j:?xcyWZhhϟn*5o 7E[RZ%^r#wL$@$@$@$@II0+k׮}JHb;1ϱ)Gbk66o71(6t@e *N0wZX>nI2Mc@,mgݢa`XZ&˝P`#ڥhY٥. ( m ֬ujW7 W    8PxdLE7 x]r?0Z8yJn5nw_5Р[[E *%4 \D%)SFponN>c,f|7΅r~mZڹsY,Y-lPlFuy(jnXZxekVB?*>&    (@HXeD&%Ks6jbǟؓ.{5֤ x E!pug)LaiRxə Q-= , Tw5#_hVZo`w _]vZ4+Væ&b|SlV-Y|ug>ׁB$@$@$@$ ĹB%U[6`u-V!SV.Vʫ )n^ټw?-f[l{ Px4N^ΩȠP9+W`>y /]lV߯p.6aD#!&yw!u֧W0돪ֆ(q=Mny΂ Gޡ 9ob36u=`V $SG/#zuj3v/VęGAwV,]KmA|d0˗B%~%hş/4ؘ0<~ڴW&>{=IHHH'8W#Z7^r_nu5e]da~M|( HSpA3N%O63׮#ɓmH$#{=Lǡܹ́ޛ峮mI^gB06|P~]0 ,XAI0AkrIɑ=a]UHYɓ'a O=eAݧZΝOrg-x1̽qt^2ob3g|_-7LP8}w*ނjU+6իr.܋6. $^bÚ )faauc+W(YGg >l{,O{XQ\rb2U+mk.mZ7sg`e}{%s-^nr]GBZGV`L]MZvhO|Yi!d1>v() PFu~Q8Or| y7kz,\Lm~3J!jRXaɜ)Z 6ˑaV>ȏ;Wewo=1YҪeS_IڝA +jlM $@$@$@$@$" \>@K=֨^E&$Z~^HkbdEKjeވ _~TkwGtiPFtVIQlUvɓӴq ƍʧv:qS] gYj ג"E %1M)jGcᬌ t14f̜Jy77S[b]{QO<ߨ |g {wPbиΐwF \Kj~UDUjcw~d~4Q3R]F-_ƽc>W9@$@$@$@$@ "/^tCɰnʀマlG|KA'e֝x C#i-Q_z*ejz墪@(_(:L8; 6oCU|や)Y깋9_Z%Yrӝn{|գ-[,tn=i?ۖvy    DL ^\~' (N| V`b-J:KB$VQMa0jx: uL2dPO)T(c]RݿC!R݂ n9;nhs{3N!^3sd|00kԈZdɒG\~D})ߦiؠ@;_/S    H,k wI$qC0Pw׌UPv+# 2>A{=uTr/Ay˺B~#If:J̙d>PRU5oJdۖRjؽ7v)L$@$@$@@*O'NFG>#{TpܧN}ȯLZ;v¸,BSde$odd\=[Vɞ=IK$@$@$@$@$<S/W׻59խ{Z%p'J:ܰ `rqi)*CX`O1*&A^ԝ0\z($@$@$@$@M&"Xfӆ/a[&aA_t$&y`ՀLz!    =S/Abq%ψ_t$P^RAebrFu?_T:@/*3- @!h* HIHHHHH1k$Uɳ,4 ֢_\O$@$@$@$@Q%@JT1= @@O6_ivS    3TjHHHHHH *&K\RҦ=ܦ+lv:UiIHHHH/Ds @!pU  HHHH-T>M$@$@$@$@$@$))H7 q'HHHHHHHHHHH(*Ta *T5D$@$@$@$@$@$@$@$@$@$@$@ P@/HHHHHHHHHHH Pw'           J zx$@$@$@$@$@$@$@$@$@$@$@qG c= $PT$ &          ;TkHHHHHHHHHHH HWǽC$@$@$@$@$p('Mzd<,     -T"í$@$@$@$@$@$@$@$@$@$@$@$ qfbYsD%) wbsy$@$@$@$@$X B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH HXOE$@$@$@$@$@ 3K".ߥagqkr)E/"3%Ic&    K {x$@$@$@$@$@EUl=q]NHu^_sa[.҈ΖDRB$@$@$@$@$P],HHHHHH9|VK/bJeJ"?"G&3+\wҸ.RdOTr.)n SĄ)WH%c**Ux)H \|Y]NG8@qB KW#jﱸ}O { ]ׯ_$Ib~+9g_ʁW_}&M%3o 6J+٥d8'HҤICR >P\ _ $ $@l3 @@irI4iHT$E+WŋhI<_@ *d-3s@!(\KI#@ ?׬㋝eݺ 3䙧IՖ*:0QJ,WL[$3ݴSmc8/sҾf+/sQ߬xoh\JLߞP @#e҃I0e3";N+G%d:F"-P(Fb6$Quvu9.%$|`򯖝TjRF\C$>${v&9u[LLBd`V;P s0ڵkrU9s~䐱h)RΓ7O'x#(s$`P7̙s䴚Vx(Q瞂I3MrɒrYu7ȂI737@̟Д}ݰ   Kn`b);OI*R| 铫˯H2%YŔpR|: g.ٱmΕKr!)Յ~pq-{BJ(TRN-YdcǎJYܸO+|4C PM|ײc.yS{ `@⠀yOzu-4~3s Đ* .KBcX @ =bX7_P*IǗ1>bȃ]7eQ*INT@Wtuc u͓LfMJ/"ŊyFl? ӧK+ŋVdM $J 9u7g jQ-kw\G!'@Jȑ p,[Rʗ/+ݦ}H0i|d*kkOis_ːɂHHHH *@7_L1$%*Zk0uI,q`[O$r9\C8{Ew:he?ϒ)(/J oƍ[:*T<8 FgF-ST?Lmߥ)F ,($#e׎mRTIɞ=%|"ʔ E|l>.Wr*ap@bxbpa>|ԙ|֬_q7|i-sk ZoڴٌG~@ru2}ϦSU>RX֝YJShS e#gvizwccb*Yl*Ӥ" IVJ"=GypCq}y^MGT!믿eJIwݰݮ;/3fɱߏʫSFeJSjUVXn.mVK"mG?U̷l&[~sΗK䞦M<;~\V,_{e=r]s%5=41c<2/LkڴͮjZ!l,ΝWS"Rt \ΛP.[.~ 3Q Od#ռܹKfDԨQ]jg&FXUAeu2{<9zY_B9yMD_`ɼ!cɟLSCɭDΏ}˚5a*Ν;>UaOȴieYw'Zha} j[ymN'}o6{J,f5Ž3 O:}Fu<-6öm姟fށgϞ5J,&hMhYu3+WNQ[ԯkc{_et9k>|D $[RhQiٲ1=^]( KG+xnU)x7|+͘1M) $[O\43~fY.h9^Re@R`m!Km?V?IJc{!cI4 Kڴi{,(G#/̣̿Kb%b-\5'_퉭vȒpNp~Cm;:k_ 9zCʨu~}zHX-vXTqN]{icSӹzZ>|tFQÉXZdQ߲Ŵ#*p0 :X'LXW*wT.%-4Dg3ϴQ3J#[D8q4S]n6S̙sO?2 DP;˸vE3&/u({?ȑ36o٪sdǟʨCb*^,]&#Fk6[ ,sDF ,嵛-Io𡍚 ²e+㏧H~= :-w# Ut`/eoJF ݛc/i`@u{nVG ҥ}Wn-#ltF*F~]DG唩S&nz+$B&g=6/ϿMV7lŠ?NRznJtQ~p{C2 j@7oQNYG1n%@9_hY&g}oȁ":ȝ ј ^\' (B#^ڪPMwÞhGס_A}θ{^YxO"sgg<\@Aȸ#Zʷ}鳉2pz7uXtQLuI\?ySgNVy xu[suٴiYp$y՗*)$@$PugKv_"~cܟrelX$B---s 2Qc[/k{(={V)Unu7T#~ϝR<>e;~G=B Ap]e{(e'OM,TXKl ypWӏIunstbg2~#O:%(*ީlP 0rgڛ}Jʒ^; oߡ (>8Y舴gn=U@ICKjw訉jXqyuN^'hʔ)ŋNH}YGZGraΜyo ;d:ܛ4X8ЁsˇN=2Nׂ 21odf8fv=V>kcЃmoqi xWDΟ;~|W-U}<}>(5B-z~~Nqrj(nZja8]T+X)/^(oCgs"̠q`LuF3͓X_({Wz_3 2f̠TnUk{mWߔǿSdm(mjש)I$6lh 굨kE-=9@"OCqN/Ϩ{W/Ev)j`3(hErac4[u_fx-2eJkyd*yQTdWKj Ov#L`Wx^+\F#[_N-PٸqϠY^k׮XyˢEK̪2KKv0r6k,z/ݭtUcN}X͘9[z'4zO|dHAUYkwx(ހ5"UVRTPH @اʍsvvl9yằN{@5CJU^`WLa1 x_z <)Ԩ̝X-4kz{{/TGz#&h 4w74 ]u!-[)QZ7T@7=S4MnG?Ղ_JG|s_ɟ~MQl,I<,e蠜>,e>>ӨK;zvo.9xzTɬm䊚 Xnlz^Y5{>w`%oi Sl|| M u]t=ѩOE{N1`xlVҮozڱsoGF!bcP0Ɋue3R^M# 7gJtPPG:w-Lq͛X]9m3@>G( 9z=_Um 9s:DkCYa SJ[j%2lctvcnr]{c#XJTu2e4cN|ݟ{lO@D݃N}**{f0 ~x|-[6W*S܇Ұa=gy)3z2={eU ;Vйf1a/=ZpϽmFmC448֭[x(Sq֬q5}!pKSp .#VҦMK$P(Ք2&Bl EW'o2ŝw7 ;w:Ptg##Xx| /<2ŝ!; E:|9xpGG,5P8ʟ@yVtPA?Qhct;iV+h4x^64]^1yF3_*! q܂;CF lP{3_F(zg3Bn xO{zj0IkwP( <7PBCb`g1k]<)tPX,q}It=x6 %ȋP7_Q%bd;;PA\N#F @x 48K&GΟHk#?s $,ҧ&ޗFZI-%iJRm1 >/+c3ݮ.,x{ eKJm-|Z71e 9k1tp;xȻQؾ0٤BrwNGNj 20 u+m)yYF|(S~B [L#һPLEfݍcƽ5DWnukkan$(8Ͽf)?ywQ&@;vxJT-FPh"]ác'ˤDl^;Rk*X0zSV`5>o jLMG4"D7nL㜟}1ӑfE;fs<_jA(tZ)S{ͮ2)(}LyGL~L^xҌPG'?N=,3E#gT߁|<.As`ABh)Pzg׾jB f DzŁ*n X 2dL>Z+ HTux6 JJ~oAJ%Yλ4*`o6e?AG*~(7=;?ÝW-ZT:j'XZ Uk.]^reDucA~_p+PŋX&Mi.tnƝwi~49gq(]hYA-\$WΜ6]7@`bJ1``>FCqX? w3g1y*7N#?yww enL}bvcyfebv,ߧ?1bx3ۨ"kӳ;c(CtꚺǨ[YŞ=H>`~0nML=o ŊRѭ:O W/xjSb9+am\jaafy20MLzvb vӂeXMa0JRkfu>T;kNl\[Z!7~̜@>3nJ,Muڴi,60}Ŧ\7+n! -3_v@HBnt+ /}{m=(l~ޛ-4TgTށ|<.As[`@6.]\zxr#ҵ9s|п~+)}kΞqɰ>T ;h%wQOw}G~UG||F}#~/+c:u[[P &ہk׮[9ѿϛlF XFg/)`ΥKVTI~F)}I+U)H6.;͏?6dQ2D YnKGiLA3@i!_y\ 1^F~XBa Z!M[7'Nx1Rpecr^ L+R{H]}Χ6pө32+q ^Sę ,neY?Nϥ3-\%vpsbWl*uӾίËo&y(S$ė GVPa{oyu՘?0;ϑ;!s[`(2c+!-Pj%_:P ݹivV(S߸:j}iWŮ4:|Sl1 @]6i\lɻ]vLSr| ;!=كA0r/-|D!C0(>ҸqjԨtz[(A+Slw`Ҹ 6:Gfnc+z fm  ܪSUmʧ;K~[JeoL5}=eSA#! ٢ȡP:.<tz;#'& "Vb 3:oq׃g vĈ $;|t?`K1k`A~(; ڑB!~LX٘- f?4kS#ؔE]];1Sڵ}_jTirу-+_J%cqxRgUu=|&9, . !իW5Vx_UreK԰bBl ^V=rE . }IfM[Mg[ϩ [Ϟ䣏ӑ4+G(:|+/>8$f{TdѮs2frԒ6i<(/+8Pf/0E btl_^u5<ύ%;#vc:W`؈"+"ζe˖UVCL5_f$ Ў3LPv6a,~%8 m Vrc6&#>E{̚+g'My O:ՙd͚\wn KP`WBP#Jzm-q1LW\kPQhȄW!WFYbW# N7O. \Ν&=P{lYiWEu(͖-Lpmw?d:aMfu/!|!G0拗.|úE Իa]L6. ӈhRu춍%pG9z3W:' ھb$͇N2;O?0noq}h|>m t`͂Q{a$[Zhf;'=޺u be \,Y} 8?>g; .27רQl1r@;vӟrڭfDe} v]t &#)>9ժ߀DgyܣisoqRhޛtJjU+}QpIKhZ TܣOӹ>6E}`]:+3m 0_eAe5FRLI .2N/j-19/w9QqfcBgc;w[ [pqNo\w6MbfȐ nB.]tזTWB#>j r*5]f5:VaeϑA 9zCU"XWvo EOk AEY58gG2UVYT;MIb(tK>+3RDE S mVuW}iԨ,[O H'5l-[ -[>yT~ 8۩5LWXEdXd@d:YљY>]RVA-_3^シ^;;t|EG6>ѸkzyguO<&q}p \*[5o#S~^m,oFjCmN^<3v0.\l+QqP Ph$8~Gf\ :X"EU0 ېkɷ~A G5N1yF9@izj:BCepT0zpکS{ED{}(MtD7{^顖$ Ħڭt)]t ӧO/C`FȀE=ލswYWbgC~4D @B P0S"_MW аN6uFQa*\ۅ4ec1J}q)?2Gz8'}&/U^}3hiCP!¢.m08 :1.QOϿ4dž:嫥dxj`Ub-):"]pV,PEVDy4|^)mجRi?s:|R7hE6 cuɦ~<ĜyEvmC{ L.9zmcs<0ٳ`Y2pap YB7b%m2&׸=J BgΜ1Dg X 41eނ0-?L Q|֯mb*U(MupmݺSz"]A jqĠ|{S,[#mB+"H-yxȾ)/W,/Uqk =V8)tL$.naW*ċ,i*iCWϮS|ۿlF-[JɣqDcs`beGMgֱ9ǥ}B]ָP+ʕ+~f)P~j(K,sFVw%}p+vvr P!fx":tL|gAGb%wm$2/94ir~~ch\Z_pѸα<܂p/>(< .$uv ?g.0'8&שUZ%`}*)P >+utQe~<}{(RgxqD%_ƌBrUxQ晃bg:EUV= 0倎|0iyT]׮ɔO?|{xm#0OLև,R :Pjnޛxwf͚E obPY. ym,ߔ=mPD̙bV2. Dt5+[FM IH > dK#%+*7N.uޟu%Kfa~8?P }R`0WP-Sfigʁ_f3v7:I̙7EyruQm: *ގv:`; x&z2tBLp(СyG=֞ms^r+X8U(_Q`+raٷR\))Z6:x/'W^3ڐ p[--Ŋr,xp)RPF kEGgZJZPp~wv0y\1jI}aMx$<[}Wd`x}?}`_ Q8}Z]?;oxv{V~聖n~s21D?tp,YܜFnڼŸNqWdI0b>%v⣓s2Fw5٩|Ǝ.;I\9Uɾqw/@]}O>f)i'B (G"& 6L`W!`d $6P\  !izvgwvs;=3|B3sν|sg?sI#=>0'uڷ'O2;hZ uUק?oܕn`6mܘy'w(CD;mo7L@~Ĉ_\7wm'".d_/JƅboSb8|*P7R[t>ŔW߽~?-Owo;MK7fOq?.>wסi/T<<}I7v=3yNWqxxmWg?<v|:]|EusZ# Okpgo>t[^\&yD~$Py7վ~b=*+^9# ũOsk^>k}qbw=Y哟H:'}㍯iEݘ_nbs+^/c|aJZOV\!@l\.'?'V_q5FkoMťg;[3}8w- 9Wocx[{{ժtm1X+?M𶷜\O=EXC&.( S7qw?8Lr;~kOUϕxT1-7N[;:>[b(dzm({Wsmw3>ʿs.7 3~xGg*[:QuA ԱX={ځ%`hteGHF[ kQݭ[/Lqѩv(7󅿩Fb:-`|Ow_&mo~*e&To{/X$7#J",8/[NT+]w'.HF_o~wfy$d ESPyXގg| ?FvLE|'.lqNgxӍąp:S[ؙׄ~339}c6 WmqwmOu&{4WڕŨLaөx>Dxlƚ?%nOݓzt٥sR_,z /K֟必\5"/)b6 ץ y*CyT؞G!N3yDhnXyRbzmy9ſg]o]מλ}U('P >9U.=.{m[Ϟq&Pﱦ TݏS ~zm1?I?Pxv@& PIX=Rڱܿ'{<_ StŅźG->x/9.=~mq˚Fzyf=ÁXܑ~tUWC?Yh6E|Q)”_)-wE)y(UR L&{y-b]4X;3&81XHlhS+;=|4wwR~Sb<֋6)k5,Gs%xG*zEylP;Z3Bg7L@%zG)exMOfQ@25w5b!?_o~';u쟑#(K _ @`-Wۚ9D*CtX^z022Z;*%5S.[<1Вfq,@\ ۱=ŴڤngR_OAJ7L@%FfbkXG ,-u{M_gddW  @g@`F ZowtFz'V5Jѧ[i  @ @ @GZ%4T  @ @ @uP+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @rZ) IDAT @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @#08[-ˍ:ww5  @ @ @@u#T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ T x&@ @ @ T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ T x&@ @ @ T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ T x&@ @ @ T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ ql֌}Ҕ6#k[2;>J4 @ @XykWt?D4۾|,ZHW?oyjg1 4ҏ]HifDvO/M=-5Q~fzŅ.;);i?ǜzoB @ @@ee=kܿʷwL}|u3r7N_^LY:u^76+6 iFz? @ @"`2yW_+БVl3=Z3+z– ”yʰyIV.L/i Sb{wN#:_l6 @ @X F^ܐڰuݟNi ,MG[>9.Z$m]KFN>W]<x}494 ];z`ږG\y~O2an @ @ @( P]?%N7ǷХ{_+F%9 e[k .IN)埃V!+6 @ @ P)Y[G>ul]L'$i2Yqw7m݀e\I7 @ @ @n|o])I?=~De.K=^dyzIs\b ҜwwVG @ @f$ P|E)t;]ELXGhZ\qz,ckKJz֛??6kK&,qbM @ @dck[D.ϋww+Ǐv&[GeG^Lfj'gH+'Zix3e ?2 z*^y @ @,6#TQKl^ɸMό +Ɲϥ`@#]Ryb#-o~S?5z.8'1.`9QK~pz;6+ҽOty)=K @ @ @` 2zxIlZ#9%i.=ZͥhE2qs8}gݕṣKbʓ/<{F^s̉Q*iat{w @ @ *7[gI>8&ńSL5,W}@jXaC_z"?6-?5<ߞHYP%r׾?= @ @ @` 4t8M˦:qkX޷]HPS3[dh9yo{x4zl$-l-g/I9ytH:G~[(%ҕy.s{=iU^~9Kbi#퀦ipʞ~J7[Sr? @ @[kۮbKWKGOGL?si`PZbm%ɶ|t9H9{;RJKrcdv#@ @ @+ PG}aYySlCgQ)l|'K֤^f#Y|Kw\vYeZ:v4WkC9v' @ @X cWCka@PNns @ @(`Q{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@# @ @ @FTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XYbjIENDB`graphql-ruby-2.2.17/guides/subscriptions/redis_dashboard_1.png000066400000000000000000003116001476434635200244710ustar00rootroot00000000000000PNG  IHDR$ iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 548 2054 1 9@IDATx @יe,^DAd nq[m4IM;iI6LIMIf:ͤiڦ46&N\5%*lW+e?]{+Qr9.{wʕA0 ܒkkkoɎS$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@` ?|\Ss4g'$@$xo&R ]%#      1G&              1D€14* #@asHHHHHHHHHHHHHH` 0` 6J$@$@$@$@$@$@$@$@$@$@$@$@$@$0P0Ɯ=&             C( Cͮ =1gIHHHHHHHHHHHHH `$@$@$@$@$@$@$@$@$@$@$@$@$@$@ccoc              1D€14* #@asHHHHHHHHHHHHHH` 0` 6J$@$@$@$@$@$@$@$@$@$@$@$@$@$0P0Ɯ=&             C( Cͮ =1gIHHHHHHHHHHHHH1WvH`ЦLF9oDkwG=:~~ ԯ܃`؃ =ͭW- (  r6?IK!hB<#_Cx-GQsf"_@Xb/pT)_?ssaM&?0WyIH]A/W@Ur/^x 33 Ht&            ( tiH"8l gt$b?|!q1PV7aʗD>m'180fTw8?@( *#            P&f"U ty{! C5:op<7븊ݏ(ύY"6"d 65?HHHHHHHHHHHHH`4P0iH%nhu¤%s1{1~z]to:i-B%wGGEi;U>}~&-. 8@E$@$@$@$@$@$@$@$@$@$@$@$@E€"I;$@7->tKSDT,?`L~iCg}#wR&ʶ({u6%ӟbK^*            0`iHV :P㟡 D@A,H۵3N)njAą#L눞U]Nm" @a 0L$0 J9mt4"a¬nAMÔ/Fgm= 8o`\L70qlu7ʻ$@$@$@$@$@$@$@$@$@$@$@$@$0C X& r?hsaky )J]U:T\ gNn"y.$@$@$@$@$@$@$@$@$@$@$@$@$ ,$@c8/>k8̣OM_@ %ZOg%.D_W]'8描#J`EyHHHHHHHHHHHH!@a7H`PEe(ƥOO#B'O"0&??(&-0J@{-}}>^"nøiS3 #w2IH o~~v N=ctPd^6&]fԾ')28,p8SwZ$].~}R^ +͑gXb!            [0$@׌8wjtO=uvWk&FjoY0"ǽ"iiGܟ_w`P导ZP0:'&           a Pp F$pGwS3NozQ4~d.R 1Qh?S?({iIR8̏9._0[B=2˒ #@apvH`xz1pQxDMj!4nbW.BxwR/ „Ц$IAK@mTB_}`HHHHHHHHHHHH<0!'  4 " P"&"s#_5E ɳbZDMz Ÿ6WkD0 XHHHHHHHHHHHH`yy2om2ϼ+V{rTl$-yII%B;J ܰe"(cH%g].>;Lc>B 0~nXNl E3/ݵiɈj17;ۏ;‹B`]XfbPfol-#           zFע~ .i񙋱郷QLum'݈\l_ Ʒ{rjqߦ}xk7L'D{VbЋykWp]?z!q10=?0G*z.WVǐx*d?JM \߈ic: 0䤄?qزtd̛; xٴCBߍ\]}qޓ[aU}"-:DNC&r`y*AxV<Ývn3*@E Rq`n?%s|}xx໾l0^6u[gNŏF|}9~% ]-p깟|I0(u<)Q>]fQPS#IHHHHHHHHHHHnUUً x' ݊{<)n돭-XV|Qn+`Ar]cCg<(w ^"yx;""ŒO帗J[^řQ 3ڰ&2:/]u6ǿ}"PBǭX?؈q1bh4N1P+>Ít|(}闸tX<0FJ@P0µ ꇡ7~twK?.1!hEKGK |ьlq= ⠐0he>AN-Kyn0N=; .4ZDEF{ulh\Ϫc/TՏ M N%S]]B7&6.!_6VhHHHHHHH>{MKg}xx;l;W݄staFJN.2cYŅޱQc@ʎƙsse86Y-/]Շp @̰Ѱz0L K.s6}D_iiGS$@$@$@$@$@$@$@$Ypf/橌y/ER{[ϕ+e-:!櫕x+Qu/?Q!,I,gcy6$يvʣ@`Q@Ȥh4M-mMSu(kh8"gg!s#gS_cTJlUp_჏0.9YE̢ *WaYzNu- PLj:w^ _[  H-%a]3յ(@g6VbeA*}-5 tTborע+8w;Tjt&_Sw1-WGe-iĄ0&w5]6]8 \_.֤r#![S֗ 3|^W&c8{59kP=nbGDA%P-)! ۝yd#*Bk W*^AZ9o$= Ix}Wǚ-oXplP%Djv/[ :H\8h,IhFmIq/RޓSة:p~Cشֶ2> +m }K8qӄ3: {$}C:=ٻͼO$@$@$@$@$@$@$@7# HG8~rטBX.A I+.6/m})6绥Wupt{%__21oy%RTnSPᐱ *F`+w"QPٙ`j(>>՗n؜|DZ^zv+Fm.FXb";m79 6@яlOú{ ]1!S3Ew ;G[kNľ}ʗ(݈m)$@$@$@$@$@$@$@$uYKbV4AgZN|| ^ ˔ƛL6"/͉(@)+lwa:&"ޅ^%*v=Υ(@mF /?o)}v! Phɭ!sDTaM8evBlO@k`lTԶ8KGtxsxCkCnHE̦y-9B]V>D;@@h0ο }zGދ3rYNDx0C)9yTw߱۸{Z 9'+" 8i-/yM 9ӣN~D`j[$M)(. )!%mvƅK@iT!:1Z$hDž:]ІۅA `_]c$(D twX?$tp=]ڎh/\_?{e\׎Ɏ}M~h*!*< z} 샾*}׉ .Y,=`y ]@߈Ua'u(D#\lc:Ҁ ;dS Ytm^ꬮAXgSMe2,vGɈD``'ZA Hm>aqX#vdD繿LNtAh3aBX|di-st]z(r!yEԄP& ]h&80t4WkA=vI#Ӑ29s]~=tܪGD 6*=r=]lLN@DkvK[+Pc~/g&[]^¶ffTuL_VIhF31m MJ %Ht)Mjڤd~_8 X : 5hl1M2-kE91       ŽC߳g%Zuvxk,?œx7]Wf~;_8 +̰Mm']Wh߿~&sK/Vq%%~S>8W;i*`}U 5t[[>1dn.U(sMLlz8'_Bj+m>7֜¸6 r,~klyw-I#/a(o+oa*ܼrعu.~330njνqU{έ g|{Il~301Qh: H][PV@ͯ!Eѿr[W|br +ED0Lh(@B5.Վ`.YxDm,(>;ɘױi3r{E 6D,\86ԔTnGCjs,3ݱȞmߞz I7?aԗ'=)E:3[-fρ(cݥǰP&GslR~ENssi25IEn1+WePуƒSDg}K9<:z4Ԧ<Ժ9Px ) Ȥ\̝dNXYSSEׁ\1q*̙$É^dg8EC션3}R\Љj[!23(|/!}M,Դ "9n׹e%@ dteR܎~ WAQNЈɫh%U_8,n,oj:)℟l`-CmؐљX1C]8Љ>1tJ>fDy$|K}ϔ7`||ey A&fSzY >V'iQp VD[*e\KUi+d,MR͕HQafy6'0YKd&;i[$32+"~wcžSQbc'zx5vVõ*Bӱ I.U× >IR|>~rYUCޅM/㱡{s1_$hH"tX!J"sxGı߼/}"*i+qYmߥL-_`[od;zn{QW(=߳Uv*?zg( @J pJr)yۡ RFWKvVm\]"rڡZڷМ¬>p(}RsiƱ3-woeMrM[wt'vvH{$-t;W攲߆حxkB)#W*xmxD$@$@$@$@$@$@$@&idFItqW1-\;_Zkl: =7՛om~oZah1gz.We/KhgwbuBd8k+@"y{#l1}(~ur}5Cܰ⦕sc«[6yK4z~j[3ZϿk;wuÜw"\6>%VȫȓX83!"'OLoTW"t76w`P1 0BO~bys{O_ʸ1ꗿ^L[fR~?%OO7pѱ$Ͻ& \_s1$)i,w 6)[ d 7/B~ `CՑ2_U 8Q&sq>+)Ѧz}T]u0xل*\;2&YDrkNK}fGO>KF1ώyd5}Vaz+%VMrzjgD!tmfG8 {Mi” '`ZP8 ެH=udg zN׌J v2,(U>2hR. >$:/^f&B/ٷAxӌT_<P]zue嫦9Z@dqVz, &d Um,]І|R1SPRomٚeܬľ`|ȊoE>F W/Iwx'&g8| E%ȝc.(aK_!ڕǺ F{?]qYoNʪm6d۴">R bq||m09" ݯl%zr1&!J~ڇدDv;ll% B),+~+ʄm0\HʿLIpDn lwۉ_A'%R`."TQg vMq/01$@$@$@$@$@$@$@$0270 [%{t^PwN``[񫧰@zwe/tz$mھy[C.V#5XaMMo޾-V*=wSpq㹥-5*nJ\IQ"rѬݹAwU]rzbZ* FDxi+ڟObCy["zY =^^ںyFcb,qOGO3ID6 ӟ鳮?^  mh"0{smRM8n[nhE]SduUMA'F-u-{e/LEʸr|3f!WmRi%?-z[W^!p F2V'F:*!_wZw3GJrF\ i.8f^ 6u4BCjg $PL0؂#DA"Pi^UaA6nۮˆ#ɲ|_T{'E=u(JGΤ 0\x6IQԀ)c쌘"+E`;@ȹS-NsDƥ@ASCtދc9.IH0QaQՆABJʷFTWa@r_j|@V'$OEjg(L] ;(ێЌJˊ`./Fr8.f\:Y򬙊g`W/!.\#ao'FG[0D`kQ(Gqee- \@al:PmA<> D8UnGSS)tHrڠuL̟Z57&`阫c        P ?] w g ͵9kyW ;9HCT&AN@raH._ҝq}ɝt0ܛnZ-+g.uqᙵ&b70+ meW od<٫zһ94RB4J[€L;!&EyqJ)Hbe+dxhhl KaWOniHi3qaׅRH99ScuVrY>؎vy[1 qupXLl0ՙyا.4_6C)x fLx3{j_."-hZ;}= 0OE8$XsոC,_'a ZɎA)Eb@ `JmY fK]BGQ\ ܀€=xt1%C[{7@YCU P9M*`Δy9阨wWAS wӷӗVody ѢR"Hyl?-WAI,|2;}h͟Ua A8>}l4@o8kF@ɏ^ÕU eA@ޝ61 廍[ t56c`0n!yxg4{C[^VҕxȂ۶uЄXY3 UJv7YVC6ZA&;$[qnm8õV9 Cص~/oe{kGQ/t=hmspto8P. =I))H4!s2_H .zc(0iREQp;_f~ u:瀭4/z ⫫v=C1Zb0SVu\Џ<[fHt!F~{w @%:QWpnlByvb\3 Zd S'keA H4/禕rkDoZ*~1̓'i1ȇX_3c'͙#^eC ;eg:&2aWFOF:xN$@$@$@$@$@$@$@%p]0+PIdtxufH鵍XNJ :[]AG}oȭ2?hgŀ|.T]!*BY1*>m"]v['l@^zmwo#?q+Ӧt8^g%rzD/GwS emLҝ0@ 8f""/#bf N\)D=+1͟PڛצOcŪթm5pY|uEnJ/ɪ2nƝPwgqd~,$F#|pbMH|H-hu˳6a@GKNtSDH Cjj.SFbE0XU^'d`<..ch.y|3@DT+iv{S%Jɬ"ԣ;2G\ /Fۇ8w{a$ KBV8(l51IB}Z㊯z5%zQWd|>#ͫИX,ܵ}]&7F2n Js]i K 9*gy=m=^A$;OVڶ^BG{+P߀ع7yQ୽a&woz*Ar 6fΑ۵(koSh2 DBw/_q4^zZ$eb6$vۋQcƳ#ngp[V+aaXb7Bigv"eۀiƠح!ݟWʪ39gQwٚ6{W-&_Q9;.$]CPXsZaO!K&g-(ܷ DqinqKKդl0I%TV|re(bSN oX:n# X| ~eW"DlT̄r㝭KNE! IJ{2e.46ԣy\hhD{Kڇ]Knn7J86ȽKƁ2f} &$tw-{Ksk=Ze       DJeIKc39 #z^Fd_ãKPǤ- KCX4yHOP9}7n@R^k~0 :L4(4ji*Z;o*I%"4_B3Wu(-h f!DTL'8 XVk<)_;~^VOEH1 ۟0*^^B㧧Yj*.\St/OƂbe1C"C5A|j'+#fO!7*< T62:aǢ"fED@d\*҆ӂ['W,09.c&DZԓhiq3X2xdD^nZ[7"sehlkERHP$s7 oP EOyLkZP_l:oy_ r<Ύb0s̲|ѬR Cۿ-oqVebú=؊&O.YF! 12f wT}jm .Γv̇kHHHHHHHHB*{Tg%/ϸᾹ 9sW`gd$!k?y}ZꓫHm<󡹫rT}8#H=gx}3`DQ2];vlܯ,E2B7MVu ( L%u93W_@7A ;_wP?\-zZ};q W[34vQD[HIAK\4*C {{qtF*,[lz` )?> >g!VZe2r1/TN+Sg T e7[kDf UCu䏬빾*^6"[xj^IeOS5BAdtrކX̘j 9 -lYYΫ\ I+e.CvEa[@Lzn}޹`o,+sk ѯ:z{P(:ovƩ6B'}(kqkPc.0MGaMoh +5N+Ay*mMgPc0!:6BJ$^B'&Nm.>v;'Ȏ) t̯AhHHHHHHH`>GUlYq[ƭZ]|^J~HKruzPQmJ!gq\-JYTn,=qmZ}-#9+ l#Qix]C8b٫C߲R]DCXf]O]%l}tubM(*ej᣺PH</00@V>$<ʔ[ R~݅RK?x2oH֯b_eŎJ؁J ( vm%{D\0\8qm6,GYٚ;ZD{Me5g`U@ZUꂯ5M2`aRT:- qFl|tB+$@$@$@$@$@$@$@$xwĬv>6`54yTzB0~~ӱ鵗"˞1T/7YNVfkM+prņ77G14Ox<>Ϭ@zo:w/VlkUɫ6a!9'_3GWpg"rX&̏%0݌NoJH2mMoz۶nˇڏLM#GMMB98[e/NDliMD8]rjL~M|ouoX8Z#8.H_&th=zŝPEѧ)6ŁSqzB' %% ѝ.3I @V? UgQY{i2abQq' tҕ>wE7 y/SmP C +N;jm֟?w,B\j*NCDDBي u5(S]*QR~uΛ&{'v] nHiGM)/FX$}%f#cJ 4hP3j6lSr7:'Cw?YȚ0q| n&%\dE&5QajkF3(=jB 5Sfc9Ѐhǡ|?~ۘ\x4%&zWTIM9N)T  N-lHcJf&"ssQFA@$~jdFfSD,*N|ZuD`\˜ 4@$@$@$@$@$@$@$@fO)mǖwmEb{o9q.aNJUSc 8sک݅Xbn[@ֽ& ʱ]Xi#[rZ)Vl{j󐾬x`m] ā=}/UqrV٢"}FY5^{yk/C^o~[^MD:E,#ssNYdB"%{R 8+7q7&7`G|lnFw6<U΍"` ^)+Eׅ5Z8?%mHNHK-*@!@Vv+nZU"P`܆C`Bě5Ac:S2؁?BUWTNI)(4p :sph'|SsgvkN@e--lcTu0b\h/ Isa۳bT]I {x0M 0 m_;+ 1pފ(~u ZkM~?V!]R۹8Vg*|x< Laotփ?B%OPC±0$r#g<klj}I^ N[py8repbaɴ5ɋBVwTb_-뤼*2GN엇ȿk5^Y1Go!<'qX~!_f4ĚiCnY^ni.e;ĈÅyg{z? S,| @,~hو6FF#/nux9BAO+J"k0NJNu8ܱ2 \GapױaQtFyd ]\j8BΖPO[+bO.j8`C hh$mRY >w6]o6RBJ0/Y3ⵇ7? <7Vkpߓx:ȧJ+\:S~% 0\f<`6Bz uS!nk ӰauM [a̻fw㼋:GymܐFSy ?ѫ% 8Dž9HX ?8~rl%8@޲Lwݍ͌ۂc{d {{T-+u^J?]% GV; >؋sE8^RaB72#C" ҀӅе9w!zO+[]H3٪h:t(:s 0=>Ao(]V~K/,Cw>K+2 %ĩcnquUtv*D&ېXZiBƬH߃}WoЙs{RsioAm8^ M-(.pJ^s lAƮ ̝'3 RzR/"+)Y8sjJAD371އwc\1||RV |/$0i0DqM@PxxD&J%ZR >KЮFGkܭג_[ĭ(KRI  )#N>=ܓd$ܯluz\6L3JFlT&`Ў&W uwfpi˾:A /Q^fpG]5EڙbzۻmF0s?l;;um;<6L8_ߐ6=A]͘x.ҴvZEգf}?*Ahu:mź1#S{uah>GgNeW^,۬mC9qF&0"͌$ᚇf    @zN(X=wڹpwFtL7gUoG}4/nGW/ץel}Ӆ7]2  `?$ nVOF -t#OĊ##ʌьg<<}:E)C[d=&us?|Vʎtߙ_W|E?aN!g>B?! eHzq.0#ꬫ5,4|u֞ MH#\u?ӄ|UlЗu+S%߽;[6te8GiCa:;MgǣuJ f̜ 6R#>҇!uX-o0yzu)@SdZjZsआi%ci:#!9b]o~Cg)߷\}9X#|%E::wc꡴#}ɻ;jڵAE:43]BƸ1+M^%;!O1 61kV Nesi%sOXuU_E'>_7 TGu"mt\=t]7JV1Y/6|L(cl_PleҚi0fv`J2y\ك   #Vܣ\+O02@G|A?+G$t]hs7#/ZgzS~,`nLwjޗß.PIQʴcgSD@,m3o8AtR2ם7׫pM3ä(v{f꜡wT fd ^ .eSv5_v sĒkOkݯf+,'giœ^oFHO/FLp948zؗOGp5WMg5eP_/?M׿'^wg`A0뮧E>5(%1ۮ'C 'y;brҫ5@?IY>r2d,9SIFNVi=,K7ZOu^ƭ:W_@ :Gd˾sZacGkzod H8q|x$rxP335ә믯cKSNMfe}Xo]/G[ Yo1zasEBS%"9xZ52Cg[WXHLFb I   1 Ȉ]k8~ށ޺%_.f䁵׷q7;Dc֏~y=wg.qȾ@3~w`)N0sf9 &mz')e\Mjv%Z$Ysv$Jv hX@@@@RX8j՞mqNU;ڕ\4Cce[?_!& }8% tƥ_ШsL^'t;;2_[ V)VZIʘ]Q} dG_ :Sb5/A %lK*j\9@@@@ 7j B08k,X:TyEY_Ǵ`Çɑ1N#?wƘQ\/~^Ýc}C}uf۲MwoF"&|ZVpfZn[2AN~y1pR^7Քcǎş  k%'6|8t (@@@UX8N-!C5u%_'?^3b@e.}h 7preyY:iF8k~A=HYѻhjw4o/]?':['9BOeqH-|@4|8c."   I 0 3G@`i\T pjs/~a?Č&`6a'ח.Rq/Ti#|kφM:~`& k)/ ^.ͻ+t䃿GBS      @? /QM:ӄ8~)B@3IOwH>B;T!bɗ5aNF?_ծ_M?"CZ~vmRC2NKY}<5@@@@@@ϖry$ A֐8 ? j ѐa7mΞuc@Ӯ&@hؑ| vYXa*[}a !     )r!54v=k[0G?XcyCc.'~wKg{J_X1|63D@@@@@@A)@`lT:|F9?tсd:+˔\6,Y(}y>w:qp)O_9yℯ]o-g2.U?ݬ!C*}i=sJs ׉#޾"     )t1 gܛjN_`I5s53uSo'[4& ؁Ca}[dn8}^󴆍yJ=JC Ӥo_z￞Mf      腡Z |KyaykXqӦj\>2kę'3M0lhS9n"0߯"     #@`S* yg>tN9yta_mFN g J5}@G;6dxF @@@@@@φL+@}u\W\ac;{kQ,ʋUȚ2oMc/<713Wrk      @ H"&Y!@j lÚ_>Pkj=A'GM\@$J;d҆Q s3R97ޟ$#@@@@@@Q]H\6ύx1 xv7@@@@@@@z'0wI       ,@`@*_       @/ % @@@@@@@HeRP7@@@@@@@z)@`@/I       @* W!       Kz Hr@@@@@@@RYT: @@@@@@@^ K@#       աn       R^@@@@@@@T 0 uC@@@@@@@       |u        0$G@@@@@@@ OS @@@@@@@`ĀH       @  "       ۑ@@@@@@@ 0 /D@@@@@@@ q#%       )/@`@_"*       @$nGJ@@@@@@@R^DT@@@@@@@ H܎       )         )@@@@@@@HyRQA@@@@@@@ 0 q;R"       %       $.@`@vD@@@@@@@  HKD@@@@@@@H\H       @  "       ۑ@@@@@@@ 0 /D@@@@@@@ q#%       )/@`@_"*       @$nGJ@@@@@@@R^DT@@@@@@@ H܎       )         )@@@@@@@HyRQA@@@@@@@ 0 q;R"       %       $.@`@vD@@@@@@@  HKD@@@@@@@H\H       @  "       ۑ@@@@@@@ 0 /D@@@@@@@ q#%       )/@`@_"*       @$nGJ@@@@@@@R^ =kH@@@@@@@ϠwF_~M'x?CG8q%\Pc XOt F@@@@@@&`R VEVXp# Z#      ),``0qu;4O֞M;C} ZoiO,2bS(       YϿpM&"3 #VpW5i@#\d@ `2ӑ6z{`y/o&='͝o lmBς7#{Q#     }!.:o݂Uy }VF$0>8hՒvZHv搝l.V"Umm7EʛC~jkJKs|n=ՙR*m_Wwߢ 6h FxiDn.4u[,rܣ5wj5Ev#̪u^jP~+ jSfCef Kw㾎v=B_GϛzzʦgēeZS2d%%ZrfoUۧ]rBfc_њGDyEza^\}> v!     xܻ9nO,טH)vj`Xk_^(uQa@@@@@6}r9pVCO;CCϻD#T5Ss<"wuNv0~f]v'r`} _J"{fhs1JV5|L1kJC?%`;WGh-k*bE2b\ (]BV[haTt]+ )ccJVݞ0BvyTuM<|_ݻTzm*_rnqH6(rK,;8QanAV 1}uk^ؠ-:/-&("LMIxG{a1`/mOM PdUkOб^uk n~[]J0lP@®5w͎IdO0pϧ/ 5|&hUZ \P@O:-"[kʹ=-y .2;mkf[t9`·智T{oGF     @n}:t{: jegl귳]'Tʧ@ 4ߙ%'0$7T vEY%NYY*L*_kdUO:h%|ނr=t|͜l ؼ2 D4ob}WeQZ֕uKEmTq*ͻ*܊PAZ =5pܺ?B0G0uϳh _G٪̌qQ%[CbM 0[FhV[BJo,2CBD-֩b .|%O;ιq m{t"kebl#5tIٱiF(^<_X9_ȱ ^)ߺ&[PʲUyC44_5/.5AZ~O?~)'xUN*0Cض Ζue*۪BfJ9!E~Uq|ԫTf)=0:0ڃp&xe`0Tb=sֳ`5w"-}ʭZ\L]<ѧ`!     0#csǝr9Wɟ.e'I`yo/}m:p׭P^;_cvvh!Vڪ{)t. 54ss%W!FRۡә[ X{? vNSvh[9"7QE oаYK>/ja>JL}5Mb',U%QF10#Y,y/UbiIV'kY{_ˌm4:=1R@H( yuY PBߴ`; - yr Jܨ-=qEU~ \УO̯c)} }&g@U뺿}Եl#     00:-״.KJ_l%, W5~ăvfm4Ë7ljI~>uL.RF-uܰ\5S'53H tF ءvpY?t`hwLSf#̼|cWaꈺWf>+ =ڻݖourdtۋjQSO+QoTxwZjLP@U3D)nq$prGq0a5׬h{ֽnJU[S,sc4gzciтN]~K5P<L rw&؎wM&QhIl!     0PDC]\ikUpUv{5m zm zi{Ҩ-&/nԻqs@{{m핎-ϡc}ѐqcp<[pm 5Ghn5\ u5͜ /H')5eb_>j\!_%7-crܿQ.zQYGۃGVYQy6j6G"OHrwW/^w]6fܤEl/fA˂8:u;R- $CtZlT28C1z!l^} EZ4j+KJԎߍ#qS}`-;66;!dcze}/      ̿tx۶ֻP{isk~ =x77WPԹj1j屻 qj0~6 ]7=չjxkEzuQa`GU*1]ʊ*\S LvEUlV$Pxy\W-Ԛ|RbH%qQef;X[w94־8l-QUo^͉5)$f޾N/7f:}Br&D RWQ ,nj06l|K[ k%W;Oώy@mAuڶ+c7pm%++Ӷjf#Ymb%peٞmݟnӢXG '}45c(ǟ66g)+ک%f3Gi?} @@@@@H}(UW.{a:>ߕ&7+{z($#Lav 0{ӮAд:|KQڏWȑKT$ TvC (ז=;;ǻ _Lʳunf#+Qɥ5ᆰ$NPiGҝʙlWL؉}23W1:{`T0 >IEޭjv~o1|02VCKĞـ9*6znwŀ@RhG"UZA͍Zri<<4{Dy3)]gT+s $(R_5kKDݪt Кuujj5_& o[S\i"IWSBb24miKƱN5>[urTt[Q e!kJ5mZ:A2dzSLRًy-|YՕ[d˵d ;A0p98S݆^Nh 7tNm(zyN%I]9 0(B@@@@8uޯu uh#{чU+uCW|KzOtޮta׍+]q/`!g?qB]cwtœ=2'?o8>\8uj14lm<<( f4N-^Ӣ*SpNRVR:\mļPΕv>8WOZ s'a晴65^OTkm{6 y_ EoT.g ( Owj_PɩGlO#0;ym7d"~Jԩ߱᭥AmE=,s|vjM'whh.Qw~QNJn)kϨzqmڅ&nA䘘bS!Q;5NR rBdjy^Is&׊.ppR5HwkD{>ߨޒstAcz8؋Qr@@@@@8C3׭7˲t/31} w|Wo9+Ү`G9ojWn;umf&\ ZdV)O冦SuS^BSQ s;tM\r~ejm#t|tG_6mqʈ+oڛKsiZMpKsZ6q(=n=QfDW YYd4uM;N5&1 R p!c5wڟ*=%H yșhUz6'Xmk'AGd!     0Ӕ~d~:!nGv6Aܣ/Cf$(}//FZWA|r3t=^EZ+奝ʹ5^f54+VẮ2 wPi+m9;jz%!7sRrT]FMf#/}&dZQj88ewLOy*wf-~]^?_qi[<k\p5s^_v!Zb/Erv|ԟTpl|6ctR#JC[lSbx>7nGyBvPN;2BnFJ:AHV^Y0Qe;k*c(l      0k7XgՎqi\ 2ikowitsf.9o냭o;݊ Qm$ySSLvz4AE?b\7Aʜ}QrϏ=2 .m~< ,x[1~WK2@@@@@M1US~voz} W\'c7)ctn%nU';ԮVkoEc0jK4RzK:% 7*g{Ne:V_'Etԕ l/.VN GVjtƺxPyggyiUb]p|k ?oí*kǣW'*wzrNTU9x{# , |ּMOUd29u@Z[k_<9w'^oK5k̘KJVވԩ`GvѲey>"?h2%Ǩ>u+JlAEZtepf&鑗b/BEsy@B"3m{{}[gb~z>1VӔR|Eh@@@@@H!sܪ^my}8G7Cٲ:}Is Vg>ڨy?.iR5C ~ḻq~?xN{iBWiS@i]F ~{ЩB`@2@O. ɩٚZ5"uְlC{ׂ}gzLGʛUho Avȭ +*ײqN+7ӽ]-}xWr-C̴mW**-o^1Q&oOUU:T뒐/;LҒҤҫ;yǭ5P%؁hϢ7$ץM Ge9U8Q Wʴh_)˕RM{QO7D/TacƊϞ6.[aG>:،4b;\g!Lgļco]cKdե8dۄ8Kil"     7iWm5kz\niJɭw2 m}MnQy'P[/P3"z>~Յ+:ha٭ڕj(\Lޘv2,m6c7j^Pkm:f;eЭh]~W!T ؝YTX 4_Ӣ iVa*#ᜩ^#4*U]Ds&TE8-ЮgX Y kڨv;sS 3T+aEKTu:*{M>f.qZMKOSê5VYwjugK=P,8ѶSw{'zgktVB*-0soݦqn_<3ZRbx]TU@ Ng-.V㞚&̨5/WkJrb9G =wV-]z-l֤UKt`dTfj|3Z@{`*`Oz}fU3CF > m>iSSy´yɓa[LJ@@@@@K`.2mYױx-W)Kzmkp+g⩁YһfjǵvYYzk$#PJ9:z* :XY%;Z ݄Ee`,Jߗ7\f;% UW TJʍ;\Wi (VUtwIy{U9L Й*͌`l=|UTmJSu#tK'V)Ayg:4o8V|y}El@@@@@3ޏC>}cku҇^kFmnګgmҳvFk5Žz#=t.~~H齭Jiwy"tw~[0w9pOJj].Wpeacr7ռVϏOnT.uUy{agt-\LX̴͝.vn:2lnfǨ-ݴK87|yZF[vG 3˴cG.+ "<~Q6#6uF/dr(}ܥʞc!∎*qcsr\cBw5)Y|eZ͗MXYBio!M3<C'^0 }HgB=~_1h|U}WPU#'Ke@e6j5)(1l &z]Z[[^֒ܣ!^E*[V;=_ @@@@@gTT!}Vڛrejti˴9w>}r=9@@@@@3)s=g֔o_abȻMnEЍ       d\qo߿B偭'"`];Z&Oo qݟ6#      $O@ӻzk:yěLi.zZc H1`@)@@@@@@,`u [)@AEv5Ƞ@@@@@@@# G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@X Es@@@@@@@ G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@X Es@@@@@@@ G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@X Es@@@@@@@ G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@Xc97۽INZ@@@@@@@N .`@N    ٻ;O8`4˿egɗJ*ЪaQ&6ZAZLCl +5ָ~!h7PDp5F sgasw=s    L)       tN       $F8S        "@`@S(        0 1Δ       @-       @b H3        -t ;"       L)       tN       $F8S        "@`@S(        0 1Δ       @-       @b H3        -t ;"       L)       tN       $F_b@@@@@ ̩˕3$5P̡gj)]O[W\c:\SKS9e@@ =r*Py)) !u@@@@lU|8E{>{;#\/uP@X^e_WEϚi x>& @@#G (]YԲ%W^qvos%uhPz*G-yvtV>?T}pd(cШ0}ofK Q3L*ϊN5'g9 @@@@'P& 0 E>4jv◧IVG$20C@"uyl <t(IG3=#% #/Fk2t6_d1h$9ij_|+.}Ixnv[0rfW\z7nSm!Q|jM(Dxft9Z62#R(%CNgہ'ku!4^0u7ϢgJgi: 1z;lD@:(,SciWR 7rxG^*Ǭ/їh٭R׾VGah?z>cy9\қjعhT pO'eɻ;hjտh.M.Rٽro    =M~s4*n[5ͻ]ŪR),RcJߺ*&<2c9L3m{@wT꤫aSbTvGUdywg;ʱZzG>X53<,!LC{w|XvD`%ﶥZ07ygY"eeFUNGZu<-o2T^A%Oe+h[EyiM\5Y\_zeoO˱Rw&9sP ܳ<iKUԡ{8"^" r8xOBYoDbIU(Ozpq*{FU4o^Ҽa3)G@J5!YR}u4i]&oQɍsTl &h~_ߛlUF5/T2R#[o@@@@WF2s(Yز([},9SY)٘IY4'saz(Z#^F|kіd1n Ur=9[B_>f?.֚;s[uȥ⋋BTF,~&iF@XxMgyQ%,Uу<#(J<.l7ޥRE;Z^J "vf .-*-עDpo_yu^ ~DOrFy꿆%7Fԧ7M'C;:g!U0ix^>m}+ӒWdcK`P*%Oγ&Zy-dC-jv^Yragۤk̗9k_ ~UC}Ê!\VDZKقYSulq`P@%ZxERCmjRke1.K(r`A@@@@$i+ό2 YMϖÝ3&⿭vG n5 =qI9NHWd֖*oQΗ3#YJ%Q*gLdF%&equJw$bM. X# 7u-[!A ?|^z(gwUXP@uՌ5YNzsM/FJ( JOJujΝkZC8fwNY>fkh}^ rjɪEaAYcU",zӾvz~! 'GP>iUYL$?.P|i{b^rϿ(CuYf˵y=Xyz@j^.UbCgvQ2x/iͣ/    =Q~LJv29g2Ң?c9fiIeUr-U*}ۃWFYԮoRsJUo yJiԇ)'6t|UZUf.jf/3<3Rժ!ޟz s.ԿCT|SNICrTvymkSUhʹ /:}/u+t*ϚLWo+Ocb޶Z_SUi:]}Ry t^st͏kɛ"Q[ 6Go"m_qWV2Fؖs3V/i/yڈ~6_ZNx    @ x}0{doְb7TcSZtm؟OAE*{Z m|)F.P4rzчu225Б>29̳V+!Uy+U]3Rj!k9ʎa*$QV7P7~oq)Lpv+&;1;Oi#Yjދ;ɡԳR# Ȏ  lLĦj3_Tg'``r77~KޫVK*{Tl;ӯo?,7$GZnkIM`%s3Tp_蹳@@@@ Oվzܪ޵ɮy2Vy7hە*r b~ό":rNy?wr0S ˪fXPe*Uocb_ɦ󜶱SiI͌D6UAޞ7Ԍ3w&fѿnFHi|6Ԩ>c֖6I:V*PETZcFvF@NX V-|ݫT9mYӣZSF+W_;ۿٻ?HwHC=MM&R͌ly(ןޟ@@@@Wüt_VMZb˸х57.pF;>)y5^[TRoR򮜮ǩ(75Уլ{˥ö yֿZ7ޥO+~|p\2)Y*UçZ[̏8'izv&ei쐘F͖  @gz_`9Sǘ|-rzhE31SZn~)'s!T@=Tx(^@OE&)@@@@+-3C0 $%+uU*'ec5ۚ}}ݪ]?:0uM#EcxvxV܏%_0RĞѐH tK ?*eqV4`+8}m>W!~}W{e&jTpSk5cR֚֬W( ~|KW,Tq_uNG@++,˔B4N.SVȗVKsGs5vo.chE7 HMLRpK y;/c!    pBTh0]wʾF%5K ynO%9Tt]ޑ=ՙQ\;$^#~iY6SѶMT3QlG<`CM6SF' +&)5tR3?YTt[_Fpg=?E  *{YNW26/֖h!g蹅{ToQv^4nPVͪ#!$~'f9a<7):+b@@@@+zQ. ֿR]5E፶uU~M7qf!lf\;V5,wPSnijYS8ҭ瑦;AyQJC&ߔ؜˦ r2B%bݑ9Vsd5IhM`Uk[p;#soje_Ys(w袜[T32?HZ[f:@D$94vfVHsR3 $JP @}Iʝ۫:;J6IFO޹F)P+rm2_ -4sU=ZK>@@@@zGozrWs?fL}t {zt@oҼMϯ9?| yVi[Sj( B6Ϭig~RK\s`ø&oPU|9NޗO:XʱZUk9뤼Z]9:LX|EٿV*G^*>g?e V+WJx]jjʉ   @W .hGcegTr2id]L%b;/3]+Ua{e\]wYE@@@@.v(CwiZW5IY?Gr5x?PV޾Ll6 hMj-cf,K2}_Vk{jogKXiVo\-W}EZVmdןm6U7CjεQ_>v- 'd =XUju?]_Mg+u9C7Cy!-עjb@/K{}TfjKCyմ DcyB~ҿ c@@@@ kԇk}gG?yҺ`^xsD63@ќY*~j65y[nP?bDNepVvRM/Uzy6cֳ mCFTR0ˌ Zz c247i[gv6ESCFWxPEVhGgVܟ<u9>_5 sGI+O%ژsnO _i+Ux]]6&9YjNSKh=MЙEQ\ޥYVjnL>qj-,-b$gZvb1#0u.R[Tֵ9Q.o, fD og;ђ͔/5Czw#imE˥e͗o}[O/ern_Ѳ V9*Yrںܢ˕?&8XlA@@@@o+c)wrxo|&-RW7:˜wV*)DOʯ~vmcߛm*X](_os5j诜rKVCGm22L 0iNsŚ˵ZQ`,U>Yy̏)o傸 ڑic-mHujTzEkѓ˔MgZ9saCK,QVJ뽁9X Tu%]ZBž*-'%/@@sBGRRV9&i>\d(CI}[WnZDY\ZevTY%S&9J]Bs#OM7UsJh"~C/V{Eb̐    t3"-HYg~:1O\9OںB\D-ׂkfmz/]Yun6]%g2[M|ë $cå*{\ ndW2#D5ixֽJnU3,"y͟Ycɣ\QS'y󯗵E <y^#=*3]/yd֬\;{|-5TjYc]lݻTo/Qvd}|VFJ5R9{sV(1u|/(L  pBO(6H==&=I& eu8_XJ.6M|ڠ^Uv$ @@@@HUm#ΠykSigm2ϟ'S4Kz   ;Q] nyN>hTwܚ_&3U?WS@@zS ‹֞*7Ձ VzsvEs~DCr4Z|Q;+nL՛8@@@,`F ?V/BO|Zdoz?,&  #= ?]dnhÕ6b2S9Qޡ}\' Nӱk8J?=~Fipބ?&&/3\[vmZ[kع cG jYV>g:_Cn 9ƺQinxZn1󶧝аUp}9&1/m74o,g==wUo 8'S_s֐M=cd#~8lG3m߬{ε[zPc0jD/vbڡ5tS4wFD9Jg@ cc_|.y5*)s3kMI+}KqMqy{駁Z[\ޱIS_i2$1   pJ 4|N"o[ǿ[ͨyg6p!^!  _ۓAh_UX {ͅEf9S&nt~B; CIjg(_B:ݡW{|ZSΏhvUfFizk:sSih{9PeYq4fLzF\#lE{:Rf-$"6?~-̿bB.G4=1z]]Vمk Q՟{F?#g9Y:Mjn5'ڿ"$ơ/ѐ?\΍^5m}t+m\oxnS5bCXsuhvDNyreF{ ~)UD9{Y0UP@܈F2>2?9@hn+>)/}D2nеo8Nj5Z?g:;F}~\y1q|hڵnC_>[lQ*<" 勵j qD엦oyG;Ãr'47Y55Ƞ묭hߔ\(kZD^O ~fksO}MDPkbֻwh/,a#<  {8:4(KҊ_O:wrqA˪.WƸW {l}YFVÊݓ>u답%2(B6Ni+ltW45eYA%&pDF;yqe ؘ2qP5+   DpŪzw^ۭ?suV@@@ LG~^ ~ʎhrUܢo>qܰCEh4oܯUpwXo齯\u[~_uсQ[Mow{+@vﰓoӫ|̍ju3T=H-7\A޹#yFgFOӰ~ЌN;^W-iOJ ש{愬^nh{~kxxiPu]ȼ~Lo?g 2bAOV.P;y6`|pa`oqG?0u qnBuiN_4 |#%m&ULcCl m/kj;};h[G,78X-޿u:b)M4;ʃuM?u|Jڽ6rZf̏1N=`FưF8WowAi^b$ij5}a[n+r}8jVC-#mC:ƙ kѾ&?m36ԽG2W}J`*;/k\k$K?ȿ0j P\Zv- x30>-ZmVڒu:h="W-mLG~Y8;3"  R}|tLL#`-sŜӗ@@@ @1/^Б55m۲C{_Q X'>ZS|UӮ0cz?d sUiʌ`Pus/Է_ؠyEZZiKѥ&?e`P{Aoi@Y}c`;}iD6sm~|T 1st?%x;s q~Lo 51eH3{(PNsɫݗf!M2 VVN:`3M÷6[S+* {d}b)J ciA֎35L0Kt}W+=Wi?32Mɟ9szOдetB2=v`e6U{Wa]qes5A4 AVϭ@;ɑyc5,' <¼U)%׮_i[eKNݣO)P#۸-\?%V_~=ij5tW~/^cOh?}K `nWݬQ!QY;~f#Wu|vxG_i&yhwQg+gېŭ;BK9ذSu0XBvź'z?_htEy   p \>뮼Pm/pJq  gOՃNg; Z$]Xlg?Y3ޒ/6Zo~'p\0IX&^/}R_fkF-_Hq?zN!cazW U]Gkآ[ۿ,9N?􊽡|F\qs? |pyO{G線sT$MZz8 G]{-ejKF״ow{@rɌpbTE!BCb/z·{| 9p{ ʵ+}Z2M=2HrL]2$Xrcpǽ&@;z5dh3͘ӯo!I   -мI yL+= t޹/t8tIZOsf@yI:Q8 Cx=Ÿk\}s/c]frNm|7ovS({n3̿o|q4yWFk=aJig; 4._w]r_ɣէc*hFJi Mp&Nh; X̠$;gG h O gl \|Mx݈Mn,-Ϯ&HabK _j1S[gbo韖dl}.Tg;ƒK2ٺCD3r1tD@@@@ @g3 3@@h@:q7^1tSю{1<ȽT1[] 7_ZҎcGw|mШ6L}Gw|fc >`t ~pa|Q sSu>F ks`?4h:sR2_(ۊk`J.#ځP "tYϜf#aS25n?.s,S5bD9C|ec77ב/Ը?k1R&ߢhT{C@@@F  X6]97)"  Ѕm6uaY]{|NHKژ\Qܡ#;ףrTxLނzF6\Cr9:iou=Kn4ΙWkqgq05,PXL#{hPD VN==V:`Cp'O&(|Z䮲S.0zh('vGϽ&i |1#e;\fk[ڳ Z"׿#S< όW   @]:@@:^p@xg@Wu4ҟYu㯴!8Cw2];Z}MbSlғ?Nh:O#!# &b[U^ތRWZsNх3$rP   @7   pD43s';= ]1Avܼ?z(v߮aI{߿̻]C 7}m؇t˞3O3v,D*z [Fn֠v_f{ F~ $/{H+☒FD:6I{'skA̟j>1Se)XZׁ-   IUPGЛ uE@i@+KwUp.&S\ځ%QǟIȡ)+B1tӫL9rTMdBǠx{ 5 +@$N7z=2{6[35ى=e*2i+1.l-U7vv.8jG˗u11,Õގu E5y$C@@ D Q_3@@>ζv_%W6O?Lz{n&4_F_C{ 7Ym{}Od5dxVxd^J7lQîm::Qmn`P`{۾Z[z=o/x:l>OF1vwzsemo~}Nձ=>e6Z#hPrh@U&   "*(Gp\~@@ .0U[v/Gis^ڨ#!YY<ژxz-y!4hz[Mo5Gwk k:;I^b'ݻ n%+hrAѸwosm ke^7`>zBD+vse66 O3o/Js)ݔś+6Zy;ZA][3rC@@A=ezU  =UGy5ռhퟆu8p6~?}N L̕coבW1ZZvk?$GF 3s Lc5y6>|CЖf4Ҩ120x~|+zP=':X,}UBQywHiZTﮎ`ޢ#v}O-Ggo;rk6y e1MbÝC'@,wz+Ic~t}֍;  س m ᐝr/?ab~sٟBVwm~vm4_m@Ї2TXF9϶퐲_{_V(}B{eXx3~tu\xaOѫV@@@@dP"3}W  @聁SY]cWmF5L] >joεz#}n]vi.3sƣ/eQ__sy.z!7֏,^]PM]ڳ5Vo(]7|M۷n x>Ш}&튷x7|C֚@r=s*vj^`]ulJ}R[OA}[Ogk8ڡ/>Wgisox?E [@@@z_P淃jӺyu V0@@(#x;œ@iwkĊ/yiSm_ j[XM_qՙ<^O<.].l MczNNoke^ 4GI׮M!CG+S^)|GiHԷJa/iX}pOoK{Ҕ[#GNO`m&܂c"!2>i3u[wkG|i˴ ts-=Ys?x\oHQU:'hIMH l8s,N)@X墿~+m4`#uκq?Ir:[UV^[?zK159ڿcnW,ܗ Sk@hf$i/TkSE1[\c1Hn/a_n4u\cVO'ʽorm|LU*q+YZYwƨӣ#i.wnL+Wy74녛5,Z6QKf#   $5?e^IqBfzt{3 Ƀ{=Xϛ-ss:H^<0yu8Nџ'om6ϊgE `     $@ (u_֧@@@@@@@z@w1gP@@@@@@@b @@@@@@@z:QK@@@@@@@:%@`@8@@@@@@@!@`@N@@@@@@@N )6B@@@@@@@w;D@@@@@@@St@@@@@@@D-@@@@@@@b @@@@@@@z:QK@@@@@@@:%@`@8@@@@@@@!@`@N@@@@@@@N )6B@@@@@@@w;D@@@@@@@St@@@@@@@D-@@@@@@@b @@@@@@@z:QK@@@@@@@:%@`@8@@@@@@@!@`@N@@@@@@@N )6B@@@@@@@w;, s$;@@@-t]:k_xz@y  _QZE/;S5Y/_jx6 ^^NDvVܠKܭ37^o{Ңk ϟ爵ؗޜl /1:яKexVx5QοV(xT(9ZğV9U?9EZ8*ArFH ICb !g-{gNy$gf}ך׬-יּ{25?6 M   Po^%Po SQmo3֠YΜrn-W8}9Q kۙ>ް>%Vp{y" H*:Zv#   @ UׇH J2B6_ l#  n1^6|b6|q``٤Ğ4e_CtI#4Xt?ߴ\e& ,";i]pvlhS@@@=_uUNֺu퀾uy:koג>>e}   /@HiUhifq6K_?7S6Z,   $kK܎5|:4y`#6}r&<JߘL-   h׻:yόk[N 0w?Yp^."  Sf3W|y7)}9-7z-'@@.F ؼ3;kyRK̜%)_?>k>/n):g~[wS'Uq6J]4g*\(%]KLJ+IJTϳTwR5sDlzSq8~JT>O2Hg̖s\/;ufgi']W W{TcMljG8%th2 L,u4ekXv*OTfZ:YpiWJmGAycIⸯ|ΡXX> q=]![4Kz ĺU`_85/O?u~I0YmuWFˤ$}uTn=x7>cRY[e?~C*;Qf:hbؿ&F1(+%VݔhIߚQ1?v9,_aΜ8N=\>?ŚSgn#ݮ?hy7K   >*Oi/}Dw4-p_\`,@@hN:owsV2eݺ&h\utҽ:6ʼue+F#4tJ /3] {h<+^,3Qӕ^4޳c%x_ {V*kV3j-Y2[Wk~~^{Y`M:ώ5ʴ:: J=;|ֲ_}Vݳh/s)?t}wϺi4^_S3%5GzRsL{ tiO=Ϊgڃ6^V^UI؛i %[/]q's[;IZs9ȶ8ں&X+=x}Сcԯo]ƙhgWUuHl4Ƚe&fMU~H[iIcyd3 +Il>x4x31 E7U+jc9F<~6;=h܏hՋ]-X~1/F}X!K>ݠ @ )̱    } jG/ߤӗs_e澚,fXjvi}Qv+C1& `u?ӄFۯFpH9,'IV5S;,L ;mf0   @5Z^> (G!3G@@mJ[XaYsq==,3[༆<}cF;zIA>?c=9|Rytbҹ<'͓כ^Ԩ鞩 w铹fpveOzY=vR-a*zMJs]7S]ʱgyvXiwեS͠X0|r{pg'J\{)m4؞ߚ> fMsǪ5C[ѤaV6O\yR̫\V@իu1J{lCڵdIp_Â܊%J enKey5gJ6S1N2ϊy?:]^ ajvVM C,6ޮAY3oϒS滋j5˃uMX9vtߙkh?oշk YwJ}vz@cvHM3so:~$ lP毯Q'oe4=*)w#0E ?]*Ifzf\\y]u?]Xy`I)^a;Nv<0󔼦UºM-VbyUD6[Sn4(XCw]qjIqwZ+V߼+z- 7[7DMV83T,'{k   4Zcw]WayFZnKۏ{@@@ @VÕkU:v٠UTYӽY:8,5M8M vMN'W'kYc<9͙2߼=&N&X%S膷3 ˴?ԓVf^Wiś`iwԖ.ϺlEl]~q/յwfW@mWΣvEE ՔzJ~A%OAVXIkak) 1|6%40;n튾yrV |CY;5K{>AZ')Sc̫>$l 6ks;5 NLљsTn<\TG~e>c+Xͮ> [disιUN6I4$X[_TZYZ8ORUr?s_ީOff(rZ}496N1vn#۸;V۟W~kM|~=f^/$jm5{ϋ-ck|0FMVw/5zc\yYzOaK>{[滫0|ygϔIV)-Q+LrT Ok{lzBa;@@@Χo,_f~M|@@`b@݄o<c-gn/v9*|c6=VLI֒n 8y?13Bmf.nW 2w"3,d;{j7>{- 蒮y>h#_k$9>dK=ק–gy9L3ykcQ>gVrfXZCΪ _:I߹OѢcO oP~k> wNfz\b_̓jK]ݻJ^8t~8; Y{ҦsܳJ>E[τp4GD'OҕKͱZG^\П<5xվ8l=GWeL^P@@@GԮ~>_ o<  E(&~r"gWJf?Ym_}kj]Ir3M;qdO91z{]68OYB 'wWٵgaMR; ,ՈK.>ui3f[tE8A˅ؘ4#@H~IgT!du݋<7&]1fM NJ]y4NZ1˳C& 6pUsdwWIR1 >{iT|S3}&%sX\ڐj{kZ6    :RlMԷb@@<mNGi=)~OW"e5Yֻb|zuyuvuGo醯`BzWa8otF>=zT<Ľ'OdV:nhCMbc_GN_N;٥̠yO>631~{V5]VF~Wr4q u&`e޲E) *{]Qqx>=B3E8ڮf8JBnY}~ל dGPe{;}6CEGltcG8BOg6#{74tҴǵ3Éc5's,5ЉAM' {-.*x"   dkߤoAAIk"  @ỶFQvȞ gJ]Ҡ@iQ'd7 [K=S̠ixwmSj/ȳJiɗN}:]:Wg쓞 f} zK0}V Sx`7I Zeʶa2{%[ΆҴ~߸ڥ224IסgN9@@@$,)B-]"ڂ  p \`C?e*Lmym %ѭ0H߸iZ_ ӿ,GT4G|ϲ?KN $'RV:D@@ЇYI'ý{O8mkfT),kKW`   @k ԇ  p1 !)'U 7z8Y/h@S֞ݽc 0ywJY?3TCԘr]F d#a5%45ӕ~GMEl?.>p֛r}z%Y(J]ho5 |jziKq@@@.P+)`}) [@@h@k?5~e..oS[]vz if2ӯj|1=gޒPlQ6^W6h$PJhԔ7!JO @\t[Iѿ :{R*m uT$:Uӌ=Z'x]}x   @[I?؀  :;򶎆+}OkGƷ>ڛT`{;kf d\y4+vV_㵫6sgUGkNd!0K֚[7(ѵ_hoPvuͼM6[:'d귶 \]dp5x%Cy_mUZ!cϬ1t6&"h`6"   ^VRY#I8@@ \pS%K(~ xI"Ho#aI^S]GyTO|'c͓R3$*W6o[#Zhk*B&PDz(s{݃ej=s/+OI+l\A?o- gtp{p^k{7T<.ѢJ_ēZ[c,TKbmŏ铢rY2߸jORT>زB_d{_O,ާo춸~c_8^iߓG@@@L Xh^$41@@hU:z|̴~*;ppu[o{pv9*̂p`]$׀]mqiv}S_ɩ2I/o2=ꞝ@JW[FĂmsb#h?]kpϫ!3pf ؿF{gk.m}x^/ɬ\:F+2e)>2A>X:C81ﯼ[]x / {zjֹ5=Z=oO)tۃiSyʵ+fݧOB4"brMx(pl 6)U#G$NH;~djruSs-Jz_+)Qցֆty9Ә3=](گ(]Z]c"CxF̾] xڮC8ژn0xc/bX\AYn_NA-zv`)*QSWO{>sjtx}%/wyE]]Ofzp}3~sjKt4vh>`i=Au_J}N#K|D&Gi熬;r" ׇ"Nwc^7KmoOu"~wmtv56W&{   @K D'mtkhtY "  p1 |Z[̼>Jr F)6!NI,3m~^qΘiíİ4qOVmu,/2UB0]zk䊶ze]'Umb:wS+J7by*7[=7Qk\}(OEñݔh #0I/ 11M@@@@@@ <u-y"1%       @sĀ6*%       gS@@@@@@@ک3 i!       ` @?@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱ@ǖ>        BB@@@@@@@ @b@{       !H f@@@@@@@ڃ*r        1  @@@@@@@h$9        BĀ0lF@@@@@@@="       @B@@@@@@@ @b@{       !H f@@@@@@@ڃ*r        1  @@@@@@@h$9        BĀ0lF@@@@@@@="       @B@@@@@@@ @b@{       !H f@@@@@@@ڃ@pC(H53 bĦ6-pXyeHHQJ|6*ӶmSӰ~JQDLS댬X'(ՅΨNW+Sb|E*~%@@@@@'xq8 tyΛ4Mܭ{͜|}ppmlpi i[ݧ#)㦵*oSM;_LgmlP˪rjtܛy69ibMgӑf 3&sOZuuܗggbshJv+ɔ}rr+'@@@,P(V6ip   WEZ<ܩP󍪶 aD}]9Jt:} ĸݰU9䆕){>[}v_sNڒ4Q'|9Ǘ);L}l@@@@~zPV_l^i]Oe:1ZG(|K{iTsqJFޡoS{KyX緽J@@@M lzBn6R  #۠/kEK@4N&΢4wv|;U랒ٌK5fs:J'stlyC)J7Ldv#   #P]n}g   m0F }k=m✍oRĻ4p f3[άS?)[of7TۭrN@@@#P\/g^_G)n@@?co_%ER 7gW J;ϞѱuPbEݿcNO;ѹEY&VhbYU}#Y_Ůww.)}7E(,=凜BI~R3jM~ΜkT|⃕w*5uZC9{ Xr1q KQTm$TUz+Ea,=M"KM؁*7g-9>vĩbTxk5W2t?Pn9s-wVE_։C:m:i{t8{.M^!MX}tiIYGu4a77'̽zƽA217EQ1u$ ToM](u6?sMW^:sXR._+ 7Ċbϱ˞ػǙڳ*-OTj@?8E}z4Ywa>!  \2Wmu tuɺ֋SG@@izZ eo|J93}l͘y/WiAqmg+Fy ysك;wh sjLJ;4vڱ?ި{_R7=U9+'J`[/jUStz;j=cu.eR) (Ǖ`0gŤ\舆(ѼC|gWTUEJ噾 Q(}wZN?gVd85xz[ l|iӭ8wjڃ6NߡVt{\oJsӮ~`>B'E4f}BJo:V?ƎI: 4t9AQ>pSiԿ^nNwEV_<0! ejm4Ƚ9}9k _9xBWJ]J8]7|R߸K.w"7;<9 hc{I܉'GX~WYF*uF]X- !"n[G*WI}˳   p Tumњ_f@@h-Ń~]ab ꗔw|~ܿYfɉ{qJ|6K3pܩlVL'VL"έX%SN;}Wu3W$$DA*pd6z AJdw/);Ce ]_|<ٚ~e&\gc'wmO_J ,P%}8: )_ǔ_0~08 qf懕˔19[O[D8}=7)WZSUmwkw' mrL_ Wt'7|wd7peVH#.ցKXa7Dg($MdWRIRMV))?k`Z4TN4l@@@h#fyN彏0EWVV@@@)WnJf)v:PRu ^嬽OE(qR V>de9X'=~OK{o-qδf=eIjϵkw@IDAT|m(z3iO Lr?e}j^U~N:Oi[OU?}S[՜ܱީwa͓ݟ^`^ ߷u~2u[UzXO1IkXdf8VHztwOZ<sUE֙3%m2n$' XCk ^]+*ʚ^֫!K?n*yiO.-<KnVΜcR LͿE 5˃uM^}\G6L36~*ު/;Un]̗L 4fW335v[,5/Ud(I}Jݯ8>Y`%ܬ)YyϽkǴ_9ѥx kfN,ux+J1hw}zҖT >MeOf{)4Ogu:WD C4(X\3mޙBb_}EL2ۂEpo%>1z6}Ҧ.tO !L&뺙OS|mIz؈   pQ lz>ݩ[}Q:p  @1֭UͪZY xr:OOQ^3ޤ +>i.٤Zp8f*6g!ꓳMc&X/k_XIs͈d>/B=i>YGhfoRc aizkv{֭3l]l~82X7Iftd 1uƼn^ LrٳSO7)ăc.UۗjNS@'tЕDk{׭j̒ml]M}w<IVھM\|.[T&tQx{:z3Gqw,(Lwȧ_%D ٺ#~VV>Ht$`s_B'hC{>kЏ1 i&Uﮪl/X皟x(w[ŵ;|k9]Ӯ6vxS5{[v=L9в@@@(~Y yJ+1?#  @p3f내(#ϓ[+_4RuR{lStGrcZ`m>=_șJjy">`c^ʤ:m{>.ArFT3л޶L_{=j_P徇T|eo'usL|h>gV;Pcb󞭳 jDG7V5Ys33JI M@@@(P_s~>z?n8'@@"&b'N1ȩtn~WyRw0{pi SY5cau\c;´ɗa3Ϳ{II82[2}e:CL Ǫ~;! .9ʮ9ğ~uQȧ;|f8 "St7y"g}] 3I3J5ʷ=~g|߬:Kt&_jϨ}0>k7 b^$ ;s߉Hyz}vI?Y9v{KdBg=::uyh(wh$|>f@   HRo?  @TmќgzqQֻb,BvשGQWoh}6jQ[k]IV0!#bԍ8J{)A-6SGNiOgՠΧCFc:!6G{\(3h+:eJycVA~Wzy=E/5qP꠴mSNs}{}6۫q*z4N?Kā Eɚ)`<1:Ι><   p lzB>_\GVE8q@@V;|*h@%_ntuiYj''Tq?ݸ>Q䷖i5a\ǻKosW{Qtoť7M_ɞsZ Q`}"<íDӇ5!Oz_R2zgqU>)ԻzgCiZK W8@@@ P]n}>;铷*:y= \#  pq \`OxžQɶ GG }ϪQFϽUJ7.t3P#S%Dq&ȓH[i`Їϝi GyU[m 2Dڴ$4͏   )PW\}nu*UAi <]qB%qU'TZ]qJNnwY^F@wQ׉CQPESwExW|gzmfטr^#huU%45UIo2@]_W]տ̋cS| _}7A%ѷK,jI)4劷TNk%^/f h>A\@@@ nѽ[J9ږ^)읬E4Y"  V~?x3ai/)B Q~h]tRJKz fcCTb'$ZHCiHЎ.q=^w+#Դ`!V4q.uݺp5bv)*\ԊQ{CQ!ԜY;Gv6#  4Y&@@F4v U5smvoõqDp_Ja|P%FD)>XWČ3}M/woV޷:p@߄=wjeõif=]ǖ 5BT7=XPGoHƏ$Dݮd{r@'ףoPVR$4A   E&z+>?&gQ~Sn \d݆E@h`=U^w lr?Yj3c$Ns*={e:;AIIck9$*W>{XliB߆؇Uuf羧_[~:v[C[]53h\#tM>{Ps\؄ŭ-Q>gGAgbm1sWl[/>[ӡz@]XG}e>`۞ӗ9W_ bWi_Х|/R"WZ:"  Sx'Qf=>)ǬkpZ  -(&٬]묟7iڅ%f^u|zxgړ|PJ 1W:#Uؤo}^25@IܭXvn `WܧjW Plcޱ.2~jS߳sMU чӆFi}CLen\Yشe:"۞s^J>o|*rB8fbYd)Rݵ|h@҇?)AՋ#,'V=탛~ikSgwo|Sݭx ^ L l^0yI؃   ЬoC@@hg?1ͱ~+7ppqv}h^QǾحNgӼO.Nf%~Җ9o*ۊZsЮ''^/h뻫|x ʧϚI>9M~ofu{)Cw[~nDt_8sn[!j <$Lzgu?~HG9Oe"     4QV&i&=]{0>J]Yf~쪷zcZ$eko7z<~jyx;㋕m1!Z<;{ywK[W?\{y}^A kTc[w)c{0zc>mJPmo*[c`鹿3lؖ.gN?*da ~nZ<CwHQ- Ǭ~IGO ߕ%kYWCpgހ݉*Ƴ>@?~AC<x~)zk\ףu̕Qҙ{?;i7ïoP6+MSEêu~A%GVJ;ƴ1BvmJNd-f\.v~-O>3ǰ@@@F@@h@1@(odRGg,՘Cu~Ϻj">?Y<ΡQCIuPĦMяs^Q_(iMxcEF;pdT >yv\uo_Su!![)2g3s{xv:vq]({-9]e$%} B0'w9ߝ?^%ӲJ>C^i} 둏Y%ڦQi[i$X踀=G7W>QiddRP3\_Lܝe^-iX\fJfiqA-JT/Cޤ~0?.QѾL"WC}^cZ|t|k7p,Ux`}>nX_;&CoxsqJھuYjxkj6˼b Zӻձ]   !]'i{.@@.2OKsΝ[2+vy}@QG#;sB㻄 ֠sfe4q icbXqqLĮkx`flOHQ8ak5뱮O^~L¹D'JTMjyE?Ot_E٣}I7JURj.^cq&FBc4S=|xN@@@@@@>}(Ā"G       4@|%1MP@@@@@@@h$+C@@@@@@@h       Uꕡ]       4̀H@@@@@@@ڪm.@@@@@@@AĀf@$       mUĀzeh        @b@3 @@@@@@@*@b@[2 @@@@@@@f 1        @[ 1^څ       @3 @@@@@@@@ V B@@@@@@@H hDB        VH hWv!       $4"!@@@@@@@h$+C@@@@@@@h       Uꕡ]       4̀H@@@@@@@ڪm.@@@@@@@Ac3Ĩ3DLLLى       prp^ة@@@@@@@ 1u@@@@@@@"@byaR@@@@@@@ZGĀq@@@@@@@΋煝J@@@@@@@hZǙZ@@@@@@@8/$v*E@@@@@@@uH hgjA@@@@@@@p^ة@@@@@@@ 1u@@@@@@@"@byaR@@@@@@@ZGĀq@@@@@@@΋煝J@@@@@@@hZǙZ@@@@@@@8/$v*E@@@@@@@uH hgjA@@@@@@@p^ة@@@@@@@ 1u@@@@@@@"@byaR@@@@@@@ZGcTC-.pLG Nc7NSD7 h@MIӇS/jZf.]ii[lrOmXێ[U)"U%+Vݔnns$   -p*+ZK%ek_C@hN/lYߴ+u>5SY?ʹ曾QwVю6&v>I]֨MJnm;Vg5ek#Z}bMJT`aMfǫ7ϵޠf  |B@@@@:{J^wԵ[ӭWE9P   T*Vwtbj{ ;s Ү,݂+|[}CdTvO +S2`ySvCq@U|D@@@92tWdW]e7+?Hz"6#  P/A*pb{*Һ`C8].^Muݪč]#  n#9aY*)Vߖ(wp3Cާ/ p@  @m0@ D$״4p{j' gFld@@@f%Zn>oj.ĎG>9]Yo7;zb@@/N``pUپS~G^ԤZv*A@@lkf:sך'|%}9-7z-CL'@@+pgPїRtdԯz_ZwxR,'%vSMIE=Sغc:QW)TPTsti"RUib%X־2}yN|S\Os^z*.ؕ3OUCtʾ^QPrΜk5Xyl3mz9-OOqAVLkb`-*z8%th2 L,u4ekXv*OTfZ:YpiWJmGAyղb8 ҕ\Wu9"G! ш>`Zd`\ҽg>\Qj~doMTGc~C*;Qf?īܯݕ&F1(+%VݔhIߚQ1?v9,_aΜ8N=\>?d~4.yF,_a)   @pSʰL{aTCifր(+ "B† Kv D [BIXv*(J.PY&Zerޅ "*Em\a=! dٌǍBA3==33&߮ן.{K_n֒ kirGwT鈺d2vC$8O W*1[+Y^fq\w1yi?Yi酻DOBQzGՕ)>C0ęXGr]sVR͹]=ՠd7VWFKU%zʴuix Ȼ?wP|mڔK#/lq F9q~R ElS.2.WOko}="і s}^KeLƎԲ53;}Kpwaxfv0و_}{嫣b;ߺifO^jd%uꗢSZ<2'繿Xм?]l@u۱|q~Ulۑ\ݼ ?)xFby {Кǯ2͓Xq [dnd!+Tu'N?onoNіp˝h=T'KJ߭AJϻ%P@/ lgy>*ϛHqR7 L[cŃwfKʴG[(/WJ     jIptiFPP@LA>t$/ÙUS+˂1>ɱ*:s#}uj8ո.A2X{CJwNf 03dYO, \@]Ѧ(G7q>ޣ w (X?.qwr50ho3{2OB=Aи0 (oroAGr  4Ήy ??9>-AlH92-y3tJƅn42le/Q&$@$@$@$@$@$@$ީc(7xp@mR̮a7 t[M%܈$?(CgEuiېȾߗV}mD^epswgotS,o\ug7sӳz ^1}ØR*,}뒪f.Z32u|j1k8=ű>$ .c0պwUʪ}ePS+Pu-ހ!?3jeYɟs%}-5' qҋƒu9HV-vx -ۋ$h;쟢{h;XIͳZcD[8KM Ơn7æ5*?؄f Mcq.lo=E zjܻcQR7W#y*9i1?'\"+,sWkٸne~|m hҔ؀lJy6wus]-h/!UjLrlՒfgpPOK@vio.{s֢dً3ۄz?/c@?3)lގvc8S~10܇IPUɲM|uE6k^>{MqF]Rlm{i$c^ 1ʻkxoÛG?(1_PJ{a:$QC18^:w+cF5!ˣϗVߐL ϗQe      ;JGj%    /'00 4h&}epB6VaevlqjE \l{vشp-]ӝVϽ* lOk|ݓJHHHHHH H*Ė"v].ԕVKHZ&ˊrIS.{X|`mudxmʶ|K8dn)S{wcߐFp儚}adIn_]Lҽ0zLYT޾i2m̱Dq3{dYy^j<a؜Ep5hE%9}v'):OL3,ih%NDA?$=Aގmp݉?;#%d8|SnTK}Jv@!4nC 2.ϻ+V%b[R1`TiSޣ[@!K"6p0\ ZkGdZ(DւTd!w=VK֔Z6 XX^IHHHHHH` ;}A5HHHHtn_]*-PLjsCj*W @Vmc.Dbm|_yo`QsPBmчO?c<5Ine9'i TC4!N 1>mJiNsMudbH;V+Av7*Lq&6onM:}peXVՌ;BX.\GqyJ1p;)Ij,02ۇwdtDl>;j\ll #ml`Bn "@2-WH>=>xܐAޔyh̽aK7:u*Qˠ8dƨtWb7}gw )P~Ogq8 | uhp]sJ 8?DsyUy}J>muEq8I]ޑ=8ct%#N0\Tyo)wSpt}C xh >SHݾ=ۍ &ۂ8٭tP&P1rfN4pk'@It_6}ޯwREePOӒg+d n 0 ;n!0HHHHI& p35)s?98飻 #c;ё ~R$~LF1*_?8bd%)x֛?:t:O3mlS0 cD:#CD$ȶc\$-"O!        A\b` ؏: y}]10xE`p*vinq6}rﻮrPaL%m#$Vʓ/d"#S1 3#Q 1lx7 \تv$Dd18yȌOU[faJv ]6='7(ACPރHHHHHHH``qcd    $0'~pLuUWYhGa:TzMI N̐E[*d~I%H`uoɡ8<i4/!_+ȅfxnrHH{KGvz:Rq:Y $Yh34"EYBX>舄Y13GHHHHHHHK 0(`o $eu}'s΍OKѮ񉎏kF0[UWLW{oql~]X,%.ٷ=ـq^:XYIjt oBe^AuD /Gc|h\_mӄur΋-݅lyJHk1/B"iI      I (( ~ƠxHHHH!iQVzO8 rw{W3C>' =/NޜTCVϐ1V& ޢ]. g!Z,(ZJ[lwW#nD݋ (=)NWBp݁36]lh; =.b-UUOvn ,DA 8{54K'y?b8ܹZ}w ̴VWUO.a^n? 3Q={/\Y5mtfbaWuvJR=/ @J%()?ʑ xܐޭZsmW&Ѣ"4[D+ue'#!{it ~y!>܅ul H }lTRn4Wv7PiZ.>|5Dw.shC01 񵭪qҿQw|ڣ[-yYCˋVF6n9YStF[ es8deJݏV!Ȉ;{кvЫ|j8`~Zc8,ttKϋ0yw5q}{5(GY媼Nɻ&ZHG _kd Eh̨1]#3ː@~ X ( &+$@$@$@$@$017``zj \Eʧ1Y!|cz}^ףtůP*n{sNeqUZND̕>_yZc}KO q|=.]^-pmPA]U=[|ڻ|A %*$gRmC *<pzth{z+/ICUGO%G~RGo$=MpJOõv 酘[9>q+rC[Tڤ0ߊ@$@$@$@$@$@$@ $>[P>&)S orIDAT1mRd.C=R#P8~Fo EC zORaGXdzqܕ!n]L*q^RWa੍>'{6W:|)4:W`AQ| pY_w+MVgVpUu>CI֓XnebHHHHHH""ѯȽ|;rFf0Z    &pf nt ZVn |%;⫫v[nX_׀EH7""!}V_Ef =ĩvV;H }qJ6sGqtI1Aogrːnį Wl~aɳXҼ5Z)RHONbEZ *0Hf2mɹxSH5~,z LSRǨlAOoqz(64$&䞴k;m2KUMspQd?v7Y[Qx1 {ӟcwy sOXS[+M>E{gDf".G0of= x=;[=j|ϦWjl{vc-_lhM$@$@$@$@$@$@!`K{<~+2RyB$@$@$@$@$?rE&Ԝ9Ԅl\㴍Ì }V,1Iw3I8̢ѡ=ʙGV v~+WoCIY~UUPV!Lj6qv*-+n{2?=0*JTs,(a:zޢ$b6SiQ:&7 \u' ss$"0ב :$e:)hwʳ$F4g            S}}E6/E`$              2_T` Qb              ``Wly$@$@$@$@$@$@$@$@$@$@$@$@$@$@_-^8pfIENDB`graphql-ruby-2.2.17/guides/subscriptions/redis_dashboard_2.png000066400000000000000000005045371476434635200245070ustar00rootroot00000000000000PNG  IHDRqW iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 996 2022 1 6<3@IDATx `ՙvpCHHHBBB !FYd+FVZ:)cЕʌ3LjmkVZ*RE $$$$,7yޛ|^s>yyn \quUxJgyHHF@߿=R,B$@7k4[L$@$@$@$@$@$@יuՑ "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "?zΒ 0{wp.Qop1ЫFo>>'Bw/(;'?CosCK$@$@$@$@$@$@$@$@$@$@$@$@q1$ ٛ0)T^#`Fsg!k_BH|,|D*/]Fst'ziHHHHHHHHHHHHF#@aFH)s _+,3>+]Ri9O I= AɷΡ(=L            qOnD,o@4zn'$,)ZE7>@?iHHHHHHHHHHH4 czx9[*~?uL!O4EeY,oNY~h1}G8- &@a~L/;Gc@w֔K"!4- J~:ʪD@>#xj4||AD]+'Սc佇           ||A 1C`@Ǖs)ߍKJQiY=Go!8>V֞3B? įމ^Txרj~]EaO#lVCGwi13R y5 n G̛R{qJ3\(Y~@||}0w H>Bb UЦ$`78@ssN$@$@$@$@$@$@$@$@$@$@$@$@ PwIng?$B?g}E@X(?4~t7\׷^BO~n'f82N ((:  (N= ?Eݻ0eɭ;8#ƻޓNa#_Axݟ{}tT [C~g^%_!:?럞Ĕb^y`f           w(̏!gI`G?OAD;Lu=&cU߈{6 ־ ^y_LŌgy0`<@$@$@$@$@$@$@$@$@$@$@$@$@y^$@c"w5?Cˡϙo?9󚰉HZLHl w$n=M-ٛ8^g& YCˎ8$ 岳8opgBIg}iFWm=+8oZ0!yf|盘|u6ʳ$@$@$@$@$@$@$@$@$@$@$@$@c8 g"AEt&bQ?y S:tpa'13a"o@A$@$@$@$@$@$@$@$@$@$@$@$0.PN8# 1Y֜|1$-qwDwԼGwq5xM4|ܴMO_`ˢP^#Ee(O?Cm_OE7$(SG׽-k˟kWMDxΗ~aLxOoSK$@$@$@$@$@$@$@$@$@$@$@$0 ԩ13vHDo/@W󂯄_&Lp@:S.Cwc3jH"wɚZĭ].μ \.ĶXeHHHHHHHHHHHHn oq`+H¯ O\Gz [t5\p-m87[?$|+FZP?Ы:&%           v(_;L$p@OS3>h>V,+>Qh?]?(4X_MR|G~~x&eπ69Ar aBTkCpL`"           (̏1fIEo|}EW<㢑Qw^1z͛_ͻbZDLz|e Ƅ8L> WjD XHHHHHHHHHHHHf!0|n^."" }om񊛅 +V{iOjr JjNwOSX+ \ ܼKG "B+/}vfty>~|O>~^kE$            \0!<|d&"Bq=}{j+Kw,<(o݅T#Çs7ᑗd ߈5 f"1&GCףV&YǦWE}H=#/hjn+Ыt D@׶sZ7QzzL> tu/wqJڬ3,85 '=p7RUhK~hpR$Z zt]M;XHHHHHHHHHHHH&0Ba~Owm5aHˋ= ,к5ˠSZ{6__XGbrQ#ҰBy~ /GYJ_łHj7ж4+" ]O4V`/k7j+1 b"+k78FƷ=`ęv"B<.#vQy+ZJ[HHHHHHHHHHHH.c)/_w>\CDϊX "2}~ ߊO坌,؂_|}garB[51io>"A .~r G!(&J֠2Ju{/Tw~*d=J_M X!|zX[?y=jKNJ'+ʧ 0yHSb .lwurڊlEGpa()c(eK`-òLh,o?mo6 EH}~ ]}Ĉ;!QaAhEKdY.RK z †hzou]>^N` \gNwcO>z\hؐn45c (1QU_^:j!/$ 1CƺnTt Bm \=`U{V?g-xe$3:|Od9YE߅sgPTRkܖ|ې3E85a_4%oh9t]7{MDrvŅϞ#؍1LeGzt;fb o̩KFhwa>\/ 7 ُP8|m]QCW%v=&SXx\kF{4E$@$@$@$@$@$@$@WKao6 Oܛ:؉/x([JӲ^)Lׇ:ܓYQ=孭{G896c ֧~'[ 4 u[7Q>hJ$|C{uq%?z -GLdn=6>wxc)s^ _჏0!1CԢ| Q f@މsRIMS m+ip"ތ4!\$碼m$`NMwpܹ(t\nJ b5kG:*,sML䪦ڦka'       83W,Y2/c{J4P W$?9 ӓb}xu]t]gI#Ќ]>vK S a"$}h4~t;w> }K9sM: (utgFl6< τɪ8Ǒ/(Yc qV<]l0 n[>Xm.%h]`\h=}¼kSG{fW`7 `KYE{)c=^$9 15`oT6o˺X-?! Ӑ`{~=lz*:A x{bwKoUhvhLVe4iA3j7l$o#gWb\i1a z:1x]?a/׵bXc_) "gQ\7N+|{q9__G-5o/Jhzo!wrgD]+)ap2T9@Hr>R,AN}B]]4P G~~{1wQ|}y7;uؽ;:PW]l,M6x +mjw֛OBh6LNLEN.O+MCіm㊥C߄0-kx |6d~盈Zr+mi~Ox{U먔uE֜/D7ǜ>kd dtu Uu ^Fa} 6PBͦ\P6t)p- 9юuhІׅ2Σm6Qj]5T go|ow;z#04R67Y/cc[~ "4$ߏζPY^]Pﮒs P{y4H vPb+~Z$B5J}O߅!AGbҕuy`]atmD&$@y >R=ΞŮ^[mLCXsvG[+ yYT^/?UH'Y{S 5SoY "*a"E fc$aMJ %/K0t+Mjڤd?s X {5hl1ݦM撵բl˜ "`'\vz}o]x7W3UĻ+fӰi | 8SLS E Ec~(-ާW;h~x՛_/3HC6;0P2䁯JD.^dMgO託Mif-!j,7=6;inQ^oML+oc*G.>9쌝CjجtLH;Hx33K*pz+w.y_ Brc.z.2Io}?.%\aqoQU4Ȅ-O* eP)db"pۜC^Ae(p\DZu[i)ȞӎÇPH,3=JmOvLd~SAi taԗ+=)E:3kmG"6zîBebS|E\_YB[n25Ac8Vn>WePыƒxG}K"^W= jSnyk 6t|!id,u' cL߁㇜1~*̝"É>de%CC;}\Љj(5fogQta}LԴ",1lُ%ԽE{@)@M&N8x^/md~$7ls--a~3D^jfƝR?,"L\7,҆bC2Gf`ɪ|D q;0+'- u'_]" ${Ƽ~S婖&(5w&a(} Hj%2&Y])ctxx^J% Pu̥+cJÄ 1=a\"4H[$ǒwLgdqVx9d6 cnnVºY;7}bc'~d5vV…*ӰQQO%޷>?rxs]')8)ÛlS^V%O +8yq_᭾{Xcw?RWfxnqNDy=xᱻQ36!k֌>dTLvqƹw>`xGg"7+bs~= 3?䥧{nK{>Q^۶;<)%ۗ~lW<;ىJm8"6cpK"x/g+-U>!Hs(~"M0?9)Ú߸"~ Pu'}AK לMMC }zƢA;^X5m"}DyB2Us:P&<:,#/O!Sڣ.Nl=+ڌowζ|{V ,z\Y=4c:²3pT)Oy-،S݌!c ҟ]J|s~2n){݇/>o_ E~Pp$=& bJWp_LnL!Z(%>N@qm>9) vUfH |cRO(E){h̘HxEH>ؙjBqi.i șM;U,Z U?cαh.}>TJg%}E'z튠 eo$Xۥ݅' k3M9k.ܔ,Lnj`q ~k>bMz|yۇ&8;lufW"3aiX1rXLA~G, ume %떹Hhg-/"P]zueVQ eoO4J:/)DiuqYB :qrq_9-Bm'8!–zCoO<,[]#6qJhcK;eSh9w\9ӣLؿ֣8=?qc7%VlWcu)Ebq[dҞ=kxYh](ڻ6byZꊱ[J^f"OAr|3g#t^(®#6҄Ov%g) D2g%ITm/N΋poY]U`/J\ܑ+"$%NE:v_Oe-0G^qG´YƉ r>)mOd/u-⽟`(9c&B(Qwj7NlRu4؞j3Tflv鎡*a RHX\Veggl#-epܶRezEdTʻ{ '<#1;#DW]6ׯy$⮆6u9A,1dKY6JM^խ5,5-Y3erOn})P ɦ"Mh ?UJT*=NARkV]LƪEf?Mb"uW{ن<$LMܗPٕg3AJEC#N_%%CT,7CF* XHEf< mhmߌlj֛92dTR*1H*[^& .C 綵º=!{ =?(4L(,?FHtc2M       qN6.Ը+ƎÐJ~긳Mw-<~ H͝'hw;˖׊0f׸ 몯/~Mx yüނl̏@?i}G$@:G Zpę(m؏OKmEpU}2!+PdH0ˬ`o]!b=%F^p@(o55(.B~;æf̚ DGmI2־IAߋ:j=-b9!ABrʤvņlA\JD]od b}0aęDyY^:!aeG]^NbX>`cxd Ö5|xV%&!%se=u;Q^=,I ve }]dW_R{c҆:!{x[Dk+kWכJ!sPl,$6J8v!OD$Q/:(omQ0C&5a@ pB-V`BL,ٕqݎzR2Lkثs)~ȹYT5$VFtspHHHHHHH`¼<:sGàoNhwdD._tʃؘDi19bL"q%'.N;9C:I3z>3:<6h_ x'(ϷW6y4q|^PzҳkHc^Si6.52@%as4e2οWtQHG# ǯ "oҟYWTT6s^- /5a4'S܊6$9>$ wظcFh .~Í#gmGyg}ޮj .¼2A!*{Q0Mkpm{q&?P)@kRC,s5>CP8"Lw%}a`uI0 9ZWsfx )*,z+޵ Ҡ+:]E8g#%>bxA]u뜴M5Ӂ# }W5UU:Ta,҅6D\/=36P₉6FiR ՝AXD @sXڸ3 !HՌӧNl=nW^ ClN^,QRUvuTrjG|G&|P C{/Zwg:$.ZlGwL"+"| %?z Tي (J} :}aįee-]Σfoh&]n@,NV4-:Ä*_mk[)B5hU~d#%OuLpqmc0m(BC VAȵ.5@1(J#bΑߋ63:ׯ1~/+̈SO;SpH{ T8W{l%v]f%>SQ׋/BL.Gxv:\ƑeäHʪǷ+ݱE $ īyY{*ϔ*AT~VEZ.8~H0G'qa0"RDuE&jp9f[dHqEii&Ǹ 7h/-i(Yf$ 5"ˏX3g'Ց2NzL0qU@vX~SnՎ= _ Kp*R& OGF:(Ȉ7:&x.F.﯏ϵǁ_>7d=M'D)Du L'Gﰧ5DCaѷ[>"+IhT3צqxοOC4rb#ET ѹqG֠j]{WFxgD|t$=3:MhdS³;OXљkM :ZP|hdNK ')yP8Bq^Dcm{ZCwU !A ?wqe"M6YhijܾIM(&,ޡNxG/c5QpD|} n:,{dd6aino+NtLGLFBxhO@;.{;z Ƴã>21|= L.]͘0S$@$@$@$@$@$@$@㏀f JBcRML*V=u^!ŋikEt 8{{joԮMAeHL0>F%\Y+֒[ܪFܝN,0"7GkGZW@o\6{Z-&Cb?9f>bVކ9?|1z] ;oX_^+% >![C>? .UA83հ2Ѡ#.Gy!MDv.zGK%[sU3;Иs qXd2۠xt]8Kڥɑy T4>;f\tP"`>ƱMțƏ2~+޼$}{!ݍ= H nl"Ոû<Iks#\0Fsvk_/#QYC6^4HsdYW"tC[[6pM pmhn4Mxp3ecnZ s ';PQz^ýGިXū[DyYV|ݽ=/k׌\{֌֭峼Ztz_eֱ'_%kV<4"K+4gaTtTTՑvIDeg?; "|u2'@+/dòdMo~"|q1vuRbǑ={F*%ʖ"xIhpynAOV_DZcX'Ydu]Ǡ=8_'GB(m-ӎ"7JJE;p 265-klm8@IDATB0+{ҟ^O ;lbXҠeދF5)&ʹ+ zrfaK\ |a~<$26.+0d"P/c b"$ߔ Pl ]t(ʛk3$}7Q74z/ujw/A bGU:p0#V[/ʐS%;y^rkoy-91wkHHHHHHHq)&s%垑z/k#R{v/n@*xg䔧^&MhMR٭QD%\Eԃ\ũ]E7uQmYԵ$$|xFgUtˁ(>ڝ}P։^6s6}""+«+ߋ/@f⫶e啌&<'^"D :RVw;L<1EI}]*xo@Hx9=o̧7t܀ދ-R.^垻=y" dQp)2g&Qx ܌M@(YR:xubYԇ3 HU]ΔbŃ ʵiL6ac9߅K%$r˵kqO@TVc@%Kܯn Y (; F[NU݃:?T-+֎1'Y~$>)"xVŠj¢XzfC/"$yvg{:HjJ\O_TRC}T?0иkWc~mB$@$@$@$@$@$@$@7գ ?z> yf7g VTywb[܍E鴸|''H%Q W[f:=܊y: 8wXk~M">ffm7T4zI8XHW#^[/*>hd z4E*Bk^YcÃȟy uS"Q,xߐ{ 8=`XW~PߠW_}}f\BZJw5H ՘z\ȯEwg, &mOKOؔtwV|P:r!KUTW #mddE}e2yL.4-2D'#rbcR:H.ErX`jLM 7vZZ\\.,tVYc>=ܣ[Hxj,h>[ƶV4+EIzWB|{jCӝ8w]cZetש{EIv.%zHf-Z_N4+ Mۿ-oqV2MjE};%ufvEblBrzN*:hnZk[#=阏>ˑ =U=~ IK3x\{ݪxRgvVw6dw'yK: ԦC]x4xQv9\!~bD_Jux55]kGfBEDW\+V5 Y)|b4Pi9Rxp77P+{"U߁ ףIQF'}(kqi`+mbRQXkHͱS0C5Oc4jzY&EYRH`#IS-vO\Nljr0Yk$$      i x&c'>7?u7l]XDG +kH2Ƙx.`H}7"WUGka4{D{f@X.^~WrjK&V l0wXn~Uּ2VGʍ%~h>)z.I(Fo^=b< JGEWQ{ C^ތZp*BBTO}CleU\rWs'r KY,6һ-)Y?܅j>YkSn[X'tlb'E"0(QVv|Ef!AԕxG( c#59SEEyBÙSvlb{t(o3ĹkqOA6o ̷Np\/╶O47ɁH>en$D|U*;n}ʭ5e2CuX,^6!!r",¼X$$ch)J9 ~Tĩ>4S`_.žŎJPݻ#3Lm*uג&?{uع"18OτN?i|E$@$@$@$@$@$@$@7+Ob o[BcG/y4l|m VI}xie=rf5zD܃oeWGs=10Oux<>gWXP)f]xbl?RU⪍تc/q-xn\&<cR0݄DgOY܈7n%V$mMly۶fɁڏ\E#G< ֦$ r\t2x(~ݓLPB+kkB#]COdXv ;4ɚ}׽¦hI\Hø"9o%ӚգIB!a5LZ>-vO)vrjDNƙv< H :W;=WtHPy_uΠP&}ܹ:UgP K$ 2V"/HdK(:{J](,!VVRhR>l@qq%_+TXfgLjCnDWn~qX0 1qfbRR45aaaujP"U~ץsPvh.KE-;)=# YkF9HZϗ|M^IsTrp]A&29#Q!"\ n"%,d'# m`Nf"B֌3QzθFR3mL8o#Eoe8URcdir^8ˋ42_L@bݙu[t ws /dra%{[W? c~m       1C@yaڎl⢇?ʹXb!¼8{Ͼoӯ`F#y>9la#Kܳ-@zFu̍"_Y=>?+_>">K : w[D ~٬(=z/BOsaMzs~= ЮC_&$`b4RfY3:PG,I |y9JQ#{ d1Ejo!_^_o nㇼ/ƕ?yJsU!va5KS2]Tʜw~; bz? /](,|'G!gA&J?4jD 뤉FL0ޤlSh& w&V'/G F n.7%׃3W?qHHHHHHH`BN1:/JDa76`ͣbxIh0aDQO[+aO.g1D_=/J&bxZ;jC^nƽ+\l't ՚=ɺlU ƛZztv^Dy3XXiX'4<A a+݄T 1PWi>ur{&0[?wyXuu,םpE GlܐFi_惟BU̒+` 0y^|'߇-NH(HH}ʕ2 ߉46ck}NOF: Z᣿"=?1a%K:w[M@WIf2eqۡ+_ꪐ>;@u%e$b8:SnX*{/-ȹe6'*j~Z'ēk 0-2!bWK:n Wh/t(s3 ə8}jѭQeayr=,܅ Iv`/$J"|'v53_aX5;|px/[dVb+ @r1hڣ1--Cb/ K}5hij"K?IYX:hԘ\9fҁ(,q݈imj/:bhQaM + wm Nd1Դ1[Fg`|V 32&s`021U&&I#4 cnk{$@$@$@$@$@$@$@㔀GNql>#\1ǏF#_lןE)<a9|#[ã۵ɹ%g4ZFeaO|貆wrGčg+֯FPjW+u'G]kQ&/8a{3EXZY] e["J>$y7 >+O5[ # &%]LՂ2iŤZle{}fΙ9g2gw 9s}뾮k+²G3v45k?'UԟV#>ms/hx>j fVO[.d^bp΁]~L p}\ FֆBxLm=/a</^ŻkHcю>Ow>.D!E|GDͣ;#Z?X o~ p' :-e@:ŏGHĄځ %Ώp`O>i?vߤwaH{R/2e47]؞p>~CRvƏ .Kq5qԿٲ(3];߅O@A$]u b{*I),$5$e-.81?~'Es7/ę{!|R[1.4qR8˯~#G>_-&#u_73AӼ>|xx͔ݾ#R#oFSp/qrMgFhxG8_[2^tX'L'b3q'BR^lۿ7`%XimNh$~>ܿUu_푃_^+bF_·n1Hd5g'k6e5DE/Z.NF,X2?|O||8J@=f?ޛb!(qA4._j>m7gi\Y ngO1sW&! SφL?v9L06Z'*?6Ky贂p;c>:.F<{~x'8&OD,w!XN.E Lo4NH/a+u3U|i_O/E^O_8O~J~wn|J~oS.Z':":" y1>       y'{t=&x0Gx|֯/2hbZRNK 9o$R΋b_22-F1Ư?KPPvgq,fVaƫ?jD_GBA$^}ޜ%&HJUyJf_3E5q>_y?Gh?D81~܉ 5{A'O _hhƿȂ 1}q+w/ِp^e(z5jov 4u)&.|#> s:g"y E__mg`7_!1?5߸Yt@v_FE$@$@$@$@$@$@@V|_kg/ʢ9}M؇'2/#w x/0 qt}1T (EU7@]7~8y(N/oM;>GA >·ez!8op>SļN$@$@$@$@$@$@$@$@$@$@$@$0a D0?aKnjx/6\νBg/sNEW*#:b?O`"Zf?no.ѐ)>>|4C?z~ ezݟa;,J[rL`̻qcet$@$@$?2F$@$@$@$@$@$@$@$  {F Xrk%WwoS%" ܟ+/~EL+uZĻb6}8xLGҗQXExs|s$N 3,XMp2HHl,r>bg`        (I&6_Oyi[DU8 \*Z/GLͿݮW=~+OΜw4&6hYk7"_R8k@`|G6 $lW1Әr'1 9HD1sW뿈;1i;O^z .@SZ߈?)|,:/+DT1>y5p^|˿a 4e&[ EvM-TH$@$'0^H]y= 3(BH ?Ο:3spao̘yL "͎>y8u2hHZ ፿G|~U,5wU+Nwh%) @%XGpI̾\{!K71 .oq'81{ot1U4 yD?U(>x _<"xZ| ݆Ngv?m;|Ex=            uܿvK!Hy6'SP>]=|M λ`*f\;W~ qO=/ã8-ᓆK3_ f?UGc=';7LHHHHHHHHHHHH G(HFC$&nAo^'OB3mqqyE9>V\zi'={?},1?e7| _݈6 8!9 ̟LH i/!yxo>onF/r.)~ ^zG.NxEx}6qꞏOk:וE1p`B>v* HHHHHHHHHHHH (!LFE$,Y_E`~OZ\zgQ>3>a"-{M}g3eomy\r1 ˽z4ͯd=?eHHHHHHHHHHHH!;ђ Սp45u(&b?/ppQ'Qpya{3PH=j& dG. 3@`ZLך-Q'yI<©?Y34i baHHHHHHHHHHHH @QO% x\0c:Qԏ::$Wu'/-{Y-7響:b#1c{&^#            CbI H`߷GWÇ-۷bg 0 ">K/MOz n1>            <`> @ky_gx,8#3 LvOv,? 8Iy'2n              IOI (w.&             ( 8Iy'2n              IOI (w.&             ( 8Iy'2n              IOI (w.&             ( 8Iy'2n              IOI  p2M$@$@$@$@$@$@$@$@$@$@$@$@$@$@5's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@x LHHHH802‰}'Ns дk%!         G 'p(БQ p ΀2ܮ Wf%Caa33 p$btO%_/4ӕVxVBc QjfH`RK[Q=BX6Lu=}?vM.C|^] WNF%6k:0>K\Lyf΢ࡽXt[#fy.+"T+2. { }6 qI(,}f1H.WJK<!SڷAH/mԁ2xRҶihoƌˤN 3Qs\/q"I3_=W|.~ϝ ah.]Y 4gP2V      ؜K$:mؼ|U ^U5a0<,nn ^8ٛ N w SŠxIh y;@a? W@QU jCNgq@ȏMd 4ne0kmZVmb m/lCsqF1ZNsho(g}I/غͷg 斡z8ڶ4]،e5XP!$;X^^ƾh4k^<<,Dϳ=ċ%YTB8ڌfT7660X]̌)w3Jn[9YDy=>mAkPYrNXĬLA@uEzu'3-*+J{kepo/:nFĈ<5siO9 uA zubVy}6wݾ(oסn`422ˆyjӛjM|gO2tJ`[+V4HFƽR/u`ۺ;܏5Ҩ؂T;8V9mϢ"cϤ9̞ӍX}罏qs䒗HHHHHH/GB&]6:Zɖ<"}jQǶ9"ݢ;>CSD=<%=|^ss|mTU;172YDp5&6a2I>=P>عkD0n(,^ kUh+o `~"c)<.,xF=c>- mh~eM[e+w)Z\Җڵ- {jƚ{*sn[Ge/x &I.֮K[  ]јў,jF(Ӻ.,1|wݑF^RЁ]UۯDnvcsp/nZ)й9]Sg\ce^Ix]S~e<B9LsM_l\1|;*8w{~Y?V=MQacֽ3'^ ֎m~pF݂gY|V.HoH37EKs~Gq1*H\R2 ηU>⨛Uf*foa5|U:˪Y 1Z;61-Dy4)zɳPʣێ!!.AhUW%taΜմ;5L(&c8,-Qn;KvP93ѾwSf&J_3xl=8!{cG=O֙ r)3U5u$Qwv0cbHՏD*#?m~ q:e3LNi,]P+?:\9UF /~oc,)c|3Vk_^%4Eַ3ͦBȷ;鋪,AzZEg&ލw kѮRQL2%|dwS04ΉbHHHHHHrA|TC ÚǂtzWcm_vu?Fz|2g+>DC]lolKgZ6"@Ek nEgZN am"MQۜįL Q}PtgS\";c)L.dWVdܜүm`Au },$ Q{1Ƥd䛤Ov'fb^xhGh]ڬo-^>DؤϩCMpQ o 2 6'?[8d)&Y.lƼUvڇ2궥sN``r[^VoEECR!Z[>N!?ߍ%+%"`_b8ҏE&ER]`S}Z`<^1[$$ގ;Dە'zyc7jGjޤ][#Y4U#Hj [н)j % } bu݋2w0<P,25'/=YɈ_Y-j#`p KƬ?Enji8~@鱧.1QyHHHHHH:b-M#I)&@,.xTJ. UfC 6Wl ѐS=hzMJ<2hRC&;Wy䦺Q{"Z|t/h"؏G1-ښ"|}26FY맮7 [uۆҼjzh:ܮixoE:E(xS;*8EbFЖuشIL6/2 aΥ(-!x}`r"BU>3āEY,P3ow-d'+Bv n-.\6q B"&[*[=(BVP7,7vA̓]ֿSm;qdh̸꽕^Gb`ʍ+ݤ-rIO XT%,r>^v5.|>Q|uU@IDATF"v4>}+3΁ %";~3kb*gΛ֎.RЇn1ǓǓIǞvV0P^&     HO)P!Da4B4% f.kwte'd">ЏBM~uc(mT_hS0uݷ}YҟdZ>liP[R%o=";DY miڹsX*J: ])}rFL6WP{O\++v߫3{,P>)S;_9]C=SmF`k^VI'1> |q-c7ۋ/SG嘻AN,$ wj)lųM^9{oeRJ*,3ʫ"ZзZ9@^r!;׭hU(D(IۙV>i%[`F2X:q\L%;(_֮C8!t6׷ϬGea.N#sj93ޱd``*$@$@$@$@$@$0 X̟E"Xw׮4I^<Ny'v}0wNH#4poìjt2#&ݧ&5NluCKzmhd]:^@-VUf~8lCQM.@|/NN?QMZ=g# 2Ifݕ&֖9WKJ9aʱփ{Dq$ torrsm.3~4dc.%8~N0{{NFy&G$@$@$@$@$@5]c%fB+Cla^]\y^ hEb8$ni9ȳ:8f]ps $n?6]0M]9z^+EC& ᨜xVEy+%bu%(^BB^r_)#A?NL|j.Z{s\p%S!1HLi' Z0dsS &Bt[ -WmKlL"sd/0JZ8]:XWaܗ7[9]<&W(R;C~aN<Ǵg1s=jWjLcﳹL_>1#46SPWHo ڦ9´nJjߠ^ _6'ύR'h( .+6Z_M: SX(揕4hcڨVYY|f$S]dzsè=-jBxup<ƙҮkH 96"c+u+ǡavZɖ6CGo?"m۶(2bIobLZ^uY[QsAj/=Vo/T#.Z㲶E0&4~tt4Y NRS"#47ټFryi,y?|gLW=(Vʷ iɿ$@$@$@$@$@$@ XΟr.JABѵoyxΎSc*+ffog +φ^.zP C8(u3h [(Be5ie F wcyĮZڲ,,^ meT[H.&|aXV#ܨT+HKW%cX,eLш瓵y%c-1谲K\k" C,9 k->ظ:;&fQqvȂm=wK \CKqRGp"ƶY^, n6 #*1wD}/{=Ȩ=>$qvXm[X"Ǫ@'Xj}$fZX==$%wbUZxQ3oҷm/e8NCk#Vo{Rr mںF~YĘzb|cOE-ځ͛|[7{kQ=7N-1ϐ dOd#)i3 '77juA48ScؑckYa2pdMJyiO+q2k`'V/5nI⌱Y*՟vJ=7h?1Өlh!?*LJ"N1[(?{Kc^hjm6X!MS9pT'?*>{zжkѓGpߋfmhKa ch~eL(61rTxTb=Aş#\f9@Uh3I;l/~@F{8>/k%wNDi׹A:ȯvN--7 ^D8[mP8|`dgӪ;@OfQ3cb2Z\+7׍ 9Un4=>$ *jEwn*X3h[3Päѿ5'85v&̷<5Y8;zN>oIYhĝ݉C1f$-,TIn]۠`Į[ ]ih%FH;/Q4GYuI c=F .9p0+vZ}ke~ݏ f}`=3:P'L\.+ #{{-nv gDjy{h2V F2f#vyhg*Jݴ ֕(lϷԩX|qZ [8ܞ{>yeX,^zlrq4eߤ_?Sz 3H;}Z Ne+b[%bldSChMZ}׈]O6DZ4k3,Sutl}k4Y5V{Ŝ39k)[0+]@ou5سņ_ JSE4Q]-j :)1ʋShVv|փ%74OQmoDY:|jb;!I;٭y!,1k2(%Le-ӹјJsyK }R絚&ryt}y)>4SD(yiP(O*5Ε󭥓zθ?mo0?亿Kxb /KմP 81Y%>&d$ŴJ]YhڭW ULV3iVɄK$S¦YBy5n &s+Pi1TY׳Gl7K6Ŏd܉W3I(ĭ@o*4moKMSs +'m0k}Fmf5h6s4e׌&Dl{Ji-+v\(d){NqGCNx߹b[nyW4 hr5Uh#]$% wL{ ZfYW+ixD(n* ,ٸ8fv^3/ `{KqJYY)9{+ϮV-ٶSN3߭Ak\5%sEcPJMfuE֣\&!M[L;< .^.S3{6.Bs*.` <3M'&2ЛP: h`GFW "PSbҗ̄^kכƖ wq]ADܐȘySb|8&.O1J !Rs bGZ3$+5w罝2&2sJhk 3X+)ΙN.PJ{1A:p  otEC}aUkX܅3]k(X:'%':,vZ3;X$k\-Xl;1`dm[,'CVc2l؍b^7f1nX:އwXnwcO'Ku+qI{n͜lY9wlqtۋrKkVm^c$"78kCرV'HS0:&Xv[q20=IBy-iT.nFpoA:Lw9'}Kn%}w&XN$*"03Gf=' h k֪"qٞ[_~6ʢ"OB%~=r.~]g5K]`f/ҏFN4m@O/g4ճE6oC̞7Y(MGd;s:uV4{VzrN9;ZG *MY;=X;W/}SUeh^~hlCU(3 [yg@[s;́>I͍vWjɂܶGajqCRVqI %3yTÙ;:~^~ZF{yqP^˜Xdί~Jrev{:ȰL&%b1s0b$     I`Ry+%( ֺooFYN̷ckE֟G "LUl66:'T/cKM (vmӏMSv5<_||>RQ.Η;mLd~J_IJdAS[SdO1s?tSm3ڻ z(+A1zםzIw}ɂbEHߥwZx1Hn|:cEMRAn2l> ݛUKY 72]k8hEjZPig*͛|o7"Xpuw&bISt^ <`pRj8}h"0Jgȿm}έh捊Łhځi%Bq[yh~mKɚ״R3Ε,Ǟ32*AK;eo'z׽Qp(2sM^~Ѝ_ǔCݎ3NjtC LF" FZQ}@D u) l0UלԳۢ,XQ1ÞѢYmrti孹7B:g֣>bUfUE?XL^lUX,[bU1u޲w_w7:&J* R[t{srC=U*++_ f q۶`id?ZzN֡ݦtMKKg2Wu&kCUPj j,fnMqO <-qx/XLUR# ϏOFX.}=h[jѺmGfZfn3Vğqtghjy7D}=b1fhnd<.h3{l6C[WUrȭ-܌QEj J'|Ui[+ lQuS+Ia=gΰщ)N<<,UOW}VѶQ_=&ϹXJ1,t89'{SSTAxZgZ@u,o/ `X7eok({U kΎCP, /֥dL-՗1lǞ2J-w&:Jލ@Ԝ LND~]/u9Ąx_@4/]DHg\Ӫ; = WR5\01=ԉC R\\~qdW0I@/8.4*qnfY<7.P3'm/?ۢ `>Mբ4MD0j4|)< vD*Ls3Oxjŗ(fE[ _+U冦aR#j6WƄ0&YE اh\ٍ߅h=[zExنq[W֡B6?ٟAlʸNzߟ"6XgòPNg B-1?:6t=݅Ֆ2>1U=avD>p#=$q~\ˠmF,qѼlqJ'v▧CڽJݑPGЄڛr*1[mq[ :hon^>)؏e 2돳q]/KܶU-[T]wF5h{1ZtwD89- 9[~t\c-j>ƞ32t]gO鿭{_Xc+$@$@$@$@$@$@*3<-k}}p?,Z9Ff^L|>">KJ|;)bށ!{‹).P2_wC_cr[n탊VgO MÃ-"f2/~Z2dE\&Bj8u_4 dϘehD<[jM{V-? VUD z#&4ݭtn2q"Zp̈z]^(Ó@K,aRpj2?B_& V,kze1bd!#vCG;qWLڭ?w%vǴm?:χj`0=Gim'z6ykJe r4|Ukcw>Pξ5"B4>MjOz,]\ w);D(,xs bqku|*="4ЦOFU孏TTNf̋3a&FɭuD* P^bcA[S͑26epysJIK,qL=mqN>4*FRA#sQǽDr{M7J/+{ѶM=+LƚGн^L:omS ,fGͬ-__5Ll"eQVE~U bf6bH[~6 T)30d*є߼ TZ0hS+-K{>>S]([W{U߃>YlB7jtdK-P,_g!Gr@,XXhU:^,,\}XyĪS* ny/iMǒaglVP3մ+^5/:dZ{_ ](7U^]i*\3KQj27HX )*Y6R;֞N{_<7,&34/8VV}0v|M(js"8*pK7a,mk޷ 2/(;-Ihi;݄GhJ;zp#bzGj.z+mf,[> G?k gĊƍrm#>n ڕko3f|_1sbEY|Llg~ے1(}okD<"     Ȟ@E"ǚw`Vm/ ւIir|jAvmմb_h,5$޿3:_~[ڌZﵢ)*Uj`` ro|͏'sr'ZnڒK31͞]` qwh*l&Em,F΅d3O0ϏiTJ&Ӧ#ñEG"mxE3"M{Kyoܥ E=s x9 {z Dٻ8h@[;v$׏g.T?rE y8QZܝdq܆̩4?tF. }tme))(S!l]H}ضغ@,w\bҷ?Ǯu*mI Yq)j3Ԝؗtw։; w-R|ϲr[G4"2FE?hjN#ᐅP"4d(&F>=)v#>}l$FI:g(o^ݫ3噄ﭭ<3łLV{[࿡n^ r~NInT4dqP=Vz*hM!w`Ǟ-h|X-*ץ󨸣oY~OЏqZb/FE%ɚ_rD۷ayFE 8G8]Rk& z:}m@ ;~N-F,D(ݰukS'z7nbm~(,'&Oǐorfbה?3ldܼHmQeAEU,Oj@FS.бЖlU̪UV}&Q(Uꩬr}v=(hۅι R&EIQ,<쉀eLj>`yujt%4Ȃ[1b{Ch4迒b=IoH$?a.;g»؏AT={ea߈6K|֊%^[[XaC;\W+FP4&oÎ"'<,sC)eucL14=x1MF1^ԇpH*YUMv㶘3P(_>1iYbuֿ$@M$.߳oMA1GXW-l_ Tzj7.U|~6 %;)k2q;zPc7*Oy? !,bnXc|2SV`դHHHHH& xZt^GqSLUk{|D[> 61ZRL3v`s=@A#ڎU- or\ߥ\ܻCIFY޳kz>;1S"xT e= MXS_A̧Cbvw~=> 0tX{ KѾ!P@KDeFK&3b~]#?[̄mY mo7Y޺ѥC&>PIm̅TFcd5fV<6^;|8Pސ` 7gkNXIla Bt@; ܳQ,pPfpr-iZq&O}SB8sL2rc%     oD0]68\`i`tf YӞgL.FX~|F~Yܚ*kE՜_ds$ޘ/wVRjdF lY 1Y75;Ъ&C/F0Է9*RB4ZY][uZ r}ԙΥ?(E3p뭰z6JrIcAizV]ݲrtgChAXO[ ZMв>~#IYGGFur,S`;shbvČtC.0ggFI1{#,풅.{P;&jn E09Y.śȂIhʘm2+ vKqm cI U\c*e/>2 %5\3+<[@ytjF[x7<]?ieu&/~łBǃB$a8W n9J%ճ+UrBdz{柑t\7\OG$@$@$@$@$@$@ L'ENY#)ءwX#]>yDhILۊiʒ擵<򫹧YLg XM|2%gN,jeID5X f2{`3P.[|nfMQ}{K ؊%F'+Rf$զ# 7P>8[v=[^ Mlքa'b"mC}bR\zskak9ݔ:l]×)TJOT|() D%Ij1ᎌb { 6f/ܵru93~gNs DZ@#%Io?цMºf"divw 嫱shʏ8*f+ߏ= -v>Jb #FvMWIm4fGI VO ϒ4Ÿl=(lf֜å;6]&^#     0%Y||Fdb-ˏH.aTyѺcZ ٭߮Ya)rC%[s=( kf;|'jTSp0,Z0$~J&ߎ:qޜ&\rE+ -dx2cSsA P};E3W[ -s }e/MBrLZz"XrM6L+0˥W|Yct,-VQ<#v)Wzh߹F%7%7*"C"UP9YԳnDTfm?􄫚iQj~g<@gnaεcV&%?ҷWq ˒9fѿY  ]R[la@.nlY +tTQoYOCͧP &bLJ43H^uv/ " D -B-(/L勎}o%_*y\ݳOJ ǡ=1E,l=b];/XJO*Q(ozOC ƵOA[&&ɏK\GO2Cd"Xݓ^EMt(׊H7FƷhPa|ss'm{aL#-g-˅3귵xHHHHHH̟zd =ӌcCğfs_W>n Yhb~jTpFkPsɕUcV<1xE7 ZtռDDf[976}=h,خ,"K=3{Ro+n[E`UnrĽۺP^n cXߒ$^;0Wb ':pcw'E1DY 8VJ~t59o9l%aV=+B / aSw :Ydcy]YpbZ FFeUM|`Tk~f-iAL݃kxaeՠy}o#1h,*m6 Z%GY!nq`ؼkHtYo;iBW&tnǚc,]̮̏5r'M>d;8V*qqg^% thOv'>&?z!ՒG%,- m%VB+}Jz!aWV,,X׆ZWbIEZ?gf9s$>ϓ̙3||q [R`ػ6r>L֋ެ U[G+h$p}ByRm`v}c˥vAԉ=tG@@HV`@'>3_n:qD:'ݍ+exV74HR9VgQ+KkA:|tИc6֜ӱ=7mx71}۲-mq÷at&:e9bo\ey=Y5b/ΧF+en}*+B,zx}3e9XKZ<@.Z2y~9X+aӴڶߴJkKJxIoٝ 0VY-*5oXc XI᭡58hܜ#9㥮%퐋'b?@Շ- ~/9-f cJh֍Whh\$n.`Nsj$/N/孎$rF3Y Zg?I$jeo5;]8b!3u^\5K^\|CK>Xjkm6m} cu9CNۡ~/֖;ྙ2sLbCB=ksک˖OY+cݑ zis[uU*{eR3ڦM"'s|Fmٵo}Q=˺.X-3]ܛk x2@|Q}R`ɳ۶,'){`ɯ'}ȳ+Yג\'z]-ڂ fDȓCK-!,5s @IDAT7 |pIQ3тooMP޻=a*.rKJ7Q_״N WFY#|oM8b h@oɆyl~X'y>`Sʂ((; e֠Q/n^&ϬǥL޺RbT޸۴)MO9C&bjV)852Ңh#5I7T1-:]Zm:\YFwUZNZ AarɝaтSHҒx=`|&2e%hitcSU-(\ZkẐb HEWHU"Mw1W 5xj );^ӔtmRjnxgú¡?VӷK FI׵VkΑO<+گvXҸ7K^oʘj~]ăbiqIڨX+wR@n\ @ǡHݲuEsKeBxqZkZ[tV0kx)^e JӲ`;2fyHPvOO` 5ָX d:~u[1훣%ѯu-ݿI}ak0vziP%ﲶ8pՖuH*p˥8%)BIi2_'?z/ΛX oW۠{GmˋN>ԑ+Oh߇Z@mFT ݇^7 [#nc5Z8#g\yB87~U2XH&ٰ{M!׿R͛5>|l.F;DA|Y9ϗD|{w\6t{&M ޜok-传_BaRE@@HJiZ[@zTɘǮx>rc|TISE1K_gR. 7j-."E*eZ>_צC;re* Jdr"CkrvDXWXѴ-)V̕")ϻNESy[ê'vB["X?V+ZcE.&&o!ݝ|4R/%~Kxp[r]T9N{VL`h!3tSliݬ"i91At"7Q/?kyT+'9h]%u÷xU0e"yS R(БydCz]2>H۸0O=﷜ɺ:?ÆDΙ@u{g-d^!WaNrnu.mRܯ[pH5YZ$GIzl(JˑW%7]Nըe{L&tnIj==9URz'x>4ҹ馸=KecZk4p{Nɔ!r@ E<mv['&In'H朕f22MrH ϖmڔ{xlz.6FoBOzVY_}sp!6oC=ߩ>d˕{/@,[FFbzN*4UG>CHˆ 3a,פkIUѢb=i_h=J¹ -Nhݧ:Ѣrhb)-#h^(ۺ\[7m]"MoKl +A@@Zή  _|jXSkEN7_S,Mvhsվ5?1͕S:dm 4{\j Ofɚ[(ZA!}ڦ5c?u]QZkSU6n:jg˩W~X*˘E=p·|"D9ͷ=j=KַJd~`;D|->LFlAy##$C<)nׄ\цǷF]hez`{aZ2HgfC+V"d =ou:b)x6T\"&k`~`κoDm8^_մs봬~}H==/(7Ȓ"/ۤ1fYݵV\,[s6Yco@/\- %3 c6ΒQ7}O}1:c0hT~3E7F=fJy!KޤޒOL`4ZyIj?\Tq.\TXZ)[wkChSҴ9sR\j>7cyF +%؂H8WzczsB|A:oVb9 nͼ.ʒqN)dHJ9v<#-l| +Ѡ#SJ]GJ7ۜ8?jp{H.*}.Ƶ2E Z%q\krkM%zO#StzuւxjɁ]ڏh; NmN J[dc[NT1_9G^]O ӸN}[ۍ9o(=u"7O{k=.sv-齎DOoq-;崞75Bg7z/שr]# Br[@@@_t?0/؈+)/0%ƺ@@@@@@@ $ݔ}wW       |m@@@@@@@:9+D@@@@@@8 8}l+       @ urV       q 0q궞?Yg@@@@@@r?lp ;y!C2͐NU1d@@@@@@ {@@@@@@@ Д}Z9I @@@@@@@y@@@@@@@Hr       vv!       Vi$1@@@@@@@C@@@@@@@ Ib       =x       @Z̧@@@@@@@ {@@@@@@@ O+'!       ` 0o       i 0VNC@@@@@@@.@`;@@@@@@@*@`>$       ]݃w       U|Z9I @@@@@@@y@@@@@@@Hr       vv!       Vi$1@@@@@@@C@@@@@@@ Ib       =x       @Z̧@@@@@@@ {@@@@@@@ O+'!       ` 0o       i 0VNC@@@@@@@.@`;@@@@@@@*@`>$       ]݃w       U|Z9I @@@@@@@y@@@@@@@Hr       vv!       VAIs-n'8Gdt+`rWb}%n]SWvx>p tXd8D@@@@@@&|yK7ܻ2~bܜ+1Wi2ݞ,fx_<ߖ#79%s       Q )#˪=:ז.eur[3!cu5U<2ɗ̫)w>A@@@@@@@yOG|i l~n);ho];~eI`e+8=#       t_ K( 9(%L q ͐ }rt)^\+& 7ɚW]eA@@@@@@@$P`ϥگRrԖKVӝR ejKM\?Nl+       >A@lV$ '3_֮,U:媔QבC"9125kd 9[ڑZsKZ/GȘqc$H+Jinok's6C .C[u??!KD_O|V;3cr^5ġ-9}BǨ7۔m}@@@@@@@rOu,j}AǞK7_, Y'e_]oM@zVd/3zR$RE&K*(@\ 9E"!ʥxde8RQ_I*fmI-R)+9!uu;n뒢)#      @='- CSkc'3L̗"),(u4?] y!A‚B) e2}|iEyc Jee η -In/y!Ay#֡zQ8u;dr9_<(9$Ay#1aֵ1       טxH*vy+R~)n*)'}ҿX/_c9z>&.+j rjcW׮5j/7 2@Ru3P)}b١Tlnyc6eMdWOH6z(7/:(w3n 5D_9ٟ- ¿}%*@l<֦,̑c[cI6em_!MٷX+j m>wn40+       @zטoP=N d&)\0(o"clT<T6.+V=<'1S-tp+".FF_"p&O t3>/gAi:Qn卥Yzد="f#+H-Mb֛J4tf e,pͣ?oE35fm{жjٲN6I;1a@@@@@@@aZ;-Gku+^|JC6P_f酲Sm0?j}m4G5Σ R*ɵ- 9&7vȳ;:-hFlUVvUٍ_l^ >?]_Pę       @ю@s쩬 2x IqSwҕt?5E dzz'{j 2'RiGvf?񁽧yc#DZ֙Hc :,eR@8WC&KQi_"ŹcRN@@@@@@@%Fc>}Y2!jNLMbaIG8D  2%N/C96il:$g>4T#lu$ߧm$?JYVƸs1       @:ӹ6ZwrORK^k#wM7@-<(Z/zU*O`*En(=0(Sj֛|qg@@@@@@@/zGpӧMK<4 Ƨɝ]$b "uѕywֿڜr,ݢχ:^7vjb W\-+п|1       @;[s!ymbe''#RW;>I>5'w[&c`{vL.,+yӻ#;$֟Nݯ/AZyr}(j@+'@@@@@@@?qL曫g-*mn$?fL֚os[G=q6>!~Q($79Vc'kK7l5&7e !       @O $ɛ[_lon=j\Ͳdz 9Oo{ﰨA&{ed)XO2Yi~M7mּwFjKnͿgѺYtjϖS%ϗX.-ZpdKsyeC`r7Yмy =RhɒqZ*&       @7rO˻6ϖj[qYaX\Z^Zmz6__"akS {k="K'GODk`}㨔XKmtuT뮖:ə]e%xA])1WX![c3祖{4Jj9ERI\"}0q0jH?xL7?D88󥩽Ij+K:kj_™W.G)U_P"/yKPޛ>/U/ ?}]| ~ )oon]v& 4yo|6ٙ       ) $Uc>t-[wcdxU229Ba5C^wryyPךfFY<}m7'41_-2^Ek@@@@@@' 6@@@@@@@H) #       O/B@@@@@@@.@@@@@@@~ŲY       }C|       @? 0OX6 @@@@@@@o@@@@@@@f!       }{        T|?b,@@@@@@@!@`o|@@@@@@@ _,       7\       Se@@@@@@@= @@@@@@@~*@`~l       @ 07r       O/B@@@@@@@.@@@@@@@~ŲY       }C|       @? 0OX6 @@@@@@@o@@@@@@@f!       }{        T|?b,@@@@@@@!0(l 0@,LdvA@@@@@@@_\tI.\ /_Ny{֘7C !(21 "      \Fv#fnS.iԔg@@@@@@@@,Нy<w-@@@@@@@N$}kXWpVUO^{H߿z_9S>su|o8 _('S']#^'Ӳ\<eće+Wʍ }w%ˌ"   УW,0>ϬJ[E ҵx0NF>|g:]&cwΔIQ3]sߒWn"[NUl)9Zy*c#dώ{qĨ 2[@NYKW ϯYWI7Zyq!YZFV i8[<䎐3x7*ee %uy×O?ISm\ȟkđK 0Z9wE~ 9w7o9>-HQT#9jmGȔ&}glC-|>.O; e8RܞG䵯ϖ_F0jew"z`}Jt;1B2FRLZzޗfRX2;*c1Jԏ}}fђW 73stFo;{\e5{>`kj`>6?q@+>uȤqb?^e%~PJګ"'[wt 3    qH1S:> I7gzIC2uuu<$EWj|DoՇV%O']ku`{YsuH͒.sY歖; P_n"FkoyZꃅRښerɸ^: wEeȵ@36lݚM N֫ۓQLݫe ֛ckl9k\N}1h~KNOεˍzm2y|E~ gkq]c+Z)~j;}m #COz/?!צr(S/Ki.*   |\c³oMAkE7# im^ ۊ<݇1v<qtS[Ե7k=D:wQKq8'= 8;OkvF&;SnlEڿ 9g;u[q:>Dv>!AyK1Z`/i{ش7>Ղ@͖z>swJ-_!oSΞdmʟ|uI}r{UEbH#_-M%3>'j cɽgуFt{s օ ďnTTnZҦf9=}p\i\3,Tu<j4藯F&+ hȆ^Z׿.w *y+b)fc3;ȏhU}S1 <=-ΉD ')vKU-`D_QRtF}&iܐbډnWw|Ϧw?eLqn4JzЗRhX%=mWI-@@@@bBtul#9I/i_yy,0zU \#VY|џ9Ƅ¥RoPx;@kp&3ӌFBycvXtУ_{@IDAT/O}w .{P v=Xο>%cՃ!6-1qiݧ44(?{Van=oZFv!|[r0zP޻mSh!Sxo:_ 2?vdzc +п ٽ9jj,.kvs߮M΋дn[7,k|_w-@p?kYexX(s7xDTysɊ-v%wؤrN64}CJ"`%n%#g,ż   I ƣ^$ݳ -=%^NɅ͚!1JscG˘ c孅:DF;CD[N̾GȧÃ-=(?j. -rC$RFt䈜om_ӁCOO~BwѺM6`1݆._^ !fҮƅlrNۜ<~D:(|}^S8)pρ}cq2?t]6N:̯uDsPi>Ĩuwْ5.$$~$sσcFg~tY[[eeEע>&:,ߵ9|D71vZwgx]:ǂ699V2gh0(s%IgGZ |6coPё -2MkYZF~{=X::lcdĞMƅ4=oɩό{4ε]&ɯU^<" |FFP~R~ :PCڴVv5r.j[@W/g{׏ zx[Nw]2j_;a SJcdM Bwh3;@mKރ,g7L]e >=A&yr`FɽI u_}uwN'D]u|P\?MkGiHr>-Ays5 )=.Ʊ4lDɊH< e2c].Y@7Anbh"crZH r =*Sͯ]]v{㜄279}`r2r݉qg'{u12_kLz]F~c|@ͮk12Hy>=X S=gl${Ieh_!}GlKܾZ<[k~\tFx'CkA3?X)]d}5оf{Y>dYo4W~4fpLgKU1]/Y `\8oY?-؈RlI@keӻ<`Y6ڝm=MurN4o#9嶭" oH?Y9 ƨMh~vb7>][l-\2Kk&wwoVWE<.,xE1yoY\j̘c´] sa!N02XcRy'Hn &_>]191fh& +WJNq~BNaȌ i!+%0vҲgL?ES~c)ڇkaKZh²%T,<ˬ\Q \# 50/(˝h`>u)gk^#Z&uL߼'ܥ5bg5ҹ$9]4ZE,`d;eRN}kԬAJCU #Iߜ*ߟL40osYh|c}' zy.9$kqS=W&tpI3 eriu54ާ^1ZF 8 JD{`3Lc[`̅qp_w4g鮎|K/kG Nw~8Ig_^+,V԰z9?\fc^TkwD2 \Q,d޶4ky0k!9/IsOkCSCwjph9/_Y$F,FH1m"o#mam7zis\#9۶kNv^i)7=Svgo'J]Z쮤juw7DO4m[C #_->__*cnRѾ|#e=Ck^84=XHB[<$+J_Xu%k]6IO5Ǫ11E`2^뽘Ays.Toy3?=L˵zmsѾ5_/H 3eܜ!)wd ʨ&Nϱg,tz]u!qȅb`;T,c!HpP|X.l~}:Ckv+6?#6z0Z$Y"6Y`sf2xMu:[ [>!rAyoZ[=[MЖ0nBGNCu (9v|S-<ħz!U_# MA܅rSiuFpoT = LZu |쵶;\n,olI5GL?].RL6([0-ӭ&8n\ !   Чy lMRC:96)9.RԯS+Od8+ʧUFMz=#'6u,DNu\]-(2rzA6N`Hڂle[e\m:yLM >5Oj;̓N?߇{j F qs$_۬AryZ۶LyZ+z-xKYdq0o"ZW!9.4@N˩y*A|1ߺW.qΐkYNj޼u|8bږeQurrģ};Hl1Q27i}M \~g[jG̿b BZCqbߍ&,]pJ# ²;thI57X2\,$o p97wr7BmrhPgfh嘁cڢEM0K?mV=89رX'^SvYñ׌ZXpq>xh+rqq Z. 8W*}a߰As>OBL>6={fYro(2GnmnMywphh3_ >fhߎA9}I:^b7/Ay[{Aq>\ C[ ~K޲cb>m߼O'Xmͧylq[uז?nXݦ&tG9;`)?Y.qcɲq /Pw I@ٰW}}7ʅw^גu9qxmom^ϙ]\ߴASڝM`8k=ݹo I[oSR`p|2¯- \{p,u3΀f+dHǵ9z00̟hݹֺ .ׂH/kKgg끎 wtȧiDt+F^gШAy$ֱ-ɢvi_>3Ia@@@@,:XK5rFμu*9ztMSm/IIuuIg?8K!~vthSޚ;*>GoGkwZmuRGimy=mHq]j_k4h0}pvK , (3Bjռv2/u[ɟ*}.oiڗ9G{x#-9fk NwJ#g3@N7yd`Ho]ye40jo~v 6)4L ^=M86mG fd'3]?,V~C^Rkv۷DV;3c^:6i:I#AfgaTa,5in W'\^zL'4޳Oyʌ5] L<3Ņy}=o)$]P''`hO9ϖ:轋5b)sEz>6/Uh4?6l]rSSƅWY2|yuh/qSLk7=/w f'Zmd ^7a8eJ[+O;z iT3Z j3nX6'#`3mt],7~'K{r:veo27)+5s eWc_Wޣ0%>CM!0C4}-CYc9Gz8۹zN_,.wT6O5tM?7Cݹo#/Zc>8\m|}@^{D›'I C,:bvyŦ>B`v(z.8t -çe32zoX"H**R5tjaiݴI\\6!P}/xD;5mtZ{k{^1km|C68W I'^${ȟu*!#   |L g{6A82Ȯ55@mnEAy;"PX{~{mZ/mҾ׭lIpd=(o4C['p& ^/r%(o$3/&x.^kӋ8vk2ikgoռO5\41{zq?"m)8aU v8䖦 8cy::Unq0(9,3j#$a04P^~rB$yy7io &W ǬIeLet%Ԛb4RìpLQ W=K~]|Yad0+𴐂8glAan- :N2#S\-/X0 Ňˌ~|ǵenK*=?+f{w59h-=~sJ71N"NIkAˍJ5} pȤKa?mkƔ%j[8W占oE RSM]z£-(?yXE223L!;9Jl8r_qaò~d$u ~cLlcO X2,>=#-\jڐ|SqPyQ=tT$J)$D+NAߏ \]Pڂ GgU*DV+P*RI 4.(Ӡ )CHܙ3L&`;wםs35[͗k XK[cek=}p %}O᭲odHP^G9(Xlw\Fm@NHPJɭ }>NM]hT6Q;î-\k^z+:҃ҰsѣAy9wl!L7<8/$8ر^&@ R[ǒ_VsG Խr0$    H]ώex&ݟڣ^6@ZR$޽~sLyܓ:auƇM^`P3=jA#kX~_7ۻ*73|E>QphZ>Jk 4[в{WKIWo(Z@ αo?03#sܚ?;iѼR)7CKa~Tc?([%^ɷng @̷ S-t}?Gf^~`П/Y5zFG)t$e`89?פTŒRΥ9GS:XFXFn mKۑ-impS2X npXi}-- ,Ü'PII7}i~^Fƛ#r\>7NQR;d#aנ%K*%w1?xѠw1 CD?{p0H':͎V'QhPhh]lQ|NQ1wƬ<7VYYZq3j$Q&ÞTps/ͥ6vW~LkrsiGɫ)qH?b6Lm(s_}YG|\w6qg7\cϺZv]yZ* D=V3˞ۭxC~v|2xOaձm9:jYh ͔vKPڸ/(ߩ %]Fkص"}v=xVr\*Kjt;L`48~=d̐쯛G{k{[yrLv&8#uj:e9QnV<    @OŽq@g响?NyE5p}(0x~@ G )z}}dyz 2ɩ~|2tV9:= ;I2$g0CAg։=k-=*tEٶh:u3/˄3^x:DnƲ"rTpcsv̵:ׯ5({ K,x-N Yz5Y9ty-8p@[2:I r잙ykktCF^ercȰ"G#$h-R_hpYKzל˕m2\t{_nA-嘎|ye:jE,olI-|%Xׂ 7܂荵{;ҥSJmБQ*ZZ<,坷;Xjܷz3o߿"oܣC[Jdׁ]   |ybjtn*y^~u|佱Z8~ݢsMR4޲Sڌ/?e!Qy=f/Y?|jEϪs9iڳq}8kXv =D$(c{W%f>+v*n1k\yS?dFxKL,Lièx-x<حc>F=jMm(Lԝ!C_Ρ2SG{@~-Y)e5Ϗ1zD9i 9G$/  LrCR!ygWʍ﯑#`OwZ~M@ɀ4~H#aߧNm$m:,|5}[%iNqm=Wd 7 '}l6֚LxP~R<aw=-'Y?y&e4}c6 +Xz`[ҢkhTc3ftJZIڍQlSR8qiϗ/e_[']whz;zP|]G,XzmPN2N GgZ'ح_m*e4r za=>\V>9OxKܵSŘf9*Z,}RfA)Ke ;w9Z>zJoh/qFrV/Ǜ+g1wꗓۍe3F=o{^1^ݠoi~58AG }@@@/@O?qDd“G9=Z[Gp[2)bOިK0ϒiˤyWKvZ1zuOw['#,;?ح\qH_*=Hot=PVn3/˸ޡYG~7˻|/~4v91#kfpm˱emXe3pXMzQ{'__pC3ae-_#7eH{Iu(xblP^s~yv^C[ldIiQ>L0q>(ܔ?t=k? `ҹo Eb)2N2׹Xpt2"B\!]H'H_+:ɋ)7>FDyX䯋զ<,$תچ*u(d8++ߐgL ]Y/Hぃ日?XM;O~?*I:XgJb2ߵiby6[[QL=)ctaNѣ~G0[@@@ 3N Z[{{<S\"*Sȴ0q4ɉk1n9N1RS 5), !Q/I?:6ɻ6Iћ:Ľ%᣽?@ sJX޲×icR=F _ab қ,]gm\/S:|k9ڬӡ-t5"n 3rݴCnU`ANgخK5oi~όI'K` ֻvԚ =`Yo%EF=Na $=+'K;F֫hyLײkU Gv oW-."! oXzN}eRN2eM·@ɯt~ l0)Wo.4|aO`"Z,]漤ZaD1chOq ]gMDe$u`扭yNE{auT֚D ʗVFm8Nc{, | N\Ǣw=f33%< ^aA2(/#ycm0C2$]Bm~n9ὢ$xFKOyú_x4."yjt۟,-QaC>KaAy#ALb;~㐁Z5NuqO6!f̛LΐZ$V#}^g?' R%MOIea=^z,fױ}/F|$#{Ar\Q4Boo3]#E7FŻOh~C_88(q/WWˑ#21Y;&K:l!|BG,I7"zԍͻ6خ16ha*/;.3[Lu.e#erUh69P&mҼ0Oʨuw;AB<Q=糔~׮YuE\#L5wK3޼uԂI}^{Rw cOt-/$xÝyQuvy 攟!?Y)wO{z4uڐ߹pLx+sF,O-Ŗ›f";Ķ&g&un/ ~g?X͠aTASX^諣J7YEPq \?55˥}1}Cٟ"/~bqp({3swm:}Cλ=iŸZFZ"QLJkCtN~   \a?=eqNw.ɖM C5K:y [;Gr%iA$'zEj;]-C~m9(س+k#὚tx_hOo9^tr6o{gˡfOy]2!~TW/r`V)Zsgb;kgۂg{̥{M; {n푛a\shF^0gVK"aW/YgUzr7,_z5 I ;f˧n:9:]w)kڸ$ !4`__@ʷ.I~ zrC[HxύQE^[tWea('ΐFy-ГU~p5sy; 1pjfٸ)i]1//_|'ڿs̷<.'3K,kٛDuooٯcڠ%XïHWu/nּu݁h}\{Chv ^pGsRs)lyXd3z~ھk߯ o5x'}|nG{6o^;[25yf˵Pۨ m(K3ꞖC>3tkOoamnRg^YZ}䟤;HJ^ J&,#7͓b~Zy齢usw#eҲt׆ϗIPlwḋ,י2V&w[/~*:_*תj ;R.=S|fs׮ fݾ9Xe27RYk}1wL񡶽qwm:} omعI7@@@葀S2v!2ɮWP[4[O Z6mfЦa4/'UKL:^xq_:N{zNaKt6ئohO{{h7kP} k1}E輕&9l|-R8q\Y2on&{**%om@Bu];^S}vA>o3:(t.Z(UP晧ǻ( &ً&JhxI>_fȢ50 =fAR6=#m<vw1p> >,4@\Aqq. }I ?jTƻx-~gn|!=S%k ^*7HC2 9wKO?v8|R&Y< }] C>)ȗ'Q_G->ybrfZf]/ gaf>6l#g÷1sO,{@&3Cw]4?wm]gC4+]W&͖^0q,S{i(+kqDO=SiiPylt h!:#F>S+ gВo ds#JkGxg+-VhL?F߭ZRh WvU]!GH; MFè}+WIU=!KeOS%Y_<K~g{_m5RD>Wt)=1޿S|=+b>f/o\n̠v_܉׉C}:teObK#^+\#ཛྷ\E}Z?AȐiu$$^llAy3ZjIZ=_ң>rzcKb߂q``է/˱ߖ"{ű-Wt.ƓbܟMKz}nn]a,uOM!e>l-{^ή;*&OHe`>"7o#mHQaD뭥Aafh#g1qw?ݧ3ɻvF YR:+G Z3Kݗ8``Y:%Ss~4bۏF\& q~?JF4?dNe~.R-Uligk[;pwf!K*Ko)eⳖmc:@x^/kɔ9ìJJ1H@ce;˹JZY?#m2(`\I:N-'Խ&nq`MF crt0Z{l[y[ջD Whe\Q*&-mt4;#+7ۊ$윷jsB7-R.3 Y{`qelk^XVw}DKel^F 4,JҺhAEeH{{ ;a +L gPbo;M/[eo?;׵"FCGhT7I}kĒҼ1 ?ZCM 3{}2xHJԸF^/_,[~tG=5&%mXR2J4\yE[IWGYYz[.@Gx. Z]ݞ{ŞƬv]lzNXl@@@@ XGaMkƗlf:oy ~$wnd\(B^˱~:k 0ed䍙-o gHvM?]ȃ*14[jyL= f2#Ww{9":ɝN4[.DžS\ n }̊;leƔd/Z Eoi @?.|['.:u"߰!h#)wHhZG:!u k Sk߿d=\?7 ?}F:/uOʍ?2:@IDATtu2|hpDs%8GoiC_ &(oI)Qvvc]-LR׈u0xln>{eF&5,W.p"˸dV ko,M+u=2_YIד1ҍRn)UcL^'#_3/ߕi1z~~GFl_α1J~W71(oQ/_hhJwmjR Si,3jui|KAy!e՚:ϱ=_F ^lxf2L&4kH1"~_ 1IޕG3˕!5Mtݷ&TtO%{[ߒّ<]]1>YvG?>Ck7u-,qFVo{oL׷dD4_c+#aCʌdp o{Ds״+wmsYFz-(x]Z<"   @oXz9ڛwoO&]{p};s՞m92KE##yyCc)J;3us%7+Ru>(7{pk]!Hsdj;_}ǔ*:7m^hp#6Fn52AZ'j?(Fʠ|H>XtW'9:qeCe8Rt1]/ݐy# {Qy[{{|k.}9"1ug뀁97xĥ | ]ϟi.Yz=n&q1>sQ?"F988/V;^dwʐTl>ni7ᆳ^Ý=ζ/N{F gJ%{L;emskd\ߴ׸Щ]D-+4Y}k"A׷Ϥ׽->ש$pMS뽼VILH'-תJJ,w3[k^O+ٞu;6Xo量.ؕE][:W;@@@K\#|\Dщd#XXZt\}yC;6JSZIlIjpnQz[Jx0*L HB i9yJbJGE[z #/AOzI Ҡ%U!*O K@}K{c -4D*؞k}zqčxK<;ꓦ{*GR9߷Rh~&{LZ0MM˵*޿Ҙ_Y}} k.ESj   % ' -*6l_(   Tm4?G@@@ 7=+kD>v̙)>,3na2tM; c@.ȉIۣeWbdߎ6У`g@@ \rUUVZ{ϐkKU%h   @񕲿nu{rbͷdbpX@ye.82ɝ#";]E >c @@@7**=2ib~oB   \6r;VmssscgNoL iQ}\~rV        滺 G       E'NKQ"       z;KOp       _#Vn̍yDv4 x<$%        Xe@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@=K\.w4 &ִq= M2@@@@@@@K^ v`KZ(7){O.~$Ky"i腀?]ʿ1> {FrG       @IZ}5]U['Ƶݍ{dw{{A˚$}ry9%*v49!      L v`~(X_lC%W{w*&s:RBn֌Gs@:2%@@@@@@@|*?ٽ5= yJwM[ʁ_z\-g2B@@@@@@@ !yGDYjsuLw^~fސPeH       M >2 7.1xDJM3K>cr:>!W -F#Vxe:       @߁\F}se۾?| Egd/|*>ddu!;O+dZy GRYT/2wC;=Y;orwPG;GU6Kf6Y+67[]]V-aj%{{Lyge·Z2qd dd[cuj͒¨g-uVɜ"g 5~:[Uu6#      $#w({of7j_u+o݇垑U|yLV%Y5JjbyXWmqTCJU-w˶ WHZmH] QJAy=rKNe7]MFo^ko+u")J"9ڃF-u&Kg{-}f@@@@@@@_ $׾{V ,k;;|枟%ilv:]}}Gȁ⟟KҬC775?C߇.Ry]J,.Wl׺Ի\.YZ~l<I/Taxw\ߨF:_Ґll}@fN^ff!Z/ͧ\RW׏eV+ki  K@@@@@@@ q±̴7:!5XXTXRo9g{L9|0#[%ml7W5|tp.Y:{p#'>Qb4g0J:?{92*3@ְA{2 q}b}—FJ4P_h++       )$&LW䟌ʞ.lσ.(]%wM^$5:b"aY)1,_%uh;?Ǻ\|7~U.9o{Zy\S>%p/++o%C.rt5GV egn{ר")RsǼ[ҧwꀆHݾU:z?UzR^^z%SGZd@@@@@@@ YK9]ʴ5 ^á>%uvg0WKV3(_!/[EJgdKzPO1~/Og1l6iuQ^m]**@U5g!7TPɟȥ8R\V7U_Yyi,z7>=SS#V@@@@@@@JzKa_|޿{3eTxO?#( 'Ç++fP^+#u/%6ߵƭ~wПX&&p: {e;vJ!cXz% [?2b@@@@@@@@JyY%}yl4]'޻>`|3<qȩ{,G]*978lZz|5Ruko)M`Gl^+KY*~cIy%{(/8+@Ƭ        Z`^RkKTn+m6C˾/Qخ77J:S8v[VX׶OE/) H^W⏼xj-K' ,9C7R7< Vm[ɮ#Zr#d#sǻt(V9`6Dok<9~Xv%{_Eɗ       @?H10/2zkLh%1kpU7:Tgnqjnmϑr ʉqHwVsYrv}$u[JgI@7 UkZVqWٸPf=a+\X6^ 4GX/\=oʪ[YakdQ|JkdERz?ؠZNj34j3|O#0`k|68uKCy+K7`Q:.\ i,yo$Y_uʽAii4pVߚ\%Yg s2_*'       @T{̋%@L rUa} */I)1_+"sSPlܜĥ_eV=Cy`kݫ| IO Kez9uKWjw׏zE VzXu؀       r.}.@;]84جúaxA]O{h40 ~mLp(#Gr!       3"      dT|F) @@@@@@@ ogE@@@@@@ȨrS       @ 08Nj       Q0@@@@@@@&@`q@@@@@@@ 3Ma       M|;/       @Fg@@@@@@@v9^@@@@@@@ (7!      7s        0Qn C@@@@@@ox@@@@@@@2*@`>       3"      dT|F) @@@@@@@ ogE@@@@@@ȨrS       @geeIvv 0 A@@@@@@@K!%ݝ1osrrʧL̎       с݈T{=Y@@@@@@@= 3|}~kq       @Obq#       @ScO@@@@@@@ KD@@@@@@@R 0{"       W|\"        @@@@@@@@@@@@@@̧nǞ        0       .@`>u;D@@@@@@@ D$@@@@@@@@ u۱'       qM†8#CR t_<+AF@@@@@@@@609.d}v*|]yLgW?dFsH^/_:#d}Q90۝Ҿ2 m}($3~4ISbHy!9tˊȡʄ&zPVn,gĢ[&? f)3YxAμיz. zno?(I?mt`       U?SCD8 ,}C-Es:fx0 ,q AQ{VbU)39$DK2dNbP FeCƒ![?ͮ-ߜ=2$abfv `Y:~@>_ia 92J&&̌G@@@@@@Wf4}wҶ%Xd3/ȤRW ] ;*/Ńe[W}QJCŰm64ٺ+C׏1LeȻzii<       ]Ono=.݁z̖$] \[3,[i,/fN;j_q;p%awq $n0'(rB NѰ>.9f9ˤxۇd        yFP=ܧg^Y1QSQ$C'7i7}vc1ŭ\g^k[%6P"WTJ[<(E\2kb[gz٣,r#Mг2Y6̶%fUm)y      M"ck:rur, #hw|d\S8$|QvϹ3IIi?7c%mO12LuH/kkQ_'7O)l~ٕe($"$/z|2CȒIpGu fc[rHn1X@@@@@@@X ?c],lpq):Mi\Xʤ2:ɡW_H晵)S2EOn=j 4Uvȭã|x>XnƤ>/Ce "-8ʻZfC        D e&g%+!nV040 ~^l۽=-#U7yWm-ߺ9ZF oO1%\XȤ#J?]»d_1T<9ɽ?z`=|m?vٖ>۵!PG;FFݵ"zG/       @1x`I69pPa^Xy(s{AR52`H+ZӞ,γwsY#E7sm󍛥!NF=>H0I^ILx1et޽vx樂o_ #|8KQsX7ٹrbI|R^ZB/mg'4~2IZ@@@@@@rg?=<^p\[1~obJ~V<Βd)i^%c7/mqypx*4džDaero;?SjCm))/g$zrdZdJ۰ ǥپO>MI֛       qxI{g 3g&/-= 9'T%.oHwo} z"4YsV:)lIpPN}Ci-dl[Ro!t nO3@A       p do&ݟZB{v{T]6})]!yU$HN(F_#Gn:ycuZY`[Vkgec->Cy~^BYd/Er͜y2: 2G9       '̟O6v3Ad_PLF&}"@ TwLgY 4?ҫ'Ƒ]IwɾkdRwCzg[,c#c߫̋X)[L {3ҹ7%k f@@@@@@@S-{"Mzity^/)PN>K*f x-rʝo/2WxtZh : @@@@@@. u$<"'/YIiپJ\+u8{]:>*kޒI.Ŏ#@}{ q&j/ƺIWcU2n\Qb5xRytm$-cLJ2-K|o-/>Yst#Ad)zs\[&}84      }/A),GT#'W #P^.J=KҝlKɛ:D}G~ⱦ[FnaoRto"Z52|#       P̵ҿYhJw݇妾2Dtu O7]+m&Bٵ/Ȕ %r}T>#a}\G@@@@@@KV 3=CZ`gyxÌ!êoxb^==%L-2Y|/svruzL={Ӓ7QE՚*)m/@@@@@@@߇]`=@M_'gȳߑ+-!ґi,ANn%J):Iƕ&iYdF*A=,aB㒰J@@@@@@.gXs^/,2 |h}T͹[Z4[.\X&=T%}D).?I 9|@5Eұl%/3s\<0Sqծ-c-Gf6ςVCfKqI       @ wۤ<;j ˹rAr3/ȾWuΚW\]X>ٻ(-@:F#w08$EID45nO!V0O$`(6)xOR )S -/}~:s3s \^s]~֠/ĖA{.&>fז/-1t?]] @ @ @ 3jS!:0}Mf">~Z|gx̽87.*_;3~/|G_3)zձy~L<3 @ @ @C8#Ou\WM>YJIs=k6ƁpK/ɅYGUJ/8Θ7}4.qa+ @ @ @ p :]Le[㯭J8.L\IRLx@ @ @ @L8m#KQ4]tsbrӬh0}),pkGZN bҴ ;z  @ @ @Z᎘?#Jk @ @ @ @v&OTߢ @ @ @ @` H̏g @ @ @ 0$GU @ @ @y#1 @ @ @ t @ @ @F{fzL @ @ @#H@b~=,]%@ @ @ @' 1? @ @ @ @AKW  @ @ @ @` H̏g @ @ @ 0$GU @ @ @y#1 @ @ @ t @ @ @F{fzL @ @ @#H@b~=,]%@ @ @ @'0hbرc# @ @ @@7;41ȑUU @ @ @G@IDAT @'Fr&?7yzL @ @ @'W͙gs `L&3P1 @ @ @ @@#Kb @ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 1,6ݴ:6|jAԗUS~t}{lz`AXWgǺwƏf5tt)1{k @ @ @T3ji亻z.e䮧cU:~ziFd~^xZg>YjdM8/M3@k;G3ѵc{=LNK7\sf2}ʥÞGܵTWKYZ4LԍNO3ψ *4n\tT`aOWw\iXĸ꣮ddUJIt{1eZiL~mdcO;cg2ufz e}+bػ=nUvƒߩybљjz ǔK,ԟֱN7]'?zyi).Cz^}z^=ߧa'@ @ @ @(IVTJN]fukqc5*%ccgJt63F;REsYtqRlmAcۿH,ݿem{N{5әseo6=wk+J,U]K?QŚi,%-|WOMk+?qKcsO;}Pl-bU#Uձ cZKX֘㿈W5'Ģ1ls|=7h}eęDz>T?paZ-fQ^jmi{=<\K79TݛrY4 o獼y @ @ @NS}9| 4ۚ4/_̟hn]KO-lG.pJxIʷE/%Zk)ʖ~IAGjon|k˂bd7ӽ,aoʀ1@K4Ig￴?8^V?6_9,)ߔYPfّ>pOGؽ/uZcť?y]#q˓Ok>Zfx-eIw2G'^ 9%团=+ez[nW @ @ @8'K&Fi7!?T|cN> P޺+]ݔXT(B\j߭G5~!^_]wkPccJ;MMu[gtvfJFH롗֞v=&+^ٶ%Vߑ( y1<5S+3^i'r>Q]=_Rl:)䋯[[|LoݫήؔXvƆ AnK)־IS,_ jI|Lߣ;c.yStJg/{r<;xᙕݞNdlnbsյ);7{, n}Җ](a @ @ @'PSbzz,>J-zpK}A4F+nZ[$W\cW~'$.Hkgso߲6^ĺiN~`F,ԚXsSԥu{kHnbEiMvҵtt}|S2zMذmS,z׌SWbmtֶX5ߡƒĺҴ.fԧYҲ%,A?+m?X]^hpH;yǶƒMQ_\pK<6w}/?8XOz~~gQlx4ɾsek,Ko]kb9o.!ÉyXتXXċ?  @ @ @N@M{Y[3#?n|Q5dX񞧢$aٖϕ\L{4&Kkw~/}:yzgղgbFzRo5ܴ(Ӛ)'_ybUD5>-mc1cj|o/W?^}Mo;JGևt~*-o0@+TXwۢ%$ͼULLƂݗDUv(}PĢܒ|Jļ\'-4f @ @ @3B|=p^LBe_=qGݷǞd[r-,ES%3Ȯe"K?2\мMz/;ܳ/4^ Q=S]Syo.]clܒO qOß>:(yý||0A c fĒ?MӹPeh%Z4]VX !MaNÒuˉzϟ YnZ>3ȿ  @ @ @'^` h૱gjZc[KF_tlߑY=;s-LAҩ'Ѹ %ӷvM#Ѧ lFLwwt{v;wƋ[IA@@.ru\<]C.hSrfąPqSL(> U ~4l{h{⽻EC#߁\ҳc]ӎ(.F;mSSlM bƴE}NМF? @ @ @g@Mq5?AL)UEfL88]?͵iq4ܰ~tV;6Kۯߝ_XRI=Nװ]|ik~zO>`rnp_ٮO|X>ٿ=zb}_26nj qS#KN[ @ @ @<[~r,>NJ* uOi>}ohWbCӔψ]㾻8~oyNmn(ϤoS<+b}scUjz=-]_k۳nMz͜h|n%`c,6SΫ @ @ @j'Ӵj]KΟ:u3ٱpoe.W-.nue\JR'l]4] " ݯaۚ*3-=2V+WEkW[ Yn Ew[oo^t|}M}<=V @ @ @ 8Z[߷:"e?Od?-$bIJkf ŏ+$YbFK.1=%n[ZwoYUH7ݱ&{AWQ̼t%WiÆ nkK?\u=Wf/w? L<ڟXKֶ/ͱ8,QteOOםj @ @ @ho细R"ҘWMwĖ+}[r /|OUyȏ_zR>Dǐ;=P@=w4U>(S1ձ(Gg+st?h9@R>սus՟{ҩ~+9 @ @ @Nue^tOP@›}o4 ^ycrZ}=/1־fZȟG]cTk\.d~!>w_~ƀUP<ŗ57ƃ>pͱ MsGẄo#6nҭ‡LOU*9O{oQ @ @ @~XJx7,hha)"X|"V_X}MŎoFUiC _禮-UGbmcc{`+Gl$^h{黚c폫|>JX yS٭Xp42ű4%~2Cmĝ7Xx:mqtW @ @ @.0|mϴjߘ5k7l`wtgߒm |̻PN/ݰ<%w2dkX[x~h{ߴ>uO)[yܸcwwO DEs}S.lv{\=kxgoQi~v=tdڸqV6{oݙ>/r瞾w/D*#}ޕhD&=lZ.j淭UtFhԦX0@(}r#Bgz7ϥ;xc,X`'3@K'҉y_X%xTD^} @ @ @ .0gIl}fJp\h+u=)]?䦴yǦ*ݴ2nLXV'c=E7~aqlBZeYlhr9{zsc3+=5'}pOSl?L^wO]b+) ^/<soZs9sM-O*~+|o |]S|>Y=_g} y;[ߩچt:s"qSOWܷG @ @ @iڈ\7~gaJouip*w-ۺX/|me=XVjh^xCߔ'%-x[̨0X091^y:rskjkSjGM-KbͳigE5}/ŹϯioW~>U bErzxQW!1X.]А{[i|mS3o\q}ZwGijscMz?W߽X~Ud{kaq%q{OF>(֍_8*S gK;nBөҦ[i  @ @ @85j*M}_Ŕߚ1Аט\]8.H5lӺєlHI!Wbv}{pvsݸ U2eqNmeDwߓ$55wԡHAީ!M@ο.=|F|OZ @ @ @@`*ʥ4}(!@ @ @ @g@MSٟ7o @ @ @ @L?> @ @ @ @Y#pF$K @ @ @ @(p$ϏQ= @ @ @ @$]]]z$Zsza @ @ @ pRNoJ~RaUN @ @ @gT @ @ @8{$g @ @ @ @ ?. @ @ @ @+ 1>[wF @ @ @gt @ @ @^ٺ3 @ @ @8$π  @ @ @ p H̟֝ @ @ @ @ 1<] @ @ @ @W@b} @ @ @3! @ @ @gug @ @ @ pH̟A @ @ @ @?{;#@ @ @ @3@@b x@ @ @ @gl @ @ @gC @ @ @8{$g @ @ @ @ ?. @ @ @ @+ 1>[wF @ @ @gaԨQ1v=zP @ @ @ @Bرcqȑ8~g٤q$M  @ @ @Tl<;6hHy @ @ @ux#Au~; @ @ @dH|1?v% t|\~nq>8ԑ=3gјt?v񝷯' O(L{W1+xB~m'G;xt[g*:'&.9k OV)8zl}ty/Hcsߋy#xvqGok8>6 @ @ @ 0|S4EvmΊGzٓ^xh僕*n_wOR>[9x]d6?D|(OKo^\듸=ToR>pjgFßcGNʓ\xU3 @ @ @_S[sqUѸ+:wkZdy ޾}@6[֪wⷒogĆb]zͮxp+⭍3KQ޵ rKŻ֓-&^jp׊8^X~v_g/ƕ (K |O>-Va{8+x 3FYG+[ Xj6~Pt1ys=ehYoyŤ|~3]swQ>?[wRL|W*K{t[Wbrޣc|T7g?0[I1y~z#@ @ @ @L$ Yv$Sr4<4Ҹw;'._1̎N# {Fo4vŖvsDc1yI#,D]ؙMKV޺jRġ}<6J9ńe y-o]+'V>ToطX8w&<_8p.x'K?obx[OW7H}qbCg~IHl5%q֚qOљ?O>(s5NB?p켷ڇZ>]m/IzWĿt<Z)|7{ύϬ8EL.'?3+[Vro  @ @ @@I*{Ho(~R޻B u&Ņ% )IZo.xqgS%||=$?">EMN}kcޏǗ~8^177{VJ#sỸ{?QLj_4Og8lhw3c|*K?WJtn٤|SHQmy6Λ.;GcGgv"h(D׸|eK\sLt--z{o6'58Py)~~}řq_d"'NkɫHN0tso-& I3@ijw.O'oh7ū/=?OIhGv( -v-%KFWJk;'jV+:G @ @ @41?pW_=ɯ^O|2MhTsDlKj|]+ u˾X%kTK)񭔘?s9r>Dt>;$㪸=Z)ή^?ۯҔw璺ωv'矌;Ŵ*S7oWPOS:9M”Z;p*P{0vN156Lxa/7.>s0=ޑuO}.榌npY?$f xo) s-,G-CxƮ gm;76d /Z.»Si @ @ @Q']G*4zz%Į&w{&6L/MnZ<>eYoƱO?X:xˎϔ'U49-!?6>-PH..Ɨā_HԾoK ~鉒|M-'ΒJQ}1[Hj"~kb3Y<ݻi!w԰od-_-1n/ƞWar5b$)`Tü|mC8{ @ @ @x=8\r`h9ȁ|jgKϗ@S!}~ Ӎl=0=7DmI>1iz}ہ ##E> ?8IN7WYOY]:oZq{4VbTCKYB}VwߜfT{xX]! @ @ @8IC$$jB4e|09b͗ @Am};^*TV|6  ;4PX.97U+}څwb]ǗϏ _U>[?4^Mts̯=v8b kpD @ @ @I>I ڱesblgb/Ʈ3}?($;ZqA~Fuw $󇙨>]2D{Y?xJI\2I=h{wrlK>bxN_> @ @ @ pFDbndWJ9#姱9wO`桯gɖ-y1x.51!GQyA4\lu %f{/Yw}8p|oJ?"8Zz02Ϩ NcwE @ @ @d |B}]RH}`N\r{}/wow>@l޺_^$^)p&N=m_vD'!US?g{MQ?ŠQ#_=1hBxusqxYX {'y _i9C`T{9#@ @ @ @t :!?z4~?+:)-%\k{1~Mcg\RLo}.qhPߤ|S𛦐/$ͻNA3+JFo\h <󷋧6>/ueEo-vyc*'~#bmfot YW{Ie @ @ @FHםcsVfcqqO>x}Ċt3d}xxiSFx$ѵ.ys+]8=.+|bce}]u_٩AO.Kwҙ fsuoCK voɈ[>'~b?wC{ @ @ @$$ړ/|EaɗCM1eyBޝ#7DOߙ{ON/ߧ@u?|.~5SqIC%?~C|d=/ @ @ @#K M=l4xӋMǾkeiLJ6L|*ѻEϊwšޘqYѹ4^zJw6>?_qdϮ881'f+gVyǛZ,L-uu<4'1$;K@oϵD4uύw|/~_t_C|'MAJ1QJwqHPoص>GE1~ܫ'3'9W7ol=-qo2}2O'hV?_}Wk1.gb)`ۣaeј2.Q%ŏS%nz[󣘳3QZo(7=ŘyiLnPxL^ܷXxV_ٸbv&{GoZ\axeްxy1y1 %%4ܣƔC]W|Go,M}vҳX9qz9\Jj:'ZүkCy%v  @ @ @8uaGֵ:{_ibrQx=3Ǧsj7{uG;'c)[TIgń.~<~B N"9ǒs-_1~›cʢR;OncNjb2ʖ]~?'?z̸4qnPC @ @ @8thbqx#G8 @ @ @8#l<;6f( d2!@ @ @ @J1_k @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q`LeᙃݝWq8uꣾ>Nшz\?QW\NS =b:3:3aJ4xE{ @ @ @HfvH^<6ndK]71ujtu׿H}am墳ĊO7ŀEd:be-1ղ{ĭV߳ŽggW]4pwL @ @ @\3;6ƍScՒYXuhnlGt|'?<.R=)֧cGZ⢆?@FŔ)s)i @ @ @>b~Ƹ7N|k4_63pcCcզްώ'F[sCI9CU|BWƭ[̜:%Fċo[3 >bU-'*^hN ]9;ov򕙜%@ @ @ @X7u74F[.tǚxS LOGmr.:eDST{vk5mղݱqIvԼmS#;!41}i  @ @ @ @l]_HMcIl M׷Ǧ㨳XW{w_͹e_{||6>ZZ+[r)!@ @ @ @*07B'ݽpHӕ7ݰ,tψlf?WGGBw2}0byiAzݽ{{t'jƤ)]Pmyo8㢾>#r~OHu(}ġ4TȒM1ۣgSti_޻'2p6ϡwO"][ɨ;SsW{Bm:oʔogDÐASݱ}[gܕ#ۧŴgƌPx?z}P @ @ @N )lWR"zwK׬y Cޘqc5*%ccg6wgW]4H??7E>%֥$-ӹ9~|aZjĂWw,JwygϭH>_[_OcI=U+_7b7Ax}ؐ((0!%Շ5{I92֘l[lWNJ)z/zpSzcMuse b?Zszw:?3#VAK|n[k>('=w|myܾ25V>xƴ*?>% @ @ @ @d >їcK6o$yL*v/RgdhIȍG+˓M-iucH+m5Ko׸*>ו?]ksoR>E4NG]}Qee!潿5ZUF (h雔[@&8g&团:XYCm1؞H_qOIʷ=v\R>[ؓ_J'  @ @ @ p]C/6ήm4|߄ҽ98/Qke:b-lUr[c>(%Iy[LfSrg*c cA4@XSg(^xly[*NUߩo%i,Ͷh+6}aI45 5[u]vun*-{c[)S6dG.Ne_\i^ /VB /'_k?JS8ݝfLXfLXgD~~!d?(Kiy(K @ @ @7 0ʛ..9tCv(\vFbR>{.rWb۪N%b^ƌ|} }݌\xk޽szvăzro~bG!=)M>OV;#]~[i)b%G]vܽFWrC[bӟ. T=$XWφOs[>DZ?G/>_Z`Ql\ |6q^{k,+/^Y|lUz(%c#Ku @ @ @ pjJךfN^H,lGr僚7ĢQo]~ru޻})o_ekpVqօZ_;;HZy3kM̍6DkkNk:Fi"ڮ1ڿ3~P\Ȧee!ϝiڲh$l?!@ @ @ @@5%k~y_C-#n\t_U]}a 0_}SV -߿Z&y.GXpWt>!VޑrIXӱ(A(uƴU?Z5+;-H#ڦ qu4ĒkŒŢ;i+9E @ @ @$pr<_ '"=%&Dr}H RI(VP"z0X`EMR  &@ T d _0{왽疙m8ξZg'^kՠ*ꭘs]諽rƛle{/лĄkͽٷa&}n/?txC0w n)X6NpOe}^*sxiVp'nÿrg0A݊vrM2Fx\`!G),w/uzl{){4LOGI5s{VN~f:eKM|t\(@ P(@ P(@ P@z@K:z"Kt:"ɼu)놧# ˭sg/+fߚ}Qυޑi+oHt_MAc22@ ̇U2o8(@ P(@ P(@ P@ 4ev^ ~a >SJԁC{grg>{@]g?wXM axY(/W| X9 4}-<sKD0>!bşи,[ۂ^~3#g^j^a8`&`[ѷ]YHh]?I*&^Z;p=m P(@ P(@ P(@ P+p?37-'!i.~J\Y[-S[ȃΝՈ! x)2|9v6|eB_3[a:Z5z{/1=(@ P(@ P(@ P(@#0WeHЯٿ}Iݶxr\;wS^%wk~Mmߙ$Koec~||;ymX!ԕens^6n .O#XS2̾Li6y ,ʒCoy nZf>!#U܆n<$VwYz9깓Qv}p!UݞtSَ|~fIP~كH"棠D Q{*4}COeD}6#MoO ? +P-r'[W2fPO?{sr{~v+VgQrضE(PgG)@ P(@ P(@ P(@ 4I<$=Sm{>E~8WwND }_w\Gڌe2ՎM犔9 v>2)K¤2| b6;3~ۅ4w'Q__s헅xjX,ҟV7Y"0- w# ~+~Y7gVszq` G^"[%>n~s~4 Jye(/^Cw錌W "I(gNt+W_9Hp¬e[FCf(@ P(@ P(@ PhmC灳h=f=P^WOw݄RF(SIl[7H4^ 8?uU۳./LJCk?wcij e=2;}za&mq[zqҽmCH|ֳǑvTb!?r3ӑ鬿kaFMR0hr(@ P(@ P(@ P@S1o?K^5>;JRg}}q -ݎ>uq~r-؎Kk ramcj/|t#pY#<&aGຫ~_2t:i.[ #| [Pw ۿn-O_DDklqo*Q5mko[6cݏlwB/k5wOaJTẇ&~2? P(@ P(@ P(@ P-%pɓ'7es2|x#ѹ3GJ VW!1||BtDϪ G^}l;;CDuq m~NsN)RξKe(~[9RF#eftҾP' szVJ[z6!k6T@ pLXGP^)Ⱥnz*G`& =֣dsP$`gKGq(v(<u(@ P(@ P(Zgst,ɀ u $ʃ?hL3VZ6R.0N{6ycK.>_V;.c}xlK'$q)q-ytyng ]gOgk+È_9}V5Rs4a&^-<\rKq 0fOݖ^ʻG>|f'1 hP+WNT,<݌xn} ye܍ݟėھw$Upݳ"mT "/wՒ`ڵu3](PV:j6:]b 8O}i5wvXdݎC!W =-,T&"`*؃J҃h؃9m'j}O2Lx?y9 F1 'L}Cˣ'>^ٚB;CP_'cx_LUثvFA?X`"&mR<+bjk 7~KK0|I 0 X:p>imJ3?(@ P(@ P(T'2 O'M[mۄ`PUuG;ޱ[[?z~N#*q<2MGcYY+Ƃb-@ :o^o0 :o7tt6޳1qqUf?j. 11rEF$tֈݬy,QwއEb͙4 w8{_XOo_|ьVzvͻj/ԓ(@ P(@ P@ 4f?_`6XPg+Qoc VD,`3, 7ǤԎI[H:*Y/ :[䤣TǼj .5$AGDE5pʺ{{b}Vyx 9O+זB," q~Ie[{,u a rw XXs9^H=Z/Y;!={'[_g6*~Ի o>^J}A &ʫA@W\ԩFrO\%1۹ZJl+cx 0'ş@[^ Bkw ȡ*ᤄ$yS0^ÿ CdE}B=16;W}eMO&?{qJ:kc%9sH={I8i쟜Cs_n3sk)ؾ% CΧXt?o ,$Jej P(@ P(@ P 4O>0,عi>*ڊf?v@2|n)=?U5&!g"KYVf8_JTuU(6͙g0Ϟ}P[f۩-ʱ^SQQerVV ('`H ~0UN5GasYH.k*ϫf[i\0In_3֡0Q<(q-dbIF#zH䜕b5(>ogܐxpTkFV^kUӞ^)"FC0K}\%wkvV+6fUQ䙩,U J4uҀGⷓch_(G uOOo]KtMys{yn bG k5%!(r5Qcft<|S3wBAcet2"Qvp(b^waX/Y~M@'׭\٭+ UW1*7nq 0'a@%bȢ|s+d#Ic׳Tk{b,|c![]gGd%l$tk:nk*Wvtlxd{Dv>R/QuB&,SmFXQ?>F3\D9`>1؃ioi[)ozf7wvw]Q oo9WӤ5S10-YV=kSq<=IǞ7rQ󪦾[[E/k6Qtf!EsxST`wz,V~un.kјrńҿmņ(\űϨh˭ {tR#>,~$sJ2C:K(@ P(@ PBC%Ѷ2_QC}TVU102'Jl=<3{%9"Czbkxխ*&#b|l%fjvVTċ]w;-bRV*87UM *q>(Cb*+{b`L?HIxШܾum?K$^bl\rŮuxtW1%+k׾[9!9l`MMڹZU: 7P[y@GQ|Y~M_"G^<jU *b:]7܏iYȚ Ur0/wiMshtw sf{kku[nt3~p v ;8 yf3ѿKLۃTfyUm }aqco=<(rN^m.+xőTYDo/A v=; UO{ y฼6@ v~-G\+u(XfBV+K[{]2"ePG's%ZǚW';|{=b,ǩjY5]hW$0.P-xT"4KAy%IQv:2zCd/9qPYWנ>yވk(>%͇e0^HEѣE2/@9"w~D P(@ P(@ P@# &'AyyZ"aFt>VJ[P^[Tsvf)?Ϡ2K{P^~kPYYjǚ`z8 hנ3_%{M2,?oMݪ{PX7EPz᩾L݋6@&ShԎoAyMK}IjY`w-Dz۹.Ac:lxQ"rp$ Y/?]އǞgk#הU7y9XOiR9=9i4Zf ؒ,r4^YVx ;'ߩ@CFdh2lZV=CP>,=GQ}?QNuMkPޙysډ!]oӅXjK|\RuՍ~|_kև= .wd+ڼv?ܾtW մ'k#q&u"v0wez@ P(@ P(@ P% y9=g7f.23"6U6F؂={u5u02^3Ad] vܼ҇ 2RѯW,"d ;7úP[;ünjĒG}P]1,+%wiAQ~rN~~QbG"{ˋx*MG~d,\.,[>>RYP4dD)-gb=<^ʱM;;IWG/ܢ:{|URS{18! \ՙ*_rfMUXP̽Y ֽRggbrJ=BXak)F3ŀ%)QCwVpF?M<a.Aj5>8~&ϭ s&|E|goFo{1hj­֝QpWo'1֠<*KEyzFHivʜjfޥH`>=0&0P(nrS <{T [>!ǡ3QMOōC%0fy |O$P#4Zo5bj 3=?USТ}'b J`QӮZ**@ *ǾSQxzNsWbCmU.Be'ʖAf :>)=e뻄_?CSc1ѱ=$czڍ2rAigqg(@ P(@ P@ Cm]:T Y#Xo0D :&jG +CkodeW!*,Ifm̿6Qшyɕ׽uD/ju=b8JeK`^ǖs&#C}QF+罂#b-[SbXW}L^F9=ʭΎxY1DD S+ [b,<(lLJ@^]"b6y6^,.)[Ҽw^&=*3_}[)49\%nDZ{P^)KzT9; Ԩ6P#} qwgu9@k]$z&!ˆ2G:wJ֝w.ʽ\]lqse%qyUX$pۥ

ď;1w5&M %@IDAT?1v7CWlt ߧA<(@ P(@ P%$:Ck8usRWfHƒw97Sz~ob;+6 d6:G!|&(6'Iꆼ߶[ ʫU蛆pd+6r ԼnĎ@.2k4y TDܘyv^G4h]c6(7cdњG`h?_u=Q٣KPkxG*t[]߹ ]TzJ.x f}rKP޽@=g>:z!3-ȃ %r=zEr]#^嵥{+Gk.Qw>N] ոQFk]<8XoOAyk݌cj&y_4F@X] i>n3ߊʚFü'|U[| NՠI;{ڑY8^Rf!k~>QKhdh'z-3AsFy!g]߂^8ss(@ P(@ PB&0cy:8#XCR_ݢ;)~_SY;}b l^¤F<|pW>n}8B4tQZ]d#%p#53}=9ӡĬk@13tj_nV*g9pt,E u_ьFWRl2y ~|gOy]ptMi 7{4"n}fh˴l#,{5F 4@@ R(կ:t\>}ǁtͺ#/ g9ڵ6`Pwl 2)zM ~v䱮b1s&"Si~7Ɋ sN)@ Q\\[\uuպow2", sd2LafK}x l]p$`d$m7~Ma P(@ P(@ P>M,92DdYΙ^A^X3C>^Ό-5 W(p].<}F`;e~}TԏCSt5g E{ۧuJc|72:1ERW&Ǽ(RgB&;0*G%׷:u5 >JnՌ=X@2gޒC1=|ߔ{KP/%x)8b1sˡ|Dqk\8^ʩp?ה@Y=><^J. fL Ŗ9F+]ן>֓/]?/d.Px]NqNӠolɨ 7[I2[_K2ȫl]ݥMYMܿm+%/ݵGwl8_S 4|[A;5䚣`Tfd)%2l? TaC P(@ P(@ @%8̪͝mm["G5' EmK9O;5kZ(@u!}r{5'x0W_f{yW'`+~J;f+98XT}ZnGa! (|J]W+| ?lae~tb6Ri_%?mcmKzE<vs6_3DdXÛGNYghYc@;U}*z_T AwK8z?Wӵx%XzR,h bZ>$ʦ+N|y[wVN[7~!ukz~8jKKKQ5P(@ P(@ P-$pe TqcYH9K~sp{Az"R:6K;ՠ5=ӻz{ӆCn~V?{ ^ nd8S]79a+z5xWW tqܶRmRB+(TLVhz46]cFMGX$fݐ#uR۴QnZ6w[T|i^5ҥZ2ZG]5f.ԗF4cI .z1nΏ 8{N܌ Wy˱oGq]a\(@ P(@ Pڽ@ӉI0S.b' eXJ aªE#w0؏.[nTjMqqor;2_%#W[ W ǥ Tonƶ?^~2C/H\utNVjpdÇjG ĘCmW2DX;ҸGcXt5Q{{FD" gs}ŮoϢ/®r ѠȞɺ.ނf$<jGQ3{K TpOת3YXΩ[2UCS qٻ)%H^alb᳘t~ssL5p(P {񦿗n^xΪmqyZuI_ | 4Tk?7u~Ln쓎}ŷݡG=*_mcFNg? fwUZ<(@ P(@ P(u5nDD#ΙXL](m)"MyBYۚ4 ݍ`a J y&ۧ)暄4啠@͡iҵꕺޭ;Q q ]e^s;T֌("wylAHeyc_2싲4TcmQ I zJ=ݼ-ꑀᣆ[_q1;N?Ҩ a٢-%Zú<=k?=!E0b{yΌcRѯn/yYcO P(@ P(@ k סx:*E:xVRS uٴm)Vrk:O#5ԡtkq%,Pl9T`}ƽ0i# bkR:6y"c/M,ǔ4 _]^ `&"{:#1}Df$f=5*URhV;ek{Ƹ#{~ba/Q~~)s>96co]6|Z-ovJAfYGWt6}Ww֠1c3z [G\ئI_3U-m>#X9=g=& h}Xٙcooi!r(@ P(@ P@.aFӷLx#o놮Q2-F@=#T3Hʋy+5\GDH'*t!kY-c1ũ(ɔZ $E{|4`)[QkFG\3_谹#1Ҹ[Izm>uRvlL^ u.bØ,|8+I GqmI{y#"9# :vy)qԶ{g3]f1ҽ>aC9cpq ^] ׌G'(3M5Kva"S^R~GTz[ H4Wcg$Ha},A\emt\?eDu}6=aJ>(,-Lo].意ĭDۘ nu1S᧽;F6@t\${4n\A8|]"*ғѠ.Z}w"o|5zc1==}qBudyUyAy| > Jԏ2Вu[*#!er?տS2QtZF#٬ܳ֍r4 I*v>rvd7,,Q=|:ؐձrstb;ɿ7\i[xڋoTw5F}4Li+5םno3~szrQv ˁkRLPR(@ P(@ P^ DzZ$(Zye|ܔgꆳWxAd&oN )=+</Ò1䚭lRlDxe*,MKAKSí37ۊxi}4AoggJuWF'ȯ }]'i^JޭOe`|HZO٧I䌜8H?~8BGCK;g]zK-%Aѓ9/f_F̣H5t; cWzcK>(wsiǩZ{G/偍p޽~cqgkU9.y{L$nzI܂r0nlǯ$f!芰m(tMP^v?`K8{g-s`~u>(/bz LҔUm_;!s,|MZꯄ] pB\wQ7ByOw>(̛=r7:>ץV}ᬿL_Yt r,H'Āw_zxB*Vy,,u[H(@ P(@ P(@1 cts0HoG0W/$+Gw{IcfbGKc3-ܖL}d=6iCwIJ50Ay4w;3lzY#ևaTL&?p{ S)@״?>N9j1 g2 ;ӹmg-{7 ʈ蓆ш!#ƽv{ 3i)oKiX"l褽~ϥKۥSmRKޱkDDC Dp)K.A.|ݿہOz 0ߘu1j3.! 7΁uh}5~c]C{(&ԗ!axv+,},0WvDdp7s47 :w| Rկ)ȃ+?nF챚.2ay;p׋m~1+<Q7`cA󐑧vv׷wKF[:'6KbCWoeo/])$zyWzEv:Gkn#&<.e#~`9$I}bapMy0&ϘWbӪ%;؟i}f1vwKUE"6͹Sz˛f}`1j?1%>$ٜv\(@ P(@ PtNmPBN *(.8y5ۢW lvj)[y WPrݔ,*F^IGڳ5^Ȍ`POEJ9+[Z7 wf}5Q]%ߵB~0Rkݎt/Bc_:j5?}Ȧ;t0>4;ꏀI3Y,{@/g}PC78. P(@ P(@ P Bc(aU(@ PtyHM?:'I1]D\@P%+gά ^-/rVk(@ P(@ P@``]_^6(t^B$M9+ӑDU ,i)jP%~hf:&ĩ+C?L*'Y>R(@ P(@ PR olTO <(@27UιB2ז㥻_? SR_{c}3fDShW2<@fWM6^](@ P(@ P*11l7(@ P c?U)kfˏU~(1I[irb +5M/ij_/P?q(@ P(@ P(p 00_v߂ l (@KD fv..?FBёd.kCսfM(xuRk#{kAN P(@ P(@v#ͥdC(@ P$`6Fc+@`Z=Cm(@ P(@ P@ p(8(@ P] 0(߮.g{k 튲=(@ P(@ Phʾ}\G(@ P(@ P(@ PBT0(@ P(@ P(@ P(@ l(@ \N24Z[Py휑ш2[lr/lO-r)@ P(@ P(@ P@`y(p UCI%TvPU?_Ŝs9Z4,)Tf8r%{(\ 6(@ P(@ P@``}__@h\<ũ69 iXG9ŌD!c/c1Io@=+@Ns5K;=K$WёVjJ=}ƍS^jWe`H]wd|LLFiLO}9H s[z+_<B0`->l8nIPws;< P(@ P(@ P腻䪭;q6+L?S6"_OL[b:s5@@? ; (Yyf]n嘪]QZjV~~1JfzolBiLQ\F蟬9',>!\iFr(@ P(@ P@`vwICA 5cudnja+'12]Ghϟ)m?ectGC8~ 0YVEȟ-Wv[0mߥYtPk6Zg* !MP2}|Oa(:%rAkyz6|2L 6bq}w`d={,*ŗ{cuCP,4& C4"ڲCHឃVdp6\Њ/5~/J+ց(@ P(@ P(@& 00TBm&XM ֪S[Զx˯QeD+;ranFtnlH;>*l=.M-K)G{w unNb-F\*-{X1Sk{?>S-N=}v^ a{.e5yQN[neiw[DƑXf.bGA9`_>qRFdwՀhLw~]~, /-7;އa]uxcwlW(@ P(@ P(p *$Vz}PQ蟌 w"tL jӨgak|14Acf`31v0xBq O(yuz°kՊ(f=eJ@x9#\Ue/gaA:G-Oב$+4籥>~lGMq!%`g\U˰,;Oo/g.{3,C{]4u4Blbѕ%stEkի^Z~Ҍ{㥥Y٬Ӑ^p=ic=V`FfmHI/oz ssvS{-BknM/L8(?qbogTK?Y3zw*u ; /?SMkgX~LF8K.'6/vfE,@?o߉J9铤̵6 Ɂv@nv?6>WJ0`)@F89r]@6d&AM}lsW.;E-)8+@P U*`/)؁x,,<2$8 P"pfkH ̜{99 9wzcם|'u{>s?<wܻyםꉏ'/r%ͽq}fO듑 7߈w/ڝiLfӅM7Şs˾9cߜ?[1m2 @ @ @D`˗UXfM÷ԱџtGm3.DةȖ/MɫFNFˆ2%ܶOm;W\r$Ƣs:sqU~c}ؕԳ3]Oy]1ֳb`]Uw}/%mXvEvU0 hCQb˘OEKޅhq߁d|boh.Yvm7qz%I?:y)S_,xF{-y}S͎ qxVO4t? P~Pڪ}1]sSn}uyOդND{`DHTj2c(3bO%kjnx<س){X53\6J=qG?_doךkOtǏ qwf8^C*&nx{lVΗޙZW=x<6B*cλ>Jy~q~[sOA @ @9 q$g㤖7/{bG M^~t-O-SUѮ;7OhHNJ}Ks6k<SZ)9ͱ땁)'v-ړ_щ 軨]w5MP>߈#/{Vm_;-ՃF?#U^w\&(oֻ-s _noMlXӟ_wOնCo%ʧ+'RA֗ZgXok!v PI~wً|R|SĻIO׃uw/}>hlYv6tgz!ڞޟ}#c//Fbp/7P>@1ym7cI`HgHˇ7K2.fS]qaxl}#oFq3zAL2xp0Gbdx0 ϧZo4l`Rnlܹb\KNzrܭ$ٝL^Xs )Kڗϔþ:7 Q'\ΒvgR3'bR̨FNGރ}78bS݅,ko&k$M6&/S&Wzb]w:Tb8?r-zWWwAC?ឪi??ꍖmſϞ #7k߃}a*.~jW~f$KG".P?O1֖;sf3|σ'::?W18zh|Ol^v8 [>T>ܛ,mTDS2 /AssQ,FG$ojذ-Nylhj!֯MXMIqˢGS{[oXllCa_tp'gT˳o&˳z3ı]i=[&m_JǙ+*꺭֍x3 vr}-ݩ2{J 8O^_9߄sߦ;NtE˺L4M?Gg_ ۏHAsRLSxzg4ɺj޿ڗ)QN~5O=~aBr3ov[fߛ-q}LSBs<55u1}>K>/_Gə﫣1}O.ݼɻߓti;׭+6{ϝM3~߻'o<'n;moٚ7훣cS\OpBZr?+ZQamQ @ @n]3jI~'\TSo{+DVw}xk5yg2i|vKfμxmWjsljÑjJǶ9=Px-*J<⓶VJ_3 q>og=P\|2M_ ۗL[\HvX^_?J?ܞr0ڎxpJ=SfyHAH]Bs?4>8ܓT4"k]ggAa!ߺǝ'3ոi5̱xn n{jCr.@T{6Ǧ(F @ @( նP[.tБb\ҳhVNI978>a/T;}Y+ŷ4Dj,NݔP>Ma˙OSOӮ-{$@0m!o(Ӑʰ}K= Ẫ;㵉173wu-hvfAWRYm{'f?|ft%X;m,sdqj+)̦{I=PpR0S8jjwY.h=Ga4> wƧ>8H|$~IE>UF wfcuM96GޱCWNG @ @fsCG'1,I\~Yb$?jpa4z{j.J*d؝M7т;&ҽG|zqpaZ,lX-uJg,9y Cc4݂dpG/F|xv~r sPe/MTxglX{ۍ;KnlĻ8q|Lc0R?O~LR[|65d=QMK%@ @ @ X~: pO9z}tJnIJymz.ƣ EZ<%pJj4N<9zXC1م7ҽ:x]gu[kL4~pNB-=U~{k3q ܸ:wH>ju*sgJRۋ\~2Vo_?HL1V+mnq[lؔ~яc_&6?c߼Ss[Q1-\AY`V27)VH @ @ @lk-<=OnT>&3G#[1</'S,7)9{lؐ/ӣ/LY|u(|icWڃӡCqgf`Wo}h‘Z>_4a|8kcݍGν%.?(=HGMɥo|/o+ѕfͱdG"| @ @ @` O7 [t)b[1T ΍Eǣ-/J*l}1hsGqx{T4Mmh!ܣŽ/<*ڞff~¤>-z Qub5P\Toܸ#5P\Du%Kf}8M^0:::2S2WnՀVN:u&S)={l{%'?Gۣ;ܥ kc_l2sDsPNs2E>O8܏E܏IV8 sbk{m/)t39~޻K;\؟/OO BW%O$0@*OcoǝoM>2˱G?o2zM| @ @Mu׌-슎ޏd[t?4uSK9^,5:?:{#=IIdiw*:™H2;#[w[u`z&-nbest c4~ EuMYtݎg4ŞEvyPלN۳It1~nd)m}Oղh{p:oݏWߡnPFݏ?F,޿H6O6s2+ۘZ^<v Roҋ{G{gx@*P?a݃q[d$2qB>/lOGOc!~=ۮ|#5{)=3q7n^$@ @ @iY8nt9]-\2n"@7yB Y~/"kK]{opd+,pOGt~4mJ]Ҟ.D{wEs \Ė'NwEgIz^+vUv5dص6y(G/:Ti#y`։əWݟ̴|r,iWbZ]eNP /̮ZgەҼ{j0Yxb햒Pmr_I~,gT-p8lol}23w%+r;fGS<Չ߉CqGqg[pocqW㝱a]!̛1~BKt~U][ @ @T`˗U˼f͚joc7-K"|8 fVHB%w%~\ƒi!zfIfol0bUJ7$dPͬ;O2IC3_U/)3v%q9)e gu[s:Ϣ{t~5o_ZS5gP$?@oSCSy]Ᾰg[Qc .@?[M^ZoXM3y]^ūs}:[Ģp;?;;Џ:c헒WW|/N=VSt NO o @ @,)0?'6 @`qFH} gz/ L~.y0ga>y7Ag|8~2}_س/nqs^~y>/ KV_  @ @ Pk`RC);ɢVɬu2qۓ˽H~N$x/뻅 @ @T@`NF @g[K)#qM\Ǔޖ,fjѶg' "I @ @} @@#hbwW3(j2I*ⓤ+AyýE~->]h]1Ψ?+ Hm&Xrbo @ @XHK/  @& dbwT3%S LaP>Eo:(p à|~X7 :# Ĩ`= @ @3KـhTX߾p#87`4%\i//Y{zZŽqm|hI?qL;2 @ @ @f Xf @撅3S멒 @ @ PF`KٛcU.cIPiKLLs  @ @ 4,e4M  @ @ @ @`/L @ @ @XKsܴ @ @ @($@ @ @ @) 04ǭb~x(VX11tbֹ:-cǃ;bG-[=J @ @ @ @Xqkf͚j'cůž8;[o^I`SF%-Nϫq  @ @ @ @`TsbB=?싩{TL&ڒ$@ @ @ @RR%Y9{7T{ @ @ @ X%ٰ3|pMO,3_"@ @ @ @ cp lɸ'1z5 0Wjڪ& @ @ @ qW7DC-5_-Zʪ @ @ @[@@u)_/?3>w98F鯎F[=qs;'Ѳ=gr[ @ @ @N\&?W-?rFϝl{{.W> @ @ @$ 0_If):6m+3ᡷmS @ @ @yV9M =FGbddo,F~fdЏz$(ɸ02׮]kLs] @ @ @ܲO}oWfeWI Y{j_QýަBs5E-"y @ @ @@ -,CT(7@  @ @ @ p gdj΍͖zxi @ @ @ @}}{SggJO$@ @ @ @ ky:H䮦6%  @ @ @ @`^[:YYi @ @ @! 0?Rtdt+f{"@ @ @ @<灷l{Ru)q8- @ @ @" 0_V9vǙGS۩ՁsGj$ @ @ @L' 0?-paC6~>bOL:G1I @ @ @ @wWr1n$Ğ>>Ni{vmNsƾ/}Sy$  @ @ @ @`F3bL#V|jE_I5wWxH'3}ݏ;essk9Wt @ @ @P@`~P-CC3oaLֆ{ƙg[}-#qÒ @ @ @ 0E@`~ X[y*o~"c]qbۺX߼ybw~ @ @ @TXqkUǚ5kvl9 $hhh445Efr> @ @ @ @`3Ϝ)ܚ!\ 뚮o  @ @ @$`)9)D @ @ @f& 0?3' @ @ @ @Ħ @ @ @̜"@ @ @ @sB @ @ @ @`f3s @ @ @I@`~Nl  @ @ @ @ I. @ @ @ 0'9)D @ @ @f& 0?3' @ @ @ @Ħ @ @ @ʙelrgmq\ՙ)sW.F) @ @ @ @r_n#Z'}#+'`Pn @ @ @,QK/сf_8 @ @ @zD&v:cHr,|6RC @ @ @ X~o3$/]~g6'5 @ @ @/`c=$@ @ @ @(`MįS_MZx5wrdV&sEY @ @ @X~f/1m&-;;@JypGt̒8IcŧVĊUbկ%w~{ǃɑj]A3 Wؑ϶ow`&@ @ @ @@"תIYan@''b3?cNBѷE]/HB'ZVEԮjM\"*e~+6~ۓ6Tox$}ޖoũN$Ƣs8c]'vFC @ @ @ p ϩfω|%oJᵮ:i>=@Td6}:c݋2sqw&S>>%wb_?P&(ݭRh|Ģ6  @ @ @X&Kr ɽ'ϟ.Iᣩ};c۔)hX_<}126~y-ŵ8s`$y-߯|qغ[-jڵdߧ]N菶 3y]@Q6 @ @ @ p]@`~^ V<>lwT4HQGu˳Iک؟nh7fbCcEeG,|+'Ճ=6D4}5N +P;po! @ @ @ O5Y{Vo=Ozn_|ڞLFG&"Gۦ)§r&GGjgO`jΓW/F#oOںd칧EӶ8lja=1A< @ @ @m_=V8Ao;Wb[4 _(Q=U.g#±S2~=;.6%  @ @ @ 0ChgxFj&ٴ#&uިɮD6֧FS/hOJ'sLd .DZ&].}ݱ~KC^-3~e&r?+i.WZC\ @ @ @] ]s;DHt댽˳n2F֦*'2mONW%<4E=+S}#.( @ @ @,e_Ne k޾3s83ܹIKW.۠|edt`f.+efϥ"e @ @ @̘_}ط;荎ښ316xՈL8m5 @ @ @:A]2ሣn뉮"}0^xPS˳\i?`ɻ .J*y>;gs92Z>V>({53~FuƉ7XxzK&@ @ @ @@kNz*\>ڿZ8Ѷظeb}{h63HQ`?*ힼPkOڟ' 9\tٶhmʁű]V!E @ @ @ ߰.j>QsK6YGTph}PhKVLd37ZhX'e4v{4=[ؑ<,ƒ¾I? IrC1Ͻ1^  @ @ @J`˗U+f͚j\HSgwc\52?[cWIн% mGӋwqrwsΡؼI`k􍝌lI[Gy!hqM-Ѻv0OW>˱Ƣs%%5_y=⥾=J*n- @ @ @:hP&Zx6x٧; s5DNjOkǧ[:{7elWO>펆{:kwIs=EѮ.'JML`K-?8̡8r3+'r&@ @ @ @gU׹~5-%{*lkk}qxwl{ۃq/ƾ'S旛D> X[l&v8g^_m-O<燯ϭܥBUeJlWޮ@K@T~f1IC @ @ @X~.j˽L.CF!yz)VI&m`ŪX: I3'u&%K**6Au¦ @ @ @_`K ^  @ @ @ p 50o)[E @ @ @y7ޙ  @ @ @ @Y  @ @ @ @ <{g&@ @ @ @[@@`d]$@ @ @ @' 0읙 @ @ @n[`u @ @ @nͳwf @ @ @oAE @ @ @y7ޙ  @ @ @ @Xy q]?>gժhX>5̻^ @ @ @ @=SsSGOu}1- @ @ @H X>1іm}?M)y  @ @ @ @Xqkf͚joc3?r1ϥ}#"k|E @ @ @N`xxxN}[]%[_:'<-A @ @ @O`yKZ>r i @ @ @ P/X"-/N~?'9$ @ @ @ @ % 0˜M2i,dRHJ @ @ @ @|ZC @ @ @X@`Ơ#@ @ @ @i4 @ @ @|-@Wעu @ @ @ @rJ6 @ @ @ @,-UHQ$ @ @ @xd6F g7ehԊ8Cq⭡9֬ @ @ @,'/_VCk֬v?޿X8y{ @ @ @ @`I ϩfωFK]\{  @ @ @ @XyK:wn#:t*Z룡!VM$@ @ @ @[U@`~#{TJ2= @ @ @ @ Rs .Jn?) @ @ @mP`BZ @ @ @SsN^sI  @ @ @ @` /= @ @ @7  @ @ @ @` /= @ @ @7  @ @ @ @` /= @ @ @s JnhLaK @ @ @L OR"q?>3YydZ @ @ @3H~kstn{mjK @ @ @VR^wF6~Fo4M @ @ @̘Ʌpu4U.(gL!@ @ @ @)`}$+-Ehh $@ @ @ @R/_V3f͚4 @ @ @%oKωM! @ @ @ 039E @ @ @$ 0?'6 @ @ @ @g$ @ @ @9ZB-ZkGg"S?-\^ @ @ @@݄szUc%m @ @ @ @!PKǾͱݑ[ZI @ @ @&0Y5NfzW]2C8hQm @ @ @ @ ת5t͚5/رw/c=;#`gT1 @ @ @,0<<\`#u3cMtěO̜/d @ @ @Թ@n[w7.z|C @ @ @@]#*8JK @ @ @ @!P߁a @ @ @ @|E @ @ @ @o @ @ @ PQ` #VV @ @ @ @.;05mv<ޖ&@ @ @ @/Pׁgё2lP vH @ @ @ @:|dcK7B󛲑=WĎGšo#?8[缚G @ @ @˗/_f͚j7]9~lg~,z:"3 @ @ @,j.]g&;7-A @ @ @R`e]*ըkcsOwŞXߐU͖/H @ @ @ @@ w`>/we$56ͱvejS @ @ @,:G 7F|C @ @ @@]s ol] {G)E @ @ @@AϽ"V5GvSDRl-;m @ @ @ @SĶR  @ @ @ @ O0#x*.\h @ @ @,)/_Vk֬v1 @ @ @ pK ϩu;c~NQ @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K@`~y @ @ @ @@ ـh @ @ @,/5zC @ @ @u& 0_g9 @ @ @ x  @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K@`~y @ @ @ @@ ـh @ @ @,/5zC @ @ @u& 0_g9 @ @ @ x  @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K@`~y @ @ @ @@ ـh @ @ @,/5zC @ @ @u& 0_g9 @ @ @ x  @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K`t. @ @ @ @@3+M @ @ @j! 0_ Eu @ @ @ @ `&@ @ @ @: @ @ @ @@ 0v @ @ @ @ZBQ @ @ @ @|  @ @ @ @@-k @ @ @ PA@` @ @ @ @PT @ @ @ 0_n @ @ @ P Z( @ @ @Tc7 @ @ @|-A @ @ @*W @ @ @BI[<^ IENDB`graphql-ruby-2.2.17/guides/subscriptions/subscription_classes.md000066400000000000000000000255511476434635200252200ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Subscription Classes desc: Subscription resolvers for pushing updates to clients index: 1 --- You can extend {{ "GraphQL::Schema::Subscription" | api_doc }} to create fields that can be subscribed to. These classes support several behaviors: - [Authorizing](#check-permissions-with-authorized) (or rejecting) initial subscription requests and subsequent updates - Returning values for [initial subscription requests](#initial-subscription-with-subscribe) - [Unsubscribing](#terminating-the-subscription-with-unsubscribe) from the server - Implicitly [scoping updates](#scope), to direct data to the right subscriber - [Skipping updates](#subsequent-updates-with-update) for certain clients (eg, don't send updates to the person who triggered the event) Continue reading to set up subscription classes. ## Add a base class First, add a base class for your application. You can hook up your base classes there: ```ruby # app/graphql/subscriptions/base_subscription.rb class Subscriptions::BaseSubscription < GraphQL::Schema::Subscription # Hook up base classes object_class Types::BaseObject field_class Types::BaseField argument_class Types::BaseArgument end ``` (This base class is a lot like the {% internal_link "mutation base class", "/mutations/mutation_classes" %}. They're both subclasses of {{ "GraphQL::Schema::Resolver" | api_doc }}.) ## Extend the base class and hook it up Define a class for each subscribable event in your system. For example, if you run a chat room, you might publish events whenever messages are posted in a room: ```ruby # app/graphql/subscriptions/message_was_posted.rb class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription end ``` Then, hook up the new class to the {% internal_link "Subscription root type", "subscriptions/subscription_type" %} with the `subscription:` option: ```ruby class Types::SubscriptionType < Types::BaseObject field :message_was_posted, subscription: Subscriptions::MessageWasPosted end ``` Now, it will be accessible as: ```graphql subscription { messageWasPosted(roomId: "abcd") { # ... } } ``` ## Arguments Subscription fields take {% internal_link "arguments", "/fields/arguments" %} just like normal fields. They also accept a {% internal_link "`loads:` option", "/mutations/mutation_classes#auto-loading-arguments" %} just like mutations. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription # `room_id` loads a `room` argument :room_id, ID, loads: Types::RoomType # It's passed to other methods as `room` def subscribe(room:) # ... end def update(room:) # ... end end ``` This can be invoked as ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { # ... } } ``` If the ID doesn't find an object, then the subscription will be unsubscribed (with `#unsubscribe`, see below). ## Fields Like mutations, you can use a generated return type for subscriptions. When you add `field(...)`s to a subscription, they'll be added to the subscription's generated return type. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription field :room, Types::RoomType, null: false field :message, Types::MessageType, null: false end ``` will generate: ```graphql type MessageWasPostedPayload { room: Room! message: Message! } ``` Which you can use in queries like: ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { room { name } message { author { handle } body postedAt } } } ``` If you remove `null: false`, then you can return different data in the initial subscription and the subsequent updates. (See lifecycle methods below.) Instead of a generated type, you can provide an already-configured type with `payload_type`: ```ruby # Just return a message payload_type Types::MessageType ``` (In that case, don't return a hash from `#subscribe` or `#update`, return a `message` object instead.) ## Scope Usually, GraphQL-Ruby uses explicitly-passed arguments to determine when a {% internal_link "trigger", "subscriptions/triggers" %} applies to an active subscription. But, you can use `subscription_scope` to configure _implicit_ conditions on updates. When `subscription_scope` is configured, only triggers with a matching `scope:` value will cause clients to receive updates. `subscription_scope` accepts a symbol and the given symbol will be looked up in `context` to find a scope value. For example, this subscription will use `context[:current_organization_id]` as a scope: ```ruby class Subscriptions::EmployeeHired < Subscriptions::BaseSubscription # ... subscription_scope :current_organization_id end ``` Clients subscribe _without_ any arguments: ```graphql subscription { employeeHired { hireDate employee { name department } } } ``` But `.trigger`s are routed using `scope:`. So, if the subscriber's context includes `current_organization_id: 100`, then the trigger must include the same `scope:` value: ```ruby MyAppSchema.subscriptions.trigger( # Field name :employee_hired, # Arguments {}, # Object { hire_date: Time.now, employee: new_employee }, # This corresponds to `context[:current_organization_id]` # in the original subscription: scope: 100 ) ``` Scope is also used for determining whether subscribers can receive the same {% internal_link "broadcast", "subscriptions/implementation#broadcast" %}. ## Check Permissions with #authorized? Suppose a client is subscribing to messages in a chat room: ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { message { author { handle } body postedAt } } } ``` You can implement `#authorized?` to check that the user has permission to subscribe to these arguments (and receive updates for these arguments), for example: ```ruby def authorized?(room:) context[:viewer].can_read_messages?(room) end ``` The method may return `false` or raise a `GraphQL::ExecutionError` to halt execution. This method is called _before_ `#subscribe` and `#update`, described below. This way, if a user's permissions have changed since they subscribed, they won't receive updates unauthorized updates. Also, if this method fails before calling `#update`, then the client will be automatically unsubscribed (with `#unsubscribe`). ## Initial Subscription with #subscribe `def subscribe(**args)` is called when a client _first_ sends a `subscription { ... }` request. In this method, you can do a few things: - Raise `GraphQL::ExecutionError` to halt and return an error - Return a value to give the client an initial response - Return `:no_response` to skip the initial response - Return `super` to fall back to the default behavior (which is `:no_response`). You can define this method to add initial responses or perform other logic before subscribing. ### Adding an Initial Response By default, GraphQL-Ruby returns _nothing_ (`:no_response`) on an initial subscription. But, you may choose to override this and return a value in `def subscribe`. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription # ... field :room, Types::RoomType def subscribe(room:) # authorize, etc ... # Return the room in the initial response { room: room } end end ``` Now, a client can get some initial data with: ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { room { name messages(last: 40) { # ... } } } } ``` ## Subsequent Updates with #update After a client has registered a subscription, the application may trigger subscription updates with `MySchema.subscriptions.trigger(...)` (see the {% internal_link "Triggers guide", "/subscriptions/triggers" %} for more). Then, `def update` will be called for each client's subscription. In this method you can: - Unsubscribe the client with `unsubscribe` - Return a value with `super` (which returns `object`) or by returning a different value. - Return `NO_UPDATE` to skip this update ### Skipping subscription updates Perhaps you don't want to send updates to a certain subscriber. For example, if someone leaves a comment, you might want to push the new comment to _other_ subscribers, but not the commenter, who already has that comment data. You can accomplish this by returning `NO_UPDATE`. ```ruby class Subscriptions::CommentWasAdded < Subscriptions::BaseSubscription def update(post_id:) comment = object # # if comment.author == context[:viewer] NO_UPDATE else # Continue updating this client, since it's not the commenter super end end end ``` ### Returning a different object for subscription updates By default, whatever object you pass to `.trigger(event_name, args, object)` will be used for responding to subscription fields. But, you can return a different object from `#update` to override this: ```ruby field :queue, Types::QueueType, null: false # eg, `MySchema.subscriptions.trigger("queueWasUpdated", {name: "low-priority"}, :low_priority)` def update(name:) # Make a Queue object which _represents_ the queue with this name queue = JobQueue.new(name) # This object was passed to `.trigger`, but we're ignoring it: object # => :low_priority # return the queue instead: { queue: queue } end ``` ## Terminating the subscription with #unsubscribe Within a subscription method, you may call `unsubscribe` to terminate the client's subscription, for example: ```ruby def update(room:) if room.archived? # Don't let anyone subscribe to messages on an archived room unsubscribe else super end end ``` `#unsubscribe` has the following effects: - The subscription is unregistered from the backend (this is backend-specific) - The client is told to unsubscribe (this is transport-specific) Arguments with `loads:` configurations will call `unsubscribe` if they are `required: true` (which is the default) and their ID doesn't return a value. (It's assumed that the subscribed object was deleted.) You can provide a final update value with `unsubscribe` by passing a value to the method: ```ruby def update(room:) if room.archived? # Don't let anyone subscribe to messages on an archived room unsubscribe({message: "This room has been archived"}) else super end end ``` ## Extras Subscription methods can access query-related metadata by configuring `extras [...]` in the class definition. For example, to use a `lookahead` and the `ast_node`: ```ruby class Subscriptions::JobFinished < GraphQL::Schema::Subscription # ... extras [:lookahead, :ast_node] def subscribe(lookahead:, ast_node:) # ... end def update(lookahead:, ast_node:) # ... end end ``` See the {% internal_link "Extra Field Metadata", "/fields/introduction#extra-field-metadata" %} for more information about available metadata. graphql-ruby-2.2.17/guides/subscriptions/subscription_type.md000066400000000000000000000030451476434635200245360ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Subscription Type desc: The root type for subscriptions index: 1 --- `Subscription` is the entry point for all subscriptions in a GraphQL system. Each field corresponds to an event which may be subscribed to: ```graphql type Subscription { # Triggered whenever a post is added postWasPublished: Post # Triggered whenever a comment is added; # to watch a certain post, provide a `postId` commentWasPublished(postId: ID): Comment } ``` This type is the root for `subscription` operations, for example: ```graphql subscription { postWasPublished { # This data will be delivered whenever `postWasPublished` # is triggered by the server: title author { name } } } ``` To add subscriptions to your system, define an `ObjectType` named `Subscription`: ```ruby # app/graphql/types/subscription_type.rb class Types::SubscriptionType < GraphQL::Schema::Object field :post_was_published, subscription: Subscriptions::PostWasPublished # ... end ``` Then, add it as the subscription root with `subscription(...)`: ```ruby # app/graphql/my_schema.rb class MySchema < GraphQL::Schema query(Types::QueryType) # ... # Add Subscription to subscription(Types::SubscriptionType) end ``` See {% internal_link "Implementing Subscriptions","subscriptions/implementation" %} for more about actually delivering updates. See {% internal_link "Subscription Classes", "subscriptions/subscription_classes" %} for more about implementing subscription root fields. graphql-ruby-2.2.17/guides/subscriptions/triggers.md000066400000000000000000000054311476434635200226000ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Triggers desc: Sending updates from your application to GraphQL index: 2 --- From your application, you can push updates to GraphQL clients with `.trigger`. Events are triggered _by name_, and the name must match fields on your {% internal_link "Subscription Type","subscriptions/subscription_type" %} ```ruby # Update the system with the new blog post: MySchema.subscriptions.trigger(:post_added, {}, new_post) ``` The arguments are: - `name`, which corresponds to the field on subscription type - `arguments`, which corresponds to the arguments on subscription type (for example, if you subscribe to comments on a certain post, the arguments would be `{post_id: comment.post_id}`.) - `object`, which will be the root object of the subscription update - `scope:` (shown below) for implicitly scoping the clients who will receive updates. ## Scope To send updates to _certain clients only_, you can use `scope:` to narrow the trigger's reach. Scopes are based on query context: a value in `context:` is used as the scope; an equivalent value must be passed with `.trigger(... scope:)` to update that client. (The value is serialized with {{ "GraphQL::Subscriptions::Serialize" | api_doc }}) To specify that a topic is scoped, add a `subscription_scope` option to the Subscription class: ```ruby class Subscriptions::CommentAdded < Subscription::BaseSubscription description "A comment was added to one of the viewer's posts" # For a given viewer, this will be triggered # whenever one of their posts gets a new comment subscription_scope :current_user_id # ... end ``` (Read more in the {% internal_link "Subscription Classes guide", "subscriptions/subscription_classes#scope" %}.) Then, subscription operations should have a `context: { current_user_id: ... }` value, for example: ```ruby # current_user_id will be the scope for some subscriptions: MySchema.execute(query_string, context: { current_user_id: current_user.id }) ``` Finally, when events happen in your app, you should provide the scoping value as `scope:`, for example: ```ruby # A new comment is added comment = post.comments.create!(attrs) # notify the author author_id = post.author.id MySchema.subscriptions.trigger(:comment_added, {}, comment, scope: author_id) ``` Since this trigger has a `scope:`, only subscribers with a matching scope value will be updated. ## Validation By default, subscriptions are re-validated when a trigger causes them to send updates. To disable this, you can pass `validate_update: false` when hooking up subscriptions to your schema. For example: ```ruby use SomeSubscriptions, validate_update: false ``` If you're sure you won't be releasing breaking changes to your schema, this setting can reduce overhead in evaluating updates. graphql-ruby-2.2.17/guides/testing/000077500000000000000000000000001476434635200171735ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/testing/helpers.md000066400000000000000000000050661476434635200211660ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Helpers desc: Running GraphQL fields in isolation index: 3 --- GraphQL-Ruby ships with a test helper method, `run_graphql_field`, that can execute a GraphQL field in isolation. It use it in your test suite, include a module, with your schema class: ```ruby # Mix in `run_graphql_field(...)` to run on `MySchema` include GraphQL::Testing::Helpers.for(MySchema) ``` Then, you can run fields using {{ "Testing::Helpers#run_graphql_field" | api_doc }}: ```ruby post = Post.first graphql_post_title = run_graphql_field("Post.title", post) assert_equal "100 Great Ideas", graphql_post_title ``` `run_graphql_field` accepts two required arguments: - Field _path_, in `Type.field` format - Runtime object: some non-`nil` object to resolve the field on. Additionally, it accepts some keyword arguments: - `arguments:`, GraphQL arguments to the field, in Ruby-style (underscore, symbol) or GraphQL-style (camel-case, string) - `context:`, the GraphQL context to use for this query `run_graphql_field` performs several GraphQL-related steps: - Checks `.visible?` on the named Object Type, raising an error if it isn't visible - Wraps the given runtime object in the GraphQL Object Type - Checks `.authorized?` on the type, calling {{ "Schema.unauthorized_object" | api_doc }} if authorization fails - Prepares arguments for field resolution - Checks `#visible?` on the field, raising an error if the field isn't visible - Checks `#authorized?` on the field, calling {{ "Schema.unauthorized_field" | api_doc }} if it fails - Calls any {% internal_link "field extensions", "/type_definitions/field_extensions" %} - Runs {% internal_link "Dataloader", "/dataloader/overview" %} and/or GraphQL-Batch, as needed ## Resolving fields on the same object You can use {{ "Testing::Helpers#with_resolution_context" | api_doc }} to use the same type, runtime object, and GraphQL context for multiple field resolutions. For example: ```ruby # Assuming `include GraphQL::Testing::Helpers.for(MySchema)` # was used above ... with_resolution_context(type: "Post", object: example_post, context: { current_user: author }) do |rc| assert_equal "100 Great Ideas", rc.run_graphql_field("title") assert_equal true, rc.run_graphql_field("viewerIsAuthor") assert_equal 5, rc.run_graphql_field("commentsCount") # Optionally, pass `arguments:` for the field: assert_equal 9, rc.run_graphql_field("commentsCount", arguments: { include_unmoderated: true }) end ``` The method yields a resolution context (`rc`, above) which responds to `run_graphql_field`. graphql-ruby-2.2.17/guides/testing/integration_tests.md000066400000000000000000000135321476434635200232660ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Integration Tests desc: Run the whole GraphQL stack in tests index: 2 --- Besides testing {% internal_link "schema structure", "/testing/schema_structure" %}, you should also test your GraphQL system's behavior. There are really a few levels to this: - __Application-level__ behaviors, like business logic, permissions, and persistence. These behaviors may be shared by your API and user interface. - __Interface-level__ behaviors, like GraphQL fields, mutations, error scenarios, and HTTP-specific behaviors. These are unique to your GraphQL system. - __Transport-level__ behaviors, like HTTP headers, parameters and status codes ## Testing Application-Level Behaviors When it comes to _how your application behaves_, you should lean on _unit tests_ which exercise application primitives directly. For example, if postings require a title and a body, you should write a test for `Post` which asserts that invalid `Post`s fail to save. Several other components of the application may be tested this way: - Permissions: test your authorization system using example resources and actors. Dedicated, high-level frameworks like [Pundit](https://github.com/varvet/pundit) make it easy to test authorization in isolation. - Business logic: What are the _operations_ that a user can perform in your system? For example, on a blog, they might be: drafting and publishing posts, moderating comments, filtering posts by category, or blocking users. Test these operations in isolation so you can be confident that the core code is correct. - Persistence (and other external services): how does your app interact with the "outside world", like databases, files, and third-party APIs? These interactions also deserve specific tests. By testing these (and other) application-level behaviors _without_ GraphQL, you can reduce the overhead of your test suite and simplify your testing scenarios. ## Testing Interface-Level Behaviors After building your application, you give it an interface so that people (or other software) can interact with it. Sometimes the interface is a website, other times it's a GraphQL API. The interface has transport-specific primitives that map to your application's primitives. For example, a React app might have components that correspond to `Post`, `Comment`, and a `Moderation` operation. (These components might even be context-specific, like `ThreadComment` or `DraftPost`.) Similarly, a GraphQL interface has types and fields that correspond to the underlying application primitives (like `Post` and `Comment` types, a `Post.isDraft` field, or a `ModerateComment` mutation). The best way to test a GraphQL interface is with _integration tests_ which run the whole GraphQL system (using `MySchema.execute(...)`). By using an integration test, you can be sure that all of GraphQL-Ruby's internal systems are engaged (validation, analysis, authorization, data loading, response type-checking, etc.). A basic integration test might look like: ```ruby it "loads posts by ID" do query_string = <<-GRAPHQL query($id: ID!){ node(id: $id) { ... on Post { title id isDraft comments(first: 5) { nodes { body } } } } } GRAPHQL post = create(:post_with_comments, title: "My Cool Thoughts") post_id = MySchema.id_from_object(post, Types::Post, {}) result = MySchema.execute(query_string, variables: { id: post_id }) post_result = result["data"]["node"] # Make sure the query worked assert_equal post_id, post_result["id"] assert_equal "My Cool Thoughts", post_result["title"] end ``` To make sure that different parts of the system are properly engaged, you can add integration tests for specific scenarios, too. For example, you could add a test to make sure that data is hidden from some users: ```ruby it "doesn't show draft posts to anyone except their author" do author = create(:user) non_author = create(:non_user) draft_post = create(:post, draft: true, author: author) query_string = <<-GRAPHQL query($id: ID!) { node(id: $id) { ... on Post { isDraft } } } GRAPHQL post_id = MySchema.id_from_object(post, Types::Post, {}) # Authors can see their drafts: author_result = MySchema.execute(query_string, context: { viewer: author }, variables: { id: post_id }) assert_equal true, author_result["data"]["node"]["isDraft"] # Other users can't see others' drafts non_author_result = MySchema.execute(query_string, context: { viewer: non_author }, variables: { id: post_id }) assert_nil author_result["data"]["node"] end ``` This test engages the underlying authorization and business logic, and provides a sanity check at the GraphQL interface layer. ## Testing Transport-Level Behaviors GraphQL is usually served over HTTP. You probably want tests that make sure that HTTP inputs are correctly prepared for GraphQL. For example, you might test that: - POST data is correctly turned into query variables - Authentication headers are used to load a `context[:viewer]` In Rails, you might use a [functional test](https://guides.rubyonrails.org/testing.html#functional-tests-for-your-controllers) for this, for example: ```ruby it "loads user token into the viewer" do query_string = "{ viewer { username } }" post graphql_path, params: { query: query_string } json_response = JSON.parse(@response.body) assert_nil json_response["data"]["viewer"], "Unauthenticated requests have no viewer" # This time, add some authentication to the HTTP request user = create(:user) post graphql_path, params: { query: query_string }, headers: { "Authorization" => "Bearer #{user.auth_token}" } json_response = JSON.parse(@response.body) assert_equal user.username, json_response["data"]["viewer"]["username"], "Authenticated requests load the viewer" end ``` graphql-ruby-2.2.17/guides/testing/overview.md000066400000000000000000000015231476434635200213640ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Overview desc: Testing a GraphQL system index: 0 redirect_from: - /schema/testing --- So, you've spiked a GraphQL API, and now you're ready to tighten things up and add some proper tests. These guides will help you think about how to ensure stability and compatibility for your GraphQL system. - {% internal_link "Structure testing", "/testing/schema_structure" %} verifies that schema changes are backwards-compatible. This way, you don't break existing clients. - {% internal_link "Integration testing", "/testing/integration_tests" %} exercises the various behaviors of the GraphQL system, making sure that it returns the right data to the right clients. - {% internal_link "Testing helpers", "/testing/helpers" %} for running GraphQL fields without writing a whole query graphql-ruby-2.2.17/guides/testing/profiling.md000066400000000000000000000104121476434635200215040ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Profiling desc: Profiling the performance of GraphQL-Ruby index: 4 --- If you want to know more about how time is spent during GraphQL queries, including GraphQL-Ruby internals, you can use Ruby profiling tools to take a closer look. If you want to investigate GraphQL-Ruby performance together, prepare a runtime profile and memory profile as described below and {% open_an_issue "Performance investigation" %} on GitHub, including those files. ## StackProf [StackProf](https://github.com/tmm1/stackprof) is a Ruby library for figuring out where an operation's time is spent. To capture a profile, surround a block with `StackProf.run { ... }`. ```ruby require "stackprof" # Prepare any GraphQL-related data or context: query_string = "{ someGraphQL ... }" context = { ... } # This will dump a profile in `tmp/graphql-prof.dump` StackProf.run(mode: :wall, interval: 10, out: "tmp/graphql-prof.dump") do # Execute the query inside the block: MySchema.execute(query_string, context: context) end ``` The `out:` option tells StackProf to create a "dump" at the given location. Then, anyone who has that file can investigate the profile using the `stackprof` command, for example: ``` $ stackprof tmp/graphql-prof.dump ================================== Mode: wall(1) Samples: 2492 (58.06% miss rate) GC: 0 (0.00%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 902 (36.2%) 94 (3.8%) GraphQL::Execution::Interpreter::Runtime#evaluate_selection_with_resolved_keyword_args 1283 (51.5%) 87 (3.5%) GraphQL::Execution::Interpreter::Runtime#continue_field 274 (11.0%) 78 (3.1%) GraphQL::Schema::Field#resolve 1068 (42.9%) 73 (2.9%) GraphQL::Execution::Interpreter::Runtime#evaluate_selection # ... ``` Additionally, `stackprof` accepts a `--method` argument which provides details about the performance and usage of a specific method, for example: ``` $ stackprof tmp/small.dump --method #gather_selections GraphQL::Execution::Interpreter::Runtime#gather_selections (/Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/interpreter/runtime.rb:305) samples: 17 self (0.7%) / 17 total (0.7%) callers: 16 ( 94.1%) GraphQL::Execution::Interpreter::Runtime#continue_field 6 ( 35.3%) Array#each 1 ( 5.9%) GraphQL::Execution::Interpreter::Runtime#run_eager callees (0 total): 6 ( Inf%) Array#each code: 1 (0.0%) / 1 (0.0%) | 305 | when :lookahead 6 (0.2%) / 6 (0.2%) | 306 | if !field_ast_nodes 3 (0.1%) / 3 (0.1%) | 307 | field_ast_nodes = [ast_node] | 308 | end ``` Anyone with the `.dump` file can perform this analysis -- it's a really useful file! If you want to investigate GraphQL-Ruby performance together, please share a runtime profile. ## MemoryProfiler [MemoryProfiler](https://github.com/SamSaffron/memory_profiler) provides insight into where an operation interacts with system memory and the Ruby heap. This is helpful because memory usage problems cause code to run slowly; fixing them can make code run fast. To produce a report, wrap a block in `MemoryProfiler.report { ... }` and then call `.pretty_print` on the result. For example, to create a report on a GraphQL query: ```ruby require 'memory_profiler' # Prepare any GraphQL-related data or context: query_string = "{ someGraphQL ... }" context = { ... } report = MemoryProfiler.report do # Execute the query inside the block: MySchema.execute(query_string, context: context) end # Write the result to a file report.pretty_print(to_file: "tmp/graphql-memory.txt") ``` The report will include many interesting sections including: - Total memory and objects allocated - Objects allocated by location and by class - String allocations, including the number of times a string with the same value was allocated All of these can indicate "hot spots" in the code and inform refactors to reduce memory use. In turn, this reduces time spent in Ruby GC. If you want to investigate GraphQL-Ruby performance together, please share a memory profile. graphql-ruby-2.2.17/guides/testing/schema_structure.md000066400000000000000000000053121476434635200230760ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Schema Structure desc: Make sure that your schema changes are backwards-compatible index: 1 --- Structural changes to a GraphQL schema come in two categories: - __Breaking__ changes may cause previously-valid queries to become invalid. For example, if you remove the `title` field, anyone who tries to query that field will get a validation error instead of response data. - __Non-breaking__ changes _add_ options to a schema without breaking previously-valid queries. Making a _breaking_ change can be bad news for your API clients, since their applications may break. But, sometimes they're required. _Non-breaking_ changes don't affect existing queries, since they just _add_ new parts to the schema. Here are few tips for managing schema structure changes. ## Maintain a `.graphql` schema dump Make structure changes part of the normal code review process by adding a `schema.graphql` artifact to your project. This way, any changes to schema structure will show up clearly in a pull request as a diff to that file. You can read about this approach in ["Tracking Schema Changes with GraphQL-Ruby"](https://rmosolgo.github.io/ruby/graphql/2017/03/16/tracking-schema-changes-with-graphql-ruby) or the built-in {{ "GraphQL::RakeTask" | api_doc }} for generating schema dumps. ## Automatically check for breaking changes You can use [GraphQL::SchemaComparator](https://github.com/xuorig/graphql-schema_comparator) to check for breaking changes during development or CI. If you maintain a dump of queries that typically run against your server, you may also utilize `GraphQL::StaticValidation` to validate these queries directly. A Rake task such as the one below can be used to identify changes that are incompatible with existing queries. ```ruby namespace :graphql do namespace :queries do desc 'Validates GraphQL queries against the current schema' task validate: [:environment] do queries_file = 'test/fixtures/files/queries.json' queries = Oj.load(File.read(queries_file)) Validate.run_validate(queries, MySchema) end module Validate def self.run_validate(queries, schema) puts '⏳ Validating queries...' puts "\n" results = queries.map { |query| schema.validate(query) } errors = results.flatten if errors.empty? puts '✅ All queries are valid' else print_errors(errors) end end def self.print_errors(errors) puts 'Detected the following errors:' puts "\n" errors.each do |error| path = error.path.join(', ') puts "❌ #{path}: #{error.message}" end end end end end ``` graphql-ruby-2.2.17/guides/type_definitions/000077500000000000000000000000001476434635200210725ustar00rootroot00000000000000graphql-ruby-2.2.17/guides/type_definitions/directives.md000066400000000000000000000113221476434635200235540ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Directives desc: Special instructions for the GraphQL runtime index: 10 --- Directives are system-defined keywords with two kinds of uses: - [runtime directives](#runtime-directives) _modify execution_, so that when they are present, the GraphQL runtime does something different; - [schema directives](#schema-directives) _annotate schema definitions_, indicating different configurations or metadata about schemas and types. ## Runtime Directives Runtime directives are server-defined keywords that modify GraphQL execution. All GraphQL systems have at least _two_ directives, `@skip` and `@include`. For example: ```ruby query ProfileView($renderingDetailedProfile: Boolean!){ viewer { handle # These fields will be included only if the check passes: ... @include(if: $renderingDetailedProfile) { location homepageUrl } } } ``` Here's how the two built-in directives work: - `@skip(if: ...)` skips the selection if the `if: ...` value is truthy ({{ "GraphQL::Schema::Directive::Skip" | api_doc }}) - `@include(if: ...)` includes the selection if the `if: ...` value is truthy ({{ "GraphQL::Schema::Directive::Include" | api_doc }}) ### Custom Runtime Directives Custom directives extend {{ "GraphQL::Schema::Directive" | api_doc }}: ```ruby # app/graphql/directives/my_directive.rb class Directives::MyDirective < GraphQL::Schema::Directive description "A nice runtime customization" location FIELD end ``` Then, they're hooked up to the schema using `directive(...)`: ```ruby class MySchema < GraphQL::Schema # Attach the custom directive to the schema directive(Directives::MyDirective) end ``` And you can reference them in the query with `@myDirective(...)`: ```ruby query { field @myDirective { id } } ``` {{ "GraphQL::Schema::Directive::Feature" | api_doc }} and {{ "GraphQL::Schema::Directive::Transform" | api_doc }} are included in the library as examples. ### Runtime hooks Directive classes may implement the following class methods to interact with the runtime: - `def self.include?(obj, args, ctx)`: If this hook returns `false`, the nodes flagged by this directive will be skipped at runtime. - `def self.resolve(obj, args, ctx)`: Wraps the resolution of flagged nodes. Resolution is passed as a __block__, so `yield` will continue resolution. Looking for a runtime hook that isn't listed here? {% open_an_issue "New directive hook: @something", " " %} to start the conversation! ## Schema Directives Schema directives are used in GraphQL's interface definition language (IDL). For example, `@deprecated` is built in to GraphQL-Ruby: ```ruby type User { firstName @deprecated(reason: "Use `name` instead") lastName @deprecated(reason: "Use `name` instead") name } ``` In the schema definition, directives express metadata about types, fields, and arguments. ### Custom Schema Directives To make a custom schema directive, extend {{ "GraphQL::Schema::Directive" | api_doc }}: ```ruby # app/graphql/directives/permission.rb class Directives::Permission < GraphQL::Schema::Directive argument :level, String locations FIELD_DEFINITION, OBJECT end ``` Then, attach it to parts of your schema with `directive(...)`: ```ruby class Types::JobPosting < Types::BaseObject directive Directives::Permission, level: "manager" end ``` Arguments and fields also accept a `directives:` keyword: ```ruby field :salary, Integer, null: false, directives: { Directives::Permission => { level: "manager" } } ``` After that: - the configured object's `.directives` method will return an array containing an instance of the specified directive - IDL dumps (from {{ "Schema.to_definition" | api_doc }}) will include the configured directives Similarly, {{ "Schema.from_definition" | api_doc }} parses directives from IDL strings. For a couple of built-in examples, check out: - {{ "GraphQL::Schema::Directive::Deprecated" | api_doc }} which implements `deprecation_reason` (via {{ "GraphQL::Schema::Member::HasDeprecationReason" | api_doc}}) - {{ "GraphQL::Schema::Directive::Flagged" | api_doc }}, which is an example of using schema directives to implement {% internal_link "visibility", "/authorization/visibility" %} ## Custom Name By default, the directive's name is taken from the class name. You can override this with `graphql_name`, for example: ```ruby class Directives::IsPrivate < GraphQL::Schema::Directive graphql_name "someOtherName" end ``` ## Arguments Like fields, directives may have {% internal_link "arguments", "/fields/arguments" %} : ```ruby argument :if, Boolean, description: "Skips the selection if this condition is true" ``` graphql-ruby-2.2.17/guides/type_definitions/enums.md000066400000000000000000000043301476434635200225430ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Enums desc: Enums are sets of discrete values index: 2 --- Enum types are sets of discrete values. An enum field must return one of the possible values of the enum. In the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL), enums are described like this: ```ruby enum MediaCategory { AUDIO IMAGE TEXT VIDEO } ``` So, a `MediaCategory` value is one of: `AUDIO`, `IMAGE`, `TEXT`, or `VIDEO`. This is similar to [ActiveRecord enums](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html). In a GraphQL query, enums are written as identifiers (not strings), for example: ```ruby search(term: "puppies", mediaType: IMAGE) { ... } ``` (Notice that `IMAGE` doesn't have quotes.) But, when GraphQL responses or variables are transported using JSON, enum values are expressed as strings, for example: ```ruby # in a graphql controller: params["variables"] # { "mediaType" => "IMAGE" } ``` ## Defining Enum Types In your application, enums extend {{ "GraphQL::Schema::Enum" | api_doc }} and define values with the `value(...)` method: ```ruby # First, a base class # app/graphql/types/base_enum.rb class Types::BaseEnum < GraphQL::Schema::Enum end # app/graphql/types/media_category.rb class Types::MediaCategory < Types::BaseEnum value "AUDIO", "An audio file, such as music or spoken word" value "IMAGE", "A still image, such as a photo or graphic" value "TEXT", "Written words" value "VIDEO", "Motion picture, may have audio" end ``` Each value may have: - A description (as the second argument or `description:` keyword) - A deprecation reason (as `deprecation_reason:`), marking this value as deprecated - A corresponding Ruby value (as `value:`), see below By default, Ruby strings correspond to GraphQL enum values. But, you can provide `value:` options to specify a different mapping. For example, if you use symbols instead of strings, you can say: ```ruby value "AUDIO", value: :audio ``` Then, GraphQL inputs of `AUDIO` will be converted to `:audio` and Ruby values of `:audio` will be converted to `"AUDIO"` in GraphQL responses. Enum classes are never instantiated and their methods are never called. graphql-ruby-2.2.17/guides/type_definitions/extensions.md000066400000000000000000000135521476434635200236210ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Extending the GraphQL-Ruby Type Definition System desc: Adding metadata and custom helpers to the DSL index: 8 redirect_from: - /schema/extending_the_dsl/ --- While integrating GraphQL into your app, you can customize the definition DSL. For example, you might: - Assign "area of responsibility" to different types and fields - DRY up shared logic between types and fields - Attach metadata for use during authorization This guide describes various options for extending the class-based definition API. Keep in mind that these approaches may change as the API matures. If you're having trouble, consider opening an issue on GitHub to get help. **Note**: This document describes best practice with GraphQL-Ruby 1.10+. For customizing schemas on older versions, use GitHub to browse older versions of this page. ## Customization Overview In general, the schema definition process goes like this: - The application defines lots of classes for the GraphQL types - Starting from root types (`query`, `mutation`, and `subscription`) and any defined `orphan_types`, the schema discovers all types, fields, arguments, enum values, and directives in the schema - Non-type objects (fields, arguments, enum values) are initialized when they're attached to the classes or instances they belong to. ## Customizing type definitions In your custom classes, you can add class-level instance variables that hold configuration. For example: ```ruby class Types::BaseObject < GraphQL::Schema::Object # Call this method in an Object class to get or set the permission level: def self.required_permission(permission_level = nil) if permission_level.nil? # return the configured value @required_permission else @required_permission = permission_level end end end # Then, in concrete classes class Dossier < BaseObject # The Dossier object type will have `.metadata[:required_permission] # => :admin` required_permission :admin end # Now, the type responds to that method: Dossier.required_permission # => :admin ``` Now, any runtime code which calls `type.required_permission` will get the configured value. ### Customizing fields Fields are generated in a different way. Instead of using classes, they are generated with instances of `GraphQL::Schema::Field` (or a subclass). In short, the definition process works like this: ```ruby # This is what happens under the hood, roughly: # In an object class: field :name, String, null: false # ... # Leads to: field_config = GraphQL::Schema::Field.new(name: :name, type: String, null: false) ``` So, you can customize this process by: - creating a custom class which extends `GraphQL::Schema::Field` - overriding `#initialize` on that class (instance methods) - registering that class as the `field_class` on Objects and Interfaces which should use the customized field. For example, you can create a custom class which accepts a new parameter to `initialize`: ```ruby class Types::BaseField < GraphQL::Schema::Field # Override #initialize to take a new argument: def initialize(*args, required_permission: nil, **kwargs, &block) @required_permission = required_permission # Pass on the default args: super(*args, **kwargs, &block) end attr_reader :required_permission end ``` Then, pass the field class as `field_class(...)` wherever it should be used: ```ruby class Types::BaseObject < GraphQL::Schema::Object # Use this class for defining fields field_class BaseField end # And.... class Types::BaseInterface < GraphQL::Schema::Interface field_class BaseField end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class BaseField end ``` Now, `BaseField.new(*args, &block)` will be used to create `GraphQL::Schema::Field`s on those types. At runtime `field.required_permission` will return the configured value. ### Customizing Connections Connections may be customized in a similar way to Fields. - Create a new class extending 'GraphQL::Types::Relay::BaseConnection' - Assign it to your object/interface type with `connection_type_class(MyCustomConnection)` For example, you can create a custom connection: ```ruby class Types::MyCustomConnection < GraphQL::Types::Relay::BaseConnection # BaseConnection has these nullable configurations # and the nodes field by default, but you can change # these options if you want edges_nullable(true) edge_nullable(true) node_nullable(true) has_nodes_field(true) field :total_count, Integer, null: false def total_count object.items.size end end ``` Then, pass the field class as `connection_type_class(...)` wherever it should be used: ```ruby module Types class Types::BaseObject < GraphQL::Schema::Object # Use this class for defining connections connection_type_class MyCustomConnection end end ``` Now, all type classes that extend `BaseObject` will have a connection_type with the additional field `totalCount`. ### Customizing Edges Edges may be customized in a similar way to Connections. - Create a new class extending 'GraphQL::Types::Relay::BaseEdge' - Assign it to your object/interface type with `edge_type_class(MyCustomEdge)` ### Customizing Arguments Arguments may be customized in a similar way to Fields. - Create a new class extending `GraphQL::Schema::Argument` - Use `argument_class(MyArgClass)` to assign it to your base field class, base resolver class, and base mutation class Then, in your custom argument class, you can use `#initialize(name, type, desc = nil, **kwargs)` to take input from the DSL. ### Customizing Enum Values Enum values may be customized in a similar way to Fields. - Create a new class extending `GraphQL::Schema::EnumValue` - Assign it to your base `Enum` class with `enum_value_class(MyEnumValueClass)` Then, in your custom enum class, you can use `#initialize(name, desc = nil, **kwargs)` to take input from the DSL. graphql-ruby-2.2.17/guides/type_definitions/field_extensions.md000066400000000000000000000143161476434635200247630ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Field Extensions desc: Programmatically modify field configuration and resolution index: 10 --- {{ "GraphQL::Schema::FieldExtension" | api_doc }} provides a way to modify user-defined fields in a programmatic way. For example, Relay connections are implemented as a field extension ({{ "GraphQL::Schema::Field::ConnectionExtension" | api_doc }}). ## Making a new extension Field extensions are subclasses of {{ "GraphQL::Schema::FieldExtension" | api_doc }}: ```ruby class MyExtension < GraphQL::Schema::FieldExtension end ``` ## Using an extension Defined extensions can be added to fields using the `extensions: [...]` option or the `extension(...)` method: ```ruby field :name, String, null: false, extensions: [UpcaseExtension] # or: field :description, String, null: false do extension(UpcaseExtension) end ``` See below for how extensions may modify fields. ## Modifying field configuration When extensions are attached, they are initialized with a `field:` and `options:`. Then, `#apply` is called, when they may extend the field they're attached to. For example: ```ruby class SearchableExtension < GraphQL::Schema::FieldExtension def apply # add an argument to this field: field.argument(:query, String, required: false, description: "A search query") end end ``` This way, an extension can encapsulate a behavior requiring several configuration options. ## Adding default argument configurations Extensions may provide _default_ argument configurations which are applied if the field doesn't define the argument for itself. The configuration is passed to {{ "Schema::FieldExtension.default_argument" | api_doc }}. For example, to define a `:query` argument if the field doesn't already have one: ```ruby class SearchableExtension < GraphQL::Schema::FieldExtension # Any field which uses this extension and _doesn't_ define # its own `:query` argument will get an argument configured with this: default_argument(:query, String, required: false, description: "A search query") end ``` Additionally, extensions may implement `def after_define` which is called _after_ the field's `do .. . end` block. This is helpful when an extension should provide _default_ configurations without overriding anything in the field definition. (When extensions are added by calling `field.extension(...)` on an already-defined field `def after_define` is called immediately.) ## Modifying field execution Extensions have two hooks that wrap field resolution. Since GraphQL-Ruby supports deferred execution, these hooks _might not_ be called back-to-back. First, {{ "GraphQL::Schema::FieldExtension#resolve" | api_doc }} is called. `resolve` should `yield(object, arguments)` to continue execution. If it doesn't `yield`, then the underlying field won't be called. Whatever `#resolve` returns will be used for continuing execution. After resolution and _after_ syncing lazy values (like `Promise`s from `graphql-batch`), {{ "GraphQL::Schema::FieldExtension#after_resolve" | api_doc }} is called. Whatever that method returns will be used as the field's return value. See the linked API docs for the parameters of those methods. ### Execution "memo" One parameter to `after_resolve` deserves special attention: `memo:`. `resolve` _may_ yield a third value. For example: ```ruby def resolve(object:, arguments:, **rest) # yield the current time as `memo` yield(object, arguments, Time.now.to_i) end ``` If a third value is yielded, it will be passed to `after_resolve` as `memo:`, for example: ```ruby def after_resolve(value:, memo:, **rest) puts "Elapsed: #{Time.now.to_i - memo}" # Return the original value value end ``` This allows the `resolve` hook to pass data to `after_resolve`. Instance variables may not be used because, in a given GraphQL query, the same field may be resolved several times concurrently, and that would result in overriding the instance variable in an unpredictable way. (In fact, extensions are frozen to prevent instance variable writes.) ## Extension options The `extension(...)` method takes an optional second argument, for example: ```ruby extension(LimitExtension, limit: 20) ``` In this case, `{limit: 20}` will be passed as `options:` to `#initialize` and `options[:limit]` will be `20`. For example, options can be used for modifying execution: ```ruby def after_resolve(value:, **rest) # Apply the limit from the options, a readable attribute on the class value.limit(options[:limit]) end ``` If you use the `extensions: [...]` option, you can pass options using a hash: ```ruby field :name, String, null: false, extensions: [LimitExtension => { limit: 20 }] ``` ## Using `extras` Extensions can have the same `extras` as fields (see {% internal_link "Extra Field Metadata", "fields/introduction#extra-field-metadata" %}). Add them by calling `extras` in the class definition: ```ruby class MyExtension < GraphQL::Schema::FieldExtension extras [:ast_node, :errors, ...] end ``` Any configured `extras` will be present in the given `arguments`, but removed before the field is resolved. (However, `extras` from _any_ extension will be present in `arguments` for _all_ extensions.) ## Adding an extension by default If you want to apply an extension to _all_ your fields, you can do this in your {% internal_link "BaseField", "/type_definitions/extensions.html#customizing-fields" %}'s `def initialize`, for example: ```ruby class Types::BaseField < GraphQL::Schema::Field def initialize(*args, **kwargs, &block) super # Add this to all fields based on this class: extension(MyDefaultExtension) end end ``` You can also _conditionally_ apply extensions in `def initialize` by adding keywords to the method definition, for example: ```ruby class Types::BaseField < GraphQL::Schema::Field # @param custom_extension [Boolean] if false, `MyCustomExtension` won't be added # @example skipping `MyCustomExtension` # field :no_extension, String, custom_extension: false def initialize(*args, custom_extension: true, **kwargs, &block) super(*args, **kwargs, &block) # Don't apply this extension if the field is configured with `custom_extension: false`: if custom_extension extension(MyCustomExtensions) end end end ``` graphql-ruby-2.2.17/guides/type_definitions/input_objects.md000066400000000000000000000125541476434635200242730ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Input Objects desc: Input objects are sets of key-value pairs which can be used as field arguments. index: 3 --- Input object types are complex inputs for GraphQL operations. They're great for fields that need a lot of structured input, like mutations or search fields. In a GraphQL request, it might look like this: ```ruby mutation { createPost(attributes: { title: "Hello World", fullText: "This is my first post", categories: [GENERAL] }) { # ^ Here is the input object ..................................................... ^ } } ``` Like a Ruby `Hash`, an input object consists of keys and values. Unlike a Hash, its keys and value types must be defined statically, as part of the GraphQL system. For example, here's an input object, expressed in the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```ruby input PostAttributes { title: String! fullText: String! categories: [PostCategory!] } ``` This input object has three possible keys: - `title` is required (denoted by `!`), and must be a `String` - `fullText` is also a required String - `categories` is optional (it doesn't have `!`), and if present, it must be a list of `PostCategory` values. ## Defining Input Object Types Input object types extend {{ "GraphQL::Schema::InputObject" | api_doc }} and define key-value pairs with the `argument(...)` method. For example: ```ruby # app/graphql/types/base_input_object.rb # Add a base class class Types::BaseInputObject < GraphQL::Schema::InputObject end class Types::PostAttributes < Types::BaseInputObject description "Attributes for creating or updating a blog post" argument :title, String, "Header for the post" argument :full_text, String, "Full body of the post" argument :categories, [Types::PostCategory], required: false end ``` For a full description of the `argument(...)` method, see the {% internal_link "argument section of the Objects guide","/fields/arguments.html" %}. ## Using Input Objects Input objects are passed to field methods as an instance of their definition class. So, inside the field method, you can access any key of the object by: - calling its method, corresponding to the name (underscore-cased) - calling `#[]` with the _camel-cased_ name of the argument (this is for compatibility with previous GraphQL-Ruby versions) ```ruby # This field takes an argument called `attributes` # which will be an instance of `PostAttributes` field :create_post, Types::Post, null: false do argument :attributes, Types::PostAttributes end def create_post(attributes:) puts attributes.class.name # => "Types::PostAttributes" # Access a value by method (underscore-cased): puts attributes.full_text # => "This is my first post" # Or by hash-style lookup (camel-cased, for compatibility): puts attributes[:fullText] # => "This is my first post" end ``` ## Customizing Input Objects You can customize the `GraphQL::Schema::Argument` class which is used for input objects: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # your customization here ... end class Types::BaseInputObject < GraphQL::Schema::InputObject # Hook up the customized argument class argument_class(Types::BaseArgument) end ``` You can also add or override methods on input object classes to customize them. They have two instance variables by default: - `@arguments`: A {{ "GraphQL::Execution::Interpreter::Arguments" | api_doc }} instance - `@context`: The current {{ "GraphQL::Query::Context" | api_doc }} Any extra methods you define on the class can be used for field resolution, as demonstrated above. ## Converting to Other Ruby Objects Your input objects can be automatically converted to other Ruby types before they're passed to your application code. This is an easy way to use `Range`'s in your schema: ```ruby class Types::DateRangeInput < Types::BaseInputObject description "Range of dates" argument :min, Types::Date, "Minimum value of the range" argument :max, Types::Date, "Maximum value of the range" def prepare min..max end end class Types::CalendarType < Types::BaseObject field :appointments, [Types::Appointment], "Appointments on your calendar", null: false do argument :during, Types::DateRangeInput, "Only show appointments within this range" end def appointments(during:) # during will be an instance of Range object.appointments.select { |appointment| during.cover?(appointment.date) } end end ``` ## `@oneOf` You can make input objects that require _exactly one_ field to be provided using `one_of`: ```ruby class FindUserInput < Types::BaseInput one_of # Either `{ id: ... }` or `{ username: ... }` may be given, # but not both -- and one of them _must_ be given. argument :id, ID, required: false argument :username, String, required: false end ``` An input object with `one_of` will require exactly one given argument and it will require that the given argument's value is not `nil`. With `one_of`, arguments must have `required: false`, since any _individual_ argument is not required. When you use `one_of`, it will appear in schema print-outs with `input ... @oneOf` and you can query it using `{ __type(name: $typename) { isOneOf } }`. This behavior is described in a [proposed change](https://github.com/graphql/graphql-spec/pull/825) to the GraphQL specification. graphql-ruby-2.2.17/guides/type_definitions/interfaces.md000066400000000000000000000202051476434635200235360ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Interfaces desc: Interfaces are lists of fields which objects may implement index: 4 redirect_from: - /types/abstract_types/ --- Interfaces are lists of fields which may be implemented by object types. An interface has fields, but it's never actually instantiated. Instead, objects may _implement_ interfaces, which makes them a _member_ of that interface. Also, fields may _return_ interface types. When this happens, the returned object may be any member of that interface. For example, let's say a `Customer` (interface) may be either an `Individual` (object) or a `Company` (object). Here's the structure in the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```graphql interface Customer { name: String! outstandingBalance: Int! } type Company implements Customer { employees: [Individual!]! name: String! outstandingBalance: Int! } type Individual implements Customer { company: Company name: String! outstandingBalance: Int! } ``` Notice that the `Customer` interface requires two fields, `name: String!` and `outstandingBalance: Int!`. Both `Company` and `Individual` implement those fields, so they can implement `Customer`. Their implementation of `Customer` is made explicit by `implements Customer` in their definition. When querying, you can get the fields on an interface: ```graphql { customers(first: 5) { name outstandingBalance } } ``` Whether the objects are `Company` or `Individual`, it doesn't matter -- you still get their `name` and `outstandingBalance`. If you want some object-specific fields, you can query them with an _inline fragment_, for example: ```graphql { customers(first: 5) { name ... on Individual { company { name } } } } ``` This means, "if the customer is an `Individual`, also get the customer's company name". Interfaces are a good choice whenever a set of objects are used interchangeably, and they have several significant fields in common. When they don't have fields in common, use a {% internal_link "Union", "/type_definitions/unions" %} instead. ## Defining Interface Types Interfaces are Ruby modules which include {{ "GraphQL::Schema::Interface" | api_doc }}. First, make a base module: ```ruby module Types::BaseInterface include GraphQL::Schema::Interface end ``` Then, include that into each interface: ```ruby module Types::RetailItem include Types::BaseInterface description "Something that can be bought" field :price, Types::Price, "How much this item costs", null: false def price # Optional: provide a special implementation of `price` here end # Optional, see below definition_methods do # Optional: if this method is defined, it overrides `Schema.resolve_type` def resolve_type(object, context) # ... end end end ``` Interface classes are never instantiated. At runtime, only their `.resolve_type` methods are called (if they're defined). ### Implementing Interfaces To define object types that implement this interface use the `implements` method: ```ruby class Types::Car < Types::BaseObject implements Types::RetailItem # ... additional fields end class Types::Purse < Types::BaseObject implements Types::RetailItem # ... additional fields end ``` Those object types will _inherit_ field definitions from those interfaces. If you add an object type which implements an interface, but that object type doesn't appear in your schema as a field return type, a union member, or a root type, then you need to add that object to the interfaces's `orphan_types`. ### Implementing Fields Interfaces may provide field implementations along with the signatures. For example: ```ruby field :price, Types::Price, "How much this item costs", null: false # Implement this field to return a `::Price` object def price ::Price.from_cents(@object.price_in_cents) end ``` This method will be called by objects who implement the interface. To override this implementation, object classes can override the `#price` method. Read more in the {% internal_link "Fields guide", "/fields/introduction" %}. ### Definition Methods You can use `definition_methods do ... end` to add helper methods to interface modules. By adding methods to `definition_methods`: - Those methods will be available as class methods in the interface itself - These class methods will _also_ be added to interfaces that `include` this interface. This way, class methods are inherited when interfaces `include` other interfaces. (`definition_methods` is like `ActiveSupport::Concern`'s `class_methods` in this regard, but it has a different name to avoid naming conflicts). For example, you can add definition helpers to your base interface, then use them in concrete interfaces later: ```ruby # First, add a helper method to `BaseInterface`'s definition methods module Types::BaseInterface include GraphQL::Schema::Interface definition_methods do # Use this to add a price field + default implementation def price_field field(:price, ::Types::Price, null: false) define_method(:price) do ::Price.from_cents(@object.price_in_cents) end end end end # Then call it later module Types::ForSale include Types::BaseInterface # This calls `price_field` from definition methods price_field end ``` The type definition DSL uses this mechanism, too, so you can override those methods here also. Note: Under the hood, `definition_methods` causes a module to be `extend`ed by the Inteface. Any calls to `extend` or `implement` may override methods from `definition_methods`. ### Resolve Type When a field's return type is an interface, GraphQL has to figure out what _specific_ object type to use for the return value. In the example above, each `customer` must be categorized as an `Individual` or `Company`. You can do this by: - Providing a top-level `Schema.resolve_type` method; _OR_ - Providing an interface-level `.resolve_type` method in `definition_methods`. This method will be called whenever an object must be disambiguated. For example: ```ruby module Types::RetailItem include Types::BaseInterface definition_methods do # Determine what object type to use for `object` def resolve_type(object, context) if object.is_a?(::Car) || object.is_a?(::Truck) Types::Car elsif object.is_a?(::Purse) Types::Purse else raise "Unexpected RetailItem: #{object.inspect}" end end end end ``` You can also optionally return a "resolved" object in addition the resolved type by returning an array: ```ruby module Types::Claim include Types::BaseInterface definition_methods do def resolve_type(object, context) type = case object.value when Success Types::Approved when Error Types::Rejected else raise "Unexpected Claim: #{object.inspect}" end [type, object.value] end end end ``` The returned array must be a tuple of `[Type, object]`. This is useful for interface or union types which are backed by a domain object which should be unwrapped before resolving the next field. ## Orphan Types If you add an object type which implements an interface, but that object type doesn't properly appear in your schema, then you need to add that object to the interfaces's `orphan_types`, for example: ```ruby module Types::RetailItem include Types::BaseInterface # ... orphan_types Types::Car end ``` Alternatively you can add the object types to the schema's `orphan_types`: ```ruby class MySchema < GraphQL::Schema orphan_types Types::Car end ``` This is required because a schema finds it types by traversing its fields, starting with `query`, `mutation` and `subscription`. If an object is _never_ the return type of a field, but only connected via an interface, then it must be explicitly connected to the schema via `orphan_types`. For example, given this schema: ```graphql type Query { node(id: ID!): Node } interface Node { id: ID! } type Comment implements Node { id: ID! } ``` `Comment` must be added via `orphan_types` since it's never used as the return type of a field. (Only `Node` and `ID` are used as return types.) graphql-ruby-2.2.17/guides/type_definitions/lists.md000066400000000000000000000111221476434635200225470ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Lists desc: Ordered lists containing other types index: 6 --- GraphQL has _list types_ which are ordered lists containing items of other types. The following examples use the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL). Fields may return a single scalar value (eg `String`), or a _list_ of scalar values (eg, `[String]`, a list of strings): ```ruby type Spy { # This spy's real name realName: String! # Any other names that this spy goes by aliases: [String!] } ``` Fields may also return lists of other types as well: ```ruby enum PostCategory { SOFTWARE UPHOLSTERY MAGIC_THE_GATHERING } type BlogPost { # Zero or more categories this post belongs to categories: [PostCategory!] # Other posts related to this one relatedPosts: [BlogPost!] } ``` Inputs may also be lists. Arguments can accept list types, for example: ```ruby type Query { # Return the latest posts, filtered by `categories` posts(categories: [PostCategory!]): [BlogPost!] } ``` When GraphQL is sent and received with JSON, GraphQL lists are expressed in JSON arrays. ## List Types in Ruby To define a list type in Ruby use `[...]` (a Ruby array with one member, the inner type). For example: ```ruby # A field returning a list type: # Equivalent to `aliases: [String!]` above field :aliases, [String] # An argument which accepts a list type: argument :categories, [Types::PostCategory], required: false ``` For input, GraphQL lists are converted to Ruby arrays. For fields that return list types, any object responding to `#each` may be returned. It will be enumerated as a GraphQL list. To define lists where `nil` may be a member of the list, use `null: true` in the definition array, for example: ```ruby # Equivalent to `previousEmployers: [Employer]!` field :previous_employers, [Types::Employer, null: true], "Previous employers; `null` represents a period of self-employment or unemployment" null: false ``` ## Lists, Nullable Lists, and Lists of Nulls Combining list types and non-null types can be a bit tricky. There are four possible combinations, based on two parameters: - Nullability of the field: can this field return `null`, or does it always return a list? - Nullability of the list items: when a list is present, may it include `null`? Here's how those combinations play out:   | nullable field | non-null field ------|------|------ nullable items | [Integer, null: true], null: true
# => [Int] | [Integer, null: true], null: false
# => [Int]! non-null items | [Integer]
# => [Int!] | [Integer], null: false
# => [Int!]! (The first line is GraphQL-Ruby code. The second line, beginning with `# =>`, is the corresponding GraphQL SDL code.) Let's look at some examples. #### Non-null lists with non-null items Here's an example field: ```ruby field :scores, [Integer], null: false # In GraphQL, # scores: [Int!]! ``` In this example, `scores` may not return `null`. It must _always_ return a list. Additionally, the list may _never_ contain `null` -- it may only contain `Int`s. (It may be empty, but it cannot have `null` in it.) Here are values the field may return: Valid | Invalid ------|------ `[]` | `null` `[1, 2, ...]`| `[null]` | | `[1, null, 2, ...]` ### Non-null lists with nullable items Here's an example field: ```ruby field :scores, [Integer, null: true], null: false # In GraphQL, # scores: [Int]! ``` In this example, `scores` may not return `null`. It must _always_ return a list. However, the list _may_ contain `null`s and/or `Int`s. Here are values the field may return: Valid | Invalid ------|------ `[]` | `null` `[1, 2, ...]`| `[null]` | `[1, null, 2, ...]` | ### Nullable lists with nullable items Here's an example field: ```ruby field :scores, [Integer, null: true] # In GraphQL, # scores: [Int] ``` In this example, `scores` return `null` _or_ a list. Additionally, the list _may_ contain `null`s and/or `Int`s. Here are values the field may return: Valid | Invalid ------|------ `null` | `[]` | `[1, 2, ...]`| `[null]` | `[1, null, 2, ...]` | ### Nullable lists with non-null items Here's an example field: ```ruby field :scores, [Integer] # In GraphQL, # scores: [Int!] ``` In this example, `scores` return `null` _or_ a list. However, if a list is present, it may _not_ contain `null` -- only `Int`s. Here are values the field may return: Valid | Invalid ------|------ `null` | `[null]` `[]` | `[1, null, 2, ...]` `[1, 2, ...]` | graphql-ruby-2.2.17/guides/type_definitions/non_nulls.md000066400000000000000000000040431476434635200234240ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Non-Null Types desc: Values which must be present index: 7 --- GraphQL's concept of _non-null_ is expressed in the [Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL) with `!`, for example: ```graphql type User { # This field _always_ returns a String, never returns `null` handle: String! # `since:` _must_ be passed a `DateTime` value, it can never be omitted or passed `null` followers(since: DateTime!): [User!]! } ``` In Ruby, this concept is expressed with `null:` for fields and `required:` for arguments. ## Non-null return types When `!` is used for field return types (like `handle: String!` above), it means that the field will _never_ (and may never) return `nil`. To make a field non-null in Ruby, use `null: false` in the field definition: ```ruby # equivalent to `handle: String!` above field :handle, String, null: false ``` This means that the field will _never_ be `nil` (and if it is, it will be removed from the response, as described below). ### Non-null error propagation If a non-null field ever returns `nil`, then the entire selection will be removed from the response and replaced with `nil`. If this removal would result in _another_ invalid `nil`, then it cascades upward, until it reaches the root `"data"` key. This is to support clients in strongly-typed languages. Any non-null field will _never_ return `null`, and client developers can depend on that. ## Non-null argument types When `!` is used for arguments (like `followers(since: DateTime!)` above), it means that the argument is _required_ for the query to execute. Any query which doesn't have a value for that argument will be rejected immediately. Arguments are non-null by default. You can use `required: false` to mark arguments as optional: ```ruby # This will be `since: DateTime` instead of `since: DateTime!` argument :since, Types::DateTime, required: false ``` Without `required: false`, any query _without_ a value for `since:` will be rejected. graphql-ruby-2.2.17/guides/type_definitions/objects.md000066400000000000000000000070601476434635200230500ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Objects desc: Objects expose data and link to other objects index: 0 --- GraphQL object types are the bread and butter of GraphQL APIs. Each object has _fields_ which expose data and may be queried by name. For example, we can query a `User` like this: ```ruby user { handle email } ``` And get back values like this: ```ruby { "user" => { "handle" => "rmosolgo", "email" => nil, } } ``` Generally speaking, GraphQL object types correspond to models in your application, like `User`, `Product`, or `Comment`. Sometimes, object types are described using the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```ruby type User { email: String handle: String! friends: [User!]! } ``` This means that `User` objects have three fields: - `email`, which may return a `String` _or_ `nil`. - `handle`, which returns a `String` but _never_ `nil` (`!` means the field never returns `nil`) - `friends`, which returns a list of other `User`s (`[...]` means the field returns a list of values; `User!` means the list contains `User` objects, and never contains `nil`.) The same object can be defined using Ruby: ```ruby class Types::User < GraphQL::Schema::Object field :email, String field :handle, String, null: false field :friends, [User], null: false end ``` The rest of this guide will describe how to define GraphQL object types in Ruby. To learn more about GraphQL object types in general, see the [GraphQL docs](https://graphql.org/learn/schema/#object-types-and-fields). ## Object classes Classes extending {{ "GraphQL::Schema::Object" | api_doc }} describe [Object types](https://graphql.org/learn/schema/#object-types-and-fields) and customize their behavior. Object fields can be created with the `field(...)` class method, [described in detail below](#fields) Field and argument names should be underscored as a convention. They will be converted to camelCase in the underlying GraphQL type and be camelCase in the schema itself. ```ruby # first, somewhere, a base class: class Types::BaseObject < GraphQL::Schema::Object end # then... class Types::TodoList < Types::BaseObject description "A list of items which may be completed" field :name, String, "The unique name of this list", null: false field :is_completed, String, "Completed status depending on all tasks being done.", null: false # Related Object: field :owner, Types::User, "The creator of this list", null: false # List field: field :viewers, [Types::User], "Users who can see this list", null: false # Connection: field :items, Types::TodoItem.connection_type, "Tasks on this list", null: false do argument :status, TodoStatus, "Restrict items to this status", required: false end end ``` ## Fields Object fields expose data about that object or connect the object to other objects. You can add fields to your object types with the `field(...)` class method. See the {% internal_link "Fields guide", "/fields/introduction" %} for details about object fields. ## Implementing interfaces If an object implements any interfaces, they can be added with `implements`, for example: ```ruby # This object implements some interfaces: implements GraphQL::Types::Relay::Node implements Types::UserAssignableType ``` When an object `implements` interfaces, it: - inherits the GraphQL field definitions from that object - includes that module into the object definition Read more about interfaces in the {% internal_link "Interfaces guide", "/type_definitions/interfaces" %} graphql-ruby-2.2.17/guides/type_definitions/scalars.md000066400000000000000000000070161476434635200230500ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Scalars desc: Scalars are "simple" data types like integers and strings index: 1 --- Scalars are "leaf" values in GraphQL. There are several built-in scalars, and you can define custom scalars, too. ({% internal_link "Enums", "/type_definitions/enums" %} are also leaf values.) The built-in scalars are: - `String`, like a JSON or Ruby string - `Int`, like a JSON or Ruby integer - `Float`, like a JSON or Ruby floating point decimal - `Boolean`, like a JSON or Ruby boolean (`true` or `false`) - `ID`, which a specialized `String` for representing unique object identifiers - `ISO8601DateTime`, an ISO 8601-encoded datetime - `ISO8601Date`, an ISO 8601-encoded date - `ISO8601Duration`, an ISO 8601-encoded duration. ⚠ This requires `ActiveSupport::Duration` to be loaded and will raise {{ "GraphQL::Error" | api_doc }} if it's `.coerce_*` methods are called when it is not defined. - `JSON`, ⚠ This returns arbitrary JSON (Ruby hashes, arrays, strings, integers, floats, booleans and nils). Take care: by using this type, you completely lose all GraphQL type safety. Consider building object types for your data instead. - `BigInt`, a numeric value which may exceed the size of a 32-bit integer Fields can return built-in scalars by referencing them by name: ```ruby # String field: field :name, String, # Integer field: field :top_score, Int, null: false # or: field :top_score, Integer, null: false # Float field field :avg_points_per_game, Float, null: false # Boolean field field :is_top_ranked, Boolean, null: false # ID field field :id, ID, null: false # ISO8601DateTime field field :created_at, GraphQL::Types::ISO8601DateTime, null: false # ISO8601Date field field :birthday, GraphQL::Types::ISO8601Date, null: false # ISO8601Duration field field :age, GraphQL::Types::ISO8601Duration, null: false # JSON field ⚠ field :parameters, GraphQL::Types::JSON, null: false # BigInt field field :sales, GraphQL::Types::BigInt, null: false ``` Custom scalars (see below) can also be used by name: ```ruby # `homepage: Url` field :homepage, Types::Url ``` In the [Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL), scalars are simply named: ```ruby scalar DateTime ``` ## Custom Scalars You can implement your own scalars by extending {{ "GraphQL::Schema::Scalar" | api_doc }}. For example: ```ruby # app/graphql/types/base_scalar.rb # Make a base class: class Types::BaseScalar < GraphQL::Schema::Scalar end # app/graphql/types/url.rb class Types::Url < Types::BaseScalar description "A valid URL, transported as a string" def self.coerce_input(input_value, context) # Parse the incoming object into a `URI` url = URI.parse(input_value) if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS) # It's valid, return the URI object url else raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL" end end def self.coerce_result(ruby_value, context) # It's transported as a string, so stringify it ruby_value.to_s end end ``` Your class must define two class methods: - `self.coerce_input` takes a GraphQL input and converts it into a Ruby value - `self.coerce_result` takes the return value of a field and prepares it for the GraphQL response JSON When incoming data is incorrect, the method may raise {{ "GraphQL::CoercionError" | api_doc }}, which will be returned to the client in the `"errors"` key. Scalar classes are never initialized; only their `.coerce_*` methods are called at runtime. graphql-ruby-2.2.17/guides/type_definitions/unions.md000066400000000000000000000043021476434635200227260ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Unions desc: Unions are sets of types which may appear in the same place (but don't share fields). index: 5 --- A union type is a set of object types which may appear in the same spot. Here's a union, expressed in [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```ruby union MediaItem = AudioClip | VideoClip | Image | TextSnippet ``` This might be used on a search field, for example: ```ruby searchMedia(term: "puppies") { ... on AudioClip { duration } ... on VideoClip { previewURL resolution } ... on Image { thumbnailURL } ... on TextSnippet { teaserText } } ``` Here, the `searchMedia` field returns `[MediaItem!]`, a list where each member is part of the `MediaItem` union. So, for each member, we want to select different fields depending on which kind of object that member is. {% internal_link "Interfaces", "/type_definitions/interfaces" %} are a similar concept, but in an interface, all types must share some common fields. Unions are a good choice when the object types don't have any significant fields in common. Since union members share _no_ fields, selections are _always_ made with typed fragments (`... on SomeType`, as seen above). ## Defining Union Types Unions extend `GraphQL::Schema::Union`. First, make a base class: ```ruby class Types::BaseUnion < GraphQL::Schema::Union end ``` Then, extend that one for each union in your schema: ```ruby class Types::CommentSubject < Types::BaseUnion description "Objects which may be commented on" possible_types Types::Post, Types::Image # Optional: if this method is defined, it will override `Schema.resolve_type` def self.resolve_type(object, context) if object.is_a?(BlogPost) Types::Post else Types::Image end end end ``` The `possible_types(*types)` method accepts one or more types which belong to this union. Union classes are never instantiated; At runtime, only their `.resolve_type` methods may be called (if defined). For information about `.resolve_type`, see the {% internal_link "Interfaces guide", "/type_definitions/interfaces#resolve-type" %}. graphql-ruby-2.2.17/javascript_client/000077500000000000000000000000001476434635200177425ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/.npmignore000066400000000000000000000001171476434635200217400ustar00rootroot00000000000000node_modules/ OperationStoreClient.js yarn.lock src/ **/__tests__/* coverage/* graphql-ruby-2.2.17/javascript_client/CHANGELOG.md000066400000000000000000000157011476434635200215570ustar00rootroot00000000000000# graphql-ruby-client # 1.13.2 (28 Feb 2024) - Update `glob` to v10+ to eliminate dependency on `inflight` #4859 # 1.13.1 (23 Feb 2024) - createAblyHandler: add typing for `onError` handler #4845 # 1.13.0 (23 Jan 2024) - Sync: add support for `generate-persisted-query-manifest` files #4798 - createActionCableHandler: remove needless `perform("send", ...)` call #4793 # 1.12.1 (29 Dec 2023) - GraphiQL: support custom `channelName` and `url` in ActionCable fetcher #4756 # 1.12.0 (7 Dec 2023) - Add GraphiQL support for subscriptions #4724 # 1.11.10 (17 Nov 2023) - `createRelaySubscriptionHandler`: Support Relay persisted queries with ActionCable #4705 # 1.11.9 (1 Sept 2023) - `createRelaySubscriptionHandler`: fix error handling in handler functions #4603 # 1.11.8 (9 May 2023) - ActionCable: accept a custom `channelName` for `createActionCableHandler` and `addGraphQLSubscriptions` #4463 # 1.11.7 (24 February 2023) - ActionCableLink: fix race condition #4359 # 1.11.6 (14 February 2023) - Sync: fix `--changeset-version` #4328 - Improve verbose logging #4328 # 1.11.5 (27 January 2023) - Sync: add a `--changeset-version` for use with Changesets #4304 - Sync: fix handling of `--header` with a single header # 1.11.4 (4 January 2023) - PusherLink: pass initial response along to the client #4282 # 1.11.3 (13 October 2022) - `createAblySubscriptions`: don't use `Error.captureStackTrace` which isn't supported in all JS runtimes #4223 - `createAblySubscriptions`: properly handle empty initial response from the interpreter (`{}`) #4226 # 1.11.2 (26 August 2022) - Sync: Add a `--header` option for custom headers #4171 # 1.11.1 (19 July 2022) - Subscriptions: ActionCableLink: only forward the result if `data` or `errors` is present #4114 # 1.11.0 (4 July 2022) - Subscriptions: Add `urql` support for Pusher #4129 # 1.10.7 (29 Mar 2022) - Dependencies: loosen apollo client and graphql version requirements to accept newer versions #4008 # 1.10.6 (10 Jan 2022) - Pusher Link: Don't pass along the `complete` handler because Apollo unsubscribes if you do #3830 # 1.10.5 (17 Dec 2021) - Dependencies: replace `actioncable` with `@rails/actioncable` #3773 # 1.10.4 (19 Nov 2021) - Sync: Also make sure documents are valid after removing `@client` fields #3715 # 1.10.3 (18 Nov 2021) - Sync: Remove any fields with `@client` before sending operations to the server #3712 # 1.10.2 (25 Oct 2021) - Pusher Link: Properly forward network errors to subscribers #3638 # 1.10.1 (22 Sept 2021) - Sync: Add `--apollo-codegen-json-output=...` option #3616 # 1.10.0 (25 Aug 2021) - Remove direct dependency on `request` #3594 - Update `createRelaySubscriptionHandler` to support Relay 11. Use `createLegacyRelaySubscriptionHandler` to get the old behavior. #3594 # 1.9.3 (31 Mar 2021) - Move `graphql` and `@apollo/client` to `peerDeps` for more flexible versions #3395 # 1.9.2 (19 Feb 2021) - Remove dependency on React by changing imports to `@apollo/client/core` #3349 # 1.9.1 (11 Feb 2021) - Support graphql 15.x in dependencies #3334 ## 1.9.0 (10 Feb 2021) - Move "compiled" `.js` files into the root directory of the published NPM package (instead of `dist/`). To upgrade, remove `dist/` from your import paths. (These files will be treated as public APIs in their own right, exposed independently to support smaller bundle sizes.) #2768 - Upgrade dependency from `apollo-link` to `@apollo/client` #3270 ## 1.8.2 (2 Feb 2021) - Pusher: Accept a `decompress:` function for handling compressed payloads #3311 ## 1.8.1 (16 Nov 2020) - Sync: When `--url` is omitted, generate an outfile without syncing with a server ## 1.8.0 (10 Nov 2020) - Ably: Support server-side `cipher_base:` config in the client ## 1.7.12 (3 Nov 2020) - Ably: Add `rewind:` config so messages aren't lost between subscribe and registration of listener. #3210 - Ably: Fix race condition where error was raised before the channel was available. #3210 ## 1.7.11 (15 June 2020) - Ably: Improve channel state handling in case the initial subscription result contains errors #2993 ## 1.7.10 (13 June 2020) - Ably: Improve error handling and channel cleanup #2991 ## 1.7.9 (15 May 2020) - Ably: _completely_ unsubscribe when subscriptions are done #2944 - Ably: propagate errors from subscriptions #2944 ## 1.7.8 (1 May 2020) - `sync`: Add support for Apollo-Android's `OperationOutput.json` #2914 ## 1.7.7 (15 Apr 2020) - Ably handler: dispatch initial response #2866 - Ably handler: catch any error in initial HTTP call #2877 ## 1.7.6 (3 Apr 2020) - Fix ActionCableLink sending unsubcribe to ActionCable #2842 ## 1.7.5 (4 Mar 2020) - Add missing dependency declarations ## 1.7.4 (18 Feb 2020) - Move all exports to top level - Fix sync body handling: wait for all chunks, improve verbose output ## 1.7.3 (17 Feb 2020) - Fix CLI for TypeScript ## 1.7.2 (17 Feb 2020) - Convert outfile generators to TypeScript and include them in published package ## 1.7.1 (17 Feb 2020) - Fix `bin` configuration in package.json ## 1.7.0 (17 Feb 2020) - Rewrite in TypeScript ## 1.6.8 (18 Sept 2019) - Properly send `Content-Type: application/json` when posting persisted operations ## 1.6.7 (18 Sept 2019) - Add post data to `--verbose` output of `sync` ## 1.6.6 (6 Aug 2019) - Add `--relay-persisted-output` for working with Relay Compiler's new `--persist-output` option #2415 ## 1.6.5 (17 July 2019) - Update dependencies #2335 ## 1.6.4 (11 May 2019) - Add `--verbose` option to `sync` #2075 - Support Relay 2.0.0 #2121 - ActionCableLink: support subscriber when there are errors but no data #2176 ## 1.6.3 (11 Jan 2019) - Fix `.unsubscribe()` for PusherLink #2042 ## 1.6.2 (14 Dec 2018) - Support identified Ably client #2003 ## 1.6.1 (30 Nov 2018) - Support `ably:` option for Relay subscriptions ## 1.6.0 (19 Nov 2018) - Fix unused requires #1943 - Add `generateClient` function to generate code _without_ the HTTP call #1941 ## 1.5.0 (27 October 2018) - Fix `export` usage in PusherLink, use `require` and `module.exports` instead #1889 - Add `AblyLink` #1925 ## 1.4.1 (19 Sept 2018) - Add `connectionOptions` to ActionCableLink #1857 ## 1.4.0 (12 Apr 2018) - Add `PusherLink` for Apollo 2 Subscriptions on Pusher - Add `OperationStoreLink` for Apollo 2 persisted queries ## 1.3.0 (30 Nov 2017) - Support HTTPS, basic auth, query string and port in `sync` #1053 - Add Apollo 2 support for ActionCable subscriptions #1120 - Add `--outfile-type=json` for stored operation manifest #1142 ## 1.2.0 (15 Nov 2017) - Support Apollo batching middleware #1092 ## 1.1.3 (11 Oct 2017) - Fix Apollo + ActionCable unsubscribe function #1019 ## 1.1.2 (9 Oct 2017) - Add channel IDs to ActionCable subscriptions #1004 ## 1.1.1 (21 Sept 2017) - Add `--add-typename` option to `sync` #967 ## 1.1.0 (18 Sept 2017) - Add subscription clients for Apollo and Relay Modern ## 1.0.2 (22 Aug 2017) - Remove debug output ## 1.0.1 (21 Aug 2017) - Rename from `graphql-pro-js` to `graphql-ruby-client` ## 1.0.0 (31 Jul 2017) - Add `sync` task graphql-ruby-2.2.17/javascript_client/LICENSE.md000066400000000000000000000007341476434635200213520ustar00rootroot00000000000000Copyright (c) Minimum Viable Software `graphql-ruby-client` is an Open Source project licensed under the terms of the LGPLv3 license. Please see https://www.gnu.org/licenses/lgpl-3.0.html for license text. `GraphQL::Pro` customers are granted a commercial-friendly license allowing private forks and modifications of `graphql-ruby-client`. Please see https://graphql.pro/ for more detail. You can find the commercial license terms at https://graphql.pro/COMM-LICENSE.html. graphql-ruby-2.2.17/javascript_client/jest.config.js000066400000000000000000000002611476434635200225100ustar00rootroot00000000000000module.exports = { roots: [ "/src" ], verbose: true, testMatch: [ "**/__tests__/**/[^.]+Test.ts", ], transform: { "^.+\\.ts$": "ts-jest" }, } graphql-ruby-2.2.17/javascript_client/package-lock.json000066400000000000000000006416461476434635200231770ustar00rootroot00000000000000{ "name": "graphql-ruby-client", "version": "1.13.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "graphql-ruby-client", "version": "1.13.3", "license": "LGPL-3.0", "dependencies": { "glob": "^10.0.0", "minimist": "^1.2.0" }, "bin": { "graphql-ruby-client": "cli.js" }, "devDependencies": { "@apollo/client": ">=3.3.13", "@rails/actioncable": "^7.0.0", "@types/glob": "^7.1.1", "@types/jest": "^25.1.2", "@types/minimist": "^1.2.0", "@types/node": "^13.7.1", "@types/pako": "^1.0.1", "@types/pusher-js": "^4.2.2", "@types/rails__actioncable": "^6.1.6", "@types/react": "^17.0.0", "@types/relay-runtime": "^14.0.0", "@types/zen-observable": "^0.8.2", "ably": "1.2.6", "graphql": ">=15.0.0", "jest": "^29.0.0", "nock": "^11.0.0", "pako": "^2.0.3", "prettier": "^1.19.1", "pusher-js": "^7.0.3", "relay-runtime": "11.0.2", "ts-jest": "^29.0.0", "typescript": "5.3.3", "urql": "^2.2.2" }, "engines": { "node": ">=10" }, "peerDependencies": { "@apollo/client": ">=3.3.6", "graphql": ">=14.3.1" } }, "node_modules/@ably/msgpack-js": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@ably/msgpack-js/-/msgpack-js-0.3.4.tgz", "integrity": "sha512-gmnsxxcN/8WfoxZxQQF9LvM3ZUbuVH0LCS6oX7EJS+VfkXWBFIgDV+h7a0sntwKSvAEg4uJzNDje7kpH8/LJ3Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "bops": "^1.0.1" } }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@apollo/client": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.8.1.tgz", "integrity": "sha512-JGGj/9bdoLEqzatRikDeN8etseY5qeFAY0vSAx/Pd0ePNsaflKzHx6V2NZ0NsGkInq+9IXXX3RLVDf0EotizMA==", "dev": true, "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/context": "^0.7.3", "@wry/equality": "^0.5.6", "@wry/trie": "^0.4.3", "graphql-tag": "^2.12.6", "hoist-non-react-statics": "^3.3.2", "optimism": "^0.17.5", "prop-types": "^15.7.2", "response-iterator": "^0.2.6", "symbol-observable": "^4.0.0", "ts-invariant": "^0.10.3", "tslib": "^2.3.0", "zen-observable-ts": "^1.2.5" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", "graphql-ws": "^5.5.5", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" }, "peerDependenciesMeta": { "graphql-ws": { "optional": true }, "react": { "optional": true }, "react-dom": { "optional": true }, "subscriptions-transport-ws": { "optional": true } } }, "node_modules/@babel/code-frame": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", "dev": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.22.10", "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/code-frame/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/code-frame/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/code-frame/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" } }, "node_modules/@babel/code-frame/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, "license": "MIT" }, "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/code-frame/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/code-frame/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/compat-data": { "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { "version": "7.22.11", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.10", "@babel/generator": "^7.22.10", "@babel/helper-compilation-targets": "^7.22.10", "@babel/helper-module-transforms": "^7.22.9", "@babel/helpers": "^7.22.11", "@babel/parser": "^7.22.11", "@babel/template": "^7.22.5", "@babel/traverse": "^7.22.11", "@babel/types": "^7.22.11", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/babel" } }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.10", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.9", "@babel/helper-validator-option": "^7.22.5", "browserslist": "^4.21.9", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.22.5", "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-module-imports": "^7.22.5", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "@babel/helper-validator-identifier": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { "version": "7.22.11", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.22.5", "@babel/traverse": "^7.22.11", "@babel/types": "^7.22.11" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { "version": "7.22.10", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" } }, "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/parser": { "version": "7.22.11", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz", "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==", "dev": true, "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/runtime": { "version": "7.22.11", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "dev": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.5", "@babel/parser": "^7.22.5", "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { "version": "7.22.11", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.10", "@babel/generator": "^7.22.10", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "@babel/parser": "^7.22.11", "@babel/types": "^7.22.11", "debug": "^4.1.0", "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { "version": "7.22.11", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, "license": "MIT" }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", "dev": true, "license": "MIT", "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { "node": ">=12" } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" }, "engines": { "node": ">=8" } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@jest/console": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.4.tgz", "integrity": "sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.6.3", "jest-util": "^29.6.3", "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/console/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@jest/core": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.4.tgz", "integrity": "sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg==", "dev": true, "license": "MIT", "dependencies": { "@jest/console": "^29.6.4", "@jest/reporters": "^29.6.4", "@jest/test-result": "^29.6.4", "@jest/transform": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.6.3", "jest-config": "^29.6.4", "jest-haste-map": "^29.6.4", "jest-message-util": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.6.4", "jest-resolve-dependencies": "^29.6.4", "jest-runner": "^29.6.4", "jest-runtime": "^29.6.4", "jest-snapshot": "^29.6.4", "jest-util": "^29.6.3", "jest-validate": "^29.6.3", "jest-watcher": "^29.6.4", "micromatch": "^4.0.4", "pretty-format": "^29.6.3", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { "node-notifier": { "optional": true } } }, "node_modules/@jest/core/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@jest/environment": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.4.tgz", "integrity": "sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/environment/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@jest/expect": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.4.tgz", "integrity": "sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA==", "dev": true, "license": "MIT", "dependencies": { "expect": "^29.6.4", "jest-snapshot": "^29.6.4" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz", "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==", "dev": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.4.tgz", "integrity": "sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.6.3", "jest-mock": "^29.6.3", "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@jest/globals": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.4.tgz", "integrity": "sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA==", "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.6.4", "@jest/expect": "^29.6.4", "@jest/types": "^29.6.3", "jest-mock": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.4.tgz", "integrity": "sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.6.4", "@jest/test-result": "^29.6.4", "@jest/transform": "^29.6.4", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.6.3", "jest-util": "^29.6.3", "jest-worker": "^29.6.4", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { "node-notifier": { "optional": true } } }, "node_modules/@jest/reporters/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-result": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.4.tgz", "integrity": "sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/console": "^29.6.4", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz", "integrity": "sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg==", "dev": true, "license": "MIT", "dependencies": { "@jest/test-result": "^29.6.4", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.6.4", "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/transform": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.4.tgz", "integrity": "sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.6.4", "jest-regex-util": "^29.6.3", "jest-util": "^29.6.3", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/transform/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@rails/actioncable": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.1.2.tgz", "integrity": "sha512-KGziTZfbmGm8/fHOpj515xupbYU+49hsp4etfdpoDJ/CEY2bRZR0cyFcJkpK6n0t/sxOHNWY6bo9vSgXZvT7Mg==", "dev": true, "license": "MIT" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "node_modules/@types/babel__core": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "node_modules/@types/babel__generator": { "version": "7.6.4", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/express-serve-static-core": { "version": "4.17.28", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, "node_modules/@types/express-serve-static-core/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "license": "MIT", "dependencies": { "@types/minimatch": "*", "@types/node": "*" } }, "node_modules/@types/glob/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/graceful-fs/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { "version": "25.2.3", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz", "integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==", "dev": true, "license": "MIT", "dependencies": { "jest-diff": "^25.2.1", "pretty-format": "^25.2.1" } }, "node_modules/@types/jest/node_modules/@jest/types": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", "@types/yargs": "^15.0.0", "chalk": "^3.0.0" }, "engines": { "node": ">= 8.3" } }, "node_modules/@types/jest/node_modules/@types/istanbul-reports": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest/node_modules/@types/yargs": { "version": "15.0.15", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/jest/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=8" } }, "node_modules/@types/jest/node_modules/diff-sequences": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", "dev": true, "license": "MIT", "engines": { "node": ">= 8.3" } }, "node_modules/@types/jest/node_modules/jest-diff": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^3.0.0", "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" }, "engines": { "node": ">= 8.3" } }, "node_modules/@types/jest/node_modules/jest-get-type": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", "dev": true, "license": "MIT", "engines": { "node": ">= 8.3" } }, "node_modules/@types/jest/node_modules/pretty-format": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" }, "engines": { "node": ">= 8.3" } }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, "license": "MIT" }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "13.13.52", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==", "dev": true, "license": "MIT" }, "node_modules/@types/pako": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz", "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==", "dev": true, "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", "dev": true, "license": "MIT" }, "node_modules/@types/pusher-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/pusher-js/-/pusher-js-4.2.2.tgz", "integrity": "sha512-LP9isBRAFlNzQohQtySJxJjzmy4zQCcv5xGZD2G3rsDnTWfpEkFKyLw3x9711pFAXwwUl9ZivxKkcnFr8umSAQ==", "dev": true, "license": "MIT" }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true, "license": "MIT" }, "node_modules/@types/rails__actioncable": { "version": "6.1.10", "resolved": "https://registry.npmjs.org/@types/rails__actioncable/-/rails__actioncable-6.1.10.tgz", "integrity": "sha512-Dr6A/+OTsoTgvnj2ynysjuMvmk2XDI4J71ljPANcODZjMafKeSau2/IJ+B4IuJ6x9bsG/DQpBxNxH6RqmyFzpw==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "17.0.65", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.65.tgz", "integrity": "sha512-oxur785xZYHvnI7TRS61dXbkIhDPnGfsXKv0cNXR/0ml4SipRIFpSMzA7HMEfOywFwJ5AOnPrXYTEiTRUQeGlQ==", "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/relay-runtime": { "version": "14.1.23", "resolved": "https://registry.npmjs.org/@types/relay-runtime/-/relay-runtime-14.1.23.tgz", "integrity": "sha512-tP2l6YLI2HJ11UzEB7j4IWeADyiPIKTehdeyHsyOzNBu7WvKsyf4kAZDmsB2NPaXp9Lud+KEJbRi/VW+jEDYCA==", "dev": true, "license": "MIT" }, "node_modules/@types/scheduler": { "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", "dev": true, "license": "MIT" }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true, "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { "version": "21.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true, "license": "MIT" }, "node_modules/@types/zen-observable": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==", "dev": true, "license": "MIT" }, "node_modules/@urql/core": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.6.1.tgz", "integrity": "sha512-gYrEHy3tViJhwIhauK6MIf2Qp09QTsgNHZRd0n71rS+hF6gdwjspf1oKljl4m25+272cJF7fPjBUGmjaiEr7Kg==", "dev": true, "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "wonka": "^4.0.14" }, "peerDependencies": { "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/@wry/context": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz", "integrity": "sha512-Nl8WTesHp89RF803Se9X3IiHjdmLBrIvPMaJkl+rKVJAYyPsz1TEUbu89943HpvujtSJgDUx9W4vZw3K1Mr3sA==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { "node": ">=8" } }, "node_modules/@wry/equality": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.6.tgz", "integrity": "sha512-D46sfMTngaYlrH+OspKf8mIJETntFnf6Hsjb0V41jAXJ7Bx2kB8Rv8RCUujuVWYttFtHkUNp7g+FwxNQAr6mXA==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { "node": ">=8" } }, "node_modules/@wry/trie": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz", "integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { "node": ">=8" } }, "node_modules/ably": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/ably/-/ably-1.2.6.tgz", "integrity": "sha512-5dBg0iMcJEP/ogY7v9FGTZ1c7y5odf6o2TM+purdfy3g5fCZ8qHyN0Wu+jaLHmNV5auFcxX2Y/W14RX3sSE2hg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@ably/msgpack-js": "^0.3.3", "request": "^2.87.0", "ws": "^5.1" }, "engines": { "node": ">=5.10.x" } }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" }, "engines": { "node": ">= 8" } }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" } }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true, "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, "license": "MIT" }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/aws4": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true, "license": "MIT" }, "node_modules/babel-jest": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.4.tgz", "integrity": "sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==", "dev": true, "license": "MIT", "dependencies": { "@jest/transform": "^29.6.4", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" }, "engines": { "node": ">=8" } }, "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" }, "engines": { "node": ">=8" } }, "node_modules/babel-plugin-istanbul/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/babel-plugin-jest-hoist": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.8.3", "@babel/plugin-syntax-import-meta": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.8.3", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-top-level-await": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/babel-preset-jest": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "node_modules/base64-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.2.tgz", "integrity": "sha512-ZXBDPMt/v/8fsIqn+Z5VwrhdR6jVka0bYobHdGia0Nxi7BJ9i/Uvml3AocHIBtIIBhZjBw5MR0aR4ROs/8+SNg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" } }, "node_modules/bops": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bops/-/bops-1.0.1.tgz", "integrity": "sha512-qCMBuZKP36tELrrgXpAfM+gHzqa0nLsWZ+L37ncsb8txYlnAoxOPpVp+g7fK0sGkMXfA0wl8uQkESqw3v4HNag==", "dev": true, "license": "MIT", "dependencies": { "base64-js": "1.0.2", "to-utf8": "0.0.1" } }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.0.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "caniuse-lite": "^1.0.30001517", "electron-to-chromium": "^1.4.477", "node-releases": "^2.0.13", "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/bs-logger": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, "engines": { "node": ">= 6" } }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/caniuse-lite": { "version": "1.0.30001524", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "CC-BY-4.0" }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true, "license": "Apache-2.0" }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true, "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" }, "engines": { "node": ">=12" } }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" } }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true, "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" } }, "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true, "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true, "license": "MIT" }, "node_modules/cross-fetch": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", "dev": true, "license": "MIT", "dependencies": { "node-fetch": "^2.6.12" } }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true, "license": "MIT" }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" }, "engines": { "node": ">=0.10" } }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/dedent": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "peerDependenciesMeta": { "babel-plugin-macros": { "optional": true } } }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "license": "MIT", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "node_modules/electron-to-chromium": { "version": "1.4.503", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==", "dev": true, "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" }, "engines": { "node": ">=4" } }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz", "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==", "dev": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.6.4", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.6.4", "jest-message-util": "^29.6.3", "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true, "license": "MIT" }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true, "engines": [ "node >=0.6.0" ], "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } }, "node_modules/fbjs": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", "dev": true, "license": "MIT", "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^1.0.35" } }, "node_modules/fbjs-css-vars": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", "dev": true, "license": "MIT" }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/foreground-child/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" }, "engines": { "node": ">= 0.12" } }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true, "license": "MIT" }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" } }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" } }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", "minimatch": "^9.0.1", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/graphql": { "version": "16.8.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.0.tgz", "integrity": "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==", "dev": true, "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, "node_modules/graphql-tag": { "version": "2.12.6", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.1.0" }, "engines": { "node": ">=10" }, "peerDependencies": { "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "dev": true, "license": "ISC", "engines": { "node": ">=4" } }, "node_modules/har-validator": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.3", "har-schema": "^2.0.0" }, "engines": { "node": ">=6" } }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.1" }, "engines": { "node": ">= 0.4.0" } }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" }, "engines": { "node": ">=0.8", "npm": ">=1.3.7" } }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" } }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "license": "MIT", "dependencies": { "has": "^1.0.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true, "license": "MIT" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, "engines": { "node": ">=10" } }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" }, "engines": { "node": ">=10" } }, "node_modules/istanbul-reports": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz", "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==", "dev": true, "license": "MIT", "dependencies": { "@jest/core": "^29.6.4", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.6.4" }, "bin": { "jest": "bin/jest.js" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { "node-notifier": { "optional": true } } }, "node_modules/jest-changed-files": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.0.0", "jest-util": "^29.6.3", "p-limit": "^3.1.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.4.tgz", "integrity": "sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw==", "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.6.4", "@jest/expect": "^29.6.4", "@jest/test-result": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.6.3", "jest-matcher-utils": "^29.6.4", "jest-message-util": "^29.6.3", "jest-runtime": "^29.6.4", "jest-snapshot": "^29.6.4", "jest-util": "^29.6.3", "p-limit": "^3.1.0", "pretty-format": "^29.6.3", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-cli": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.4.tgz", "integrity": "sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/core": "^29.6.4", "@jest/test-result": "^29.6.4", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", "jest-config": "^29.6.4", "jest-util": "^29.6.3", "jest-validate": "^29.6.3", "prompts": "^2.0.1", "yargs": "^17.3.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { "node-notifier": { "optional": true } } }, "node_modules/jest-config": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.4.tgz", "integrity": "sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.6.4", "@jest/types": "^29.6.3", "babel-jest": "^29.6.4", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.6.4", "jest-environment-node": "^29.6.4", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.6.4", "jest-runner": "^29.6.4", "jest-util": "^29.6.3", "jest-validate": "^29.6.3", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.6.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, "ts-node": { "optional": true } } }, "node_modules/jest-config/node_modules/@types/node": { "version": "20.5.7", "dev": true, "license": "MIT", "optional": true, "peer": true }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jest-diff": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", "dev": true, "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-each": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.6.3", "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.4.tgz", "integrity": "sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.6.4", "@jest/fake-timers": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.6.3", "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.4.tgz", "integrity": "sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.6.3", "jest-worker": "^29.6.4", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "node_modules/jest-haste-map/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-leak-detector": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", "dev": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz", "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.6.4", "jest-get-type": "^29.6.3", "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.6.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-mock": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-mock/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", "engines": { "node": ">=6" }, "peerDependencies": { "jest-resolve": "*" }, "peerDependenciesMeta": { "jest-resolve": { "optional": true } } }, "node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.4.tgz", "integrity": "sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.6.4", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.6.3", "jest-validate": "^29.6.3", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve-dependencies": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz", "integrity": "sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.6.4" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.4.tgz", "integrity": "sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw==", "dev": true, "license": "MIT", "dependencies": { "@jest/console": "^29.6.4", "@jest/environment": "^29.6.4", "@jest/test-result": "^29.6.4", "@jest/transform": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.6.3", "jest-environment-node": "^29.6.4", "jest-haste-map": "^29.6.4", "jest-leak-detector": "^29.6.3", "jest-message-util": "^29.6.3", "jest-resolve": "^29.6.4", "jest-runtime": "^29.6.4", "jest-util": "^29.6.3", "jest-watcher": "^29.6.4", "jest-worker": "^29.6.4", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-runtime": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.4.tgz", "integrity": "sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA==", "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.6.4", "@jest/fake-timers": "^29.6.4", "@jest/globals": "^29.6.4", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.6.4", "@jest/transform": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.6.4", "jest-message-util": "^29.6.3", "jest-mock": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.6.4", "jest-snapshot": "^29.6.4", "jest-util": "^29.6.3", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runtime/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jest-snapshot": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.4.tgz", "integrity": "sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.6.4", "@jest/transform": "^29.6.4", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.6.4", "graceful-fs": "^4.2.9", "jest-diff": "^29.6.4", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.6.4", "jest-message-util": "^29.6.3", "jest-util": "^29.6.3", "natural-compare": "^1.4.0", "pretty-format": "^29.6.3", "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-validate": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-watcher": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.4.tgz", "integrity": "sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/test-result": "^29.6.4", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.6.3", "string-length": "^4.0.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-watcher/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-worker": { "version": "29.6.4", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.4.tgz", "integrity": "sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "jest-util": "^29.6.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker/node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, "license": "MIT" }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { "node": ">=4" } }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" }, "engines": { "node": ">=6" } }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" }, "engines": { "node": ">=0.6.0" } }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, "engines": { "node": ">=8" } }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, "engines": { "node": ">=10" } }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, "license": "ISC" }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": "*" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/nock": { "version": "11.9.1", "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.13", "mkdirp": "^0.5.0", "propagate": "^2.0.0" }, "engines": { "node": ">= 8.0" } }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "peerDependenciesMeta": { "encoding": { "optional": true } } }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optimism": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.17.5.tgz", "integrity": "sha512-TEcp8ZwK1RczmvMnvktxHSF2tKgMWjJ71xEFGX5ApLh67VsMSTy1ZUlipJw8W+KaqgOmQ+4pqwkeivY89j+4Vw==", "dev": true, "license": "MIT", "dependencies": { "@wry/context": "^0.7.0", "@wry/trie": "^0.4.3", "tslib": "^2.3.0" } }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, "engines": { "node": ">=8" } }, "node_modules/p-locate/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "license": "ISC", "engines": { "node": "14 || >=16.14" } }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/prettier": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "dev": true, "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=4" } }, "node_modules/pretty-format": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/pretty-format/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true, "license": "MIT" }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, "license": "MIT", "dependencies": { "asap": "~2.0.3" } }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" }, "engines": { "node": ">= 6" } }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true, "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true, "license": "MIT" }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pure-rand": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", "dev": true, "funding": [ { "type": "individual", "url": "https://github.com/sponsors/dubzzz" }, { "type": "opencollective", "url": "https://opencollective.com/fast-check" } ], "license": "MIT" }, "node_modules/pusher-js": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-7.6.0.tgz", "integrity": "sha512-5CJ7YN5ZdC24E0ETraCU5VYFv0IY5ziXhrS0gS5+9Qrro1E4M1lcZhtr9H1H+6jNSLj1LKKAgcLeE1EH9GxMlw==", "dev": true, "license": "MIT", "dependencies": { "@types/express-serve-static-core": "4.17.28", "@types/node": "^14.14.31", "tweetnacl": "^1.0.3" } }, "node_modules/pusher-js/node_modules/@types/node": { "version": "14.18.56", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.56.tgz", "integrity": "sha512-+k+57NVS9opgrEn5l9c0gvD1r6C+PtyhVE4BTnMMRwiEA8ZO8uFcs6Yy2sXIy0eC95ZurBtRSvhZiHXBysbl6w==", "dev": true, "license": "MIT" }, "node_modules/pusher-js/node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true, "license": "Unlicense" }, "node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.6" } }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true, "license": "MIT" }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", "dev": true, "license": "MIT" }, "node_modules/relay-runtime": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-11.0.2.tgz", "integrity": "sha512-xxZkIRnL8kNE1cxmwDXX8P+wSeWLR+0ACFyAiAhvfWWAyjXb+bhjJ2FSsRGlNYfkqaTNEuDqpnodQV1/fF7Idw==", "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "fbjs": "^3.0.0", "invariant": "^2.2.4" } }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, "engines": { "node": ">= 6" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, "engines": { "node": ">=8" } }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/response-iterator": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz", "integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true, "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", "dev": true, "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" }, "engines": { "node": ">=0.10.0" } }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, "engines": { "node": ">=10" } }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" } }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10" } }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" }, "engines": { "node": ">=8" } }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, "engines": { "node": ">=8.0" } }, "node_modules/to-utf8": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==", "dev": true, "license": "MIT" }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" }, "engines": { "node": ">=0.8" } }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, "license": "MIT" }, "node_modules/ts-invariant": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.1.0" }, "engines": { "node": ">=8" } }, "node_modules/ts-jest": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { "optional": true }, "@jest/types": { "optional": true }, "babel-jest": { "optional": true }, "esbuild": { "optional": true } } }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true, "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, "engines": { "node": "*" } }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true, "license": "Unlicense" }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { "node": ">=14.17" } }, "node_modules/ua-parser-js": { "version": "1.0.35", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/ua-parser-js" }, { "type": "paypal", "url": "https://paypal.me/faisalman" } ], "license": "MIT", "engines": { "node": "*" } }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" }, "bin": { "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/urql": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/urql/-/urql-2.2.3.tgz", "integrity": "sha512-XMkSYJKW9s4ZlbSuxcUz3fTBIykOn0sGileRXQeyZpaRBXJPVz5saSY05k7jdefNxShZtTI+/nr7PYUWQertfg==", "dev": true, "license": "MIT", "dependencies": { "@urql/core": "^2.6.1", "wonka": "^4.0.14" }, "peerDependencies": { "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", "react": ">= 16.8.0" } }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true, "license": "MIT", "bin": { "uuid": "bin/uuid" } }, "node_modules/v8-to-istanbul": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0" }, "engines": { "node": ">=10.12.0" } }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "engines": [ "node >=0.6.0" ], "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "node_modules/verror/node_modules/extsprintf": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", "dev": true, "engines": [ "node >=0.6.0" ], "license": "MIT" }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" }, "engines": { "node": ">= 8" } }, "node_modules/wonka": { "version": "4.0.15", "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==", "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" }, "engines": { "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/ws": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", "dev": true, "license": "MIT", "dependencies": { "async-limiter": "~1.0.0" } }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" } }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==", "dev": true, "license": "MIT" }, "node_modules/zen-observable-ts": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz", "integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==", "dev": true, "license": "MIT", "dependencies": { "zen-observable": "0.8.15" } } } } graphql-ruby-2.2.17/javascript_client/package.json000066400000000000000000000025061476434635200222330ustar00rootroot00000000000000{ "name": "graphql-ruby-client", "version": "1.13.2", "description": "JavaScript client for graphql-ruby", "main": "index.js", "types": "index.d.ts", "repository": "https://github.com/rmosolgo/graphql-ruby", "author": "Robert Mosolgo", "license": "LGPL-3.0", "bin": "./cli.js", "devDependencies": { "@apollo/client": ">=3.3.13", "@rails/actioncable": "^7.0.0", "@types/glob": "^7.1.1", "@types/jest": "^25.1.2", "@types/minimist": "^1.2.0", "@types/node": "^13.7.1", "@types/pako": "^1.0.1", "@types/pusher-js": "^4.2.2", "@types/rails__actioncable": "^6.1.6", "@types/react": "^17.0.0", "@types/relay-runtime": "^14.0.0", "@types/zen-observable": "^0.8.2", "ably": "1.2.6", "graphql": ">=15.0.0", "jest": "^29.0.0", "nock": "^11.0.0", "pako": "^2.0.3", "prettier": "^1.19.1", "pusher-js": "^7.0.3", "relay-runtime": "11.0.2", "ts-jest": "^29.0.0", "typescript": "5.3.3", "urql": "^2.2.2" }, "scripts": { "test": "tsc && jest", "prepublishOnly": "tsc" }, "dependencies": { "glob": "^10.0.0", "minimist": "^1.2.0" }, "peerDependencies": { "@apollo/client": ">=3.3.6", "graphql": ">=14.3.1" }, "prettier": { "semi": false, "trailingComma": "none" }, "engines": { "node": ">=10" } } graphql-ruby-2.2.17/javascript_client/readme.md000066400000000000000000000011211476434635200215140ustar00rootroot00000000000000Find the `graphql-ruby-client` docs on the [GraphQL-Ruby website](https://graphql-ruby.org/javascript_client/overview). ## License `graphql-ruby-client` is available under the LGPLv3 license; [graphql-pro](https://graphql.pro) customers are granted a special commercial license. ## Development - With GraphQL-Ruby: - Install the dependencies with `rake js:install` - Run the tests with `rake js:test` - Stand-alone: - Install dependencies `yarn install` - Run the tests `yarn run test` - Run the TypeScript compiler: `yarn tsc -w` - Install for local development with `npm link .` graphql-ruby-2.2.17/javascript_client/src/000077500000000000000000000000001476434635200205315ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__generated__/000077500000000000000000000000001476434635200232635ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__generated__/Card_card.graphql.js000066400000000000000000000014471476434635200271260ustar00rootroot00000000000000/** * @flow */ /* eslint-disable */ 'use strict'; /*:: import type {ConcreteFragment} from 'relay-runtime'; export type Card_card = {| +id: string; +name: string; +imageUrl: string; |}; */ const fragment /*: ConcreteFragment*/ = { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "Card_card", "selections": [ { "kind": "ScalarField", "alias": null, "args": null, "name": "id", "storageKey": null }, { "kind": "ScalarField", "alias": null, "args": null, "name": "name", "storageKey": null }, { "kind": "ScalarField", "alias": "imageUrl", "args": null, "name": "image_url", "storageKey": null } ], "type": "Card" }; module.exports = fragment; graphql-ruby-2.2.17/javascript_client/src/__generated__/GetStuff.graphql.js000066400000000000000000000113141476434635200270050ustar00rootroot00000000000000/** * This file was generated by: * relay-compiler * * @providesModule AppFeedQuery.graphql * @generated SignedSource<> * @relayHash 353e010cb78d082b29cb63ee7e9027b3 * @flow * @nogrep */ 'use strict'; /*:: import type {ConcreteBatch} from 'relay-runtime'; */ /* eslint-disable comma-dangle, quotes */ /* query AppFeedQuery { feed(type: NEW, limit: 5) { ...Feed } } fragment Feed on Entry { repository { owner { login } name } ...FeedEntry } fragment FeedEntry on Entry { repository { owner { login } name stargazers_count } postedBy { login } } */ const batch /*: ConcreteBatch*/ = { "fragment": { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "AppFeedQuery", "selections": [ { "kind": "LinkedField", "alias": null, "args": [ { "kind": "Literal", "name": "limit", "value": 5, "type": "Int" }, { "kind": "Literal", "name": "type", "value": "NEW", "type": "FeedType!" } ], "concreteType": "Entry", "name": "feed", "plural": true, "selections": [ { "kind": "FragmentSpread", "name": "Feed", "args": null } ], "storageKey": "feed{\"limit\":5,\"type\":\"NEW\"}" } ], "type": "Query" }, "id": null, "kind": "Batch", "metadata": {}, "name": "AppFeedQuery", "query": { "argumentDefinitions": [], "kind": "Root", "name": "AppFeedQuery", "operation": "query", "selections": [ { "kind": "LinkedField", "alias": null, "args": [ { "kind": "Literal", "name": "limit", "value": 5, "type": "Int" }, { "kind": "Literal", "name": "type", "value": "NEW", "type": "FeedType!" } ], "concreteType": "Entry", "name": "feed", "plural": true, "selections": [ { "kind": "InlineFragment", "type": "Entry", "selections": [ { "kind": "LinkedField", "alias": null, "args": null, "concreteType": "Repository", "name": "repository", "plural": false, "selections": [ { "kind": "LinkedField", "alias": null, "args": null, "concreteType": "User", "name": "owner", "plural": false, "selections": [ { "kind": "ScalarField", "alias": null, "args": null, "name": "login", "storageKey": null } ], "storageKey": null }, { "kind": "ScalarField", "alias": null, "args": null, "name": "name", "storageKey": null }, { "kind": "ScalarField", "alias": null, "args": null, "name": "stargazers_count", "storageKey": null } ], "storageKey": null }, { "kind": "LinkedField", "alias": null, "args": null, "concreteType": "User", "name": "postedBy", "plural": false, "selections": [ { "kind": "ScalarField", "alias": null, "args": null, "name": "login", "storageKey": null } ], "storageKey": null } ] } ], "storageKey": "feed{\"limit\":5,\"type\":\"NEW\"}" } ] }, "text": "query AppFeedQuery {\n feed(type: NEW, limit: 5) {\n ...Feed\n }\n}\n\nfragment Feed on Entry {\n repository {\n owner {\n login\n }\n name\n }\n ...FeedEntry\n}\n\nfragment FeedEntry on Entry {\n repository {\n owner {\n login\n }\n name\n stargazers_count\n }\n postedBy {\n login\n }\n}\n" }; module.exports = batch; graphql-ruby-2.2.17/javascript_client/src/__tests__/000077500000000000000000000000001476434635200224675ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__tests__/__snapshots__/000077500000000000000000000000001476434635200253055ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__tests__/__snapshots__/syncTest.ts.snap000066400000000000000000000512131476434635200304330ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`sync operations Input files Merges fragments and operations across files 1`] = ` [ { "alias": "4568c28d403794e011363caf815ec827", "body": "fragment Frag1 on Query { moreStuff } query GetStuff { ...Frag1 }", "name": "GetStuff", }, { "alias": "faf462be033e16dd2a56130d56a9192f", "body": "fragment Frag1 on Query { moreStuff } fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } query GetStuff2 { stuff ...Frag1 ...Frag2 }", "name": "GetStuff2", }, { "alias": "aab385a1685772ad520fc70d468030fa", "body": "fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } fragment Frag4 on Query { evenMoreStuff { stuffInside } } query GetStuff3 { stuff { withStuffInside } ...Frag2 ...Frag4 }", "name": "GetStuff3", }, { "alias": "b2cb0b317d071f9f38905fba21d73258", "body": "query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } }", "name": "GetStuffIsolated", }, { "alias": "6cdae165fd6dc5dc5900e5a2bba90cc2", "body": "query GetStuffIsolated2 { things { existHere } }", "name": "GetStuffIsolated2", }, ] `; exports[`sync operations Input files Uses mode: file to process each file separately 1`] = ` [ { "alias": "664225b943e29ea8c6aae40bbde8923a", "body": "fragment Frag1 on Query { moreStuff }", "name": "", }, { "alias": "269bbe8bbe7f6a0b9dae7f98b45a9675", "body": "fragment Frag2 on Query { ...Frag3 }", "name": "", }, { "alias": "d12578840c6518c746b125ae2e7a8ab1", "body": "fragment Frag3 on Query { evenMoreStuff }", "name": "", }, { "alias": "1a1b6154fb1db8bc6652edfbd7d9ac8a", "body": "fragment Frag4 on Query { evenMoreStuff { stuffInside } }", "name": "", }, { "alias": "8ab1711fcbb7befc98d06ef7d155fd81", "body": "query GetStuff { ...Frag1 }", "name": "GetStuff", }, { "alias": "cf517696fbd9ec204cd402f48c831090", "body": "query GetStuff2 { stuff ...Frag1 ...Frag2 }", "name": "GetStuff2", }, { "alias": "1e5290206d87a4da749118d84f7e2c65", "body": "query GetStuff3 { stuff { withStuffInside } ...Frag2 ...Frag4 }", "name": "GetStuff3", }, { "alias": "b2cb0b317d071f9f38905fba21d73258", "body": "query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } }", "name": "GetStuffIsolated", }, { "alias": "6cdae165fd6dc5dc5900e5a2bba90cc2", "body": "query GetStuffIsolated2 { things { existHere } }", "name": "GetStuffIsolated2", }, ] `; exports[`sync operations Logging Can be quieted with quiet: true 1`] = `[]`; exports[`sync operations Logging Logs progress 1`] = ` [ [ "Syncing 5 operations to bogus...", ], [ "Generating client module in src/OperationStoreClient.js...", ], [ "✓ Done!", ], ] `; exports[`sync operations Printing the result prints failure and sends the message to the promise 1`] = ` [ [ "Syncing 5 operations to http://example.com/stored_operations/sync...", ], [ "[Sync] 2 Headers:", ], [ "[Sync] Content-Type: application/json", ], [ "[Sync] Content-Length: 1132", ], [ "[Sync] Data:", "{"operations":[{"name":"GetStuff","body":"fragment Frag1 on Query {\\n moreStuff\\n}\\n\\nquery GetStuff {\\n ...Frag1\\n}","alias":"4568c28d403794e011363caf815ec827"},{"name":"GetStuff2","body":"fragment Frag1 on Query {\\n moreStuff\\n}\\n\\nfragment Frag2 on Query {\\n ...Frag3\\n}\\n\\nfragment Frag3 on Query {\\n evenMoreStuff\\n}\\n\\nquery GetStuff2 {\\n stuff\\n ...Frag1\\n ...Frag2\\n}","alias":"faf462be033e16dd2a56130d56a9192f"},{"name":"GetStuff3","body":"fragment Frag2 on Query {\\n ...Frag3\\n}\\n\\nfragment Frag3 on Query {\\n evenMoreStuff\\n}\\n\\nfragment Frag4 on Query {\\n evenMoreStuff {\\n stuffInside\\n }\\n}\\n\\nquery GetStuff3 {\\n stuff {\\n withStuffInside\\n }\\n ...Frag2\\n ...Frag4\\n}","alias":"aab385a1685772ad520fc70d468030fa"},{"name":"GetStuffIsolated","body":"query GetStuffIsolated {\\n ...FragIsolated\\n things {\\n existHere\\n }\\n}\\n\\nfragment FragIsolated on Query {\\n evenMoreStuff {\\n stuffInside\\n }\\n}","alias":"b2cb0b317d071f9f38905fba21d73258"},{"name":"GetStuffIsolated2","body":"query GetStuffIsolated2 {\\n things {\\n existHere\\n }\\n}","alias":"6cdae165fd6dc5dc5900e5a2bba90cc2"}]}", ], [ "[Sync] Response Headers: ", "{"content-type":"application/json"}", ], [ "[Sync] Response Body: ", "{"errors":{"4568c28d403794e011363caf815ec827":["something"]},"failed":["4568c28d403794e011363caf815ec827"],"added":["defg"],"not_modified":[]}", ], [ " 0 added", ], [ " 0 not modified", ], [ " 1 failed", ], ] `; exports[`sync operations Printing the result prints failure and sends the message to the promise 2`] = ` [ [ "Sync failed, errors:", ], [ " GetStuff:", ], [ " ✘ something", ], ] `; exports[`sync operations Printing the result prints success 1`] = ` [ [ "Syncing 5 operations to http://example.com/stored_operations/sync...", ], [ "[Sync] 2 Headers:", ], [ "[Sync] Content-Type: application/json", ], [ "[Sync] Content-Length: 1132", ], [ "[Sync] Data:", "{"operations":[{"name":"GetStuff","body":"fragment Frag1 on Query {\\n moreStuff\\n}\\n\\nquery GetStuff {\\n ...Frag1\\n}","alias":"4568c28d403794e011363caf815ec827"},{"name":"GetStuff2","body":"fragment Frag1 on Query {\\n moreStuff\\n}\\n\\nfragment Frag2 on Query {\\n ...Frag3\\n}\\n\\nfragment Frag3 on Query {\\n evenMoreStuff\\n}\\n\\nquery GetStuff2 {\\n stuff\\n ...Frag1\\n ...Frag2\\n}","alias":"faf462be033e16dd2a56130d56a9192f"},{"name":"GetStuff3","body":"fragment Frag2 on Query {\\n ...Frag3\\n}\\n\\nfragment Frag3 on Query {\\n evenMoreStuff\\n}\\n\\nfragment Frag4 on Query {\\n evenMoreStuff {\\n stuffInside\\n }\\n}\\n\\nquery GetStuff3 {\\n stuff {\\n withStuffInside\\n }\\n ...Frag2\\n ...Frag4\\n}","alias":"aab385a1685772ad520fc70d468030fa"},{"name":"GetStuffIsolated","body":"query GetStuffIsolated {\\n ...FragIsolated\\n things {\\n existHere\\n }\\n}\\n\\nfragment FragIsolated on Query {\\n evenMoreStuff {\\n stuffInside\\n }\\n}","alias":"b2cb0b317d071f9f38905fba21d73258"},{"name":"GetStuffIsolated2","body":"query GetStuffIsolated2 {\\n things {\\n existHere\\n }\\n}","alias":"6cdae165fd6dc5dc5900e5a2bba90cc2"}]}", ], ] `; exports[`sync operations Printing the result prints success 2`] = ` [ [ "[Sync] Response Headers: ", "{"content-type":"application/json"}", ], [ "[Sync] Response Body: ", "{"errors":{},"failed":[],"added":["defg"],"not_modified":["xyz","123"]}", ], [ " 1 added", ], [ " 2 not modified", ], [ " 0 failed", ], [ "Generating client module in src/OperationStoreClient.js...", ], [ "✓ Done!", ], ] `; exports[`sync operations Printing the result prints success 3`] = `[]`; exports[`sync operations Relay support Uses Apollo Android OperationOutput JSON files 1`] = ` [ { "alias": "aba626ea9bdf465954e89e5590eb2c1a", "body": "mutation RemoveTodoMutation($input: RemoveTodoInput!) { removeTodo(input: $input) { deletedTodoId user { completedCount totalCount id } } }", }, { "alias": "67c2bc8aa3185a209d6651b4feb63c04", "body": "query appQuery( $userId: String ) { user(id: $userId) { ...TodoApp_user id } } fragment TodoApp_user on User { id userId totalCount ...TodoListFooter_user ...TodoList_user } fragment TodoListFooter_user on User { id userId completedCount todos(first: 2147483647) { edges { node { id complete __typename } cursor } pageInfo { endCursor hasNextPage } } totalCount } fragment TodoList_user on User { todos(first: 2147483647) { edges { node { id complete ...Todo_todo __typename } cursor } pageInfo { endCursor hasNextPage } } id userId totalCount completedCount ...Todo_user } fragment Todo_todo on Todo { complete id text } fragment Todo_user on User { id userId totalCount completedCount } ", }, { "alias": "db9904c31d91416f21d45fe3d153884c", "body": "mutation MarkAllTodosMutation( $input: MarkAllTodosInput! ) { markAllTodos(input: $input) { changedTodos { id complete } user { id completedCount } } } ", }, { "alias": "2eb8c9941fdb3117fdbc08d15fab62d0", "body": "mutation AddTodoMutation( $input: AddTodoInput! ) { addTodo(input: $input) { todoEdge { __typename cursor node { complete id text } } user { id totalCount } } } ", }, { "alias": "d970fd7dbf118794415dec7324d463e3", "body": "mutation RenameTodoMutation( $input: RenameTodoInput! ) { renameTodo(input: $input) { todo { id text } } } ", }, { "alias": "a49217db31a8be3f4107763b957d5fca", "body": "mutation RemoveCompletedTodosMutation( $input: RemoveCompletedTodosInput! ) { removeCompletedTodos(input: $input) { deletedTodoIds user { completedCount totalCount id } } } ", }, { "alias": "d7dda774dcfa32fe0d9661e01cac9a4a", "body": "mutation ChangeTodoStatusMutation( $input: ChangeTodoStatusInput! ) { changeTodoStatus(input: $input) { todo { id complete } user { id completedCount } } } ", }, ] `; exports[`sync operations Relay support Uses Apollo Codegen JSON files 1`] = ` [ { "alias": "22cc98c61c1402c92b230b7c515e07eb793a5152c388b015e86df4652ec58156", "body": "mutation UpdateSomething($name: String!) { updateSomething(name: $name) { __typename name } }", "name": "UpdateSomething", }, { "alias": "688df2ea182541c70a34c55ca056dc249014bf9f33c64eee527120c714e936fc", "body": "query getHelloWorld { helloWorld ...MoreFields } fragment MoreFields on Query { __typename }", "name": "getHelloWorld", }, ] `; exports[`sync operations Relay support Uses Relay generated .js files 1`] = ` [ { "alias": "353e010cb78d082b29cb63ee7e9027b3", "body": "query AppFeedQuery { feed(type: NEW, limit: 5) { ...Feed } } fragment Feed on Entry { repository { owner { login } name } ...FeedEntry } fragment FeedEntry on Entry { repository { owner { login } name stargazers_count } postedBy { login } } ", "name": "AppFeedQuery", }, ] `; exports[`sync operations Relay support Uses relay --persist-output JSON files 1`] = ` [ { "alias": "aba626ea9bdf465954e89e5590eb2c1a", "body": "mutation RemoveTodoMutation( $input: RemoveTodoInput! ) { removeTodo(input: $input) { deletedTodoId user { completedCount totalCount id } } } ", }, { "alias": "67c2bc8aa3185a209d6651b4feb63c04", "body": "query appQuery( $userId: String ) { user(id: $userId) { ...TodoApp_user id } } fragment TodoApp_user on User { id userId totalCount ...TodoListFooter_user ...TodoList_user } fragment TodoListFooter_user on User { id userId completedCount todos(first: 2147483647) { edges { node { id complete __typename } cursor } pageInfo { endCursor hasNextPage } } totalCount } fragment TodoList_user on User { todos(first: 2147483647) { edges { node { id complete ...Todo_todo __typename } cursor } pageInfo { endCursor hasNextPage } } id userId totalCount completedCount ...Todo_user } fragment Todo_todo on Todo { complete id text } fragment Todo_user on User { id userId totalCount completedCount } ", }, { "alias": "db9904c31d91416f21d45fe3d153884c", "body": "mutation MarkAllTodosMutation( $input: MarkAllTodosInput! ) { markAllTodos(input: $input) { changedTodos { id complete } user { id completedCount } } } ", }, { "alias": "2eb8c9941fdb3117fdbc08d15fab62d0", "body": "mutation AddTodoMutation( $input: AddTodoInput! ) { addTodo(input: $input) { todoEdge { __typename cursor node { complete id text } } user { id totalCount } } } ", }, { "alias": "d970fd7dbf118794415dec7324d463e3", "body": "mutation RenameTodoMutation( $input: RenameTodoInput! ) { renameTodo(input: $input) { todo { id text } } } ", }, { "alias": "a49217db31a8be3f4107763b957d5fca", "body": "mutation RemoveCompletedTodosMutation( $input: RemoveCompletedTodosInput! ) { removeCompletedTodos(input: $input) { deletedTodoIds user { completedCount totalCount id } } } ", }, { "alias": "d7dda774dcfa32fe0d9661e01cac9a4a", "body": "mutation ChangeTodoStatusMutation( $input: ChangeTodoStatusInput! ) { changeTodoStatus(input: $input) { todo { id complete } user { id completedCount } } } ", }, ] `; exports[`sync operations custom file processing options Adds .graphql to the glob if needed 1`] = ` [ { "alias": "b8086942c2fbb6ac69b97cbade848033", "body": "query GetStuff { stuff }", "name": "GetStuff", }, ] `; exports[`sync operations custom file processing options Adds .graphql to the glob if needed 2`] = ` [ { "alias": "b8086942c2fbb6ac69b97cbade848033", "body": "query GetStuff { stuff }", "name": "GetStuff", }, ] `; exports[`sync operations custom file processing options Uses a custom hash function if provided 1`] = ` [ { "alias": "GETSTUFF", "body": "query GetStuff { stuff }", "name": "GetStuff", }, ] `; exports[`sync operations generating artifacts without syncing works with persisted query manifest 1`] = ` " /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = { "TestQuery1": "4a29162b05ee4d82ad02e8f50af4bf112f47181ec558a7100a", "TestQuery2": "xyz-123" } /** * The client who synced these operations with the server * @return {String} * @private */ var _client = "test-1" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + "/" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error("Failed to find persisted alias for operation name: " + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient " `; exports[`sync operations generating artifacts without syncing works without a URL 1`] = ` " /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = { "GetStuff": "b8086942c2fbb6ac69b97cbade848033" } /** * The client who synced these operations with the server * @return {String} * @private */ var _client = "test-1" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + "/" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error("Failed to find persisted alias for operation name: " + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient " `; exports[`sync operations verbose Adds debug output 1`] = ` [ [ "[Sync] glob: ", "./src/__tests__/documents**/*.graphql*", ], [ "[Sync] 1 files:", ], [ "[Sync] - src/__tests__/documents/doc1.graphql", ], [ "Syncing 1 operations to bogus...", ], [ "Verbose!", ], [ "Generating client module in src/OperationStoreClient.js...", ], [ "✓ Done!", ], ] `; graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/000077500000000000000000000000001476434635200252715ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/apollo.config.js000066400000000000000000000003751476434635200303660ustar00rootroot00000000000000// apollo client:codegen gen/output.json --target json module.exports = { client: { service: { name: "testSchema", localSchemaFile: "./schema.graphql", }, includes: ["./*.ts"], mergeInFieldsFromFragmentSpreads: true, } } graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/fragment.ts000066400000000000000000000001651476434635200274460ustar00rootroot00000000000000import { gql } from '@apollo/client'; export const MORE_FIELDS = gql` fragment MoreFields on Query { __typename } ` graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/gen/000077500000000000000000000000001476434635200260425ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/gen/output.json000066400000000000000000000056271476434635200303070ustar00rootroot00000000000000{ "operations": [ { "filePath": "file:///Users/rmosolgo/code/graphql-ruby/javascript_client/src/sync/__tests__/apolloExample/mutation.ts", "operationName": "UpdateSomething", "operationType": "mutation", "rootType": "Mutation", "variables": [ { "name": "name", "type": "String!" } ], "source": "mutation UpdateSomething($name: String!) {\n updateSomething(name: $name) {\n __typename\n name\n somethingElse @client\n }\n}", "fields": [ { "responseName": "updateSomething", "fieldName": "updateSomething", "type": "UpdateSomethingPayload", "args": [ { "name": "name", "value": { "kind": "Variable", "variableName": "name" }, "type": "String!" } ], "isConditional": false, "isDeprecated": false, "fields": [ { "responseName": "__typename", "fieldName": "__typename", "type": "String!", "isConditional": false }, { "responseName": "name", "fieldName": "name", "type": "String!", "isConditional": false, "isDeprecated": false } ], "fragmentSpreads": [], "inlineFragments": [] } ], "fragmentSpreads": [], "inlineFragments": [], "fragmentsReferenced": [], "sourceWithFragments": "mutation UpdateSomething($name: String!) {\n updateSomething(name: $name) {\n __typename\n name\n }\n}", "operationId": "22cc98c61c1402c92b230b7c515e07eb793a5152c388b015e86df4652ec58156" }, { "filePath": "file:///Users/rmosolgo/code/graphql-ruby/javascript_client/src/sync/__tests__/apolloExample/query.ts", "operationName": "getHelloWorld", "operationType": "query", "rootType": "Query", "variables": [], "source": "query getHelloWorld {\n helloWorld\n ...MoreFields\n}", "fields": [ { "responseName": "helloWorld", "fieldName": "helloWorld", "type": "String!", "isConditional": false, "isDeprecated": false } ], "fragmentSpreads": [ "MoreFields" ], "inlineFragments": [], "fragmentsReferenced": [ "MoreFields" ], "sourceWithFragments": "query getHelloWorld {\n helloWorld\n ...MoreFields\n}\nfragment MoreFields on Query {\n __typename\n}", "operationId": "688df2ea182541c70a34c55ca056dc249014bf9f33c64eee527120c714e936fc" } ], "fragments": [ { "typeCondition": "Query", "possibleTypes": [ "Query" ], "fragmentName": "MoreFields", "filePath": "file:///Users/rmosolgo/code/graphql-ruby/javascript_client/src/sync/__tests__/apolloExample/fragment.ts", "source": "fragment MoreFields on Query {\n __typename\n}", "fields": [ { "responseName": "__typename", "fieldName": "__typename", "type": "String!", "isConditional": false } ], "fragmentSpreads": [], "inlineFragments": [] } ], "typesUsed": [], "unionTypes": [], "interfaceTypes": [] } graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/mutation.ts000066400000000000000000000002431476434635200275000ustar00rootroot00000000000000import { gql } from '@apollo/client'; export const UPDATE_SOMETHING = gql` mutation UpdateSomething($name: String!) { updateSomething(name: $name) { name } } ` graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/query.ts000066400000000000000000000002741476434635200270110ustar00rootroot00000000000000import { gql } from '@apollo/client'; import { MORE_FIELDS } from './fragment'; export const GET_HELLO_WORLD = gql` query getHelloWorld { helloWorld ... MoreFields } ${MORE_FIELDS} ` graphql-ruby-2.2.17/javascript_client/src/__tests__/apolloExample/schema.graphql000066400000000000000000000002421476434635200301070ustar00rootroot00000000000000type Query { helloWorld: String! } type Mutation { updateSomething(name: String!): UpdateSomethingPayload } type UpdateSomethingPayload { name: String! } graphql-ruby-2.2.17/javascript_client/src/__tests__/cliTest.ts000066400000000000000000000025321476434635200244500ustar00rootroot00000000000000var childProcess = require("child_process") describe("CLI", () => { it("exits 1 on error", () => { expect(() => { childProcess.execSync("node ./cli.js sync", {stdio: "pipe"}) }).toThrow("Client name must be provided for sync") }) it("exits 0 on OK", () => { childProcess.execSync("node ./cli.js sync -h", {stdio: "pipe"}) }) it("runs with some options", () => { var buffer = childProcess.execSync("node ./cli.js sync --client=something --header=Abcd:efgh --header=\"Abc: 123 45\" --changeset-version=2023-01-01 --mode=file --path=\"**/doc1.graphql\" --verbose", {stdio: "pipe"}) var response = buffer.toString().replace(/\033\[[0-9;]*m/g, "") expect(response).toEqual("No URL; Generating artifacts without syncing them\n[Sync] glob: **/doc1.graphql\n[Sync] 1 files:\n[Sync] - src/__tests__/documents/doc1.graphql\nGenerating client module in src/OperationStoreClient.js...\n✓ Done!\n") }) it("runs with just one header", () => { var buffer = childProcess.execSync("node ./cli.js sync --client=something --header=Ab-cd:ef-gh --mode=file --path=\"**/doc1.graphql\"", {stdio: "pipe"}) var response = buffer.toString().replace(/\033\[[0-9;]*m/g, "") expect(response).toEqual("No URL; Generating artifacts without syncing them\nGenerating client module in src/OperationStoreClient.js...\n✓ Done!\n") }) }) graphql-ruby-2.2.17/javascript_client/src/__tests__/documents/000077500000000000000000000000001476434635200244705ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__tests__/documents/doc1.graphql000066400000000000000000000000331476434635200266720ustar00rootroot00000000000000query GetStuff { stuff } graphql-ruby-2.2.17/javascript_client/src/__tests__/example-apollo-android-operation-output.json000066400000000000000000000055541476434635200332640ustar00rootroot00000000000000{ "aba626ea9bdf465954e89e5590eb2c1a": { "name": "RemoveTodoMutation", "source": "mutation RemoveTodoMutation(\n $input: RemoveTodoInput!\n) {\n removeTodo(input: $input) {\n deletedTodoId\n user {\n completedCount\n totalCount\n thing @client\n id\n }\n }\n}\n" }, "67c2bc8aa3185a209d6651b4feb63c04": { "name": "appQuery", "source": "query appQuery(\n $userId: String\n) {\n user(id: $userId) {\n ...TodoApp_user\n id\n }\n}\n\nfragment TodoApp_user on User {\n id\n userId\n totalCount\n ...TodoListFooter_user\n ...TodoList_user\n}\n\nfragment TodoListFooter_user on User {\n id\n userId\n completedCount\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n totalCount\n}\n\nfragment TodoList_user on User {\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n ...Todo_todo\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n userId\n totalCount\n completedCount\n ...Todo_user\n}\n\nfragment Todo_todo on Todo {\n complete\n id\n text\n}\n\nfragment Todo_user on User {\n id\n userId\n totalCount\n completedCount\n}\n" }, "db9904c31d91416f21d45fe3d153884c": { "name": "MarkAllTodosMutation", "source": "mutation MarkAllTodosMutation(\n $input: MarkAllTodosInput!\n) {\n markAllTodos(input: $input) {\n changedTodos {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n" }, "2eb8c9941fdb3117fdbc08d15fab62d0": { "name": "AddTodoMutation", "source": "mutation AddTodoMutation(\n $input: AddTodoInput!\n) {\n addTodo(input: $input) {\n todoEdge {\n __typename\n cursor\n node {\n complete\n id\n text\n }\n }\n user {\n id\n totalCount\n }\n }\n}\n" }, "d970fd7dbf118794415dec7324d463e3": { "name": "RenameTodoMutation", "source": "mutation RenameTodoMutation(\n $input: RenameTodoInput!\n) {\n renameTodo(input: $input) {\n todo {\n id\n text\n }\n }\n}\n" }, "a49217db31a8be3f4107763b957d5fca": { "name": "RemoveCompletedTodosMutation", "source": "mutation RemoveCompletedTodosMutation(\n $input: RemoveCompletedTodosInput!\n) {\n removeCompletedTodos(input: $input) {\n deletedTodoIds\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n" }, "d7dda774dcfa32fe0d9661e01cac9a4a": { "name": "ChangeTodoStatusMutation", "source": "mutation ChangeTodoStatusMutation(\n $input: ChangeTodoStatusInput!\n) {\n changeTodoStatus(input: $input) {\n todo {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n" } } graphql-ruby-2.2.17/javascript_client/src/__tests__/example-relay-persisted-queries.json000066400000000000000000000047301476434635200316060ustar00rootroot00000000000000{ "aba626ea9bdf465954e89e5590eb2c1a": "mutation RemoveTodoMutation(\n $input: RemoveTodoInput!\n) {\n removeTodo(input: $input) {\n deletedTodoId\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n", "67c2bc8aa3185a209d6651b4feb63c04": "query appQuery(\n $userId: String\n) {\n user(id: $userId) {\n ...TodoApp_user\n id\n }\n}\n\nfragment TodoApp_user on User {\n id\n userId\n totalCount\n ...TodoListFooter_user\n ...TodoList_user\n}\n\nfragment TodoListFooter_user on User {\n id\n userId\n completedCount\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n totalCount\n}\n\nfragment TodoList_user on User {\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n ...Todo_todo\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n userId\n totalCount\n completedCount\n ...Todo_user\n}\n\nfragment Todo_todo on Todo {\n complete\n id\n text\n}\n\nfragment Todo_user on User {\n id\n userId\n totalCount\n completedCount\n}\n", "db9904c31d91416f21d45fe3d153884c": "mutation MarkAllTodosMutation(\n $input: MarkAllTodosInput!\n) {\n markAllTodos(input: $input) {\n changedTodos {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n", "2eb8c9941fdb3117fdbc08d15fab62d0": "mutation AddTodoMutation(\n $input: AddTodoInput!\n) {\n addTodo(input: $input) {\n todoEdge {\n __typename\n cursor\n node {\n complete\n id\n text\n }\n }\n user {\n id\n totalCount\n }\n }\n}\n", "d970fd7dbf118794415dec7324d463e3": "mutation RenameTodoMutation(\n $input: RenameTodoInput!\n) {\n renameTodo(input: $input) {\n todo {\n id\n text\n }\n }\n}\n", "a49217db31a8be3f4107763b957d5fca": "mutation RemoveCompletedTodosMutation(\n $input: RemoveCompletedTodosInput!\n) {\n removeCompletedTodos(input: $input) {\n deletedTodoIds\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n", "d7dda774dcfa32fe0d9661e01cac9a4a": "mutation ChangeTodoStatusMutation(\n $input: ChangeTodoStatusInput!\n) {\n changeTodoStatus(input: $input) {\n todo {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n" } graphql-ruby-2.2.17/javascript_client/src/__tests__/indexTest.ts000066400000000000000000000014761476434635200250160ustar00rootroot00000000000000import {sync} from "../index" import childProcess from "child_process" describe("root module", () => { it("exports the sync function", () => { expect(sync).toBeInstanceOf(Function) }) it("exports things at root level", () => { // Make sure that the compiled JavaScript // has all the expected exports. var testScript = "var client = require('./index'); console.log(JSON.stringify({ keys: Object.keys(client).sort() }))" var output = childProcess.execSync("node -e \"" + testScript + "\"") var outputData = JSON.parse(output.toString()) var expectedKeys = [ "AblyLink", "ActionCableLink", "PusherLink", "addGraphQLSubscriptions", "createRelaySubscriptionHandler", "generateClient", "sync" ] expect(outputData.keys).toEqual(expectedKeys) }) }) graphql-ruby-2.2.17/javascript_client/src/__tests__/project/000077500000000000000000000000001476434635200241355ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/__tests__/project/frag_1.graphql000066400000000000000000000000501476434635200266470ustar00rootroot00000000000000fragment Frag1 on Query { moreStuff } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/frag_2.graphql000066400000000000000000000000471476434635200266560ustar00rootroot00000000000000fragment Frag2 on Query { ...Frag3 } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/frag_3.graphql000066400000000000000000000000541476434635200266550ustar00rootroot00000000000000fragment Frag3 on Query { evenMoreStuff } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/frag_4.graphql000066400000000000000000000001021476434635200266500ustar00rootroot00000000000000fragment Frag4 on Query { evenMoreStuff { stuffInside } } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/op_1.graphql000066400000000000000000000000361476434635200263520ustar00rootroot00000000000000query GetStuff { ...Frag1 } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/op_2.graphql000066400000000000000000000000621476434635200263520ustar00rootroot00000000000000query GetStuff2 { stuff ...Frag1 ...Frag2 } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/op_3.graphql000066400000000000000000000001141476434635200263510ustar00rootroot00000000000000query GetStuff3 { stuff { withStuffInside } ...Frag2 ...Frag4 } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/op_isolated_1.graphql000066400000000000000000000002241476434635200302350ustar00rootroot00000000000000query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } } graphql-ruby-2.2.17/javascript_client/src/__tests__/project/op_isolated_2.graphql000066400000000000000000000000721476434635200302370ustar00rootroot00000000000000query GetStuffIsolated2 { things { existHere } } graphql-ruby-2.2.17/javascript_client/src/__tests__/syncTest.ts000066400000000000000000000320661476434635200246620ustar00rootroot00000000000000import sync from "../sync" import Logger from "../sync/logger" var fs = require("fs") var nock = require("nock") interface MockOperation { alias: string, } interface MockPayload { operations: MockOperation[], generatedCode: string, } interface MockedObject { mock: { calls: object } } describe("sync operations", () => { beforeEach(() => { global.console.error = jest.fn() global.console.log = jest.fn() if (fs.existsSync("./src/OperationStoreClient.js")) { fs.unlinkSync("./src/OperationStoreClient.js") } }) afterEach(() => { jest.clearAllMocks(); }) describe("generating artifacts without syncing", () => { it("works without a URL", () => { var options = { client: "test-1", path: "./src/__tests__/documents", } return sync(options).then(function() { var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(generatedCode).toMatch('"GetStuff": "b8086942c2fbb6ac69b97cbade848033"') expect(generatedCode).toMatchSnapshot() }) }) it("works with persisted query manifest", () => { var options = { client: "test-1", outfile: "./src/OperationStoreClient.js", apolloPersistedQueryManifest: "./src/sync/__tests__/generate-persisted-query-manifest.json", } return sync(options).then(function() { var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(generatedCode).toMatch('"TestQuery2": "xyz-123"') expect(generatedCode).toMatchSnapshot() }) }) }) describe("custom HTTP options", () => { it("uses the provided `send` option & provided URL", () => { var url: string var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", headers: { "X-Something-Special": "🎂", }, changesetVersion: "2023-05-05", quiet: true, send: (_sendPayload: object, options: { url: string, headers: {[key: string]: string}, changesetVersion: string }) => { url = options.url Object.keys(options.headers).forEach((h) => { url += "?" + h + "=" + options.headers[h] }) url += "&changesetVersion=" + options.changesetVersion }, } return sync(options).then(function() { expect(url).toEqual("bogus?X-Something-Special=🎂&changesetVersion=2023-05-05") }) }) }) describe("verbose", () => { it("Adds debug output", () => { var spy = (console.log as unknown) as MockedObject var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", verbose: true, send: (_sendPayload: string, opts: { logger: Logger }) => { opts.logger.log("Verbose!") }, } return sync(options).then(function() { expect(spy.mock.calls).toMatchSnapshot() }) }) }) describe("custom file processing options", () => { it("Adds .graphql to the glob if needed", () => { var payload: MockPayload var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", quiet: true, send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function() { expect(payload.operations).toMatchSnapshot() var optionsWithExt = {...options, glob: "./**/*.graphql"} return sync(optionsWithExt).then(function() { // Get the same result, even when the glob already has a file extension expect(payload.operations).toMatchSnapshot() }) }) }) it("Uses a custom hash function if provided", () => { var payload: MockPayload var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", quiet: true, hash: (graphQLBody: string) => { // This is a bad hack to get the operation name var opName = graphQLBody.match(/query ([A-Za-z]+) \{/) return opName ? opName[1].toUpperCase() : null }, send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function() { expect(payload.operations).toMatchSnapshot() }) }) }) describe("Relay support", () => { it("Uses Relay generated .js files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, path: "./src/__generated__", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations).toMatchSnapshot() }) }) it("Uses relay --persist-output JSON files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, relayPersistedOutput: "./src/__tests__/example-relay-persisted-queries.json", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { return expect(payload.operations).toMatchSnapshot() }) }) it("Uses Apollo Android OperationOutput JSON files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, apolloAndroidOperationOutput: "./src/__tests__/example-apollo-android-operation-output.json", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations[0].alias).toEqual("aba626ea9bdf465954e89e5590eb2c1a") return expect(payload.operations).toMatchSnapshot() }) }) it("Uses Apollo Codegen JSON files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, apolloCodegenJsonOutput: "./src/__tests__/apolloExample/gen/output.json", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations[0].alias).toEqual("22cc98c61c1402c92b230b7c515e07eb793a5152c388b015e86df4652ec58156") return expect(payload.operations).toMatchSnapshot() }) }) }) describe("Input files", () => { it("Merges fragments and operations across files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, path: "./src/__tests__/project/", url: "bogus", // mode: "project" is the default send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations).toMatchSnapshot() }) }) it("Uses mode: file to process each file separately", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, path: "./src/__tests__/project", url: "bogus", mode: "file", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function() { expect(payload.operations).toMatchSnapshot() }) }) }) describe("Promise result", () => { it("Yields the payload and generated code", () => { var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function(ppayload: unknown) { var payload = ppayload as MockPayload expect(payload.operations.length).toEqual(5) var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(payload.generatedCode).toEqual(generatedCode) fs.unlinkSync("./src/OperationStoreClient.js") }) }) }) describe("Sync output", () => { it("Generates a usable artifact for middleware", () => { var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(generatedCode).toMatch('"GetStuff": "4568c28d403794e011363caf815ec827"') expect(generatedCode).toMatch('module.exports = OperationStoreClient') expect(generatedCode).toMatch('var _client = "test-1"') fs.unlinkSync("./src/OperationStoreClient.js") }) }) it("Takes an outfile option", () => { var options = { client: "test-2", path: "./src/__tests__/project", url: "bogus", quiet: true, outfile: "__crazy_outfile.js", send: () => { }, } return sync(options).then(function() { var generatedCode = fs.readFileSync("./__crazy_outfile.js", "utf8") expect(generatedCode).toMatch('"GetStuff": "4568c28d403794e011363caf815ec827"') expect(generatedCode).toMatch('module.exports = OperationStoreClient') expect(generatedCode).toMatch('var _client = "test-2"') fs.unlinkSync("./__crazy_outfile.js") }) }) it("Skips outfile generation when using --persist-output artifact", () => { var options = { client: "test-2", relayPersistedOutput: "./src/__tests__/example-relay-persisted-queries.json", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { // This is the default outfile: var wasWritten = fs.existsSync("./src/OperationStoreClient.js") expect(wasWritten).toBe(false) }) }) it("Skips outfile generation when using --apollo-android-operation-output artifact", () => { var options = { client: "test-2", apolloAndroidOperationOutput: "./src/__tests__/example-apollo-android-operation-output.json", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { // This is the default outfile: var wasWritten = fs.existsSync("./src/OperationStoreClient.js") expect(wasWritten).toBe(false) }) }) }) describe("Logging", () => { it("Logs progress", () => { var spy = (console.log as unknown) as MockedObject var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", send: () => { }, } return sync(options).then(function() { expect(spy.mock.calls).toMatchSnapshot() }) }) it("Can be quieted with quiet: true", () => { var spy = (console.log as unknown) as MockedObject var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { expect(spy.mock.calls).toMatchSnapshot() }) }) }) describe("Printing the result", () => { function buildMockRespondingWith(status: number, data: object) { return nock("http://example.com").post("/stored_operations/sync").reply(status, data) } it("prints failure and sends the message to the promise", () => { var spyConsoleLog = (console.log as unknown) as MockedObject var spyConsoleError = (console.error as unknown) as MockedObject buildMockRespondingWith(422, { errors: { "4568c28d403794e011363caf815ec827": ["something"] }, failed: ["4568c28d403794e011363caf815ec827"], added: ["defg"], not_modified: [], }) var options = { client: "test-1", path: "./src/__tests__/project", url: "http://example.com/stored_operations/sync", quiet: false, } var syncPromise = sync(options) return syncPromise.catch((errmsg: string) => { expect(errmsg).toEqual("Sync failed: GetStuff: something") expect(spyConsoleLog.mock.calls).toMatchSnapshot() expect(spyConsoleError.mock.calls).toMatchSnapshot() jest.clearAllMocks(); }) }) it("prints success", () => { var spyConsoleLog = (console.log as unknown) as MockedObject var spyConsoleError = (console.error as unknown) as MockedObject buildMockRespondingWith(422, { errors: {}, failed: [], added: ["defg"], not_modified: ["xyz", "123"], }) var options = { client: "test-1", path: "./src/__tests__/project", url: "http://example.com/stored_operations/sync", quiet: false, } var syncPromise = sync(options) expect(spyConsoleLog.mock.calls).toMatchSnapshot() jest.clearAllMocks(); return syncPromise.then(() => { expect(spyConsoleLog.mock.calls).toMatchSnapshot() expect(spyConsoleError.mock.calls).toMatchSnapshot() jest.clearAllMocks(); }) }) }) }) graphql-ruby-2.2.17/javascript_client/src/cli.ts000077500000000000000000000103161476434635200216540ustar00rootroot00000000000000#!/usr/bin/env node import parseArgs from "minimist" import sync from "./sync/index" var argv = parseArgs(process.argv.slice(2)) if (argv.help || argv.h) { console.log(`usage: graphql-ruby-client sync Read .graphql files and push the contained operations to a GraphQL::Pro::OperationStore required arguments: --url= URL where data should be POSTed --client= Identifier for this client application optional arguments: --path= Path to .graphql files (default is "./**/*.graphql") --outfile= Target file for generated code --outfile-type= Target type for generated code (default is "js") --secret= HMAC authentication key --relay-persisted-output= Path to a .json file from "relay-compiler ... --persist-output" (Outfile generation is skipped by default.) --apollo-codegen-json-output= Path to a .json file from "apollo client:codegen ... --target json" (Outfile generation is skipped by default.) --apollo-android-operation-output= Path to a .json file from Apollo-Android's "generateOperationOutput" feature. (Outfile generation is skipped by default.) --apollo-persisted-query-manifest= Path to a .json file from Apollo's "generate-persisted-query-manifest" tool. (Outfile generation is skipped by default.) --mode= Treat files like a certain kind of project: relay: treat files like relay-compiler output project: treat files like a cohesive project (fragments are shared, names must be unique) file: treat each file like a stand-alone operation By default, this flag is set to: - "relay" if "__generated__" in the path - otherwise, "project" --header=
: Add a header to the outgoing HTTP request (may be repeated) --changeset-version= Populates \`context[:changeset_version]\` for this sync (for the GraphQL-Enterprise "Changesets" feature) --add-typename Automatically adds the "__typename" field to your queries --quiet Suppress status logging --verbose Print debug output --help Print this message `) } else { var commandName = argv._[0] if (commandName !== "sync") { console.log("Only `graphql-ruby-client sync` is supported") } else { var parsedHeaders: {[key: string]: string} = {} if (argv.header) { if (typeof(argv.header) === "string") { var headerParts = argv.header.split(":") parsedHeaders[headerParts[0]] = headerParts[1] } else { argv.header.forEach((h: string) => { var headerParts = h.split(":") parsedHeaders[headerParts[0]] = headerParts[1] }) } } var result = sync({ path: argv.path, relayPersistedOutput: argv["relay-persisted-output"], apolloCodegenJsonOutput: argv["apollo-codegen-json-output"], apolloAndroidOperationOutput: argv["apollo-android-operation-output"], apolloPersistedQueryManifest: argv["apollo-persisted-query-manifest"], url: argv.url, client: argv.client, outfile: argv.outfile, outfileType: argv["outfile-type"], secret: argv.secret, mode: argv.mode, headers: parsedHeaders, addTypename: argv["add-typename"], quiet: argv.hasOwnProperty("quiet"), verbose: argv.hasOwnProperty("verbose"), changesetVersion: argv["changeset-version"], }) result.then(function() { process.exit(0) }).catch(function() { // The error is logged by the function process.exit(1) }) } } graphql-ruby-2.2.17/javascript_client/src/index.ts000066400000000000000000000010551476434635200222110ustar00rootroot00000000000000import sync from "./sync" import { generateClient } from "./sync/generateClient" import ActionCableLink from "./subscriptions/ActionCableLink" import PusherLink from "./subscriptions/PusherLink" import AblyLink from "./subscriptions/AblyLink" import addGraphQLSubscriptions from "./subscriptions/addGraphQLSubscriptions" import createRelaySubscriptionHandler from "./subscriptions/createRelaySubscriptionHandler" export { sync, generateClient, ActionCableLink, PusherLink, AblyLink, addGraphQLSubscriptions, createRelaySubscriptionHandler, } graphql-ruby-2.2.17/javascript_client/src/subscriptions/000077500000000000000000000000001476434635200234405ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/subscriptions/AblyLink.ts000066400000000000000000000100641476434635200255160ustar00rootroot00000000000000// An Apollo Link for using graphql-pro's Ably subscriptions // // @example Adding subscriptions to a HttpLink // // Load Ably and create a client // var Ably = require('ably') // // Be sure to create an API key with "Subscribe" and "Presence" permissions only, // // and use that limited API key here: // var ablyClient = new Ably.Realtime({ key: "yourapp.key:secret" }) // // // Build a combined link, initialize the client: // const ablyLink = new AblyLink({ably: ablyClient}) // const link = ApolloLink.from([authLink, ablyLink, httpLink]) // const client = new ApolloClient(link: link, ...) // // @example Building a subscription, then subscribing to it // subscription = client.subscribe({ // variables: { room: roomName}, // query: gql` // subscription MessageAdded($room: String!) { // messageWasAdded(room: $room) { // room { // messages { // id // body // author { // screenname // } // } // } // } // } // ` // }) // // subscription.subscribe({ next: ({data, errors}) => { // // Do something with `data` and/or `errors` // }}) // import { ApolloLink, Observable, FetchResult, NextLink, Operation } from "@apollo/client/core" import { Realtime } from "ably" type RequestResult = Observable, Record>> class AblyLink extends ApolloLink { ably: Realtime constructor(options: { ably: Realtime }) { super() // Retain a handle to the Ably client this.ably = options.ably } request(operation: Operation, forward: NextLink): RequestResult { return new Observable((observer) => { // Check the result of the operation forward(operation).subscribe({ next: (data) => { // If the operation has the subscription header, it's a subscription const subscriptionChannelConfig = this._getSubscriptionChannel(operation) if (subscriptionChannelConfig) { // This will keep pushing to `.next` this._createSubscription(subscriptionChannelConfig, observer) } else { // This isn't a subscription, // So pass the data along and close the observer. observer.next(data) observer.complete() } }}) }) } _getSubscriptionChannel(operation: Operation) { const response = operation.getContext().response // Check to see if the response has the header const subscriptionChannel = response.headers.get("X-Subscription-ID") // The server returns this header when encryption is enabled. const cipherKey = response.headers.get("X-Subscription-Key") return { channel: subscriptionChannel, key: cipherKey } } _createSubscription(subscriptionChannelConfig: { channel: string, key: string }, observer: { next: Function, complete: Function}) { const subscriptionChannel = subscriptionChannelConfig["channel"] const subscriptionKey = subscriptionChannelConfig["key"] const ablyOptions = subscriptionKey ? { cipher: { key: subscriptionKey } } : {} const ablyChannel = this.ably.channels.get(subscriptionChannel, ablyOptions) const ablyClientId = this.ably.auth.clientId // Register presence, so that we can detect empty channels and clean them up server-side if (ablyClientId) { ablyChannel.presence.enter() } else { ablyChannel.presence.enterClient("graphql-subscriber", "subscribed") } // Subscribe for more update ablyChannel.subscribe("update", function(message) { var payload = message.data const result = payload.result if (result) { // Send the new response to listeners observer.next(result) } if (!payload.more) { // This is the end, the server says to unsubscribe if (ablyClientId) { ablyChannel.presence.leave() } else { ablyChannel.presence.leaveClient("graphql-subscriber") } ablyChannel.unsubscribe() observer.complete() } }) } } export default AblyLink graphql-ruby-2.2.17/javascript_client/src/subscriptions/ActionCableLink.ts000066400000000000000000000045251476434635200270000ustar00rootroot00000000000000import { ApolloLink, Observable, FetchResult, Operation, NextLink } from "@apollo/client/core" import type { Consumer } from "@rails/actioncable" import { print } from "graphql" type RequestResult = FetchResult<{ [key: string]: any; }, Record, Record> type ConnectionParams = object | ((operation: Operation) => object) class ActionCableLink extends ApolloLink { cable: Consumer channelName: string actionName: string connectionParams: ConnectionParams constructor(options: { cable: Consumer, channelName?: string, actionName?: string, connectionParams?: ConnectionParams }) { super() this.cable = options.cable this.channelName = options.channelName || "GraphqlChannel" this.actionName = options.actionName || "execute" this.connectionParams = options.connectionParams || {} } // Interestingly, this link does _not_ call through to `next` because // instead, it sends the request to ActionCable. request(operation: Operation, _next: NextLink): Observable { return new Observable((observer) => { var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var actionName = this.actionName var connectionParams = (typeof this.connectionParams === "function") ? this.connectionParams(operation) : this.connectionParams var channel = this.cable.subscriptions.create(Object.assign({},{ channel: this.channelName, channelId: channelId }, connectionParams), { connected: function() { this.perform( actionName, { query: operation.query ? print(operation.query) : null, variables: operation.variables, // This is added for persisted operation support: operationId: (operation as {operationId?: string}).operationId, operationName: operation.operationName } ) }, received: function(payload) { if (payload?.result?.data || payload?.result?.errors) { observer.next(payload.result) } if (!payload.more) { observer.complete() } } }) // Make the ActionCable subscription behave like an Apollo subscription return Object.assign(channel, {closed: false}) }) } } export default ActionCableLink graphql-ruby-2.2.17/javascript_client/src/subscriptions/ActionCableSubscriber.ts000066400000000000000000000052161476434635200302040ustar00rootroot00000000000000import printer from "graphql/language/printer" import registry from "./registry" import type { Consumer } from "@rails/actioncable" interface ApolloNetworkInterface { applyMiddlewares: Function query: (req: object) => Promise _opts: any } class ActionCableSubscriber { _cable: Consumer _networkInterface: ApolloNetworkInterface _channelName: string constructor(cable: Consumer, networkInterface: ApolloNetworkInterface, channelName?: string) { this._cable = cable this._networkInterface = networkInterface this._channelName = channelName || "GraphqlChannel" } /** * Send `request` over ActionCable (`registry._cable`), * calling `handler` with any incoming data. * Return the subscription so that the registry can unsubscribe it later. * @param {Object} registry * @param {Object} request * @param {Function} handler * @return {ID} An ID for unsubscribing */ subscribe(request: any, handler: any) { var networkInterface = this._networkInterface // unique-ish var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var channel = this._cable.subscriptions.create({ channel: this._channelName, channelId: channelId, }, { // After connecting, send the data over ActionCable connected: function() { // applyMiddlewares code is inspired by networkInterface internals var opts = Object.assign({}, networkInterface._opts) networkInterface .applyMiddlewares({request: request, options: opts}) .then(function() { var queryString = request.query ? printer.print(request.query) : null var operationName = request.operationName var operationId = request.operationId var variables = JSON.stringify(request.variables) var channelParams = Object.assign({}, request, { query: queryString, variables: variables, operationId: operationId, operationName: operationName, }) channel.perform("execute", channelParams) }) }, // Payload from ActionCable should have at least two keys: // - more: true if this channel should stay open // - result: the GraphQL response for this result received: function(payload) { var result = payload.result if (result) { handler(result.errors, result.data) } if (!payload.more) { registry.unsubscribe(id) } }, }) var id = registry.add(channel) return id } unsubscribe(id: number) { registry.unsubscribe(id) } } export default ActionCableSubscriber graphql-ruby-2.2.17/javascript_client/src/subscriptions/PusherLink.ts000066400000000000000000000136751476434635200261100ustar00rootroot00000000000000// An Apollo Link for using graphql-pro's Pusher subscriptions // // @example Adding subscriptions to a HttpLink // // Load Pusher and create a client // import Pusher from "pusher-js" // var pusherClient = new Pusher("your-app-key", { cluster: "us2" }) // // // Build a combined link, initialize the client: // const pusherLink = new PusherLink({pusher: pusherClient}) // const link = ApolloLink.from([authLink, pusherLink, httpLink]) // const client = new ApolloClient(link: link, ...) // // @example Building a subscription, then subscribing to it // subscription = client.subscribe({ // variables: { room: roomName}, // query: gql` // subscription MessageAdded($room: String!) { // messageWasAdded(room: $room) { // room { // messages { // id // body // author { // screenname // } // } // } // } // } // ` // }) // // subscription.subscribe({ next: ({data, errors}) => { // // Do something with `data` and/or `errors` // }}) // import { ApolloLink, Observable, Observer, Operation, NextLink, FetchResult } from "@apollo/client/core" import Pusher from "pusher-js" type RequestResult = FetchResult<{ [key: string]: any; }, Record, Record> type Subscription = { closed: boolean; unsubscribe(): void; } class PusherLink extends ApolloLink { pusher: Pusher decompress: (result: string) => any constructor(options: { pusher: Pusher, decompress?: (result: string) => any}) { super() // Retain a handle to the Pusher client this.pusher = options.pusher if (options.decompress) { this.decompress = options.decompress } else { this.decompress = function(_result: string) { throw new Error("Received compressed_result but PusherLink wasn't configured with `decompress: (result: string) => any`. Add this configuration.") } } } request(operation: Operation, forward: NextLink): Observable { const subscribeObservable = new Observable((_observer: any) => { }) // Capture the super method const prevSubscribe = subscribeObservable.subscribe.bind(subscribeObservable) // Override subscribe to return an `unsubscribe` object, see // https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L182-L212 subscribeObservable.subscribe = ( observerOrNext: Observer | ((value: RequestResult) => void), onError?: (error: any) => void, onComplete?: () => void ): Subscription => { // Call super if (typeof(observerOrNext) == "function") { prevSubscribe(observerOrNext, onError, onComplete) } else { prevSubscribe(observerOrNext) } const observer = getObserver(observerOrNext, onError, onComplete) var subscriptionChannel: string // Check the result of the operation const resultObservable = forward(operation) // When the operation is done, try to get the subscription ID from the server resultObservable.subscribe({ next: (data: any) => { // If the operation has the subscription header, it's a subscription const response = operation.getContext().response // Check to see if the response has the header subscriptionChannel = response.headers.get("X-Subscription-ID") if (subscriptionChannel) { // Set up the pusher subscription for updates from the server const pusherChannel = this.pusher.subscribe(subscriptionChannel) // Pass along the initial payload: if (data.data && Object.keys(data.data).length > 0) { observer.next(data) } // Subscribe for more update pusherChannel.bind("update", (payload: any) => { this._onUpdate(subscriptionChannel, observer, payload) }) } else { // This isn't a subscription, // So pass the data along and close the observer. observer.next(data) observer.complete() } }, error: observer.error, // complete: observer.complete Don't pass this because Apollo unsubscribes if you do }) // Return an object that will unsubscribe _if_ the query was a subscription. return { closed: false, unsubscribe: () => { subscriptionChannel && this.pusher.unsubscribe(subscriptionChannel) } } } return subscribeObservable } _onUpdate(subscriptionChannel: string, observer: { next: Function, complete: Function }, payload: {more: boolean, compressed_result?: string, result?: object}): void { let result: any if (payload.compressed_result) { result = this.decompress(payload.compressed_result) } else { result = payload.result } if (result) { // Send the new response to listeners observer.next(result) } if (!payload.more) { // This is the end, the server says to unsubscribe this.pusher.unsubscribe(subscriptionChannel) observer.complete() } } } // Turn `subscribe` arguments into an observer-like thing, see getObserver // https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L347-L361 function getObserver( observerOrNext: Function | Observer, onError?: (e: Error) => void, onComplete?: () => void, ) { if (typeof observerOrNext === 'function') { // Duck-type an observer return { next: (v: T) => observerOrNext(v), error: (e: Error) => onError && onError(e), complete: () => onComplete && onComplete(), } } else { // Make an object that calls to the given object, with safety checks return { next: (v: T) => observerOrNext.next && observerOrNext.next(v), error: (e: Error) => observerOrNext.error && observerOrNext.error(e), complete: () => observerOrNext.complete && observerOrNext.complete(), } } } export default PusherLink graphql-ruby-2.2.17/javascript_client/src/subscriptions/PusherSubscriber.ts000066400000000000000000000057001476434635200273040ustar00rootroot00000000000000import registry from "./registry" import Pusher from "pusher-js" interface ApolloNetworkInterface { use: Function useAfter: Function query: (req: object) => Promise } /** * Make a new subscriber for `addGraphQLSubscriptions` * * @param {Pusher} pusher */ class PusherSubscriber { _pusher: Pusher _networkInterface: ApolloNetworkInterface _decompress: (compressed: string) => any constructor(pusher: Pusher, networkInterface: ApolloNetworkInterface, decompress?: (compressed: string) => any) { this._pusher = pusher this._networkInterface = networkInterface this._decompress = decompress || function(_compressed) { throw new Error("Received compressed_result but this addGraphQLSubscriptions wasn't configured with `decompress: (result: string) => any`. Add this configuration.")} // This is a bit tricky: // only the _request_ is passed to the `subscribe` function, s // so we have to attach the subscription id to the `request`. // However, the request is _not_ available in the afterware function. // So: // - Add the request to `options` so it's available in afterware // - In the afterware, update the request to hold the header value // - Finally, in `subscribe`, read the subscription ID off of `request` networkInterface.use([{ applyMiddleware: function({request, options}: any, next: Function) { options.request = request next() } }]) networkInterface.useAfter([{ applyAfterware: function({response, options}: any, next: Function) { options.request.__subscriptionId = response.headers.get("X-Subscription-ID") next() } }]) } // Implement the Apollo subscribe API subscribe(request: {__subscriptionId: string}, handler: any) { var pusher = this._pusher var networkInterface = this._networkInterface var decompress = this._decompress var subscription = { _channelName: "", // set after the successful POST unsubscribe: function() { if (this._channelName) { pusher.unsubscribe(this._channelName) } } } var id = registry.add(subscription) // Send the subscription as a query // Get the channel ID from the response headers networkInterface.query(request).then(function(_executionResult: any){ var subscriptionChannel = request.__subscriptionId subscription._channelName = subscriptionChannel var pusherChannel = pusher.subscribe(subscriptionChannel) // When you get an update form Pusher, send it to Apollo pusherChannel.bind("update", function(payload: any) { var result = payload.compressed_result ? decompress(payload.compressed_result) : payload.result if (result) { handler(result.errors, result.data) } if (!payload.more) { registry.unsubscribe(id) } }) }) return id } unsubscribe(id: number) { registry.unsubscribe(id) } } export default PusherSubscriber graphql-ruby-2.2.17/javascript_client/src/subscriptions/SubscriptionExchange.ts000066400000000000000000000070241476434635200301420ustar00rootroot00000000000000import Pusher from "pusher-js" import Urql from "urql" type ForwardCallback = (...args: any[]) => void const SubscriptionExchange = { create(options: { pusher: Pusher }) { const pusher = options.pusher return function(operation: Urql.Operation) { // urql will call `.subscribed` on the returned object: // https://github.com/FormidableLabs/urql/blob/f89cfd06d9f14ae9cb3be10b21bd5cbd12ca275c/packages/core/src/exchanges/subscription.ts#L68-L73 // https://github.com/FormidableLabs/urql/blob/f89cfd06d9f14ae9cb3be10b21bd5cbd12ca275c/packages/core/src/exchanges/subscription.ts#L82-L97 return { subscribe: ({next, error, complete}: { next: ForwardCallback, error: ForwardCallback, complete: ForwardCallback}) => { // Somehow forward the operation to be POSTed to the server, // I don't see an option for passing this on to the `fetchExchange` const fetchBody = JSON.stringify({ query: operation.query, variables: operation.variables, }) var pusherChannelName: string const subscriptionId = "" + operation.key var fetchOptions = operation.context.fetchOptions if (typeof fetchOptions === "function") { fetchOptions = fetchOptions() } else if (fetchOptions == null) { fetchOptions = {} } const headers = { ...(fetchOptions.headers), ...{ 'Content-Type': 'application/json', 'X-Subscription-ID': subscriptionId } } const defaultFetchOptions = { method: "POST" } const mergedFetchOptions = { ...defaultFetchOptions, ...fetchOptions, body: fetchBody, headers: headers, } const fetchFn = operation.context.fetch || fetch fetchFn(operation.context.url, mergedFetchOptions) .then((fetchResult) => { // Get the server-provided subscription ID pusherChannelName = fetchResult.headers.get("X-Subscription-ID") as string // Set up a subscription to Pusher, forwarding updates to // the `next` function provided by urql const pusherChannel = pusher.subscribe(pusherChannelName) pusherChannel.bind("update", (payload: {result: object, more: boolean}) => { // Here's an update to this subscription, // pass it on: if (payload.result) { next(payload.result) } // If the server signals that this is the end, // then unsubscribe the client: if (!payload.more) { complete() } }) // Continue processing the initial result for the subscription return fetchResult.json() }) .then((jsonResult) => { // forward the initial result to urql next(jsonResult) }) .catch(error) // urql will call `.unsubscribe()` if it's returned here: // https://github.com/FormidableLabs/urql/blob/f89cfd06d9f14ae9cb3be10b21bd5cbd12ca275c/packages/core/src/exchanges/subscription.ts#L102 return { unsubscribe: () => { // When requested by urql, disconnect from this channel pusherChannelName && pusher.unsubscribe(pusherChannelName) } } } } } } } export default SubscriptionExchange graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/000077500000000000000000000000001476434635200253765ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/ActionCableLinkTest.ts000066400000000000000000000111561476434635200315740ustar00rootroot00000000000000import ActionCableLink from "../ActionCableLink" import { parse } from "graphql" import type { Consumer } from "@rails/actioncable" import { Operation } from "@apollo/client/core" describe("ActionCableLink", () => { var log: any[] var cable: any var options: any var query: any var operation: Operation beforeEach(() => { log = [] cable = { subscriptions: { create: function(channelName: string | object, options: {connected: Function, received: Function}) { var channel = channelName var params = typeof channel === "object" ? channel : { channel } var alreadyConnected = false var subscription = Object.assign( Object.create({ perform: function(actionName: string, options: object) { log.push(["perform", { actionName: actionName, options: options }]) }, unsubscribe: function() { log.push(["unsubscribe"]) } }), { params }, options ) subscription.connected = subscription.connected.bind(subscription) var received = subscription.received subscription.received = function(data: any) { if (!alreadyConnected) { alreadyConnected = true subscription.connected() } received(data) } subscription.__proto__.unsubscribe = subscription.__proto__.unsubscribe.bind(subscription) return subscription } } } options = { cable: (cable as unknown) as Consumer } query = parse("subscription { foo { bar } }") operation = ({ query: query, variables: { a: 1 }, operationId: "operationId", operationName: "operationName" } as unknown) as Operation }) it("delegates to the cable", () => { var observable = new ActionCableLink(options).request(operation, null as any) // unpack the underlying subscription var subscription: any = (observable.subscribe(function(result: any) { log.push(["received", result]) }) as any)._cleanup subscription.received({ result: { data: null }, more: true }) subscription.received({ result: { data: "data 1" }, more: true }) subscription.received({ result: { data: "data 2" }, more: false }) expect(log).toEqual([ [ "perform", { actionName: "execute", options: { query: "subscription {\n foo {\n bar\n }\n}", variables: { a: 1 }, operationId: "operationId", operationName: "operationName" } } ], ["received", { data: "data 1" }], ["received", { data: "data 2" }], ["unsubscribe"] ]) }) it("delegates a manual unsubscribe to the cable", () => { var observable = new ActionCableLink(options).request(operation, null as any) // unpack the underlying subscription var subscription: any = (observable.subscribe(function(result: any) { log.push(["received", result]) }) as any)._cleanup subscription.received({ result: { data: null }, more: true }) subscription.received({ result: { data: "data 1" }, more: true }) subscription.unsubscribe() expect(log).toEqual([ [ "perform", { actionName: "execute", options: { query: "subscription {\n foo {\n bar\n }\n}", variables: { a: 1 }, operationId: "operationId", operationName: "operationName" } } ], ["received", { data: "data 1" }], ["unsubscribe"] ]) }) it("forward object connectionParams to subscription creation", () => { var observable = new ActionCableLink(Object.assign(options, { connectionParams: { test: 1 } })). request(operation, null as any) // unpack the underlying subscription var subscription: any = (observable.subscribe(() => null) as any)._cleanup subscription.unsubscribe() expect(subscription.params["test"]).toEqual(1) }) it("calls connectionParams during subscription creation to fetch additional params", () => { var observable = new ActionCableLink( Object.assign(options, { connectionParams: () => ({ test: 1 })} ) ).request(operation, null as any) // unpack the underlying subscription var subscription: any = (observable.subscribe(() => null) as any)._cleanup subscription.unsubscribe() expect(subscription.params["test"]).toEqual(1) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/PusherLinkTest.ts000066400000000000000000000170671476434635200307050ustar00rootroot00000000000000import PusherLink from "../PusherLink" import { parse } from "graphql" import Pusher from "pusher-js" import { Operation } from "@apollo/client/core" import pako from 'pako' type MockChannel = { bind: (action: string, handler: Function) => void, } describe("PusherLink", () => { var channelName = "abcd-efgh" var log: any[] var pusher: any var options: any var link: any var query: any var operation: Operation beforeEach(() => { log = [] pusher = { _channels: {}, trigger: function(channel: string, event: string, data: any) { var handlers = this._channels[channel] if (handlers) { handlers.forEach(function(handler: [string, Function]) { if (handler[0] == event) { handler[1](data) } }) } }, subscribe: function(channel: string): MockChannel { log.push(["subscribe", channel]) var handlers = this._channels[channel] if (!handlers) { handlers = this._channels[channel] = [] } return { bind: (action: string, handler: Function): void => { handlers.push([action, handler]) } } }, unsubscribe: (channel: string): void => { log.push(["unsubscribe", channel]) }, } options = { pusher: (pusher as unknown) as Pusher } link = new PusherLink(options) query = parse("subscription { foo { bar } }") operation = ({ query: query, variables: { a: 1 }, operationId: "operationId", operationName: "operationName", getContext: () => { return { response: { headers: { get: (headerName: string) => { if (headerName == "X-Subscription-ID") { return channelName } else { throw "Unsupported header name: " + headerName } } } } } } } as unknown) as Operation }) it("forwards errors to error handlers", () => { let passedErrorHandler: Function = () => {} var observable = link.request(operation, function(_operation: Operation): any { return { subscribe: (options: { next: Function, error: Function, complete: Function }): void => { passedErrorHandler = options.error {} } } }) let errorHandlerWasCalled = false function createdErrorHandler(_err: Error) { errorHandlerWasCalled = true } observable.subscribe(function(result: any) { log.push(["received", result]) }, createdErrorHandler) if (passedErrorHandler) { passedErrorHandler(new Error) } expect(errorHandlerWasCalled).toBe(true) }) it("doesn't call the link request's `complete` handler because otherwise Apollo would clean up subscriptions", () => { let passedComplete: Function = () => {} var observable = link.request(operation, function(_operation: Operation): any { return { subscribe: (options: { next: Function, error: Function, complete: Function }): void => { passedComplete = options.complete {} } } }) observable.subscribe(function(result: any) { log.push(["received", result]) }, null, function() { log.push(["completed"])}) expect(log).toEqual([]) expect(passedComplete).toBeUndefined() }) it("delegates to pusher", () => { var requestFinished: Function = () => {} var observable = link.request(operation, function(_operation: Operation): any { return { subscribe: (options: { next: Function }): void => { requestFinished = options.next } } }) // unpack the underlying subscription observable.subscribe(function(result: any) { log.push(["received", result]) }) // Pretend the HTTP link finished requestFinished({ data: "initial payload" }) pusher.trigger(channelName, "update", { result: { data: "data 1" }, more: true }) pusher.trigger(channelName, "update", { result: { data: "data 2" }, more: false }) expect(log).toEqual([ ["subscribe", "abcd-efgh"], ["received", { data: "initial payload"}], ["received", { data: "data 1" }], ["received", { data: "data 2" }], ["unsubscribe", "abcd-efgh"] ]) }) it("delegates a manual unsubscribe to pusher", () => { var requestFinished: Function = () => {} var observable = link.request(operation, function(_operation: Operation): any { return { subscribe: (options: { next: Function }): void => { requestFinished = options.next } } }) // unpack the underlying subscription var subscription = observable.subscribe(function(result: any) { log.push(["received", result]) }) // Pretend the HTTP link finished requestFinished({ data: "initial payload" }) pusher.trigger(channelName, "update", { result: { data: "data 1" }, more: true }) subscription.unsubscribe() expect(log).toEqual([ ["subscribe", "abcd-efgh"], ["received", { data: "initial payload"}], ["received", { data: "data 1" }], ["unsubscribe", "abcd-efgh"] ]) }) it("doesn't send empty initial responses", () => { var requestFinished: Function = () => {} var observable = link.request(operation, function(_operation: Operation): any { return { subscribe: (options: { next: Function }): void => { requestFinished = options.next } } }) // unpack the underlying subscription var subscription = observable.subscribe(function(result: any) { log.push(["received", result]) }) // Pretend the HTTP link finished requestFinished({ data: null }) pusher.trigger(channelName, "update", { result: { data: "data 1" }, more: true }) subscription.unsubscribe() expect(log).toEqual([ ["subscribe", "abcd-efgh"], ["received", { data: "data 1" }], ["unsubscribe", "abcd-efgh"] ]) }) it("throws an error when no `decompress:` is configured", () => { const link = new PusherLink({ pusher: new Pusher("123"), }) const observer = { next: (_result: object) => {}, complete: () => {}, } const payload = { more: true, compressed_result: "abcdef", } expect(() => { link._onUpdate("abc", observer, payload) }).toThrow("Received compressed_result but PusherLink wasn't configured with `decompress: (result: string) => any`. Add this configuration.") }) it("decompresses compressed_result", () => { const link = new PusherLink({ pusher: new Pusher("123"), decompress: (compressed) => { const buff = Buffer.from(compressed, 'base64'); return JSON.parse(pako.inflate(buff, { to: 'string' })); }, }) const results: Array = [] const observer = { next: (result: object) => { results.push(result) }, complete: () => { results.push("complete") }, } const compressedData = pako.deflate(JSON.stringify({ a: 1, b: 2})) // Browsers have `TextEncoder` for this const compressedStr = Buffer.from(compressedData).toString("base64") const payload = { more: true, compressed_result: compressedStr, } // Send a dummy payload and then terminate the subscription link._onUpdate("abc", observer, payload) link._onUpdate("abc", observer, { more: false }) expect(results).toEqual([{a: 1, b: 2}, "complete"]) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/SubscriptionExchangeTest.ts000066400000000000000000000056451476434635200327470ustar00rootroot00000000000000import SubscriptionExchange from "../SubscriptionExchange" import Pusher from "pusher-js" import Urql from "urql" import {parse} from "graphql" import { nextTick } from "process" type MockChannel = { bind: (action: string, handler: Function) => void, } describe("SubscriptionExchange", () => { var channelName = "1234" var log: any[] var pusher: any var options: any var pusherExchange: any var operation: any beforeEach(() => { log = [] pusher = { _channels: {}, trigger: function(channel: string, event: string, data: any) { var handlers = this._channels[channel] if (handlers) { handlers.forEach(function(handler: [string, Function]) { if (handler[0] == event) { handler[1](data) } }) } }, subscribe: function(channel: string): MockChannel { log.push(["subscribe", channel]) var handlers = this._channels[channel] if (!handlers) { handlers = this._channels[channel] = [] } return { bind: (action: string, handler: Function): void => { handlers.push([action, handler]) } } }, unsubscribe: (channel: string): void => { delete pusher._channels[channel] log.push(["unsubscribe", channel]) }, } options = { pusher: (pusher as unknown) as Pusher } pusherExchange = SubscriptionExchange.create(options) operation = { query: parse("{ foo { bar } }"), variables: {}, key: Number(channelName), context: { url: "/graphql", requestPolicy: "network-only", fetch: () => { var headers = new Headers headers.append("X-Subscription-ID", channelName) const jsonData = { data: { foo: "bar" }} return Promise.resolve(({ headers: headers, json: () => { return jsonData } } as unknown) as Response) } }, kind: "subscription", } as Urql.Operation }) it("calls through to handlers and can be unsubscribed", () => { const subscriber = pusherExchange(operation) const next = (data: any) => { log.push(["next", data]) } const error = (err: any) => { log.push(["error", err]) } const complete = (data: any) => { log.push(["complete", data]) } const subscription = subscriber.subscribe({ next, error, complete }) return new Promise((resolve, _reject) => { nextTick(() => { pusher.trigger(channelName, { result: {}, more: true }) expect(Object.keys(pusher._channels)).toEqual([channelName]) subscription.unsubscribe() expect(Object.keys(pusher._channels)).toEqual([]) const expectedLog = [ ["subscribe", "1234"], ["next", { data: { foo: "bar" } }], ["unsubscribe", "1234"] ] expect(log).toEqual(expectedLog) resolve(true) }) }) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/addGraphQLSubscriptionsTest.ts000066400000000000000000000014741476434635200333530ustar00rootroot00000000000000import addGraphQLSubscriptions from "../addGraphQLSubscriptions" describe("addGraphQLSubscriptions", () => { it("delegates to the subscriber", () => { var state: {[key: string]: string} = {} var subscriber = { subscribe: function(req: string, handler: string) { state[req] = handler return req + "/" + handler }, unsubscribe(id: string) { var key = id.split("/")[0] delete state[key] } } var dummyNetworkInterface = addGraphQLSubscriptions({}, {subscriber: subscriber}) var id = dummyNetworkInterface.subscribe("abc", "def") expect(id).toEqual("abc/def") expect(Object.keys(state).length).toEqual(1) expect(state["abc"]).toEqual("def") dummyNetworkInterface.unsubscribe(id) expect(Object.keys(state).length).toEqual(0) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/createAblyFetcherTest.ts000066400000000000000000000057641476434635200321760ustar00rootroot00000000000000import createAblyFetcher from "../createAblyFetcher" import { Realtime } from "ably" function createAbly() { const _channels: {[key: string]: any } = {} const ably = { _channels: _channels, channels: { get(channelName: string) { return _channels[channelName] ||= { _listeners: [] as [string, Function][], name: channelName, presence: { enterClient(_clientName: string, _status: string) {}, leaveClient(_clientName: string) {}, }, detach(callback: Function) { callback() }, subscribe(eventName: string, callback: Function) { this._listeners.push([eventName, callback]) }, unsubscribe(){} } }, release(channelName: string) { delete _channels[channelName] } }, __testTrigger(channelName: string, eventName: string, data: any) { const channel = this.channels.get(channelName) const handler = channel._listeners.find((l: any) => l[0] == eventName) if (handler) { handler[1](data) } } } return ably } describe("createAblyFetcher", () => { it("yields updates for subscriptions", () => { const ably = createAbly() const fetchLog: any[] = [] const dummyFetch = function(url: string, fetchArgs: any) { fetchLog.push([url, fetchArgs.customOpt]) const dummyResponse = { json: () => { return { data: { hi: "First response" } } }, headers: { get() { return fetchArgs.body.includes("subscription") ? "abcd" : null } } } return Promise.resolve(dummyResponse) } const fetcher = createAblyFetcher({ ably: (ably as unknown) as Realtime, url: "/graphql", fetch: ((dummyFetch as unknown) as typeof fetch), fetchOptions: {customOpt: true} }) const result = fetcher({ variables: {}, operationName: "hello", body: "subscription hello { hi }" }, {}) return result.next().then((res) => { expect(res.value.data.hi).toEqual("First response") expect(fetchLog).toEqual([["/graphql", true]]) }).then(() => { const promise = result.next().then((res2) => { expect(res2).toEqual({ value: { data: { hi: "Bonjour" } }, done: false }) }) ably.__testTrigger("abcd", "update", { data: { result: { data: { hi: "Bonjour" } } } }) return promise.then(() => { // Test non-subscriptions too: expect(Object.keys(ably._channels)).toEqual(["abcd"]) const queryResult = fetcher({ variables: {}, operationName: null, body: "{ __typename }"}, {}) return queryResult.next().then((res) => { expect(res.value.data).toEqual({ hi: "First response"}) return queryResult.next().then((res2) => { expect(res2.done).toEqual(true) expect(ably._channels).toEqual({}) }) }) }) }) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/createAblyHandlerTest.ts000066400000000000000000000315741476434635200321710ustar00rootroot00000000000000import { OnErrorData, createAblyHandler } from "../createAblyHandler" import { Realtime, Types } from "ably" const dummyOperation = { text: "", name: "" } const channelTemplate = { presence: { enter() {}, enterClient() {}, leave(callback?: (err?: Types.ErrorInfo) => void) { if (callback) callback() } }, subscribe: () => {}, unsubscribe: () => {}, on: () => {}, detach: (callback?: (err?: Types.ErrorInfo) => void) => { if (callback) callback() } } const createDummyConsumer = ( channel: any = channelTemplate, release = (_channelName: string) => {} ): Realtime => (({ auth: { clientId: "foo" }, channels: { get: () => channel, release } } as unknown) as Realtime) const nextTick = () => new Promise(resolve => setTimeout(resolve, 0)) describe("createAblyHandler", () => { it("returns a function producing a disposable subscription", async () => { const subscriptionId = "dummy-subscription" var wasUnsubscribed = false var wasDetached = false var releasedChannelName const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", subscriptionId]]), body: { data: { foo: "bar" } } }) ), ably: createDummyConsumer( { ...channelTemplate, unsubscribe: () => { wasUnsubscribed = true }, detach: (callback?: (err?: Types.ErrorInfo) => void) => { if (callback) callback() wasDetached = true }, name: subscriptionId }, (channelName: string) => { releasedChannelName = channelName } ) }) const subscription = producer( dummyOperation, {}, {}, { onError: () => {}, onNext: () => {}, onCompleted: () => {} } ) await nextTick() await subscription.dispose() expect(wasUnsubscribed).toEqual(true) expect(wasDetached).toEqual(true) expect(releasedChannelName).toEqual(subscriptionId) }) it("dispatches the immediate response in case of success", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { data: { foo: "bar" } } }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBeUndefined() expect(nextInvokedWith).toEqual({ data: { foo: "bar" } }) }) it("dispatches the immediate response in case of error", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const dummyErrors = [{ message: "baz" }] const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { errors: dummyErrors } }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: () => {}, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toEqual(dummyErrors) expect(nextInvokedWith).toBeUndefined() }) it("doesn't dispatch anything for an empty response", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: {} }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBeUndefined() expect(nextInvokedWith).toBeUndefined() }) it("doesn't dispatch anything for an empty data object", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { data: {} } }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBeUndefined() expect(nextInvokedWith).toBeUndefined() }) it("dispatches caught errors", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const error = new Error("blam") const producer = createAblyHandler({ fetchOperation: () => new Promise((_resolve, reject) => reject(error)), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBe(error) expect(nextInvokedWith).toBeUndefined() }) it("detaches the channel when the subscription is disposed during initial response", async () => { let detached = false const ably = createDummyConsumer({ ...channelTemplate, detach() { detached = true } }) const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { errors: {} } }) ), ably }) const { dispose } = producer( dummyOperation, {}, {}, { onError: async () => { dispose() }, onNext: async () => {}, onCompleted: () => {} } ) await nextTick() expect(detached).toBe(true) }) describe("integration with Ably", () => { const key = process.env.ABLY_KEY const testWithAblyKey = key ? test : test.skip test("onError is called when using invalid key", async () => { const ably = new Realtime({ key: "integration-test:invalid", log: { level: 0 } }) await new Promise(resolve => { const fetchOperation = async () => ({ headers: new Map([["X-Subscription-ID", "foo"]]) }) const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: any) => { expect(error.message).toMatch(/Invalid key in request/) resolve() } const onNext = () => console.log("onNext") const onCompleted = () => console.log("onCompleted") const observer = { onError, onNext, onCompleted } ablyHandler(operation, variables, cacheConfig, observer) }) ably.close() }) // For executing this test you need to provide a valid Ably API key in // environment variable ABLY_KEY testWithAblyKey( "onError is called for too many subscriptions", async () => { const ably = new Realtime({ key, log: { level: 0 } }) await new Promise(resolve => { let subscriptionCounter = 0 const fetchOperation = async () => { subscriptionCounter += 1 return { headers: new Map([ ["X-Subscription-ID", `foo-${subscriptionCounter}`] ]) } } const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: any) => { expect(error.message).toMatch(/Maximum number of channels/) resolve() } const onNext = () => console.log("onNext") const onCompleted = () => console.log("onCompleted") const observer = { onError, onNext, onCompleted } for (let i = 0; i < 201; ++i) { ablyHandler(operation, variables, cacheConfig, observer) } }) ably.close() } ) // For executing this test you need to provide a valid Ably API key in // environment variable ABLY_KEY // // This test might take longer than the default jest timeout of 5s. // Consider setting a higher timeout when running in CI. testWithAblyKey("can make more than 200 subscriptions", async () => { let caughtError = null const ably = new Realtime({ key, log: { level: 0 } }) let subscriptionCounter = 0 const fetchOperation = async () => { subscriptionCounter += 1 return { headers: new Map([ ["X-Subscription-ID", `foo-${subscriptionCounter}`] ]) } } const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: OnErrorData) => { caughtError = error } const onNext = () => {} const onCompleted = () => {} const observer = { onError, onNext, onCompleted } const disposals = [] for (let i = 0; i < 200; ++i) { const { dispose } = ablyHandler( operation, variables, cacheConfig, observer ) await new Promise(resolve => setTimeout(resolve, 0)) disposals.push(dispose()) } await Promise.all(disposals) // 201st subscription - should work now that previous 200 subscriptions have been diposed const { dispose } = ablyHandler( operation, variables, cacheConfig, observer ) await new Promise(resolve => setTimeout(resolve, 0)) await dispose() ably.close() if (caughtError) throw caughtError }) // For executing this test you need to provide a valid Ably API key in // environment variable ABLY_KEY testWithAblyKey( "receives message sent before subscribe takes effect", async () => { let caughtError = null const ably = new Realtime({ key, log: { level: 0 } }) ably.connect() const subscriptionId = Math.random().toString(36) const fetchOperation = async () => ({ headers: new Map([["X-Subscription-ID", subscriptionId]]), body: { data: "immediateResult" } }) const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: OnErrorData) => { caughtError = error } const messages: any[] = [] const onNext = (message: any) => { messages.push(message.data) } const onCompleted = () => {} const observer = { onError, onNext, onCompleted } // Publish before subscribe await new Promise((resolve, reject) => { const ablyPublisher = new Realtime({ key, log: { level: 0 } }) const publishChannel = ablyPublisher.channels.get(subscriptionId) publishChannel.publish( "update", { result: { data: "asyncResult" } }, err => { ablyPublisher.close() if (err) { reject(err) } else { resolve() } } ) }) const { dispose } = ablyHandler( operation, variables, cacheConfig, observer ) for (let i = 0; i < 20 && messages.length < 2; ++i) { await new Promise(resolve => setTimeout(resolve, 100)) } await dispose() ably.close() if (caughtError) throw caughtError expect(messages).toEqual(["immediateResult", "asyncResult"]) } ) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/createActionCableFetcherTest.ts000066400000000000000000000037141476434635200334440ustar00rootroot00000000000000import createActionCableFetcher from "../createActionCableFetcher" import type { Consumer } from "@rails/actioncable" import { parse } from "graphql" describe("createActionCableFetcherTest", () => { it("yields updates for subscriptions", () => { var handlers: any var log: [string, any][]= [] var dummyActionCableConsumer = { subscriptions: { create: (_conn: any, newHandlers: any) => { handlers = newHandlers return { perform: (evt: string, data: any) => { log.push([evt, data]) } } } } } const fetchLog: any[] = [] const dummyFetch = function(url: string, fetchArgs: any) { fetchLog.push([url, fetchArgs.custom]) return Promise.resolve({ json: () => { {} } }) } var options = { consumer: (dummyActionCableConsumer as unknown) as Consumer, url: "/some_graphql_endpoint", fetch: dummyFetch as typeof fetch, fetchOptions: { custom: true, } } var fetcher = createActionCableFetcher(options) const queryStr = "subscription listen { update { message } }" const doc = parse(queryStr) const res = fetcher({ operationName: "listen", query: queryStr, variables: {}}, { documentAST: doc }) const promise = res.next().then((result) => { handlers.connected() // trigger the GraphQL send expect(result).toEqual({ value: { data: "hello" } , done: false }) expect(fetchLog).toEqual([]) expect(log).toEqual([ ["execute", { operationName: "listen", query: queryStr, variables: {} }], ]) }) handlers.received({ result: { data: "hello" } }) // simulate an update return promise.then(() => { let res2 = fetcher({ operationName: null, query: "{ __typename } ", variables: {}}, {}) const promise2 = res2.next().then(() => { expect(fetchLog).toEqual([["/some_graphql_endpoint", true]]) }) return promise2 }) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/createActionCableHandlerTest.ts000066400000000000000000000035671476434635200334470ustar00rootroot00000000000000import { createActionCableHandler } from "../createActionCableHandler" import type { Consumer } from "@rails/actioncable" describe("createActionCableHandler", () => { it("returns a function producing a disposable subscription", () => { var wasDisposed = false var subscription = { unsubscribe: () => (wasDisposed = true) } var dummyActionCableConsumer = { subscriptions: { create: () => subscription }, } var options = { cable: (dummyActionCableConsumer as unknown) as Consumer } var producer = createActionCableHandler(options) producer({text: "", name: ""}, {}, {}, { onError: () => {}, onNext: () => {}, onCompleted: () => {} }).dispose() expect(wasDisposed).toEqual(true) }) it("uses a provided clientName and operation.id", () => { var handlers: any var log: [string, any][]= [] var dummyActionCableConsumer = { subscriptions: { create: (_conn: any, newHandlers: any) => { handlers = newHandlers return { perform: (evt: string, data: any) => { log.push([evt, data]) } } } } } var options = { cable: (dummyActionCableConsumer as unknown) as Consumer, clientName: "client-1", } var producer = createActionCableHandler(options); producer( {text: "", name: "", id: "abcdef"}, {}, {}, { onError: () => {}, onNext: (result: any) => { log.push(["onNext", result])}, onCompleted: () => { log.push(["onCompleted", null])} } ) handlers.connected() // trigger the GraphQL send handlers.received({ result: { data: { a: "1" } }, more: false }) expect(log).toEqual([ ["execute", { operationId: "client-1/abcdef", operationName: "", query: "", variables: {} }], ["onNext", { data: { a: "1" } }], ["onCompleted", null], ]) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/createPusherFetcherTest.ts000066400000000000000000000056461476434635200325540ustar00rootroot00000000000000import createPusherFetcher from "../createPusherFetcher" import type Pusher from "pusher-js" type MockChannel = { bind: (action: string, handler: Function) => void, unsubscribe: () => void, } describe("createPusherFetcher", () => { it("yields updates for subscriptions", () => { const pusher = { _channels: {} as {[key: string]: [string, Function][]}, trigger: function(channel: string, event: string, data: any) { var handlers = this._channels[channel] if (handlers) { handlers.forEach(function(handler: [string, Function]) { if (handler[0] == event) { handler[1](data) } }) } }, subscribe: function(channel: string): MockChannel { var handlers = this._channels[channel] if (!handlers) { handlers = this._channels[channel] = [] } return { bind: (action: string, handler: Function): void => { handlers.push([action, handler]) }, unsubscribe: () => { delete this._channels[channel] } } }, unsubscribe: (_channel: string): void => { }, } const fetchLog: any[] = [] const dummyFetch = function(url: string, fetchArgs: any) { fetchLog.push([url, fetchArgs.customOpt]) const dummyResponse = { json: () => { return { data: { hi: "First response" } } }, headers: { get() { return fetchArgs.body.includes("subscription") ? "abcd" : null } } } return Promise.resolve(dummyResponse) } const fetcher = createPusherFetcher({ pusher: (pusher as unknown) as Pusher, url: "/graphql", fetch: ((dummyFetch as unknown) as typeof fetch), fetchOptions: {customOpt: true} }) const result = fetcher({ variables: {}, operationName: "hello", body: "subscription hello { hi }" }, {}) return result.next().then((res) => { expect(res.value.data.hi).toEqual("First response") expect(fetchLog).toEqual([["/graphql", true]]) }).then(() => { const promise = result.next().then((res2) => { expect(res2).toEqual({ value: { data: { hi: "Bonjour" } }, done: false }) }) pusher.trigger("abcd", "update", { result: { data: { hi: "Bonjour" } } }) return promise.then(() => { // Test non-subscriptions too: expect(Object.keys(pusher._channels)).toEqual(["abcd"]) const queryResult = fetcher({ variables: {}, operationName: null, body: "{ __typename }"}, {}) return queryResult.next().then((res) => { expect(res.value.data).toEqual({ hi: "First response"}) return queryResult.next().then((res2) => { expect(res2.done).toEqual(true) expect(pusher._channels).toEqual({}) }) }) }) }) }) }) createRelaySubscriptionHandlerTest.ts000066400000000000000000000023201476434635200346670ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__import createRelaySubscriptionHandler from "../createRelaySubscriptionHandler" import { createLegacyRelaySubscriptionHandler } from "../createRelaySubscriptionHandler" import type { Consumer } from "@rails/actioncable" import { Network} from 'relay-runtime' describe("createRelaySubscriptionHandler", () => { it("returns a function producing a observable subscription", () => { var dummyActionCableConsumer = { subscriptions: { create: () => ({ unsubscribe: () => ( true) }) }, } var options = { cable: (dummyActionCableConsumer as unknown) as Consumer } var handler = createRelaySubscriptionHandler(options) var fetchQuery: any // basically, make sure this doesn't blow up during type-checking or runtime expect(Network.create(fetchQuery, handler)).toBeTruthy() }) }) describe("createLegacyRelaySubscriptionHandler", () => { it("still works", () => { var dummyActionCableConsumer = { subscriptions: { create: () => ({ unsubscribe: () => ( true) }) }, } var options = { cable: (dummyActionCableConsumer as unknown) as Consumer } expect(createLegacyRelaySubscriptionHandler(options)).toBeInstanceOf(Function) }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/__tests__/registryTest.ts000066400000000000000000000021471476434635200304620ustar00rootroot00000000000000import registry from "../registry" describe("subscription registry", () => { it("adds and unsubscribes", () => { // A subscription is something that responds to `.unsubscribe` var wasUnsubscribed1 = false var subscription1 = { unsubscribe: function() { wasUnsubscribed1 = true } } var wasUnsubscribed2 = false var subscription2 = { unsubscribe: function() { wasUnsubscribed2 = true } } // Adding a subscription returns an ID for unsubscribing var id1 = registry.add(subscription1) var id2 = registry.add(subscription2) expect(typeof id1).toEqual("number") expect(typeof id2).toEqual("number") // Unsubscribing calls the `.unsubscribe `function registry.unsubscribe(id1) expect(wasUnsubscribed1).toEqual(true) expect(wasUnsubscribed2).toEqual(false) registry.unsubscribe(id2) expect(wasUnsubscribed1).toEqual(true) expect(wasUnsubscribed2).toEqual(true) }) it("raises on unknown ids", () => { expect(() => { registry.unsubscribe(999) }).toThrow("No subscription found for id: 999") }) }) graphql-ruby-2.2.17/javascript_client/src/subscriptions/addGraphQLSubscriptions.ts000066400000000000000000000056731476434635200305620ustar00rootroot00000000000000import ActionCableSubscriber from "./ActionCableSubscriber" import PusherSubscriber from "./PusherSubscriber" import Pusher from "pusher-js" import type { Consumer } from "@rails/actioncable" interface Subscriber { subscribe: Function unsubscribe: Function } /** * Modify an Apollo network interface to * subscribe an unsubscribe using `cable:`. * Based on `addGraphQLSubscriptions` from `subscriptions-transport-ws`. * * This function assigns `.subscribe` and `.unsubscribe` functions * to the provided networkInterface. * @example Adding ActionCable subscriptions to a HTTP network interface * // Load ActionCable and create a consumer * var ActionCable = require('@rails/actioncable') * var cable = ActionCable.createConsumer() * window.cable = cable * * // Load ApolloClient and create a network interface * var apollo = require('apollo-client') * var RailsNetworkInterface = apollo.createNetworkInterface({ * uri: '/graphql', * opts: { * credentials: 'include', * }, * headers: { * 'X-CSRF-Token': $("meta[name=csrf-token]").attr("content"), * } * }); * * // Add subscriptions to the network interface * var addGraphQLSubscriptions = require("graphql-ruby-client/subscriptions/addGraphQLSubscriptions") * addGraphQLSubscriptions(RailsNetworkInterface, {cable: cable}) * * // Optionally, add persisted query support: * var OperationStoreClient = require("./OperationStoreClient") * RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) * * @example Subscriptions with Pusher & graphql-pro * var pusher = new Pusher(appId, options) * addGraphQLSubscriptions(RailsNetworkInterface, {pusher: pusher}) * * @param {Object} networkInterface - an HTTP NetworkInterface * @param {ActionCable.Consumer} options.cable - A cable for subscribing with * @param {Pusher} options.pusher - A pusher client for subscribing with */ function addGraphQLSubscriptions(networkInterface: any, options: { pusher?: Pusher, cable?: Consumer, subscriber?: Subscriber, decompress?: (compressed: string) => any, channelName?: string }) { if (!options) { options = {} } var subscriber: Subscriber if (options.subscriber) { // Right now this is just for testing subscriber = options.subscriber } else if (options.cable) { subscriber = new ActionCableSubscriber(options.cable, networkInterface, options.channelName) } else if (options.pusher) { subscriber = new PusherSubscriber(options.pusher, networkInterface, options.decompress) } else { throw new Error("Must provide cable: or pusher: option") } var networkInterfaceWithSubscriptions = Object.assign(networkInterface, { subscribe: function(request: any, handler: any) { var id = subscriber.subscribe(request, handler) return id }, unsubscribe(id: number) { subscriber.unsubscribe(id) }, }) return networkInterfaceWithSubscriptions } export default addGraphQLSubscriptions graphql-ruby-2.2.17/javascript_client/src/subscriptions/createAblyFetcher.ts000066400000000000000000000051511476434635200273660ustar00rootroot00000000000000import type Types from "ably" type AblyFetcherOptions = { ably: Types.Realtime, url: String, fetch?: typeof fetch, fetchOptions?: any, } type SubscriptionIteratorPayload = { value: any, done: Boolean } const clientName = "graphiql-subscriber" export default function createAblyFetcher(options: AblyFetcherOptions) { var currentChannel: Types.Types.RealtimeChannelCallbacks | null = null return async function*(graphqlParams: any, _fetcherParams: any) { var nextPromiseResolve: Function | null = null var shouldBreak = false var iterator = { [Symbol.asyncIterator]() { return { next(): Promise { return new Promise((resolve, _reject) => { nextPromiseResolve = resolve }) }, return(): Promise { if (currentChannel) { currentChannel.presence.leaveClient(clientName) currentChannel.unsubscribe() const channelName = currentChannel.name currentChannel.detach(() => { options.ably.channels.release(channelName) }) currentChannel = null nextPromiseResolve = null } return Promise.resolve({ value: null, done: true }) } } } } const fetchFn = options.fetch || window.fetch fetchFn("/graphql", { method: "POST", body: JSON.stringify(graphqlParams), headers: { 'content-type': 'application/json', }, ... options.fetchOptions }).then((r) => { const subId = r.headers.get("X-Subscription-ID") if (subId) { currentChannel && currentChannel.unsubscribe() currentChannel = options.ably.channels.get(subId, { modes: ["SUBSCRIBE", "PRESENCE"] }) currentChannel.presence.enterClient(clientName, "subscribed", (err) => { if (err) { console.error(err) } }) currentChannel.subscribe("update", (message: Types.Types.Message) => { console.log("update", message) if (nextPromiseResolve) { nextPromiseResolve({ value: message.data.result, done: false }) } }) if (nextPromiseResolve) { nextPromiseResolve({ value: r.json(), done: false }) } } else { shouldBreak = true if (nextPromiseResolve) { nextPromiseResolve({ value: r.json(), done: false}) } } }) for await (const payload of iterator) { yield payload if (shouldBreak) { break } } } } graphql-ruby-2.2.17/javascript_client/src/subscriptions/createAblyHandler.ts000066400000000000000000000140161476434635200273630ustar00rootroot00000000000000import { Realtime, Types } from "ably" // TODO: // - end-to-end test // - extract update code, inject it as a function? interface AblyHandlerOptions { ably: Realtime fetchOperation: Function } interface GraphQLError { message: string path: (string | number)[] locations: number[][] extensions?: object } type OnErrorData = AblyError | Error| GraphQLError[] interface ApolloObserver { onError: (err: OnErrorData) => void onNext: Function onCompleted: Function } const anonymousClientId = "graphql-subscriber" // Current max. number of rewound messages in the initial response to // subscribe. See // https://github.com/ably/docs/blob/baa0a4666079abba3a3e19e82eb99ca8b8a735d0/content/realtime/channels/channel-parameters/rewind.textile#additional-information // Note that using a higher value emits a warning. const maxNumRewindMessages = 100 class AblyError extends Error { constructor(public reason: Types.ErrorInfo) { super(reason.message) } get code() { return this.reason.code } get statusCode() { return this.reason.statusCode } } function createAblyHandler(options: AblyHandlerOptions) { const { ably, fetchOperation } = options const isAnonymousClient = () => !ably.auth.clientId || ably.auth.clientId === "*" return ( operation: object, variables: object, cacheConfig: object, observer: ApolloObserver ) => { let channel: Types.RealtimeChannelCallbacks | null = null const dispatchResult = (result: { errors?: GraphQLError[]; data: any }) => { if (result) { if (result.errors) { // What kind of error stuff belongs here? observer.onError(result.errors) } else if (result.data && Object.keys(result.data).length > 0) { observer.onNext({ data: result.data }) } } } const updateHandler = (message: Types.Message) => { // TODO Extract this code // When we get a response, send the update to `observer` const payload = message.data dispatchResult(payload.result) if (!payload.more) { // Subscription is finished observer.onCompleted() } } ;(async () => { try { // POST the subscription like a normal query const response = await fetchOperation(operation, variables, cacheConfig) const channelName = response.headers.get("X-Subscription-ID") if (!channelName) { throw new Error("Missing X-Subscription-ID header") } const channelKey = response.headers.get("X-Subscription-Key") channel = ably.channels.get(channelName, { params: { rewind: String(maxNumRewindMessages) }, cipher: channelKey ? { key: channelKey } : undefined, modes: ["SUBSCRIBE", "PRESENCE"] }) channel.on("failed", function(stateChange: Types.ChannelStateChange) { observer.onError( stateChange.reason ? new AblyError(stateChange.reason) : new Error("Ably channel changed to failed state") ) }) channel.on("suspended", function( stateChange: Types.ChannelStateChange ) { // Note: suspension can be a temporary condition and isn't necessarily // an error, however we handle the case where the channel gets // suspended before it is attached because that's the only way to // propagate error 90010 (see https://help.ably.io/error/90010) if ( stateChange.previous === "attaching" && stateChange.current === "suspended" ) { observer.onError( stateChange.reason ? new AblyError(stateChange.reason) : new Error("Ably channel suspended before being attached") ) } }) // Register presence, so that we can detect empty channels and clean them up server-side const enterCallback = (errorInfo: Types.ErrorInfo | undefined) => { if (errorInfo && channel) { observer.onError(new AblyError(errorInfo)) } } if (isAnonymousClient()) { channel.presence.enterClient( anonymousClientId, "subscribed", enterCallback ) } else { channel.presence.enter("subscribed", enterCallback) } // When you get an update from ably, give it to Relay channel.subscribe("update", updateHandler) // Dispatch the result _after_ setting up the channel, // because Relay might immediately dispose of the subscription. // (In that case, we want to make sure the channel is cleaned up properly.) dispatchResult(response.body) } catch (error) { observer.onError(error as Error) } })() return { dispose: async () => { try { if (channel) { const disposedChannel = channel channel = null disposedChannel.unsubscribe() // Ensure channel is no longer attaching, as otherwise detach does // nothing if (disposedChannel.state === "attaching") { await new Promise((resolve, _reject) => { const onStateChange = ( stateChange: Types.ChannelStateChange ) => { if (stateChange.current !== "attaching") { disposedChannel.off(onStateChange) resolve() } } disposedChannel.on(onStateChange) }) } await new Promise((resolve, reject) => { disposedChannel.detach(err => { if (err) { reject(new AblyError(err)) } else { resolve() } }) }) ably.channels.release(disposedChannel.name) } } catch (error) { observer.onError(error as Error) } } } } } export { createAblyHandler, AblyHandlerOptions, OnErrorData } graphql-ruby-2.2.17/javascript_client/src/subscriptions/createActionCableFetcher.ts000066400000000000000000000054131476434635200306440ustar00rootroot00000000000000 import { visit } from "graphql"; import type { Consumer, Subscription } from "@rails/actioncable" type ActionCableFetcherOptions = { consumer: Consumer, url: string, channelName?: string, fetch?: typeof fetch, fetchOptions?: any, } type SubscriptionIteratorPayload = { value: any, done: Boolean } export default function createActionCableFetcher(options: ActionCableFetcherOptions) { let currentChannel: Subscription | null = null const consumer = options.consumer const url = options.url || "/graphql" const channelName = options.channelName || "GraphqlChannel" const subscriptionFetcher = async function*(graphqlParams: any, fetcherOpts: any) { let isSubscription = false; let nextPromiseResolve: Function | null = null; fetcherOpts.documentAST && visit(fetcherOpts.documentAST, { OperationDefinition(node) { if (graphqlParams.operationName === node.name?.value && node.operation === 'subscription') { isSubscription = true; } }, }); if (isSubscription) { currentChannel?.unsubscribe() currentChannel = consumer.subscriptions.create(channelName, { connected: function() { currentChannel?.perform("execute", { query: graphqlParams.query, operationName: graphqlParams.operationName, variables: graphqlParams.variables, }) }, received: function(data: any) { if (nextPromiseResolve) { nextPromiseResolve({ value: data.result, done: false }) } } } as any ) var iterator = { [Symbol.asyncIterator]() { return { next(): Promise { return new Promise((resolve, _reject) => { nextPromiseResolve = resolve }) }, return(): Promise { if (currentChannel) { currentChannel.unsubscribe() currentChannel = null } return Promise.resolve({ value: null, done: true }) } } } } for await (const payload of iterator) { yield payload } } else { const fetchFn = options.fetch || window.fetch // Not a subscription fetcher, post to the given URL yield fetchFn(url, { method: "POST", body: JSON.stringify({ query: graphqlParams.query, operationName: graphqlParams.operationName, variables: graphqlParams.variables, }), headers: { 'content-type': 'application/json', }, ... options.fetchOptions }).then((r) => r.json()) return } } return subscriptionFetcher } graphql-ruby-2.2.17/javascript_client/src/subscriptions/createActionCableHandler.ts000066400000000000000000000051701476434635200306410ustar00rootroot00000000000000import type { Consumer } from "@rails/actioncable" /** * Create a Relay Modern-compatible subscription handler. * * @param {ActionCable.Consumer} cable - An ActionCable consumer from `.createConsumer` * @param {OperationStoreClient} operations - A generated OperationStoreClient for graphql-pro's OperationStore * @return {Function} */ interface ActionCableHandlerOptions { cable: Consumer operations?: { getOperationId: Function} channelName?: string clientName?: string } function createActionCableHandler(options: ActionCableHandlerOptions) { return function (operation: { text: string, name: string, id?: string }, variables: object, _cacheConfig: object, observer: {onError: Function, onNext: Function, onCompleted: Function}) { // unique-ish var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var cable = options.cable var operations = options.operations // Register the subscription by subscribing to the channel const channel = cable.subscriptions.create({ channel: options.channelName || "GraphqlChannel", channelId: channelId, }, { connected: function() { var channelParams: object // Once connected, send the GraphQL data over the channel // Use the stored operation alias if possible if (operations) { channelParams = { variables: variables, operationName: operation.name, operationId: operations.getOperationId(operation.name) } } else { channelParams = { variables: variables, operationName: operation.name, query: operation.text, operationId: (operation.id && options.clientName ? (options.clientName + "/" + operation.id) : null), } } channel.perform("execute", channelParams) }, // This result is sent back from ActionCable. received: function(payload: { result: { errors: any[], data: object }, more: boolean}) { // When we get a response, send the update to `observer` const result = payload.result if (result && result.errors) { // What kind of error stuff belongs here? observer.onError(result.errors) } else if (result) { observer.onNext({data: result.data}) } if (!payload.more) { // Subscription is finished observer.onCompleted() } } }) // Return an object for Relay to unsubscribe with return { dispose: function() { channel.unsubscribe() } } } } export { createActionCableHandler, ActionCableHandlerOptions } graphql-ruby-2.2.17/javascript_client/src/subscriptions/createPusherFetcher.ts000066400000000000000000000040651476434635200277500ustar00rootroot00000000000000import type Pusher from "pusher-js" import type { Channel } from "pusher-js" type PusherFetcherOptions = { pusher: Pusher, url: String, fetch?: typeof fetch, fetchOptions: any, } type SubscriptionIteratorPayload = { value: any, done: Boolean } export default function createPusherFetcher(options: PusherFetcherOptions) { var currentChannel: Channel | null = null return async function*(graphqlParams: any, _fetcherParams: any) { var nextPromiseResolve: Function | null = null var shouldBreak = false var iterator = { [Symbol.asyncIterator]() { return { next(): Promise { return new Promise((resolve, _reject) => { nextPromiseResolve = resolve }) }, return(): Promise { if (currentChannel) { currentChannel.unsubscribe() currentChannel = null } return Promise.resolve({ value: null, done: true }) } } } } const fetchFn = options.fetch || window.fetch fetchFn("/graphql", { method: "POST", body: JSON.stringify(graphqlParams), headers: { 'content-type': 'application/json', }, ...options.fetchOptions }).then((r) => { const subId = r.headers.get("X-Subscription-ID") if (subId) { currentChannel && currentChannel.unsubscribe() currentChannel = options.pusher.subscribe(subId) currentChannel.bind("update", (payload: any) => { if (nextPromiseResolve) { nextPromiseResolve({ value: payload.result, done: false }) } }) if (nextPromiseResolve) { nextPromiseResolve({ value: r.json(), done: false }) } } else { shouldBreak = true if (nextPromiseResolve) { nextPromiseResolve({ value: r.json(), done: false}) } } }) for await (const payload of iterator) { yield payload if (shouldBreak) { break } } } } graphql-ruby-2.2.17/javascript_client/src/subscriptions/createPusherHandler.ts000066400000000000000000000037331476434635200277460ustar00rootroot00000000000000import Pusher from "pusher-js" // TODO: // - end-to-end test // - extract update code, inject it as a function? interface PusherHandlerOptions { pusher: Pusher, fetchOperation: Function, decompress?: Function, } function createPusherHandler(options: PusherHandlerOptions) { var pusher = options.pusher var fetchOperation = options.fetchOperation var decompress = options.decompress || function(_compressed: string) { throw new Error("Received compressed_result but createPusherHandler wasn't configured with `decompress: (result: string) => any`. Add this configuration.") } return function (operation: object, variables: object, cacheConfig: object, observer: { onNext: Function, onError: Function, onCompleted: Function}) { var channelName: string // POST the subscription like a normal query fetchOperation(operation, variables, cacheConfig).then(function(response: { headers: { get: Function } }) { channelName = response.headers.get("X-Subscription-ID") var channel = pusher.subscribe(channelName) // When you get an update from pusher, give it to Relay channel.bind("update", function(payload: {more: boolean, result?: any, compressed_result?: string}) { // TODO Extract this code // When we get a response, send the update to `observer` let result: any = null if (payload.compressed_result) { result = decompress(payload.compressed_result) } else { result = payload.result } if (result && result.errors) { // What kind of error stuff belongs here? observer.onError(result.errors) } else if (result) { observer.onNext({data: result.data}) } if (!payload.more) { // Subscription is finished observer.onCompleted() } }) }) return { dispose: function() { pusher.unsubscribe(channelName) } } } } export { createPusherHandler, PusherHandlerOptions } graphql-ruby-2.2.17/javascript_client/src/subscriptions/createRelaySubscriptionHandler.ts000066400000000000000000000054571476434635200321660ustar00rootroot00000000000000import { createActionCableHandler, ActionCableHandlerOptions } from "./createActionCableHandler" import { createPusherHandler, PusherHandlerOptions } from "./createPusherHandler" import { createAblyHandler, AblyHandlerOptions } from "./createAblyHandler" import { RequestParameters, Variables, Observable } from "relay-runtime" function createLegacyRelaySubscriptionHandler(options: ActionCableHandlerOptions | PusherHandlerOptions | AblyHandlerOptions) { var handler: any if ((options as ActionCableHandlerOptions).cable) { handler = createActionCableHandler(options as ActionCableHandlerOptions) } else if ((options as PusherHandlerOptions).pusher) { handler = createPusherHandler(options as PusherHandlerOptions) } else if ((options as AblyHandlerOptions).ably) { handler = createAblyHandler(options as AblyHandlerOptions) } else { throw new Error("Missing options for subscription handler") } return handler } /** * Transport-agnostic wrapper for Relay Modern subscription handlers. * @example Add ActionCable subscriptions * var subscriptionHandler = createHandler({ * cable: cable, * operations: OperationStoreClient, * }) * var network = Network.create(fetchQuery, subscriptionHandler) * @param {ActionCable.Consumer} options.cable - A consumer from `.createConsumer` * @param {Pusher} options.pusher - A Pusher client * @param {Ably.Realtime} options.ably - An Ably client * @param {OperationStoreClient} options.operations - A generated `OperationStoreClient` for graphql-pro's OperationStore * @return {Function} A handler for a Relay Modern network */ function createRelaySubscriptionHandler(options: ActionCableHandlerOptions | PusherHandlerOptions | AblyHandlerOptions) { const handler = createLegacyRelaySubscriptionHandler(options) // Turn the handler into a relay-ready subscribe function return (request: RequestParameters, variables: Variables): any => { return Observable.from({ subscribe: (observer: { next: any | ((v: any) => void); complete: () => void; error: (error: Error) => void; }) => { const client = handler( { text: request.text || "", name: request.name, id: request.id, }, variables, {}, { onError: (_error: Error) => { observer.error; }, onNext: (res: any) => { if (!res || !res.data) { return; } observer.next(res); }, onCompleted: observer.complete, } ); return { unsubscribe: () => { client.dispose(); }, }; }, }); }; } export { createLegacyRelaySubscriptionHandler } export default createRelaySubscriptionHandler graphql-ruby-2.2.17/javascript_client/src/subscriptions/registry.ts000066400000000000000000000017031476434635200256610ustar00rootroot00000000000000interface ApolloSubscription { unsubscribe: Function } // State management for subscriptions. // Used to add subscriptions to an Apollo network intrface. class ApolloSubscriptionRegistry { // Apollo expects unique ids to reference each subscription, // here's a simple incrementing ID generator which starts at 1 // (so it's always truthy) _id: number // for unsubscribing when Apollo asks us to _subscriptions: {[key: number]: ApolloSubscription} constructor() { this._id = 1 this._subscriptions = {} } add(subscription: ApolloSubscription): number { var id = this._id++ this._subscriptions[id] = subscription return id } unsubscribe(id: number): void { var subscription = this._subscriptions[id] if (!subscription) { throw new Error("No subscription found for id: " + id) } subscription.unsubscribe() delete this._subscriptions[id] } } export default new ApolloSubscriptionRegistry graphql-ruby-2.2.17/javascript_client/src/sync/000077500000000000000000000000001476434635200215055ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/000077500000000000000000000000001476434635200234435ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/__snapshots__/000077500000000000000000000000001476434635200262615ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/__snapshots__/generateClientTest.ts.snap000066400000000000000000000057421476434635200333720ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`returns generated code 1`] = ` " /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = { "GetStuff": "b8086942c2fbb6ac69b97cbade848033" } /** * The client who synced these operations with the server * @return {String} * @private */ var _client = "test-client" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + "/" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error("Failed to find persisted alias for operation name: " + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient " `; generateJsonClientTest.ts.snap000066400000000000000000000004341476434635200341360ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/__snapshots__// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`generates a valid json object string that maps names to operations 1`] = ` "{ "a": "b", "c-d": "e-f" }" `; exports[`generates a valid json object string that maps names to operations 2`] = ` { "a": "b", "c-d": "e-f", } `; prepareIsolatedFilesTest.ts.snap000066400000000000000000000016571476434635200344710ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/__snapshots__// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`builds out single operations 1`] = ` [ { "alias": "", "body": "query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } }", "name": "GetStuffIsolated", }, { "alias": "", "body": "query GetStuffIsolated2 { things { existHere } }", "name": "GetStuffIsolated2", }, ] `; exports[`with --add-typename builds out single operations with __typename fields 1`] = ` [ { "alias": "", "body": "query GetStuffIsolated { ...FragIsolated things { existHere __typename } } fragment FragIsolated on Query { evenMoreStuff { stuffInside __typename } }", "name": "GetStuffIsolated", }, { "alias": "", "body": "query GetStuffIsolated2 { things { existHere __typename } }", "name": "GetStuffIsolated2", }, ] `; preparePersistedQueryListTest.ts.snap000066400000000000000000000007131476434635200355560ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/__snapshots__// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`reads generate-persisted-query-manifest output 1`] = ` [ { "alias": "4a29162b05ee4d82ad02e8f50af4bf112f47181ec558a7100a", "body": "query TestQuery1 { testing { id label description __typename } }", "name": "TestQuery1", }, { "alias": "xyz-123", "body": "query TestQuery2 { testing2 { id2 label description2 __typename } }", "name": "TestQuery2", }, ] `; graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/__snapshots__/prepareProjectTest.ts.snap000066400000000000000000000016361476434635200334240ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`merging a project builds out separate operations 1`] = ` [ { "alias": "", "body": "query GetStuff2 { stuff ...Frag1 ...Frag2 } fragment Frag1 on Query { moreStuff } fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff }", "name": "GetStuff2", }, { "alias": "", "body": "query GetStuff { ...Frag1 } fragment Frag1 on Query { moreStuff }", "name": "GetStuff", }, ] `; exports[`merging a project with --add-typename builds out operation with __typename fields 1`] = ` [ { "alias": "", "body": "query GetStuff3 { stuff { withStuffInside __typename } ...Frag2 ...Frag4 } fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } fragment Frag4 on Query { evenMoreStuff { stuffInside __typename } }", "name": "GetStuff3", }, ] `; graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/addTypenameToSelectionSetTest.ts000066400000000000000000000006671476434635200317440ustar00rootroot00000000000000import {addTypenameToSelectionSet} from "../addTypenameToSelectionSet" import { parse, print } from "graphql" describe("adding typename", () => { it("adds to fields and inline fragments", () => { var doc = parse("{ a { b ... { c } } }") var newDoc = addTypenameToSelectionSet(doc) var newString = print(newDoc).replace(/\s+/g, " ").trim() expect(newString).toEqual("{ a { b ... { c __typename } __typename } }") }) }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/generate-persisted-query-manifest.json000066400000000000000000000007711476434635200331040ustar00rootroot00000000000000{ "format": "apollo-persisted-query-manifest", "version": 1, "operations": [ { "id": "4a29162b05ee4d82ad02e8f50af4bf112f47181ec558a7100a", "name": "TestQuery1", "type": "query", "body": "query TestQuery1 {\n testing {\n id\n label\n description\n __typename\n} }" }, { "id": "xyz-123", "name": "TestQuery2", "type": "mutation", "body": "query TestQuery2 {\n testing2 {\n id2\n label\n description2\n __typename\n} }" } ] } graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/generateClientTest.ts000066400000000000000000000003611476434635200276040ustar00rootroot00000000000000import { generateClient } from "../generateClient" it("returns generated code", function() { var code = generateClient({ path: "./src/__tests__/documents/*.graphql", client: "test-client", }) expect(code).toMatchSnapshot() }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/generateJsClientTest.ts000066400000000000000000000044021476434635200301010ustar00rootroot00000000000000import { generateClientCode, JS_TYPE, OperationStoreClient } from "../generateClient" import fs from "fs" function withExampleClient(mapName: string, callback: (client: OperationStoreClient) => void) { // Generate some code and write it to a file var exampleOperations = [ {name: "a", alias: "b", body: ""}, {name: "c-d", alias: "e-f", body: ""} ] var jsCode = generateClientCode("example-client", exampleOperations, JS_TYPE) var filename = "./src/sync/__tests__/" + mapName + ".js" fs.writeFileSync(filename, jsCode) // Load the module and use it var exampleModule = require("./" + mapName) callback(exampleModule) // Clean up the generated file fs.unlinkSync(filename) } it("generates a valid JavaScript module that maps names to operations", () => { withExampleClient("map1", (exampleClient) => { // It does the mapping expect(exampleClient.getPersistedQueryAlias("a")).toEqual("b") expect(exampleClient.getPersistedQueryAlias("c-d")).toEqual("e-f") // It returns a param expect(exampleClient.getOperationId("a")).toEqual("example-client/b") }) }) it("generates an Apollo middleware", () => { withExampleClient("map2", (exampleClient) => { var nextWasCalled = false var next = () => { nextWasCalled = true } var req = { operationName: "a", query: "x", operationId: "", } exampleClient.apolloMiddleware.applyMiddleware({request: req}, next) expect(nextWasCalled).toEqual(true) expect(req.query).toBeUndefined() expect(req.operationId).toEqual("example-client/b") }) }) it("generates an Apollo Link", () => { var fakeOperation = { operationName: "a", context: { http: {} }, setContext: function(c: { http: object }) { this.context = c }, extensions: { operationId: "", }, } var forwardedOperation: object var fakeForward = function(operation: object) { forwardedOperation = operation } withExampleClient("map3", (exampleClient) => { exampleClient.apolloLink(fakeOperation, fakeForward) expect(fakeOperation.extensions.operationId).toEqual("example-client/b") expect(fakeOperation.context.http).toEqual({includeQuery: false, includeExtensions: true}) expect(forwardedOperation).toEqual(fakeOperation) }) }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/generateJsonClientTest.ts000066400000000000000000000012641476434635200304410ustar00rootroot00000000000000import { generateClientCode, JSON_TYPE } from "../generateClient" function withExampleClient(callback: (client: string) => void) { // Generate some code and write it to a file var exampleOperations = [ {name: "a", alias: "b", body: ""}, {name: "c-d", alias: "e-f", body: ""} ] var json = generateClientCode("example-client", exampleOperations, JSON_TYPE) // Run callback with generated client callback(json) } it("generates a valid json object string that maps names to operations", () => { withExampleClient((json) => { expect(json).toMatchSnapshot() // String version expect(JSON.parse(json)).toMatchSnapshot() // Object version (i.e., valid JSON) }) }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/prepareIsolatedFilesTest.ts000066400000000000000000000012241476434635200307600ustar00rootroot00000000000000import prepareIsolatedFiles from "../prepareIsolatedFiles" it("builds out single operations", () => { var filenames = [ "./src/__tests__/project/op_isolated_1.graphql", "./src/__tests__/project/op_isolated_2.graphql", ] var ops = prepareIsolatedFiles(filenames, false) expect(ops).toMatchSnapshot() }) describe("with --add-typename", () => { it("builds out single operations with __typename fields", () => { var filenames = [ "./src/__tests__/project/op_isolated_1.graphql", "./src/__tests__/project/op_isolated_2.graphql", ] var ops = prepareIsolatedFiles(filenames, true) expect(ops).toMatchSnapshot() }) }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/preparePersistedQueryListTest.ts000066400000000000000000000004571476434635200320640ustar00rootroot00000000000000import preparePersistedQueryList from "../preparePersistedQueryList" it("reads generate-persisted-query-manifest output", () => { const manifestPath = "./src/sync/__tests__/generate-persisted-query-manifest.json" var ops = preparePersistedQueryList(manifestPath) expect(ops).toMatchSnapshot() }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/prepareProjectTest.ts000066400000000000000000000025101476434635200276360ustar00rootroot00000000000000import prepareProject from "../prepareProject" describe("merging a project", () => { it("builds out separate operations", () => { var filenames = [ "./src/__tests__/project/op_2.graphql", "./src/__tests__/project/op_1.graphql", "./src/__tests__/project/frag_1.graphql", "./src/__tests__/project/frag_2.graphql", "./src/__tests__/project/frag_3.graphql", ] var ops = prepareProject(filenames, false) expect(ops).toMatchSnapshot() }) describe("with --add-typename", () => { it("builds out operation with __typename fields", () => { var filenames = [ "./src/__tests__/project/op_3.graphql", "./src/__tests__/project/frag_2.graphql", "./src/__tests__/project/frag_3.graphql", "./src/__tests__/project/frag_4.graphql", ] var ops = prepareProject(filenames, true) expect(ops).toMatchSnapshot() }) }) it("blows up on duplicate names", () => { var filenames = [ "./src/__tests__/documents/doc1.graphql", "./src/__tests__/project/op_2.graphql", "./src/__tests__/project/op_1.graphql", "./src/__tests__/project/frag_1.graphql", ] expect(() => { prepareProject(filenames, false) }).toThrow("Found duplicate definition name: GetStuff, fragment & operation names must be unique to sync") }) }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/removeClientFieldsTest.ts000066400000000000000000000046141476434635200304430ustar00rootroot00000000000000import { removeClientFieldsFromString } from "../removeClientFields" describe("removing @client fields", () => { function normalizeString(str: string) { return str.replace(/\s+/g, " ").trim() } it("returns a string without any fields with @client", () => { var newString = removeClientFieldsFromString("{ f1 f2 @client { a b } f3 { a b @client } }") var expectedString = "{ f1 f3 { a } }" expect(normalizeString(newString)).toEqual(expectedString) }) it("leaves other strings unchanged", () => { var originalString = "{ f1 f2 @other { a b } f3 { a b @notClient } }" var newString = removeClientFieldsFromString(originalString) expect(normalizeString(newString)).toEqual(originalString) }) it("removes references to fragments that contain all client fields", () => { var originalString = ` { f1 ...Fragment1 ... on Query { f3 ...Fragment2 } ...Fragment3 } fragment Fragment1 on Query { f2 @client f3 ...Fragment2 } fragment Fragment2 on Query { f4 @client f5 @client f6 @client { f7 f8 } } fragment Fragment3 on Query { ...Fragment2 } ` var expectedString = ` { f1 ...Fragment1 ... on Query { f3 } } fragment Fragment1 on Query { f3 } ` var newString = removeClientFieldsFromString(originalString) expect(normalizeString(newString)).toEqual(normalizeString(expectedString)) }) it("removes now-unused variables", () => { var newString = removeClientFieldsFromString("query($thing: ID!){ f1 f2(thing: $thing) @client }") var expectedString = "{ f1 }" expect(normalizeString(newString)).toEqual(expectedString) }) it("removes fragments that are spread inside client fields", () => { // from https://github.com/apollographql/apollo-client/pull/6892/ var originalString = ` query Simple { networkField field @client { ...ClientFragment } } fragment ClientFragment on Thing { ...NestedFragment } fragment NestedFragment on Thing { otherField bar }` var expectedString = ` query Simple { networkField } ` var newString = removeClientFieldsFromString(originalString) expect(normalizeString(newString)).toEqual(normalizeString(expectedString)) }) }) graphql-ruby-2.2.17/javascript_client/src/sync/__tests__/sendPayloadTest.ts000066400000000000000000000065101476434635200271200ustar00rootroot00000000000000jest.dontMock('nock'); import nock from "nock" import Logger from "../logger"; import sendPayload from "../sendPayload" var fakeLogger = { log: function() {}, bright: function(str: string) { return str }, colorize: function(str: string) { return str }, red: function(str: string) { return str }, green: function(str: string) { return str }, error: function() {}, isQuiet: true, } as Logger describe("Posting GraphQL to OperationStore Endpoint", () => { it("Posts to the specified URL", () => { var mock = nock("http://example.com") .post("/stored_operations/sync") .reply(200, { "ok" : "ok" }) return sendPayload("payload", { url: "http://example.com/stored_operations/sync", logger: fakeLogger }).then(function() { expect(mock.isDone()).toEqual(true) }) }) it("Uses HTTPS when provided", () => { var mock = nock("https://example2.com") .post("/stored_operations/sync") .reply(200, { "ok" : "ok" }) return sendPayload("payload", { url: "https://example2.com/stored_operations/sync", logger: fakeLogger }).then(function() { expect(mock.isDone()).toEqual(true) }) }) it("Uses auth, port, and query", () => { var mock = nock("https://example2.com:229") .post("/stored_operations/sync?q=1") .basicAuth({ user: "username", pass: "pass" }) .reply(200, { "ok" : "ok" }) return sendPayload("payload", { url: "https://username:pass@example2.com:229/stored_operations/sync?q=1", logger: fakeLogger }).then(function() { expect(mock.isDone()).toEqual(true) }) }) it("Returns the response JSON to the promise", () => { nock("http://example.com") .post("/stored_operations/sync") .reply(200, { result: "ok" }) return sendPayload("payload", { url: "http://example.com/stored_operations/sync", logger: fakeLogger }).then(function(response) { expect(response).toEqual('{"result":"ok"}') }) }) it("Sends headers and changeset version", () => { var mock = nock("http://example.com", { reqheaders: { thing: "Stuff", "Changeset-Version": "2023-01-01", } }) .post("/stored_operations/sync") .reply(200, { result: "ok" }) return sendPayload("payload", { url: "http://example.com/stored_operations/sync", logger: fakeLogger, headers: { thing: "Stuff" }, changesetVersion: "2023-01-01" }).then(function(_response) { expect(mock.isDone()).toEqual(true) }) }) it("Adds an hmac-sha256 header if key is present", () => { var payload = { "payload": [1,2,3] } var key = "2f26b770ded2a04279bc4bf824ca54ac" // ruby -ropenssl -e 'puts OpenSSL::HMAC.hexdigest("SHA256", "2f26b770ded2a04279bc4bf824ca54ac", "{\"payload\":[1,2,3]}")' // f6eab31abc2fa446dbfd2e9c10a778aaffd4d0c1d62dd9513d6f7ea60557987c var signature = "f6eab31abc2fa446dbfd2e9c10a778aaffd4d0c1d62dd9513d6f7ea60557987c" var mock = nock("http://example.com", { reqheaders: { 'authorization': 'GraphQL::Pro Abc ' + signature }}) .post("/stored_operations/sync") .reply(200, { result: "ok" }) var opts = {secret: key, client: "Abc", url: "http://example.com/stored_operations/sync", logger: fakeLogger } return sendPayload(payload, opts).then(function(response) { expect(response).toEqual('{"result":"ok"}') expect(mock.isDone()).toEqual(true) }) }) }) graphql-ruby-2.2.17/javascript_client/src/sync/addTypenameToSelectionSet.ts000066400000000000000000000022651476434635200271420ustar00rootroot00000000000000import { visit, ASTNode, FieldNode, InlineFragmentNode, Kind } from "graphql" const TYPENAME_FIELD: FieldNode = { kind: Kind.FIELD, name: { kind: Kind.NAME, value: "__typename", }, selectionSet: { kind: Kind.SELECTION_SET, selections: [] } } function addTypenameIfAbsent(node: FieldNode | InlineFragmentNode): undefined | FieldNode | InlineFragmentNode { if (node.selectionSet) { const alreadyHasThisField = node.selectionSet.selections.some(function(selection) { return ( selection.kind === "Field" && selection.name.value === "__typename" ) }) if (!alreadyHasThisField) { return { ...node, selectionSet: { ...node.selectionSet, selections: [...node.selectionSet.selections, TYPENAME_FIELD] } } } else { return undefined } } else { return undefined } } function addTypenameToSelectionSet(node: ASTNode) { var visitor = { Field: { leave: addTypenameIfAbsent, }, InlineFragment: { leave: addTypenameIfAbsent, } } var newNode = visit(node, visitor) return newNode } export { addTypenameToSelectionSet, addTypenameIfAbsent } graphql-ruby-2.2.17/javascript_client/src/sync/generateClient.ts000066400000000000000000000117201476434635200250070ustar00rootroot00000000000000import { globSync } from "glob" import prepareRelay from "./prepareRelay" import prepareIsolatedFiles from './prepareIsolatedFiles' import prepareProject from "./prepareProject" import md5 from "./md5" import generateJs from "./outfileGenerators/js" import generateJson from "./outfileGenerators/json" var JS_TYPE = "js"; var JSON_TYPE = "json"; var generators = { [JS_TYPE]: generateJs, [JSON_TYPE]: generateJson, }; interface GenerateClientCodeOptions { path?: string // A glob to recursively search for `.graphql` files (Default is `./`) mode?: string // If `"file"`, treat each file separately. If `"project"`, concatenate all files and extract each operation. If `"relay"`, treat it as relay-compiler output addTypename?: boolean // Indicates if the "__typename" field are automatically added to your queries clientType?: string // The type of the generated code (i.e., json, js) client: string // the Client ID that these operations belong to hash?: Function // A custom hash function for query strings with the signature `options.hash(string) => digest` (Default is `md5(string) => digest`) verbose?: boolean // If true, print debug output } interface OperationStoreClient { getOperationId: (operationName: string) => string getPersistedQueryAlias: (operationName: string) => string apolloMiddleware: { applyMiddleware: (req: any, next: any) => any } apolloLink: (operation: any, forward: any) => any } /** * Generate a JavaScript client module based on local `.graphql` files. * * See {gatherOperations} and {generateClientCode} for options. * @return {String} The generated JavaScript code */ function generateClient(options: GenerateClientCodeOptions): string { var payload = gatherOperations(options) var generatedCode = generateClientCode(options.client, payload.operations, options.clientType) return generatedCode } interface ClientOperation { alias: string, name?: string, body: string, } /** * Parse files in the specified path and generate an alias for each operation. */ function gatherOperations(options: GenerateClientCodeOptions) { var graphqlGlob = options.path || "./" // Check for file ext already, add it if missing var containsFileExt = graphqlGlob.indexOf(".graphql") > -1 || graphqlGlob.indexOf(".gql") > -1 if (!containsFileExt) { graphqlGlob = graphqlGlob + "**/*.graphql*" } var hashFunc = options.hash || md5 var filesMode = options.mode || (graphqlGlob.indexOf("__generated__") > -1 ? "relay" : "project") var addTypename = !!options.addTypename var verbose = !!options.verbose var operations: ClientOperation[] = [] var filenames: string[] = globSync(graphqlGlob, {}).sort() if (verbose) { console.log("[Sync] glob: ", graphqlGlob) console.log("[Sync] " + filenames.length + " files:") console.log(filenames.map(function(f) { return "[Sync] - " + f }).join("\n")) } if (filesMode == "relay") { operations = prepareRelay(filenames) } else { if (filesMode === "file") { operations = prepareIsolatedFiles(filenames, addTypename) } else if (filesMode === "project") { operations = prepareProject(filenames, addTypename) } else { throw new Error("Unexpected mode: " + filesMode) } // Update the operations with the hash of the body operations.forEach(function(op) { op.alias = hashFunc(op.body) // console.log("operation", op.alias, op.body) }) } return { operations: operations } } /** * Given a map of { name => alias } pairs, generate outfile based on type. * @param {String} clientName - the client ID that this map belongs to * @param {Object} nameToAlias - `name => alias` pairs * @param {String} type - the outfile's type * @return {String} generated outfile code */ function generateClientCode(clientName: string, operations: ClientOperation[], type?: string): string { if (!clientName) { throw new Error("Client name is required to generate a persisted alias lookup map"); } var nameToAlias: {[key: string] : string | null} = {} operations.forEach(function(op) { // This can be blank from relay-perisisted-output, // but typescript doesn't know that we don't use this function in that case // (Er, I should make _two_ interfaces, but I haven't yet.) if (op.name) { nameToAlias[op.name] = op.alias } }) // Build up the map var keyValuePairs = "{" keyValuePairs += Object.keys(nameToAlias).map(function(operationName) { var persistedAlias = nameToAlias[operationName] return "\n \"" + operationName + "\": \"" + persistedAlias + "\"" }).join(",") keyValuePairs += "\n}" var outfileType = type || JS_TYPE var generateOutfile = generators[outfileType]; if (!generateOutfile) { throw new Error("Unknown generator type " + outfileType + " encountered for generating the outFile"); } return generateOutfile(outfileType, clientName, keyValuePairs); } export { generateClient, generateClientCode, gatherOperations, JS_TYPE, JSON_TYPE, ClientOperation, OperationStoreClient, } graphql-ruby-2.2.17/javascript_client/src/sync/index.ts000066400000000000000000000244631476434635200231750ustar00rootroot00000000000000import sendPayload from "./sendPayload" import { generateClientCode, gatherOperations, ClientOperation } from "./generateClient" import Logger from "./logger" import fs from "fs" import { removeClientFieldsFromString } from "./removeClientFields" import preparePersistedQueryList from "./preparePersistedQueryList" interface SyncOptions { path?: string, relayPersistedOutput?: string, apolloAndroidOperationOutput?: string, apolloCodegenJsonOutput?: string, apolloPersistedQueryManifest?: string, secret?: string url?: string, mode?: string, outfile?: string, outfileType?: string, client: string, send?: Function, hash?: Function, verbose?: boolean, quiet?: boolean, addTypename?: boolean, changesetVersion?: string, headers?: {[key: string]: string}, } /** * Find `.graphql` files in `path`, * then prepare them & send them to the configured endpoint. * * @param {Object} options * @param {String} options.path - A glob to recursively search for `.graphql` files (Default is `./`) * @param {String} options.relayPersistedOutput - A path to a `.json` file from `relay-compiler`'s `--persist-output` option * @param {String} options.apolloCodegenJsonOutput - A path to a `.json` file from `apollo client:codegen ... --type json` * @param {String} options.apolloPersistedQueryManifest - A path to a `.json` file from `generate-persisted-query-manifest` * @param {String} options.secret - HMAC-SHA256 key which must match the server secret (default is no encryption) * @param {String} options.url - Target URL for sending prepared queries. If omitted, then an outfile is generated without sending operations to the server. * @param {String} options.mode - If `"file"`, treat each file separately. If `"project"`, concatenate all files and extract each operation. If `"relay"`, treat it as relay-compiler output * @param {Boolean} options.addTypename - Indicates if the "__typename" field are automatically added to your queries * @param {String} options.outfile - Where the generated code should be written * @param {String} options.outfileType - The type of the generated code (i.e., json, js) * @param {String} options.client - the Client ID that these operations belong to * @param {Function} options.send - A function for sending the payload to the server, with the signature `options.send(payload)`. (Default is an HTTP `POST` request) * @param {Function} options.hash - A custom hash function for query strings with the signature `options.hash(string) => digest` (Default is `md5(string) => digest`) * @param {Boolean} options.verbose - If true, log debug output * @param {Object} options.headers - If present, extra headers to add to the HTTP request * @param {String} options.changesetVersion - If present, sent to populate `context[:changeset_version]` on the server * @return {Promise} Rejects with an Error or String if something goes wrong. Resolves with the operation payload if successful. */ function sync(options: SyncOptions) { var logger = new Logger(!!options.quiet) var verbose = !!options.verbose var url = options.url if (!url) { logger.log("No URL; Generating artifacts without syncing them") } var clientName = options.client if (!clientName) { throw new Error("Client name must be provided for sync") } var encryptionKey = options.secret if (encryptionKey) { logger.log("Authenticating with HMAC") } var graphqlGlob = options.path var hashFunc = options.hash var sendFunc = options.send || sendPayload var gatherMode = options.mode var clientType = options.outfileType if (options.relayPersistedOutput) { // relay-compiler has already generated an artifact for us var payload: { operations: ClientOperation[] } = { operations: [] } var relayOutputText = fs.readFileSync(options.relayPersistedOutput, "utf8") var relayOutput = JSON.parse(relayOutputText) var operationBody for (var hash in relayOutput) { operationBody = relayOutput[hash] payload.operations.push({ body: operationBody, alias: hash, }) } } else if (options.apolloAndroidOperationOutput) { // Apollo Android has already generated an artifact (https://www.apollographql.com/docs/android/advanced/persisted-queries/#operationoutputjson) var payload: { operations: ClientOperation[] } = { operations: [] } var apolloAndroidOutputText = fs.readFileSync(options.apolloAndroidOperationOutput, "utf8") var apolloAndroidOutput = JSON.parse(apolloAndroidOutputText) var operationData // Structure is { operationId => { "name" => "...", "source" => "query { ... } " } } for (var operationId in apolloAndroidOutput) { operationData = apolloAndroidOutput[operationId] let bodyWithoutClientFields = removeClientFieldsFromString(operationData.source) payload.operations.push({ body: bodyWithoutClientFields, alias: operationId, }) } } else if (options.apolloCodegenJsonOutput) { var payload: { operations: ClientOperation[] } = { operations: [] } const jsonText = fs.readFileSync(options.apolloCodegenJsonOutput).toString() const jsonData = JSON.parse(jsonText) jsonData.operations.map(function(operation: {operationId: string, operationName: string, sourceWithFragments: string}) { const bodyWithoutClientFields = removeClientFieldsFromString(operation.sourceWithFragments) payload.operations.push({ alias: operation.operationId, name: operation.operationName, body: bodyWithoutClientFields, }) }) } else if (options.apolloPersistedQueryManifest) { var payload: { operations: ClientOperation[] } = { operations: preparePersistedQueryList(options.apolloPersistedQueryManifest) } } else { var payload = gatherOperations({ path: graphqlGlob, hash: hashFunc, mode: gatherMode, addTypename: !!options.addTypename, clientType: clientType, client: clientName, verbose: verbose, }) } var outfile: string | null if (options.outfile) { outfile = options.outfile } else if (options.relayPersistedOutput || options.apolloAndroidOperationOutput || options.apolloCodegenJsonOutput || options.apolloPersistedQueryManifest) { // These artifacts have embedded IDs in its generated files, // no need to generate an outfile. outfile = null } else if (fs.existsSync("src")) { outfile = "src/OperationStoreClient.js" } else { outfile = "OperationStoreClient.js" } var syncPromise = new Promise(function(resolve, reject) { if (payload.operations.length === 0) { logger.log("No operations found in " + options.path + ", not syncing anything") resolve(null) return } else if(!url) { // This is a local-only run to generate an artifact resolve(payload) return } else { logger.log("Syncing " + payload.operations.length + " operations to " + logger.bright(url) + "...") var sendOpts = { url: url, client: clientName, secret: encryptionKey, headers: options.headers, changesetVersion: options.changesetVersion, logger: logger, } var sendPromise = Promise.resolve(sendFunc(payload, sendOpts)) return sendPromise.then(function(response) { var responseData if (response) { try { responseData = JSON.parse(response) var aliasToNameMap: {[key: string] : string | undefined} = {} payload.operations.forEach(function(op) { aliasToNameMap[op.alias] = op.name }) var failed = responseData.failed.length // These might get overriden for status output var notModified = responseData.not_modified.length var added = responseData.added.length if (failed) { // Override these to reflect reality notModified = 0 added = 0 } var addedColor = added ? "green" : "dim" logger.log(" " + logger.colorize(addedColor, added + " added")) var notModifiedColor = notModified ? "reset" : "dim" logger.log(" " + logger.colorize(notModifiedColor, notModified + " not modified")) var failedColor = failed ? "red" : "dim" logger.log(" " + logger.colorize(failedColor, failed + " failed")) if (failed) { logger.error("Sync failed, errors:") var failedOperationAlias: string var failedOperationName: string var errors var allErrors: string[] = [] for (failedOperationAlias in responseData.errors) { failedOperationName = aliasToNameMap[failedOperationAlias] || failedOperationAlias logger.error(" " + failedOperationName + ":") errors = responseData.errors[failedOperationAlias] errors.forEach(function(errMessage: string) { allErrors.push(failedOperationName + ": " + errMessage) logger.error(" " + logger.red("✘") + " " + errMessage) }) } reject("Sync failed: " + allErrors.join(", ")) return } } catch (err) { logger.log("Failed to print sync result:", err as string) reject(err) return } } resolve(payload) return }).catch(function(err) { logger.error(logger.red("Sync failed:")) logger.error(err) reject(err) return }) } }) return syncPromise.then(function(_payload) { // The payload is yielded when sync was successful, but typescript had // trouble using it from ^^ here. So instead, just use its presence as a signal to continue. // Don't generate a new file when we're using relay-comipler's --persist-output if (_payload && outfile) { var generatedCode = generateClientCode(clientName, payload.operations, clientType) var finishedPayload = { operations: payload.operations, generatedCode, } logger.log("Generating client module in " + logger.colorize("bright", outfile) + "...") fs.writeFileSync(outfile, generatedCode, "utf8") logger.log(logger.green("✓ Done!")) return finishedPayload } else { logger.log(logger.green("✓ Done!")) return payload } }) } export default sync graphql-ruby-2.2.17/javascript_client/src/sync/logger.ts000066400000000000000000000017441476434635200233420ustar00rootroot00000000000000class Logger { isQuiet: boolean constructor(isQuiet: boolean) { this.isQuiet = isQuiet } log(...args: string[]) { this.isQuiet ? null : console.log(...args) } error(...args: string[]) { this.isQuiet ? null : console.error(...args) } colorize(color: string, text: string) { var prefix = colors[color] if (!prefix) { throw new Error("No color named: " + color) } return prefix + text + colors.reset } // Shortcuts to `.colorize`, add more as-needed. red(text: string) { return this.colorize("red", text) } green(text: string) { return this.colorize("green", text) } bright(text: string) { return this.colorize("bright", text) } } const colors: {[key: string]: string} = { yellow: "\x1b[33m", red: "\x1b[31m", green: "\x1b[32m", blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", reset: "\x1b[0m", bright: "\x1b[1m", dim: "\x1b[2m", } export default Logger graphql-ruby-2.2.17/javascript_client/src/sync/md5.ts000066400000000000000000000003411476434635200225400ustar00rootroot00000000000000import crypto from 'crypto' // Return the hex-encoded md5 hash of `inputString` function md5(inputString: string): string { return crypto.createHash("md5") .update(inputString) .digest("hex") } export default md5 graphql-ruby-2.2.17/javascript_client/src/sync/outfileGenerators/000077500000000000000000000000001476434635200252065ustar00rootroot00000000000000graphql-ruby-2.2.17/javascript_client/src/sync/outfileGenerators/js.ts000066400000000000000000000057521476434635200262030ustar00rootroot00000000000000function generateOutfile(_type: string, clientName: string, keyValuePairs: string) { return ` /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = ${keyValuePairs} /** * The client who synced these operations with the server * @return {String} * @private */ var _client = "${clientName}" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + "/" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error("Failed to find persisted alias for operation name: " + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient ` } export default generateOutfile; graphql-ruby-2.2.17/javascript_client/src/sync/outfileGenerators/json.ts000066400000000000000000000002251476434635200265260ustar00rootroot00000000000000function generateOutfile(_type: string, _clientName: string, keyValuePairs: string) { return `${keyValuePairs}` } export default generateOutfile; graphql-ruby-2.2.17/javascript_client/src/sync/prepareIsolatedFiles.ts000066400000000000000000000027541476434635200261730ustar00rootroot00000000000000import fs from "fs" import {parse, visit, print, OperationDefinitionNode} from "graphql" import {addTypenameIfAbsent} from "./addTypenameToSelectionSet" import { removeClientFields } from "./removeClientFields" /** * Read a bunch of GraphQL files and treat them as islands. * Don't join any fragments from other files. * Don't make assertions about name uniqueness. * */ function prepareIsolatedFiles(filenames: string[], addTypename: boolean) { return filenames.map(function(filename) { var fileOperationBody = fs.readFileSync(filename, "utf8") var fileOperationName = "" var ast = parse(fileOperationBody) var visitor = { OperationDefinition: { enter: function(node: OperationDefinitionNode) { if (fileOperationName.length > 0) { throw new Error("Found multiple operations in " + filename + ": " + fileOperationName + ", " + node.name + ". Files must contain only one operation") } else if (node.name && node.name.value) { fileOperationName = node.name.value } }, }, InlineFragment: { leave: addTypename ? addTypenameIfAbsent : () => {} }, Field: { leave: addTypename ? addTypenameIfAbsent : () => {} } } ast = visit(ast, visitor) ast = removeClientFields(ast) return { // populate alias later, when hashFunc is available alias: "", name: fileOperationName, body: print(ast), } }) } export default prepareIsolatedFiles graphql-ruby-2.2.17/javascript_client/src/sync/preparePersistedQueryList.ts000066400000000000000000000010621476434635200272570ustar00rootroot00000000000000import fs from "fs" // Transform the output from generate-persisted-query-manifest // to something that OperationStore `sync` can use. export default function preparePersistedQueryList(pqlPath: string) { const pqlString = fs.readFileSync(pqlPath, "utf8") const pqlJson = JSON.parse(pqlString) return pqlJson.operations.map(function(persistedQueryConfig: { body: string, id: string, name: string, type: string }) { return { body: persistedQueryConfig.body, alias: persistedQueryConfig.id, name: persistedQueryConfig.name } }) } graphql-ruby-2.2.17/javascript_client/src/sync/prepareProject.ts000066400000000000000000000073401476434635200250460ustar00rootroot00000000000000import { addTypenameIfAbsent } from "./addTypenameToSelectionSet"; import fs from "fs" import {parse, visit, print, OperationDefinitionNode, FragmentDefinitionNode, FragmentSpreadNode, DocumentNode} from "graphql" import { removeClientFields } from "./removeClientFields"; /** * Take a whole bunch of GraphQL in one big string * and validate it, especially: * * - operation names are unique * - fragment names are unique * * Then, split each operation into a free-standing document, * so it has all the fragments it needs. */ function prepareProject(filenames: string[], addTypename: boolean) { if(!filenames.length) { return []; } var allGraphQL = "" filenames.forEach(function(filename) { allGraphQL += fs.readFileSync(filename) }) var ast = parse(allGraphQL) // This will contain { name: [name, name] } pairs var definitionDependencyNames: {[key: string] : string[] } = {} var allOperationNames: string[] = [] var currentDependencyNames = null // When entering a fragment or operation, // start recording its dependencies var enterDefinition = function(node: FragmentDefinitionNode | OperationDefinitionNode) { // Technically, it could be an anonymous definition if (node.name) { var definitionName = node.name.value if (definitionDependencyNames[definitionName]) { throw new Error("Found duplicate definition name: " + definitionName + ", fragment & operation names must be unique to sync") } else { currentDependencyNames = definitionDependencyNames[definitionName] = [] } } } var visitor = { OperationDefinition: { enter: function(node: OperationDefinitionNode) { enterDefinition(node) node.name && allOperationNames.push(node.name.value) }, }, FragmentDefinition: { enter: enterDefinition, }, // When entering a fragment spread, register it as a // dependency of its context FragmentSpread: { enter: function(node: FragmentSpreadNode) { currentDependencyNames.push(node.name.value) } }, Field: { leave: addTypename ? addTypenameIfAbsent : () => {} }, InlineFragment: { leave: addTypename ? addTypenameIfAbsent : () => {} } } // Find the dependencies, build the accumulator ast = visit(ast, visitor) ast = removeClientFields(ast) // For each operation, build a separate document of that operation and its deps // then print the new document to a string var operations = allOperationNames.map(function(operationName) { var visitedDepNames: string[] = [] var depNamesToVisit = [operationName] var depName while (depNamesToVisit.length > 0) { depName = depNamesToVisit.shift() if (depName) { visitedDepNames.push(depName) definitionDependencyNames[depName].forEach(function(nextDepName) { if (visitedDepNames.indexOf(nextDepName) === -1) { depNamesToVisit.push(nextDepName) } }) } } var newAST = extractDefinitions(ast, visitedDepNames) return { name: operationName, body: print(newAST), alias: "", // will be filled in later, when hashFunc is available } }) return operations } // Return a new AST which contains only `definitionNames` function extractDefinitions(ast: DocumentNode, definitionNames: string[]) { var removeDefinitionNode = function(node: FragmentDefinitionNode | OperationDefinitionNode) { if (node.name && definitionNames.indexOf(node.name.value) === -1) { return null } else { return undefined } } var visitor = { OperationDefinition: removeDefinitionNode, FragmentDefinition: removeDefinitionNode, } var newAST = visit(ast, visitor) return newAST } export default prepareProject graphql-ruby-2.2.17/javascript_client/src/sync/prepareRelay.ts000066400000000000000000000035261476434635200245160ustar00rootroot00000000000000import path from "path" import fs from "fs" interface RelayCompilerOperation { params?: RelayCompilerOperation text: string name: string } /** * Read relay-compiler output * and extract info for persisting them & writing a map: * * - alias: get the relayHash from the header * - name: get the name from the JavaScript object * - body: get the text from the JavaScript object * * @param {Array} filenames - Filenames to read * @return {Array} List of operations to persist & write to a map */ function prepareRelay(filenames: string[]) { var currentDirectory = process.cwd() var operations = filenames.map(function(filename) { // Search the file for the relayHash var textContent = fs.readFileSync(filename, "utf8") var operationAlias = textContent.match(/@relayHash ([a-z0-9]+)/) // Only operations get `relayHash`, so // skip over generated fragments if (operationAlias) { // Require the file to get values from the JavaScript code var absoluteFilename = path.resolve(currentDirectory, filename) var operation: RelayCompilerOperation = require(absoluteFilename) var operationBody, operationName // Support Relay version ^2.0.0 if (operation.params) { operationBody = operation.params.text operationName = operation.params.name } else { // Support Relay versions < 2.0.0 operationBody = operation.text operationName = operation.name } return { alias: operationAlias[1], name: operationName, body: operationBody, } } else { return { alias: "", name: "not-found", body: "not-found", } } }) // Remove the nulls var operationsWithoutNulls = operations.filter(function(o) { return o.alias.length }) return operationsWithoutNulls } export default prepareRelay graphql-ruby-2.2.17/javascript_client/src/sync/removeClientFields.ts000066400000000000000000000076101476434635200256440ustar00rootroot00000000000000import { parse, DocumentNode, VariableDefinitionNode, print, visit } from "graphql" function removeClientFields(node: DocumentNode) { // Deleting fields can create invalid documents: // - If variables were used by those fields (or their subfields), then their definitions are invalid // - If a fragment contained only deleted fields, it is now empty and therefore invalid and should be deleted // - If a fragment spread names a deleted fragment, it is now invalid // - If a client field contained a fragment spread and it's deleted, then a fragment may be left unspread let anythingWasRemoved = false const usedVariables: string[] = [] let definedFragments: string[] = [] let spreadFragments: string[] = [] // First pass: remove as much as possible, even if the document is left invalid. // - remove fields that have @client // - remove fragment definitions that become empty let newDoc = visit(node, { Field: { enter: (node) => { if (node.directives && node.directives.some((d) => { return d.name.value === "client" })) { anythingWasRemoved = true // Delete this node return null } else { return undefined } } }, // FragmentSpread: ... Don't do this now, because we might find some in Fragment Definitions that are deleted later. FragmentDefinition: { leave: (node) => { if (node.selectionSet.selections.length == 0) { // All the fields in this fragment were removed return null } else { definedFragments.push(node.name.value) return undefined } } }, FragmentSpread: { enter: (node) => { spreadFragments.push(node.name.value) } }, Variable: { enter: (node, _key, parent) => { if ((parent as VariableDefinitionNode).kind !== 'VariableDefinition') { // This will only find variables that are used _after_ `@client` fields are deleted. // (If `@client` fields are deleted, then their arguments aren't visited) usedVariables.push(node.name.value) } }, }, }) if (anythingWasRemoved) { // At this point, we can remove variables that aren't used. newDoc = visit(newDoc, { VariableDefinition: { enter: (node) => { if (!usedVariables.includes(node.variable.name.value)) { return null } else { return undefined } } }, }) // Then, remove spreads of empty fragment definitions as long as we keep finding them // Also remove definitions of fragments that aren't spread anymore while (anythingWasRemoved) { let previouslyDefinedFragments = definedFragments let previouslySpreadFragments = spreadFragments definedFragments = [] spreadFragments = [] anythingWasRemoved = false newDoc = visit(newDoc, { FragmentSpread: { enter: (node) => { if (!previouslyDefinedFragments.includes(node.name.value)) { anythingWasRemoved = true return null } else { spreadFragments.push(node.name.value) return undefined } } }, FragmentDefinition: { enter: (node) => { if (node.selectionSet.selections.length == 0 || !previouslySpreadFragments.includes(node.name.value)) { anythingWasRemoved = true return null } else { definedFragments.push(node.name.value) return undefined } } } }) } } return newDoc } function removeClientFieldsFromString(body: string): string { if (body.includes("@client")) { const ast = parse(body) const newAst = removeClientFields(ast) return print(newAst) } else { return body } } export { removeClientFields, removeClientFieldsFromString } graphql-ruby-2.2.17/javascript_client/src/sync/sendPayload.ts000066400000000000000000000067351476434635200243330ustar00rootroot00000000000000import http from "http" import https from "https" import url from "url" import crypto from 'crypto' import Logger from './logger' interface SendPayloadOptions { url: string, logger: Logger, secret?: string, client?: string, headers?: { [key: string]: string }, changesetVersion?: string, } /** * Use HTTP POST to send this payload to the endpoint. * * Override this function with `options.send` to use custom auth. * * @private * @param {Object} payload - JS object to be posted as form data * @param {String} options.url - Target URL * @param {String} options.secret - (optional) used for HMAC header if provided * @param {String} options.client - (optional) used for HMAC header if provided * @param {Logger} options.logger - A logger for when `verbose` is true * @param {Object} options.headers - (optional) extra headers for the request * @return {Promise} */ function sendPayload(payload: any, options: SendPayloadOptions) { var syncUrl = options.url var key = options.secret var clientName = options.client var logger = options.logger // Prepare JS object as form data var postData = JSON.stringify(payload) // Get parts of URL for request options var parsedURL = url.parse(syncUrl) // Prep options for HTTP request var defaultHeaders: {[key: string]: string} = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData).toString() } if (options.changesetVersion) { logger.log("Changeset Version: ", logger.bright(options.changesetVersion)) defaultHeaders["Changeset-Version"] = options.changesetVersion } var allHeaders = Object.assign({}, options.headers, defaultHeaders) var httpOptions = { protocol: parsedURL.protocol, hostname: parsedURL.hostname, port: parsedURL.port, path: parsedURL.path, auth: parsedURL.auth, method: 'POST', headers: allHeaders, }; // If an auth key was provided, add a HMAC header var authDigest = null if (key) { authDigest = crypto.createHmac('sha256', key) .update(postData) .digest('hex') var header = "GraphQL::Pro " + clientName + " " + authDigest httpOptions.headers["Authorization"] = header } var headerNames = Object.keys(httpOptions.headers) logger.log("[Sync] " + headerNames.length + " Headers:") headerNames.forEach((headerName) => { logger.log("[Sync] " + headerName + ": " + httpOptions.headers[headerName]) }) logger.log("[Sync] Data:", postData) var httpClient = parsedURL.protocol === "https:" ? https : http var promise = new Promise(function(resolve, reject) { // Make the request, // hook up response handler const req = httpClient.request(httpOptions, (res) => { res.setEncoding('utf8'); // Gather the response from the server var body = "" res.on('data', (chunk) => { body += chunk }); res.on("end", () => { logger.log("[Sync] Response Headers: ", JSON.stringify(res.headers)) logger.log("[Sync] Response Body: ", body) var status = res.statusCode // 422 gets special treatment because // the body has error messages if (status && status > 299 && status != 422) { reject(" Server responded with " + res.statusCode) } else { resolve(body) } }) }); req.on('error', (e) => { reject(e) }); // Send the data, fire the request req.write(postData); req.end(); }) return promise } export default sendPayload graphql-ruby-2.2.17/javascript_client/tsconfig.json000066400000000000000000000134401476434635200224530ustar00rootroot00000000000000{ "include": ["./src"], "exclude": ["**/*.js"], "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ "noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedParameters": true, /* Report errors on unused parameters. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } graphql-ruby-2.2.17/lib/000077500000000000000000000000001476434635200150045ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/generators/000077500000000000000000000000001476434635200171555ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/generators/graphql/000077500000000000000000000000001476434635200206135ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/generators/graphql/core.rb000066400000000000000000000031621476434635200220720ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' module Graphql module Generators module Core def self.included(base) base.send( :class_option, :directory, type: :string, default: "app/graphql", desc: "Directory where generated files should be saved" ) end def insert_root_type(type, name) log :add_root_type, type sentinel = /< GraphQL::Schema\s*\n/m in_root do if File.exist?(schema_file_path) inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false end end end def schema_file_path "#{options[:directory]}/#{schema_name.underscore}.rb" end def create_dir(dir) empty_directory(dir) if !options[:skip_keeps] create_file("#{dir}/.keep") end end def module_namespacing_when_supported if defined?(module_namespacing) module_namespacing { yield } else yield end end private def schema_name @schema_name ||= begin if options[:schema] options[:schema] else "#{parent_name}Schema" end end end def parent_name require File.expand_path("config/application", destination_root) if Rails.application.class.respond_to?(:module_parent_name) Rails.application.class.module_parent_name else Rails.application.class.parent_name end end end end end graphql-ruby-2.2.17/lib/generators/graphql/enum_generator.rb000066400000000000000000000012511476434635200241510ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an enum type by name, with the given values. # To add a `value:` option, add another value after a `:`. # # ``` # rails g graphql:enum ProgrammingLanguage RUBY PYTHON PERL PERL6:"PERL" # ``` class EnumGenerator < TypeGeneratorBase desc "Create a GraphQL::EnumType with the given name and values" source_root File.expand_path('../templates', __FILE__) private def graphql_type "enum" end def prepared_values custom_fields.map { |v| v.split(":", 2) } end end end end graphql-ruby-2.2.17/lib/generators/graphql/field_extractor.rb000066400000000000000000000013411476434635200243150ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' module Graphql module Generators module FieldExtractor def fields columns = [] columns += (klass&.columns&.map { |c| generate_column_string(c) } || []) columns + custom_fields end def generate_column_string(column) name = column.name required = column.null ? "" : "!" type = column_type_string(column) "#{name}:#{required}#{type}" end def column_type_string(column) column.name == "id" ? "ID" : column.type.to_s.camelize end def klass @klass ||= Module.const_get(name.camelize) rescue NameError @klass = nil end end end end graphql-ruby-2.2.17/lib/generators/graphql/input_generator.rb000066400000000000000000000023631476434635200243510ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' require 'generators/graphql/field_extractor' module Graphql module Generators # Generate an input type by name, # with the specified fields. # # ``` # rails g graphql:object PostType name:string! # ``` class InputGenerator < TypeGeneratorBase desc "Create a GraphQL::InputObjectType with the given name and fields" source_root File.expand_path('../templates', __FILE__) include FieldExtractor def self.normalize_type_expression(type_expression, mode:, null: true) case type_expression.camelize when "Text", "Citext" ["String", null] when "Decimal" ["Float", null] when "DateTime", "Datetime" ["GraphQL::Types::ISO8601DateTime", null] when "Date" ["GraphQL::Types::ISO8601Date", null] when "Json", "Jsonb", "Hstore" ["GraphQL::Types::JSON", null] else super end end private def graphql_type "input" end def type_ruby_name super.gsub(/Type\z/, "InputType") end def type_file_name super.gsub(/_type\z/, "_input_type") end end end end graphql-ruby-2.2.17/lib/generators/graphql/install/000077500000000000000000000000001476434635200222615ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/generators/graphql/install/mutation_root_generator.rb000066400000000000000000000020571476434635200275630ustar00rootroot00000000000000# frozen_string_literal: true require "rails/generators/base" require_relative "../core" module Graphql module Generators module Install class MutationRootGenerator < Rails::Generators::Base include Core desc "Create mutation base type, mutation root tipe, and adds the latter to the schema" source_root File.expand_path('../templates', __FILE__) class_option :schema, type: :string, default: nil, desc: "Name for the schema constant (default: {app_name}Schema)" class_option :skip_keeps, type: :boolean, default: false, desc: "Skip .keep files for source control" def generate create_dir("#{options[:directory]}/mutations") template("base_mutation.erb", "#{options[:directory]}/mutations/base_mutation.rb", { skip: true }) template("mutation_type.erb", "#{options[:directory]}/types/mutation_type.rb", { skip: true }) insert_root_type('mutation', 'MutationType') end end end end end graphql-ruby-2.2.17/lib/generators/graphql/install/templates/000077500000000000000000000000001476434635200242575ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/generators/graphql/install/templates/base_mutation.erb000066400000000000000000000005071476434635200276050ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument field_class Types::BaseField input_object_class Types::BaseInputObject object_class Types::BaseObject end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/install/templates/mutation_type.erb000066400000000000000000000005041476434635200276510ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class MutationType < Types::BaseObject # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World" end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/install_generator.rb000066400000000000000000000140541476434635200246600ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators' require 'rails/generators/base' require_relative 'core' require_relative 'relay' module Graphql module Generators # Add GraphQL to a Rails app with `rails g graphql:install`. # # Setup a folder structure for GraphQL: # # ``` # - app/ # - graphql/ # - resolvers/ # - types/ # - base_argument.rb # - base_field.rb # - base_enum.rb # - base_input_object.rb # - base_interface.rb # - base_object.rb # - base_scalar.rb # - base_union.rb # - query_type.rb # - loaders/ # - mutations/ # - base_mutation.rb # - {app_name}_schema.rb # ``` # # (Add `.gitkeep`s by default, support `--skip-keeps`) # # Add a controller for serving GraphQL queries: # # ``` # app/controllers/graphql_controller.rb # ``` # # Add a route for that controller: # # ```ruby # # config/routes.rb # post "/graphql", to: "graphql#execute" # ``` # # Accept a `--batch` option which adds `GraphQL::Batch` setup. # # Use `--skip-graphiql` to skip `graphiql-rails` installation. # # TODO: also add base classes class InstallGenerator < Rails::Generators::Base include Core include Relay desc "Install GraphQL folder structure and boilerplate code" source_root File.expand_path('../templates', __FILE__) class_option :schema, type: :string, default: nil, desc: "Name for the schema constant (default: {app_name}Schema)" class_option :skip_keeps, type: :boolean, default: false, desc: "Skip .keep files for source control" class_option :skip_graphiql, type: :boolean, default: false, desc: "Skip graphiql-rails installation" class_option :skip_mutation_root_type, type: :boolean, default: false, desc: "Skip creation of the mutation root type" class_option :relay, type: :boolean, default: true, desc: "Include installation of Relay conventions (nodes, connections, edges)" class_option :batch, type: :boolean, default: false, desc: "Include GraphQL::Batch installation" class_option :playground, type: :boolean, default: false, desc: "Use GraphQL Playground over Graphiql as IDE" # These two options are taken from Rails' own generators' class_option :api, type: :boolean, desc: "Preconfigure smaller stack for API only apps" def create_folder_structure create_dir("#{options[:directory]}/types") template("schema.erb", schema_file_path) ["base_object", "base_argument", "base_field", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type| template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb") end # All resolvers are defined as living in their own module, including this class. template("base_resolver.erb", "#{options[:directory]}/resolvers/base_resolver.rb") # Note: You can't have a schema without the query type, otherwise introspection breaks template("query_type.erb", "#{options[:directory]}/types/query_type.rb") insert_root_type('query', 'QueryType') invoke "graphql:install:mutation_root" unless options.skip_mutation_root_type? template("graphql_controller.erb", "app/controllers/graphql_controller.rb") route('post "/graphql", to: "graphql#execute"') if options[:batch] gem("graphql-batch") create_dir("#{options[:directory]}/loaders") end if options.api? say("Skipped graphiql, as this rails project is API only") say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app") elsif !options[:skip_graphiql] # `gem(...)` uses `gsub_file(...)` under the hood, which is a no-op for `rails destroy...` (when `behavior == :revoke`). # So handle that case by calling `gsub_file` with `force: true`. if behavior == :invoke && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails") gem("graphiql-rails", group: :development) elsif behavior == :revoke gemfile_pattern = /\n\s*gem ('|")graphiql-rails('|"), :?group(:| =>) :development/ gsub_file Rails.root.join("Gemfile"), gemfile_pattern, "", { force: true } end # This is a little cheat just to get cleaner shell output: log :route, 'graphiql-rails' shell.mute do # Rails 5.2 has better support for `route`? if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY end end end if options[:playground] gem("graphql_playground-rails", group: :development) log :route, 'graphql_playground-rails' shell.mute do if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" end RUBY end end end if options[:relay] install_relay end if gemfile_modified? say "Gemfile has been modified, make sure you `bundle install`" end end private def gemfile_modified? @gemfile_modified end def gem(*args) @gemfile_modified = true super(*args) end end end end graphql-ruby-2.2.17/lib/generators/graphql/interface_generator.rb000066400000000000000000000011231476434635200251430ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an interface type by name, # with the specified fields. # # ``` # rails g graphql:interface NamedEntityType name:String! # ``` class InterfaceGenerator < TypeGeneratorBase desc "Create a GraphQL::InterfaceType with the given name and fields" source_root File.expand_path('../templates', __FILE__) private def graphql_type "interface" end def fields custom_fields end end end end graphql-ruby-2.2.17/lib/generators/graphql/loader_generator.rb000066400000000000000000000011151476434635200244520ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators' require "rails/generators/named_base" require_relative "core" module Graphql module Generators # @example Generate a `GraphQL::Batch` loader by name. # rails g graphql:loader RecordLoader class LoaderGenerator < Rails::Generators::NamedBase include Core desc "Create a GraphQL::Batch::Loader by name" source_root File.expand_path('../templates', __FILE__) def create_loader_file template "loader.erb", "#{options[:directory]}/loaders/#{file_path}.rb" end end end end graphql-ruby-2.2.17/lib/generators/graphql/mutation_create_generator.rb000066400000000000000000000010771476434635200263760ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'orm_mutations_base' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class MutationCreateGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM create mutation for the given model class" source_root File.expand_path('../templates', __FILE__) private def operation_type "create" end end end end graphql-ruby-2.2.17/lib/generators/graphql/mutation_delete_generator.rb000066400000000000000000000010771476434635200263750ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'orm_mutations_base' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation DeletePostMutation class MutationDeleteGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM delete mutation for the given model class" source_root File.expand_path('../templates', __FILE__) private def operation_type "delete" end end end end graphql-ruby-2.2.17/lib/generators/graphql/mutation_generator.rb000066400000000000000000000021411476434635200250440ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators' require 'rails/generators/named_base' require_relative 'core' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class MutationGenerator < Rails::Generators::NamedBase include Core desc "Create a Relay Classic mutation by name" source_root File.expand_path('../templates', __FILE__) def create_mutation_file template "mutation.erb", File.join(options[:directory], "/mutations/", class_path, "#{file_name}.rb") sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m in_root do path = "#{options[:directory]}/types/mutation_type.rb" invoke "graphql:install:mutation_root" unless File.exist?(path) inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{file_name}, mutation: Mutations::#{class_name}\n", after: sentinel, verbose: false, force: false end end end end end graphql-ruby-2.2.17/lib/generators/graphql/mutation_update_generator.rb000066400000000000000000000010771476434635200264150ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'orm_mutations_base' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation UpdatePostMutation class MutationUpdateGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM update mutation for the given model class" source_root File.expand_path('../templates', __FILE__) private def operation_type "update" end end end end graphql-ruby-2.2.17/lib/generators/graphql/object_generator.rb000066400000000000000000000027011476434635200244540ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' require 'generators/graphql/field_extractor' module Graphql module Generators # Generate an object type by name, # with the specified fields. # # ``` # rails g graphql:object PostType name:String! # ``` # # Add the Node interface with `--node`. class ObjectGenerator < TypeGeneratorBase desc "Create a GraphQL::ObjectType with the given name and fields." \ "If the given type name matches an existing ActiveRecord model, the generated type will automatically include fields for the models database columns." source_root File.expand_path('../templates', __FILE__) include FieldExtractor class_option :node, type: :boolean, default: false, desc: "Include the Relay Node interface" def self.normalize_type_expression(type_expression, mode:, null: true) case type_expression.camelize when "Text", "Citext" ["String", null] when "Decimal" ["Float", null] when "DateTime", "Datetime" ["GraphQL::Types::ISO8601DateTime", null] when "Date" ["GraphQL::Types::ISO8601Date", null] when "Json", "Jsonb", "Hstore" ["GraphQL::Types::JSON", null] else super end end private def graphql_type "object" end end end end graphql-ruby-2.2.17/lib/generators/graphql/orm_mutations_base.rb000066400000000000000000000027721476434635200250420ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators' require 'rails/generators/named_base' require_relative 'core' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class OrmMutationsBase < Rails::Generators::NamedBase include Core include Rails::Generators::ResourceHelpers desc "Create a Relay Classic mutation by name" class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" class_option 'namespaced_types', type: :boolean, required: false, default: false, banner: "Namespaced", desc: "If the generated types will be namespaced" def create_mutation_file template "mutation_#{operation_type}.erb", File.join(options[:directory], "/mutations/", class_path, "#{file_name}_#{operation_type}.rb") sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m in_root do path = "#{options[:directory]}/types/mutation_type.rb" invoke "graphql:install:mutation_root" unless File.exist?(path) inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{file_name}_#{operation_type}, mutation: Mutations::#{class_name}#{operation_type.classify}\n", after: sentinel, verbose: false, force: false end end end end end graphql-ruby-2.2.17/lib/generators/graphql/relay.rb000066400000000000000000000050341476434635200222560ustar00rootroot00000000000000# frozen_string_literal: true module Graphql module Generators module Relay def install_relay # Add Node, `node(id:)`, and `nodes(ids:)` template("node_type.erb", "#{options[:directory]}/types/node_type.rb") in_root do fields = <<-RUBY field :node, Types::NodeType, null: true, description: "Fetches an object given its ID." do argument :id, ID, required: true, description: "ID of the object." end def node(id:) context.schema.object_from_id(id, context) end field :nodes, [Types::NodeType, null: true], null: true, description: "Fetches a list of objects given a list of IDs." do argument :ids, [ID], required: true, description: "IDs of the objects." end def nodes(ids:) ids.map { |id| context.schema.object_from_id(id, context) } end RUBY inject_into_file "#{options[:directory]}/types/query_type.rb", fields, after: /class .*QueryType\s*<\s*[^\s]+?\n/m, force: false end # Add connections and edges template("base_connection.erb", "#{options[:directory]}/types/base_connection.rb") template("base_edge.erb", "#{options[:directory]}/types/base_edge.rb") connectionable_type_files = { "#{options[:directory]}/types/base_object.rb" => /class .*BaseObject\s*<\s*[^\s]+?\n/m, "#{options[:directory]}/types/base_union.rb" => /class .*BaseUnion\s*<\s*[^\s]+?\n/m, "#{options[:directory]}/types/base_interface.rb" => /include GraphQL::Schema::Interface\n/m, } in_root do connectionable_type_files.each do |type_class_file, sentinel| inject_into_file type_class_file, " connection_type_class(Types::BaseConnection)\n", after: sentinel, force: false inject_into_file type_class_file, " edge_type_class(Types::BaseEdge)\n", after: sentinel, force: false end end # Add object ID hooks & connection plugin schema_code = <<-RUBY # Relay-style Object Identification: # Return a string UUID for `object` def self.id_from_object(object, type_definition, query_ctx) # For example, use Rails' GlobalID library (https://github.com/rails/globalid): object.to_gid_param end # Given a string UUID, find the object def self.object_from_id(global_id, query_ctx) # For example, use Rails' GlobalID library (https://github.com/rails/globalid): GlobalID.find(global_id) end RUBY inject_into_file schema_file_path, schema_code, before: /^end\n/m, force: false end end end end graphql-ruby-2.2.17/lib/generators/graphql/relay_generator.rb000066400000000000000000000007261476434635200243270ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators' require 'rails/generators/base' require_relative 'core' require_relative 'relay' module Graphql module Generators class RelayGenerator < Rails::Generators::Base include Core include Relay desc "Add base types and fields for Relay-style nodes and connections" source_root File.expand_path('../templates', __FILE__) def install_relay super end end end end graphql-ruby-2.2.17/lib/generators/graphql/scalar_generator.rb000066400000000000000000000007231476434635200244550ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a scalar type by given name. # # ``` # rails g graphql:scalar Date # ``` class ScalarGenerator < TypeGeneratorBase desc "Create a GraphQL::ScalarType with the given name" source_root File.expand_path('../templates', __FILE__) private def graphql_type "scalar" end end end end graphql-ruby-2.2.17/lib/generators/graphql/templates/000077500000000000000000000000001476434635200226115ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/generators/graphql/templates/base_argument.erb000066400000000000000000000002361476434635200261200ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseArgument < GraphQL::Schema::Argument end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_connection.erb000066400000000000000000000004671476434635200264430ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseConnection < Types::BaseObject # add `nodes` and `pageInfo` fields, as well as `edge_type(...)` and `node_nullable(...)` overrides include GraphQL::Types::Relay::ConnectionBehaviors end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_edge.erb000066400000000000000000000004161476434635200252020ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseEdge < Types::BaseObject # add `node` and `cursor` fields, as well as `node_type(...)` override include GraphQL::Types::Relay::EdgeBehaviors end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_enum.erb000066400000000000000000000002261476434635200252410ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseEnum < GraphQL::Schema::Enum end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_field.erb000066400000000000000000000002771476434635200253660ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_input_object.erb000066400000000000000000000003131476434635200267570ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_interface.erb000066400000000000000000000003151476434635200262340ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types module BaseInterface include GraphQL::Schema::Interface field_class Types::BaseField end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_object.erb000066400000000000000000000002731476434635200255450ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseObject < GraphQL::Schema::Object field_class Types::BaseField end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_resolver.erb000066400000000000000000000002031476434635200261310ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Resolvers class BaseResolver < GraphQL::Schema::Resolver end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_scalar.erb000066400000000000000000000002321476434635200255370ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseScalar < GraphQL::Schema::Scalar end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/base_union.erb000066400000000000000000000002301476434635200254200ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseUnion < GraphQL::Schema::Union end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/enum.erb000066400000000000000000000004671476434635200242560ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseEnum description "<%= human_name %> enum" <% prepared_values.each do |v| %> value "<%= v[0] %>"<%= v.length > 1 ? ", value: #{v[1]}" : "" %> <% end %> end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/graphql_controller.erb000066400000000000000000000031511476434635200272040ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> class GraphqlController < ApplicationController # If accessing from outside this domain, nullify the session # This allows for outside API access while preventing CSRF attacks, # but you'll have to authenticate your user separately # protect_from_forgery with: :null_session def execute variables = prepare_variables(params[:variables]) query = params[:query] operation_name = params[:operationName] context = { # Query context goes here, for example: # current_user: current_user, } result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name) render json: result rescue StandardError => e raise e unless Rails.env.development? handle_error_in_development(e) end private # Handle variables in form data, JSON body, or a blank value def prepare_variables(variables_param) case variables_param when String if variables_param.present? JSON.parse(variables_param) || {} else {} end when Hash variables_param when ActionController::Parameters variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. when nil {} else raise ArgumentError, "Unexpected parameter: #{variables_param}" end end def handle_error_in_development(e) logger.error e.message logger.error e.backtrace.join("\n") render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500 end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/input.erb000066400000000000000000000003601476434635200244410ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseInputObject <% normalized_fields.each do |f| %> <%= f.to_input_argument %> <% end %> end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/interface.erb000066400000000000000000000003671476434635200252510ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types module <%= ruby_class_name %> include Types::BaseInterface <% normalized_fields.each do |f| %> <%= f.to_object_field %> <% end %> end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/loader.erb000066400000000000000000000010141476434635200245450ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Loaders class <%= class_name %> < GraphQL::Batch::Loader # Define `initialize` to store grouping arguments, eg # # Loaders::<%= class_name %>.for(group).load(value) # # def initialize() # end # `keys` contains each key from `.load(key)`. # Find the corresponding values, then # call `fulfill(key, value)` or `fulfill(key, nil)` # for each key. def perform(keys) end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/mutation.erb000066400000000000000000000006221476434635200251430ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %> < BaseMutation # TODO: define return fields # field :post, Types::PostType, null: false # TODO: define arguments # argument :name, String, required: true # TODO: define resolve method # def resolve(name:) # { post: ... } # end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/mutation_create.erb000066400000000000000000000015041476434635200264660ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %>Create < BaseMutation description "Creates a new <%= file_name %>" field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false argument :<%= file_name %>_input, Types::<%= options[:namespaced_types] ? 'Inputs::' : '' %><%= class_name %>InputType, required: true def resolve(<%= file_name %>_input:) <%= singular_table_name %> = ::<%= orm_class.build(class_name, "**#{file_name}_input") %> raise GraphQL::ExecutionError.new "Error creating <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.save %> { <%= file_name %>: <%= singular_table_name %> } end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/mutation_delete.erb000066400000000000000000000012741476434635200264710ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %>Delete < BaseMutation description "Deletes a <%= file_name %> by ID" field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false argument :id, ID, required: true def resolve(id:) <%= singular_table_name %> = ::<%= orm_class.find(class_name, "id") %> raise GraphQL::ExecutionError.new "Error deleting <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.destroy %> { <%= file_name %>: <%= singular_table_name %> } end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/mutation_update.erb000066400000000000000000000015671476434635200265160ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %>Update < BaseMutation description "Updates a <%= file_name %> by id" field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false argument :id, ID, required: true argument :<%= file_name %>_input, Types::<%= options[:namespaced_types] ? 'Inputs::' : '' %><%= class_name %>InputType, required: true def resolve(id:, <%= file_name %>_input:) <%= singular_table_name %> = ::<%= orm_class.find(class_name, "id") %> raise GraphQL::ExecutionError.new "Error updating <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.update("**#{file_name}_input") %> { <%= file_name %>: <%= singular_table_name %> } end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/node_type.erb000066400000000000000000000003521476434635200252710ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types module NodeType include Types::BaseInterface # Add the `id` field include GraphQL::Types::Relay::NodeBehaviors end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/object.erb000066400000000000000000000004621476434635200245530ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseObject <% if options.node %> implements GraphQL::Types::Relay::Node <% end %><% normalized_fields.each do |f| %> <%= f.to_object_field %> <% end %> end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/query_type.erb000066400000000000000000000006411476434635200255120ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class QueryType < Types::BaseObject # Add root-level fields here. # They will be entry points for queries on your schema. # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World!" end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/scalar.erb000066400000000000000000000007261476434635200245550ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseScalar def self.coerce_input(input_value, context) # Override this to prepare a client-provided GraphQL value for your Ruby code input_value end def self.coerce_result(ruby_value, context) # Override this to serialize a Ruby value for the GraphQL response ruby_value.to_s end end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/schema.erb000066400000000000000000000016351476434635200245500ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> class <%= schema_name %> < GraphQL::Schema query(Types::QueryType) <% if options[:batch] %> # GraphQL::Batch setup: use GraphQL::Batch <% else %> # For batch-loading (see https://graphql-ruby.org/dataloader/overview.html) use GraphQL::Dataloader <% end %> # GraphQL-Ruby calls this when something goes wrong while running a query: def self.type_error(err, context) # if err.is_a?(GraphQL::InvalidNullError) # # report to your bug tracker here # return nil # end super end # Union and Interface Resolution def self.resolve_type(abstract_type, obj, ctx) # TODO: Implement this method # to return the correct GraphQL object type for `obj` raise(GraphQL::RequiredImplementationMissingError) end # Stop validating when it encounters this many errors: validate_max_errors(100) end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/templates/union.erb000066400000000000000000000004021476434635200244270ustar00rootroot00000000000000# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseUnion <% if custom_fields.any? %> possible_types <%= normalized_possible_types.join(", ") %> <% end %> end end <% end -%> graphql-ruby-2.2.17/lib/generators/graphql/type_generator.rb000066400000000000000000000110071476434635200241660ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators' require 'rails/generators/base' require 'graphql' require 'active_support' require 'active_support/core_ext/string/inflections' require_relative 'core' module Graphql module Generators class TypeGeneratorBase < Rails::Generators::NamedBase include Core class_option 'namespaced_types', type: :boolean, required: false, default: false, banner: "Namespaced", desc: "If the generated types will be namespaced" argument :custom_fields, type: :array, default: [], banner: "name:type name:type ...", desc: "Fields for this object (type may be expressed as Ruby or GraphQL)" attr_accessor :graphql_type def create_type_file template "#{graphql_type}.erb", "#{options[:directory]}/types#{subdirectory}/#{type_file_name}.rb" end # Take a type expression in any combination of GraphQL or Ruby styles # and return it in a specified output style # TODO: nullability / list with `mode: :graphql` doesn't work # @param type_expresson [String] # @param mode [Symbol] # @param null [Boolean] # @return [(String, Boolean)] The type expression, followed by `null:` value def self.normalize_type_expression(type_expression, mode:, null: true) if type_expression.start_with?("!") normalize_type_expression(type_expression[1..-1], mode: mode, null: false) elsif type_expression.end_with?("!") normalize_type_expression(type_expression[0..-2], mode: mode, null: false) elsif type_expression.start_with?("[") && type_expression.end_with?("]") name, is_null = normalize_type_expression(type_expression[1..-2], mode: mode, null: null) ["[#{name}]", is_null] elsif type_expression.end_with?("Type") normalize_type_expression(type_expression[0..-5], mode: mode, null: null) elsif type_expression.start_with?("Types::") normalize_type_expression(type_expression[7..-1], mode: mode, null: null) elsif type_expression.start_with?("types.") normalize_type_expression(type_expression[6..-1], mode: mode, null: null) else case mode when :ruby case type_expression when "Int" ["Integer", null] when "Integer", "Float", "Boolean", "String", "ID" [type_expression, null] else ["Types::#{type_expression.camelize}Type", null] end when :graphql [type_expression.camelize, null] else raise "Unexpected normalize mode: #{mode}" end end end private # @return [String] The user-provided type name, normalized to Ruby code def type_ruby_name @type_ruby_name ||= self.class.normalize_type_expression(name, mode: :ruby)[0] end # @return [String] The user-provided type name, as a GraphQL name def type_graphql_name @type_graphql_name ||= self.class.normalize_type_expression(name, mode: :graphql)[0] end # @return [String] The user-provided type name, as a file name (without extension) def type_file_name @type_file_name ||= "#{type_graphql_name}Type".underscore end # @return [Array] User-provided fields, in `(name, Ruby type name)` pairs def normalized_fields @normalized_fields ||= fields.map { |f| name, raw_type = f.split(":", 2) type_expr, null = self.class.normalize_type_expression(raw_type, mode: :ruby) NormalizedField.new(name, type_expr, null) } end def ruby_class_name class_prefix = if options[:namespaced_types] "#{graphql_type.pluralize.camelize}::" else "" end @ruby_class_name || class_prefix + type_ruby_name.sub(/^Types::/, "") end def subdirectory if options[:namespaced_types] "/#{graphql_type.pluralize}" else "" end end class NormalizedField def initialize(name, type_expr, null) @name = name @type_expr = type_expr @null = null end def to_object_field "field :#{@name}, #{@type_expr}#{@null ? '' : ', null: false'}" end def to_input_argument "argument :#{@name}, #{@type_expr}, required: false" end end end end end graphql-ruby-2.2.17/lib/generators/graphql/union_generator.rb000066400000000000000000000015441476434635200243420ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a union type by name # with the specified member types. # # ``` # rails g graphql:union SearchResultType ImageType AudioType # ``` class UnionGenerator < TypeGeneratorBase desc "Create a GraphQL::UnionType with the given name and possible types" source_root File.expand_path('../templates', __FILE__) argument :possible_types, type: :array, default: [], banner: "type type ...", desc: "Possible types for this union (expressed as Ruby or GraphQL)" private def graphql_type "union" end def normalized_possible_types custom_fields.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] } end end end end graphql-ruby-2.2.17/lib/graphql.rb000066400000000000000000000071071476434635200167740ustar00rootroot00000000000000# frozen_string_literal: true require "delegate" require "json" require "set" require "singleton" require "forwardable" module GraphQL # forwards-compat for argument handling module Ruby2Keywords if RUBY_VERSION < "2.7" def ruby2_keywords(*) end end end class Error < StandardError end # This error is raised when GraphQL-Ruby encounters a situation # that it *thought* would never happen. Please report this bug! class InvariantError < Error def initialize(message) message += " This is probably a bug in GraphQL-Ruby, please report this error on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md" super(message) end end class RequiredImplementationMissingError < Error end class << self def default_parser @default_parser ||= GraphQL::Language::Parser end attr_writer :default_parser end # Turn a query string or schema definition into an AST # @param graphql_string [String] a GraphQL query string or schema definition # @return [GraphQL::Language::Nodes::Document] def self.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil) default_parser.parse(graphql_string, trace: trace, filename: filename) end # Read the contents of `filename` and parse them as GraphQL # @param filename [String] Path to a `.graphql` file containing IDL or query # @return [GraphQL::Language::Nodes::Document] def self.parse_file(filename) content = File.read(filename) default_parser.parse(content, filename: filename) end # @return [Array] def self.scan(graphql_string) default_parser.scan(graphql_string) end def self.parse_with_racc(string, filename: nil, trace: GraphQL::Tracing::NullTrace) warn "`GraphQL.parse_with_racc` is deprecated; GraphQL-Ruby no longer uses racc for parsing. Call `GraphQL.parse` or `GraphQL::Language::Parser.parse` instead." GraphQL::Language::Parser.parse(string, filename: filename, trace: trace) end def self.scan_with_ruby(graphql_string) GraphQL::Language::Lexer.tokenize(graphql_string) end NOT_CONFIGURED = Object.new private_constant :NOT_CONFIGURED module EmptyObjects EMPTY_HASH = {}.freeze EMPTY_ARRAY = [].freeze end end # Order matters for these: require "graphql/execution_error" require "graphql/runtime_type_error" require "graphql/unresolved_type_error" require "graphql/invalid_null_error" require "graphql/analysis_error" require "graphql/coercion_error" require "graphql/invalid_name_error" require "graphql/integer_decoding_error" require "graphql/integer_encoding_error" require "graphql/string_encoding_error" require "graphql/date_encoding_error" require "graphql/duration_encoding_error" require "graphql/type_kinds" require "graphql/name_validator" require "graphql/language" require_relative "./graphql/railtie" if defined? Rails::Railtie require "graphql/analysis" require "graphql/tracing" require "graphql/dig" require "graphql/execution" require "graphql/pagination" require "graphql/schema" require "graphql/query" require "graphql/dataloader" require "graphql/types" require "graphql/static_validation" require "graphql/execution" require "graphql/schema/built_in_types" require "graphql/schema/loader" require "graphql/schema/printer" require "graphql/introspection" require "graphql/relay" require "graphql/version" require "graphql/subscriptions" require "graphql/parse_error" require "graphql/backtrace" require "graphql/unauthorized_error" require "graphql/unauthorized_field_error" require "graphql/load_application_object_failed_error" require "graphql/testing" graphql-ruby-2.2.17/lib/graphql/000077500000000000000000000000001476434635200164425ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/analysis.rb000066400000000000000000000000751476434635200206140ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/analysis/ast" graphql-ruby-2.2.17/lib/graphql/analysis/000077500000000000000000000000001476434635200202655ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/analysis/ast.rb000066400000000000000000000062601476434635200214050ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/analysis/ast/visitor" require "graphql/analysis/ast/analyzer" require "graphql/analysis/ast/field_usage" require "graphql/analysis/ast/query_complexity" require "graphql/analysis/ast/max_query_complexity" require "graphql/analysis/ast/query_depth" require "graphql/analysis/ast/max_query_depth" require "timeout" module GraphQL module Analysis module AST module_function # Analyze a multiplex, and all queries within. # Multiplex analyzers are ran for all queries, keeping state. # Query analyzers are ran per query, without carrying state between queries. # # @param multiplex [GraphQL::Execution::Multiplex] # @param analyzers [Array] # @return [Array] Results from multiplex analyzers def analyze_multiplex(multiplex, analyzers) multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) } multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do query_results = multiplex.queries.map do |query| if query.valid? analyze_query( query, query.analyzers, multiplex_analyzers: multiplex_analyzers ) else [] end end multiplex_results = multiplex_analyzers.map(&:result) multiplex_errors = analysis_errors(multiplex_results) multiplex.queries.each_with_index do |query, idx| query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx]) end multiplex_results end end # @param query [GraphQL::Query] # @param analyzers [Array] # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_analyzers: []) query.current_trace.analyze_query(query: query) do query_analyzers = analyzers .map { |analyzer| analyzer.new(query) } .tap { _1.select!(&:analyze?) } analyzers_to_run = query_analyzers + multiplex_analyzers if analyzers_to_run.any? analyzers_to_run.select!(&:visit?) if analyzers_to_run.any? visitor = GraphQL::Analysis::AST::Visitor.new( query: query, analyzers: analyzers_to_run ) # `nil` or `0` causes no timeout Timeout::timeout(query.validate_timeout_remaining) do visitor.visit end if visitor.rescued_errors.any? return visitor.rescued_errors end end query_analyzers.map(&:result) else [] end end rescue Timeout::Error [GraphQL::AnalysisError.new("Timeout on validation of query")] rescue GraphQL::UnauthorizedError # This error was raised during analysis and will be returned the client before execution [] end def analysis_errors(results) results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } } end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/000077500000000000000000000000001476434635200210545ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/analysis/ast/analyzer.rb000066400000000000000000000057231476434635200232350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Query analyzer for query ASTs. Query analyzers respond to visitor style methods # but are prefixed by `enter` and `leave`. # # When an analyzer is initialized with a Multiplex, you can always get the current query from # `visitor.query` in the visit methods. # # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze class Analyzer def initialize(subject) @subject = subject if subject.is_a?(GraphQL::Query) @query = subject @multiplex = nil else @multiplex = subject @query = nil end end # Analyzer hook to decide at analysis time whether a query should # be analyzed or not. # @return [Boolean] If the query should be analyzed or not def analyze? true end # Analyzer hook to decide at analysis time whether analysis # requires a visitor pass; can be disabled for precomputed results. # @return [Boolean] If analysis requires visitation or not def visit? true end # The result for this analyzer. Returning {GraphQL::AnalysisError} results # in a query error. # @return [Any] The analyzer result def result raise GraphQL::RequiredImplementationMissingError end class << self private def build_visitor_hooks(member_name) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def on_enter_#{member_name}(node, parent, visitor) end def on_leave_#{member_name}(node, parent, visitor) end EOS end end build_visitor_hooks :argument build_visitor_hooks :directive build_visitor_hooks :document build_visitor_hooks :enum build_visitor_hooks :field build_visitor_hooks :fragment_spread build_visitor_hooks :inline_fragment build_visitor_hooks :input_object build_visitor_hooks :list_type build_visitor_hooks :non_null_type build_visitor_hooks :null_value build_visitor_hooks :operation_definition build_visitor_hooks :type_name build_visitor_hooks :variable_definition build_visitor_hooks :variable_identifier build_visitor_hooks :abstract_node protected # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing attr_reader :subject # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex # (When this is `nil`, use `visitor.query` inside visit methods to get the current query) attr_reader :query # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query attr_reader :multiplex end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/field_usage.rb000066400000000000000000000060371476434635200236560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST class FieldUsage < Analyzer def initialize(query) super @used_fields = Set.new @used_deprecated_fields = Set.new @used_deprecated_arguments = Set.new @used_deprecated_enum_values = Set.new end def on_leave_field(node, parent, visitor) field_defn = visitor.field_definition field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}" @used_fields << field @used_deprecated_fields << field if field_defn.deprecation_reason arguments = visitor.query.arguments_for(node, field_defn) # If there was an error when preparing this argument object, # then this might be an error or something: if arguments.respond_to?(:argument_values) extract_deprecated_arguments(arguments.argument_values) end end def result { used_fields: @used_fields.to_a, used_deprecated_fields: @used_deprecated_fields.to_a, used_deprecated_arguments: @used_deprecated_arguments.to_a, used_deprecated_enum_values: @used_deprecated_enum_values.to_a, } end private def extract_deprecated_arguments(argument_values) argument_values.each_pair do |_argument_name, argument| if argument.definition.deprecation_reason @used_deprecated_arguments << argument.definition.path end next if argument.value.nil? argument_type = argument.definition.type if argument_type.non_null? argument_type = argument_type.of_type end if argument_type.kind.input_object? extract_deprecated_arguments(argument.value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance elsif argument_type.kind.enum? extract_deprecated_enum_value(argument_type, argument.value) elsif argument_type.list? inner_type = argument_type.unwrap case inner_type.kind when TypeKinds::INPUT_OBJECT argument.value.each do |value| extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance end when TypeKinds::ENUM argument.value.each do |value| extract_deprecated_enum_value(inner_type, value) end else # Not a kind of input that we track end end end end def extract_deprecated_enum_value(enum_type, value) enum_value = @query.warden.enum_values(enum_type).find { |ev| ev.value == value } if enum_value&.deprecation_reason @used_deprecated_enum_values << enum_value.path end end end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/max_query_complexity.rb000066400000000000000000000012251476434635200256700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Used under the hood to implement complexity validation, # see {Schema#max_complexity} and {Query#max_complexity} class MaxQueryComplexity < QueryComplexity def result return if subject.max_complexity.nil? total_complexity = max_possible_complexity if total_complexity > subject.max_complexity GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}") else nil end end end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/max_query_depth.rb000066400000000000000000000010561476434635200246010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST class MaxQueryDepth < QueryDepth def result configured_max_depth = if query query.max_depth else multiplex.schema.max_depth end if configured_max_depth && @max_depth > configured_max_depth GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}") else nil end end end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/query_complexity.rb000066400000000000000000000201411476434635200250210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # Calculate the complexity of a query, using {Field#complexity} values. module AST class QueryComplexity < Analyzer # State for the query complexity calculation: # - `complexities_on_type` holds complexity scores for each type def initialize(query) super @complexities_on_type_by_query = {} end # Overide this method to use the complexity result def result max_possible_complexity end # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie: # Hash> class ScopedTypeComplexity < Hash # A proc for defaulting empty namespace requests as a new scope hash. DEFAULT_PROC = ->(h, k) { h[k] = {} } attr_reader :field_definition, :response_path, :query # @param parent_type [Class] The owner of `field_definition` # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration # @param query [GraphQL::Query] Used for `query.possible_types` # @param response_path [Array] The path to the response key for the field # @return [Hash>] def initialize(parent_type, field_definition, query, response_path) super(&DEFAULT_PROC) @parent_type = parent_type @field_definition = field_definition @query = query @response_path = response_path @nodes = [] end # @return [Array] attr_reader :nodes def own_complexity(child_complexity) @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity) end end def on_enter_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? parent_type = visitor.parent_type_definition field_key = node.alias || node.name # Find or create a complexity scope stack for this query. scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)] # Find or create the complexity costing node for this field. scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path) scope.nodes.push(node) scopes_stack.push(scope) end def on_leave_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? scopes_stack = @complexities_on_type_by_query[visitor.query] scopes_stack.pop end private # @return [Integer] def max_possible_complexity @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)| total + merged_max_complexity_for_scopes(query, [scopes_stack.first]) end end # @param query [GraphQL::Query] Used for `query.possible_types` # @param scopes [Array] Array of scoped type complexities # @return [Integer] def merged_max_complexity_for_scopes(query, scopes) # Aggregate a set of all possible scope types encountered (scope keys). # Use a hash, but ignore the values; it's just a fast way to work with the keys. possible_scope_types = scopes.each_with_object({}) do |scope, memo| memo.merge!(scope) end # Expand abstract scope types into their concrete implementations; # overlapping abstracts coalesce through their intersecting types. possible_scope_types.keys.each do |possible_scope_type| next unless possible_scope_type.kind.abstract? query.possible_types(possible_scope_type).each do |impl_type| possible_scope_types[impl_type] ||= true end possible_scope_types.delete(possible_scope_type) end # Aggregate the lexical selections that may apply to each possible type, # and then return the maximum cost among possible typed selections. possible_scope_types.each_key.reduce(0) do |max, possible_scope_type| # Collect inner selections from all scopes that intersect with this possible type. all_inner_selections = scopes.each_with_object([]) do |scope, memo| scope.each do |scope_type, inner_selections| memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type) end end # Find the maximum complexity for the scope type among possible lexical branches. complexity = merged_max_complexity(query, all_inner_selections) complexity > max ? complexity : max end end def types_intersect?(query, a, b) return true if a == b a_types = query.possible_types(a) query.possible_types(b).any? { |t| a_types.include?(t) } end # A hook which is called whenever a field's max complexity is calculated. # Override this method to capture individual field complexity details. # # @param scoped_type_complexity [ScopedTypeComplexity] # @param max_complexity [Numeric] Field's maximum complexity including child complexity # @param child_complexity [Numeric, nil] Field's child complexity def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil) end # @param inner_selections [Array>] Field selections for a scope # @return [Integer] Total complexity value for all these selections in the parent scope def merged_max_complexity(query, inner_selections) # Aggregate a set of all unique field selection keys across all scopes. # Use a hash, but ignore the values; it's just a fast way to work with the keys. unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo| memo.merge!(inner_selection) end # Add up the total cost for each unique field name's coalesced selections unique_field_keys.each_key.reduce(0) do |total, field_key| composite_scopes = nil field_cost = 0 # Collect composite selection scopes for further aggregation, # leaf selections report their costs directly. inner_selections.each do |inner_selection| child_scope = inner_selection[field_key] next unless child_scope # Empty child scopes are leaf nodes with zero child complexity. if child_scope.empty? field_cost = child_scope.own_complexity(0) field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil) else composite_scopes ||= [] composite_scopes << child_scope end end if composite_scopes child_complexity = merged_max_complexity_for_scopes(query, composite_scopes) # This is the last composite scope visited; assume it's representative (for backwards compatibility). # Note: it would be more correct to score each composite scope and use the maximum possibility. field_cost = composite_scopes.last.own_complexity(child_complexity) field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity) end total + field_cost end end end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/query_depth.rb000066400000000000000000000032301476434635200237300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for measuring the depth of a given query. # # See https://graphql-ruby.org/queries/ast_analysis.html for more examples. # # @example Logging the depth of a query # class LogQueryDepth < GraphQL::Analysis::QueryDepth # def result # log("GraphQL query depth: #{@max_depth}") # end # end # # # In your Schema file: # # class MySchema < GraphQL::Schema # query_analyzer LogQueryDepth # end # # # When you run the query, the depth will get logged: # # Schema.execute(query_str) # # GraphQL query depth: 8 # module AST class QueryDepth < Analyzer def initialize(query) @max_depth = 0 @current_depth = 0 @count_introspection_fields = query.schema.count_introspection_fields super end def on_enter_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? || (@count_introspection_fields == false && visitor.field_definition.introspection?) @current_depth += 1 end def on_leave_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? || (@count_introspection_fields == false && visitor.field_definition.introspection?) if @max_depth < @current_depth @max_depth = @current_depth end @current_depth -= 1 end def result @max_depth end end end end end graphql-ruby-2.2.17/lib/graphql/analysis/ast/visitor.rb000066400000000000000000000216221476434635200231030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Depth first traversal through a query AST, calling AST analyzers # along the way. # # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting # only the selected operation, providing helpers for common use cases such # as skipped fields and visiting fragment spreads. # # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries class Visitor < GraphQL::Language::StaticVisitor def initialize(query:, analyzers:) @analyzers = analyzers @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @rescued_errors = [] @query = query @schema = query.schema @response_path = [] @skip_stack = [false] super(query.selected_operation) end # @return [GraphQL::Query] the query being visited attr_reader :query # @return [Array] Types whose scope we've entered attr_reader :object_types # @return [Array] The path to the response key for the current field def response_path @response_path.dup end # Visitor Hooks [ :operation_definition, :fragment_definition, :inline_fragment, :field, :directive, :argument, :fragment_spread ].each do |node_type| module_eval <<-RUBY, __FILE__, __LINE__ def call_on_enter_#{node_type}(node, parent) @analyzers.each do |a| begin a.on_enter_#{node_type}(node, parent, self) rescue AnalysisError => err @rescued_errors << err end end end def call_on_leave_#{node_type}(node, parent) @analyzers.each do |a| begin a.on_leave_#{node_type}(node, parent, self) rescue AnalysisError => err @rescued_errors << err end end end RUBY end def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) @object_types.push(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") call_on_enter_operation_definition(node, parent) super call_on_leave_operation_definition(node, parent) @object_types.pop @path.pop end def on_fragment_definition(node, parent) on_fragment_with_type(node) do @path.push("fragment #{node.name}") @in_fragment_def = false call_on_enter_fragment_definition(node, parent) super @in_fragment_def = false call_on_leave_fragment_definition(node, parent) end end def on_inline_fragment(node, parent) on_fragment_with_type(node) do @path.push("...#{node.type ? " on #{node.type.name}" : ""}") call_on_enter_inline_fragment(node, parent) super call_on_leave_inline_fragment(node, parent) end end def on_field(node, parent) @response_path.push(node.alias || node.name) parent_type = @object_types.last # This could be nil if the previous field wasn't found: field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap @object_types.push(next_object_type) else @object_types.push(nil) end @path.push(node.alias || node.name) @skipping = @skip_stack.last || skip?(node) @skip_stack << @skipping call_on_enter_field(node, parent) super @skipping = @skip_stack.pop call_on_leave_field(node, parent) @response_path.pop @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) directive_defn = @schema.directives[node.name] @directive_definitions.push(directive_defn) call_on_enter_directive(node, parent) super call_on_leave_directive(node, parent) @directive_definitions.pop end def on_argument(node, parent) argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? arg_type.get_argument(node.name, @query.context) else nil end elsif (directive_defn = @directive_definitions.last) directive_defn.get_argument(node.name, @query.context) elsif (field_defn = @field_definitions.last) field_defn.get_argument(node.name, @query.context) else nil end @argument_definitions.push(argument_defn) @path.push(node.name) call_on_enter_argument(node, parent) super call_on_leave_argument(node, parent) @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) @path.push("... #{node.name}") call_on_enter_fragment_spread(node, parent) enter_fragment_spread_inline(node) super leave_fragment_spread_inline(node) call_on_leave_fragment_spread(node, parent) @path.pop end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to def previous_field_definition @field_definitions[-2] end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition @argument_definitions.last end # @return [GraphQL::Argument, nil] The previous GraphQL argument def previous_argument_definition @argument_definitions[-2] end private # Visit a fragment spread inline instead of visiting the definition # by itself. def enter_fragment_spread_inline(fragment_spread) fragment_def = query.fragments[fragment_spread.name] object_type = if fragment_def.type @query.warden.get_type(fragment_def.type.name) else object_types.last end object_types << object_type on_fragment_definition_children(fragment_def) end # Visit a fragment spread inline instead of visiting the definition # by itself. def leave_fragment_spread_inline(_fragment_spread) object_types.pop end def skip?(ast_node) dir = ast_node.directives dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query) end def on_fragment_with_type(node) object_type = if node.type @query.warden.get_type(node.type.name) else @object_types.last end @object_types.push(object_type) yield(node) @object_types.pop @path.pop end end end end end graphql-ruby-2.2.17/lib/graphql/analysis_error.rb000066400000000000000000000001471476434635200220250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class AnalysisError < GraphQL::ExecutionError end end graphql-ruby-2.2.17/lib/graphql/backtrace.rb000066400000000000000000000027231476434635200207120ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/backtrace/inspect_result" require "graphql/backtrace/table" require "graphql/backtrace/traced_error" require "graphql/backtrace/tracer" require "graphql/backtrace/trace" module GraphQL # Wrap unhandled errors with {TracedError}. # # {TracedError} provides a GraphQL backtrace with arguments and return values. # The underlying error is available as {TracedError#cause}. # # @example toggling backtrace annotation # class MySchema < GraphQL::Schema # if Rails.env.development? || Rails.env.test? # use GraphQL::Backtrace # end # end # class Backtrace include Enumerable extend Forwardable def_delegators :to_a, :each, :[] def self.use(schema_defn) schema_defn.trace_with(self::Trace) end def initialize(context, value: nil) @table = Table.new(context, value: value) end def inspect @table.to_table end alias :to_s :inspect def to_a @table.to_backtrace end # Used for internal bookkeeping # @api private class Frame attr_reader :path, :query, :ast_node, :object, :field, :arguments, :parent_frame def initialize(path:, query:, ast_node:, object:, field:, arguments:, parent_frame:) @path = path @query = query @ast_node = ast_node @field = field @object = object @arguments = arguments @parent_frame = parent_frame end end end end graphql-ruby-2.2.17/lib/graphql/backtrace/000077500000000000000000000000001476434635200203615ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/backtrace/inspect_result.rb000066400000000000000000000021051476434635200237470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace module InspectResult module_function def inspect_result(obj) case obj when Hash "{" + obj.map do |key, val| "#{key}: #{inspect_truncated(val)}" end.join(", ") + "}" when Array "[" + obj.map { |v| inspect_truncated(v) }.join(", ") + "]" when Query::Context::SharedMethods if obj.invalid_null? "nil" else inspect_truncated(obj.value) end else inspect_truncated(obj) end end def inspect_truncated(obj) case obj when Hash "{...}" when Array "[...]" when Query::Context::SharedMethods if obj.invalid_null? "nil" else inspect_truncated(obj.value) end when GraphQL::Execution::Lazy "(unresolved)" else "#{obj.inspect}" end end end end end graphql-ruby-2.2.17/lib/graphql/backtrace/table.rb000066400000000000000000000101561476434635200220000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace # A class for turning a context into a human-readable table or array class Table MIN_COL_WIDTH = 4 MAX_COL_WIDTH = 100 HEADERS = [ "Loc", "Field", "Object", "Arguments", "Result", ] def initialize(context, value:) @context = context @override_value = value end # @return [String] A table layout of backtrace with metadata def to_table @to_table ||= render_table(rows) end # @return [Array] An array of position + field name entries def to_backtrace @to_backtrace ||= begin backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" } # skip the header entry backtrace.shift backtrace end end private def rows @rows ||= build_rows(@context, rows: [HEADERS], top: true) end # @return [String] def render_table(rows) max = Array.new(HEADERS.length, MIN_COL_WIDTH) rows.each do |row| row.each_with_index do |col, idx| col_len = col.length max_len = max[idx] if col_len > max_len if col_len > MAX_COL_WIDTH max[idx] = MAX_COL_WIDTH else max[idx] = col_len end end end end table = "".dup last_col_idx = max.length - 1 rows.each do |row| table << row.map.each_with_index do |col, idx| max_len = max[idx] if idx < last_col_idx col = col.ljust(max_len) end if col.length > max_len col = col[0, max_len - 3] + "..." end col end.join(" | ") table << "\n" end table end # @return [Array] 5 items for a backtrace table (not `key`) def build_rows(context_entry, rows:, top: false) case context_entry when Backtrace::Frame field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias value = if top && @override_value @override_value else value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path) end rows << [ "#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}", "#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}", "#{context_entry.object.object.inspect}", context_entry.arguments.to_h.inspect, # rubocop:disable Development/ContextIsPassedCop -- unrelated method Backtrace::InspectResult.inspect_result(value), ] if (parent = context_entry.parent_frame) build_rows(parent, rows: rows) else rows end when GraphQL::Query::Context query = context_entry.query op = query.selected_operation if op op_type = op.operation_type position = "#{op.line}:#{op.col}" else op_type = "query" position = "?:?" end op_name = query.selected_operation_name object = query.root_value if object.is_a?(GraphQL::Schema::Object) object = object.object end value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], []) rows << [ "#{position}", "#{op_type}#{op_name ? " #{op_name}" : ""}", "#{object.inspect}", query.variables.to_h.inspect, Backtrace::InspectResult.inspect_result(value), ] else raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})" end end def value_at(runtime, path) response = runtime.final_result path.each do |key| if response && (response = response[key]) next else break end end response end end end end graphql-ruby-2.2.17/lib/graphql/backtrace/trace.rb000066400000000000000000000047731476434635200220170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace module Trace def initialize(*args, **kwargs, &block) @__backtrace_contexts = {} @__backtrace_last_context = nil super end def validate(query:, validate:) if query.multiplex push_query_backtrace_context(query) end super end def analyze_query(query:) if query.multiplex # missing for stand-alone static validation push_query_backtrace_context(query) end super end def execute_query(query:) push_query_backtrace_context(query) super end def execute_query_lazy(query:, multiplex:) query ||= multiplex.queries.first push_query_backtrace_context(query) super end def execute_field(field:, query:, ast_node:, arguments:, object:) push_field_backtrace_context(field, query, ast_node, arguments, object) super end def execute_field_lazy(field:, query:, ast_node:, arguments:, object:) push_field_backtrace_context(field, query, ast_node, arguments, object) super end def execute_multiplex(multiplex:) super rescue StandardError => err # This is an unhandled error from execution, # Re-raise it with a GraphQL trace. potential_context = @__backtrace_last_context if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(Backtrace::Frame) raise TracedError.new(err, potential_context) else raise end end private def push_query_backtrace_context(query) push_data = query push_key = [] @__backtrace_contexts[push_key] = push_data @__backtrace_last_context = push_data end def push_field_backtrace_context(field, query, ast_node, arguments, object) push_key = query.context[:current_path] push_storage = @__backtrace_contexts parent_frame = push_storage[push_key[0..-2]] if parent_frame.is_a?(GraphQL::Query) parent_frame = parent_frame.context end push_data = Frame.new( query: query, path: push_key, ast_node: ast_node, field: field, object: object, arguments: arguments, parent_frame: parent_frame, ) push_storage[push_key] = push_data @__backtrace_last_context = push_data end end end end graphql-ruby-2.2.17/lib/graphql/backtrace/traced_error.rb000066400000000000000000000033251476434635200233640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace # When {Backtrace} is enabled, raised errors are wrapped with {TracedError}. class TracedError < GraphQL::Error # @return [Array] Printable backtrace of GraphQL error context attr_reader :graphql_backtrace # @return [GraphQL::Query::Context] The context at the field where the error was raised attr_reader :context MESSAGE_TEMPLATE = <<-MESSAGE Unhandled error during GraphQL execution: %{cause_message} %{cause_backtrace} %{cause_backtrace_more} Use #cause to access the original exception (including #cause.backtrace). GraphQL Backtrace: %{graphql_table} MESSAGE # This many lines of the original Ruby backtrace # are included in the message CAUSE_BACKTRACE_PREVIEW_LENGTH = 10 def initialize(err, current_ctx) @context = current_ctx backtrace = Backtrace.new(current_ctx, value: err) @graphql_backtrace = backtrace.to_a cause_backtrace_preview = err.backtrace.first(CAUSE_BACKTRACE_PREVIEW_LENGTH).join("\n ") cause_backtrace_remainder_length = err.backtrace.length - CAUSE_BACKTRACE_PREVIEW_LENGTH cause_backtrace_more = if cause_backtrace_remainder_length < 0 "" elsif cause_backtrace_remainder_length == 1 "... and 1 more line\n" else "... and #{cause_backtrace_remainder_length} more lines\n" end message = MESSAGE_TEMPLATE % { cause_message: err.message, cause_backtrace: cause_backtrace_preview, cause_backtrace_more: cause_backtrace_more, graphql_table: backtrace.inspect, } super(message) end end end end graphql-ruby-2.2.17/lib/graphql/backtrace/tracer.rb000066400000000000000000000051521476434635200221710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace # TODO this is not fiber-friendly module Tracer module_function # Implement the {GraphQL::Tracing} API. def trace(key, metadata) case key when "lex", "parse" # No context here, don't have a query yet nil when "execute_multiplex", "analyze_multiplex" # No query context yet nil when "validate", "analyze_query", "execute_query", "execute_query_lazy" push_key = [] if (query = metadata[:query]) || ((queries = metadata[:queries]) && (query = queries.first)) push_data = query multiplex = query.multiplex elsif (multiplex = metadata[:multiplex]) push_data = multiplex.queries.first end when "execute_field", "execute_field_lazy" query = metadata[:query] multiplex = query.multiplex push_key = query.context[:current_path] parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]] if parent_frame.is_a?(GraphQL::Query) parent_frame = parent_frame.context end push_data = Frame.new( query: query, path: push_key, ast_node: metadata[:ast_node], field: metadata[:field], object: metadata[:object], arguments: metadata[:arguments], parent_frame: parent_frame, ) else # Custom key, no backtrace data for this nil end if push_data && multiplex push_storage = multiplex.context[:graphql_backtrace_contexts] ||= {} push_storage[push_key] = push_data multiplex.context[:last_graphql_backtrace_context] = push_data end if key == "execute_multiplex" multiplex_context = metadata[:multiplex].context begin yield rescue StandardError => err # This is an unhandled error from execution, # Re-raise it with a GraphQL trace. potential_context = multiplex_context[:last_graphql_backtrace_context] if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(Backtrace::Frame) raise TracedError.new(err, potential_context) else raise end ensure multiplex_context.delete(:graphql_backtrace_contexts) multiplex_context.delete(:last_graphql_backtrace_context) end else yield end end end end end graphql-ruby-2.2.17/lib/graphql/coercion_error.rb000066400000000000000000000001471476434635200220030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class CoercionError < GraphQL::ExecutionError end end graphql-ruby-2.2.17/lib/graphql/dataloader.rb000066400000000000000000000200141476434635200210640ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/dataloader/null_dataloader" require "graphql/dataloader/request" require "graphql/dataloader/request_all" require "graphql/dataloader/source" module GraphQL # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}. # # @example Installing Dataloader # # class MySchema < GraphQL::Schema # use GraphQL::Dataloader # end # # @example Waiting for batch-loaded data in a GraphQL field # # field :team, Types::Team, null: true # # def team # dataloader.with(Sources::Record, Team).load(object.team_id) # end # class Dataloader class << self attr_accessor :default_nonblocking end NonblockingDataloader = Class.new(self) { self.default_nonblocking = true } def self.use(schema, nonblocking: nil) schema.dataloader_class = if nonblocking warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.") NonblockingDataloader else self end end # Call the block with a Dataloader instance, # then run all enqueued jobs and return the result of the block. def self.with_dataloading(&block) dataloader = self.new result = nil dataloader.append_job { result = block.call(dataloader) } dataloader.run result end def initialize(nonblocking: self.class.default_nonblocking) @source_cache = Hash.new { |h, k| h[k] = {} } @pending_jobs = [] if !nonblocking.nil? @nonblocking = nonblocking end end def nonblocking? @nonblocking end # This is called before the fiber is spawned, from the parent context (i.e. from # the thread or fiber that it is scheduled from). # # @return [Hash] Current fiber-local variables def get_fiber_variables fiber_vars = {} Thread.current.keys.each do |fiber_var_key| # This variable should be fresh in each new fiber if fiber_var_key != :__graphql_runtime_info fiber_vars[fiber_var_key] = Thread.current[fiber_var_key] end end fiber_vars end # Set up the fiber variables in a new fiber. # # This is called within the fiber, right after it is spawned. # # @param vars [Hash] Fiber-local variables from {get_fiber_variables} # @return [void] def set_fiber_variables(vars) vars.each { |k, v| Thread.current[k] = v } nil end # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on. # # @param source_class [Class] # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`, # and cached for the lifetime of this {Multiplex}. if RUBY_VERSION < "3" || RUBY_ENGINE != "ruby" # truffle-ruby wasn't doing well with the implementation below def with(source_class, *batch_args) batch_key = source_class.batch_key_for(*batch_args) @source_cache[source_class][batch_key] ||= begin source = source_class.new(*batch_args) source.setup(self) source end end else def with(source_class, *batch_args, **batch_kwargs) batch_key = source_class.batch_key_for(*batch_args, **batch_kwargs) @source_cache[source_class][batch_key] ||= begin source = source_class.new(*batch_args, **batch_kwargs) source.setup(self) source end end end # Tell the dataloader that this fiber is waiting for data. # # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber). # # @return [void] def yield Fiber.yield nil end # @api private Nothing to see here def append_job(&job) # Given a block, queue it up to be worked through when `#run` is called. # (If the dataloader is already running, than a Fiber will pick this up later.) @pending_jobs.push(job) nil end # Clear any already-loaded objects from {Source} caches # @return [void] def clear_cache @source_cache.each do |_source_class, batched_sources| batched_sources.each_value(&:clear_cache) end nil end # Use a self-contained queue for the work in the block. def run_isolated prev_queue = @pending_jobs prev_pending_keys = {} @source_cache.each do |source_class, batched_sources| batched_sources.each do |batch_args, batched_source_instance| if batched_source_instance.pending? prev_pending_keys[batched_source_instance] = batched_source_instance.pending.dup batched_source_instance.pending.clear end end end @pending_jobs = [] res = nil # Make sure the block is inside a Fiber, so it can `Fiber.yield` append_job { res = yield } run res ensure @pending_jobs = prev_queue prev_pending_keys.each do |source_instance, pending| pending.each do |key, value| if !source_instance.results.key?(key) source_instance.pending[key] = value end end end end def run job_fibers = [] next_job_fibers = [] source_fibers = [] next_source_fibers = [] first_pass = true manager = spawn_fiber do while first_pass || job_fibers.any? first_pass = false while (f = (job_fibers.shift || spawn_job_fiber)) if f.alive? finished = run_fiber(f) if !finished next_job_fibers << f end end end join_queues(job_fibers, next_job_fibers) while source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) } while (f = source_fibers.shift || spawn_source_fiber) if f.alive? finished = run_fiber(f) if !finished next_source_fibers << f end end end join_queues(source_fibers, next_source_fibers) end end end run_fiber(manager) if manager.alive? raise "Invariant: Manager fiber didn't terminate properly." end if job_fibers.any? raise "Invariant: job fibers should have exited but #{job_fibers.size} remained" end if source_fibers.any? raise "Invariant: source fibers should have exited but #{source_fibers.size} remained" end rescue UncaughtThrowError => e throw e.tag, e.value end def run_fiber(f) f.resume end def spawn_fiber fiber_vars = get_fiber_variables Fiber.new(blocking: !@nonblocking) { set_fiber_variables(fiber_vars) yield # With `.transfer`, you have to explicitly pass back to the parent -- # if the fiber is allowed to terminate normally, control is passed to the main fiber instead. true } end private def join_queues(prev_queue, new_queue) @nonblocking && Fiber.scheduler.run prev_queue.concat(new_queue) new_queue.clear end def spawn_job_fiber if @pending_jobs.any? spawn_fiber do while job = @pending_jobs.shift job.call end end end end def spawn_source_fiber pending_sources = nil @source_cache.each_value do |source_by_batch_params| source_by_batch_params.each_value do |source| if source.pending? pending_sources ||= [] pending_sources << source end end end if pending_sources spawn_fiber do pending_sources.each(&:run_pending_keys) end end end end end require "graphql/dataloader/async_dataloader" graphql-ruby-2.2.17/lib/graphql/dataloader/000077500000000000000000000000001476434635200205425ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/dataloader/async_dataloader.rb000066400000000000000000000046731476434635200243760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Dataloader class AsyncDataloader < Dataloader def yield if (condition = Thread.current[:graphql_dataloader_next_tick]) condition.wait else Fiber.yield end nil end def run job_fibers = [] next_job_fibers = [] source_tasks = [] next_source_tasks = [] first_pass = true sources_condition = Async::Condition.new manager = spawn_fiber do while first_pass || job_fibers.any? first_pass = false while (f = (job_fibers.shift || spawn_job_fiber)) if f.alive? finished = run_fiber(f) if !finished next_job_fibers << f end end end job_fibers.concat(next_job_fibers) next_job_fibers.clear Sync do |root_task| while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) } while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition)) if task.alive? root_task.yield # give the source task a chance to run next_source_tasks << task end end sources_condition.signal source_tasks.concat(next_source_tasks) next_source_tasks.clear end end end end manager.resume if manager.alive? raise "Invariant: Manager didn't terminate successfully: #{manager}" end rescue UncaughtThrowError => e throw e.tag, e.value end private def spawn_source_task(parent_task, condition) pending_sources = nil @source_cache.each_value do |source_by_batch_params| source_by_batch_params.each_value do |source| if source.pending? pending_sources ||= [] pending_sources << source end end end if pending_sources fiber_vars = get_fiber_variables parent_task.async do set_fiber_variables(fiber_vars) Thread.current[:graphql_dataloader_next_tick] = condition pending_sources.each(&:run_pending_keys) end end end end end end graphql-ruby-2.2.17/lib/graphql/dataloader/null_dataloader.rb000066400000000000000000000012451476434635200242230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Dataloader # The default implementation of dataloading -- all no-ops. # # The Dataloader interface isn't public, but it enables # simple internal code while adding the option to add Dataloader. class NullDataloader < Dataloader # These are all no-ops because code was # executed sychronously. def run; end def run_isolated; yield; end def yield raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources." end def append_job yield nil end end end end graphql-ruby-2.2.17/lib/graphql/dataloader/request.rb000066400000000000000000000012431476434635200225570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Dataloader # @see Source#request which returns an instance of this class Request def initialize(source, key) @source = source @key = key end # Call this method to cause the current Fiber to wait for the results of this request. # # @return [Object] the object loaded for `key` def load @source.load(@key) end def load_with_deprecation_warning warn("Returning `.request(...)` from GraphQL::Dataloader is deprecated, use `.load(...)` instead. (See usage of #{@source} with #{@key.inspect}).") load end end end end graphql-ruby-2.2.17/lib/graphql/dataloader/request_all.rb000066400000000000000000000007521476434635200234130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Dataloader # @see Source#request_all which returns an instance of this. class RequestAll < Request def initialize(source, keys) @source = source @keys = keys end # Call this method to cause the current Fiber to wait for the results of this request. # # @return [Array] One object for each of `keys` def load @source.load_all(@keys) end end end end graphql-ruby-2.2.17/lib/graphql/dataloader/source.rb000066400000000000000000000157651476434635200224050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Dataloader class Source # Called by {Dataloader} to prepare the {Source}'s internal state # @api private def setup(dataloader) # These keys have been requested but haven't been fetched yet @pending = {} # These keys have been passed to `fetch` but haven't been finished yet @fetching = {} # { key => result } @results = {} @dataloader = dataloader end attr_reader :dataloader # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result. def request(value) res_key = result_key_for(value) if !@results.key?(res_key) @pending[res_key] ||= value end Dataloader::Request.new(self, value) end # Implement this method to return a stable identifier if different # key objects should load the same data value. # # @param value [Object] A value passed to `.request` or `.load`, for which a value will be loaded # @return [Object] The key for tracking this pending data def result_key_for(value) value end # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results. def request_all(values) values.each do |v| res_key = result_key_for(v) if !@results.key?(res_key) @pending[res_key] ||= v end end Dataloader::RequestAll.new(self, values) end # @param value [Object] A loading value which will be passed to {#fetch} if it isn't already in the internal cache. # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded. def load(value) result_key = result_key_for(value) if @results.key?(result_key) result_for(result_key) else @pending[result_key] ||= value sync([result_key]) result_for(result_key) end end # @param values [Array] Loading keys which will be passed to `#fetch` (or read from the internal cache). # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded. def load_all(values) result_keys = [] pending_keys = [] values.each { |v| k = result_key_for(v) result_keys << k if !@results.key?(k) @pending[k] ||= v pending_keys << k end } if pending_keys.any? sync(pending_keys) end result_keys.map { |k| result_for(k) } end # Subclasses must implement this method to return a value for each of `keys` # @param keys [Array] keys passed to {#load}, {#load_all}, {#request}, or {#request_all} # @return [Array] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`. def fetch(keys) # somehow retrieve these from the backend raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys" end MAX_ITERATIONS = 1000 # Wait for a batch, if there's anything to batch. # Then run the batch and update the cache. # @return [void] def sync(pending_result_keys) @dataloader.yield iterations = 0 while pending_result_keys.any? { |key| !@results.key?(key) } iterations += 1 if iterations > MAX_ITERATIONS raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency." end @dataloader.yield end nil end # @return [Boolean] True if this source has any pending requests for data. def pending? !@pending.empty? end # Add these key-value pairs to this source's cache # (future loads will use these merged values). # @param new_results [Hash Object>] key-value pairs to cache in this source # @return [void] def merge(new_results) new_results.each do |new_k, new_v| key = result_key_for(new_k) @results[key] = new_v end nil end # Called by {GraphQL::Dataloader} to resolve and pending requests to this source. # @api private # @return [void] def run_pending_keys if !@fetching.empty? @fetching.each_key { |k| @pending.delete(k) } end return if @pending.empty? fetch_h = @pending @pending = {} @fetching.merge!(fetch_h) results = fetch(fetch_h.values) fetch_h.each_with_index do |(key, _value), idx| @results[key] = results[idx] end nil rescue StandardError => error fetch_h.each_key { |key| @results[key] = error } ensure fetch_h && fetch_h.each_key { |k| @fetching.delete(k) } end # These arguments are given to `dataloader.with(source_class, ...)`. The object # returned from this method is used to de-duplicate batch loads under the hood # by using it as a Hash key. # # By default, the arguments are all put in an Array. To customize how this source's # batches are merged, override this method to return something else. # # For example, if you pass `ActiveRecord::Relation`s to `.with(...)`, you could override # this method to call `.to_sql` on them, thus merging `.load(...)` calls when they apply # to equivalent relations. # # @param batch_args [Array] # @param batch_kwargs [Hash] # @return [Object] def self.batch_key_for(*batch_args, **batch_kwargs) [*batch_args, **batch_kwargs] end # Clear any already-loaded objects for this source # @return [void] def clear_cache @results.clear nil end attr_reader :pending, :results private # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error # @param key [Object] key passed to {#load} or {#load_all} # @return [Object] The result from {#fetch} for `key`. # @api private def result_for(key) if !@results.key?(key) raise GraphQL::InvariantError, <<-ERR Fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys}) This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new. ERR end result = @results[key] if result.is_a?(StandardError) # Dup it because the rescuer may modify it. # (This happens for GraphQL::ExecutionErrors, at least) raise result.dup end result end end end end graphql-ruby-2.2.17/lib/graphql/date_encoding_error.rb000066400000000000000000000010241476434635200227600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::ISO8601Date` is asked to return a value # that cannot be parsed to a Ruby Date. # # @see GraphQL::Types::ISO8601Date which raises this error class DateEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :date_value def initialize(value) @date_value = value super("Date cannot be parsed: #{value}. \nDate must be be able to be parsed as a Ruby Date object.") end end end graphql-ruby-2.2.17/lib/graphql/dig.rb000066400000000000000000000011231476434635200175270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Dig # implemented using the old activesupport #dig instead of the ruby built-in # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments # to handle stringified/symbolized keys. # # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly # @return [Object] def dig(own_key, *rest_keys) val = self[own_key] if val.nil? || rest_keys.empty? val else val.dig(*rest_keys) end end end end graphql-ruby-2.2.17/lib/graphql/duration_encoding_error.rb000066400000000000000000000011201476434635200236650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::ISO8601Duration` is asked to return a value # that cannot be parsed as an ISO8601-formatted duration by ActiveSupport::Duration. # # @see GraphQL::Types::ISO8601Duration which raises this error class DurationEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :duration_value def initialize(value) @duration_value = value super("Duration cannot be parsed: #{value}. \nDuration must be an ISO8601-formatted duration.") end end end graphql-ruby-2.2.17/lib/graphql/execution.rb000066400000000000000000000007161476434635200207760ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/directive_checks" require "graphql/execution/interpreter" require "graphql/execution/lazy" require "graphql/execution/lookahead" require "graphql/execution/multiplex" require "graphql/execution/errors" module GraphQL module Execution # @api private class Skip < GraphQL::Error; end # Just a singleton for implementing {Query::Context#skip} # @api private SKIP = Skip.new end end graphql-ruby-2.2.17/lib/graphql/execution/000077500000000000000000000000001476434635200204455ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/execution/directive_checks.rb000066400000000000000000000020341476434635200242670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Boolean checks for how an AST node's directives should # influence its execution # @api private module DirectiveChecks SKIP = "skip" INCLUDE = "include" module_function # @return [Boolean] Should this node be included in the query? def include?(directive_ast_nodes, query) directive_ast_nodes.each do |directive_ast_node| name = directive_ast_node.name directive_defn = query.schema.directives[name] case name when SKIP args = query.arguments_for(directive_ast_node, directive_defn) if args[:if] == true return false end when INCLUDE args = query.arguments_for(directive_ast_node, directive_defn) if args[:if] == false return false end else # Undefined directive, or one we don't care about end end true end end end end graphql-ruby-2.2.17/lib/graphql/execution/errors.rb000066400000000000000000000073441476434635200223160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Errors # Register this handler, updating the # internal handler index to maintain least-to-most specific. # # @param error_class [Class] # @param error_handlers [Hash] # @param error_handler [Proc] # @return [void] def self.register_rescue_from(error_class, error_handlers, error_handler) subclasses_handlers = {} this_level_subclasses = [] # During this traversal, do two things: # - Identify any already-registered subclasses of this error class # and gather them up to be inserted _under_ this class # - Find the point in the index where this handler should be inserted # (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered) while (error_handlers) do this_level_subclasses.clear # First, identify already-loaded handlers that belong # _under_ this one. (That is, they're handlers # for subclasses of `error_class`.) error_handlers.each do |err_class, handler| if err_class < error_class subclasses_handlers[err_class] = handler this_level_subclasses << err_class end end # Any handlers that we'll be moving, delete them from this point in the index this_level_subclasses.each do |err_class| error_handlers.delete(err_class) end # See if any keys in this hash are superclasses of this new class: next_index_point = error_handlers.find { |err_class, handler| error_class < err_class } if next_index_point error_handlers = next_index_point[1][:subclass_handlers] else # this new handler doesn't belong to any sub-handlers, # so insert it in the current set of `handlers` break end end # Having found the point at which to insert this handler, # register it and merge any subclass handlers back in at this point. this_class_handlers = error_handlers[error_class] this_class_handlers[:handler] = error_handler this_class_handlers[:subclass_handlers].merge!(subclasses_handlers) nil end # @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited def self.find_handler_for(schema, error_class) handlers = schema.error_handlers[:subclass_handlers] handler = nil while (handlers) do _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class } if next_handler handlers = next_handler[:subclass_handlers] handler = next_handler else # Don't reassign `handler` -- # let the previous assignment carry over outside this block. break end end # check for a handler from a parent class: if schema.superclass.respond_to?(:error_handlers) parent_handler = find_handler_for(schema.superclass, error_class) end # If the inherited handler is more specific than the one defined here, # use it. # If it's a tie (or there is no parent handler), use the one defined here. # If there's an inherited one, but not one defined here, use the inherited one. # Otherwise, there's no handler for this error, return `nil`. if parent_handler && handler && parent_handler[:class] < handler[:class] parent_handler elsif handler handler elsif parent_handler parent_handler else nil end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter.rb000066400000000000000000000162031476434635200233370ustar00rootroot00000000000000# frozen_string_literal: true require "fiber" require "graphql/execution/interpreter/argument_value" require "graphql/execution/interpreter/arguments" require "graphql/execution/interpreter/arguments_cache" require "graphql/execution/interpreter/execution_errors" require "graphql/execution/interpreter/runtime" require "graphql/execution/interpreter/resolve" require "graphql/execution/interpreter/handles_raw_value" module GraphQL module Execution class Interpreter class << self # Used internally to signal that the query shouldn't be executed # @api private NO_OPERATION = GraphQL::EmptyObjects::EMPTY_HASH # @param schema [GraphQL::Schema] # @param queries [Array] # @param context [Hash] # @param max_complexity [Integer, nil] # @return [Array] One result per query def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity) queries = query_options.map do |opts| case opts when Hash schema.query_class.new(schema, nil, **opts) when GraphQL::Query opts else raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})" end end multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity) multiplex.current_trace.execute_multiplex(multiplex: multiplex) do schema = multiplex.schema queries = multiplex.queries lazies_at_depth = Hash.new { |h, k| h[k] = [] } multiplex_analyzers = schema.multiplex_analyzers if multiplex.max_complexity multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity] end schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers) begin # Since this is basically the batching context, # share it for a whole multiplex multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy(deprecation_warning: false).new # Do as much eager evaluation of the query as possible results = [] queries.each_with_index do |query, idx| if query.subscription? && !query.subscription_update? query.context.namespace(:subscriptions)[:events] = [] end multiplex.dataloader.append_job { operation = query.selected_operation result = if operation.nil? || !query.valid? || query.context.errors.any? NO_OPERATION else begin # Although queries in a multiplex _share_ an Interpreter instance, # they also have another item of state, which is private to that query # in particular, assign it here: runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth) query.context.namespace(:interpreter_runtime)[:runtime] = runtime query.current_trace.execute_query(query: query) do runtime.run_eager end rescue GraphQL::ExecutionError => err query.context.errors << err NO_OPERATION end end results[idx] = result } end multiplex.dataloader.run # Then, work through lazy results in a breadth-first way multiplex.dataloader.append_job { query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil queries = multiplex ? multiplex.queries : [query] final_values = queries.map do |query| runtime = query.context.namespace(:interpreter_runtime)[:runtime] # it might not be present if the query has an error runtime ? runtime.final_result : nil end final_values.compact! multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader) end } multiplex.dataloader.run # Then, find all errors and assign the result to the query object results.each_with_index do |data_result, idx| query = queries[idx] if (events = query.context.namespace(:subscriptions)[:events]) && events.any? schema.subscriptions.write_subscription(query, events) end # Assign the result so that it can be accessed in instrumentation query.result_values = if data_result.equal?(NO_OPERATION) if !query.valid? || query.context.errors.any? # A bit weird, but `Query#static_errors` _includes_ `query.context.errors` { "errors" => query.static_errors.map(&:to_h) } else data_result end else result = {} if query.context.errors.any? error_result = query.context.errors.map(&:to_h) result["errors"] = error_result end result["data"] = query.context.namespace(:interpreter_runtime)[:runtime].final_result result end if query.context.namespace?(:__query_result_extensions__) query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__) end # Get the Query::Result, not the Hash results[idx] = query.result end results rescue Exception # TODO rescue at a higher level so it will catch errors in analysis, too # Assign values here so that the query's `@executed` becomes true queries.map { |q| q.result_values ||= {} } raise ensure queries.map { |query| runtime = query.context.namespace(:interpreter_runtime)[:runtime] if runtime runtime.delete_all_interpreter_context end } end end end end class ListResultFailedError < GraphQL::Error def initialize(value:, path:, field:) message = "Failed to build a GraphQL list result for field `#{field.path}` at path `#{path.join(".")}`.\n".dup message << "Expected `#{value.inspect}` (#{value.class}) to implement `.each` to satisfy the GraphQL return type `#{field.type.to_type_signature}`.\n" if field.connection? message << "\nThis field was treated as a Relay-style connection; add `connection: false` to the `field(...)` to disable this behavior." end super(message) end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/000077500000000000000000000000001476434635200230105ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/execution/interpreter/argument_value.rb000066400000000000000000000015731476434635200263610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # A container for metadata regarding arguments present in a GraphQL query. # @see Interpreter::Arguments#argument_values for a hash of these objects. class ArgumentValue def initialize(definition:, value:, default_used:) @definition = definition @value = value @default_used = default_used end # @return [Object] The Ruby-ready value for this Argument attr_reader :value # @return [GraphQL::Schema::Argument] The definition instance for this argument attr_reader :definition # @return [Boolean] `true` if the schema-defined `default_value:` was applied in this case. (No client-provided value was present.) def default_used? @default_used end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/arguments.rb000066400000000000000000000064301476434635200253450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # A wrapper for argument hashes in GraphQL queries. # # This object is immutable so that the runtime code can be sure that # modifications don't leak from one use to another # # @see GraphQL::Query#arguments_for to get access to these objects. class Arguments extend Forwardable include GraphQL::Dig # The Ruby-style arguments hash, ready for a resolver. # This hash is the one used at runtime. # # @return [Hash] attr_reader :keyword_arguments # @param argument_values [nil, Hash{Symbol => ArgumentValue}] # @param keyword_arguments [nil, Hash{Symbol => Object}] def initialize(keyword_arguments: nil, argument_values:) @empty = argument_values.nil? || argument_values.empty? # This is only present when `extras` have been merged in: if keyword_arguments # This is a little crazy. We expect the `:argument_details` extra to _include extras_, # but the object isn't created until _after_ extras are put together. # So, we have to use a special flag here to say, "at the last minute, add yourself to the keyword args." # # Otherwise: # - We can't access the final Arguments instance _while_ we're preparing extras # - After we _can_ access it, it's frozen, so we can't add anything. # # So, this flag gives us a chance to sneak it in before freezing, _and_ while we have access # to the new Arguments instance itself. if keyword_arguments[:argument_details] == :__arguments_add_self keyword_arguments[:argument_details] = self end @keyword_arguments = keyword_arguments.freeze elsif !@empty @keyword_arguments = {} argument_values.each do |name, arg_val| @keyword_arguments[name] = arg_val.value end @keyword_arguments.freeze else @keyword_arguments = NO_ARGS end @argument_values = argument_values ? argument_values.freeze : NO_ARGS freeze end # @return [Hash{Symbol => ArgumentValue}] attr_reader :argument_values def empty? @empty end def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values, :size, :to_h def_delegators :argument_values, :each_value def inspect "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>" end # Create a new arguments instance which includes these extras. # # This is called by the runtime to implement field `extras: [...]` # # @param extra_args [Hash Object>] # @return [Interpreter::Arguments] # @api private def merge_extras(extra_args) self.class.new( argument_values: argument_values, keyword_arguments: keyword_arguments.merge(extra_args) ) end NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH EMPTY = self.new(argument_values: nil, keyword_arguments: NO_ARGS).freeze end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/arguments_cache.rb000066400000000000000000000076421476434635200264760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter class ArgumentsCache def initialize(query) @query = query @dataloader = query.context.dataloader @storage = Hash.new do |h, argument_owner| args_by_parent = if argument_owner.arguments_statically_coercible? shared_values_cache = {} Hash.new do |h2, ignored_parent_object| h2[ignored_parent_object] = shared_values_cache end else Hash.new do |h2, parent_object| args_by_node = {} args_by_node.compare_by_identity h2[parent_object] = args_by_node end end args_by_parent.compare_by_identity h[argument_owner] = args_by_parent end @storage.compare_by_identity end def fetch(ast_node, argument_owner, parent_object) # This runs eagerly if no block is given @storage[argument_owner][parent_object][ast_node] ||= begin args_hash = self.class.prepare_args_hash(@query, ast_node) kwarg_arguments = argument_owner.coerce_arguments(parent_object, args_hash, @query.context) @query.after_lazy(kwarg_arguments) do |resolved_args| @storage[argument_owner][parent_object][ast_node] = resolved_args end end end # @yield [Interpreter::Arguments, Lazy] The finally-loaded arguments def dataload_for(ast_node, argument_owner, parent_object, &block) # First, normalize all AST or Ruby values to a plain Ruby hash arg_storage = @storage[argument_owner][parent_object] if (args = arg_storage[ast_node]) yield(args) else args_hash = self.class.prepare_args_hash(@query, ast_node) argument_owner.coerce_arguments(parent_object, args_hash, @query.context) do |resolved_args| arg_storage[ast_node] = resolved_args yield(resolved_args) end end nil end private NO_ARGUMENTS = GraphQL::EmptyObjects::EMPTY_HASH NO_VALUE_GIVEN = NOT_CONFIGURED def self.prepare_args_hash(query, ast_arg_or_hash_or_value) case ast_arg_or_hash_or_value when Hash if ast_arg_or_hash_or_value.empty? return NO_ARGUMENTS end args_hash = {} ast_arg_or_hash_or_value.each do |k, v| args_hash[k] = prepare_args_hash(query, v) end args_hash when Array ast_arg_or_hash_or_value.map { |v| prepare_args_hash(query, v) } when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive if ast_arg_or_hash_or_value.arguments.empty? # rubocop:disable Development/ContextIsPassedCop -- AST-related return NO_ARGUMENTS end args_hash = {} ast_arg_or_hash_or_value.arguments.each do |arg| # rubocop:disable Development/ContextIsPassedCop -- AST-related v = prepare_args_hash(query, arg.value) if v != NO_VALUE_GIVEN args_hash[arg.name] = v end end args_hash when GraphQL::Language::Nodes::VariableIdentifier if query.variables.key?(ast_arg_or_hash_or_value.name) variable_value = query.variables[ast_arg_or_hash_or_value.name] prepare_args_hash(query, variable_value) else NO_VALUE_GIVEN end when GraphQL::Language::Nodes::Enum ast_arg_or_hash_or_value.name when GraphQL::Language::Nodes::NullValue nil else ast_arg_or_hash_or_value end end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/execution_errors.rb000066400000000000000000000013471476434635200267410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter class ExecutionErrors def initialize(ctx, ast_node, path) @context = ctx @ast_node = ast_node @path = path end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end err.ast_node ||= @ast_node err.path ||= @path @context.add_error(err) end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/handles_raw_value.rb000066400000000000000000000004401476434635200270160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # Wrapper for raw values class RawValue def initialize(obj = nil) @object = obj end def resolve @object end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/resolve.rb000066400000000000000000000071251476434635200250210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter module Resolve # Continue field results in `results` until there's nothing else to continue. # @return [void] def self.resolve_all(results, dataloader) dataloader.append_job { resolve(results, dataloader) } nil end def self.resolve_each_depth(lazies_at_depth, dataloader) depths = lazies_at_depth.keys depths.sort! next_depth = depths.first if next_depth lazies = lazies_at_depth[next_depth] lazies_at_depth.delete(next_depth) if lazies.any? dataloader.append_job { lazies.each(&:value) # resolve these Lazy instances } # Run lazies _and_ dataloader, see if more are enqueued dataloader.run resolve_each_depth(lazies_at_depth, dataloader) end end nil end # After getting `results` back from an interpreter evaluation, # continue it until you get a response-ready Ruby value. # # `results` is one level of _depth_ of a query or multiplex. # # Resolve all lazy values in that depth before moving on # to the next level. # # It's assumed that the lazies will # return {Lazy} instances if there's more work to be done, # or return {Hash}/{Array} if the query should be continued. # # @return [void] def self.resolve(results, dataloader) # There might be pending jobs here that _will_ write lazies # into the result hash. We should run them out, so we # can be sure that all lazies will be present in the result hashes. # A better implementation would somehow interleave (or unify) # these approaches. dataloader.run next_results = [] while results.any? result_value = results.shift if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash) results.concat(result_value.values) next elsif result_value.is_a?(Runtime::GraphQLResultArray) results.concat(result_value.values) next elsif result_value.is_a?(Array) results.concat(result_value) next elsif result_value.is_a?(Lazy) loaded_value = result_value.value if loaded_value.is_a?(Lazy) # Since this field returned another lazy, # add it to the same queue results << loaded_value elsif loaded_value.is_a?(Runtime::GraphQLResultHash) || loaded_value.is_a?(Runtime::GraphQLResultArray) || loaded_value.is_a?(Hash) || loaded_value.is_a?(Array) # Add these values in wholesale -- # they might be modified by later work in the dataloader. next_results << loaded_value end end end if next_results.any? # Any pending data loader jobs may populate the # resutl arrays or result hashes accumulated in # `next_results``. Run those **to completion** # before continuing to resolve `next_results`. # (Just `.append_job` doesn't work if any pending # jobs require multiple passes.) dataloader.run dataloader.append_job { resolve(next_results, dataloader) } end nil end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/runtime.rb000066400000000000000000001230071476434635200250230ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/interpreter/runtime/graphql_result" module GraphQL module Execution class Interpreter # I think it would be even better if we could somehow make # `continue_field` not recursive. "Trampolining" it somehow. # # @api private class Runtime class CurrentState def initialize @current_object = nil @current_field = nil @current_arguments = nil @current_result_name = nil @current_result = nil @was_authorized_by_scope_items = nil end attr_accessor :current_result, :current_result_name, :current_arguments, :current_field, :current_object, :was_authorized_by_scope_items end # @return [GraphQL::Query] attr_reader :query # @return [Class] attr_reader :schema # @return [GraphQL::Query::Context] attr_reader :context def initialize(query:, lazies_at_depth:) @query = query @current_trace = query.current_trace @dataloader = query.multiplex.dataloader @lazies_at_depth = lazies_at_depth @schema = query.schema @context = query.context @response = GraphQLResultHash.new(nil, nil, false) # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve` @runtime_directive_names = [] noop_resolve_owner = GraphQL::Schema::Directive.singleton_class @schema_directives = schema.directives @schema_directives.each do |name, dir_defn| if dir_defn.method(:resolve).owner != noop_resolve_owner @runtime_directive_names << name end end # { Class => Boolean } @lazy_cache = {} @lazy_cache.compare_by_identity end def final_result @response && @response.graphql_result_data end def inspect "#<#{self.class.name} response=#{@response.inspect}>" end def tap_or_each(obj_or_array) if obj_or_array.is_a?(Array) obj_or_array.each do |item| yield(item, true) end else yield(obj_or_array, false) end end # This _begins_ the execution. Some deferred work # might be stored up in lazies. # @return [void] def run_eager root_operation = query.selected_operation root_op_type = root_operation.operation_type || "query" root_type = schema.root_type_for_operation(root_op_type) st = get_current_runtime_state st.current_object = query.root_value st.current_result = @response runtime_object = root_type.wrap(query.root_value, context) runtime_object = schema.sync_lazy(runtime_object) if runtime_object.nil? # Root .authorized? returned false. @response = nil else call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections) # This is kind of a hack -- `gathered_selections` is an Array if any of the selections # require isolation during execution (because of runtime directives). In that case, # make a new, isolated result hash for writing the result into. (That isolated response # is eventually merged back into the main response) # # Otherwise, `gathered_selections` is a hash of selections which can be # directly evaluated and the results can be written right into the main response hash. tap_or_each(gathered_selections) do |selections, is_selection_array| if is_selection_array selection_response = GraphQLResultHash.new(nil, nil, false) final_response = @response else selection_response = @response final_response = nil end @dataloader.append_job { st = get_current_runtime_state st.current_object = query.root_value st.current_result_name = nil st.current_result = selection_response # This is a less-frequent case; use a fast check since it's often not there. if (directives = selections[:graphql_directives]) selections.delete(:graphql_directives) end call_method_on_directives(:resolve, runtime_object, directives) do evaluate_selections( runtime_object, root_type, root_op_type == "mutation", selections, selection_response, final_response, nil, st, ) end } end end end nil end def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {}) selections.each do |node| # Skip gathering this if the directive says so if !directives_include?(node, owner_object, owner_type) next end if node.is_a?(GraphQL::Language::Nodes::Field) response_key = node.alias || node.name selections = selections_by_name[response_key] # if there was already a selection of this field, # use an array to hold all selections, # otherise, use the single node to represent the selection if selections # This field was already selected at least once, # add this node to the list of selections s = Array(selections) s << node selections_by_name[response_key] = s else # No selection was found for this field yet selections_by_name[response_key] = node end else # This is an InlineFragment or a FragmentSpread if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) } next_selections = {} next_selections[:graphql_directives] = node.directives if selections_to_run selections_to_run << next_selections else selections_to_run = [] selections_to_run << selections_by_name selections_to_run << next_selections end else next_selections = selections_by_name end case node when GraphQL::Language::Nodes::InlineFragment if node.type type_defn = schema.get_type(node.type.name, context) if query.warden.possible_types(type_defn).include?(owner_type) result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections) if !result.equal?(next_selections) selections_to_run = result end end else # it's an untyped fragment, definitely continue result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections) if !result.equal?(next_selections) selections_to_run = result end end when GraphQL::Language::Nodes::FragmentSpread fragment_def = query.fragments[node.name] type_defn = query.get_type(fragment_def.type.name) if query.warden.possible_types(type_defn).include?(owner_type) result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections) if !result.equal?(next_selections) selections_to_run = result end end else raise "Invariant: unexpected selection class: #{node.class}" end end end selections_to_run || selections_by_name end NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH # @return [void] def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists finished_jobs = 0 enqueued_jobs = gathered_selections.size gathered_selections.each do |result_name, field_ast_nodes_or_ast_node| @dataloader.append_job { runtime_state = get_current_runtime_state evaluate_selection( result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object, runtime_state ) finished_jobs += 1 if target_result && finished_jobs == enqueued_jobs selections_result.merge_into(target_result) end } # Field resolution may pause the fiber, # so it wouldn't get to the `Resolve` call that happens below. # So instead trigger a run from this outer context. if is_eager_selection @dataloader.clear_cache @dataloader.run @dataloader.clear_cache end end selections_result end # @return [void] def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists return if dead_result?(selections_result) # As a performance optimization, the hash key will be a `Node` if # there's only one selection of the field. But if there are multiple # selections of the field, it will be an Array of nodes if field_ast_nodes_or_ast_node.is_a?(Array) field_ast_nodes = field_ast_nodes_or_ast_node ast_node = field_ast_nodes.first else field_ast_nodes = nil ast_node = field_ast_nodes_or_ast_node end field_name = ast_node.name field_defn = query.warden.get_field(owner_type, field_name) # Set this before calling `run_with_directives`, so that the directive can have the latest path runtime_state.current_field = field_defn runtime_state.current_result = selections_result runtime_state.current_result_name = result_name if field_defn.dynamic_introspection owner_object = field_defn.owner.wrap(owner_object, context) end return_type = field_defn.type if !field_defn.any_arguments? resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY if field_defn.extras.size == 0 evaluate_selection_with_resolved_keyword_args( NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state ) else evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state) end else @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments| runtime_state = get_current_runtime_state # This might be in a different fiber evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state) end end end def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, runtime_state) # rubocop:disable Metrics/ParameterLists after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state| return_type_non_null = return_type.non_null? if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError) continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result) next end kwarg_arguments = if field_defn.extras.empty? if resolved_arguments.empty? # We can avoid allocating the `{ Symbol => Object }` hash in this case NO_ARGS else resolved_arguments.keyword_arguments end else # Bundle up the extras, then make a new arguments instance # that includes the extras, too. extra_args = {} field_defn.extras.each do |extra| case extra when :ast_node extra_args[:ast_node] = ast_node when :execution_errors extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path) when :path extra_args[:path] = current_path when :lookahead if !field_ast_nodes field_ast_nodes = [ast_node] end extra_args[:lookahead] = Execution::Lookahead.new( query: query, ast_nodes: field_ast_nodes, field: field_defn, ) when :argument_details # Use this flag to tell Interpreter::Arguments to add itself # to the keyword args hash _before_ freezing everything. extra_args[:argument_details] = :__arguments_add_self when :parent extra_args[:parent] = parent_object else extra_args[extra] = field_defn.fetch_extra(extra, context) end end if extra_args.any? resolved_arguments = resolved_arguments.merge_extras(extra_args) end resolved_arguments.keyword_arguments end evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state) end end def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state) # rubocop:disable Metrics/ParameterLists runtime_state.current_field = field_defn runtime_state.current_object = object runtime_state.current_arguments = resolved_arguments runtime_state.current_result_name = result_name runtime_state.current_result = selection_result # Optimize for the case that field is selected only once if field_ast_nodes.nil? || field_ast_nodes.size == 1 next_selections = ast_node.selections directives = ast_node.directives else next_selections = [] directives = [] field_ast_nodes.each { |f| next_selections.concat(f.selections) directives.concat(f.directives) } end field_result = call_method_on_directives(:resolve, object, directives) do if directives.any? # This might be executed in a different context; reset this info runtime_state = get_current_runtime_state runtime_state.current_field = field_defn runtime_state.current_object = object runtime_state.current_arguments = resolved_arguments runtime_state.current_result_name = result_name runtime_state.current_result = selection_result end # Actually call the field resolver and capture the result app_result = begin @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do field_defn.resolve(object, kwarg_arguments, context) end rescue GraphQL::ExecutionError => err err rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end end after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state| continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result) if HALT != continue_value was_scoped = runtime_state.was_authorized_by_scope_items runtime_state.was_authorized_by_scope_items = nil continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state) end end end # If this field is a root mutation field, immediately resolve # all of its child fields before moving on to the next root mutation field. # (Subselections of this mutation will still be resolved level-by-level.) if is_eager_field Interpreter::Resolve.resolve_all([field_result], @dataloader) else # Return this from `after_lazy` because it might be another lazy that needs to be resolved field_result end end def dead_result?(selection_result) selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead) end def set_result(selection_result, result_name, value, is_child_result, is_non_null) if !dead_result?(selection_result) if value.nil? && is_non_null # This is an invalid nil that should be propagated # One caller of this method passes a block, # namely when application code returns a `nil` to GraphQL and it doesn't belong there. # The other possibility for reaching here is when a field returns an ExecutionError, so we write # `nil` to the response, not knowing whether it's an invalid `nil` or not. # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.) # TODO the code is trying to tell me something. yield if block_given? parent = selection_result.graphql_parent if parent.nil? # This is a top-level result hash @response = nil else name_in_parent = selection_result.graphql_result_name is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent set_result(parent, name_in_parent, nil, false, is_non_null_in_parent) set_graphql_dead(selection_result) end elsif is_child_result selection_result.set_child_result(result_name, value) else selection_result.set_leaf(result_name, value) end end end # Mark this node and any already-registered children as dead, # so that it accepts no more writes. def set_graphql_dead(selection_result) case selection_result when GraphQLResultArray selection_result.graphql_dead = true selection_result.values.each { |v| set_graphql_dead(v) } when GraphQLResultHash selection_result.graphql_dead = true selection_result.each { |k, v| set_graphql_dead(v) } else # It's a scalar, no way to mark it dead. end end def current_path st = get_current_runtime_state result = st.current_result path = result && result.path if path && (rn = st.current_result_name) path = path.dup path.push(rn) end path end HALT = Object.new def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists case value when nil if is_non_null set_result(selection_result, result_name, nil, false, is_non_null) do # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.) err = parent_type::InvalidNullError.new(parent_type, field, value) schema.type_error(err, context) end else set_result(selection_result, result_name, nil, false, is_non_null) end HALT when GraphQL::Error # Handle these cases inside a single `when` # to avoid the overhead of checking three different classes # every time. if value.is_a?(GraphQL::ExecutionError) if selection_result.nil? || !dead_result?(selection_result) value.path ||= current_path value.ast_node ||= ast_node context.errors << value if selection_result set_result(selection_result, result_name, nil, false, is_non_null) end end HALT elsif value.is_a?(GraphQL::UnauthorizedFieldError) value.field ||= field # this hook might raise & crash, or it might return # a replacement value next_value = begin schema.unauthorized_field(value) rescue GraphQL::ExecutionError => err err end continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result) elsif value.is_a?(GraphQL::UnauthorizedError) # this hook might raise & crash, or it might return # a replacement value next_value = begin schema.unauthorized_object(value) rescue GraphQL::ExecutionError => err err end continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result) elsif GraphQL::Execution::SKIP == value # It's possible a lazy was already written here case selection_result when GraphQLResultHash selection_result.delete(result_name) when GraphQLResultArray selection_result.graphql_skip_at(result_name) when nil # this can happen with directives else raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})" end HALT else # What could this actually _be_? Anyhow, # preserve the default behavior of doing nothing with it. value end when Array # It's an array full of execution errors; add them all. if value.any? && value.all?(GraphQL::ExecutionError) list_type_at_all = (field && (field.type.list?)) if selection_result.nil? || !dead_result?(selection_result) value.each_with_index do |error, index| error.ast_node ||= ast_node error.path ||= current_path + (list_type_at_all ? [index] : []) context.errors << error end if selection_result if list_type_at_all result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v } set_result(selection_result, result_name, result_without_errors, false, is_non_null) else set_result(selection_result, result_name, nil, false, is_non_null) end end end HALT else value end when GraphQL::Execution::Interpreter::RawValue # Write raw value directly to the response without resolving nested objects set_result(selection_result, result_name, value.resolve, false, is_non_null) HALT else value end end # The resolver for `field` returned `value`. Continue to execute the query, # treating `value` as `type` (probably the return type of the field). # # Use `next_selections` to resolve object fields, if there are any. # # Location information from `path` and `ast_node`. # # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists if current_type.non_null? current_type = current_type.of_type is_non_null = true end case current_type.kind.name when "SCALAR", "ENUM" r = begin current_type.coerce_result(value, context) rescue StandardError => err schema.handle_or_reraise(context, err) end set_result(selection_result, result_name, r, false, is_non_null) r when "UNION", "INTERFACE" resolved_type_or_lazy = resolve_type(current_type, value) after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state| if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2 resolved_type, resolved_value = resolved_type_result else resolved_type = resolved_type_result resolved_value = value end possible_types = query.possible_types(current_type) if !possible_types.include?(resolved_type) parent_type = field.owner_type err_class = current_type::UnresolvedTypeError type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types) schema.type_error(type_error, context) set_result(selection_result, result_name, nil, false, is_non_null) nil else continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) end end when "OBJECT" object_proxy = begin was_scoped ? current_type.wrap_scoped(value, context) : current_type.wrap(value, context) rescue GraphQL::ExecutionError => err err end after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state| continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result) if HALT != continue_value response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null) set_result(selection_result, result_name, response_hash, true, is_non_null) gathered_selections = gather_selections(continue_value, current_type, next_selections) # There are two possibilities for `gathered_selections`: # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution). # This case is handled below, and the result can be written right into the main `response_hash` above. # In this case, `gathered_selections` is a hash of selections. # 2. Some selections of this object have runtime directives that may or may not modify execution. # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation. # (Technically, it's possible that one of those entries _doesn't_ require isolation.) tap_or_each(gathered_selections) do |selections, is_selection_array| if is_selection_array this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null) final_result = response_hash else this_result = response_hash final_result = nil end # reset this mutable state # Unset `result_name` here because it's already included in the new response hash runtime_state.current_object = continue_value runtime_state.current_result_name = nil runtime_state.current_result = this_result # This is a less-frequent case; use a fast check since it's often not there. if (directives = selections[:graphql_directives]) selections.delete(:graphql_directives) end call_method_on_directives(:resolve, continue_value, directives) do evaluate_selections( continue_value, current_type, false, selections, this_result, final_result, owner_object.object, runtime_state, ) this_result end end end end when "LIST" inner_type = current_type.of_type # This is true for objects, unions, and interfaces use_dataloader_job = !inner_type.unwrap.kind.input? inner_type_non_null = inner_type.non_null? response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null) set_result(selection_result, result_name, response_list, true, is_non_null) idx = nil list_value = begin value.each do |inner_value| idx ||= 0 this_idx = idx idx += 1 if use_dataloader_job @dataloader.append_job do resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state) end else resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state) end end response_list rescue NoMethodError => err # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.) if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true) # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug. raise ListResultFailedError.new(value: value, field: field, path: current_path) else # This was some other NoMethodError -- let it bubble to reveal the real error. raise end rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err ex_err rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end end # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set) error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null? continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result) else raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})" end end def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists runtime_state.current_result_name = this_idx runtime_state.current_result = response_list call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do # This will update `response_list` with the lazy after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state| continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list) if HALT != continue_value continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state) end end end end def call_method_on_directives(method_name, object, directives, &block) return yield if directives.nil? || directives.empty? run_directive(method_name, object, directives, 0, &block) end def run_directive(method_name, object, directives, idx, &block) dir_node = directives[idx] if !dir_node yield else dir_defn = @schema_directives.fetch(dir_node.name) raw_dir_args = arguments(nil, dir_defn, dir_node) dir_args = continue_value( raw_dir_args, # value dir_defn, # parent_type nil, # field false, # is_non_null dir_node, # ast_node nil, # result_name nil, # selection_result ) if dir_args == HALT nil else dir_defn.public_send(method_name, object, dir_args, context) do run_directive(method_name, object, directives, idx + 1, &block) end end end end # Check {Schema::Directive.include?} for each directive that's present def directives_include?(node, graphql_object, parent_type) node.directives.each do |dir_node| dir_defn = @schema_directives.fetch(dir_node.name) args = arguments(graphql_object, dir_defn, dir_node) if !dir_defn.include?(graphql_object, args, context) return false end end true end def get_current_runtime_state current_state = Thread.current[:__graphql_runtime_info] ||= begin per_query_state = {} per_query_state.compare_by_identity per_query_state end current_state[@query] ||= CurrentState.new end def minimal_after_lazy(value, &block) if lazy?(value) GraphQL::Execution::Lazy.new do result = @schema.sync_lazy(value) # The returned result might also be lazy, so check it, too minimal_after_lazy(result, &block) end else yield(value) end end # @param obj [Object] Some user-returned value that may want to be batched # @param field [GraphQL::Schema::Field] # @param eager [Boolean] Set to `true` for mutation root fields only # @param trace [Boolean] If `false`, don't wrap this with field tracing # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it. def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block) if lazy?(lazy_obj) orig_result = result was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items lazy = GraphQL::Execution::Lazy.new(field: field) do # This block might be called in a new fiber; # In that case, this will initialize a new state # to avoid conflicting with the parent fiber. runtime_state = get_current_runtime_state runtime_state.current_object = owner_object runtime_state.current_field = field runtime_state.current_arguments = arguments runtime_state.current_result_name = result_name runtime_state.current_result = orig_result runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items # Wrap the execution of _this_ method with tracing, # but don't wrap the continuation below inner_obj = begin if trace @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do schema.sync_lazy(lazy_obj) end else schema.sync_lazy(lazy_obj) end rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err ex_err rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end end yield(inner_obj, runtime_state) end if eager lazy.value else set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here current_depth = 0 while result current_depth += 1 result = result.graphql_parent end @lazies_at_depth[current_depth] << lazy lazy end else # Don't need to reset state here because it _wasn't_ lazy. yield(lazy_obj, runtime_state) end end def arguments(graphql_object, arg_owner, ast_node) if arg_owner.arguments_statically_coercible? query.arguments_for(ast_node, arg_owner) else # The arguments must be prepared in the context of the given object query.arguments_for(ast_node, arg_owner, parent_object: graphql_object) end end def delete_all_interpreter_context per_query_state = Thread.current[:__graphql_runtime_info] if per_query_state per_query_state.delete(@query) if per_query_state.size == 0 Thread.current[:__graphql_runtime_info] = nil end end nil end def resolve_type(type, value) resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do query.resolve_type(type, value) end if lazy?(resolved_type) GraphQL::Execution::Lazy.new do @current_trace.resolve_type_lazy(query: query, type: type, object: value) do schema.sync_lazy(resolved_type) end end else [resolved_type, resolved_value] end end def lazy?(object) obj_class = object.class is_lazy = @lazy_cache[obj_class] if is_lazy.nil? is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object) end is_lazy end end end end end graphql-ruby-2.2.17/lib/graphql/execution/interpreter/runtime/000077500000000000000000000000001476434635200244735ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/execution/interpreter/runtime/graphql_result.rb000066400000000000000000000142101476434635200300520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter class Runtime module GraphQLResult def initialize(result_name, parent_result, is_non_null_in_parent) @graphql_parent = parent_result if parent_result && parent_result.graphql_dead @graphql_dead = true end @graphql_result_name = result_name @graphql_is_non_null_in_parent = is_non_null_in_parent # Jump through some hoops to avoid creating this duplicate storage if at all possible. @graphql_metadata = nil end def path @path ||= build_path([]) end def build_path(path_array) graphql_result_name && path_array.unshift(graphql_result_name) @graphql_parent ? @graphql_parent.build_path(path_array) : path_array end attr_accessor :graphql_dead attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects) attr_accessor :graphql_result_data end class GraphQLResultHash def initialize(_result_name, _parent_result, _is_non_null_in_parent) super @graphql_result_data = {} end include GraphQLResult attr_accessor :graphql_merged_into def set_leaf(key, value) # This is a hack. # Basically, this object is merged into the root-level result at some point. # But the problem is, some lazies are created whose closures retain reference to _this_ # object. When those lazies are resolved, they cause an update to this object. # # In order to return a proper top-level result, we have to update that top-level result object. # In order to return a proper partial result (eg, for a directive), we have to update this object, too. # Yowza. if (t = @graphql_merged_into) t.set_leaf(key, value) end @graphql_result_data[key] = value # keep this up-to-date if it's been initialized @graphql_metadata && @graphql_metadata[key] = value value end def set_child_result(key, value) if (t = @graphql_merged_into) t.set_child_result(key, value) end @graphql_result_data[key] = value.graphql_result_data # If we encounter some part of this response that requires metadata tracking, # then create the metadata hash if necessary. It will be kept up-to-date after this. (@graphql_metadata ||= @graphql_result_data.dup)[key] = value value end def delete(key) @graphql_metadata && @graphql_metadata.delete(key) @graphql_result_data.delete(key) end def each (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) } end def values (@graphql_metadata || @graphql_result_data).values end def key?(k) @graphql_result_data.key?(k) end def [](k) (@graphql_metadata || @graphql_result_data)[k] end def merge_into(into_result) self.each do |key, value| case value when GraphQLResultHash next_into = into_result[key] if next_into value.merge_into(next_into) else into_result.set_child_result(key, value) end when GraphQLResultArray # There's no special handling of arrays because currently, there's no way to split the execution # of a list over several concurrent flows. into_result.set_child_result(key, value) else # We have to assume that, since this passed the `fields_will_merge` selection, # that the old and new values are the same. into_result.set_leaf(key, value) end end @graphql_merged_into = into_result end end class GraphQLResultArray include GraphQLResult def initialize(_result_name, _parent_result, _is_non_null_in_parent) super @graphql_result_data = [] end def graphql_skip_at(index) # Mark this index as dead. It's tricky because some indices may already be storing # `Lazy`s. So the runtime is still holding indexes _before_ skipping, # this object has to coordinate incoming writes to account for any already-skipped indices. @skip_indices ||= [] @skip_indices << index offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index} delete_at_index = index - offset_by @graphql_metadata && @graphql_metadata.delete_at(delete_at_index) @graphql_result_data.delete_at(delete_at_index) end def set_leaf(idx, value) if @skip_indices offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx } idx -= offset_by end @graphql_result_data[idx] = value @graphql_metadata && @graphql_metadata[idx] = value value end def set_child_result(idx, value) if @skip_indices offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx } idx -= offset_by end @graphql_result_data[idx] = value.graphql_result_data # If we encounter some part of this response that requires metadata tracking, # then create the metadata hash if necessary. It will be kept up-to-date after this. (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value value end def values (@graphql_metadata || @graphql_result_data) end end end end end end graphql-ruby-2.2.17/lib/graphql/execution/lazy.rb000066400000000000000000000043071476434635200217550ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/lazy/lazy_method_map" module GraphQL module Execution # This wraps a value which is available, but not yet calculated, like a promise or future. # # Calling `#value` will trigger calculation & return the "lazy" value. # # This is an itty-bitty promise-like object, with key differences: # - It has only two states, not-resolved and resolved # - It has no error-catching functionality # @api private class Lazy attr_reader :field # Create a {Lazy} which will get its inner value by calling the block # @param field [GraphQL::Schema::Field] # @param get_value_func [Proc] a block to get the inner value (later) def initialize(field: nil, &get_value_func) @get_value_func = get_value_func @resolved = false @field = field end # @return [Object] The wrapped value, calling the lazy block if necessary def value if !@resolved @resolved = true v = @get_value_func.call if v.is_a?(Lazy) v = v.value end @value = v end # `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance # (fewer clauses in a hot `case` block), but now it requires special handling here. # I think it's still worth it for the performance win, but if the number of special # cases grows, then maybe it's worth rethinking somehow. if @value.is_a?(StandardError) && @value != GraphQL::Execution::SKIP raise @value else @value end end # @return [Lazy] A {Lazy} whose value depends on another {Lazy}, plus any transformations in `block` def then self.class.new { yield(value) } end # @param lazies [Array] Maybe-lazy objects # @return [Lazy] A lazy which will sync all of `lazies` def self.all(lazies) self.new { lazies.map { |l| l.is_a?(Lazy) ? l.value : l } } end # This can be used for fields which _had no_ lazy results # @api private NullResult = Lazy.new(){} NullResult.value end end end graphql-ruby-2.2.17/lib/graphql/execution/lazy/000077500000000000000000000000001476434635200214245ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/execution/lazy/lazy_method_map.rb000066400000000000000000000055771476434635200251430ustar00rootroot00000000000000# frozen_string_literal: true require 'thread' begin require 'concurrent' rescue LoadError # no problem, we'll fallback to our own map end module GraphQL module Execution class Lazy # {GraphQL::Schema} uses this to match returned values to lazy resolution methods. # Methods may be registered for classes, they apply to its subclasses also. # The result of this lookup is cached for future resolutions. # Instances of this class are thread-safe. # @api private # @see {Schema#lazy?} looks up values from this map class LazyMethodMap def initialize(use_concurrent: defined?(Concurrent::Map)) @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new end def initialize_copy(other) @storage = other.storage.dup end # @param lazy_class [Class] A class which represents a lazy value (subclasses may also be used) # @param lazy_value_method [Symbol] The method to call on this class to get its value def set(lazy_class, lazy_value_method) @storage[lazy_class] = lazy_value_method end # @param value [Object] an object which may have a `lazy_value_method` registered for its class or superclasses # @return [Symbol, nil] The `lazy_value_method` for this object, or nil def get(value) @storage.compute_if_absent(value.class) { find_superclass_method(value.class) } end def each @storage.each_pair { |k, v| yield(k, v) } end protected attr_reader :storage private def find_superclass_method(value_class) @storage.each_pair { |lazy_class, lazy_value_method| return lazy_value_method if value_class < lazy_class } nil end # Mock the Concurrent::Map API class ConcurrentishMap extend Forwardable # Technically this should be under the mutex too, # but I know it's only used when the lock is already acquired. def_delegators :@storage, :each_pair, :size def initialize @semaphore = Mutex.new # Access to this hash must always be managed by the mutex # since it may be modified at runtime @storage = {} end def []=(key, value) @semaphore.synchronize { @storage[key] = value } end def compute_if_absent(key) @semaphore.synchronize { @storage.fetch(key) { @storage[key] = yield } } end def initialize_copy(other) @semaphore = Mutex.new @storage = other.copy_storage end protected def copy_storage @semaphore.synchronize { @storage.dup } end end end end end end graphql-ruby-2.2.17/lib/graphql/execution/lookahead.rb000066400000000000000000000357701476434635200227350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Lookahead creates a uniform interface to inspect the forthcoming selections. # # It assumes that the AST it's working with is valid. (So, it's safe to use # during execution, but if you're using it directly, be sure to validate first.) # # A field may get access to its lookahead by adding `extras: [:lookahead]` # to its configuration. # # @example looking ahead in a field # field :articles, [Types::Article], null: false, # extras: [:lookahead] # # # For example, imagine a faster database call # # may be issued when only some fields are requested. # # # # Imagine that _full_ fetch must be made to satisfy `fullContent`, # # we can look ahead to see if we need that field. If we do, # # we make the expensive database call instead of the cheap one. # def articles(lookahead:) # if lookahead.selects?(:full_content) # fetch_full_articles(object) # else # fetch_preview_articles(object) # end # end class Lookahead # @param query [GraphQL::Query] # @param ast_nodes [Array, Array] # @param field [GraphQL::Schema::Field] if `ast_nodes` are fields, this is the field definition matching those nodes # @param root_type [Class] if `ast_nodes` are operation definition, this is the root type for that operation def initialize(query:, ast_nodes:, field: nil, root_type: nil, owner_type: nil) @ast_nodes = ast_nodes.freeze @field = field @root_type = root_type @query = query @selected_type = @field ? @field.type.unwrap : root_type @owner_type = owner_type end # @return [Array] attr_reader :ast_nodes # @return [GraphQL::Schema::Field] attr_reader :field # @return [GraphQL::Schema::Object, GraphQL::Schema::Union, GraphQL::Schema::Interface] attr_reader :owner_type # @return [Hash] def arguments if defined?(@arguments) @arguments else @arguments = if @field @query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args| args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args end else nil end end end # True if this node has a selection on `field_name`. # If `field_name` is a String, it is treated as a GraphQL-style (camelized) # field name and used verbatim. If `field_name` is a Symbol, it is # treated as a Ruby-style (underscored) name and camelized before comparing. # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in the next selection. This method will return false # if any of the given `arguments:` are not present and matching in the next selection. # (But, the next selection may contain _more_ than the given arguments.) # @param field_name [String, Symbol] # @param arguments [Hash] Arguments which must match in the selection # @return [Boolean] def selects?(field_name, selected_type: @selected_type, arguments: nil) selection(field_name, selected_type: selected_type, arguments: arguments).selected? end # True if this node has a selection with alias matching `alias_name`. # If `alias_name` is a String, it is treated as a GraphQL-style (camelized) # field name and used verbatim. If `alias_name` is a Symbol, it is # treated as a Ruby-style (underscored) name and camelized before comparing. # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in the next selection. This method will return false # if any of the given `arguments:` are not present and matching in the next selection. # (But, the next selection may contain _more_ than the given arguments.) # @param alias_name [String, Symbol] # @param arguments [Hash] Arguments which must match in the selection # @return [Boolean] def selects_alias?(alias_name, arguments: nil) alias_selection(alias_name, arguments: arguments).selected? end # @return [Boolean] True if this lookahead represents a field that was requested def selected? true end # Like {#selects?}, but can be used for chaining. # It returns a null object (check with {#selected?}) # @param field_name [String, Symbol] # @return [GraphQL::Execution::Lookahead] def selection(field_name, selected_type: @selected_type, arguments: nil) next_field_defn = case field_name when String @query.get_field(selected_type, field_name) when Symbol # Try to avoid the `.to_s` below, if possible all_fields = if selected_type.kind.fields? @query.warden.fields(selected_type) else # Handle unions by checking possible @query.warden .possible_types(selected_type) .map { |t| @query.warden.fields(t) } .tap(&:flatten!) end if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name }) match_by_orig_name else # Symbol#name is only present on 3.0+ sym_s = field_name.respond_to?(:name) ? field_name.name : field_name.to_s guessed_name = Schema::Member::BuildType.camelize(sym_s) @query.get_field(selected_type, guessed_name) end end lookahead_for_selection(next_field_defn, selected_type, arguments) end # Like {#selection}, but for aliases. # It returns a null object (check with {#selected?}) # @return [GraphQL::Execution::Lookahead] def alias_selection(alias_name, selected_type: @selected_type, arguments: nil) alias_cache_key = [alias_name, arguments] return alias_selections[key] if alias_selections.key?(alias_name) alias_node = lookup_alias_node(ast_nodes, alias_name) return NULL_LOOKAHEAD unless alias_node next_field_defn = @query.get_field(selected_type, alias_node.name) alias_arguments = @query.arguments_for(alias_node, next_field_defn) if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) alias_arguments = alias_arguments.keyword_arguments end return NULL_LOOKAHEAD if arguments && arguments != alias_arguments alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name) end # Like {#selection}, but for all nodes. # It returns a list of Lookaheads for all Selections # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in each selection. This method will filter the selections # if any of the given `arguments:` do not match the given selection. # # @example getting the name of a selection # def articles(lookahead:) # next_lookaheads = lookahead.selections # => [#, ...] # next_lookaheads.map(&:name) #=> [:full_content, :title] # end # # @param arguments [Hash] Arguments which must match in the selection # @return [Array] def selections(arguments: nil) subselections_by_type = {} subselections_on_type = subselections_by_type[@selected_type] = {} @ast_nodes.each do |node| find_selections(subselections_by_type, subselections_on_type, @selected_type, node.selections, arguments) end subselections = [] subselections_by_type.each do |type, ast_nodes_by_response_key| ast_nodes_by_response_key.each do |response_key, ast_nodes| field_defn = @query.get_field(type, ast_nodes.first.name) lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type) subselections.push(lookahead) end end subselections end # The method name of the field. # It returns the method_sym of the Lookahead's field. # # @example getting the name of a selection # def articles(lookahead:) # article.selection(:full_content).name # => :full_content # # ... # end # # @return [Symbol] def name @field && @field.original_name end def inspect "#" end # This is returned for {Lookahead#selection} when a non-existent field is passed class NullLookahead < Lookahead # No inputs required here. def initialize end def selected? false end def selects?(*) false end def selection(*) NULL_LOOKAHEAD end def selections(*) [] end def inspect "#" end end # A singleton, so that misses don't come with overhead. NULL_LOOKAHEAD = NullLookahead.new private def skipped_by_directive?(ast_selection) ast_selection.directives.each do |directive| dir_defn = @query.schema.directives.fetch(directive.name) directive_class = dir_defn if directive_class dir_args = @query.arguments_for(directive, dir_defn) return true unless directive_class.static_include?(dir_args, @query.context) end end false end def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments) ast_selections.each do |ast_selection| next if skipped_by_directive?(ast_selection) case ast_selection when GraphQL::Language::Nodes::Field response_key = ast_selection.alias || ast_selection.name if selections_on_type.key?(response_key) selections_on_type[response_key] << ast_selection elsif arguments.nil? || arguments.empty? selections_on_type[response_key] = [ast_selection] else field_defn = @query.get_field(selected_type, ast_selection.name) if arguments_match?(arguments, field_defn, ast_selection) selections_on_type[response_key] = [ast_selection] end end when GraphQL::Language::Nodes::InlineFragment on_type = selected_type subselections_on_type = selections_on_type if (t = ast_selection.type) # Assuming this is valid, that `t` will be found. on_type = @query.get_type(t.name) subselections_on_type = subselections_by_type[on_type] ||= {} end find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments) when GraphQL::Language::Nodes::FragmentSpread frag_defn = lookup_fragment(ast_selection) # Again, assuming a valid AST on_type = @query.get_type(frag_defn.type.name) subselections_on_type = subselections_by_type[on_type] ||= {} find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments) else raise "Invariant: Unexpected selection type: #{ast_selection.class}" end end end # If a selection on `node` matches `field_name` (which is backed by `field_defn`) # and matches the `arguments:` constraints, then add that node to `matches` def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED) return if skipped_by_directive?(node) case node when GraphQL::Language::Nodes::Field if node.name == field_name && (NOT_CONFIGURED.equal?(alias_name) || node.alias == alias_name) if arguments.nil? || arguments.empty? # No constraint applied matches << node elsif arguments_match?(arguments, field_defn, node) matches << node end end when GraphQL::Language::Nodes::InlineFragment node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) } when GraphQL::Language::Nodes::FragmentSpread frag_defn = lookup_fragment(node) frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) } else raise "Unexpected selection comparison on #{node.class.name} (#{node})" end end def arguments_match?(arguments, field_defn, field_node) query_kwargs = @query.arguments_for(field_node, field_defn) arguments.all? do |arg_name, arg_value| arg_name_sym = if arg_name.is_a?(String) Schema::Member::BuildType.underscore(arg_name).to_sym else arg_name end # Make sure the constraint is present with a matching value query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value end end def lookahead_for_selection(field_defn, selected_type, arguments, alias_name = NOT_CONFIGURED) return NULL_LOOKAHEAD unless field_defn next_nodes = [] field_name = field_defn.name @ast_nodes.each do |ast_node| ast_node.selections.each do |selection| find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name) end end return NULL_LOOKAHEAD if next_nodes.empty? Lookahead.new(query: @query, ast_nodes: next_nodes, field: field_defn, owner_type: selected_type) end def alias_selections return @alias_selections if defined?(@alias_selections) @alias_selections ||= {} end def lookup_alias_node(nodes, name) return if nodes.empty? nodes.flat_map(&:children) .flat_map { |child| unwrap_fragments(child) } .find { |child| child.is_a?(GraphQL::Language::Nodes::Field) && child.alias == name } end def unwrap_fragments(node) case node when GraphQL::Language::Nodes::InlineFragment node.children when GraphQL::Language::Nodes::FragmentSpread lookup_fragment(node).children else [node] end end def lookup_fragment(ast_selection) @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})") end end end end graphql-ruby-2.2.17/lib/graphql/execution/multiplex.rb000066400000000000000000000032621476434635200230200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Execute multiple queries under the same multiplex "umbrella". # They can share a batching context and reduce redundant database hits. # # The flow is: # # - Multiplex instrumentation setup # - Query instrumentation setup # - Analyze the multiplex + each query # - Begin each query # - Resolve lazy values, breadth-first across all queries # - Finish each query (eg, get errors) # - Query instrumentation teardown # - Multiplex instrumentation teardown # # If one query raises an application error, all queries will be in undefined states. # # Validation errors and {GraphQL::ExecutionError}s are handled in isolation: # one of these errors in one query will not affect the other queries. # # @see {Schema#multiplex} for public API # @api private class Multiplex include Tracing::Traceable attr_reader :context, :queries, :schema, :max_complexity, :dataloader, :current_trace def initialize(schema:, queries:, context:, max_complexity:) @schema = schema @queries = queries @queries.each { |q| q.multiplex = self } @context = context @current_trace = @context[:trace] || schema.new_trace(multiplex: self) @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new @tracers = schema.tracers + (context[:tracers] || []) # Support `context: {backtrace: true}` if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) @tracers << GraphQL::Backtrace::Tracer end @max_complexity = max_complexity end end end end graphql-ruby-2.2.17/lib/graphql/execution_error.rb000066400000000000000000000032171476434635200222060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # If a field's resolve function returns a {ExecutionError}, # the error will be inserted into the response's `"errors"` key # and the field will resolve to `nil`. class ExecutionError < GraphQL::Error # @return [GraphQL::Language::Nodes::Field] the field where the error occurred attr_accessor :ast_node # @return [String] an array describing the JSON-path into the execution # response which corresponds to this error. attr_accessor :path # @return [Hash] Optional data for error objects # @deprecated Use `extensions` instead of `options`. The GraphQL spec # recommends that any custom entries in an error be under the # `extensions` key. attr_accessor :options # @return [Hash] Optional custom data for error objects which will be added # under the `extensions` key. attr_accessor :extensions def initialize(message, ast_node: nil, options: nil, extensions: nil) @ast_node = ast_node @options = options @extensions = extensions super(message) end # @return [Hash] An entry for the response's "errors" key def to_h hash = { "message" => message, } if ast_node hash["locations"] = [ { "line" => ast_node.line, "column" => ast_node.col, } ] end if path hash["path"] = path end if options hash.merge!(options) end if extensions hash["extensions"] = extensions.each_with_object({}) { |(key, value), ext| ext[key.to_s] = value } end hash end end end graphql-ruby-2.2.17/lib/graphql/integer_decoding_error.rb000066400000000000000000000011001476434635200234610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range. # # For really big integer values, consider `GraphQL::Types::BigInt` # # @see GraphQL::Types::Int which raises this error class IntegerDecodingError < GraphQL::RuntimeTypeError # The value which couldn't be decoded attr_reader :integer_value def initialize(value) @integer_value = value super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.") end end end graphql-ruby-2.2.17/lib/graphql/integer_encoding_error.rb000066400000000000000000000022151476434635200235030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is asked to return a value outside of 32-bit integer range. # # For values outside that range, consider: # # - `ID` for database primary keys or other identifiers # - `GraphQL::Types::BigInt` for really big integer values # # @see GraphQL::Types::Int which raises this error class IntegerEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :integer_value # @return [GraphQL::Schema::Field] The field that returned a too-big integer attr_reader :field # @return [Array] Where the field appeared in the GraphQL response attr_reader :path def initialize(value, context:) @integer_value = value @field = context[:current_field] @path = context[:current_path] message = "Integer out of bounds: #{value}".dup if @path message << " @ #{@path.join(".")}" end if @field message << " (#{@field.path})" end message << ". Consider using ID or GraphQL::Types::BigInt instead." super(message) end end end graphql-ruby-2.2.17/lib/graphql/introspection.rb000066400000000000000000000053641476434635200216770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false) # The introspection query to end all introspection queries, copied from # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js <<-QUERY.gsub(/\n{2,}/, "\n") query IntrospectionQuery { __schema { #{include_schema_description ? "description" : ""} queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations #{include_is_repeatable ? "isRepeatable" : ""} args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } } } } fragment FullType on __Type { kind name description #{include_specified_by_url ? "specifiedByURL" : ""} #{include_is_one_of ? "isOneOf" : ""} fields(includeDeprecated: true) { name description args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue #{include_deprecated_args ? 'isDeprecated' : ''} #{include_deprecated_args ? 'deprecationReason' : ''} } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } QUERY end end end require "graphql/introspection/base_object" require "graphql/introspection/input_value_type" require "graphql/introspection/enum_value_type" require "graphql/introspection/type_kind_enum" require "graphql/introspection/type_type" require "graphql/introspection/field_type" require "graphql/introspection/directive_location_enum" require "graphql/introspection/directive_type" require "graphql/introspection/schema_type" require "graphql/introspection/introspection_query" require "graphql/introspection/dynamic_fields" require "graphql/introspection/entry_points" graphql-ruby-2.2.17/lib/graphql/introspection/000077500000000000000000000000001476434635200213425ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/introspection/base_object.rb000066400000000000000000000004451476434635200241320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class BaseObject < GraphQL::Schema::Object introspection(true) def self.field(*args, **kwargs, &block) kwargs[:introspection] = true super(*args, **kwargs, &block) end end end end graphql-ruby-2.2.17/lib/graphql/introspection/directive_location_enum.rb000066400000000000000000000010611476434635200265570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DirectiveLocationEnum < GraphQL::Schema::Enum graphql_name "__DirectiveLocation" description "A Directive can be adjacent to many parts of the GraphQL language, "\ "a __DirectiveLocation describes one such possible adjacencies." GraphQL::Schema::Directive::LOCATIONS.each do |location| value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location) end introspection true end end end graphql-ruby-2.2.17/lib/graphql/introspection/directive_type.rb000066400000000000000000000031641476434635200247120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DirectiveType < Introspection::BaseObject graphql_name "__Directive" description "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document."\ "\n\n"\ "In some cases, you need to provide options to alter GraphQL's execution behavior "\ "in ways field arguments will not suffice, such as conditionally including or "\ "skipping a field. Directives provide this by describing additional information "\ "to the executor." field :name, String, null: false, method: :graphql_name field :description, String field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do argument :include_deprecated, Boolean, required: false, default_value: false end field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation? field :on_fragment, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_fragment? field :on_field, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_field? field :is_repeatable, Boolean, method: :repeatable? def args(include_deprecated:) args = @context.warden.arguments(@object) args = args.reject(&:deprecation_reason) unless include_deprecated args end end end end graphql-ruby-2.2.17/lib/graphql/introspection/dynamic_fields.rb000066400000000000000000000004571476434635200246470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DynamicFields < Introspection::BaseObject field :__typename, String, "The name of this type", null: false, dynamic_introspection: true def __typename object.class.graphql_name end end end end graphql-ruby-2.2.17/lib/graphql/introspection/entry_points.rb000066400000000000000000000017011476434635200244230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class EntryPoints < Introspection::BaseObject field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do argument :name, String end def __schema # Apply wrapping manually since this field isn't wrapped by instrumentation schema = context.schema schema_type = schema.introspection_system.types["__Schema"] schema_type.wrap(schema, context) end def __type(name:) if context.warden.reachable_type?(name) context.warden.get_type(name) elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name }) type else nil end end end end end graphql-ruby-2.2.17/lib/graphql/introspection/enum_value_type.rb000066400000000000000000000013011476434635200250630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class EnumValueType < Introspection::BaseObject graphql_name "__EnumValue" description "One possible value for a given Enum. Enum values are unique values, not a "\ "placeholder for a string or numeric value. However an Enum value is returned in "\ "a JSON response as a string." field :name, String, null: false field :description, String field :is_deprecated, Boolean, null: false field :deprecation_reason, String def name object.graphql_name end def is_deprecated !!@object.deprecation_reason end end end end graphql-ruby-2.2.17/lib/graphql/introspection/field_type.rb000066400000000000000000000020151476434635200240110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class FieldType < Introspection::BaseObject graphql_name "__Field" description "Object and Interface types are described by a list of Fields, each of which has "\ "a name, potentially a list of arguments, and a return type." field :name, String, null: false field :description, String field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do argument :include_deprecated, Boolean, required: false, default_value: false end field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :is_deprecated, Boolean, null: false field :deprecation_reason, String def is_deprecated !!@object.deprecation_reason end def args(include_deprecated:) args = @context.warden.arguments(@object) args = args.reject(&:deprecation_reason) unless include_deprecated args end end end end graphql-ruby-2.2.17/lib/graphql/introspection/input_value_type.rb000066400000000000000000000046651476434635200252760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class InputValueType < Introspection::BaseObject graphql_name "__InputValue" description "Arguments provided to Fields or Directives and the input fields of an "\ "InputObject are represented as Input Values which describe their type and "\ "optionally a default value." field :name, String, null: false field :description, String field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :default_value, String, "A GraphQL-formatted string representing the default value for this input value." field :is_deprecated, Boolean, null: false field :deprecation_reason, String def is_deprecated !!@object.deprecation_reason end def default_value if @object.default_value? value = @object.default_value if value.nil? 'null' else if (@object.type.kind.list? || (@object.type.kind.non_null? && @object.type.of_type.kind.list?)) && !value.respond_to?(:map) # This is a bit odd -- we expect the default value to be an application-style value, so we use coerce result below. # But coerce_result doesn't wrap single-item lists, which are valid inputs to list types. # So, apply that wrapper here if needed. value = [value] end coerced_default_value = @object.type.coerce_result(value, @context) serialize_default_value(coerced_default_value, @object.type) end else nil end end private # Recursively serialize, taking care not to add quotes to enum values def serialize_default_value(value, type) if value.nil? 'null' elsif type.kind.list? inner_type = type.of_type "[" + value.map { |v| serialize_default_value(v, inner_type) }.join(", ") + "]" elsif type.kind.non_null? serialize_default_value(value, type.of_type) elsif type.kind.enum? value elsif type.kind.input_object? "{" + value.map do |k, v| arg_defn = type.get_argument(k, context) "#{k}: #{serialize_default_value(v, arg_defn.type)}" end.join(", ") + "}" else GraphQL::Language.serialize(value) end end end end end graphql-ruby-2.2.17/lib/graphql/introspection/introspection_query.rb000066400000000000000000000005031476434635200260120ustar00rootroot00000000000000# frozen_string_literal: true # This query is used by graphql-client so don't add the includeDeprecated # argument for inputFields since the server may not support it. Two stage # introspection queries will be required to handle this in clients. GraphQL::Introspection::INTROSPECTION_QUERY = GraphQL::Introspection.query graphql-ruby-2.2.17/lib/graphql/introspection/schema_type.rb000066400000000000000000000036051476434635200241740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class SchemaType < Introspection::BaseObject graphql_name "__Schema" description "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all "\ "available types and directives on the server, as well as the entry points for "\ "query, mutation, and subscription operations." field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false, scope: false field :query_type, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at." field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at." field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false, scope: false field :description, String, resolver_method: :schema_description def schema_description context.schema.description end def types types = context.warden.reachable_types + context.schema.extra_types types.sort_by!(&:graphql_name) types end def query_type permitted_root_type("query") end def mutation_type permitted_root_type("mutation") end def subscription_type permitted_root_type("subscription") end def directives @context.warden.directives end private def permitted_root_type(op_type) @context.warden.root_type_for_operation(op_type) end end end end graphql-ruby-2.2.17/lib/graphql/introspection/type_kind_enum.rb000066400000000000000000000006041476434635200247010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class TypeKindEnum < GraphQL::Schema::Enum graphql_name "__TypeKind" description "An enum describing what kind of type a given `__Type` is." GraphQL::TypeKinds::TYPE_KINDS.each do |type_kind| value(type_kind.name, type_kind.description) end introspection true end end end graphql-ruby-2.2.17/lib/graphql/introspection/type_type.rb000066400000000000000000000070671476434635200237230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class TypeType < Introspection::BaseObject graphql_name "__Type" description "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in "\ "GraphQL as represented by the `__TypeKind` enum.\n\n"\ "Depending on the kind of a type, certain fields describe information about that type. "\ "Scalar types provide no information beyond a name and description, while "\ "Enum types provide their values. Object and Interface types provide the fields "\ "they describe. Abstract types, Union and Interface, provide the Object types "\ "possible at runtime. List and NonNull types compose other types." field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false field :name, String, method: :graphql_name field :description, String field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false do argument :include_deprecated, Boolean, required: false, default_value: false end field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false do argument :include_deprecated, Boolean, required: false, default_value: false end field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false do argument :include_deprecated, Boolean, required: false, default_value: false end field :of_type, GraphQL::Schema::LateBoundType.new("__Type") field :specifiedByURL, String, resolver_method: :specified_by_url field :is_one_of, Boolean, null: false def is_one_of object.kind.input_object? && object.directives.any? { |d| d.graphql_name == "oneOf" } end def specified_by_url if object.kind.scalar? object.specified_by_url else nil end end def kind @object.kind.name end def enum_values(include_deprecated:) if !@object.kind.enum? nil else enum_values = @context.warden.enum_values(@object) if !include_deprecated enum_values = enum_values.select {|f| !f.deprecation_reason } end enum_values end end def interfaces if @object.kind.object? || @object.kind.interface? @context.warden.interfaces(@object).sort_by(&:graphql_name) else nil end end def input_fields(include_deprecated:) if @object.kind.input_object? args = @context.warden.arguments(@object) args = args.reject(&:deprecation_reason) unless include_deprecated args else nil end end def possible_types if @object.kind.abstract? @context.warden.possible_types(@object).sort_by(&:graphql_name) else nil end end def fields(include_deprecated:) if !@object.kind.fields? nil else fields = @context.warden.fields(@object) if !include_deprecated fields = fields.select {|f| !f.deprecation_reason } end fields.sort_by(&:name) end end def of_type @object.kind.wraps? ? @object.of_type : nil end end end end graphql-ruby-2.2.17/lib/graphql/invalid_name_error.rb000066400000000000000000000005001476434635200226210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class InvalidNameError < GraphQL::ExecutionError attr_reader :name, :valid_regex def initialize(name, valid_regex) @name = name @valid_regex = valid_regex super("Names must match #{@valid_regex.inspect} but '#{@name}' does not") end end end graphql-ruby-2.2.17/lib/graphql/invalid_null_error.rb000066400000000000000000000025661476434635200226710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Raised automatically when a field's resolve function returns `nil` # for a non-null field. class InvalidNullError < GraphQL::RuntimeTypeError # @return [GraphQL::BaseType] The owner of {#field} attr_reader :parent_type # @return [GraphQL::Field] The field which failed to return a value attr_reader :field # @return [nil, GraphQL::ExecutionError] The invalid value for this field attr_reader :value def initialize(parent_type, field, value) @parent_type = parent_type @field = field @value = value super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}") end # @return [Hash] An entry for the response's "errors" key def to_h { "message" => message } end # @deprecated always false def parent_error? false end class << self attr_accessor :parent_class def subclass_for(parent_class) subclass = Class.new(self) subclass.parent_class = parent_class subclass end def inspect if (name.nil? || parent_class.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation) "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError" else super end end end end end graphql-ruby-2.2.17/lib/graphql/language.rb000066400000000000000000000017671476434635200205650ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/language/block_string" require "graphql/language/printer" require "graphql/language/sanitized_printer" require "graphql/language/document_from_schema_definition" require "graphql/language/generation" require "graphql/language/lexer" require "graphql/language/nodes" require "graphql/language/cache" require "graphql/language/parser" require "graphql/language/static_visitor" require "graphql/language/token" require "graphql/language/visitor" require "graphql/language/definition_slice" module GraphQL module Language # @api private def self.serialize(value) if value.is_a?(Hash) serialized_hash = value.map do |k, v| "#{k}:#{serialize v}" end.join(",") "{#{serialized_hash}}" elsif value.is_a?(Array) serialized_array = value.map do |v| serialize v end.join(",") "[#{serialized_array}]" else JSON.generate(value, quirks_mode: true) end end end end graphql-ruby-2.2.17/lib/graphql/language/000077500000000000000000000000001476434635200202255ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/language/block_string.rb000066400000000000000000000061251476434635200232360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module BlockString # Remove leading and trailing whitespace from a block string. # See "Block Strings" in https://github.com/facebook/graphql/blob/master/spec/Section%202%20--%20Language.md def self.trim_whitespace(str) # Early return for the most common cases: if str == "" return "".dup elsif !(has_newline = str.include?("\n")) && !(str.start_with?(" ")) return str end lines = has_newline ? str.split("\n") : [str] common_indent = nil # find the common whitespace lines.each_with_index do |line, idx| if idx == 0 next end line_length = line.size line_indent = if line.match?(/\A [^ ]/) 2 elsif line.match?(/\A [^ ]/) 4 elsif line.match?(/\A[^ ]/) 0 else line[/\A */].size end if line_indent < line_length && (common_indent.nil? || line_indent < common_indent) common_indent = line_indent end end # Remove the common whitespace if common_indent && common_indent > 0 lines.each_with_index do |line, idx| if idx == 0 next else line.slice!(0, common_indent) end end end # Remove leading & trailing blank lines while lines.size > 0 && contains_only_whitespace?(lines.first) lines.shift end while lines.size > 0 && contains_only_whitespace?(lines.last) lines.pop end # Rebuild the string lines.size > 1 ? lines.join("\n") : (lines.first || "".dup) end def self.print(str, indent: '') line_length = 120 - indent.length block_str = "".dup triple_quotes = "\"\"\"\n" block_str << indent block_str << triple_quotes if str.include?("\n") str.split("\n") do |line| if line == '' block_str << "\n" else break_line(line, line_length) do |subline| block_str << indent block_str << subline block_str << "\n" end end end else break_line(str, line_length) do |subline| block_str << indent block_str << subline block_str << "\n" end end block_str << indent block_str << triple_quotes end private def self.break_line(line, length) return yield(line) if line.length < length + 5 parts = line.split(Regexp.new("((?: |^).{15,#{length - 40}}(?= |$))")) return yield(line) if parts.length < 4 yield(parts.slice!(0, 3).join) parts.each_with_index do |part, i| next if i % 2 == 1 yield "#{part[1..-1]}#{parts[i + 1]}" end nil end def self.contains_only_whitespace?(line) line.match?(/^\s*$/) end end end end graphql-ruby-2.2.17/lib/graphql/language/cache.rb000066400000000000000000000014641476434635200216220ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/version' require 'digest/sha2' module GraphQL module Language class Cache def initialize(path) @path = path end DIGEST = Digest::SHA256.new << GraphQL::VERSION def fetch(filename) hash = DIGEST.dup << filename begin hash << File.mtime(filename).to_i.to_s rescue SystemCallError return yield end cache_path = @path.join(hash.to_s) if cache_path.exist? Marshal.load(cache_path.read) else payload = yield tmp_path = "#{cache_path}.#{rand}" @path.mkpath File.binwrite(tmp_path, Marshal.dump(payload)) File.rename(tmp_path, cache_path.to_s) payload end end end end end graphql-ruby-2.2.17/lib/graphql/language/definition_slice.rb000066400000000000000000000022301476434635200240560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module DefinitionSlice extend self def slice(document, name) definitions = {} document.definitions.each { |d| definitions[d.name] = d } names = Set.new DependencyVisitor.find_definition_dependencies(definitions, name, names) definitions = document.definitions.select { |d| names.include?(d.name) } Nodes::Document.new(definitions: definitions) end private class DependencyVisitor < GraphQL::Language::StaticVisitor def initialize(doc, definitions, names) @names = names @definitions = definitions super(doc) end def on_fragment_spread(node, parent) if fragment = @definitions[node.name] self.class.find_definition_dependencies(@definitions, fragment.name, @names) end super end def self.find_definition_dependencies(definitions, name, names) names.add(name) visitor = self.new(definitions[name], definitions, names) visitor.visit nil end end end end end graphql-ruby-2.2.17/lib/graphql/language/document_from_schema_definition.rb000066400000000000000000000312261476434635200271470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # @api private # # {GraphQL::Language::DocumentFromSchemaDefinition} is used to convert a {GraphQL::Schema} object # To a {GraphQL::Language::Document} AST node. # # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param include_introspection_types [Boolean] Whether or not to include introspection types in the AST # @param include_built_in_scalars [Boolean] Whether or not to include built in scalars in the AST # @param include_built_in_directives [Boolean] Whether or not to include built in directives in the AST class DocumentFromSchemaDefinition def initialize( schema, context: nil, include_introspection_types: false, include_built_in_directives: false, include_built_in_scalars: false, always_include_schema: false ) @schema = schema @always_include_schema = always_include_schema @include_introspection_types = include_introspection_types @include_built_in_scalars = include_built_in_scalars @include_built_in_directives = include_built_in_directives @include_one_of = false schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context) @warden = @schema.warden_class.new( schema: @schema, context: schema_context, ) schema_context.warden = @warden end def document GraphQL::Language::Nodes::Document.new( definitions: build_definition_nodes ) end def build_schema_node if !schema_respects_root_name_conventions?(@schema) GraphQL::Language::Nodes::SchemaDefinition.new( query: (q = warden.root_type_for_operation("query")) && q.graphql_name, mutation: (m = warden.root_type_for_operation("mutation")) && m.graphql_name, subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name, directives: definition_directives(@schema, :schema_directives) ) else # A plain `schema ...` _must_ include root type definitions. # If the only difference is directives, then you have to use `extend schema` GraphQL::Language::Nodes::SchemaExtension.new(directives: definition_directives(@schema, :schema_directives)) end end def build_object_type_node(object_type) ints = warden.interfaces(object_type) if ints.any? ints.sort_by!(&:graphql_name) ints.map! { |iface| build_type_name_node(iface) } end GraphQL::Language::Nodes::ObjectTypeDefinition.new( name: object_type.graphql_name, interfaces: ints, fields: build_field_nodes(warden.fields(object_type)), description: object_type.description, directives: directives(object_type), ) end def build_field_node(field) GraphQL::Language::Nodes::FieldDefinition.new( name: field.graphql_name, arguments: build_argument_nodes(warden.arguments(field)), type: build_type_name_node(field.type), description: field.description, directives: directives(field), ) end def build_union_type_node(union_type) GraphQL::Language::Nodes::UnionTypeDefinition.new( name: union_type.graphql_name, description: union_type.description, types: warden.possible_types(union_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) }, directives: directives(union_type), ) end def build_interface_type_node(interface_type) GraphQL::Language::Nodes::InterfaceTypeDefinition.new( name: interface_type.graphql_name, interfaces: warden.interfaces(interface_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) }, description: interface_type.description, fields: build_field_nodes(warden.fields(interface_type)), directives: directives(interface_type), ) end def build_enum_type_node(enum_type) GraphQL::Language::Nodes::EnumTypeDefinition.new( name: enum_type.graphql_name, values: warden.enum_values(enum_type).sort_by(&:graphql_name).map do |enum_value| build_enum_value_node(enum_value) end, description: enum_type.description, directives: directives(enum_type), ) end def build_enum_value_node(enum_value) GraphQL::Language::Nodes::EnumValueDefinition.new( name: enum_value.graphql_name, description: enum_value.description, directives: directives(enum_value), ) end def build_scalar_type_node(scalar_type) GraphQL::Language::Nodes::ScalarTypeDefinition.new( name: scalar_type.graphql_name, description: scalar_type.description, directives: directives(scalar_type), ) end def build_argument_node(argument) if argument.default_value? default_value = build_default_value(argument.default_value, argument.type) else default_value = nil end argument_node = GraphQL::Language::Nodes::InputValueDefinition.new( name: argument.graphql_name, description: argument.description, type: build_type_name_node(argument.type), default_value: default_value, directives: directives(argument), ) argument_node end def build_input_object_node(input_object) GraphQL::Language::Nodes::InputObjectTypeDefinition.new( name: input_object.graphql_name, fields: build_argument_nodes(warden.arguments(input_object)), description: input_object.description, directives: directives(input_object), ) end def build_directive_node(directive) GraphQL::Language::Nodes::DirectiveDefinition.new( name: directive.graphql_name, repeatable: directive.repeatable?, arguments: build_argument_nodes(warden.arguments(directive)), locations: build_directive_location_nodes(directive.locations), description: directive.description, ) end def build_directive_location_nodes(locations) locations.sort.map { |location| build_directive_location_node(location) } end def build_directive_location_node(location) GraphQL::Language::Nodes::DirectiveLocation.new( name: location.to_s ) end def build_type_name_node(type) case type.kind.name when "LIST" GraphQL::Language::Nodes::ListType.new( of_type: build_type_name_node(type.of_type) ) when "NON_NULL" GraphQL::Language::Nodes::NonNullType.new( of_type: build_type_name_node(type.of_type) ) else @cached_type_name_nodes ||= {} @cached_type_name_nodes[type.graphql_name] ||= GraphQL::Language::Nodes::TypeName.new(name: type.graphql_name) end end def build_default_value(default_value, type) if default_value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type.kind.name when "SCALAR" type.coerce_isolated_result(default_value) when "ENUM" GraphQL::Language::Nodes::Enum.new(name: type.coerce_isolated_result(default_value)) when "INPUT_OBJECT" GraphQL::Language::Nodes::InputObject.new( arguments: default_value.to_h.map do |arg_name, arg_value| args = @warden.arguments(type) arg = args.find { |a| a.keyword.to_s == arg_name.to_s } if arg.nil? raise ArgumentError, "No argument definition on #{type.graphql_name} for argument: #{arg_name.inspect} (expected one of: #{args.map(&:keyword)})" end GraphQL::Language::Nodes::Argument.new( name: arg.graphql_name.to_s, value: build_default_value(arg_value, arg.type) ) end ) when "NON_NULL" build_default_value(default_value, type.of_type) when "LIST" default_value.to_a.map { |v| build_default_value(v, type.of_type) } else raise GraphQL::RequiredImplementationMissingError, "Unexpected default value type #{type.inspect}" end end def build_type_definition_node(type) case type.kind.name when "OBJECT" build_object_type_node(type) when "UNION" build_union_type_node(type) when "INTERFACE" build_interface_type_node(type) when "SCALAR" build_scalar_type_node(type) when "ENUM" build_enum_type_node(type) when "INPUT_OBJECT" build_input_object_node(type) else raise TypeError end end def build_argument_nodes(arguments) if arguments.any? nodes = arguments.map { |arg| build_argument_node(arg) } nodes.sort_by!(&:name) nodes else arguments end end def build_directive_nodes(directives) directives .map { |directive| build_directive_node(directive) } .sort_by(&:name) end def build_definition_nodes dirs_to_build = warden.directives if !include_built_in_directives dirs_to_build = dirs_to_build.reject { |directive| directive.default_directive? } end definitions = build_directive_nodes(dirs_to_build) type_nodes = build_type_definition_nodes(warden.reachable_types + schema.extra_types) if @include_one_of # This may have been set to true when iterating over all types definitions.concat(build_directive_nodes([GraphQL::Schema::Directive::OneOf])) end definitions.concat(type_nodes) if include_schema_node? definitions.unshift(build_schema_node) end definitions end def build_type_definition_nodes(types) if !include_introspection_types types = types.reject { |type| type.introspection? } end if !include_built_in_scalars types = types.reject { |type| type.kind.scalar? && type.default_scalar? } end types .map { |type| build_type_definition_node(type) } .sort_by(&:name) end def build_field_nodes(fields) f_nodes = fields.map { |field| build_field_node(field) } f_nodes.sort_by!(&:name) f_nodes end private def include_schema_node? always_include_schema || !schema_respects_root_name_conventions?(schema) || !schema.schema_directives.empty? end def schema_respects_root_name_conventions?(schema) (schema.query.nil? || schema.query.graphql_name == 'Query') && (schema.mutation.nil? || schema.mutation.graphql_name == 'Mutation') && (schema.subscription.nil? || schema.subscription.graphql_name == 'Subscription') end def directives(member) definition_directives(member, :directives) end def definition_directives(member, directives_method) dirs = if !member.respond_to?(directives_method) || member.directives.empty? EmptyObjects::EMPTY_ARRAY else member.public_send(directives_method).map do |dir| args = [] dir.arguments.argument_values.each_value do |arg_value| # rubocop:disable Development/ContextIsPassedCop -- directive instance method arg_defn = arg_value.definition if arg_defn.default_value? && arg_value.value == arg_defn.default_value next else value_node = build_default_value(arg_value.value, arg_value.definition.type) args << GraphQL::Language::Nodes::Argument.new( name: arg_value.definition.name, value: value_node, ) end end # If this schema uses this built-in directive definition, # include it in the print-out since it's not part of the spec yet. @include_one_of ||= dir.class == GraphQL::Schema::Directive::OneOf GraphQL::Language::Nodes::Directive.new( name: dir.class.graphql_name, arguments: args ) end end dirs end attr_reader :schema, :warden, :always_include_schema, :include_introspection_types, :include_built_in_directives, :include_built_in_scalars end end end graphql-ruby-2.2.17/lib/graphql/language/generation.rb000066400000000000000000000016461476434635200227140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Exposes {.generate}, which turns AST nodes back into query strings. module Generation extend self # Turn an AST node back into a string. # # @example Turning a document into a query # document = GraphQL.parse(query_string) # GraphQL::Language::Generation.generate(document) # # => "{ ... }" # # @param node [GraphQL::Language::Nodes::AbstractNode] an AST node to recursively stringify # @param indent [String] Whitespace to add to each printed node # @param printer [GraphQL::Language::Printer] An optional custom printer for printing AST nodes. Defaults to GraphQL::Language::Printer # @return [String] Valid GraphQL for `node` def generate(node, indent: "", printer: GraphQL::Language::Printer.new) printer.print(node, indent: indent) end end end end graphql-ruby-2.2.17/lib/graphql/language/lexer.rb000066400000000000000000000247111476434635200216760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language class Lexer def initialize(graphql_str, filename: nil) if !(graphql_str.encoding == Encoding::UTF_8 || graphql_str.ascii_only?) graphql_str = graphql_str.dup.force_encoding(Encoding::UTF_8) end @string = graphql_str @filename = filename @scanner = StringScanner.new(graphql_str) @pos = nil end def eos? @scanner.eos? end attr_reader :pos def advance @scanner.skip(IGNORE_REGEXP) return false if @scanner.eos? @pos = @scanner.pos next_byte = @string.getbyte(@pos) next_byte_is_for = FIRST_BYTES[next_byte] case next_byte_is_for when ByteFor::PUNCTUATION @scanner.pos += 1 PUNCTUATION_NAME_FOR_BYTE[next_byte] when ByteFor::NAME if len = @scanner.skip(KEYWORD_REGEXP) case len when 2 :ON when 12 :SUBSCRIPTION else pos = @pos # Use bytes 2 and 3 as a unique identifier for this keyword bytes = (@string.getbyte(pos + 2) << 8) | @string.getbyte(pos + 1) KEYWORD_BY_TWO_BYTES[_hash(bytes)] end else @scanner.skip(IDENTIFIER_REGEXP) :IDENTIFIER end when ByteFor::IDENTIFIER @scanner.skip(IDENTIFIER_REGEXP) :IDENTIFIER when ByteFor::NUMBER @scanner.skip(NUMERIC_REGEXP) # Check for a matched decimal: @scanner[1] ? :FLOAT : :INT when ByteFor::ELLIPSIS if @string.getbyte(@pos + 1) != 46 || @string.getbyte(@pos + 2) != 46 raise_parse_error("Expected `...`, actual: #{@string[@pos..@pos + 2].inspect}") end @scanner.pos += 3 :ELLIPSIS when ByteFor::STRING if @scanner.skip(BLOCK_STRING_REGEXP) || @scanner.skip(QUOTED_STRING_REGEXP) :STRING else raise_parse_error("Expected string or block string, but it was malformed") end else @scanner.pos += 1 :UNKNOWN_CHAR end rescue ArgumentError => err if err.message == "invalid byte sequence in UTF-8" raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil) end end def token_value @string.byteslice(@scanner.pos - @scanner.matched_size, @scanner.matched_size) rescue StandardError => err raise GraphQL::Error, "(token_value failed: #{err.class}: #{err.message})" end def debug_token_value(token_name) if token_name && Lexer::Punctuation.const_defined?(token_name) Lexer::Punctuation.const_get(token_name) elsif token_name == :ELLIPSIS "..." elsif token_name == :STRING string_value elsif @scanner.matched_size.nil? @scanner.peek(1) else token_value end end ESCAPES = /\\["\\\/bfnrt]/ ESCAPES_REPLACE = { '\\"' => '"', "\\\\" => "\\", "\\/" => '/', "\\b" => "\b", "\\f" => "\f", "\\n" => "\n", "\\r" => "\r", "\\t" => "\t", } UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o def string_value str = token_value is_block = str.start_with?('"""') if is_block str.gsub!(/\A"""|"""\z/, '') else str.gsub!(/\A"|"\z/, '') end if is_block str = Language::BlockString.trim_whitespace(str) end if !str.valid_encoding? || !str.match?(VALID_STRING) raise_parse_error("Bad unicode escape in #{str.inspect}") else Lexer.replace_escaped_characters_in_place(str) if !str.valid_encoding? raise_parse_error("Bad unicode escape in #{str.inspect}") else str end end end def line_number @scanner.string[0..@pos].count("\n") + 1 end def column_number @scanner.string[0..@pos].split("\n").last.length end def raise_parse_error(message, line = line_number, col = column_number) raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename) end IGNORE_REGEXP = %r{ (?: [, \c\r\n\t]+ | \#.*$ )* }x IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/ INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/ FLOAT_DECIMAL_REGEXP = /[.][0-9]+/ FLOAT_EXP_REGEXP = /[eE][+-]?[0-9]+/ NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/ KEYWORDS = [ "on", "fragment", "true", "false", "null", "query", "mutation", "subscription", "schema", "scalar", "type", "extend", "implements", "interface", "union", "enum", "input", "directive", "repeatable" ].freeze KEYWORD_REGEXP = /#{Regexp.union(KEYWORDS.sort)}\b/ KEYWORD_BY_TWO_BYTES = [ :INTERFACE, :MUTATION, :EXTEND, :FALSE, :ENUM, :TRUE, :NULL, nil, nil, nil, nil, nil, nil, nil, :QUERY, nil, nil, :REPEATABLE, :IMPLEMENTS, :INPUT, :TYPE, :SCHEMA, nil, nil, nil, :DIRECTIVE, :UNION, nil, nil, :SCALAR, nil, :FRAGMENT ] # This produces a unique integer for bytes 2 and 3 of each keyword string # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html def _hash key (key * 18592990) >> 27 & 0x1f end module Punctuation LCURLY = '{' RCURLY = '}' LPAREN = '(' RPAREN = ')' LBRACKET = '[' RBRACKET = ']' COLON = ':' VAR_SIGN = '$' DIR_SIGN = '@' EQUALS = '=' BANG = '!' PIPE = '|' AMP = '&' end # A sparse array mapping the bytes for each punctuation # to a symbol name for that punctuation PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr| punct = Punctuation.const_get(name) arr[punct.ord] = name } QUOTE = '"' UNICODE_DIGIT = /[0-9A-Za-z]/ FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/ N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{4,}#{Punctuation::RCURLY}}x UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})} # # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value STRING_ESCAPE = %r{[\\][\\/bfnrt]} BLOCK_QUOTE = '"""' ESCAPED_QUOTE = /\\"/; STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/ QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x BLOCK_STRING_REGEXP = %r{ #{BLOCK_QUOTE} (?: [^"\\] | # Any characters that aren't a quote or slash (?= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate # A surrogate pair combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000 [combined].pack('U'.freeze) else # Two separate code points [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze) end else [codepoint_1].pack('U'.freeze) end end nil end # This is not used during parsing because the parser # doesn't actually need tokens. def self.tokenize(string) lexer = GraphQL::Language::Lexer.new(string) tokens = [] prev_token = nil while (token_name = lexer.advance) new_token = [ token_name, lexer.line_number, lexer.column_number, lexer.debug_token_value(token_name), prev_token, ] tokens << new_token prev_token = new_token end tokens end end end end graphql-ruby-2.2.17/lib/graphql/language/nodes.rb000066400000000000000000000633631476434635200216750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module Nodes NONE = GraphQL::EmptyObjects::EMPTY_ARRAY # {AbstractNode} is the base class for all nodes in a GraphQL AST. # # It provides some APIs for working with ASTs: # - `children` returns all AST nodes attached to this one. Used for tree traversal. # - `scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes. # - `to_query_string` turns an AST node into a GraphQL string class AbstractNode module DefinitionNode # This AST node's {#line} returns the first line, which may be the description. # @return [Integer] The first line of the definition (not the description) attr_reader :definition_line def initialize(definition_line: nil, **_rest) @definition_line = definition_line super(**_rest) end end attr_reader :filename def line @line ||= (@source_string && @pos) ? @source_string[0..@pos].count("\n") + 1 : nil end def col @col ||= if @source_string && @pos if @pos == 0 1 else @source_string[0..@pos].split("\n").last.length end end end def definition_line @definition_line ||= (@source_string && @definition_pos) ? @source_string[0..@definition_pos].count("\n") + 1 : nil end # Value equality # @return [Boolean] True if `self` is equivalent to `other` def ==(other) return true if equal?(other) other.kind_of?(self.class) && other.scalars == self.scalars && other.children == self.children end NO_CHILDREN = GraphQL::EmptyObjects::EMPTY_ARRAY # @return [Array] all nodes in the tree below this one def children NO_CHILDREN end # @return [Array] Scalar values attached to this node def scalars NO_CHILDREN end # This might be unnecessary, but its easiest to add it here. def initialize_copy(other) @children = nil @scalars = nil @query_string = nil end def children_method_name self.class.children_method_name end def position [line, col] end def to_query_string(printer: GraphQL::Language::Printer.new) if printer.is_a?(GraphQL::Language::Printer) @query_string ||= printer.print(self) else printer.print(self) end end # This creates a copy of `self`, with `new_options` applied. # @param new_options [Hash] # @return [AbstractNode] a shallow copy of `self` def merge(new_options) dup.merge!(new_options) end # Copy `self`, but modify the copy so that `previous_child` is replaced by `new_child` def replace_child(previous_child, new_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Get the value from this (original) node prev_children = public_send(method_name) if prev_children.is_a?(Array) # Copy that list, and replace `previous_child` with `new_child` # in the list. new_children = prev_children.dup prev_idx = new_children.index(previous_child) new_children[prev_idx] = new_child else # Use the new value for the given attribute new_children = new_child end # Copy this node, but with the new child value copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end # TODO DRY with `replace_child` def delete_child(previous_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Copy that list, and delete previous_child new_children = public_send(method_name).dup new_children.delete(previous_child) # Copy this node, but with the new list of children: copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end protected def merge!(new_options) new_options.each do |key, value| instance_variable_set(:"@#{key}", value) end self end class << self # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # Add a default `#visit_method` and `#children_method_name` using the class name def inherited(child_class) super name_underscored = child_class.name .split("::").last .gsub(/([a-z])([A-Z])/,'\1_\2') # insert underscores .downcase # remove caps child_class.module_eval <<-RUBY, __FILE__, __LINE__ def visit_method :on_#{name_underscored} end class << self attr_accessor :children_method_name def visit_method :on_#{name_underscored} end end self.children_method_name = :#{name_underscored}s RUBY end def children_of_type @children_methods end private # Name accessors which return lists of nodes, # along with the kind of node they return, if possible. # - Add a reader for these children # - Add a persistent update method to add a child # - Generate a `#children` method def children_methods(children_of_type) if defined?(@children_methods) raise "Can't re-call .children_methods for #{self} (already have: #{@children_methods})" else @children_methods = children_of_type end if children_of_type == false @children_methods = {} # skip else children_of_type.each do |method_name, node_type| module_eval <<-RUBY, __FILE__, __LINE__ # A reader for these children attr_reader :#{method_name} RUBY if node_type # Only generate a method if we know what kind of node to make module_eval <<-RUBY, __FILE__, __LINE__ # Singular method: create a node with these options # and return a new `self` which includes that node in this list. def merge_#{method_name.to_s.sub(/s$/, "")}(**node_opts) merge(#{method_name}: #{method_name} + [#{node_type.name}.new(**node_opts)]) end RUBY end end if children_of_type.size == 1 module_eval <<-RUBY, __FILE__, __LINE__ alias :children #{children_of_type.keys.first} RUBY else module_eval <<-RUBY, __FILE__, __LINE__ def children @children ||= begin if #{children_of_type.keys.map { |k| "@#{k}.any?" }.join(" || ")} new_children = [] #{children_of_type.keys.map { |k| "new_children.concat(@#{k})" }.join("; ")} new_children.freeze new_children else NO_CHILDREN end end end RUBY end end if defined?(@scalar_methods) if !@initialize_was_generated @initialize_was_generated = true generate_initialize else # This method was defined manually end else raise "Can't generate_initialize because scalar_methods wasn't called; call it before children_methods" end end # These methods return a plain Ruby value, not another node # - Add reader methods # - Add a `#scalars` method def scalar_methods(*method_names) if defined?(@scalar_methods) raise "Can't re-call .scalar_methods for #{self} (already have: #{@scalar_methods})" else @scalar_methods = method_names end if method_names == [false] @scalar_methods = [] # skip it else module_eval <<-RUBY, __FILE__, __LINE__ # add readers for each scalar attr_reader #{method_names.map { |m| ":#{m}"}.join(", ")} def scalars @scalars ||= [#{method_names.map { |k| "@#{k}" }.join(", ")}].freeze end RUBY end end DEFAULT_INITIALIZE_OPTIONS = [ "line: nil", "col: nil", "pos: nil", "filename: nil", "source_string: nil", ] def generate_initialize scalar_method_names = @scalar_methods # TODO: These probably should be scalar methods, but `types` returns an array [:types, :description].each do |extra_method| if method_defined?(extra_method) scalar_method_names += [extra_method] end end all_method_names = scalar_method_names + @children_methods.keys if all_method_names.include?(:alias) # Rather than complicating this special case, # let it be overridden (in field) return else arguments = scalar_method_names.map { |m| "#{m}: nil"} + @children_methods.keys.map { |m| "#{m}: NO_CHILDREN" } + DEFAULT_INITIALIZE_OPTIONS assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} + @children_methods.keys.map { |m| "@#{m} = #{m}.freeze" } if name.end_with?("Definition") && name != "FragmentDefinition" arguments << "definition_pos: nil" assignments << "@definition_pos = definition_pos" end keywords = scalar_method_names.map { |m| "#{m}: #{m}"} + @children_methods.keys.map { |m| "#{m}: #{m}" } module_eval <<-RUBY, __FILE__, __LINE__ def initialize(#{arguments.join(", ")}) @line = line @col = col @pos = pos @filename = filename @source_string = source_string #{assignments.join("\n")} end def self.from_a(filename, line, col, #{(scalar_method_names + @children_methods.keys).join(", ")}) self.new(filename: filename, line: line, col: col, #{keywords.join(", ")}) end RUBY end end # rubocop:enable Development/NoEvalCop end end # Base class for non-null type names and list type names class WrapperType < AbstractNode scalar_methods :of_type children_methods(false) end # Base class for nodes whose only value is a name (no child nodes or other scalars) class NameOnlyNode < AbstractNode scalar_methods :name children_methods(false) end # A key-value pair for a field's inputs class Argument < AbstractNode scalar_methods :name, :value children_methods(false) # @!attribute name # @return [String] the key for this argument # @!attribute value # @return [String, Float, Integer, Boolean, Array, InputObject, VariableIdentifier] The value passed for this key def children @children ||= Array(value).flatten.tap { _1.select! { |v| v.is_a?(AbstractNode) } } end end class Directive < AbstractNode scalar_methods :name children_methods(arguments: GraphQL::Language::Nodes::Argument) end class DirectiveLocation < NameOnlyNode end class DirectiveDefinition < AbstractNode attr_reader :description scalar_methods :name, :repeatable children_methods( arguments: Nodes::Argument, locations: Nodes::DirectiveLocation, ) end # An enum value. The string is available as {#name}. class Enum < NameOnlyNode end # A null value literal. class NullValue < NameOnlyNode end # A single selection in a GraphQL query. class Field < AbstractNode scalar_methods :name, :alias children_methods({ arguments: GraphQL::Language::Nodes::Argument, selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # @!attribute selections # @return [Array] Selections on this object (or empty array if this is a scalar field) def initialize(name: nil, arguments: NONE, directives: NONE, selections: NONE, field_alias: nil, line: nil, col: nil, pos: nil, filename: nil, source_string: nil) @name = name @arguments = arguments || NONE @directives = directives || NONE @selections = selections || NONE # oops, alias is a keyword: @alias = field_alias @line = line @col = col @pos = pos @filename = filename @source_string = source_string end def self.from_a(filename, line, col, field_alias, name, arguments, directives, selections) # rubocop:disable Metrics/ParameterLists self.new(filename: filename, line: line, col: col, field_alias: field_alias, name: name, arguments: arguments, directives: directives, selections: selections) end # Override this because default is `:fields` self.children_method_name = :selections end # A reusable fragment, defined at document-level. class FragmentDefinition < AbstractNode scalar_methods :name, :type children_methods({ selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions # @!attribute name # @return [String] the identifier for this fragment, which may be applied with `...#{name}` # @!attribute type # @return [String] the type condition for this fragment (name of type which it may apply to) def initialize(name: nil, type: nil, directives: NONE, selections: NONE, filename: nil, pos: nil, source_string: nil, line: nil, col: nil) @name = name @type = type @directives = directives @selections = selections @filename = filename @pos = pos @source_string = source_string @line = line @col = col end def self.from_a(filename, line, col, name, type, directives, selections) self.new(filename: filename, line: line, col: col, name: name, type: type, directives: directives, selections: selections) end end # Application of a named fragment in a selection class FragmentSpread < AbstractNode scalar_methods :name children_methods(directives: GraphQL::Language::Nodes::Directive) self.children_method_name = :selections # @!attribute name # @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name} end # An unnamed fragment, defined directly in the query with `... { }` class InlineFragment < AbstractNode scalar_methods :type children_methods({ directives: GraphQL::Language::Nodes::Directive, selections: GraphQL::Language::Nodes::Field, }) self.children_method_name = :selections # @!attribute type # @return [String, nil] Name of the type this fragment applies to, or `nil` if this fragment applies to any type end # A collection of key-value inputs which may be a field argument class InputObject < AbstractNode scalar_methods(false) children_methods(arguments: GraphQL::Language::Nodes::Argument) # @!attribute arguments # @return [Array] A list of key-value pairs inside this input object # @return [Hash] Recursively turn this input object into a Ruby Hash def to_h(options={}) arguments.inject({}) do |memo, pair| v = pair.value memo[pair.name] = serialize_value_for_hash v memo end end self.children_method_name = :value private def serialize_value_for_hash(value) case value when InputObject value.to_h when Array value.map do |v| serialize_value_for_hash v end when Enum value.name when NullValue nil else value end end end # A list type definition, denoted with `[...]` (used for variable type definitions) class ListType < WrapperType end # A non-null type definition, denoted with `...!` (used for variable type definitions) class NonNullType < WrapperType end # An operation-level query variable class VariableDefinition < AbstractNode scalar_methods :name, :type, :default_value children_methods(directives: Directive) # @!attribute default_value # @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided # @!attribute type # @return [TypeName, NonNullType, ListType] The expected type of this value # @!attribute name # @return [String] The identifier for this variable, _without_ `$` self.children_method_name = :variables end # A query, mutation or subscription. # May be anonymous or named. # May be explicitly typed (eg `mutation { ... }`) or implicitly a query (eg `{ ... }`). class OperationDefinition < AbstractNode scalar_methods :operation_type, :name children_methods({ variables: GraphQL::Language::Nodes::VariableDefinition, directives: GraphQL::Language::Nodes::Directive, selections: GraphQL::Language::Nodes::Field, }) # @!attribute variables # @return [Array] Variable $definitions for this operation # @!attribute selections # @return [Array] Root-level fields on this operation # @!attribute operation_type # @return [String, nil] The root type for this operation, or `nil` for implicit `"query"` # @!attribute name # @return [String, nil] The name for this operation, or `nil` if unnamed self.children_method_name = :definitions end # This is the AST root for normal queries # # @example Deriving a document by parsing a string # document = GraphQL.parse(query_string) # # @example Creating a string from a document # document.to_query_string # # { ... } # # @example Creating a custom string from a document # class VariableScrubber < GraphQL::Language::Printer # def print_argument(arg) # print_string("#{arg.name}: ") # end # end # # document.to_query_string(printer: VariableScrubber.new) # class Document < AbstractNode scalar_methods false children_methods(definitions: nil) # @!attribute definitions # @return [Array] top-level GraphQL units: operations or fragments def slice_definition(name) GraphQL::Language::DefinitionSlice.slice(self, name) end end # A type name, used for variable definitions class TypeName < NameOnlyNode end # Usage of a variable in a query. Name does _not_ include `$`. class VariableIdentifier < NameOnlyNode self.children_method_name = :value end class SchemaDefinition < AbstractNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class SchemaExtension < AbstractNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class ScalarTypeDefinition < AbstractNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class ScalarTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class InputValueDefinition < AbstractNode attr_reader :description scalar_methods :name, :type, :default_value children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :fields end class FieldDefinition < AbstractNode attr_reader :description scalar_methods :name, :type children_methods({ arguments: GraphQL::Language::Nodes::InputValueDefinition, directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :fields # this is so that `children_method_name` of `InputValueDefinition` works properly # with `#replace_child` alias :fields :arguments def merge(new_options) if (f = new_options.delete(:fields)) new_options[:arguments] = f end super end end class ObjectTypeDefinition < AbstractNode attr_reader :description scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class ObjectTypeExtension < AbstractNode scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class InterfaceTypeDefinition < AbstractNode attr_reader :description scalar_methods :name children_methods({ interfaces: GraphQL::Language::Nodes::TypeName, directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class InterfaceTypeExtension < AbstractNode scalar_methods :name children_methods({ interfaces: GraphQL::Language::Nodes::TypeName, directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class UnionTypeDefinition < AbstractNode attr_reader :description, :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class UnionTypeExtension < AbstractNode attr_reader :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class EnumValueDefinition < AbstractNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :values end class EnumTypeDefinition < AbstractNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) self.children_method_name = :definitions end class EnumTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) self.children_method_name = :definitions end class InputObjectTypeDefinition < AbstractNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :definitions end class InputObjectTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :definitions end end end end graphql-ruby-2.2.17/lib/graphql/language/parser.rb000066400000000000000000000567311476434635200220620ustar00rootroot00000000000000# frozen_string_literal: true require "strscan" require "graphql/language/nodes" module GraphQL module Language class Parser include GraphQL::Language::Nodes include EmptyObjects class << self attr_accessor :cache def parse(graphql_str, filename: nil, trace: Tracing::NullTrace) self.new(graphql_str, filename: filename, trace: trace).parse end def parse_file(filename, trace: Tracing::NullTrace) if cache cache.fetch(filename) do parse(File.read(filename), filename: filename, trace: trace) end else parse(File.read(filename), filename: filename, trace: trace) end end end def initialize(graphql_str, filename: nil, trace: Tracing::NullTrace) if graphql_str.nil? raise GraphQL::ParseError.new("No query string was present", nil, nil, nil) end @lexer = Lexer.new(graphql_str, filename: filename) @graphql_str = graphql_str @filename = filename @trace = trace end def parse @document ||= begin @trace.parse(query_string: @graphql_str) do document end rescue SystemStackError raise GraphQL::ParseError.new("This query is too large to execute.", nil, nil, @query_str, filename: @filename) end end private attr_reader :token_name def advance_token @token_name = @lexer.advance end def pos @lexer.pos end def document any_tokens = advance_token defns = [] if any_tokens defns << definition else # Only ignored characters is not a valid document raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @graphql_str) end while !@lexer.eos? defns << definition end Document.new(pos: 0, definitions: defns, filename: @filename, source_string: @graphql_str) end def definition case token_name when :FRAGMENT loc = pos expect_token :FRAGMENT f_name = if !at?(:ON) parse_name end expect_token :ON f_type = parse_type_name directives = parse_directives selections = selection_set Nodes::FragmentDefinition.new( pos: loc, name: f_name, type: f_type, directives: directives, selections: selections, filename: @filename, source_string: @graphql_str ) when :QUERY, :MUTATION, :SUBSCRIPTION, :LCURLY op_loc = pos op_type = case token_name when :LCURLY "query" else parse_operation_type end op_name = at?(:IDENTIFIER) ? parse_name : nil variable_definitions = if at?(:LPAREN) expect_token(:LPAREN) defs = [] while !at?(:RPAREN) loc = pos expect_token(:VAR_SIGN) var_name = parse_name expect_token(:COLON) var_type = self.type default_value = if at?(:EQUALS) advance_token value end directives = parse_directives defs << Nodes::VariableDefinition.new( pos: loc, name: var_name, type: var_type, default_value: default_value, directives: directives, filename: @filename, source_string: @graphql_str ) end expect_token(:RPAREN) defs else EmptyObjects::EMPTY_ARRAY end directives = parse_directives OperationDefinition.new( pos: op_loc, operation_type: op_type, name: op_name, variables: variable_definitions, directives: directives, selections: selection_set, filename: @filename, source_string: @graphql_str ) when :EXTEND loc = pos advance_token case token_name when :SCALAR advance_token name = parse_name directives = parse_directives ScalarTypeExtension.new(pos: loc, name: name, directives: directives, filename: @filename, source_string: @graphql_str) when :TYPE advance_token name = parse_name implements_interfaces = parse_implements directives = parse_directives field_defns = at?(:LCURLY) ? parse_field_definitions : EMPTY_ARRAY ObjectTypeExtension.new(pos: loc, name: name, interfaces: implements_interfaces, directives: directives, fields: field_defns, filename: @filename, source_string: @graphql_str) when :INTERFACE advance_token name = parse_name directives = parse_directives interfaces = parse_implements fields_definition = at?(:LCURLY) ? parse_field_definitions : EMPTY_ARRAY InterfaceTypeExtension.new(pos: loc, name: name, directives: directives, fields: fields_definition, interfaces: interfaces, filename: @filename, source_string: @graphql_str) when :UNION advance_token name = parse_name directives = parse_directives union_member_types = parse_union_members UnionTypeExtension.new(pos: loc, name: name, directives: directives, types: union_member_types, filename: @filename, source_string: @graphql_str) when :ENUM advance_token name = parse_name directives = parse_directives enum_values_definition = parse_enum_value_definitions Nodes::EnumTypeExtension.new(pos: loc, name: name, directives: directives, values: enum_values_definition, filename: @filename, source_string: @graphql_str) when :INPUT advance_token name = parse_name directives = parse_directives input_fields_definition = parse_input_object_field_definitions InputObjectTypeExtension.new(pos: loc, name: name, directives: directives, fields: input_fields_definition, filename: @filename, source_string: @graphql_str) when :SCHEMA advance_token directives = parse_directives query = mutation = subscription = nil if at?(:LCURLY) advance_token while !at?(:RCURLY) if at?(:QUERY) advance_token expect_token(:COLON) query = parse_name elsif at?(:MUTATION) advance_token expect_token(:COLON) mutation = parse_name elsif at?(:SUBSCRIPTION) advance_token expect_token(:COLON) subscription = parse_name else expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION]) end end expect_token :RCURLY end SchemaExtension.new( subscription: subscription, mutation: mutation, query: query, directives: directives, pos: loc, filename: @filename, source_string: @graphql_str, ) else expect_one_of([:SCHEMA, :SCALAR, :TYPE, :ENUM, :INPUT, :UNION, :INTERFACE]) end else loc = pos desc = at?(:STRING) ? string_value : nil defn_loc = pos case token_name when :SCHEMA advance_token directives = parse_directives query = mutation = subscription = nil expect_token :LCURLY while !at?(:RCURLY) if at?(:QUERY) advance_token expect_token(:COLON) query = parse_name elsif at?(:MUTATION) advance_token expect_token(:COLON) mutation = parse_name elsif at?(:SUBSCRIPTION) advance_token expect_token(:COLON) subscription = parse_name else expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION]) end end expect_token :RCURLY SchemaDefinition.new(pos: loc, definition_pos: defn_loc, query: query, mutation: mutation, subscription: subscription, directives: directives, filename: @filename, source_string: @graphql_str) when :DIRECTIVE advance_token expect_token :DIR_SIGN name = parse_name arguments_definition = parse_argument_definitions repeatable = if at?(:REPEATABLE) advance_token true else false end expect_token :ON directive_locations = [DirectiveLocation.new(pos: pos, name: parse_name, filename: @filename, source_string: @graphql_str)] while at?(:PIPE) advance_token directive_locations << DirectiveLocation.new(pos: pos, name: parse_name, filename: @filename, source_string: @graphql_str) end DirectiveDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, arguments: arguments_definition, locations: directive_locations, repeatable: repeatable, filename: @filename, source_string: @graphql_str) when :TYPE advance_token name = parse_name implements_interfaces = parse_implements directives = parse_directives field_defns = at?(:LCURLY) ? parse_field_definitions : EMPTY_ARRAY ObjectTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, interfaces: implements_interfaces, directives: directives, fields: field_defns, filename: @filename, source_string: @graphql_str) when :INTERFACE advance_token name = parse_name interfaces = parse_implements directives = parse_directives fields_definition = parse_field_definitions InterfaceTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, fields: fields_definition, interfaces: interfaces, filename: @filename, source_string: @graphql_str) when :UNION advance_token name = parse_name directives = parse_directives union_member_types = parse_union_members UnionTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, types: union_member_types, filename: @filename, source_string: @graphql_str) when :SCALAR advance_token name = parse_name directives = parse_directives ScalarTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, filename: @filename, source_string: @graphql_str) when :ENUM advance_token name = parse_name directives = parse_directives enum_values_definition = parse_enum_value_definitions Nodes::EnumTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, values: enum_values_definition, filename: @filename, source_string: @graphql_str) when :INPUT advance_token name = parse_name directives = parse_directives input_fields_definition = parse_input_object_field_definitions InputObjectTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, fields: input_fields_definition, filename: @filename, source_string: @graphql_str) else expect_one_of([:SCHEMA, :SCALAR, :TYPE, :ENUM, :INPUT, :UNION, :INTERFACE]) end end end def parse_input_object_field_definitions if at?(:LCURLY) expect_token :LCURLY list = [] while !at?(:RCURLY) list << parse_input_value_definition end expect_token :RCURLY list else EMPTY_ARRAY end end def parse_enum_value_definitions if at?(:LCURLY) expect_token :LCURLY list = [] while !at?(:RCURLY) v_loc = pos description = if at?(:STRING); string_value; end defn_loc = pos enum_value = parse_enum_name v_directives = parse_directives list << EnumValueDefinition.new(pos: v_loc, definition_pos: defn_loc, description: description, name: enum_value, directives: v_directives, filename: @filename, source_string: @graphql_str) end expect_token :RCURLY list else EMPTY_ARRAY end end def parse_union_members if at?(:EQUALS) expect_token :EQUALS list = [parse_type_name] while at?(:PIPE) advance_token list << parse_type_name end list else EMPTY_ARRAY end end def parse_implements if at?(:IMPLEMENTS) advance_token list = [] while true advance_token if at?(:AMP) break unless at?(:IDENTIFIER) list << parse_type_name end list else EMPTY_ARRAY end end def parse_field_definitions expect_token :LCURLY list = [] while !at?(:RCURLY) loc = pos description = if at?(:STRING); string_value; end defn_loc = pos name = parse_name arguments_definition = parse_argument_definitions expect_token :COLON type = self.type directives = parse_directives list << FieldDefinition.new(pos: loc, definition_pos: defn_loc, description: description, name: name, arguments: arguments_definition, type: type, directives: directives, filename: @filename, source_string: @graphql_str) end expect_token :RCURLY list end def parse_argument_definitions if at?(:LPAREN) advance_token list = [] while !at?(:RPAREN) list << parse_input_value_definition end expect_token :RPAREN list else EMPTY_ARRAY end end def parse_input_value_definition loc = pos description = if at?(:STRING); string_value; end defn_loc = pos name = parse_name expect_token :COLON type = self.type default_value = if at?(:EQUALS) advance_token value else nil end directives = parse_directives InputValueDefinition.new(pos: loc, definition_pos: defn_loc, description: description, name: name, type: type, default_value: default_value, directives: directives, filename: @filename, source_string: @graphql_str) end def type type = case token_name when :IDENTIFIER parse_type_name when :LBRACKET list_type end if at?(:BANG) type = Nodes::NonNullType.new(pos: pos, of_type: type) expect_token(:BANG) end type end def list_type loc = pos expect_token(:LBRACKET) type = Nodes::ListType.new(pos: loc, of_type: self.type) expect_token(:RBRACKET) type end def parse_operation_type val = if at?(:QUERY) "query" elsif at?(:MUTATION) "mutation" elsif at?(:SUBSCRIPTION) "subscription" else expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION]) end advance_token val end def selection_set expect_token(:LCURLY) selections = [] while @token_name != :RCURLY selections << if at?(:ELLIPSIS) loc = pos advance_token case token_name when :ON, :DIR_SIGN, :LCURLY if_type = if at?(:ON) advance_token parse_type_name else nil end directives = parse_directives Nodes::InlineFragment.new(pos: loc, type: if_type, directives: directives, selections: selection_set, filename: @filename, source_string: @graphql_str) else name = parse_name_without_on directives = parse_directives # Can this ever happen? # expect_token(:IDENTIFIER) if at?(:ON) FragmentSpread.new(pos: loc, name: name, directives: directives, filename: @filename, source_string: @graphql_str) end else loc = pos name = parse_name field_alias = nil if at?(:COLON) advance_token field_alias = name name = parse_name end arguments = at?(:LPAREN) ? parse_arguments : nil directives = at?(:DIR_SIGN) ? parse_directives : nil selection_set = at?(:LCURLY) ? self.selection_set : nil Nodes::Field.new(pos: loc, field_alias: field_alias, name: name, arguments: arguments, directives: directives, selections: selection_set, filename: @filename, source_string: @graphql_str) end end expect_token(:RCURLY) selections end def parse_name case token_name when :IDENTIFIER expect_token_value(:IDENTIFIER) when :SCHEMA advance_token "schema" when :SCALAR advance_token "scalar" when :IMPLEMENTS advance_token "implements" when :INTERFACE advance_token "interface" when :UNION advance_token "union" when :ENUM advance_token "enum" when :INPUT advance_token "input" when :DIRECTIVE advance_token "directive" when :TYPE advance_token "type" when :QUERY advance_token "query" when :MUTATION advance_token "mutation" when :SUBSCRIPTION advance_token "subscription" when :TRUE advance_token "true" when :FALSE advance_token "false" when :FRAGMENT advance_token "fragment" when :REPEATABLE advance_token "repeatable" when :NULL advance_token "null" when :ON advance_token "on" when :DIRECTIVE advance_token "directive" when :EXTEND advance_token "extend" else expect_token(:NAME) end end def parse_name_without_on if at?(:ON) expect_token(:IDENTIFIER) else parse_name end end # Any identifier, but not true, false, or null def parse_enum_name if at?(:TRUE) || at?(:FALSE) || at?(:NULL) expect_token(:IDENTIFIER) else parse_name end end def parse_type_name TypeName.new(pos: pos, name: parse_name, filename: @filename, source_string: @graphql_str) end def parse_directives if at?(:DIR_SIGN) dirs = [] while at?(:DIR_SIGN) loc = pos advance_token name = parse_name arguments = parse_arguments dirs << Nodes::Directive.new(pos: loc, name: name, arguments: arguments, filename: @filename, source_string: @graphql_str) end dirs else EMPTY_ARRAY end end def parse_arguments if at?(:LPAREN) advance_token args = [] while !at?(:RPAREN) loc = pos name = parse_name expect_token(:COLON) args << Nodes::Argument.new(pos: loc, name: name, value: value, filename: @filename, source_string: @graphql_str) end if args.empty? expect_token(:ARGUMENT_NAME) # At least one argument is required end expect_token(:RPAREN) args else EMPTY_ARRAY end end def string_value token_value = @lexer.string_value expect_token :STRING token_value end def value case token_name when :INT expect_token_value(:INT).to_i when :FLOAT expect_token_value(:FLOAT).to_f when :STRING string_value when :TRUE advance_token true when :FALSE advance_token false when :NULL advance_token NullValue.new(pos: pos, name: "null", filename: @filename, source_string: @graphql_str) when :IDENTIFIER Nodes::Enum.new(pos: pos, name: expect_token_value(:IDENTIFIER), filename: @filename, source_string: @graphql_str) when :LBRACKET advance_token list = [] while !at?(:RBRACKET) list << value end expect_token(:RBRACKET) list when :LCURLY start = pos advance_token args = [] while !at?(:RCURLY) loc = pos n = parse_name expect_token(:COLON) args << Argument.new(pos: loc, name: n, value: value, filename: @filename, source_string: @graphql_str) end expect_token(:RCURLY) InputObject.new(pos: start, arguments: args, filename: @filename, source_string: @graphql_str) when :VAR_SIGN loc = pos advance_token VariableIdentifier.new(pos: loc, name: parse_name, filename: @filename, source_string: @graphql_str) else expect_token(:VALUE) end end def at?(expected_token_name) @token_name == expected_token_name end def expect_token(expected_token_name) unless @token_name == expected_token_name raise_parse_error("Expected #{expected_token_name}, actual: #{token_name || "(none)"} (#{debug_token_value.inspect})") end advance_token end def expect_one_of(token_names) raise_parse_error("Expected one of #{token_names.join(", ")}, actual: #{token_name || "NOTHING"} (#{debug_token_value.inspect})") end def raise_parse_error(message) message += " at [#{@lexer.line_number}, #{@lexer.column_number}]" raise GraphQL::ParseError.new( message, @lexer.line_number, @lexer.column_number, @graphql_str, filename: @filename, ) end # Only use when we care about the expected token's value def expect_token_value(tok) token_value = @lexer.token_value expect_token(tok) token_value end # token_value works for when the scanner matched something # which is usually fine and it's good for it to be fast at that. def debug_token_value @lexer.debug_token_value(token_name) end end end end graphql-ruby-2.2.17/lib/graphql/language/printer.rb000066400000000000000000000426031476434635200222420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language class Printer OMISSION = "... (truncated)" class TruncatableBuffer class TruncateSizeReached < StandardError; end DEFAULT_INIT_CAPACITY = 500 def initialize(truncate_size: nil) @out = String.new(capacity: truncate_size || DEFAULT_INIT_CAPACITY) @truncate_size = truncate_size end def append(other) if @truncate_size && (@out.size + other.size) > @truncate_size @out << other.slice(0, @truncate_size - @out.size) raise(TruncateSizeReached, "Truncate size reached") else @out << other end end def to_string @out end end # Turn an arbitrary AST node back into a string. # # @example Turning a document into a query string # document = GraphQL.parse(query_string) # GraphQL::Language::Printer.new.print(document) # # => "{ ... }" # # # @example Building a custom printer # # class MyPrinter < GraphQL::Language::Printer # def print_argument(arg) # print_string("#{arg.name}: ") # end # end # # MyPrinter.new.print(document) # # => "mutation { pay(creditCard: ) { success } }" # # @param node [Nodes::AbstractNode] # @param indent [String] Whitespace to add to the printed node # @param truncate_size [Integer, nil] The size to truncate to. # @return [String] Valid GraphQL for `node` def print(node, indent: "", truncate_size: nil) truncate_size = truncate_size ? [truncate_size - OMISSION.size, 0].max : nil @out = TruncatableBuffer.new(truncate_size: truncate_size) print_node(node, indent: indent) @out.to_string rescue TruncatableBuffer::TruncateSizeReached @out.to_string << OMISSION end protected def print_string(str) @out.append(str) end def print_document(document) document.definitions.each_with_index do |d, i| print_node(d) print_string("\n\n") if i < document.definitions.size - 1 end end def print_argument(argument) print_string(argument.name) print_string(": ") print_node(argument.value) end def print_input_object(input_object) print_string("{") input_object.arguments.each_with_index do |a, i| print_argument(a) print_string(", ") if i < input_object.arguments.size - 1 end print_string("}") end def print_directive(directive) print_string("@") print_string(directive.name) if directive.arguments.any? print_string("(") directive.arguments.each_with_index do |a, i| print_argument(a) print_string(", ") if i < directive.arguments.size - 1 end print_string(")") end end def print_enum(enum) print_string(enum.name) end def print_null_value print_string("null") end def print_field(field, indent: "") print_string(indent) if field.alias print_string(field.alias) print_string(": ") end print_string(field.name) if field.arguments.any? print_string("(") field.arguments.each_with_index do |a, i| print_argument(a) print_string(", ") if i < field.arguments.size - 1 end print_string(")") end print_directives(field.directives) print_selections(field.selections, indent: indent) end def print_fragment_definition(fragment_def, indent: "") print_string(indent) print_string("fragment") if fragment_def.name print_string(" ") print_string(fragment_def.name) end if fragment_def.type print_string(" on ") print_node(fragment_def.type) end print_directives(fragment_def.directives) print_selections(fragment_def.selections, indent: indent) end def print_fragment_spread(fragment_spread, indent: "") print_string(indent) print_string("...") print_string(fragment_spread.name) print_directives(fragment_spread.directives) end def print_inline_fragment(inline_fragment, indent: "") print_string(indent) print_string("...") if inline_fragment.type print_string(" on ") print_node(inline_fragment.type) end print_directives(inline_fragment.directives) print_selections(inline_fragment.selections, indent: indent) end def print_list_type(list_type) print_string("[") print_node(list_type.of_type) print_string("]") end def print_non_null_type(non_null_type) print_node(non_null_type.of_type) print_string("!") end def print_operation_definition(operation_definition, indent: "") print_string(indent) print_string(operation_definition.operation_type) if operation_definition.name print_string(" ") print_string(operation_definition.name) end if operation_definition.variables.any? print_string("(") operation_definition.variables.each_with_index do |v, i| print_variable_definition(v) print_string(", ") if i < operation_definition.variables.size - 1 end print_string(")") end print_directives(operation_definition.directives) print_selections(operation_definition.selections, indent: indent) end def print_type_name(type_name) print_string(type_name.name) end def print_variable_definition(variable_definition) print_string("$") print_string(variable_definition.name) print_string(": ") print_node(variable_definition.type) unless variable_definition.default_value.nil? print_string(" = ") print_node(variable_definition.default_value) end variable_definition.directives.each do |dir| print_string(" ") print_directive(dir) end end def print_variable_identifier(variable_identifier) print_string("$") print_string(variable_identifier.name) end def print_schema_definition(schema, extension: false) has_conventional_names = (schema.query.nil? || schema.query == 'Query') && (schema.mutation.nil? || schema.mutation == 'Mutation') && (schema.subscription.nil? || schema.subscription == 'Subscription') if has_conventional_names && schema.directives.empty? return end extension ? print_string("extend schema") : print_string("schema") if schema.directives.any? schema.directives.each do |dir| print_string("\n ") print_node(dir) end if !has_conventional_names print_string("\n") end end if !has_conventional_names if schema.directives.empty? print_string(" ") end print_string("{\n") print_string(" query: #{schema.query}\n") if schema.query print_string(" mutation: #{schema.mutation}\n") if schema.mutation print_string(" subscription: #{schema.subscription}\n") if schema.subscription print_string("}") end end def print_scalar_type_definition(scalar_type, extension: false) extension ? print_string("extend ") : print_description(scalar_type) print_string("scalar ") print_string(scalar_type.name) print_directives(scalar_type.directives) end def print_object_type_definition(object_type, extension: false) extension ? print_string("extend ") : print_description(object_type) print_string("type ") print_string(object_type.name) print_implements(object_type) unless object_type.interfaces.empty? print_directives(object_type.directives) print_field_definitions(object_type.fields) end def print_implements(type) print_string(" implements ") i = 0 type.interfaces.each do |int| if i > 0 print_string(" & ") end print_string(int.name) i += 1 end end def print_input_value_definition(input_value) print_string(input_value.name) print_string(": ") print_node(input_value.type) unless input_value.default_value.nil? print_string(" = ") print_node(input_value.default_value) end print_directives(input_value.directives) end def print_arguments(arguments, indent: "") if arguments.all? { |arg| !arg.description } print_string("(") arguments.each_with_index do |arg, i| print_input_value_definition(arg) print_string(", ") if i < arguments.size - 1 end print_string(")") return end print_string("(\n") arguments.each_with_index do |arg, i| print_description(arg, indent: " " + indent, first_in_block: i == 0) print_string(" ") print_string(indent) print_input_value_definition(arg) print_string("\n") if i < arguments.size - 1 end print_string("\n") print_string(indent) print_string(")") end def print_field_definition(field) print_string(field.name) unless field.arguments.empty? print_arguments(field.arguments, indent: " ") end print_string(": ") print_node(field.type) print_directives(field.directives) end def print_interface_type_definition(interface_type, extension: false) extension ? print_string("extend ") : print_description(interface_type) print_string("interface ") print_string(interface_type.name) print_implements(interface_type) if interface_type.interfaces.any? print_directives(interface_type.directives) print_field_definitions(interface_type.fields) end def print_union_type_definition(union_type, extension: false) extension ? print_string("extend ") : print_description(union_type) print_string("union ") print_string(union_type.name) print_directives(union_type.directives) if union_type.types.any? print_string(" = ") i = 0 union_type.types.each do |t| if i > 0 print_string(" | ") end print_string(t.name) i += 1 end end end def print_enum_type_definition(enum_type, extension: false) extension ? print_string("extend ") : print_description(enum_type) print_string("enum ") print_string(enum_type.name) print_directives(enum_type.directives) if enum_type.values.any? print_string(" {\n") enum_type.values.each.with_index do |value, i| print_description(value, indent: " ", first_in_block: i == 0) print_enum_value_definition(value) end print_string("}") end end def print_enum_value_definition(enum_value) print_string(" ") print_string(enum_value.name) print_directives(enum_value.directives) print_string("\n") end def print_input_object_type_definition(input_object_type, extension: false) extension ? print_string("extend ") : print_description(input_object_type) print_string("input ") print_string(input_object_type.name) print_directives(input_object_type.directives) if !input_object_type.fields.empty? print_string(" {\n") input_object_type.fields.each.with_index do |field, i| print_description(field, indent: " ", first_in_block: i == 0) print_string(" ") print_input_value_definition(field) print_string("\n") end print_string("}") end end def print_directive_definition(directive) print_description(directive) print_string("directive @") print_string(directive.name) if directive.arguments.any? print_arguments(directive.arguments) end if directive.repeatable print_string(" repeatable") end print_string(" on ") i = 0 directive.locations.each do |loc| if i > 0 print_string(" | ") end print_string(loc.name) i += 1 end end def print_description(node, indent: "", first_in_block: true) return unless node.description print_string("\n") if indent != "" && !first_in_block print_string(GraphQL::Language::BlockString.print(node.description, indent: indent)) end def print_field_definitions(fields) return if fields.empty? print_string(" {\n") i = 0 fields.each do |field| print_description(field, indent: " ", first_in_block: i == 0) print_string(" ") print_field_definition(field) print_string("\n") i += 1 end print_string("}") end def print_directives(directives) return if directives.empty? directives.each do |d| print_string(" ") print_directive(d) end end def print_selections(selections, indent: "") return if selections.empty? print_string(" {\n") selections.each do |selection| print_node(selection, indent: indent + " ") print_string("\n") end print_string(indent) print_string("}") end def print_node(node, indent: "") case node when Nodes::Document print_document(node) when Nodes::Argument print_argument(node) when Nodes::Directive print_directive(node) when Nodes::Enum print_enum(node) when Nodes::NullValue print_null_value when Nodes::Field print_field(node, indent: indent) when Nodes::FragmentDefinition print_fragment_definition(node, indent: indent) when Nodes::FragmentSpread print_fragment_spread(node, indent: indent) when Nodes::InlineFragment print_inline_fragment(node, indent: indent) when Nodes::InputObject print_input_object(node) when Nodes::ListType print_list_type(node) when Nodes::NonNullType print_non_null_type(node) when Nodes::OperationDefinition print_operation_definition(node, indent: indent) when Nodes::TypeName print_type_name(node) when Nodes::VariableDefinition print_variable_definition(node) when Nodes::VariableIdentifier print_variable_identifier(node) when Nodes::SchemaDefinition print_schema_definition(node) when Nodes::SchemaExtension print_schema_definition(node, extension: true) when Nodes::ScalarTypeDefinition print_scalar_type_definition(node) when Nodes::ScalarTypeExtension print_scalar_type_definition(node, extension: true) when Nodes::ObjectTypeDefinition print_object_type_definition(node) when Nodes::ObjectTypeExtension print_object_type_definition(node, extension: true) when Nodes::InputValueDefinition print_input_value_definition(node) when Nodes::FieldDefinition print_field_definition(node) when Nodes::InterfaceTypeDefinition print_interface_type_definition(node) when Nodes::InterfaceTypeExtension print_interface_type_definition(node, extension: true) when Nodes::UnionTypeDefinition print_union_type_definition(node) when Nodes::UnionTypeExtension print_union_type_definition(node, extension: true) when Nodes::EnumTypeDefinition print_enum_type_definition(node) when Nodes::EnumTypeExtension print_enum_type_definition(node, extension: true) when Nodes::EnumValueDefinition print_enum_value_definition(node) when Nodes::InputObjectTypeDefinition print_input_object_type_definition(node) when Nodes::InputObjectTypeExtension print_input_object_type_definition(node, extension: true) when Nodes::DirectiveDefinition print_directive_definition(node) when FalseClass, Float, Integer, NilClass, String, TrueClass, Symbol print_string(GraphQL::Language.serialize(node)) when Array print_string("[") node.each_with_index do |v, i| print_node(v) print_string(", ") if i < node.length - 1 end print_string("]") when Hash print_string("{") node.each_with_index do |(k, v), i| print_string(k) print_string(": ") print_node(v) print_string(", ") if i < node.length - 1 end print_string("}") else print_string(GraphQL::Language.serialize(node.to_s)) end end end end end graphql-ruby-2.2.17/lib/graphql/language/sanitized_printer.rb000066400000000000000000000155471476434635200243230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # A custom printer used to print sanitized queries. It inlines provided variables # within the query for facilitate logging and analysis of queries. # # The printer returns `nil` if the query is invalid. # # Since the GraphQL Ruby AST for a GraphQL query doesnt contain any reference # on the type of fields or arguments, we have to track the current object, field # and input type while printing the query. # # @example Printing a scrubbed string # printer = QueryPrinter.new(query) # puts printer.sanitized_query_string # # @see {Query#sanitized_query_string} class SanitizedPrinter < GraphQL::Language::Printer REDACTED = "\"\"" def initialize(query, inline_variables: true) @query = query @current_type = nil @current_field = nil @current_input_type = nil @inline_variables = inline_variables end # @return [String, nil] A scrubbed query string, if the query was valid. def sanitized_query_string if query.valid? print(query.document) else nil end end def print_node(node, indent: "") case node when FalseClass, Float, Integer, String, TrueClass if @current_argument && redact_argument_value?(@current_argument, node) print_string(redacted_argument_value(@current_argument)) else super end when Array old_input_type = @current_input_type if @current_input_type && @current_input_type.list? @current_input_type = @current_input_type.of_type @current_input_type = @current_input_type.of_type if @current_input_type.non_null? end super @current_input_type = old_input_type else super end end # Indicates whether or not to redact non-null values for the given argument. Defaults to redacting all strings # arguments but this can be customized by subclasses. def redact_argument_value?(argument, value) # Default to redacting any strings or custom scalars encoded as strings type = argument.type.unwrap value.is_a?(String) && type.kind.scalar? && (type.graphql_name == "String" || !type.default_scalar?) end # Returns the value to use for redacted versions of the given argument. Defaults to the # string "". def redacted_argument_value(argument) REDACTED end def print_argument(argument) # We won't have type information if we're recursing into a custom scalar return super if @current_input_type && @current_input_type.kind.scalar? arg_owner = @current_input_type || @current_directive || @current_field old_current_argument = @current_argument @current_argument = arg_owner.get_argument(argument.name, @query.context) old_input_type = @current_input_type @current_input_type = @current_argument.type.non_null? ? @current_argument.type.of_type : @current_argument.type argument_value = if coerce_argument_value_to_list?(@current_input_type, argument.value) [argument.value] else argument.value end print_string("#{argument.name}: ") print_node(argument_value) @current_input_type = old_input_type @current_argument = old_current_argument end def coerce_argument_value_to_list?(type, value) type.list? && !value.is_a?(Array) && !value.nil? && !value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) end def print_variable_identifier(variable_id) if @inline_variables variable_value = query.variables[variable_id.name] print_node(value_to_ast(variable_value, @current_input_type)) else super end end def print_field(field, indent: "") @current_field = query.get_field(@current_type, field.name) old_type = @current_type @current_type = @current_field.type.unwrap super @current_type = old_type end def print_inline_fragment(inline_fragment, indent: "") old_type = @current_type if inline_fragment.type @current_type = query.get_type(inline_fragment.type.name) end super @current_type = old_type end def print_fragment_definition(fragment_def, indent: "") old_type = @current_type @current_type = query.get_type(fragment_def.type.name) super @current_type = old_type end def print_directive(directive) @current_directive = query.schema.directives[directive.name] super @current_directive = nil end # Print the operation definition but do not include the variable # definitions since we will inline them within the query def print_operation_definition(operation_definition, indent: "") old_type = @current_type @current_type = query.schema.public_send(operation_definition.operation_type) if @inline_variables print_string("#{indent}#{operation_definition.operation_type}") print_string(" #{operation_definition.name}") if operation_definition.name print_directives(operation_definition.directives) print_selections(operation_definition.selections, indent: indent) else super end @current_type = old_type end private def value_to_ast(value, type) type = type.of_type if type.non_null? if value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type.kind.name when "INPUT_OBJECT" value = if value.respond_to?(:to_unsafe_h) # for ActionController::Parameters value.to_unsafe_h else value.to_h end arguments = value.map do |key, val| sub_type = type.get_argument(key.to_s, @query.context).type GraphQL::Language::Nodes::Argument.new( name: key.to_s, value: value_to_ast(val, sub_type) ) end GraphQL::Language::Nodes::InputObject.new( arguments: arguments ) when "LIST" if value.is_a?(Array) value.map { |v| value_to_ast(v, type.of_type) } else [value].map { |v| value_to_ast(v, type.of_type) } end when "ENUM" if value.is_a?(GraphQL::Language::Nodes::Enum) # if it was a default value, it's already wrapped value else GraphQL::Language::Nodes::Enum.new(name: value) end else value end end attr_reader :query end end end graphql-ruby-2.2.17/lib/graphql/language/static_visitor.rb000066400000000000000000000140571476434635200236270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Like `GraphQL::Language::Visitor` except it doesn't support # making changes to the document -- only visiting it as-is. class StaticVisitor def initialize(document) @document = document end # Visit `document` and all children # @return [void] def visit # `@document` may be any kind of node: visit_method = @document.visit_method result = public_send(visit_method, @document, nil) @result = if result.is_a?(Array) result.first else # The node wasn't modified @document end end def on_document_children(document_node) document_node.children.each do |child_node| visit_method = child_node.visit_method public_send(visit_method, child_node, document_node) end end def on_field_children(new_node) new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop on_argument(arg_node, new_node) end visit_directives(new_node) visit_selections(new_node) end def visit_directives(new_node) new_node.directives.each do |dir_node| on_directive(dir_node, new_node) end end def visit_selections(new_node) new_node.selections.each do |selection| case selection when GraphQL::Language::Nodes::Field on_field(selection, new_node) when GraphQL::Language::Nodes::InlineFragment on_inline_fragment(selection, new_node) when GraphQL::Language::Nodes::FragmentSpread on_fragment_spread(selection, new_node) else raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})" end end end def on_fragment_definition_children(new_node) visit_directives(new_node) visit_selections(new_node) end alias :on_inline_fragment_children :on_fragment_definition_children def on_operation_definition_children(new_node) new_node.variables.each do |arg_node| on_variable_definition(arg_node, new_node) end visit_directives(new_node) visit_selections(new_node) end def on_argument_children(new_node) new_node.children.each do |value_node| case value_node when Language::Nodes::VariableIdentifier on_variable_identifier(value_node, new_node) when Language::Nodes::InputObject on_input_object(value_node, new_node) when Language::Nodes::Enum on_enum(value_node, new_node) when Language::Nodes::NullValue on_null_value(value_node, new_node) else raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})" end end end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # We don't use `alias` here because it breaks `super` def self.make_visit_methods(ast_node_class) node_method = ast_node_class.visit_method children_of_type = ast_node_class.children_of_type child_visit_method = :"#{node_method}_children" class_eval(<<-RUBY, __FILE__, __LINE__ + 1) # The default implementation for visiting an AST node. # It doesn't _do_ anything, but it continues to visiting the node's children. # To customize this hook, override one of its make_visit_methods (or the base method?) # in your subclasses. # # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node. # @return [void] def #{node_method}(node, parent) #{ if method_defined?(child_visit_method) "#{child_visit_method}(node)" elsif children_of_type children_of_type.map do |child_accessor, child_class| "node.#{child_accessor}.each do |child_node| #{child_class.visit_method}(child_node, node) end" end.join("\n") else "" end } end RUBY end [ Language::Nodes::Argument, Language::Nodes::Directive, Language::Nodes::DirectiveDefinition, Language::Nodes::DirectiveLocation, Language::Nodes::Document, Language::Nodes::Enum, Language::Nodes::EnumTypeDefinition, Language::Nodes::EnumTypeExtension, Language::Nodes::EnumValueDefinition, Language::Nodes::Field, Language::Nodes::FieldDefinition, Language::Nodes::FragmentDefinition, Language::Nodes::FragmentSpread, Language::Nodes::InlineFragment, Language::Nodes::InputObject, Language::Nodes::InputObjectTypeDefinition, Language::Nodes::InputObjectTypeExtension, Language::Nodes::InputValueDefinition, Language::Nodes::InterfaceTypeDefinition, Language::Nodes::InterfaceTypeExtension, Language::Nodes::ListType, Language::Nodes::NonNullType, Language::Nodes::NullValue, Language::Nodes::ObjectTypeDefinition, Language::Nodes::ObjectTypeExtension, Language::Nodes::OperationDefinition, Language::Nodes::ScalarTypeDefinition, Language::Nodes::ScalarTypeExtension, Language::Nodes::SchemaDefinition, Language::Nodes::SchemaExtension, Language::Nodes::TypeName, Language::Nodes::UnionTypeDefinition, Language::Nodes::UnionTypeExtension, Language::Nodes::VariableDefinition, Language::Nodes::VariableIdentifier, ].each do |ast_node_class| make_visit_methods(ast_node_class) end # rubocop:disable Development/NoEvalCop end end end graphql-ruby-2.2.17/lib/graphql/language/token.rb000066400000000000000000000014511476434635200216730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Emitted by the lexer and passed to the parser. # Contains type, value and position data. class Token # @return [Symbol] The kind of token this is attr_reader :name # @return [String] The text of this token attr_reader :value attr_reader :prev_token, :line, :col def initialize(name, value, line, col, prev_token) @name = name @value = -value @line = line @col = col @prev_token = prev_token end alias to_s value def to_i; @value.to_i; end def to_f; @value.to_f; end def line_and_column [@line, @col] end def inspect "(#{@name} #{@value.inspect} [#{@line}:#{@col}])" end end end end graphql-ruby-2.2.17/lib/graphql/language/visitor.rb000066400000000000000000000261061476434635200222560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Depth-first traversal through the tree, calling hooks at each stop. # # @example Create a visitor counting certain field names # class NameCounter < GraphQL::Language::Visitor # def initialize(document, field_name) # super(document) # @field_name = field_name # @count = 0 # end # # attr_reader :count # # def on_field(node, parent) # # if this field matches our search, increment the counter # if node.name == @field_name # @count += 1 # end # # Continue visiting subfields: # super # end # end # # # Initialize a visitor # visitor = NameCounter.new(document, "name") # # Run it # visitor.visit # # Check the result # visitor.count # # => 3 # # @see GraphQL::Language::StaticVisitor for a faster visitor that doesn't support modifying the document class Visitor class DeleteNode; end # When this is returned from a visitor method, # Then the `node` passed into the method is removed from `parent`'s children. DELETE_NODE = DeleteNode.new def initialize(document) @document = document @result = nil end # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied attr_reader :result # Visit `document` and all children # @return [void] def visit # `@document` may be any kind of node: visit_method = :"#{@document.visit_method}_with_modifications" result = public_send(visit_method, @document, nil) @result = if result.is_a?(Array) result.first else # The node wasn't modified @document end end def on_document_children(document_node) new_node = document_node document_node.children.each do |child_node| visit_method = :"#{child_node.visit_method}_with_modifications" new_child_and_node = public_send(visit_method, child_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end def on_field_children(new_node) new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop new_child_and_node = on_argument_with_modifications(arg_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node = visit_directives(new_node) new_node = visit_selections(new_node) new_node end def visit_directives(new_node) new_node.directives.each do |dir_node| new_child_and_node = on_directive_with_modifications(dir_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end def visit_selections(new_node) new_node.selections.each do |selection| new_child_and_node = case selection when GraphQL::Language::Nodes::Field on_field_with_modifications(selection, new_node) when GraphQL::Language::Nodes::InlineFragment on_inline_fragment_with_modifications(selection, new_node) when GraphQL::Language::Nodes::FragmentSpread on_fragment_spread_with_modifications(selection, new_node) else raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})" end # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end def on_fragment_definition_children(new_node) new_node = visit_directives(new_node) new_node = visit_selections(new_node) new_node end alias :on_inline_fragment_children :on_fragment_definition_children def on_operation_definition_children(new_node) new_node.variables.each do |arg_node| new_child_and_node = on_variable_definition_with_modifications(arg_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node = visit_directives(new_node) new_node = visit_selections(new_node) new_node end def on_argument_children(new_node) new_node.children.each do |value_node| new_child_and_node = case value_node when Language::Nodes::VariableIdentifier on_variable_identifier_with_modifications(value_node, new_node) when Language::Nodes::InputObject on_input_object_with_modifications(value_node, new_node) when Language::Nodes::Enum on_enum_with_modifications(value_node, new_node) when Language::Nodes::NullValue on_null_value_with_modifications(value_node, new_node) else raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})" end # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # We don't use `alias` here because it breaks `super` def self.make_visit_methods(ast_node_class) node_method = ast_node_class.visit_method children_of_type = ast_node_class.children_of_type child_visit_method = :"#{node_method}_children" class_eval(<<-RUBY, __FILE__, __LINE__ + 1) # The default implementation for visiting an AST node. # It doesn't _do_ anything, but it continues to visiting the node's children. # To customize this hook, override one of its make_visit_methods (or the base method?) # in your subclasses. # # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node. # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`. def #{node_method}(node, parent) if node.equal?(DELETE_NODE) # This might be passed to `super(DELETE_NODE, ...)` # by a user hook, don't want to keep visiting in that case. [node, parent] else new_node = node #{ if method_defined?(child_visit_method) "new_node = #{child_visit_method}(new_node)" elsif children_of_type children_of_type.map do |child_accessor, child_class| "node.#{child_accessor}.each do |child_node| new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end" end.join("\n") else "" end } if new_node.equal?(node) [node, parent] else [new_node, parent] end end end def #{node_method}_with_modifications(node, parent) new_node_and_new_parent = #{node_method}(node, parent) apply_modifications(node, parent, new_node_and_new_parent) end RUBY end [ Language::Nodes::Argument, Language::Nodes::Directive, Language::Nodes::DirectiveDefinition, Language::Nodes::DirectiveLocation, Language::Nodes::Document, Language::Nodes::Enum, Language::Nodes::EnumTypeDefinition, Language::Nodes::EnumTypeExtension, Language::Nodes::EnumValueDefinition, Language::Nodes::Field, Language::Nodes::FieldDefinition, Language::Nodes::FragmentDefinition, Language::Nodes::FragmentSpread, Language::Nodes::InlineFragment, Language::Nodes::InputObject, Language::Nodes::InputObjectTypeDefinition, Language::Nodes::InputObjectTypeExtension, Language::Nodes::InputValueDefinition, Language::Nodes::InterfaceTypeDefinition, Language::Nodes::InterfaceTypeExtension, Language::Nodes::ListType, Language::Nodes::NonNullType, Language::Nodes::NullValue, Language::Nodes::ObjectTypeDefinition, Language::Nodes::ObjectTypeExtension, Language::Nodes::OperationDefinition, Language::Nodes::ScalarTypeDefinition, Language::Nodes::ScalarTypeExtension, Language::Nodes::SchemaDefinition, Language::Nodes::SchemaExtension, Language::Nodes::TypeName, Language::Nodes::UnionTypeDefinition, Language::Nodes::UnionTypeExtension, Language::Nodes::VariableDefinition, Language::Nodes::VariableIdentifier, ].each do |ast_node_class| make_visit_methods(ast_node_class) end # rubocop:enable Development/NoEvalCop private def apply_modifications(node, parent, new_node_and_new_parent) if new_node_and_new_parent.is_a?(Array) new_node = new_node_and_new_parent[0] new_parent = new_node_and_new_parent[1] if new_node.is_a?(Nodes::AbstractNode) && !node.equal?(new_node) # The user-provided hook returned a new node. new_parent = new_parent && new_parent.replace_child(node, new_node) return new_node, new_parent elsif new_node.equal?(DELETE_NODE) # The user-provided hook requested to remove this node new_parent = new_parent && new_parent.delete_child(node) return nil, new_parent elsif new_node_and_new_parent.none? { |n| n == nil || n.class < Nodes::AbstractNode } # The user-provided hook returned an array of who-knows-what # return nil here to signify that no changes should be made nil else new_node_and_new_parent end else # The user-provided hook didn't make any modifications. # In fact, the hook might have returned who-knows-what, so # ignore the return value and use the original values. new_node_and_new_parent end end end end end graphql-ruby-2.2.17/lib/graphql/load_application_object_failed_error.rb000066400000000000000000000017521476434635200263410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Raised when a argument is configured with `loads:` and the client provides an `ID`, # but no object is loaded for that ID. # # @see GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed, A hook which you can override in resolvers, mutations and input objects. class LoadApplicationObjectFailedError < GraphQL::ExecutionError # @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up attr_reader :argument # @return [String] The ID provided by the client attr_reader :id # @return [Object] The value found with this ID attr_reader :object # @return [GraphQL::Query::Context] attr_reader :context def initialize(argument:, id:, object:, context:) @id = id @argument = argument @object = object @context = context super("No object found for `#{argument.graphql_name}: #{id.inspect}`") end end end graphql-ruby-2.2.17/lib/graphql/name_validator.rb000066400000000000000000000004771476434635200217640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class NameValidator VALID_NAME_REGEX = /^[_a-zA-Z][_a-zA-Z0-9]*$/ def self.validate!(name) name = name.is_a?(String) ? name : name.to_s raise GraphQL::InvalidNameError.new(name, VALID_NAME_REGEX) unless name.match?(VALID_NAME_REGEX) end end end graphql-ruby-2.2.17/lib/graphql/pagination.rb000066400000000000000000000004441476434635200211220ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/array_connection" require "graphql/pagination/active_record_relation_connection" require "graphql/pagination/connections" require "graphql/pagination/mongoid_relation_connection" require "graphql/pagination/sequel_dataset_connection" graphql-ruby-2.2.17/lib/graphql/pagination/000077500000000000000000000000001476434635200205735ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/pagination/active_record_relation_connection.rb000066400000000000000000000034331476434635200300500ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination # Customizes `RelationConnection` to work with `ActiveRecord::Relation`s. class ActiveRecordRelationConnection < Pagination::RelationConnection private def relation_count(relation) int_or_hash = if already_loaded?(relation) relation.size elsif relation.respond_to?(:unscope) relation.unscope(:order).count(:all) else # Rails 3 relation.count end if int_or_hash.is_a?(Integer) int_or_hash else # Grouped relations return count-by-group hashes int_or_hash.length end end def relation_limit(relation) if relation.is_a?(Array) nil else relation.limit_value end end def relation_offset(relation) if relation.is_a?(Array) nil else relation.offset_value end end def null_relation(relation) if relation.respond_to?(:none) relation.none else # Rails 3 relation.where("1=2") end end def set_limit(nodes, limit) if already_loaded?(nodes) nodes.take(limit) else super end end def set_offset(nodes, offset) if already_loaded?(nodes) # If the client sent a bogus cursor beyond the size of the relation, # it might get `nil` from `#[...]`, so return an empty array in that case nodes[offset..-1] || [] else super end end private def already_loaded?(relation) relation.is_a?(Array) || relation.loaded? end end end end graphql-ruby-2.2.17/lib/graphql/pagination/array_connection.rb000066400000000000000000000037531476434635200244650ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/connection" module GraphQL module Pagination class ArrayConnection < Pagination::Connection def nodes load_nodes @nodes end def has_previous_page load_nodes @has_previous_page end def has_next_page load_nodes @has_next_page end def cursor_for(item) idx = items.find_index(item) + 1 encode(idx.to_s) end private def index_from_cursor(cursor) decode(cursor).to_i end # Populate all the pagination info _once_, # It doesn't do anything on subsequent calls. def load_nodes @nodes ||= begin sliced_nodes = if before && after end_idx = index_from_cursor(before) - 2 end_idx < 0 ? [] : items[index_from_cursor(after)..end_idx] || [] elsif before end_idx = index_from_cursor(before) - 2 end_idx < 0 ? [] : items[0..end_idx] || [] elsif after items[index_from_cursor(after)..-1] || [] else items end @has_previous_page = if last # There are items preceding the ones in this result sliced_nodes.count > last elsif after # We've paginated into the Array a bit, there are some behind us index_from_cursor(after) > 0 else false end @has_next_page = if before # The original array is longer than the `before` index index_from_cursor(before) < items.length + 1 elsif first # There are more items after these items sliced_nodes.count > first else false end limited_nodes = sliced_nodes limited_nodes = limited_nodes.first(first) if first limited_nodes = limited_nodes.last(last) if last limited_nodes end end end end end graphql-ruby-2.2.17/lib/graphql/pagination/connection.rb000066400000000000000000000234331476434635200232640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Pagination # A Connection wraps a list of items and provides cursor-based pagination over it. # # Connections were introduced by Facebook's `Relay` front-end framework, but # proved to be generally useful for GraphQL APIs. When in doubt, use connections # to serve lists (like Arrays, ActiveRecord::Relations) via GraphQL. # # Unlike the previous connection implementation, these default to bidirectional pagination. # # Pagination arguments and context may be provided at initialization or assigned later (see {Schema::Field::ConnectionExtension}). class Connection class PaginationImplementationMissingError < GraphQL::Error end # @return [Object] A list object, from the application. This is the unpaginated value passed into the connection. attr_reader :items # @return [GraphQL::Query::Context] attr_reader :context def context=(new_ctx) @context = new_ctx if @was_authorized_by_scope_items.nil? @was_authorized_by_scope_items = detect_was_authorized_by_scope_items end @context end # @return [Object] the object this collection belongs to attr_accessor :parent # Raw access to client-provided values. (`max_page_size` not applied to first or last.) attr_accessor :before_value, :after_value, :first_value, :last_value # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. def before if defined?(@before) @before else @before = @before_value == "" ? nil : @before_value end end # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. def after if defined?(@after) @after else @after = @after_value == "" ? nil : @after_value end end # @return [Hash Object>] The field arguments from the field that returned this connection attr_accessor :arguments # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation` # @param context [Query::Context] # @param parent [Object] The object this collection belongs to # @param first [Integer, nil] The limit parameter from the client, if it provided one # @param after [String, nil] A cursor for pagination, if the client provided one # @param last [Integer, nil] Limit parameter from the client, if provided # @param before [String, nil] A cursor for pagination, if the client provided one. # @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set. # @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given. def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, last: nil, before: nil, edge_class: nil, arguments: nil) @items = items @parent = parent @context = context @field = field @first_value = first @after_value = after @last_value = last @before_value = before @arguments = arguments @edge_class = edge_class || self.class::Edge # This is only true if the object was _initialized_ with an override # or if one is assigned later. @has_max_page_size_override = max_page_size != NOT_CONFIGURED @max_page_size = if max_page_size == NOT_CONFIGURED nil else max_page_size end @has_default_page_size_override = default_page_size != NOT_CONFIGURED @default_page_size = if default_page_size == NOT_CONFIGURED nil else default_page_size end @was_authorized_by_scope_items = detect_was_authorized_by_scope_items end def was_authorized_by_scope_items? @was_authorized_by_scope_items end def max_page_size=(new_value) @has_max_page_size_override = true @max_page_size = new_value end def max_page_size if @has_max_page_size_override @max_page_size else context.schema.default_max_page_size end end def has_max_page_size_override? @has_max_page_size_override end def default_page_size=(new_value) @has_default_page_size_override = true @default_page_size = new_value end def default_page_size if @has_default_page_size_override @default_page_size else context.schema.default_page_size end end def has_default_page_size_override? @has_default_page_size_override end attr_writer :first # @return [Integer, nil] # A clamped `first` value. # (The underlying instance variable doesn't have limits on it.) # If neither `first` nor `last` is given, but `default_page_size` is # present, default_page_size is used for first. If `default_page_size` # is greater than `max_page_size``, it'll be clamped down to # `max_page_size`. If `default_page_size` is nil, use `max_page_size`. def first @first ||= begin capped = limit_pagination_argument(@first_value, max_page_size) if capped.nil? && last.nil? capped = limit_pagination_argument(default_page_size, max_page_size) || max_page_size end capped end end # This is called by `Relay::RangeAdd` -- it can be overridden # when `item` needs some modifications based on this connection's state. # # @param item [Object] An item newly added to `items` # @return [Edge] def range_add_edge(item) edge_class.new(item, self) end attr_writer :last # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it) def last @last ||= limit_pagination_argument(@last_value, max_page_size) end # @return [Array] {nodes}, but wrapped with Edge instances def edges @edges ||= nodes.map { |n| @edge_class.new(n, self) } end # @return [Class] A wrapper class for edges of this connection attr_accessor :edge_class # @return [GraphQL::Schema::Field] The field this connection was returned by attr_accessor :field # @return [Array] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value} def nodes raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`" end # A dynamic alias for compatibility with {Relay::BaseConnection}. # @deprecated use {#nodes} instead def edge_nodes nodes end # The connection object itself implements `PageInfo` fields def page_info self end # @return [Boolean] True if there are more items after this page def has_next_page raise PaginationImplementationMissingError, "Implement #{self.class}#has_next_page to return the next-page check" end # @return [Boolean] True if there were items before these items def has_previous_page raise PaginationImplementationMissingError, "Implement #{self.class}#has_previous_page to return the previous-page check" end # @return [String] The cursor of the first item in {nodes} def start_cursor nodes.first && cursor_for(nodes.first) end # @return [String] The cursor of the last item in {nodes} def end_cursor nodes.last && cursor_for(nodes.last) end # Return a cursor for this item. # @param item [Object] one of the passed in {items}, taken from {nodes} # @return [String] def cursor_for(item) raise PaginationImplementationMissingError, "Implement #{self.class}#cursor_for(item) to return the cursor for #{item.inspect}" end private def detect_was_authorized_by_scope_items if @context && (current_runtime_state = Thread.current[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[@context.query]) query_runtime_state.was_authorized_by_scope_items else nil end end # @param argument [nil, Integer] `first` or `last`, as provided by the client # @param max_page_size [nil, Integer] # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size` def limit_pagination_argument(argument, max_page_size) if argument if argument < 0 argument = 0 elsif max_page_size && argument > max_page_size argument = max_page_size end end argument end def decode(cursor) context.schema.cursor_encoder.decode(cursor, nonce: true) end def encode(cursor) context.schema.cursor_encoder.encode(cursor, nonce: true) end # A wrapper around paginated items. It includes a {cursor} for pagination # and could be extended with custom relationship-level data. class Edge attr_reader :node def initialize(node, connection) @connection = connection @node = node end def parent @connection.parent end def cursor @cursor ||= @connection.cursor_for(@node) end def was_authorized_by_scope_items? @connection.was_authorized_by_scope_items? end end end end end graphql-ruby-2.2.17/lib/graphql/pagination/connections.rb000066400000000000000000000101271476434635200234430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Pagination # A schema-level connection wrapper manager. # # Attach as a plugin. # # @example Adding a custom wrapper # class MySchema < GraphQL::Schema # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection) # end # # @example Removing default connection support for arrays (they can still be manually wrapped) # class MySchema < GraphQL::Schema # connections.delete(Array) # end # # @see {Schema.connections} class Connections class ImplementationMissingError < GraphQL::Error end def initialize(schema:) @schema = schema @wrappers = {} add_default end def add(nodes_class, implementation) @wrappers[nodes_class] = implementation end def delete(nodes_class) @wrappers.delete(nodes_class) end def all_wrappers all_wrappers = {} @schema.ancestors.reverse_each do |schema_class| if schema_class.respond_to?(:connections) && (c = schema_class.connections) all_wrappers.merge!(c.wrappers) end end all_wrappers end def wrapper_for(items, wrappers: all_wrappers) impl = nil items.class.ancestors.each { |cls| impl = wrappers[cls] break if impl } impl end # Used by the runtime to wrap values in connection wrappers. # @api Private def wrap(field, parent, items, arguments, context) return items if GraphQL::Execution::Interpreter::RawValue === items wrappers = context ? context.namespace(:connections)[:all_wrappers] : all_wrappers impl = wrapper_for(items, wrappers: wrappers) if impl impl.new( items, context: context, parent: parent, field: field, max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size, default_page_size: field.has_default_page_size? ? field.default_page_size : context.schema.default_page_size, first: arguments[:first], after: arguments[:after], last: arguments[:last], before: arguments[:before], arguments: arguments, edge_class: edge_class_for_field(field), ) else raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})" end end # use an override if there is one # @api private def edge_class_for_field(field) conn_type = field.type.unwrap conn_type_edge_type = conn_type.respond_to?(:edge_class) && conn_type.edge_class if conn_type_edge_type && conn_type_edge_type != Pagination::Connection::Edge conn_type_edge_type else nil end end protected attr_reader :wrappers private def add_default add(Array, Pagination::ArrayConnection) if defined?(ActiveRecord::Relation) add(ActiveRecord::Relation, Pagination::ActiveRecordRelationConnection) end if defined?(Sequel::Dataset) add(Sequel::Dataset, Pagination::SequelDatasetConnection) end if defined?(Mongoid::Criteria) add(Mongoid::Criteria, Pagination::MongoidRelationConnection) end # Mongoid 5 and 6 if defined?(Mongoid::Relations::Targets::Enumerable) add(Mongoid::Relations::Targets::Enumerable, Pagination::MongoidRelationConnection) end # Mongoid 7 if defined?(Mongoid::Association::Referenced::HasMany::Targets::Enumerable) add(Mongoid::Association::Referenced::HasMany::Targets::Enumerable, Pagination::MongoidRelationConnection) end # Mongoid 7.3+ if defined?(Mongoid::Association::Referenced::HasMany::Enumerable) add(Mongoid::Association::Referenced::HasMany::Enumerable, Pagination::MongoidRelationConnection) end end end end end graphql-ruby-2.2.17/lib/graphql/pagination/mongoid_relation_connection.rb000066400000000000000000000010461476434635200266710ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination class MongoidRelationConnection < Pagination::RelationConnection def relation_offset(relation) relation.options.skip end def relation_limit(relation) relation.options.limit end def relation_count(relation) relation.all.count(relation.options.slice(:limit, :skip)) end def null_relation(relation) relation.without_options.none end end end end graphql-ruby-2.2.17/lib/graphql/pagination/relation_connection.rb000066400000000000000000000171601476434635200251610ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/connection" module GraphQL module Pagination # A generic class for working with database query objects. class RelationConnection < Pagination::Connection def nodes load_nodes @nodes end def has_previous_page if @has_previous_page.nil? @has_previous_page = if after_offset && after_offset > 0 true elsif last # See whether there are any nodes _before_ the current offset. # If there _is no_ current offset, then there can't be any nodes before it. # Assume that if the offset is positive, there are nodes before the offset. limited_nodes !(@paged_nodes_offset.nil? || @paged_nodes_offset == 0) else false end end @has_previous_page end def has_next_page if @has_next_page.nil? @has_next_page = if before_offset && before_offset > 0 true elsif first if @nodes && @nodes.count < first false else relation_larger_than(sliced_nodes, @sliced_nodes_offset, first) end else false end end @has_next_page end def cursor_for(item) load_nodes # index in nodes + existing offset + 1 (because it's offset, not index) offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0) encode(offset.to_s) end private # @param relation [Object] A database query object # @param _initial_offset [Integer] The number of items already excluded from the relation # @param size [Integer] The value against which we check the relation size # @return [Boolean] True if the number of items in this relation is larger than `size` def relation_larger_than(relation, _initial_offset, size) relation_count(set_limit(relation, size + 1)) == size + 1 end # @param relation [Object] A database query object # @return [Integer, nil] The offset value, or nil if there isn't one def relation_offset(relation) raise "#{self.class}#relation_offset(relation) must return the offset value for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Integer, nil] The limit value, or nil if there isn't one def relation_limit(relation) raise "#{self.class}#relation_limit(relation) must return the limit value for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Integer, nil] The number of items in this relation (hopefully determined without loading all records into memory!) def relation_count(relation) raise "#{self.class}#relation_count(relation) must return the count of records for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Object] A modified query object which will return no records def null_relation(relation) raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})" end # @return [Integer] def offset_from_cursor(cursor) decode(cursor).to_i end # Abstract this operation so we can always ignore inputs less than zero. # (Sequel doesn't like it, understandably.) def set_offset(relation, offset_value) if offset_value >= 0 relation.offset(offset_value) else relation.offset(0) end end # Abstract this operation so we can always ignore inputs less than zero. # (Sequel doesn't like it, understandably.) def set_limit(relation, limit_value) if limit_value > 0 relation.limit(limit_value) elsif limit_value == 0 null_relation(relation) else relation end end def calculate_sliced_nodes_parameters if defined?(@sliced_nodes_limit) return else next_offset = relation_offset(items) || 0 relation_limit = relation_limit(items) if after_offset next_offset += after_offset end if before_offset && after_offset if after_offset < before_offset # Get the number of items between the two cursors space_between = before_offset - after_offset - 1 relation_limit = space_between else # The cursors overextend one another to an empty set @sliced_nodes_null_relation = true end elsif before_offset # Use limit to cut off the tail of the relation relation_limit = before_offset - 1 end @sliced_nodes_limit = relation_limit @sliced_nodes_offset = next_offset end end # Apply `before` and `after` to the underlying `items`, # returning a new relation. def sliced_nodes @sliced_nodes ||= begin calculate_sliced_nodes_parameters paginated_nodes = items if @sliced_nodes_null_relation paginated_nodes = null_relation(paginated_nodes) else if @sliced_nodes_limit paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit) end if @sliced_nodes_offset paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset) end end paginated_nodes end end # @return [Integer, nil] def before_offset @before_offset ||= before && offset_from_cursor(before) end # @return [Integer, nil] def after_offset @after_offset ||= after && offset_from_cursor(after) end # Apply `first` and `last` to `sliced_nodes`, # returning a new relation def limited_nodes @limited_nodes ||= begin calculate_sliced_nodes_parameters if @sliced_nodes_null_relation # it's an empty set return sliced_nodes end relation_limit = @sliced_nodes_limit relation_offset = @sliced_nodes_offset if first && (relation_limit.nil? || relation_limit > first) # `first` would create a stricter limit that the one already applied, so add it relation_limit = first end if last if relation_limit if last <= relation_limit # `last` is a smaller slice than the current limit, so apply it relation_offset += (relation_limit - last) relation_limit = last end else # No limit, so get the last items sliced_nodes_count = relation_count(sliced_nodes) relation_offset += (sliced_nodes_count - [last, sliced_nodes_count].min) relation_limit = last end end @paged_nodes_offset = relation_offset paginated_nodes = items paginated_nodes = set_offset(paginated_nodes, relation_offset) if relation_limit paginated_nodes = set_limit(paginated_nodes, relation_limit) end paginated_nodes end end # Load nodes after applying first/last/before/after, # returns an array of nodes def load_nodes # Return an array so we can consistently use `.index(node)` on it @nodes ||= limited_nodes.to_a end end end end graphql-ruby-2.2.17/lib/graphql/pagination/sequel_dataset_connection.rb000066400000000000000000000011731476434635200263440ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination # Customizes `RelationConnection` to work with `Sequel::Dataset`s. class SequelDatasetConnection < Pagination::RelationConnection private def relation_offset(relation) relation.opts[:offset] end def relation_limit(relation) relation.opts[:limit] end def relation_count(relation) # Remove order to make it faster relation.order(nil).count end def null_relation(relation) relation.where(false) end end end end graphql-ruby-2.2.17/lib/graphql/parse_error.rb000066400000000000000000000007751476434635200213230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class ParseError < GraphQL::Error attr_reader :line, :col, :query def initialize(message, line, col, query, filename: nil) if filename message += " (#{filename})" end super(message) @line = line @col = col @query = query end def to_h locations = line ? [{ "line" => line, "column" => col }] : [] { "message" => message, "locations" => locations, } end end end graphql-ruby-2.2.17/lib/graphql/query.rb000066400000000000000000000372041476434635200201420ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/context" require "graphql/query/fingerprint" require "graphql/query/null_context" require "graphql/query/result" require "graphql/query/variables" require "graphql/query/input_validation_result" require "graphql/query/variable_validation_error" require "graphql/query/validation_pipeline" module GraphQL # A combination of query string and {Schema} instance which can be reduced to a {#result}. class Query include Tracing::Traceable extend Forwardable class OperationNameMissingError < GraphQL::ExecutionError def initialize(name) msg = if name.nil? %|An operation name is required| else %|No operation named "#{name}"| end super(msg) end end attr_reader :schema, :context, :provided_variables # The value for root types attr_accessor :root_value # @return [nil, String] The operation name provided by client or the one inferred from the document. Used to determine which operation to run. attr_accessor :operation_name # @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined) attr_reader :validate # @param new_validate [Boolean] if false, static validation is skipped. This can't be reasssigned after validation. def validate=(new_validate) if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated? raise ArgumentError, "Can't reassign Query#validate= after validation has run, remove this assignment." else @validate = new_validate end end # @return [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. attr_reader :static_validator # @param new_validate [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. This can't be reasssigned after validation. def static_validator=(new_validator) if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated? raise ArgumentError, "Can't reassign Query#static_validator= after validation has run, remove this assignment." elsif !new_validator.is_a?(GraphQL::StaticValidation::Validator) raise ArgumentError, "Expected a `GraphQL::StaticValidation::Validator` instance." else @static_validator = new_validator end end attr_writer :query_string # @return [GraphQL::Language::Nodes::Document] def document # It's ok if this hasn't been assigned yet if @query_string || @document with_prepared_ast { @document } else nil end end def inspect "query ..." end # @return [String, nil] The name of the operation to run (may be inferred) def selected_operation_name return nil unless selected_operation selected_operation.name end # @return [String, nil] the triggered event, if this query is a subscription update attr_reader :subscription_topic attr_reader :tracers # Prepare query `query_string` on `schema` # @param schema [GraphQL::Schema] # @param query_string [String] # @param context [#[]] an arbitrary hash of values which you can access in {GraphQL::Field#resolve} # @param variables [Hash] values for `$variables` in the query # @param operation_name [String] if the query string contains many operations, this is the one which should be executed # @param root_value [Object] the object used to resolve fields on the root type # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value) # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value) def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil) # Even if `variables: nil` is passed, use an empty hash for simpler logic variables ||= {} @schema = schema @context = schema.context_class.new(query: self, object: root_value, values: context) @warden = warden @subscription_topic = subscription_topic @root_value = root_value @fragments = nil @operations = nil @validate = validate self.static_validator = static_validator if static_validator context_tracers = (context ? context.fetch(:tracers, []) : []) @tracers = schema.tracers + context_tracers # Support `ctx[:backtrace] = true` for wrapping backtraces if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) if schema.trace_class <= GraphQL::Tracing::CallLegacyTracers context_tracers += [GraphQL::Backtrace::Tracer] @tracers << GraphQL::Backtrace::Tracer end end if context_tracers.any? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers) raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it." end @analysis_errors = [] if variables.is_a?(String) raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables." else @provided_variables = variables || {} end @query_string = query_string || query @document = document if @query_string && @document raise ArgumentError, "Query should only be provided a query string or a document, not both." end if @query_string && !@query_string.is_a?(String) raise ArgumentError, "Query string argument should be a String, got #{@query_string.class.name} instead." end # A two-layer cache of type resolution: # { abstract_type => { value => resolved_type } } @resolved_types_cache = Hash.new do |h1, k1| h1[k1] = Hash.new do |h2, k2| h2[k2] = @schema.resolve_type(k1, k2, @context) end end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @operation_name = operation_name @prepared_ast = false @validation_pipeline = nil @max_depth = max_depth @max_complexity = max_complexity @result_values = nil @executed = false @logger = if context && context[:logger] == false Logger.new(IO::NULL) elsif context && (l = context[:logger]) l else schema.default_logger end end # If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document def query_string @query_string ||= (document ? document.to_query_string : nil) end def interpreter? true end attr_accessor :multiplex # @return [GraphQL::Tracing::Trace] def current_trace @current_trace ||= context[:trace] || (multiplex ? multiplex.current_trace : schema.new_trace(multiplex: multiplex, query: self)) end def subscription_update? @subscription_topic && subscription? end # A lookahead for the root selections of this query # @return [GraphQL::Execution::Lookahead] def lookahead @lookahead ||= begin ast_node = selected_operation root_type = warden.root_type_for_operation(ast_node.operation_type || "query") GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node]) end end # @api private def result_values=(result_hash) if @executed raise "Invariant: Can't reassign result" else @executed = true @result_values = result_hash end end # @api private attr_reader :result_values def fragments with_prepared_ast { @fragments } end def operations with_prepared_ast { @operations } end # Get the result for this query, executing it once # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys def result if !@executed Execution::Interpreter.run_all(@schema, [self], context: @context) end @result ||= Query::Result.new(query: self, values: @result_values) end def executed? @executed end def static_errors validation_errors + analysis_errors + context.errors end # This is the operation to run for this query. # If more than one operation is present, it must be named at runtime. # @return [GraphQL::Language::Nodes::OperationDefinition, nil] def selected_operation with_prepared_ast { @selected_operation } end # Determine the values for variables of this query, using default values # if a value isn't provided at runtime. # # If some variable is invalid, errors are added to {#validation_errors}. # # @return [GraphQL::Query::Variables] Variables to apply to this query def variables @variables ||= begin with_prepared_ast { GraphQL::Query::Variables.new( @context, @ast_variables, @provided_variables, ) } end end # Node-level cache for calculating arguments. Used during execution and query analysis. # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @param definition [GraphQL::Schema::Field] # @param parent_object [GraphQL::Schema::Object] # @return Hash{Symbol => Object} def arguments_for(ast_node, definition, parent_object: nil) arguments_cache.fetch(ast_node, definition, parent_object) end def arguments_cache @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self) end # A version of the given query string, with: # - Variables inlined to the query # - Strings replaced with `` # @return [String, nil] Returns nil if the query is invalid. def sanitized_query_string(inline_variables: true) with_prepared_ast { schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string } end # This contains a few components: # # - The selected operation name (or `anonymous`) # - The fingerprint of the query string # - The number of given variables (for readability) # - The fingerprint of the given variables # # This fingerprint can be used to track runs of the same operation-variables combination over time. # # @see operation_fingerprint # @see variables_fingerprint # @return [String] An opaque hash identifying this operation-variables combination def fingerprint @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}" end # @return [String] An opaque hash for identifying this query's given query string and selected operation def operation_fingerprint @operation_fingerprint ||= "#{selected_operation_name || "anonymous"}/#{Fingerprint.generate(query_string)}" end # @return [String] An opaque hash for identifying this query's given a variable values (not including defaults) def variables_fingerprint @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}" end def validation_pipeline with_prepared_ast { @validation_pipeline } end def_delegators :validation_pipeline, :validation_errors, :analyzers, :ast_analyzers, :max_depth, :max_complexity, :validate_timeout_remaining attr_accessor :analysis_errors def valid? validation_pipeline.valid? && analysis_errors.empty? end def warden with_prepared_ast { @warden } end def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType] # @param value [Object] Any runtime value # @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type} # @see {#possible_types} to apply filtering from `only` / `except` def resolve_type(abstract_type, value = NOT_CONFIGURED) if value.is_a?(Symbol) && value == NOT_CONFIGURED # Old method signature value = abstract_type abstract_type = nil end if value.is_a?(GraphQL::Schema::Object) value = value.object end @resolved_types_cache[abstract_type][value] end def mutation? with_prepared_ast { @mutation } end def query? with_prepared_ast { @query } end def subscription? with_prepared_ast { @subscription } end # @api private def handle_or_reraise(err) schema.handle_or_reraise(context, err) end def after_lazy(value, &block) if !defined?(@runtime_instance) @runtime_instance = context.namespace(:interpreter_runtime)[:runtime] end if @runtime_instance @runtime_instance.minimal_after_lazy(value, &block) else @schema.after_lazy(value, &block) end end attr_reader :logger private def find_operation(operations, operation_name) if operation_name.nil? && operations.length == 1 operations.values.first elsif !operations.key?(operation_name) nil else operations.fetch(operation_name) end end def prepare_ast @prepared_ast = true @warden ||= @schema.warden_class.new(schema: @schema, context: @context) parse_error = nil @document ||= begin if query_string GraphQL.parse(query_string, trace: self.current_trace) end rescue GraphQL::ParseError => err parse_error = err @schema.parse_error(err, @context) nil end @fragments = {} @operations = {} if @document @document.definitions.each do |part| case part when GraphQL::Language::Nodes::FragmentDefinition @fragments[part.name] = part when GraphQL::Language::Nodes::OperationDefinition @operations[part.name] = part end end elsif parse_error # This will be handled later else parse_error = GraphQL::ExecutionError.new("No query string was present") @context.add_error(parse_error) end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @subscription = false operation_name_error = nil if @operations.any? @selected_operation = find_operation(@operations, @operation_name) if @selected_operation.nil? operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name) else if @operation_name.nil? @operation_name = @selected_operation.name end @ast_variables = @selected_operation.variables @mutation = @selected_operation.operation_type == "mutation" @query = @selected_operation.operation_type == "query" @subscription = @selected_operation.operation_type == "subscription" end end @validation_pipeline = GraphQL::Query::ValidationPipeline.new( query: self, parse_error: parse_error, operation_name_error: operation_name_error, max_depth: @max_depth, max_complexity: @max_complexity ) end # Since the query string is processed at the last possible moment, # any internal values which depend on it should be accessed within this wrapper. def with_prepared_ast if !@prepared_ast prepare_ast end yield end end end graphql-ruby-2.2.17/lib/graphql/query/000077500000000000000000000000001476434635200176075ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/query/context.rb000066400000000000000000000204511476434635200216220ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/context/scoped_context" module GraphQL class Query # Expose some query-specific info to field resolve functions. # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`. class Context module SharedMethods # Return this value to tell the runtime # to exclude this field from the response altogether def skip GraphQL::Execution::SKIP end # Add error at query-level. # @param error [GraphQL::ExecutionError] an execution error # @return [void] def add_error(error) if !error.is_a?(ExecutionError) raise TypeError, "expected error to be a ExecutionError, but was #{error.class}" end errors << error nil end # @example Print the GraphQL backtrace during field resolution # puts ctx.backtrace # # @return [GraphQL::Backtrace] The backtrace for this point in query execution def backtrace GraphQL::Backtrace.new(self) end def execution_errors @execution_errors ||= ExecutionErrors.new(self) end end class ExecutionErrors def initialize(ctx) @context = ctx end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end # This will assign ast_node and path @context.add_error(err) end alias :>> :add alias :push :add end include SharedMethods extend Forwardable # @return [Array] errors returned during execution attr_reader :errors # @return [GraphQL::Query] The query whose context this is attr_reader :query # @return [GraphQL::Schema] attr_reader :schema # @return [Array] The current position in the result attr_reader :path # Make a new context which delegates key lookup to `values` # @param query [GraphQL::Query] the query who owns this context # @param values [Hash] A hash of arbitrary values which will be accessible at query-time def initialize(query:, schema: query.schema, values:, object:) @query = query @schema = schema @provided_values = values || {} @object = object # Namespaced storage, where user-provided values are in `nil` namespace: @storage = Hash.new { |h, k| h[k] = {} } @storage[nil] = @provided_values @errors = [] @path = [] @value = nil @context = self # for SharedMethods TODO delete sharedmethods @scoped_context = ScopedContext.new(self) end # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }` def response_extensions namespace(:__query_result_extensions__) end def dataloader @dataloader ||= self[:dataloader] || (query.multiplex ? query.multiplex.dataloader : schema.dataloader_class.new) end # @api private attr_writer :interpreter # @api private attr_writer :value # @api private attr_reader :scoped_context def []=(key, value) @provided_values[key] = value end def_delegators :@query, :trace, :interpreter? RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]) # @!method []=(key, value) # Reassign `key` to the hash passed to {Schema#execute} as `context:` # Lookup `key` from the hash passed to {Schema#execute} as `context:` def [](key) if @scoped_context.key?(key) @scoped_context[key] elsif @provided_values.key?(key) @provided_values[key] elsif RUNTIME_METADATA_KEYS.include?(key) if key == :current_path current_path else (current_runtime_state = Thread.current[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[@query]) && (query_runtime_state.public_send(key)) end else # not found nil end end def current_path current_runtime_state = Thread.current[:__graphql_runtime_info] query_runtime_state = current_runtime_state && current_runtime_state[@query] path = query_runtime_state && (result = query_runtime_state.current_result) && (result.path) if path && (rn = query_runtime_state.current_result_name) path = path.dup path.push(rn) end path end def delete(key) if @scoped_context.key?(key) @scoped_context.delete(key) else @provided_values.delete(key) end end UNSPECIFIED_FETCH_DEFAULT = Object.new def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT) if RUNTIME_METADATA_KEYS.include?(key) (runtime = Thread.current[:__graphql_runtime_info]) && (query_runtime_state = runtime[@query]) && (query_runtime_state.public_send(key)) elsif @scoped_context.key?(key) scoped_context[key] elsif @provided_values.key?(key) @provided_values[key] elsif default != UNSPECIFIED_FETCH_DEFAULT default elsif block_given? yield(self, key) else raise KeyError.new(key: key) end end def dig(key, *other_keys) if RUNTIME_METADATA_KEYS.include?(key) (current_runtime_state = Thread.current[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[@query]) && (obj = query_runtime_state.public_send(key)) && if other_keys.empty? obj else obj.dig(*other_keys) end elsif @scoped_context.key?(key) @scoped_context.dig(key, *other_keys) else @provided_values.dig(key, *other_keys) end end def to_h if (current_scoped_context = @scoped_context.merged_context) @provided_values.merge(current_scoped_context) else @provided_values end end alias :to_hash :to_h def key?(key) @scoped_context.key?(key) || @provided_values.key?(key) end # @return [GraphQL::Schema::Warden] def warden @warden ||= (@query && @query.warden) end # @api private attr_writer :warden # Get an isolated hash for `ns`. Doesn't affect user-provided storage. # @param ns [Object] a usage-specific namespace identifier # @return [Hash] namespaced storage def namespace(ns) if ns == :interpreter self else @storage[ns] end end # @return [Boolean] true if this namespace was accessed before def namespace?(ns) @storage.key?(ns) end def logger @query && @query.logger end def inspect "#" end def scoped_merge!(hash) @scoped_context.merge!(hash) end def scoped_set!(key, value) scoped_merge!(key => value) nil end # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded) # block of code. # # @example using scoped context inside a promise # scoped_ctx = context.scoped # SomeBatchLoader.load(...).then do |thing| # # use a scoped_ctx which was created _before_ dataloading: # scoped_ctx.set!(:thing, thing) # end # @return [Context::Scoped] def scoped Scoped.new(@scoped_context, current_path) end class Scoped def initialize(scoped_context, path) @path = path @scoped_context = scoped_context end def merge!(hash) @scoped_context.merge!(hash, at: @path) end def set!(key, value) @scoped_context.merge!({ key => value }, at: @path) nil end end end end end graphql-ruby-2.2.17/lib/graphql/query/context/000077500000000000000000000000001476434635200212735ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/query/context/scoped_context.rb000066400000000000000000000047621476434635200246520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class Context class ScopedContext def initialize(query_context) @query_context = query_context @scoped_contexts = nil @all_keys = nil end def merged_context if @scoped_contexts.nil? GraphQL::EmptyObjects::EMPTY_HASH else merged_ctx = {} each_present_path_ctx do |path_ctx| merged_ctx = path_ctx.merge(merged_ctx) end merged_ctx end end def merge!(hash, at: current_path) @all_keys ||= Set.new @all_keys.merge(hash.keys) ctx = @scoped_contexts ||= {} at.each do |path_part| ctx = ctx[path_part] ||= { parent: ctx } end this_scoped_ctx = ctx[:scoped_context] ||= {} this_scoped_ctx.merge!(hash) end def key?(key) if @all_keys && @all_keys.include?(key) each_present_path_ctx do |path_ctx| if path_ctx.key?(key) return true end end end false end def [](key) each_present_path_ctx do |path_ctx| if path_ctx.key?(key) return path_ctx[key] end end nil end def current_path @query_context.current_path || GraphQL::EmptyObjects::EMPTY_ARRAY end def dig(key, *other_keys) each_present_path_ctx do |path_ctx| if path_ctx.key?(key) found_value = path_ctx[key] if other_keys.any? return found_value.dig(*other_keys) else return found_value end end end nil end private # Start at the current location, # but look up the tree for previously-assigned scoped values def each_present_path_ctx ctx = @scoped_contexts if ctx.nil? # no-op else current_path.each do |path_part| if ctx.key?(path_part) ctx = ctx[path_part] else break end end while ctx if (scoped_ctx = ctx[:scoped_context]) yield(scoped_ctx) end ctx = ctx[:parent] end end end end end end end graphql-ruby-2.2.17/lib/graphql/query/fingerprint.rb000066400000000000000000000013401476434635200224610ustar00rootroot00000000000000# frozen_string_literal: true require 'digest/sha2' module GraphQL class Query # @api private # @see Query#query_fingerprint # @see Query#variables_fingerprint # @see Query#fingerprint module Fingerprint # Make an obfuscated hash of the given string (either a query string or variables JSON) # @param string [String] # @return [String] A normalized, opaque hash def self.generate(input_str) # Implemented to be: # - Short (and uniform) length # - Stable # - Irreversibly Opaque (don't want to leak variable values) # - URL-friendly bytes = Digest::SHA256.digest(input_str) Base64.urlsafe_encode64(bytes) end end end end graphql-ruby-2.2.17/lib/graphql/query/input_validation_result.rb000066400000000000000000000026211476434635200251040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class InputValidationResult attr_accessor :problems def self.from_problem(explanation, path = nil, extensions: nil, message: nil) result = self.new result.add_problem(explanation, path, extensions: extensions, message: message) result end def initialize(valid: true, problems: nil) @valid = valid @problems = problems end def valid? @valid end def add_problem(explanation, path = nil, extensions: nil, message: nil) @problems ||= [] @valid = false problem = { "path" => path || [], "explanation" => explanation } if extensions problem["extensions"] = extensions end if message problem["message"] = message end @problems.push(problem) end def merge_result!(path, inner_result) return if inner_result.nil? || inner_result.valid? if inner_result.problems inner_result.problems.each do |p| item_path = [path, *p["path"]] add_problem(p["explanation"], item_path, message: p["message"], extensions: p["extensions"]) end end # It could have been explicitly set on inner_result (if it had no problems) @valid = false end VALID = self.new VALID.freeze end end end graphql-ruby-2.2.17/lib/graphql/query/null_context.rb000066400000000000000000000014721476434635200226560ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/context" module GraphQL class Query # This object can be `ctx` in places where there is no query class NullContext < Context include Singleton class NullQuery def after_lazy(value) yield(value) end end class NullSchema < GraphQL::Schema end extend Forwardable attr_reader :schema, :query, :warden, :dataloader def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key? def initialize @query = NullQuery.new @dataloader = GraphQL::Dataloader::NullDataloader.new @schema = NullSchema @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema) end def interpreter? true end end end end graphql-ruby-2.2.17/lib/graphql/query/result.rb000066400000000000000000000031631476434635200214550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # A result from {Schema#execute}. # It provides the requested data and # access to the {Query} and {Query::Context}. class Result extend Forwardable def initialize(query:, values:) @query = query @to_h = values end # @return [GraphQL::Query] The query that was executed attr_reader :query # @return [Hash] The resulting hash of "data" and/or "errors" attr_reader :to_h def_delegators :@query, :context, :mutation?, :query?, :subscription? def_delegators :@to_h, :[], :keys, :values, :to_json, :as_json # Delegate any hash-like method to the underlying hash. def method_missing(method_name, *args, &block) if @to_h.respond_to?(method_name) @to_h.public_send(method_name, *args, &block) else super end end def respond_to_missing?(method_name, include_private = false) @to_h.respond_to?(method_name) || super end def inspect "#" end # A result is equal to another object when: # # - The other object is a Hash whose value matches `result.to_h` # - The other object is a Result whose value matches `result.to_h` # # (The query is ignored for comparing result equality.) # # @return [Boolean] def ==(other) case other when Hash @to_h == other when Query::Result @to_h == other.to_h else super end end end end end graphql-ruby-2.2.17/lib/graphql/query/validation_pipeline.rb000066400000000000000000000074231476434635200241610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Contain the validation pipeline and expose the results. # # 0. Checks in {Query#initialize}: # - Rescue a ParseError, halt if there is one # - Check for selected operation, halt if not found # 1. Validate the AST, halt if errors # 2. Validate the variables, halt if errors # 3. Run query analyzers, halt if errors # # {#valid?} is false if any of the above checks halted the pipeline. # # @api private class ValidationPipeline attr_reader :max_depth, :max_complexity, :validate_timeout_remaining def initialize(query:, parse_error:, operation_name_error:, max_depth:, max_complexity:) @validation_errors = [] @parse_error = parse_error @operation_name_error = operation_name_error @query = query @schema = query.schema @max_depth = max_depth @max_complexity = max_complexity @has_validated = false end # @return [Boolean] does this query have errors that should prevent it from running? def valid? ensure_has_validated @valid end # @return [Array] Static validation errors for the query string def validation_errors ensure_has_validated @validation_errors end def analyzers ensure_has_validated @query_analyzers end def has_validated? @has_validated == true end private # If the pipeline wasn't run yet, run it. # If it was already run, do nothing. def ensure_has_validated return if @has_validated @has_validated = true if @parse_error # This is kind of crazy: we push the parse error into `ctx` # in `def self.parse_error` by default so that users can _opt out_ by redefining that hook. # That means we can't _re-add_ the error here (otherwise we'd either # add it twice _or_ override the user's choice to not add it). # So we just have to know that it was invalid and go from there. @valid = false return elsif @operation_name_error @validation_errors << @operation_name_error else validator = @query.static_validator || @schema.static_validator validation_result = validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors) @validation_errors.concat(validation_result[:errors]) @validate_timeout_remaining = validation_result[:remaining_timeout] if @validation_errors.empty? @validation_errors.concat(@query.variables.errors) end if @validation_errors.empty? @query_analyzers = build_analyzers( @schema, @max_depth, @max_complexity ) end end @valid = @validation_errors.empty? rescue SystemStackError => err @valid = false @schema.query_stack_error(@query, err) end # If there are max_* values, add them, # otherwise reuse the schema's list of analyzers. def build_analyzers(schema, max_depth, max_complexity) qa = schema.query_analyzers.dup if max_depth || max_complexity # Depending on the analysis engine, we must use different analyzers # remove this once everything has switched over to AST analyzers if max_depth qa << GraphQL::Analysis::AST::MaxQueryDepth end if max_complexity qa << GraphQL::Analysis::AST::MaxQueryComplexity end qa else qa end end end end end graphql-ruby-2.2.17/lib/graphql/query/variable_validation_error.rb000066400000000000000000000025251476434635200253500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class VariableValidationError < GraphQL::ExecutionError attr_accessor :value, :validation_result def initialize(variable_ast, type, value, validation_result, msg: nil) @value = value @validation_result = validation_result msg ||= "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value" if problem_fields.any? msg += " for #{problem_fields.join(", ")}" end super(msg) self.ast_node = variable_ast end def to_h # It is possible there are other extension items in this error, so handle # a one level deep merge explicitly. However beyond that only show the # latest value and problems. super.merge({ "extensions" => { "value" => value, "problems" => validation_result.problems }}) do |key, oldValue, newValue| if oldValue.respond_to?(:merge) oldValue.merge(newValue) else newValue end end end private def problem_fields @problem_fields ||= @validation_result .problems .reject { |problem| problem["path"].empty? } .map { |problem| "#{problem['path'].join('.')} (#{problem['explanation']})" } end end end end graphql-ruby-2.2.17/lib/graphql/query/variables.rb000066400000000000000000000073431476434635200221130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Read-only access to query variables, applying default values if needed. class Variables extend Forwardable # @return [Array] Any errors encountered when parsing the provided variables and literal values attr_reader :errors attr_reader :context def initialize(ctx, ast_variables, provided_variables) schema = ctx.schema @context = ctx @provided_variables = deep_stringify(provided_variables) @errors = [] @storage = ast_variables.each_with_object({}) do |ast_variable, memo| if schema.validate_max_errors && schema.validate_max_errors <= @errors.count add_max_errors_reached_message break end # Find the right value for this variable: # - First, use the value provided at runtime # - Then, fall back to the default value from the query string # If it's still nil, raise an error if it's required. variable_type = schema.type_from_ast(ast_variable.type, context: ctx) if variable_type.nil? || !variable_type.unwrap.kind.input? # Pass -- it will get handled by a validator else variable_name = ast_variable.name default_value = ast_variable.default_value provided_value = @provided_variables[variable_name] value_was_provided = @provided_variables.key?(variable_name) max_errors = schema.validate_max_errors - @errors.count if schema.validate_max_errors begin validation_result = variable_type.validate_input(provided_value, ctx, max_errors: max_errors) if validation_result.valid? if value_was_provided # Add the variable if a value was provided memo[variable_name] = provided_value elsif default_value != nil memo[variable_name] = if default_value.is_a?(Language::Nodes::NullValue) nil else default_value end end end rescue GraphQL::ExecutionError => ex # TODO: This should really include the path to the problematic node in the variable value # like InputValidationResults generated by validate_non_null_input but unfortunately we don't # have this information available in the coerce_input call chain. Note this path is the path # that appears under errors.extensions.problems.path and NOT the result path under errors.path. validation_result = GraphQL::Query::InputValidationResult.from_problem(ex.message) end if !validation_result.valid? @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result) end end end end def_delegators :@storage, :length, :key?, :[], :fetch, :to_h private def deep_stringify(val) case val when Array val.map { |v| deep_stringify(v) } when Hash new_val = {} val.each do |k, v| new_val[k.to_s] = deep_stringify(v) end new_val else val end end def add_max_errors_reached_message message = "Too many errors processing variables, max validation error limit reached. Execution aborted" validation_result = GraphQL::Query::InputValidationResult.from_problem(message) errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message) end end end end graphql-ruby-2.2.17/lib/graphql/railtie.rb000066400000000000000000000006111476434635200204160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Railtie < Rails::Railtie config.graphql = ActiveSupport::OrderedOptions.new config.graphql.parser_cache = false initializer("graphql.cache") do |app| if config.graphql.parser_cache Language::Parser.cache ||= Language::Cache.new( app.root.join("tmp/cache/graphql") ) end end end end graphql-ruby-2.2.17/lib/graphql/rake_task.rb000066400000000000000000000116461476434635200207430ustar00rootroot00000000000000# frozen_string_literal: true require "fileutils" require "rake" require "graphql/rake_task/validate" module GraphQL # A rake task for dumping a schema as IDL or JSON. # # By default, schemas are looked up by name as constants using `schema_name:`. # You can provide a `load_schema` function to return your schema another way. # # Use `load_context:` and `visible?` to dump schemas under certain visibility constraints. # # @example Dump a Schema to .graphql + .json files # require "graphql/rake_task" # GraphQL::RakeTask.new(schema_name: "MySchema") # # # $ rake graphql:schema:dump # # Schema IDL dumped to ./schema.graphql # # Schema JSON dumped to ./schema.json # # @example Invoking the task from Ruby # require "rake" # Rake::Task["graphql:schema:dump"].invoke # # @example Providing arguments to build the introspection query # require "graphql/rake_task" # GraphQL::RakeTask.new(schema_name: "MySchema", include_is_one_of: true) class RakeTask include Rake::DSL DEFAULT_OPTIONS = { namespace: "graphql", dependencies: nil, schema_name: nil, load_schema: ->(task) { Object.const_get(task.schema_name) }, load_context: ->(task) { {} }, directory: ".", idl_outfile: "schema.graphql", json_outfile: "schema.json", include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false } # @return [String] Namespace for generated tasks attr_writer :namespace def rake_namespace @namespace end # @return [Array] attr_accessor :dependencies # @return [String] By default, used to find the schema as a constant. # @see {#load_schema} for loading a schema another way attr_accessor :schema_name # @return [<#call(task)>] A proc for loading the target GraphQL schema attr_accessor :load_schema # @return [<#call(task)>] A callable for loading the query context attr_accessor :load_context # @return [String] target for IDL task attr_accessor :idl_outfile # @return [String] target for JSON task attr_accessor :json_outfile # @return [String] directory for IDL & JSON files attr_accessor :directory # @return [Boolean] Options for additional fields in the introspection query JSON response # @see GraphQL::Schema.as_json attr_accessor :include_deprecated_args, :include_schema_description, :include_is_repeatable, :include_specified_by_url, :include_is_one_of # Set the parameters of this task by passing keyword arguments # or assigning attributes inside the block def initialize(options = {}) all_options = DEFAULT_OPTIONS.merge(options) all_options.each do |k, v| self.public_send("#{k}=", v) end if block_given? yield(self) end define_task end private # Use the provided `method_name` to generate a string from the specified schema # then write it to `file`. def write_outfile(method_name, file) schema = @load_schema.call(self) context = @load_context.call(self) result = case method_name when :to_json schema.to_json( include_is_one_of: include_is_one_of, include_deprecated_args: include_deprecated_args, include_is_repeatable: include_is_repeatable, include_specified_by_url: include_specified_by_url, include_schema_description: include_schema_description, context: context ) when :to_definition schema.to_definition(context: context) else raise ArgumentError, "Unexpected schema dump method: #{method_name.inspect}" end dir = File.dirname(file) FileUtils.mkdir_p(dir) if !result.end_with?("\n") result += "\n" end File.write(file, result) end def idl_path File.join(@directory, @idl_outfile) end def json_path File.join(@directory, @json_outfile) end def load_rails_environment_if_defined if Rake::Task.task_defined?('environment') Rake::Task['environment'].invoke end end # Use the Rake DSL to add tasks def define_task namespace(@namespace) do namespace("schema") do desc("Dump the schema to IDL in #{idl_path}") task :idl => @dependencies do load_rails_environment_if_defined write_outfile(:to_definition, idl_path) puts "Schema IDL dumped into #{idl_path}" end desc("Dump the schema to JSON in #{json_path}") task :json => @dependencies do load_rails_environment_if_defined write_outfile(:to_json, json_path) puts "Schema JSON dumped into #{json_path}" end desc("Dump the schema to JSON and IDL") task :dump => [:idl, :json] end end end end end graphql-ruby-2.2.17/lib/graphql/rake_task/000077500000000000000000000000001476434635200204065ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/rake_task/validate.rb000066400000000000000000000046421476434635200225320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class RakeTask extend Rake::DSL desc "Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org" task "graphql:pro:validate", [:gem_version] do |t, args| version = args[:gem_version] if version.nil? raise ArgumentError, "A specific version is required, eg `rake graphql:pro:validate[1.12.0]`" end check = "\e[32m✓\e[0m" ex = "\e[31m✘\e[0m" puts "Validating graphql-pro v#{version}" puts " - Checking for graphql-pro credentials..." creds = `bundle config gems.graphql.pro --parseable`[/[a-z0-9]{11}:[a-z0-9]{11}/] if creds.nil? puts " #{ex} failed, please set with `bundle config gems.graphql.pro $MY_CREDENTIALS`" exit(1) else puts " #{check} found" end puts " - Fetching the gem..." fetch_result = `gem fetch graphql-pro -v #{version} --source https://#{creds}@gems.graphql.pro` if fetch_result.empty? puts " #{ex} failed to fetch v#{version}" exit(1) else puts " #{check} fetched" end puts " - Validating digest..." require "digest/sha2" gem_digest = Digest::SHA512.new.hexdigest(File.read("graphql-pro-#{version}.gem")) require "net/http" github_uri = URI("https://raw.githubusercontent.com/rmosolgo/graphql-ruby/master/guides/pro/checksums/graphql-pro-#{version}.txt") # Remove final newline from .txt file github_digest = Net::HTTP.get(github_uri).chomp docs_uri = URI("https://graphql-ruby.org/pro/checksums/graphql-pro-#{version}.txt") docs_digest = Net::HTTP.get(docs_uri).chomp if docs_digest == gem_digest && github_digest == gem_digest puts " #{check} validated from GitHub" puts " #{check} validated from graphql-ruby.org" else puts " #{ex} SHA mismatch:" puts " Downloaded: #{gem_digest}" puts " GitHub: #{github_digest}" puts " graphql-ruby.org: #{docs_digest}" puts "" puts " This download of graphql-pro is invalid, please open an issue:" puts " https://github.com/rmosolgo/graphql-ruby/issues/new?title=graphql-pro%20digest%20mismatch%20(#{version})" exit(1) end puts "\e[32m✔\e[0m graphql-pro #{version} validated successfully!" end end end graphql-ruby-2.2.17/lib/graphql/relay.rb000066400000000000000000000001011476434635200200730ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/relay/range_add' graphql-ruby-2.2.17/lib/graphql/relay/000077500000000000000000000000001476434635200175565ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/relay/range_add.rb000066400000000000000000000040621476434635200220110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # This provides some isolation from `GraphQL::Relay` internals. # # Given a list of items and a new item, it will provide a connection and an edge. # # The connection doesn't receive outside arguments, so the list of items # should be ordered and paginated before providing it here. # # @example Adding a comment to list of comments # post = Post.find(args[:post_id]) # comments = post.comments # new_comment = comments.build(body: args[:body]) # new_comment.save! # # range_add = GraphQL::Relay::RangeAdd.new( # parent: post, # collection: comments, # item: new_comment, # context: context, # ) # # response = { # post: post, # comments_connection: range_add.connection, # new_comment_edge: range_add.edge, # } class RangeAdd attr_reader :edge, :connection, :parent # @param collection [Object] The list of items to wrap in a connection # @param item [Object] The newly-added item (will be wrapped in `edge_class`) # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection # @param parent [Object] The owner of `collection`, will be passed to the connection if provided # @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class) def initialize(collection:, item:, context:, parent: nil, edge_class: nil) conn_class = context.schema.connections.wrapper_for(collection) # The rest will be added by ConnectionExtension @connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class) # Check if this connection supports it, to support old versions of GraphQL-Pro @edge = if @connection.respond_to?(:range_add_edge) @connection.range_add_edge(item) else @connection.edge_class.new(item, @connection) end @parent = parent end end end end graphql-ruby-2.2.17/lib/graphql/rubocop.rb000066400000000000000000000002131476434635200204340ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/rubocop/graphql/default_null_true" require "graphql/rubocop/graphql/default_required_true" graphql-ruby-2.2.17/lib/graphql/rubocop/000077500000000000000000000000001476434635200201135ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/rubocop/graphql/000077500000000000000000000000001476434635200215515ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/rubocop/graphql/base_cop.rb000066400000000000000000000024411476434635200236520ustar00rootroot00000000000000# frozen_string_literal: true require "rubocop" module GraphQL module Rubocop module GraphQL class BaseCop < RuboCop::Cop::Base extend RuboCop::Cop::AutoCorrector # Return the source of `send_node`, but without the keyword argument represented by `pair_node` def source_without_keyword_argument(send_node, pair_node) # work back to the preceeding comma first_pos = pair_node.location.expression.begin_pos end_pos = pair_node.location.expression.end_pos node_source = send_node.source_range.source node_first_pos = send_node.location.expression.begin_pos relative_first_pos = first_pos - node_first_pos relative_last_pos = end_pos - node_first_pos begin_removal_pos = relative_first_pos while node_source[begin_removal_pos] != "," begin_removal_pos -= 1 if begin_removal_pos < 1 raise "Invariant: somehow backtracked to beginning of node looking for a comma (node source: #{node_source.inspect})" end end end_removal_pos = relative_last_pos cleaned_node_source = node_source[0...begin_removal_pos] + node_source[end_removal_pos..-1] cleaned_node_source end end end end end graphql-ruby-2.2.17/lib/graphql/rubocop/graphql/default_null_true.rb000066400000000000000000000024521476434635200256160ustar00rootroot00000000000000# frozen_string_literal: true require_relative "base_cop" module GraphQL module Rubocop module GraphQL # Identify (and auto-correct) any field configuration which duplicates # the default `null: true` property. # # `null: true` is default because nullable fields can always be converted # to non-null fields (`null: false`) without a breaking change. (The opposite change, from `null: false` # to `null: true`, change.) # # @example # # Both of these define `name: String` in GraphQL: # # # bad # field :name, String, null: true # # # good # field :name, String # class DefaultNullTrue < BaseCop MSG = "`null: true` is the default and can be removed." def_node_matcher :field_config_with_null_true?, <<-Pattern ( send nil? :field ... (hash $(pair (sym :null) (true)) ...) ) Pattern def on_send(node) field_config_with_null_true?(node) do |null_config| add_offense(null_config) do |corrector| cleaned_node_source = source_without_keyword_argument(node, null_config) corrector.replace(node.source_range, cleaned_node_source) end end end end end end end graphql-ruby-2.2.17/lib/graphql/rubocop/graphql/default_required_true.rb000066400000000000000000000025441476434635200264660ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./base_cop" module GraphQL module Rubocop module GraphQL # Identify (and auto-correct) any argument configuration which duplicates # the default `required: true` property. # # `required: true` is default because required arguments can always be converted # to optional arguments (`required: false`) without a breaking change. (The opposite change, from `required: false` # to `required: true`, change.) # # @example # # Both of these define `id: ID!` in GraphQL: # # # bad # argument :id, ID, required: true # # # good # argument :id, ID # class DefaultRequiredTrue < BaseCop MSG = "`required: true` is the default and can be removed." def_node_matcher :argument_config_with_required_true?, <<-Pattern ( send {nil? _} :argument ... (hash <$(pair (sym :required) (true)) ...>) ) Pattern def on_send(node) argument_config_with_required_true?(node) do |required_config| add_offense(required_config) do |corrector| cleaned_node_source = source_without_keyword_argument(node, required_config) corrector.replace(node, cleaned_node_source) end end end end end end end graphql-ruby-2.2.17/lib/graphql/runtime_type_error.rb000066400000000000000000000001411476434635200227200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class RuntimeTypeError < GraphQL::Error end end graphql-ruby-2.2.17/lib/graphql/schema.rb000066400000000000000000001547631476434635200202470ustar00rootroot00000000000000# frozen_string_literal: true require "logger" require "graphql/schema/addition" require "graphql/schema/always_visible" require "graphql/schema/base_64_encoder" require "graphql/schema/find_inherited_value" require "graphql/schema/finder" require "graphql/schema/invalid_type_error" require "graphql/schema/introspection_system" require "graphql/schema/late_bound_type" require "graphql/schema/null_mask" require "graphql/schema/timeout" require "graphql/schema/type_expression" require "graphql/schema/unique_within_type" require "graphql/schema/warden" require "graphql/schema/build_from_definition" require "graphql/schema/validator" require "graphql/schema/member" require "graphql/schema/wrapper" require "graphql/schema/list" require "graphql/schema/non_null" require "graphql/schema/argument" require "graphql/schema/enum_value" require "graphql/schema/enum" require "graphql/schema/field_extension" require "graphql/schema/field" require "graphql/schema/input_object" require "graphql/schema/interface" require "graphql/schema/scalar" require "graphql/schema/object" require "graphql/schema/union" require "graphql/schema/directive" require "graphql/schema/directive/deprecated" require "graphql/schema/directive/include" require "graphql/schema/directive/one_of" require "graphql/schema/directive/skip" require "graphql/schema/directive/feature" require "graphql/schema/directive/flagged" require "graphql/schema/directive/transform" require "graphql/schema/directive/specified_by" require "graphql/schema/type_membership" require "graphql/schema/resolver" require "graphql/schema/mutation" require "graphql/schema/has_single_input_argument" require "graphql/schema/relay_classic_mutation" require "graphql/schema/subscription" module GraphQL # A GraphQL schema which may be queried with {GraphQL::Query}. # # The {Schema} contains: # # - types for exposing your application # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions) # - execution strategies for running incoming queries # # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}. # The schema will traverse the tree of fields & types, using those as starting points. # Any undiscoverable types may be provided with the `types` configuration. # # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations. # (These configurations can be overridden by specific calls to {Schema#execute}) # # @example defining a schema # class MySchema < GraphQL::Schema # query QueryType # # If types are only connected by way of interfaces, they must be added here # orphan_types ImageType, AudioType # end # class Schema extend GraphQL::Schema::Member::HasAstNode extend GraphQL::Schema::FindInheritedValue class DuplicateNamesError < GraphQL::Error attr_reader :duplicated_name def initialize(duplicated_name:, duplicated_definition_1:, duplicated_definition_2:) @duplicated_name = duplicated_name super( "Found two visible definitions for `#{duplicated_name}`: #{duplicated_definition_1}, #{duplicated_definition_2}" ) end end class UnresolvedLateBoundTypeError < GraphQL::Error attr_reader :type def initialize(type:) @type = type super("Late bound type was never found: #{type.inspect}") end end # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string. class InvalidDocumentError < Error; end; class << self # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [Class] the schema described by `input` def from_introspection(introspection_result) GraphQL::Schema::Loader.load(introspection_result) end # Create schema from an IDL schema or file containing an IDL definition. # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution # @param parser [Object] An object for handling definition string parsing (must respond to `parse`) # @param using [Hash] Plugins to attach to the created schema with `use(key, value)` # @return [Class] the schema described by `document` def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}) # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls") GraphQL::Schema::BuildFromDefinition.from_definition_path( self, definition_or_path, default_resolve: default_resolve, parser: parser, using: using, ) else GraphQL::Schema::BuildFromDefinition.from_definition( self, definition_or_path, default_resolve: default_resolve, parser: parser, using: using, ) end end def deprecated_graphql_definition graphql_definition(silence_deprecation_warning: true) end # @return [GraphQL::Subscriptions] def subscriptions(inherited: true) defined?(@subscriptions) ? @subscriptions : (inherited ? find_inherited_value(:subscriptions, nil) : nil) end def subscriptions=(new_implementation) @subscriptions = new_implementation end # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set. def default_trace_mode(new_mode = nil) if new_mode @default_trace_mode = new_mode elsif defined?(@default_trace_mode) @default_trace_mode elsif superclass.respond_to?(:default_trace_mode) superclass.default_trace_mode else :default end end def trace_class(new_class = nil) if new_class # If any modules were already added for `:default`, # re-apply them here mods = trace_modules_for(:default) mods.each { |mod| new_class.include(mod) } trace_mode(:default, new_class) backtrace_class = Class.new(new_class) backtrace_class.include(GraphQL::Backtrace::Trace) trace_mode(:default_backtrace, backtrace_class) end trace_class_for(:default, build: true) end # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined. def trace_class_for(mode, build: false) if (trace_class = own_trace_modes[mode]) trace_class elsif superclass.respond_to?(:trace_class_for) && (trace_class = superclass.trace_class_for(mode, build: false)) trace_class elsif build own_trace_modes[mode] = build_trace_mode(mode) else nil end end # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested. # {default_trace_mode} is used when no `trace_mode: ...` is requested. # # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)` # unless `trace_mode` is explicitly given. (This class will not recieve any default trace modules.) # # Subclasses of the schema will use `trace_class` as a base class for this mode and those # subclass also will _not_ receive default tracing modules. # # @param mode_name [Symbol] # @param trace_class [Class] subclass of GraphQL::Tracing::Trace # @return void def trace_mode(mode_name, trace_class) own_trace_modes[mode_name] = trace_class nil end def own_trace_modes @own_trace_modes ||= {} end module DefaultTraceClass end private_constant :DefaultTraceClass def build_trace_mode(mode) case mode when :default # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class. base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace Class.new(base_class) do include DefaultTraceClass end when :default_backtrace schema_base_class = trace_class_for(:default, build: true) Class.new(schema_base_class) do include(GraphQL::Backtrace::Trace) end else # First, see if the superclass has a custom-defined class for this. # Then, if it doesn't, use this class's default trace base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default, build: true) # Prepare the default trace class if it hasn't been initialized yet base_class ||= (own_trace_modes[:default] = build_trace_mode(:default)) mods = trace_modules_for(mode) if base_class < DefaultTraceClass mods = trace_modules_for(:default) + mods end # Copy the existing default options into this mode's options default_options = trace_options_for(:default) add_trace_options_for(mode, default_options) Class.new(base_class) do mods.any? && include(*mods) end end end def own_trace_modules @own_trace_modules ||= Hash.new { |h, k| h[k] = [] } end # @return [Array] Modules added for tracing in `trace_mode`, including inherited ones def trace_modules_for(trace_mode) modules = own_trace_modules[trace_mode] if superclass.respond_to?(:trace_modules_for) modules += superclass.trace_modules_for(trace_mode) end modules end # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}. # @see {#as_json} # @return [String] def to_json(**args) JSON.pretty_generate(as_json(**args)) end # Return the Hash response of {Introspection::INTROSPECTION_QUERY}. # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects # @return [Hash] GraphQL result def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false) introspection_query = Introspection.query( include_deprecated_args: include_deprecated_args, include_schema_description: include_schema_description, include_is_repeatable: include_is_repeatable, include_is_one_of: include_is_one_of, include_specified_by_url: include_specified_by_url, ) execute(introspection_query, context: context).to_h end # Return the GraphQL IDL for the schema # @param context [Hash] # @return [String] def to_definition(context: {}) GraphQL::Schema::Printer.print_schema(self, context: context) end # Return the GraphQL::Language::Document IDL AST for the schema # @return [GraphQL::Language::Document] def to_document GraphQL::Language::DocumentFromSchemaDefinition.new(self).document end # @return [String, nil] def description(new_description = nil) if new_description @description = new_description elsif defined?(@description) @description else find_inherited_value(:description, nil) end end def find(path) if !@finder @find_cache = {} @finder ||= GraphQL::Schema::Finder.new(self) end @find_cache[path] ||= @finder.find(path) end def static_validator GraphQL::StaticValidation::Validator.new(schema: self) end def use(plugin, **kwargs) if kwargs.any? plugin.use(self, **kwargs) else plugin.use(self) end own_plugins << [plugin, kwargs] end def plugins find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins end # Build a map of `{ name => type }` and return it # @return [Hash Class>] A dictionary of type classes by their GraphQL name # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes. def types(context = GraphQL::Query::NullContext.instance) all_types = non_introspection_types.merge(introspection_system.types) visible_types = {} all_types.each do |k, v| visible_types[k] =if v.is_a?(Array) visible_t = nil v.each do |t| if t.visible?(context) if visible_t.nil? visible_t = t else raise DuplicateNamesError.new( duplicated_name: k, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect ) end end end visible_t else v end end visible_types end # @param type_name [String] # @return [Module, nil] A type, or nil if there's no type called `type_name` def get_type(type_name, context = GraphQL::Query::NullContext.instance) local_entry = own_types[type_name] type_defn = case local_entry when nil nil when Array visible_t = nil warden = Warden.from_context(context) local_entry.each do |t| if warden.visible_type?(t, context) if visible_t.nil? visible_t = t else raise DuplicateNamesError.new( duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect ) end end end visible_t when Module local_entry else raise "Invariant: unexpected own_types[#{type_name.inspect}]: #{local_entry.inspect}" end type_defn || introspection_system.types[type_name] || # todo context-specific introspection? (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil) end # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility? def has_defined_type?(type_name) own_types.key?(type_name) || introspection_system.types.key?(type_name) || (superclass.respond_to?(:has_defined_type?) ? superclass.has_defined_type?(type_name) : false) end # @api private attr_writer :connections # @return [GraphQL::Pagination::Connections] if installed def connections if defined?(@connections) @connections else inherited_connections = find_inherited_value(:connections, nil) # This schema is part of an inheritance chain which is using new connections, # make a new instance, so we don't pollute the upstream one. if inherited_connections @connections = Pagination::Connections.new(schema: self) else nil end end end def new_connections? !!connections end def query(new_query_object = nil) if new_query_object if @query_object raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}" else @query_object = new_query_object add_type_and_traverse(new_query_object, root: true) nil end else @query_object || find_inherited_value(:query) end end def mutation(new_mutation_object = nil) if new_mutation_object if @mutation_object raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}" else @mutation_object = new_mutation_object add_type_and_traverse(new_mutation_object, root: true) nil end else @mutation_object || find_inherited_value(:mutation) end end def subscription(new_subscription_object = nil) if new_subscription_object if @subscription_object raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}" else @subscription_object = new_subscription_object add_subscription_extension_if_necessary add_type_and_traverse(new_subscription_object, root: true) nil end else @subscription_object || find_inherited_value(:subscription) end end # @see [GraphQL::Schema::Warden] Restricted access to root types # @return [GraphQL::ObjectType, nil] def root_type_for_operation(operation) case operation when "query" query when "mutation" mutation when "subscription" subscription else raise ArgumentError, "unknown operation type: #{operation}" end end def root_types @root_types end def warden_class if defined?(@warden_class) @warden_class elsif superclass.respond_to?(:warden_class) superclass.warden_class else GraphQL::Schema::Warden end end attr_writer :warden_class # @param type [Module] The type definition whose possible types you want to see # @return [Hash] All possible types, if no `type` is given. # @return [Array] Possible types for `type`, if it's given. def possible_types(type = nil, context = GraphQL::Query::NullContext.instance) if type # TODO duck-typing `.possible_types` would probably be nicer here if type.kind.union? type.possible_types(context: context) else stored_possible_types = own_possible_types[type.graphql_name] visible_possible_types = if stored_possible_types && type.kind.interface? stored_possible_types.select do |possible_type| possible_type.interfaces(context).include?(type) end else stored_possible_types end visible_possible_types || introspection_system.possible_types[type.graphql_name] || ( superclass.respond_to?(:possible_types) ? superclass.possible_types(type, context) : EMPTY_ARRAY ) end else find_inherited_value(:possible_types, EMPTY_HASH) .merge(own_possible_types) .merge(introspection_system.possible_types) end end def union_memberships(type = nil) if type own_um = own_union_memberships.fetch(type.graphql_name, EMPTY_ARRAY) inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY) own_um + inherited_um else joined_um = own_union_memberships.dup find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v| um = joined_um[k] ||= [] um.concat(v) end joined_um end end # @api private # @see GraphQL::Dataloader def dataloader_class @dataloader_class || GraphQL::Dataloader::NullDataloader end attr_writer :dataloader_class def references_to(to_type = nil, from: nil) @own_references_to ||= {} if to_type if !to_type.is_a?(String) to_type = to_type.graphql_name end if from refs = @own_references_to[to_type] ||= [] refs << from else get_references_to(to_type) || EMPTY_ARRAY end else # `@own_references_to` can be quite large for big schemas, # and generally speaking, we won't inherit any values. # So optimize the most common case -- don't create a duplicate Hash. inherited_value = find_inherited_value(:references_to, EMPTY_HASH) if inherited_value.any? inherited_value.merge(@own_references_to) else @own_references_to end end end def type_from_ast(ast_node, context: nil) type_owner = context ? context.warden : self GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node) end def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance) parent_type = case type_or_name when LateBoundType get_type(type_or_name.name, context) when String get_type(type_or_name, context) when Module type_or_name else raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})" end if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context)) field elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name)) entry_point_field elsif (dynamic_field = introspection_system.dynamic_field(name: field_name)) dynamic_field else nil end end def get_fields(type, context = GraphQL::Query::NullContext.instance) type.fields(context) end def introspection(new_introspection_namespace = nil) if new_introspection_namespace @introspection = new_introspection_namespace # reset this cached value: @introspection_system = nil else @introspection || find_inherited_value(:introspection) end end def introspection_system if !@introspection_system @introspection_system = Schema::IntrospectionSystem.new(self) @introspection_system.resolve_late_bindings end @introspection_system end def cursor_encoder(new_encoder = nil) if new_encoder @cursor_encoder = new_encoder end @cursor_encoder || find_inherited_value(:cursor_encoder, Base64Encoder) end def default_max_page_size(new_default_max_page_size = nil) if new_default_max_page_size @default_max_page_size = new_default_max_page_size else @default_max_page_size || find_inherited_value(:default_max_page_size) end end def default_page_size(new_default_page_size = nil) if new_default_page_size @default_page_size = new_default_page_size else @default_page_size || find_inherited_value(:default_page_size) end end def query_execution_strategy(new_query_execution_strategy = nil, deprecation_warning: true) if deprecation_warning warn "GraphQL::Schema.query_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead." warn " #{caller(1, 1).first}" end if new_query_execution_strategy @query_execution_strategy = new_query_execution_strategy else @query_execution_strategy || (superclass.respond_to?(:query_execution_strategy) ? superclass.query_execution_strategy(deprecation_warning: false) : self.default_execution_strategy) end end def mutation_execution_strategy(new_mutation_execution_strategy = nil, deprecation_warning: true) if deprecation_warning warn "GraphQL::Schema.mutation_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead." warn " #{caller(1, 1).first}" end if new_mutation_execution_strategy @mutation_execution_strategy = new_mutation_execution_strategy else @mutation_execution_strategy || (superclass.respond_to?(:mutation_execution_strategy) ? superclass.mutation_execution_strategy(deprecation_warning: false) : self.default_execution_strategy) end end def subscription_execution_strategy(new_subscription_execution_strategy = nil, deprecation_warning: true) if deprecation_warning warn "GraphQL::Schema.subscription_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead." warn " #{caller(1, 1).first}" end if new_subscription_execution_strategy @subscription_execution_strategy = new_subscription_execution_strategy else @subscription_execution_strategy || (superclass.respond_to?(:subscription_execution_strategy) ? superclass.subscription_execution_strategy(deprecation_warning: false) : self.default_execution_strategy) end end attr_writer :validate_timeout def validate_timeout(new_validate_timeout = nil) if new_validate_timeout @validate_timeout = new_validate_timeout elsif defined?(@validate_timeout) @validate_timeout else find_inherited_value(:validate_timeout) end end # Validate a query string according to this schema. # @param string_or_document [String, GraphQL::Language::Nodes::Document] # @return [Array] def validate(string_or_document, rules: nil, context: nil) doc = if string_or_document.is_a?(String) GraphQL.parse(string_or_document) else string_or_document end query = query_class.new(self, document: doc, context: context) validator_opts = { schema: self } rules && (validator_opts[:rules] = rules) validator = GraphQL::StaticValidation::Validator.new(**validator_opts) res = validator.validate(query, timeout: validate_timeout, max_errors: validate_max_errors) res[:errors] end def query_class(new_query_class = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_query_class) @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query) else @query_class = new_query_class end end attr_writer :validate_max_errors def validate_max_errors(new_validate_max_errors = nil) if new_validate_max_errors @validate_max_errors = new_validate_max_errors elsif defined?(@validate_max_errors) @validate_max_errors else find_inherited_value(:validate_max_errors) end end attr_writer :max_complexity def max_complexity(max_complexity = nil) if max_complexity @max_complexity = max_complexity elsif defined?(@max_complexity) @max_complexity else find_inherited_value(:max_complexity) end end attr_writer :analysis_engine def analysis_engine @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine) end def using_ast_analysis? true end def interpreter? true end attr_writer :interpreter def error_bubbling(new_error_bubbling = nil) if !new_error_bubbling.nil? warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0") @error_bubbling = new_error_bubbling else @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling end end attr_writer :error_bubbling attr_writer :max_depth def max_depth(new_max_depth = nil, count_introspection_fields: true) if new_max_depth @max_depth = new_max_depth @count_introspection_fields = count_introspection_fields elsif defined?(@max_depth) @max_depth else find_inherited_value(:max_depth) end end def count_introspection_fields if defined?(@count_introspection_fields) @count_introspection_fields else find_inherited_value(:count_introspection_fields, true) end end def disable_introspection_entry_points @disable_introspection_entry_points = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_schema_introspection_entry_point @disable_schema_introspection_entry_point = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_type_introspection_entry_point @disable_type_introspection_entry_point = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_introspection_entry_points? if instance_variable_defined?(:@disable_introspection_entry_points) @disable_introspection_entry_points else find_inherited_value(:disable_introspection_entry_points?, false) end end def disable_schema_introspection_entry_point? if instance_variable_defined?(:@disable_schema_introspection_entry_point) @disable_schema_introspection_entry_point else find_inherited_value(:disable_schema_introspection_entry_point?, false) end end def disable_type_introspection_entry_point? if instance_variable_defined?(:@disable_type_introspection_entry_point) @disable_type_introspection_entry_point else find_inherited_value(:disable_type_introspection_entry_point?, false) end end # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema # @return [Array] Type definitions added to this schema def extra_types(*new_extra_types) if new_extra_types.any? new_extra_types = new_extra_types.flatten @own_extra_types ||= [] @own_extra_types.concat(new_extra_types) end inherited_et = find_inherited_value(:extra_types, nil) if inherited_et if @own_extra_types inherited_et + @own_extra_types else inherited_et end else @own_extra_types || EMPTY_ARRAY end end def orphan_types(*new_orphan_types) if new_orphan_types.any? new_orphan_types = new_orphan_types.flatten add_type_and_traverse(new_orphan_types, root: false) own_orphan_types.concat(new_orphan_types.flatten) end inherited_ot = find_inherited_value(:orphan_types, nil) if inherited_ot if own_orphan_types.any? inherited_ot + own_orphan_types else inherited_ot end else own_orphan_types end end def default_execution_strategy if superclass <= GraphQL::Schema superclass.default_execution_strategy else @default_execution_strategy ||= GraphQL::Execution::Interpreter end end def default_analysis_engine if superclass <= GraphQL::Schema superclass.default_analysis_engine else @default_analysis_engine ||= GraphQL::Analysis::AST end end def default_logger(new_default_logger = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_default_logger) if defined?(@default_logger) @default_logger elsif superclass.respond_to?(:default_logger) superclass.default_logger elsif defined?(Rails) && Rails.respond_to?(:logger) && (rails_logger = Rails.logger) rails_logger else def_logger = Logger.new($stdout) def_logger.info! # It doesn't output debug info by default def_logger end elsif new_default_logger == nil @default_logger = Logger.new(IO::NULL) else @default_logger = new_default_logger end end def context_class(new_context_class = nil) if new_context_class @context_class = new_context_class else @context_class || find_inherited_value(:context_class, GraphQL::Query::Context) end end def rescue_from(*err_classes, &handler_block) err_classes.each do |err_class| Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block) end end NEW_HANDLER_HASH = ->(h, k) { h[k] = { class: k, handler: nil, subclass_handlers: Hash.new(&NEW_HANDLER_HASH), } } def error_handlers @error_handlers ||= { class: nil, handler: nil, subclass_handlers: Hash.new(&NEW_HANDLER_HASH), } end # @api private def handle_or_reraise(context, err) handler = Execution::Errors.find_handler_for(self, err.class) if handler obj = context[:current_object] args = context[:current_arguments] args = args && args.respond_to?(:keyword_arguments) ? args.keyword_arguments : nil field = context[:current_field] if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end handler[:handler].call(err, obj, args, context, field) else raise err end end # rubocop:disable Lint/DuplicateMethods module ResolveTypeWithType def resolve_type(type, obj, ctx) maybe_lazy_resolve_type_result = if type.is_a?(Module) && type.respond_to?(:resolve_type) type.resolve_type(obj, ctx) else super end after_lazy(maybe_lazy_resolve_type_result) do |resolve_type_result| if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 resolved_type = resolve_type_result[0] resolved_value = resolve_type_result[1] else resolved_type = resolve_type_result resolved_value = obj end if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) [resolved_type, resolved_value] else raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`" end end end end def resolve_type(type, obj, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types, Interface types, or `loads:` (tried to resolve: #{type.name})" end # rubocop:enable Lint/DuplicateMethods def inherited(child_class) if self == GraphQL::Schema child_class.directives(default_directives.values) child_class.extend(SubclassGetReferencesTo) end # Make sure the child class has these built out, so that # subclasses can be modified by later calls to `trace_with` own_trace_modes.each do |name, _class| child_class.own_trace_modes[name] = child_class.build_trace_mode(name) end child_class.singleton_class.prepend(ResolveTypeWithType) super end def object_from_id(node_id, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)" end def id_from_object(object, type, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)" end def visible?(member, ctx) member.visible?(ctx) end def schema_directive(dir_class, **options) @own_schema_directives ||= [] Member::HasDirectives.add_directive(self, @own_schema_directives, dir_class, options) end def schema_directives Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives) end # This hook is called when an object fails an `authorized?` check. # You might report to your bug tracker here, so you can correct # the field resolvers not to return unauthorized objects. # # By default, this hook just replaces the unauthorized object with `nil`. # # Whatever value is returned from this method will be used instead of the # unauthorized object (accessible as `unauthorized_error.object`). If an # error is raised, then `nil` will be used. # # If you want to add an error to the `"errors"` key, raise a {GraphQL::ExecutionError} # in this hook. # # @param unauthorized_error [GraphQL::UnauthorizedError] # @return [Object] The returned object will be put in the GraphQL response def unauthorized_object(unauthorized_error) nil end # This hook is called when a field fails an `authorized?` check. # # By default, this hook implements the same behavior as unauthorized_object. # # Whatever value is returned from this method will be used instead of the # unauthorized field . If an error is raised, then `nil` will be used. # # If you want to add an error to the `"errors"` key, raise a {GraphQL::ExecutionError} # in this hook. # # @param unauthorized_error [GraphQL::UnauthorizedFieldError] # @return [Field] The returned field will be put in the GraphQL response def unauthorized_field(unauthorized_error) unauthorized_object(unauthorized_error) end def type_error(type_error, ctx) case type_error when GraphQL::InvalidNullError ctx.errors << type_error when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError raise type_error when GraphQL::IntegerDecodingError nil end end # A function to call when {#execute} receives an invalid query string # # The default is to add the error to `context.errors` # @param parse_err [GraphQL::ParseError] The error encountered during parsing # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred # @return void def parse_error(parse_err, ctx) ctx.errors.push(parse_err) end def lazy_resolve(lazy_class, value_method) lazy_methods.set(lazy_class, value_method) end def instrument(instrument_step, instrumenter, options = {}) warn <<~WARN Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html" (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first}) WARN trace_with(Tracing::LegacyHooksTrace) own_instrumenters[instrument_step] << instrumenter end # Add several directives at once # @param new_directives [Class] def directives(*new_directives) if new_directives.any? new_directives.flatten.each { |d| directive(d) } end inherited_dirs = find_inherited_value(:directives, default_directives) if own_directives.any? inherited_dirs.merge(own_directives) else inherited_dirs end end # Attach a single directive to this schema # @param new_directive [Class] # @return void def directive(new_directive) add_type_and_traverse(new_directive, root: false) end def default_directives @default_directives ||= { "include" => GraphQL::Schema::Directive::Include, "skip" => GraphQL::Schema::Directive::Skip, "deprecated" => GraphQL::Schema::Directive::Deprecated, "oneOf" => GraphQL::Schema::Directive::OneOf, "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy, }.freeze end def tracer(new_tracer) default_trace = trace_class_for(:default, build: true) if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers) trace_with(GraphQL::Tracing::CallLegacyTracers) end own_tracers << new_tracer end def tracers find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers end # Mix `trace_mod` into this schema's `Trace` class so that its methods # will be called at runtime. # # @param trace_mod [Module] A module that implements tracing methods # @param mode [Symbol] Trace module will only be used for this trade mode # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize` # @return [void] def trace_with(trace_mod, mode: :default, **options) if mode.is_a?(Array) mode.each { |m| trace_with(trace_mod, mode: m, **options) } else tc = own_trace_modes[mode] ||= build_trace_mode(mode) tc.include(trace_mod) own_trace_modules[mode] << trace_mod add_trace_options_for(mode, options) if mode == :default # This module is being added as a default tracer. If any other mode classes # have already been created, but get their default behavior from a superclass, # Then mix this into this schema's subclass. # (But don't mix it into mode classes that aren't default-based.) own_trace_modes.each do |other_mode_name, other_mode_class| if other_mode_class < DefaultTraceClass # Don't add it back to the inheritance tree if it's already there if !(other_mode_class < trace_mod) other_mode_class.include(trace_mod) end # Add any options so they'll be available add_trace_options_for(other_mode_name, options) end end end end nil end # The options hash for this trace mode # @return [Hash] def trace_options_for(mode) @trace_options_for_mode ||= {} @trace_options_for_mode[mode] ||= begin # It may be time to create an options hash for a mode that wasn't registered yet. # Mix in the default options in that case. default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default) # Make sure this returns a new object so that other hashes aren't modified later if superclass.respond_to?(:trace_options_for) superclass.trace_options_for(mode).merge(default_options) else default_options.dup end end end # Create a trace instance which will include the trace modules specified for the optional mode. # # If no `mode:` is given, then {default_trace_mode} will be used. # # @param mode [Symbol] Trace modules for this trade mode will be included # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize` # @return [Tracing::Trace] def new_trace(mode: nil, **options) target = options[:query] || options[:multiplex] mode ||= target && target.context[:trace_mode] trace_mode = if mode mode elsif target && target.context[:backtrace] if default_trace_mode != :default raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)" else own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace) options_trace_mode = :default :default_backtrace end else default_trace_mode end options_trace_mode ||= trace_mode base_trace_options = trace_options_for(options_trace_mode) trace_options = base_trace_options.merge(options) trace_class_for_mode = trace_class_for(trace_mode, build: true) trace_class_for_mode.new(**trace_options) end def query_analyzer(new_analyzer) own_query_analyzers << new_analyzer end def query_analyzers find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers end def multiplex_analyzer(new_analyzer) own_multiplex_analyzers << new_analyzer end def multiplex_analyzers find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers end def sanitized_printer(new_sanitized_printer = nil) if new_sanitized_printer @own_sanitized_printer = new_sanitized_printer else @own_sanitized_printer || GraphQL::Language::SanitizedPrinter end end # Execute a query on itself. # @see {Query#initialize} for arguments. # @return [Hash] query result, ready to be serialized as JSON def execute(query_str = nil, **kwargs) if query_str kwargs[:query] = query_str end # Some of the query context _should_ be passed to the multiplex, too multiplex_context = if (ctx = kwargs[:context]) { backtrace: ctx[:backtrace], tracers: ctx[:tracers], trace: ctx[:trace], dataloader: ctx[:dataloader], trace_mode: ctx[:trace_mode], } else {} end # Since we're running one query, don't run a multiplex-level complexity analyzer all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context) all_results[0] end # Execute several queries on itself, concurrently. # # @example Run several queries at once # context = { ... } # queries = [ # { query: params[:query_1], variables: params[:variables_1], context: context }, # { query: params[:query_2], variables: params[:variables_2], context: context }, # ] # results = MySchema.multiplex(queries) # render json: { # result_1: results[0], # result_2: results[1], # } # # @see {Query#initialize} for query keyword arguments # @see {Execution::Multiplex#run_all} for multiplex keyword arguments # @param queries [Array] Keyword arguments for each query # @param context [Hash] Multiplex-level context # @return [Array] One result for each query in the input def multiplex(queries, **kwargs) GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs) end def instrumenters inherited_instrumenters = find_inherited_value(:instrumenters) || Hash.new { |h,k| h[k] = [] } inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own| inherited + own end end # @api private def add_subscription_extension_if_necessary if !defined?(@subscription_extension_added) && subscription && self.subscriptions @subscription_extension_added = true subscription.all_field_definitions.each do |field| if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) } field.extension(Subscriptions::DefaultSubscriptionResolveExtension) end end end end def query_stack_error(query, err) query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute.")) end # Call the given block at the right time, either: # - Right away, if `value` is not registered with `lazy_resolve` # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`) # @api private def after_lazy(value, &block) if lazy?(value) GraphQL::Execution::Lazy.new do result = sync_lazy(value) # The returned result might also be lazy, so check it, too after_lazy(result, &block) end else yield(value) if block_given? end end # Override this method to handle lazy objects in a custom way. # @param value [Object] an instance of a class registered with {.lazy_resolve} # @return [Object] A GraphQL-ready (non-lazy) object # @api private def sync_lazy(value) lazy_method = lazy_method_name(value) if lazy_method synced_value = value.public_send(lazy_method) sync_lazy(synced_value) else value end end # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}. def lazy_method_name(obj) lazy_methods.get(obj) end # @return [Boolean] True if this object should be lazily resolved def lazy?(obj) !!lazy_method_name(obj) end # Return a lazy if any of `maybe_lazies` are lazy, # otherwise, call the block eagerly and return the result. # @param maybe_lazies [Array] # @api private def after_any_lazies(maybe_lazies) if maybe_lazies.any? { |l| lazy?(l) } GraphQL::Execution::Lazy.all(maybe_lazies).then do |result| yield result end else yield maybe_lazies end end private def add_trace_options_for(mode, new_options) t_opts = trace_options_for(mode) t_opts.merge!(new_options) nil end # @param t [Module, Array] # @return [void] def add_type_and_traverse(t, root:) if root @root_types ||= [] @root_types << t end new_types = Array(t) addition = Schema::Addition.new(schema: self, own_types: own_types, new_types: new_types) addition.types.each do |name, types_entry| # rubocop:disable Development/ContextIsPassedCop -- build-time, not query-time if (prev_entry = own_types[name]) prev_entries = case prev_entry when Array prev_entry when Module own_types[name] = [prev_entry] else raise "Invariant: unexpected prev_entry at #{name.inspect} when adding #{t.inspect}" end case types_entry when Array prev_entries.concat(types_entry) prev_entries.uniq! # in case any are being re-visited when Module if !prev_entries.include?(types_entry) prev_entries << types_entry end else raise "Invariant: unexpected types_entry at #{name} when adding #{t.inspect}" end else if types_entry.is_a?(Array) types_entry.uniq! end own_types[name] = types_entry end end own_possible_types.merge!(addition.possible_types) { |key, old_val, new_val| old_val + new_val } own_union_memberships.merge!(addition.union_memberships) addition.references.each { |thing, pointers| pointers.each { |pointer| references_to(thing, from: pointer) } } addition.directives.each { |dir_class| own_directives[dir_class.graphql_name] = dir_class } addition.arguments_with_default_values.each do |arg| arg.validate_default_value end end def lazy_methods if !defined?(@lazy_methods) if inherited_map = find_inherited_value(:lazy_methods) # this isn't _completely_ inherited :S (Things added after `dup` won't work) @lazy_methods = inherited_map.dup else @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new @lazy_methods.set(GraphQL::Execution::Lazy, :value) @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning) end end @lazy_methods end def own_types @own_types ||= {} end def non_introspection_types find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types) end def own_plugins @own_plugins ||= [] end def own_orphan_types @own_orphan_types ||= [] end def own_possible_types @own_possible_types ||= {} end def own_union_memberships @own_union_memberships ||= {} end def own_directives @own_directives ||= {} end def own_instrumenters @own_instrumenters ||= Hash.new { |h,k| h[k] = [] } end def own_tracers @own_tracers ||= [] end def own_query_analyzers @defined_query_analyzers ||= [] end def own_multiplex_analyzers @own_multiplex_analyzers ||= [] end # This is overridden in subclasses to check the inheritance chain def get_references_to(type_name) @own_references_to[type_name] end end module SubclassGetReferencesTo def get_references_to(type_name) own_refs = @own_references_to[type_name] inherited_refs = superclass.references_to(type_name) if inherited_refs&.any? if own_refs&.any? own_refs + inherited_refs else inherited_refs end else own_refs end end end # Install these here so that subclasses will also install it. self.connections = GraphQL::Pagination::Connections.new(schema: self) end end graphql-ruby-2.2.17/lib/graphql/schema/000077500000000000000000000000001476434635200177025ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/addition.rb000066400000000000000000000240021476434635200220200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Addition attr_reader :directives, :possible_types, :types, :union_memberships, :references, :arguments_with_default_values def initialize(schema:, own_types:, new_types:) @schema = schema @own_types = own_types @directives = Set.new @possible_types = {} @types = {} @union_memberships = {} @references = Hash.new { |h, k| h[k] = [] } @arguments_with_default_values = [] add_type_and_traverse(new_types) end private def references_to(thing, from:) @references[thing] << from end def get_type(name) local_type = @types[name] # This isn't really sophisticated, but # I think it's good enough to support the current usage of LateBoundTypes if local_type.is_a?(Array) local_type = local_type.first end local_type || @schema.get_type(name) end # Lookup using `own_types` here because it's ok to override # inherited types by name def get_local_type(name) @types[name] || @own_types[name] end def add_directives_from(owner) if (dir_instances = owner.directives).any? dirs = dir_instances.map(&:class) @directives.merge(dirs) add_type_and_traverse(dirs) end end def add_type_and_traverse(new_types) late_types = [] path = [] new_types.each do |t| path.push(t.graphql_name) add_type(t, owner: nil, late_types: late_types, path: path) path.pop end missed_late_types = 0 while (late_type_vals = late_types.shift) type_owner, lt = late_type_vals if lt.is_a?(String) type = Member::BuildType.constantize(lt) # Reset the counter, since we might succeed next go-round missed_late_types = 0 update_type_owner(type_owner, type) add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name]) elsif lt.is_a?(LateBoundType) if (type = get_type(lt.name)) # Reset the counter, since we might succeed next go-round missed_late_types = 0 update_type_owner(type_owner, type) add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name]) else missed_late_types += 1 # Add it back to the list, maybe we'll be able to resolve it later. late_types << [type_owner, lt] if missed_late_types == late_types.size # We've looked at all of them and haven't resolved one. raise UnresolvedLateBoundTypeError.new(type: lt) else # Try the next one end end else raise ArgumentError, "Unexpected late type: #{lt.inspect}" end end nil end def update_type_owner(owner, type) case owner when Module if owner.kind.union? # It's a union with possible_types # Replace the item by class name owner.assign_type_membership_object_type(type) @possible_types[owner.graphql_name] = owner.possible_types elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?) new_interfaces = [] owner.interfaces.each do |int_t| if int_t.is_a?(String) && int_t == type.graphql_name new_interfaces << type elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name new_interfaces << type else # Don't re-add proper interface definitions, # they were probably already added, maybe with options. end end owner.implements(*new_interfaces) new_interfaces.each do |int| pt = @possible_types[int.graphql_name] ||= [] if !pt.include?(owner) && owner.is_a?(Class) pt << owner end int.interfaces.each do |indirect_int| if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name)) update_type_owner(owner, indirect_int_type) end end end end when nil # It's a root type @types[type.graphql_name] = type when GraphQL::Schema::Field, GraphQL::Schema::Argument orig_type = owner.type # Apply list/non-null wrapper as needed if orig_type.respond_to?(:of_type) transforms = [] while (orig_type.respond_to?(:of_type)) if orig_type.kind.non_null? transforms << :to_non_null_type elsif orig_type.kind.list? transforms << :to_list_type else raise "Invariant: :of_type isn't non-null or list" end orig_type = orig_type.of_type end transforms.reverse_each { |t| type = type.public_send(t) } end owner.type = type else raise "Unexpected update: #{owner.inspect} #{type.inspect}" end end def add_type(type, owner:, late_types:, path:) if type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType) late_types << [owner, type] return end if owner.is_a?(Class) && owner < GraphQL::Schema::Union um = @union_memberships[type.graphql_name] ||= [] um << owner end if (prev_type = get_local_type(type.graphql_name)) && (prev_type == type || (prev_type.is_a?(Array) && prev_type.include?(type))) # No need to re-visit elsif type.is_a?(Class) && type < GraphQL::Schema::Directive @directives << type type.all_argument_definitions.each do |arg| arg_type = arg.type.unwrap references_to(arg_type, from: arg) path.push(arg.graphql_name) add_type(arg_type, owner: arg, late_types: late_types, path: path) path.pop if arg.default_value? @arguments_with_default_values << arg end end else prev_type = @types[type.graphql_name] if prev_type.nil? @types[type.graphql_name] = type elsif prev_type.is_a?(Array) prev_type << type else @types[type.graphql_name] = [prev_type, type] end add_directives_from(type) if type.kind.fields? type.all_field_definitions.each do |field| name = field.graphql_name field_type = field.type.unwrap references_to(field_type, from: field) path.push(name) add_type(field_type, owner: field, late_types: late_types, path: path) add_directives_from(field) field.all_argument_definitions.each do |arg| add_directives_from(arg) arg_type = arg.type.unwrap references_to(arg_type, from: arg) path.push(arg.graphql_name) add_type(arg_type, owner: arg, late_types: late_types, path: path) path.pop if arg.default_value? @arguments_with_default_values << arg end end path.pop end end if type.kind.input_object? type.all_argument_definitions.each do |arg| add_directives_from(arg) arg_type = arg.type.unwrap references_to(arg_type, from: arg) path.push(arg.graphql_name) add_type(arg_type, owner: arg, late_types: late_types, path: path) path.pop if arg.default_value? @arguments_with_default_values << arg end end end if type.kind.union? @possible_types[type.graphql_name] = type.all_possible_types path.push("possible_types") type.all_possible_types.each do |t| add_type(t, owner: type, late_types: late_types, path: path) end path.pop end if type.kind.interface? path.push("orphan_types") type.orphan_types.each do |t| add_type(t, owner: type, late_types: late_types, path: path) end path.pop end if type.kind.object? possible_types_for_this_name = @possible_types[type.graphql_name] ||= [] possible_types_for_this_name << type end if type.kind.object? || type.kind.interface? path.push("implements") type.interface_type_memberships.each do |interface_type_membership| case interface_type_membership when Schema::TypeMembership interface_type = interface_type_membership.abstract_type # We can get these now; we'll have to get late-bound types later if interface_type.is_a?(Module) && type.is_a?(Class) implementers = @possible_types[interface_type.graphql_name] ||= [] implementers << type end when String, Schema::LateBoundType interface_type = interface_type_membership else raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})" end add_type(interface_type, owner: type, late_types: late_types, path: path) end path.pop end if type.kind.enum? type.all_enum_value_definitions.each do |value_definition| add_directives_from(value_definition) end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/always_visible.rb000066400000000000000000000003251476434635200232440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class AlwaysVisible def self.use(schema, **opts) schema.warden_class = GraphQL::Schema::Warden::NullWarden end end end end graphql-ruby-2.2.17/lib/graphql/schema/argument.rb000066400000000000000000000353341476434635200220610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Argument include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasDeprecationReason include GraphQL::Schema::Member::HasValidators include GraphQL::EmptyObjects # @return [String] the GraphQL name for this argument, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name # @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to attr_reader :owner # @param new_prepare [Method, Proc] # @return [Symbol] A method or proc to call to transform this value before sending it to field resolution method def prepare(new_prepare = NOT_CONFIGURED) if new_prepare != NOT_CONFIGURED @prepare = new_prepare end @prepare end # @return [Symbol] This argument's name in Ruby keyword arguments attr_reader :keyword # @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load attr_reader :loads # @return [Boolean] true if a resolver defined this argument def from_resolver? @from_resolver end # @param arg_name [Symbol] # @param type_expr # @param desc [String] # @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. # @param description [String] # @param default_value [Object] # @param as [Symbol] Override the keyword name when passed to a method # @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution # @param camelize [Boolean] if true, the name will be camelized when building the schema # @param from_resolver [Boolean] if true, a Resolver class defined this argument # @param directives [Hash{Class => Hash}] # @param deprecation_reason [String] # @param validates [Hash, nil] Options for building validators, if any should be applied # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value` def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block) arg_name ||= name @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s) NameValidator.validate!(@name) @type_expr = type_expr || type @description = desc || description @null = required != true @default_value = default_value if replace_null_with_default if !default_value? raise ArgumentError, "`replace_null_with_default: true` requires a default value, please provide one with `default_value: ...`" end @replace_null_with_default = true end @owner = owner @as = as @loads = loads @keyword = as || (arg_name.is_a?(Symbol) ? arg_name : Schema::Member::BuildType.underscore(@name).to_sym) @prepare = prepare @ast_node = ast_node @from_resolver = from_resolver self.deprecation_reason = deprecation_reason if directives directives.each do |dir_class, dir_options| directive(dir_class, **dir_options) end end if validates && !validates.empty? self.validates(validates) end if required == :nullable self.owner.validates(required: { argument: arg_name }) end if definition_block # `self` will still be self, it will also be the first argument to the block: instance_exec(self, &definition_block) end end def inspect "#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>" end # @param default_value [Object] The value to use when the client doesn't provide one # @return [Object] the value used when the client doesn't provide a value for this argument def default_value(new_default_value = NOT_CONFIGURED) if new_default_value != NOT_CONFIGURED @default_value = new_default_value end @default_value end # @return [Boolean] True if this argument has a default value def default_value? @default_value != NOT_CONFIGURED end def replace_null_with_default? @replace_null_with_default end attr_writer :description # @return [String] Documentation for this argument def description(text = nil) if text @description = text else @description end end # @return [String] Deprecation reason for this argument def deprecation_reason(text = nil) if text self.deprecation_reason = text else super() end end def deprecation_reason=(new_reason) validate_deprecated_or_optional(null: @null, deprecation_reason: new_reason) super end def visible?(context) true end def authorized?(obj, value, ctx) authorized_as_type?(obj, value, ctx, as_type: type) end def authorized_as_type?(obj, value, ctx, as_type:) if value.nil? return true end if as_type.kind.non_null? as_type = as_type.of_type end if as_type.kind.list? value.each do |v| if !authorized_as_type?(obj, v, ctx, as_type: as_type.of_type) return false end end elsif as_type.kind.input_object? return as_type.authorized?(obj, value, ctx) end # None of the early-return conditions were activated, # so this is authorized. true end def type=(new_type) validate_input_type(new_type) # This isn't true for LateBoundTypes, but we can assume those will # be updated via this codepath later in schema setup. if new_type.respond_to?(:non_null?) validate_deprecated_or_optional(null: !new_type.non_null?, deprecation_reason: deprecation_reason) end @type = new_type end def type @type ||= begin parsed_type = begin Member::BuildType.parse_type(@type_expr, null: @null) rescue StandardError => err raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace end # Use the setter method to get validations self.type = parsed_type end end def statically_coercible? return @statically_coercible if defined?(@statically_coercible) requires_parent_object = @prepare.is_a?(String) || @prepare.is_a?(Symbol) || @own_validators @statically_coercible = !requires_parent_object end # Apply the {prepare} configuration to `value`, using methods from `obj`. # Used by the runtime. # @api private def prepare_value(obj, value, context: nil) if value.is_a?(GraphQL::Schema::InputObject) value = value.prepare end Schema::Validator.validate!(validators, obj, context, value) if @prepare.nil? value elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol) if obj.nil? # The problem here is, we _used to_ prepare while building variables. # But now we don't have the runtime object there. # # This will have to be called later, when the runtime object _is_ available. value elsif obj.respond_to?(@prepare) obj.public_send(@prepare, value) elsif owner.respond_to?(@prepare) owner.public_send(@prepare, value, context || obj.context) else raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}. "\ "Could not find prepare method #{@prepare} on #{obj.class} or #{owner}." end elsif @prepare.respond_to?(:call) @prepare.call(value, context || obj.context) else raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}" end end # @api private def coerce_into_values(parent_object, values, context, argument_values) arg_name = graphql_name arg_key = keyword default_used = false if values.key?(arg_name) value = values[arg_name] elsif values.key?(arg_key) value = values[arg_key] elsif default_value? value = default_value default_used = true else # no value at all owner.validate_directive_argument(self, nil) return end if value.nil? && replace_null_with_default? value = default_value default_used = true end loaded_value = nil coerced_value = begin type.coerce_input(value, context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end # If this isn't lazy, then the block returns eagerly and assigns the result here # If it _is_ lazy, then we write the lazy to the hash, then update it later argument_values[arg_key] = context.query.after_lazy(coerced_value) do |resolved_coerced_value| owner.validate_directive_argument(self, resolved_coerced_value) prepared_value = begin prepare_value(parent_object, resolved_coerced_value, context: context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end if loads && !from_resolver? loaded_value = begin load_and_authorize_value(owner, prepared_value, context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end end maybe_loaded_value = loaded_value || prepared_value context.query.after_lazy(maybe_loaded_value) do |resolved_loaded_value| # TODO code smell to access such a deeply-nested constant in a distant module argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new( value: resolved_loaded_value, definition: self, default_used: default_used, ) end end end def load_and_authorize_value(load_method_owner, coerced_value, context) if coerced_value.nil? return nil end arg_load_method = "load_#{keyword}" if load_method_owner.respond_to?(arg_load_method) custom_loaded_value = if load_method_owner.is_a?(Class) load_method_owner.public_send(arg_load_method, coerced_value, context) else load_method_owner.public_send(arg_load_method, coerced_value) end context.query.after_lazy(custom_loaded_value) do |custom_value| if loads if type.list? loaded_values = custom_value.each_with_index.map { |custom_val, idx| id = coerced_value[idx] load_method_owner.authorize_application_object(self, id, context, custom_val) } context.schema.after_any_lazies(loaded_values, &:itself) else load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value) end else custom_value end end elsif loads if type.list? loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) } context.schema.after_any_lazies(loaded_values, &:itself) else load_method_owner.load_and_authorize_application_object(self, coerced_value, context) end else coerced_value end end # @api private def validate_default_value coerced_default_value = begin # This is weird, but we should accept single-item default values for list-type arguments. # If we used `coerce_isolated_input` below, it would do this for us, but it's not really # the right thing here because we expect default values in application format (Ruby values) # not GraphQL format (scalar values). # # But I don't think Schema::List#coerce_result should apply wrapping to single-item lists. prepped_default_value = if default_value.nil? nil elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map) [default_value] else default_value end type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil? rescue GraphQL::Schema::Enum::UnresolvedValueError # It raises this, which is helpful at runtime, but not here... default_value end res = type.valid_isolated_input?(coerced_default_value) if !res raise InvalidDefaultValueError.new(self) end end class InvalidDefaultValueError < GraphQL::Error def initialize(argument) message = "`#{argument.path}` has an invalid default value: `#{argument.default_value.inspect}` isn't accepted by `#{argument.type.to_type_signature}`; update the default value or the argument type." super(message) end end private def validate_input_type(input_type) if input_type.is_a?(String) || input_type.is_a?(GraphQL::Schema::LateBoundType) # Do nothing; assume this will be validated later elsif input_type.kind.non_null? || input_type.kind.list? validate_input_type(input_type.unwrap) elsif !input_type.kind.input? raise ArgumentError, "Invalid input type for #{path}: #{input_type.graphql_name}. Must be scalar, enum, or input object, not #{input_type.kind.name}." else # It's an input type, we're OK end end def validate_deprecated_or_optional(null:, deprecation_reason:) if deprecation_reason && !null raise ArgumentError, "Required arguments cannot be deprecated: #{path}." end end end end end graphql-ruby-2.2.17/lib/graphql/schema/base_64_encoder.rb000066400000000000000000000010271476434635200231510ustar00rootroot00000000000000# frozen_string_literal: true require "base64" module GraphQL class Schema # @api private module Base64Encoder def self.encode(unencoded_text, nonce: false) Base64.urlsafe_encode64(unencoded_text, padding: false) end def self.decode(encoded_text, nonce: false) # urlsafe_decode64 is for forward compatibility Base64.urlsafe_decode64(encoded_text) rescue ArgumentError raise GraphQL::ExecutionError, "Invalid input: #{encoded_text.inspect}" end end end end graphql-ruby-2.2.17/lib/graphql/schema/build_from_definition.rb000066400000000000000000000502411476434635200245630ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map" module GraphQL class Schema module BuildFromDefinition class << self # @see {Schema.from_definition} def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs) from_document(schema_superclass, parser.parse(definition_string), **kwargs) end def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs) from_document(schema_superclass, parser.parse_file(definition_path), **kwargs) end def from_document(schema_superclass, document, default_resolve:, using: {}, relay: false) Builder.build(schema_superclass, document, default_resolve: default_resolve || {}, relay: relay, using: using) end end # @api private module Builder include GraphQL::EmptyObjects extend self def build(schema_superclass, document, default_resolve:, using: {}, relay:) raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document) if default_resolve.is_a?(Hash) default_resolve = ResolveMap.new(default_resolve) end schema_defns = document.definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::SchemaDefinition) } if schema_defns.size > 1 raise InvalidDocumentError.new('Must provide only one schema definition.') end schema_definition = schema_defns.first types = {} directives = schema_superclass.directives.dup type_resolver = build_resolve_type(types, directives, ->(type_name) { types[type_name] ||= Schema::LateBoundType.new(type_name)}) # Make a different type resolver because we need to coerce directive arguments # _while_ building the schema. # It will dig for a type if it encounters a custom type. This could be a problem if there are cycles. directive_type_resolver = nil directive_type_resolver = build_resolve_type(types, directives, ->(type_name) { types[type_name] ||= begin defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name } if defn build_definition_from_node(defn, directive_type_resolver, default_resolve) elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name]) built_in_defn else raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it." end end }) directives.merge!(GraphQL::Schema.default_directives) document.definitions.each do |definition| if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition) directives[definition.name] = build_directive(definition, directive_type_resolver) end end # In case any directives referenced built-in types for their arguments: replace_late_bound_types_with_built_in(types) schema_extensions = nil document.definitions.each do |definition| case definition when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition nil # already handled when GraphQL::Language::Nodes::SchemaExtension schema_extensions ||= [] schema_extensions << definition else # It's possible that this was already loaded by the directives prev_type = types[definition.name] if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType) types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve) end end end replace_late_bound_types_with_built_in(types) if schema_definition if schema_definition.query raise InvalidDocumentError.new("Specified query type \"#{schema_definition.query}\" not found in document.") unless types[schema_definition.query] query_root_type = types[schema_definition.query] end if schema_definition.mutation raise InvalidDocumentError.new("Specified mutation type \"#{schema_definition.mutation}\" not found in document.") unless types[schema_definition.mutation] mutation_root_type = types[schema_definition.mutation] end if schema_definition.subscription raise InvalidDocumentError.new("Specified subscription type \"#{schema_definition.subscription}\" not found in document.") unless types[schema_definition.subscription] subscription_root_type = types[schema_definition.subscription] end if schema_definition.query.nil? && schema_definition.mutation.nil? && schema_definition.subscription.nil? # This schema may have been given with directives only, # check for defaults: query_root_type = types['Query'] mutation_root_type = types['Mutation'] subscription_root_type = types['Subscription'] end else query_root_type = types['Query'] mutation_root_type = types['Mutation'] subscription_root_type = types['Subscription'] end raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type builder = self schema_class = Class.new(schema_superclass) do begin # Add these first so that there's some chance of resolving late-bound types orphan_types types.values query query_root_type mutation mutation_root_type subscription subscription_root_type rescue Schema::UnresolvedLateBoundTypeError => err type_name = err.type.name err_backtrace = err.backtrace raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace end if default_resolve.respond_to?(:resolve_type) def self.resolve_type(*args) self.definition_default_resolve.resolve_type(*args) end else def self.resolve_type(*args) NullResolveType.call(*args) end end directives directives.values if schema_definition ast_node(schema_definition) builder.build_directives(self, schema_definition, type_resolver) end using.each do |plugin, options| if options use(plugin, **options) else use(plugin) end end # Empty `orphan_types` -- this will make unreachable types ... unreachable. own_orphan_types.clear class << self attr_accessor :definition_default_resolve end self.definition_default_resolve = default_resolve def definition_default_resolve self.class.definition_default_resolve end def self.inherited(child_class) child_class.definition_default_resolve = self.definition_default_resolve end end if schema_extensions schema_extensions.each do |ext| build_directives(schema_class, ext, type_resolver) end end schema_class end NullResolveType = ->(type, obj, ctx) { raise(GraphQL::RequiredImplementationMissingError, "Generated Schema cannot use Interface or Union types for execution. Implement resolve_type on your resolver.") } def build_definition_from_node(definition, type_resolver, default_resolve) case definition when GraphQL::Language::Nodes::EnumTypeDefinition build_enum_type(definition, type_resolver) when GraphQL::Language::Nodes::ObjectTypeDefinition build_object_type(definition, type_resolver) when GraphQL::Language::Nodes::InterfaceTypeDefinition build_interface_type(definition, type_resolver) when GraphQL::Language::Nodes::UnionTypeDefinition build_union_type(definition, type_resolver) when GraphQL::Language::Nodes::ScalarTypeDefinition build_scalar_type(definition, type_resolver, default_resolve: default_resolve) when GraphQL::Language::Nodes::InputObjectTypeDefinition build_input_object_type(definition, type_resolver) end end # Modify `types`, replacing any late-bound references to built-in types # with their actual definitions. # # (Schema definitions are allowed to reference those built-ins without redefining them.) # @return void def replace_late_bound_types_with_built_in(types) GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar| existing_type = types[scalar_name] if existing_type.is_a?(GraphQL::Schema::LateBoundType) types[scalar_name] = built_in_scalar end end end def build_directives(definition, ast_node, type_resolver) dirs = prepare_directives(ast_node, type_resolver) dirs.each do |(dir_class, options)| if definition.respond_to?(:schema_directive) # it's a schema definition.schema_directive(dir_class, **options) else definition.directive(dir_class, **options) end end end def prepare_directives(ast_node, type_resolver) dirs = [] ast_node.directives.each do |dir_node| if dir_node.name == "deprecated" # This is handled using `deprecation_reason` next else dir_class = type_resolver.call(dir_node.name) if dir_class.nil? raise ArgumentError, "No definition for @#{dir_node.name} #{ast_node.respond_to?(:name) ? "on #{ast_node.name} " : ""}at #{ast_node.line}:#{ast_node.col}" end options = args_to_kwargs(dir_class, dir_node) dirs << [dir_class, options] end end dirs end def args_to_kwargs(arg_owner, node) if node.respond_to?(:arguments) kwargs = {} node.arguments.each do |arg_node| arg_defn = arg_owner.get_argument(arg_node.name) kwargs[arg_defn.keyword] = args_to_kwargs(arg_defn.type.unwrap, arg_node.value) end kwargs elsif node.is_a?(Array) node.map { |n| args_to_kwargs(arg_owner, n) } elsif node.is_a?(Language::Nodes::Enum) node.name else # scalar node end end def build_enum_type(enum_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Enum) do graphql_name(enum_type_definition.name) builder.build_directives(self, enum_type_definition, type_resolver) description(enum_type_definition.description) ast_node(enum_type_definition) enum_type_definition.values.each do |enum_value_definition| value(enum_value_definition.name, value: enum_value_definition.name, deprecation_reason: builder.build_deprecation_reason(enum_value_definition.directives), description: enum_value_definition.description, directives: builder.prepare_directives(enum_value_definition, type_resolver), ast_node: enum_value_definition, ) end end end def build_deprecation_reason(directives) deprecated_directive = directives.find{ |d| d.name == 'deprecated' } return unless deprecated_directive reason = deprecated_directive.arguments.find{ |a| a.name == 'reason' } return GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON unless reason reason.value end def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:) builder = self Class.new(GraphQL::Schema::Scalar) do graphql_name(scalar_type_definition.name) description(scalar_type_definition.description) ast_node(scalar_type_definition) builder.build_directives(self, scalar_type_definition, type_resolver) if default_resolve.respond_to?(:coerce_input) # Put these method definitions in another method to avoid retaining `type_resolve` # from this method's bindiing builder.build_scalar_type_coerce_method(self, :coerce_input, default_resolve) builder.build_scalar_type_coerce_method(self, :coerce_result, default_resolve) end end end def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve) scalar_class.define_singleton_method(method_name) do |val, ctx| default_definition_resolve.public_send(method_name, self, val, ctx) end end def build_union_type(union_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Union) do graphql_name(union_type_definition.name) description(union_type_definition.description) possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) }) ast_node(union_type_definition) builder.build_directives(self, union_type_definition, type_resolver) end end def build_object_type(object_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Object) do graphql_name(object_type_definition.name) description(object_type_definition.description) ast_node(object_type_definition) builder.build_directives(self, object_type_definition, type_resolver) object_type_definition.interfaces.each do |interface_name| interface_defn = type_resolver.call(interface_name) implements(interface_defn) end builder.build_fields(self, object_type_definition.fields, type_resolver, default_resolve: true) end end def build_input_object_type(input_object_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::InputObject) do graphql_name(input_object_type_definition.name) description(input_object_type_definition.description) ast_node(input_object_type_definition) builder.build_directives(self, input_object_type_definition, type_resolver) builder.build_arguments(self, input_object_type_definition.fields, type_resolver) end end def build_default_value(default_value) case default_value when GraphQL::Language::Nodes::Enum default_value.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject default_value.to_h when Array default_value.map { |v| build_default_value(v) } else default_value end end def build_arguments(type_class, arguments, type_resolver) builder = self arguments.each do |argument_defn| default_value_kwargs = if !argument_defn.default_value.nil? { default_value: builder.build_default_value(argument_defn.default_value) } else EMPTY_HASH end type_class.argument( argument_defn.name, type: type_resolver.call(argument_defn.type), required: false, description: argument_defn.description, deprecation_reason: builder.build_deprecation_reason(argument_defn.directives), ast_node: argument_defn, camelize: false, directives: prepare_directives(argument_defn, type_resolver), **default_value_kwargs ) end end def build_directive(directive_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Directive) do graphql_name(directive_definition.name) description(directive_definition.description) repeatable(directive_definition.repeatable) locations(*directive_definition.locations.map { |location| location.name.to_sym }) ast_node(directive_definition) builder.build_arguments(self, directive_definition.arguments, type_resolver) end end def build_interface_type(interface_type_definition, type_resolver) builder = self Module.new do include GraphQL::Schema::Interface graphql_name(interface_type_definition.name) description(interface_type_definition.description) interface_type_definition.interfaces.each do |interface_name| interface_defn = type_resolver.call(interface_name) implements(interface_defn) end ast_node(interface_type_definition) builder.build_directives(self, interface_type_definition, type_resolver) builder.build_fields(self, interface_type_definition.fields, type_resolver, default_resolve: nil) end end def build_fields(owner, field_definitions, type_resolver, default_resolve:) builder = self field_definitions.each do |field_definition| resolve_method_name = -"resolve_field_#{field_definition.name}" schema_field_defn = owner.field( field_definition.name, description: field_definition.description, type: type_resolver.call(field_definition.type), null: true, connection_extension: nil, deprecation_reason: build_deprecation_reason(field_definition.directives), ast_node: field_definition, method_conflict_warning: false, camelize: false, directives: prepare_directives(field_definition, type_resolver), resolver_method: resolve_method_name, ) builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver) # Don't do this for interfaces if default_resolve define_field_resolve_method(owner, resolve_method_name, field_definition.name) end end end def define_field_resolve_method(owner, method_name, field_name) owner.define_method(method_name) { |**args| field_instance = self.class.get_field(field_name) context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context) } end def build_resolve_type(lookup_hash, directives, missing_type_handler) resolve_type_proc = nil resolve_type_proc = ->(ast_node) { case ast_node when GraphQL::Language::Nodes::TypeName type_name = ast_node.name if lookup_hash.key?(type_name) lookup_hash[type_name] else missing_type_handler.call(type_name) end when GraphQL::Language::Nodes::NonNullType resolve_type_proc.call(ast_node.of_type).to_non_null_type when GraphQL::Language::Nodes::ListType resolve_type_proc.call(ast_node.of_type).to_list_type when String directives[ast_node] else raise "Unexpected ast_node: #{ast_node.inspect}" end } resolve_type_proc end end private_constant :Builder end end end graphql-ruby-2.2.17/lib/graphql/schema/build_from_definition/000077500000000000000000000000001476434635200242345ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/build_from_definition/resolve_map.rb000066400000000000000000000054151476434635200271020ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map/default_resolve" module GraphQL class Schema module BuildFromDefinition # Wrap a user-provided hash of resolution behavior for easy access at runtime. # # Coerce scalar values by: # - Checking for a function in the map like `{ Date: { coerce_input: ->(val, ctx) { ... }, coerce_result: ->(val, ctx) { ... } } }` # - Falling back to a passthrough # # Interface/union resolution can be provided as a `resolve_type:` key. # # @api private class ResolveMap module NullScalarCoerce def self.call(val, _ctx) val end end def initialize(user_resolve_hash) @resolve_hash = Hash.new do |h, k| # For each type name, provide a new hash if one wasn't given: h[k] = Hash.new do |h2, k2| if k2 == "coerce_input" || k2 == "coerce_result" # This isn't an object field, it's a scalar coerce function. # Use a passthrough NullScalarCoerce else # For each field, provide a resolver that will # make runtime checks & replace itself h2[k2] = DefaultResolve.new(h2, k2) end end end @user_resolve_hash = user_resolve_hash # User-provided resolve functions take priority over the default: @user_resolve_hash.each do |type_name, fields| type_name_s = type_name.to_s case fields when Hash fields.each do |field_name, resolve_fn| @resolve_hash[type_name_s][field_name.to_s] = resolve_fn end when Proc # for example, "resolve_type" @resolve_hash[type_name_s] = fields else raise ArgumentError, "Unexpected resolve hash value for #{type_name.inspect}: #{fields.inspect} (#{fields.class})" end end # Check the normalized hash, not the user input: if @resolve_hash.key?("resolve_type") define_singleton_method :resolve_type do |type, obj, ctx| @resolve_hash.fetch("resolve_type").call(type, obj, ctx) end end end def call(type, field, obj, args, ctx) resolver = @resolve_hash[type.graphql_name][field.graphql_name] resolver.call(obj, args, ctx) end def coerce_input(type, value, ctx) @resolve_hash[type.graphql_name]["coerce_input"].call(value, ctx) end def coerce_result(type, value, ctx) @resolve_hash[type.graphql_name]["coerce_result"].call(value, ctx) end end end end end graphql-ruby-2.2.17/lib/graphql/schema/build_from_definition/resolve_map/000077500000000000000000000000001476434635200265505ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb000066400000000000000000000032361476434635200322640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module BuildFromDefinition class ResolveMap class DefaultResolve def initialize(field_map, field_name) @field_map = field_map @field_name = field_name end # Make some runtime checks about # how `obj` implements the `field_name`. # # Create a new resolve function according to that implementation, then: # - update `field_map` with this implementation # - call the implementation now (to satisfy this field execution) # # If `obj` doesn't implement `field_name`, raise an error. def call(obj, args, ctx) method_name = @field_name if !obj.respond_to?(method_name) raise KeyError, "Can't resolve field #{method_name} on #{obj.inspect}" else method_arity = obj.method(method_name).arity resolver = case method_arity when 0, -1 # -1 Handles method_missing, eg openstruct ->(o, a, c) { o.public_send(method_name) } when 1 ->(o, a, c) { o.public_send(method_name, a) } when 2 ->(o, a, c) { o.public_send(method_name, a, c) } else raise "Unexpected resolve arity: #{method_arity}. Must be 0, 1, 2" end # Call the resolver directly next time @field_map[method_name] = resolver # Call through this time resolver.call(obj, args, ctx) end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/built_in_types.rb000066400000000000000000000004471476434635200232650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema BUILT_IN_TYPES = { "Int" => GraphQL::Types::Int, "String" => GraphQL::Types::String, "Float" => GraphQL::Types::Float, "Boolean" => GraphQL::Types::Boolean, "ID" => GraphQL::Types::ID, } end end graphql-ruby-2.2.17/lib/graphql/schema/directive.rb000066400000000000000000000200641476434635200222070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Subclasses of this can influence how {GraphQL::Execution::Interpreter} runs queries. # # - {.include?}: if it returns `false`, the field or fragment will be skipped altogether, as if it were absent # - {.resolve}: Wraps field resolution (so it should call `yield` to continue) class Directive < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasArguments::HasDirectiveArguments class << self # Directives aren't types, they don't have kinds. undef_method :kind def path "@#{super}" end # Return a name based on the class name, # but downcase the first letter. def default_graphql_name @default_graphql_name ||= begin camelized_name = super.dup camelized_name[0] = camelized_name[0].downcase -camelized_name end end def locations(*new_locations) if new_locations.any? new_locations.each do |new_loc| if !LOCATIONS.include?(new_loc.to_sym) raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` " end end @locations = new_locations else @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : []) end end def default_directive(new_default_directive = nil) if new_default_directive != nil @default_directive = new_default_directive elsif @default_directive.nil? @default_directive = (superclass.respond_to?(:default_directive) ? superclass.default_directive : false) else !!@default_directive end end def default_directive? default_directive end # If false, this part of the query won't be evaluated def include?(_object, arguments, context) static_include?(arguments, context) end # Determines whether {Execution::Lookahead} considers the field to be selected def static_include?(_arguments, _context) true end # Continuing is passed as a block; `yield` to continue def resolve(object, arguments, context) yield end # Continuing is passed as a block, yield to continue. def resolve_each(object, arguments, context) yield end def on_field? locations.include?(FIELD) end def on_fragment? locations.include?(FRAGMENT_SPREAD) && locations.include?(INLINE_FRAGMENT) end def on_operation? locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION) end def repeatable? !!@repeatable end def repeatable(new_value) @repeatable = new_value end private def inherited(subclass) super subclass.class_exec do @default_graphql_name ||= nil end end end # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module] attr_reader :owner # @return [GraphQL::Interpreter::Arguments] attr_reader :arguments def initialize(owner, **arguments) @owner = owner assert_valid_owner # It's be nice if we had the real context here, but we don't. What we _would_ get is: # - error handling # - lazy resolution # Probably, those won't be needed here, since these are configuration arguments, # not runtime arguments. @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext.instance) end def graphql_name self.class.graphql_name end LOCATIONS = [ QUERY = :QUERY, MUTATION = :MUTATION, SUBSCRIPTION = :SUBSCRIPTION, FIELD = :FIELD, FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION, FRAGMENT_SPREAD = :FRAGMENT_SPREAD, INLINE_FRAGMENT = :INLINE_FRAGMENT, SCHEMA = :SCHEMA, SCALAR = :SCALAR, OBJECT = :OBJECT, FIELD_DEFINITION = :FIELD_DEFINITION, ARGUMENT_DEFINITION = :ARGUMENT_DEFINITION, INTERFACE = :INTERFACE, UNION = :UNION, ENUM = :ENUM, ENUM_VALUE = :ENUM_VALUE, INPUT_OBJECT = :INPUT_OBJECT, INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION, VARIABLE_DEFINITION = :VARIABLE_DEFINITION, ] DEFAULT_DEPRECATION_REASON = 'No longer supported' LOCATION_DESCRIPTIONS = { QUERY: 'Location adjacent to a query operation.', MUTATION: 'Location adjacent to a mutation operation.', SUBSCRIPTION: 'Location adjacent to a subscription operation.', FIELD: 'Location adjacent to a field.', FRAGMENT_DEFINITION: 'Location adjacent to a fragment definition.', FRAGMENT_SPREAD: 'Location adjacent to a fragment spread.', INLINE_FRAGMENT: 'Location adjacent to an inline fragment.', SCHEMA: 'Location adjacent to a schema definition.', SCALAR: 'Location adjacent to a scalar definition.', OBJECT: 'Location adjacent to an object type definition.', FIELD_DEFINITION: 'Location adjacent to a field definition.', ARGUMENT_DEFINITION: 'Location adjacent to an argument definition.', INTERFACE: 'Location adjacent to an interface definition.', UNION: 'Location adjacent to a union definition.', ENUM: 'Location adjacent to an enum definition.', ENUM_VALUE: 'Location adjacent to an enum value definition.', INPUT_OBJECT: 'Location adjacent to an input object type definition.', INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.', VARIABLE_DEFINITION: 'Location adjacent to a variable definition.', } private def assert_valid_owner case @owner when Class if @owner < GraphQL::Schema::Object assert_has_location(OBJECT) elsif @owner < GraphQL::Schema::Union assert_has_location(UNION) elsif @owner < GraphQL::Schema::Enum assert_has_location(ENUM) elsif @owner < GraphQL::Schema::InputObject assert_has_location(INPUT_OBJECT) elsif @owner < GraphQL::Schema::Scalar assert_has_location(SCALAR) elsif @owner < GraphQL::Schema assert_has_location(SCHEMA) else raise "Unexpected directive owner class: #{@owner}" end when Module assert_has_location(INTERFACE) when GraphQL::Schema::Argument if @owner.owner.is_a?(GraphQL::Schema::Field) assert_has_location(ARGUMENT_DEFINITION) else assert_has_location(INPUT_FIELD_DEFINITION) end when GraphQL::Schema::Field assert_has_location(FIELD_DEFINITION) when GraphQL::Schema::EnumValue assert_has_location(ENUM_VALUE) else raise "Unexpected directive owner: #{@owner.inspect}" end end def assert_has_location(location) if !self.class.locations.include?(location) raise ArgumentError, <<-MD Directive `@#{self.class.graphql_name}` can't be attached to #{@owner.graphql_name} because #{location} isn't included in its locations (#{self.class.locations.join(", ")}). Use `locations(#{location})` to update this directive's definition, or remove it from #{@owner.graphql_name}. MD end end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/000077500000000000000000000000001476434635200216605ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/directive/deprecated.rb000066400000000000000000000015561476434635200243140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Deprecated < GraphQL::Schema::Directive description "Marks an element of a GraphQL schema as no longer supported." locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION) reason_description = "Explains why this element was deprecated, usually also including a "\ "suggestion for how to access supported similar data. Formatted "\ "in [Markdown](https://daringfireball.net/projects/markdown/)." argument :reason, String, reason_description, default_value: Directive::DEFAULT_DEPRECATION_REASON, required: false default_directive true end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/feature.rb000066400000000000000000000054061476434635200236450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive might be used along with a server-side feature flag system like Flipper. # # With that system, you could use this directive to exclude parts of a query # if the current viewer doesn't have certain flags enabled. # (So, this flag would be for internal clients, like your iOS app, not third-party API clients.) # # To use it, you have to implement `.enabled?`, for example: # # @example Implementing the Feature directive # # app/graphql/directives/feature.rb # class Directives::Feature < GraphQL::Schema::Directive::Feature # def self.enabled?(flag_name, _obj, context) # # Translate some GraphQL data for Ruby: # flag_key = flag_name.underscore # current_user = context[:viewer] # # Check the feature flag however your app does it: # MyFeatureFlags.enabled?(current_user, flag_key) # end # end # # @example Flagging a part of the query # viewer { # # This field only runs if `.enabled?("recommendationEngine", obj, context)` # # returns true. Otherwise, it's treated as if it didn't exist. # recommendations @feature(flag: "recommendationEngine") { # name # rating # } # } class Feature < Schema::Directive description "Directs the executor to run this only if a certain server-side feature is enabled." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :flag, String, description: "The name of the feature to check before continuing" # Implement the Directive API def self.include?(object, arguments, context) flag_name = arguments[:flag] self.enabled?(flag_name, object, context) end # Override this method in your app's subclass of this directive. # # @param flag_name [String] The client-provided string of a feature to check # @param object [GraphQL::Schema::Objct] The currently-evaluated GraphQL object instance # @param context [GraphQL::Query::Context] # @return [Boolean] If truthy, execution will continue def self.enabled?(flag_name, object, context) raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})" end end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/flagged.rb000066400000000000000000000045221476434635200236010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # This is _similar_ to {Directive::Feature}, except it's prescribed by the server, not the client. # # In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present. class Flagged < GraphQL::Schema::Directive def initialize(target, **options) if target.is_a?(Module) && !target.ancestors.include?(VisibleByFlag) # This is type class of some kind, `include` will put this module # in between the type class itself and its super class, so `super` will work fine target.include(VisibleByFlag) elsif !target.is_a?(VisibleByFlag) # This is an instance of a base class. `include` won't put this in front of the # base class implementation, so we need to `.prepend`. # `#visible?` could probably be moved to a module and then this could use `include` instead. target.class.prepend(VisibleByFlag) end super end description "Hides this part of the schema unless the named flag is present in context[:flags]" locations( GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::OBJECT, GraphQL::Schema::Directive::SCALAR, GraphQL::Schema::Directive::ENUM, GraphQL::Schema::Directive::UNION, GraphQL::Schema::Directive::INTERFACE, GraphQL::Schema::Directive::INPUT_OBJECT, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION, ) argument :by, [String], "Flags to check for this schema member" module VisibleByFlag def self.included(schema_class) schema_class.extend(self) end def visible?(context) if dir = self.directives.find { |d| d.is_a?(Flagged) } relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f # rubocop:disable Development/ContextIsPassedCop -- definition-related relevant_flags && relevant_flags.any? && super else super end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/include.rb000066400000000000000000000012351476434635200236310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Include < GraphQL::Schema::Directive description "Directs the executor to include this field or fragment only when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, description: "Included when true." default_directive true def self.static_include?(args, ctx) !!args[:if] end end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/one_of.rb000066400000000000000000000010551476434635200234530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class OneOf < GraphQL::Schema::Directive description "Requires that exactly one field must be supplied and that field must not be `null`." locations(GraphQL::Schema::Directive::INPUT_OBJECT) default_directive true def initialize(...) super owner.extend(IsOneOf) end module IsOneOf def one_of? true end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/skip.rb000066400000000000000000000012071476434635200231530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Skip < Schema::Directive description "Directs the executor to skip this field or fragment when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, description: "Skipped when true." default_directive true def self.static_include?(args, ctx) !args[:if] end end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/specified_by.rb000066400000000000000000000007031476434635200246320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class SpecifiedBy < GraphQL::Schema::Directive description "Exposes a URL that specifies the behavior of this scalar." locations(GraphQL::Schema::Directive::SCALAR) default_directive true argument :url, String, description: "The URL that specifies the behavior of this scalar." end end end end graphql-ruby-2.2.17/lib/graphql/schema/directive/transform.rb000066400000000000000000000036271476434635200242300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive takes the return value of the tagged part of the query, # and if the named transform is whitelisted and applies to the return value, # it's applied by calling a method with that name. # # @example Installing the directive # class MySchema < GraphQL::Schema # directive(GraphQL::Schema::Directive::Transform) # end # # @example Transforming strings # viewer { # username @transform(by: "upcase") # } class Transform < Schema::Directive description "Directs the executor to run named transform on the return value." locations( GraphQL::Schema::Directive::FIELD, ) argument :by, String, description: "The name of the transform to run if applicable" TRANSFORMS = [ "upcase", "downcase", # ?? ] # Implement the Directive API def self.resolve(object, arguments, context) path = context.namespace(:interpreter)[:current_path] return_value = yield transform_name = arguments[:by] if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name) return_value = return_value.public_send(transform_name) response = context.namespace(:interpreter_runtime)[:runtime].final_result *keys, last = path keys.each do |key| if response && (response = response[key]) next else break end end if response response[last] = return_value end nil end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/enum.rb000066400000000000000000000151061476434635200211760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Extend this class to define GraphQL enums in your schema. # # By default, GraphQL enum values are translated into Ruby strings. # You can provide a custom value with the `value:` keyword. # # @example # # equivalent to # # enum PizzaTopping { # # MUSHROOMS # # ONIONS # # PEPPERS # # } # class PizzaTopping < GraphQL::Schema::Enum # value :MUSHROOMS # value :ONIONS # value :PEPPERS # end class Enum < GraphQL::Schema::Member extend GraphQL::Schema::Member::ValidatesInput class UnresolvedValueError < GraphQL::Error def initialize(value:, enum:, context:) fix_message = ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead." message = if (cp = context[:current_path]) && (cf = context[:current_field]) "`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}" else "`#{value.inspect}` was returned for `#{enum.graphql_name}`#{fix_message}" end super(message) end end class MissingValuesError < GraphQL::Error def initialize(enum_type) @enum_type = enum_type super("Enum types require at least one value, but #{enum_type.graphql_name} didn't provide any for this query. Make sure at least one value is defined and visible for this query.") end end class << self # Define a value for this enum # @param graphql_name [String, Symbol] the GraphQL value for this, usually `SCREAMING_CASE` # @param description [String], the GraphQL description for this value, present in documentation # @param value [Object], the translated Ruby value for this object (defaults to `graphql_name`) # @param deprecation_reason [String] if this object is deprecated, include a message here # @return [void] # @see {Schema::EnumValue} which handles these inputs by default def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) key = value.graphql_name prev_value = own_values[key] case prev_value when nil own_values[key] = value when GraphQL::Schema::EnumValue own_values[key] = [prev_value, value] when Array prev_value << value else raise "Invariant: Unexpected enum value for #{key.inspect}: #{prev_value.inspect}" end value end # @return [Array] Possible values of this enum def enum_values(context = GraphQL::Query::NullContext.instance) inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil visible_values = [] warden = Warden.from_context(context) own_values.each do |key, values_entry| if (v = Warden.visible_entry?(:visible_enum_value?, values_entry, context, warden)) visible_values << v end end if inherited_values # Local values take precedence over inherited ones inherited_values.each do |i_val| if !visible_values.any? { |v| v.graphql_name == i_val.graphql_name } visible_values << i_val end end end visible_values end # @return [Array] An unfiltered list of all definitions def all_enum_value_definitions all_defns = if superclass.respond_to?(:all_enum_value_definitions) superclass.all_enum_value_definitions else [] end @own_values && @own_values.each do |_key, value| if value.is_a?(Array) all_defns.concat(value) else all_defns << value end end all_defns end # @return [Hash GraphQL::Schema::EnumValue>] Possible values of this enum, keyed by name. def values(context = GraphQL::Query::NullContext.instance) enum_values(context).each_with_object({}) { |val, obj| obj[val.graphql_name] = val } end # @return [Class] for handling `value(...)` inputs and building `GraphQL::Enum::EnumValue`s out of them def enum_value_class(new_enum_value_class = nil) if new_enum_value_class @enum_value_class = new_enum_value_class elsif defined?(@enum_value_class) && @enum_value_class @enum_value_class else superclass <= GraphQL::Schema::Enum ? superclass.enum_value_class : nil end end def kind GraphQL::TypeKinds::ENUM end def validate_non_null_input(value_name, ctx, max_errors: nil) allowed_values = ctx.warden.enum_values(self) matching_value = allowed_values.find { |v| v.graphql_name == value_name } if matching_value.nil? GraphQL::Query::InputValidationResult.from_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}") else nil end end def coerce_result(value, ctx) warden = ctx.warden all_values = warden ? warden.enum_values(self) : values.each_value enum_value = all_values.find { |val| val.value == value } if enum_value enum_value.graphql_name else raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx) end end def coerce_input(value_name, ctx) all_values = ctx.warden ? ctx.warden.enum_values(self) : values.each_value if v = all_values.find { |val| val.graphql_name == value_name } v.value elsif v = all_values.find { |val| val.value == value_name } # this is for matching default values, which are "inputs", but they're # the Ruby value, not the GraphQL string. v.value else nil end end def inherited(child_class) child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError)) super end private def own_values @own_values ||= {} end end enum_value_class(GraphQL::Schema::EnumValue) end end end graphql-ruby-2.2.17/lib/graphql/schema/enum_value.rb000066400000000000000000000042741476434635200223760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # A possible value for an {Enum}. # # You can extend this class to customize enum values in your schema. # # @example custom enum value class # # define a custom class: # class CustomEnumValue < GraphQL::Schema::EnumValue # def initialize(*args) # # arguments to `value(...)` in Enum classes are passed here # super # end # end # # class BaseEnum < GraphQL::Schema::Enum # # use it for these enums: # enum_value_class CustomEnumValue # end class EnumValue < GraphQL::Schema::Member include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasDeprecationReason attr_reader :graphql_name # @return [Class] The enum type that owns this value attr_reader :owner def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block) @graphql_name = graphql_name.to_s GraphQL::NameValidator.validate!(@graphql_name) @description = desc || description @value = value == NOT_CONFIGURED ? @graphql_name : value if deprecation_reason self.deprecation_reason = deprecation_reason end @owner = owner @ast_node = ast_node if directives directives.each do |dir_class, dir_options| directive(dir_class, **dir_options) end end if block_given? instance_exec(self, &block) end end def description(new_desc = nil) if new_desc @description = new_desc end @description end def value(new_val = nil) unless new_val.nil? @value = new_val end @value end def inspect "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>" end def visible?(_ctx); true; end def authorized?(_ctx); true; end end end end graphql-ruby-2.2.17/lib/graphql/schema/field.rb000066400000000000000000001103601476434635200213130ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/field/connection_extension" require "graphql/schema/field/scope_extension" module GraphQL class Schema class Field include GraphQL::Schema::Member::HasArguments include GraphQL::Schema::Member::HasArguments::FieldConfigured include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasValidators extend GraphQL::Schema::FindInheritedValue include GraphQL::EmptyObjects include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasDeprecationReason class FieldImplementationFailed < GraphQL::Error; end # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name attr_writer :description # @return [Symbol] Method or hash key on the underlying object to look up attr_reader :method_sym # @return [String] Method or hash key on the underlying object to look up attr_reader :method_str attr_reader :hash_key attr_reader :dig_keys # @return [Symbol] The method on the type to look up def resolver_method if @resolver_class @resolver_class.resolver_method else @resolver_method end end # @return [Class] The thing this field was defined on (type, mutation, resolver) attr_accessor :owner # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type) def owner_type @owner_type ||= if owner.nil? raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!" elsif owner < GraphQL::Schema::Mutation owner.payload_type else owner end end # @return [Symbol] the original name of the field, passed in by the user attr_reader :original_name # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one def resolver @resolver_class end # @return [Boolean] Is this field a predefined introspection field? def introspection? @introspection end def inspect "#<#{self.class} #{path}#{all_argument_definitions.any? ? "(...)" : ""}: #{type.to_type_signature}>" end alias :mutation :resolver # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value) attr_reader :trace # @return [String, nil] def subscription_scope @subscription_scope || (@resolver_class.respond_to?(:subscription_scope) ? @resolver_class.subscription_scope : nil) end attr_writer :subscription_scope # Create a field instance from a list of arguments, keyword arguments, and a block. # # This method implements prioritization between the `resolver` or `mutation` defaults # and the local overrides via other keywords. # # It also normalizes positional arguments into keywords for {Schema::Field#initialize}. # @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration # @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration # @return [GraphQL::Schema:Field] an instance of `self` # @see {.initialize} for other options def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block) if (resolver_class = resolver || mutation || subscription) # Add a reference to that parent class kwargs[:resolver_class] = resolver_class end if name kwargs[:name] = name end if !type.nil? if desc if kwargs[:description] raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{kwargs[:description].inspect})" end kwargs[:description] = desc kwargs[:type] = type elsif (resolver || mutation) && type.is_a?(String) # The return type should be copied from the resolver, and the second positional argument is the description kwargs[:description] = type else kwargs[:type] = type end if type.is_a?(Class) && type < GraphQL::Schema::Mutation raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead" end end new(**kwargs, &block) end # Can be set with `connection: true|false` or inferred from a type name ending in `*Connection` # @return [Boolean] if true, this field will be wrapped with Relay connection behavior def connection? if @connection.nil? # Provide default based on type name return_type_name = if @return_type_expr Member::BuildType.to_type_name(@return_type_expr) elsif @resolver_class && @resolver_class.type Member::BuildType.to_type_name(@resolver_class.type) else # As a last ditch, try to force loading the return type: type.unwrap.name end @connection = return_type_name.end_with?("Connection") && return_type_name != "Connection" else @connection end end # @return [Boolean] if true, the return type's `.scope_items` method will be applied to this field's return value def scoped? if !@scope.nil? # The default was overridden @scope elsif @return_type_expr # Detect a list return type, but don't call `type` since that may eager-load an otherwise lazy-loaded type @return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection? elsif @resolver_class resolver_type = @resolver_class.type_expr resolver_type.is_a?(Array) || (resolver_type.is_a?(String) && resolver_type.include?("[")) || connection? else false end end # This extension is applied to fields when {#connection?} is true. # # You can override it in your base field definition. # @return [Class] A {FieldExtension} subclass for implementing pagination behavior. # @example Configuring a custom extension # class Types::BaseField < GraphQL::Schema::Field # connection_extension(MyCustomExtension) # end def self.connection_extension(new_extension_class = nil) if new_extension_class @connection_extension = new_extension_class else @connection_extension ||= find_inherited_value(:connection_extension, ConnectionExtension) end end # @return Boolean attr_reader :relay_node_field # @return Boolean attr_reader :relay_nodes_field # @return [Boolean] Should we warn if this field's name conflicts with a built-in method? def method_conflict_warning? @method_conflict_warning end # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API) # @param type [Class, GraphQL::BaseType, Array] The return type of this field # @param owner [Class] The type that this field belongs to # @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` # @param description [String] Field description # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`) # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`) # @param dig [Array] The nested hash keys to lookup on the underlying hash to resolve this field using dig # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`) # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added. # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. # @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results. # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__` # @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver. # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also) # @param camelize [Boolean] If true, the field name will be camelized when building the schema # @param complexity [Numeric] When provided, set the complexity for this field # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads # @param extensions [Array Object>>] Named extensions to apply to this field (see also {#extension}) # @param directives [Hash{Class => Hash}] Directives to apply to this field # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method # @param validates [Array] Configurations for validating this field # @param fallback_value [Object] A fallback value if the method is not defined def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block) if name.nil? raise ArgumentError, "missing first `name` argument or keyword `name:`" end if !(resolver_class) if type.nil? raise ArgumentError, "missing second `type` argument or keyword `type:`" end end @original_name = name name_s = -name.to_s @underscored_name = -Member::BuildType.underscore(name_s) @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s) NameValidator.validate!(@name) @description = description @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary self.deprecation_reason = deprecation_reason if method && hash_key && dig raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)" end if resolver_method if method raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)" end if hash_key || dig raise ArgumentError, "Provide `hash_key:`, `dig:`, _or_ `resolver_method:`, not multiple. (called with: `hash_key: #{hash_key.inspect}, dig: #{dig.inspect}, resolver_method: #{resolver_method.inspect}`)" end end method_name = method || hash_key || name_s @dig_keys = dig if hash_key @hash_key = hash_key @hash_key_str = hash_key.to_s else @hash_key = NOT_CONFIGURED @hash_key_str = NOT_CONFIGURED end @method_str = -method_name.to_s @method_sym = method_name.to_sym @resolver_method = (resolver_method || name_s).to_sym @complexity = complexity @dynamic_introspection = dynamic_introspection @return_type_expr = type @return_type_null = if !null.nil? null elsif resolver_class nil else true end @connection = connection @max_page_size = max_page_size @default_page_size = default_page_size @introspection = introspection @extras = extras @broadcastable = broadcastable @resolver_class = resolver_class @scope = scope @trace = trace @relay_node_field = relay_node_field @relay_nodes_field = relay_nodes_field @ast_node = ast_node @method_conflict_warning = method_conflict_warning @fallback_value = fallback_value arguments.each do |name, arg| case arg when Hash argument(name: name, **arg) when GraphQL::Schema::Argument add_argument(arg) when Array arg.each { |a| add_argument(a) } else raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}" end end @owner = owner @subscription_scope = subscription_scope @extensions = EMPTY_ARRAY @call_after_define = false # This should run before connection extension, # but should it run after the definition block? if scoped? self.extension(ScopeExtension) end # The problem with putting this after the definition_block # is that it would override arguments if connection? && connection_extension self.extension(connection_extension) end # Do this last so we have as much context as possible when initializing them: if extensions.any? self.extensions(extensions) end if resolver_class && resolver_class.extensions.any? self.extensions(resolver_class.extensions) end if directives.any? directives.each do |(dir_class, options)| self.directive(dir_class, **options) end end if !validates.empty? self.validates(validates) end if block_given? if definition_block.arity == 1 yield self else instance_eval(&definition_block) end end self.extensions.each(&:after_define_apply) @call_after_define = true end attr_accessor :dynamic_introspection # If true, subscription updates with this field can be shared between viewers # @return [Boolean, nil] # @see GraphQL::Subscriptions::BroadcastAnalyzer def broadcastable? if !NOT_CONFIGURED.equal?(@broadcastable) @broadcastable elsif @resolver_class @resolver_class.broadcastable? else nil end end # @param text [String] # @return [String] def description(text = nil) if text @description = text elsif !NOT_CONFIGURED.equal?(@description) @description elsif @resolver_class @resolver_class.description else nil end end # Read extension instances from this field, # or add new classes/options to be initialized on this field. # Extensions are executed in the order they are added. # # @example adding an extension # extensions([MyExtensionClass]) # # @example adding multiple extensions # extensions([MyExtensionClass, AnotherExtensionClass]) # # @example adding an extension with options # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }]) # # @param extensions [Array Hash>>] Add extensions to this field. For hash elements, only the first key/value is used. # @return [Array] extensions to apply to this field def extensions(new_extensions = nil) if new_extensions new_extensions.each do |extension_config| if extension_config.is_a?(Hash) extension_class, options = *extension_config.to_a[0] self.extension(extension_class, options) else self.extension(extension_config) end end end @extensions end # Add `extension` to this field, initialized with `options` if provided. # # @example adding an extension # extension(MyExtensionClass) # # @example adding an extension with options # extension(MyExtensionClass, filter: true) # # @param extension_class [Class] subclass of {Schema::FieldExtension} # @param options [Hash] if provided, given as `options:` when initializing `extension`. # @return [void] def extension(extension_class, options = nil) extension_inst = extension_class.new(field: self, options: options) if @extensions.frozen? @extensions = @extensions.dup end if @call_after_define extension_inst.after_define_apply end @extensions << extension_inst nil end # Read extras (as symbols) from this field, # or add new extras to be opted into by this field's resolver. # # @param new_extras [Array] Add extras to this field # @return [Array] def extras(new_extras = nil) if new_extras.nil? # Read the value field_extras = @extras if @resolver_class && @resolver_class.extras.any? field_extras + @resolver_class.extras else field_extras end else if @extras.frozen? @extras = @extras.dup end # Append to the set of extras on this field @extras.concat(new_extras) end end def calculate_complexity(query:, nodes:, child_complexity:) if respond_to?(:complexity_for) lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner) complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead) elsif connection? arguments = query.arguments_for(nodes.first, self) max_possible_page_size = nil if arguments.respond_to?(:[]) # It might have been an error if arguments[:first] max_possible_page_size = arguments[:first] end if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size) max_possible_page_size = arguments[:last] end elsif arguments.is_a?(GraphQL::UnauthorizedError) raise arguments end if max_possible_page_size.nil? max_possible_page_size = default_page_size || query.schema.default_page_size || max_page_size || query.schema.default_max_page_size end if max_possible_page_size.nil? raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `default_page_size`, `max_page_size` or `default_max_page_size`" else metadata_complexity = 0 lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner) lookahead.selections.each do |next_lookahead| # this includes `pageInfo`, `nodes` and `edges` and any custom fields # TODO this doesn't support procs yet -- unlikely to need it. metadata_complexity += next_lookahead.field.complexity if next_lookahead.name != :nodes && next_lookahead.name != :edges # subfields, eg, for pageInfo -- assumes no subselections metadata_complexity += next_lookahead.selections.size end end # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be? items_complexity = child_complexity - metadata_complexity subfields_complexity = (max_possible_page_size * items_complexity) + metadata_complexity # Apply this field's own complexity apply_own_complexity_to(subfields_complexity, query, nodes) end else apply_own_complexity_to(child_complexity, query, nodes) end end def complexity(new_complexity = nil) case new_complexity when Proc if new_complexity.parameters.size != 3 fail( "A complexity proc should always accept 3 parameters: ctx, args, child_complexity. "\ "E.g.: complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }" ) else @complexity = new_complexity end when Numeric @complexity = new_complexity when nil if @resolver_class @complexity || @resolver_class.complexity || 1 else @complexity || 1 end else raise("Invalid complexity: #{new_complexity.inspect} on #{@name}") end end # @return [Boolean] True if this field's {#max_page_size} should override the schema default. def has_max_page_size? !NOT_CONFIGURED.equal?(@max_page_size) || (@resolver_class && @resolver_class.has_max_page_size?) end # @return [Integer, nil] Applied to connections if {#has_max_page_size?} def max_page_size if !NOT_CONFIGURED.equal?(@max_page_size) @max_page_size elsif @resolver_class && @resolver_class.has_max_page_size? @resolver_class.max_page_size else nil end end # @return [Boolean] True if this field's {#default_page_size} should override the schema default. def has_default_page_size? !NOT_CONFIGURED.equal?(@default_page_size) || (@resolver_class && @resolver_class.has_default_page_size?) end # @return [Integer, nil] Applied to connections if {#has_default_page_size?} def default_page_size if !NOT_CONFIGURED.equal?(@default_page_size) @default_page_size elsif @resolver_class && @resolver_class.has_default_page_size? @resolver_class.default_page_size else nil end end class MissingReturnTypeError < GraphQL::Error; end attr_writer :type def type if @resolver_class return_type = @return_type_expr || @resolver_class.type_expr if return_type.nil? raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)" end nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null Member::BuildType.parse_type(return_type, null: nullable) else @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null) end rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err # Let this propagate up raise err rescue StandardError => err raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace end def visible?(context) if @resolver_class @resolver_class.visible?(context) else true end end def authorized?(object, args, context) if @resolver_class # The resolver _instance_ will check itself during `resolve()` @resolver_class.authorized?(object, context) else if args.size > 0 if (arg_values = context[:current_arguments]) # ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not. using_arg_values = true arg_values = arg_values.argument_values else arg_values = args using_arg_values = false end args = context.warden.arguments(self) args.each do |arg| arg_key = arg.keyword if arg_values.key?(arg_key) arg_value = arg_values[arg_key] if using_arg_values if arg_value.default_used? # pass -- no auth required for default used next else application_arg_value = arg_value.value if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments) application_arg_value.keyword_arguments end end else application_arg_value = arg_value end if !arg.authorized?(object, application_arg_value, context) return false end end end end true end end # This method is called by the interpreter for each field. # You can extend it in your base field classes. # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args) # @param ctx [GraphQL::Query::Context] def resolve(object, args, query_ctx) # Unwrap the GraphQL object to get the application object. application_object = object.object method_receiver = nil method_to_call = nil method_args = nil @own_validators && Schema::Validator.validate!(validators, application_object, query_ctx, args) query_ctx.query.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized| if is_authorized with_extensions(object, args, query_ctx) do |obj, ruby_kwargs| method_args = ruby_kwargs if @resolver_class if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end obj = @resolver_class.new(object: obj, context: query_ctx, field: self) end inner_object = obj.object if !NOT_CONFIGURED.equal?(@hash_key) hash_value = if inner_object.is_a?(Hash) inner_object.key?(@hash_key) ? inner_object[@hash_key] : inner_object[@hash_key_str] elsif inner_object.respond_to?(:[]) inner_object[@hash_key] else nil end if hash_value == false hash_value else hash_value || (@fallback_value != NOT_CONFIGURED ? @fallback_value : nil) end elsif obj.respond_to?(resolver_method) method_to_call = resolver_method method_receiver = obj # Call the method with kwargs, if there are any if ruby_kwargs.any? obj.public_send(resolver_method, **ruby_kwargs) else obj.public_send(resolver_method) end elsif inner_object.is_a?(Hash) if @dig_keys inner_object.dig(*@dig_keys) elsif inner_object.key?(@method_sym) inner_object[@method_sym] elsif inner_object.key?(@method_str) || !inner_object.default_proc.nil? inner_object[@method_str] elsif @fallback_value != NOT_CONFIGURED @fallback_value else nil end elsif inner_object.respond_to?(@method_sym) method_to_call = @method_sym method_receiver = obj.object if ruby_kwargs.any? inner_object.public_send(@method_sym, **ruby_kwargs) else inner_object.public_send(@method_sym) end elsif @fallback_value != NOT_CONFIGURED @fallback_value else raise <<-ERR Failed to implement #{@owner.graphql_name}.#{@name}, tried: - `#{obj.class}##{resolver_method}`, which did not exist - `#{inner_object.class}##{@method_sym}`, which did not exist - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{inner_object}`, but it wasn't a Hash To implement this field, define one of the methods above (and check for typos), or supply a `fallback_value`. ERR end end else raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self) end end rescue GraphQL::UnauthorizedFieldError => err err.field ||= self begin query_ctx.schema.unauthorized_field(err) rescue GraphQL::ExecutionError => err err end rescue GraphQL::UnauthorizedError => err begin query_ctx.schema.unauthorized_object(err) rescue GraphQL::ExecutionError => err err end rescue ArgumentError if method_receiver && method_to_call assert_satisfactory_implementation(method_receiver, method_to_call, method_args) end # if the line above doesn't raise, re-raise raise rescue GraphQL::ExecutionError => err err end # @param ctx [GraphQL::Query::Context] def fetch_extra(extra_name, ctx) if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name) self.public_send(extra_name) elsif ctx.respond_to?(extra_name) ctx.public_send(extra_name) else raise GraphQL::RequiredImplementationMissingError, "Unknown field extra for #{self.path}: #{extra_name.inspect}" end end private def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs) method_defn = receiver.method(method_name) unsatisfied_ruby_kwargs = ruby_kwargs.dup unsatisfied_method_params = [] encountered_keyrest = false method_defn.parameters.each do |(param_type, param_name)| case param_type when :key unsatisfied_ruby_kwargs.delete(param_name) when :keyreq if unsatisfied_ruby_kwargs.key?(param_name) unsatisfied_ruby_kwargs.delete(param_name) else unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL." end when :keyrest encountered_keyrest = true when :req unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it." when :opt, :rest # This is fine, although it will never be present end end if encountered_keyrest unsatisfied_ruby_kwargs.clear end if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any? raise FieldImplementationFailed.new, <<-ERR Failed to call `#{method_name.inspect}` on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments: #{ unsatisfied_ruby_kwargs .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." } .concat(unsatisfied_method_params) .join("\n") } ERR end end class ExtendedState def initialize(args, object) @arguments = args @object = object @memos = nil @added_extras = nil end attr_accessor :arguments, :object, :memos, :added_extras end # Wrap execution with hooks. # Written iteratively to avoid big stack traces. # @return [Object] Whatever the def with_extensions(obj, args, ctx) if @extensions.empty? yield(obj, args) else # This is a hack to get the _last_ value for extended obj and args, # in case one of the extensions doesn't `yield`. # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays) extended = ExtendedState.new(args, obj) value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args| if (added_extras = extended.added_extras) args = args.dup added_extras.each { |e| args.delete(e) } end yield(obj, args) end extended_obj = extended.object extended_args = extended.arguments # rubocop:disable Development/ContextIsPassedCop memos = extended.memos || EMPTY_HASH ctx.query.after_lazy(value) do |resolved_value| idx = 0 @extensions.each do |ext| memo = memos[idx] # TODO after_lazy? resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo) idx += 1 end resolved_value end end end def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0) extension = @extensions[idx] if extension extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo| if memo memos = extended.memos ||= {} memos[idx] = memo end if (extras = extension.added_extras) ae = extended.added_extras ||= [] ae.concat(extras) end extended.object = extended_obj extended.arguments = extended_args run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) } end else yield(obj, args) end end def apply_own_complexity_to(child_complexity, query, nodes) case (own_complexity = complexity) when Numeric own_complexity + child_complexity when Proc arguments = query.arguments_for(nodes.first, self) if arguments.is_a?(GraphQL::ExecutionError) return child_complexity elsif arguments.respond_to?(:keyword_arguments) arguments = arguments.keyword_arguments end own_complexity.call(query.context, arguments, child_complexity) else raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}" end end end end end graphql-ruby-2.2.17/lib/graphql/schema/field/000077500000000000000000000000001476434635200207655ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/field/connection_extension.rb000066400000000000000000000057661476434635200255630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Field class ConnectionExtension < GraphQL::Schema::FieldExtension def apply field.argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false field.argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false field.argument :first, "Int", "Returns the first _n_ elements from the list.", required: false field.argument :last, "Int", "Returns the last _n_ elements from the list.", required: false end # Remove pagination args before passing it to a user method def resolve(object:, arguments:, context:) next_args = arguments.dup next_args.delete(:first) next_args.delete(:last) next_args.delete(:before) next_args.delete(:after) yield(object, next_args, arguments) end def after_resolve(value:, object:, arguments:, context:, memo:) original_arguments = memo # rename some inputs to avoid conflicts inside the block maybe_lazy = value value = nil context.query.after_lazy(maybe_lazy) do |resolved_value| value = resolved_value if value.is_a? GraphQL::ExecutionError # This isn't even going to work because context doesn't have ast_node anymore context.add_error(value) nil elsif value.nil? nil elsif value.is_a?(GraphQL::Pagination::Connection) # update the connection with some things that may not have been provided value.context ||= context value.parent ||= object.object value.first_value ||= original_arguments[:first] value.after_value ||= original_arguments[:after] value.last_value ||= original_arguments[:last] value.before_value ||= original_arguments[:before] value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method value.field ||= field if field.has_max_page_size? && !value.has_max_page_size_override? value.max_page_size = field.max_page_size end if field.has_default_page_size? && !value.has_default_page_size_override? value.default_page_size = field.default_page_size end if context.schema.new_connections? && (custom_t = context.schema.connections.edge_class_for_field(@field)) value.edge_class = custom_t end value else context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers context.schema.connections.wrap(field, object.object, value, original_arguments, context) end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/field/scope_extension.rb000066400000000000000000000016361476434635200245250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Field class ScopeExtension < GraphQL::Schema::FieldExtension def after_resolve(object:, arguments:, context:, value:, memo:) if value.nil? value else ret_type = @field.type.unwrap if ret_type.respond_to?(:scope_items) scoped_items = ret_type.scope_items(value, context) if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects if (current_runtime_state = Thread.current[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[context.query]) query_runtime_state.was_authorized_by_scope_items = true end end scoped_items else value end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/field_extension.rb000066400000000000000000000136461476434635200234200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Extend this class to make field-level customizations to resolve behavior. # # When a extension is added to a field with `extension(MyExtension)`, a `MyExtension` instance # is created, and its hooks are applied whenever that field is called. # # The instance is frozen so that instance variables aren't modified during query execution, # which could cause all kinds of issues due to race conditions. class FieldExtension # @return [GraphQL::Schema::Field] attr_reader :field # @return [Object] attr_reader :options # @return [Array, nil] `default_argument`s added, if any were added (otherwise, `nil`) attr_reader :added_default_arguments # Called when the extension is mounted with `extension(name, options)`. # The instance will be frozen to avoid improper use of state during execution. # @param field [GraphQL::Schema::Field] The field where this extension was mounted # @param options [Object] The second argument to `extension`, or `{}` if nothing was passed. def initialize(field:, options:) @field = field @options = options || {} @added_default_arguments = nil apply end class << self # @return [Array(Array, Hash), nil] A list of default argument configs, or `nil` if there aren't any def default_argument_configurations args = superclass.respond_to?(:default_argument_configurations) ? superclass.default_argument_configurations : nil if @own_default_argument_configurations if args args.concat(@own_default_argument_configurations) else args = @own_default_argument_configurations.dup end end args end # @see Argument#initialize # @see HasArguments#argument def default_argument(*argument_args, **argument_kwargs) configs = @own_default_argument_configurations ||= [] configs << [argument_args, argument_kwargs] end # If configured, these `extras` will be added to the field if they aren't already present, # but removed by from `arguments` before the field's `resolve` is called. # (The extras _will_ be present for other extensions, though.) # # @param new_extras [Array] If provided, assign extras used by this extension # @return [Array] any extras assigned to this extension def extras(new_extras = nil) if new_extras @own_extras = new_extras end inherited_extras = self.superclass.respond_to?(:extras) ? superclass.extras : nil if @own_extras if inherited_extras inherited_extras + @own_extras else @own_extras end elsif inherited_extras inherited_extras else GraphQL::EmptyObjects::EMPTY_ARRAY end end end # Called when this extension is attached to a field. # The field definition may be extended during this method. # @return [void] def apply end # Called after the field's definition block has been executed. # (Any arguments from the block are present on `field`) # @return [void] def after_define end # @api private def after_define_apply after_define if (configs = self.class.default_argument_configurations) existing_keywords = field.all_argument_definitions.map(&:keyword) existing_keywords.uniq! @added_default_arguments = [] configs.each do |config| argument_args, argument_kwargs = config arg_name = argument_args[0] if !existing_keywords.include?(arg_name) @added_default_arguments << arg_name field.argument(*argument_args, **argument_kwargs) end end end if (extras = self.class.extras).any? @added_extras = extras - field.extras field.extras(@added_extras) else @added_extras = nil end freeze end # @api private attr_reader :added_extras # Called before resolving {#field}. It should either: # # - `yield` values to continue execution; OR # - return something else to shortcut field execution. # # Whatever this method returns will be used for execution. # # @param object [Object] The object the field is being resolved on # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @yieldparam object [Object] The object to continue resolving the field on # @yieldparam arguments [Hash] The keyword arguments to continue resolving with # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later # @return [Object] The return value for this field. def resolve(object:, arguments:, context:) yield(object, arguments, nil) end # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced, # but before the value was added to the GraphQL response. # # Whatever this hook returns will be used as the return value. # # @param object [Object] The object the field is being resolved on # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @param value [Object] Whatever the field previously returned # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one # @return [Object] The return value for this field. def after_resolve(object:, arguments:, context:, value:, memo:) value end end end end graphql-ruby-2.2.17/lib/graphql/schema/find_inherited_value.rb000066400000000000000000000015261476434635200244020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module FindInheritedValue def self.extended(child_cls) child_cls.singleton_class.include(GraphQL::EmptyObjects) end def self.included(child_cls) child_cls.include(GraphQL::EmptyObjects) end private def find_inherited_value(method_name, default_value = nil) if self.is_a?(Class) superclass.respond_to?(method_name, true) ? superclass.send(method_name) : default_value else ancestors_except_self = ancestors ancestors_except_self.delete(self) ancestors_except_self.each do |ancestor| if ancestor.respond_to?(method_name, true) return ancestor.send(method_name) end end default_value end end end end end graphql-ruby-2.2.17/lib/graphql/schema/finder.rb000066400000000000000000000115131476434635200214770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Find schema members using string paths # # @example Finding object types # MySchema.find("SomeObjectType") # # @example Finding fields # MySchema.find("SomeObjectType.myField") # # @example Finding arguments # MySchema.find("SomeObjectType.myField.anArgument") # # @example Finding directives # MySchema.find("@include") # class Finder class MemberNotFoundError < ArgumentError; end def initialize(schema) @schema = schema end def find(path) path = path.split(".") type_or_directive = path.shift if type_or_directive.start_with?("@") directive = schema.directives[type_or_directive[1..-1]] if directive.nil? raise MemberNotFoundError, "Could not find directive `#{type_or_directive}` in schema." end return directive if path.empty? find_in_directive(directive, path: path) else type = schema.get_type(type_or_directive) # rubocop:disable Development/ContextIsPassedCop -- build-time if type.nil? raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema." end return type if path.empty? find_in_type(type, path: path) end end private attr_reader :schema def find_in_directive(directive, path:) argument_name = path.shift argument = directive.get_argument(argument_name) # rubocop:disable Development/ContextIsPassedCop -- build-time if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}." end argument end def find_in_type(type, path:) case type.kind.name when "OBJECT" find_in_fields_type(type, kind: "object", path: path) when "INTERFACE" find_in_fields_type(type, kind: "interface", path: path) when "INPUT_OBJECT" find_in_input_object(type, path: path) when "UNION" # Error out if path that was provided is too long # i.e UnionType.PossibleType.aField # Use PossibleType.aField instead. if invalid = path.first raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead." end when "ENUM" find_in_enum_type(type, path: path) else raise "Unexpected find_in_type: #{type.inspect} (#{path})" end end def find_in_fields_type(type, kind:, path:) field_name = path.shift field = schema.get_field(type, field_name) if field.nil? raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type.graphql_name}`." end return field if path.empty? find_in_field(field, path: path) end def find_in_field(field, path:) argument_name = path.shift argument = field.get_argument(argument_name) # rubocop:disable Development/ContextIsPassedCop -- build-time if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`." end # Error out if path that was provided is too long # i.e Type.field.argument.somethingBad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on a field." end argument end def find_in_input_object(input_object, path:) field_name = path.shift input_field = input_object.get_argument(field_name) # rubocop:disable Development/ContextIsPassedCop -- build-time if input_field.nil? raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object.graphql_name}`." end # Error out if path that was provided is too long # i.e InputType.inputField.bad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an input field." end input_field end def find_in_enum_type(enum_type, path:) value_name = path.shift enum_value = enum_type.enum_values.find { |v| v.graphql_name == value_name } # rubocop:disable Development/ContextIsPassedCop -- build-time, not runtime if enum_value.nil? raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type.graphql_name}`." end # Error out if path that was provided is too long # i.e Enum.VALUE.wat if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an enum value." end enum_value end end end end graphql-ruby-2.2.17/lib/graphql/schema/has_single_input_argument.rb000066400000000000000000000117751476434635200254770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module HasSingleInputArgument def resolve_with_support(**inputs) if inputs[:input].is_a?(InputObject) input = inputs[:input].to_kwargs else input = inputs[:input] end new_extras = field ? field.extras : [] all_extras = self.class.extras + new_extras # Transfer these from the top-level hash to the # shortcutted `input:` object all_extras.each do |ext| # It's possible that the `extra` was not passed along by this point, # don't re-add it if it wasn't given here. if inputs.key?(ext) input[ext] = inputs[ext] end end if input # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are. input_kwargs = input.to_h else # Relay Classic Mutations with no `argument`s # don't require `input:` input_kwargs = {} end if input_kwargs.any? super(**input_kwargs) else super() end end def self.included(base) base.extend(ClassMethods) end module ClassMethods def dummy @dummy ||= begin d = Class.new(GraphQL::Schema::Resolver) d.argument_class(self.argument_class) # TODO make this lazier? d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}") d end end def field_arguments(context = GraphQL::Query::NullContext.instance) dummy.arguments(context) end def get_field_argument(name, context = GraphQL::Query::NullContext.instance) dummy.get_argument(name, context) end def own_field_arguments dummy.own_arguments end def any_field_arguments? dummy.any_arguments? end def all_field_argument_definitions dummy.all_argument_definitions end # Also apply this argument to the input type: def argument(*args, own_argument: false, **kwargs, &block) it = input_type # make sure any inherited arguments are already added to it arg = super(*args, **kwargs, &block) # This definition might be overriding something inherited; # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions prev_args = it.own_arguments[arg.graphql_name] case prev_args when GraphQL::Schema::Argument if prev_args.owner != self it.own_arguments.delete(arg.graphql_name) end when Array prev_args.reject! { |a| a.owner != self } if prev_args.empty? it.own_arguments.delete(arg.graphql_name) end end it.add_argument(arg) arg end # The base class for generated input object types # @param new_class [Class] The base class to use for generating input object definitions # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject}) def input_object_class(new_class = nil) if new_class @input_object_class = new_class end @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject) end # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type # @return [Class] The generated {Schema::InputObject} class for this mutation's `input` def input_type(new_input_type = nil) if new_input_type @input_type = new_input_type end @input_type ||= generate_input_type end private # Generate the input type for the `input:` argument # To customize how input objects are generated, override this method # @return [Class] a subclass of {.input_object_class} def generate_input_type mutation_args = all_argument_definitions mutation_class = self Class.new(input_object_class) do class << self def default_graphql_name "#{self.mutation.graphql_name}Input" end def description(new_desc = nil) super || "Autogenerated input type of #{self.mutation.graphql_name}" end end mutation(mutation_class) # these might be inherited: mutation_args.each do |arg| add_argument(arg) end end end end private def authorize_arguments(args, values) # remove the `input` wrapper to match values input_args = args["input"].type.unwrap.arguments(context) super(input_args, values) end end end end graphql-ruby-2.2.17/lib/graphql/schema/input_object.rb000066400000000000000000000214641476434635200227230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class InputObject < GraphQL::Schema::Member extend Forwardable extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader extend GraphQL::Schema::Member::ValidatesInput extend GraphQL::Schema::Member::HasValidators include GraphQL::Dig # @return [GraphQL::Query::Context] The context for this query attr_reader :context # @return [GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance attr_reader :arguments # Ruby-like hash behaviors, read-only def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty? def initialize(arguments, ruby_kwargs:, context:, defaults_used:) @context = context @ruby_style_hash = ruby_kwargs @arguments = arguments # Apply prepares, not great to have it duplicated here. self.class.arguments(context).each_value do |arg_defn| ruby_kwargs_key = arg_defn.keyword if @ruby_style_hash.key?(ruby_kwargs_key) # Weirdly, procs are applied during coercion, but not methods. # Probably because these methods require a `self`. if arg_defn.prepare.is_a?(Symbol) || context.nil? prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key]) overwrite_argument(ruby_kwargs_key, prepared_value) end end end end def to_h unwrap_value(@ruby_style_hash) end def to_hash to_h end def prepare if @context object = @context[:current_object] # Pass this object's class with `as` so that messages are rendered correctly from inherited validators Schema::Validator.validate!(self.class.validators, object, @context, @ruby_style_hash, as: self.class) self else self end end def self.authorized?(obj, value, ctx) # Authorize each argument (but this doesn't apply if `prepare` is implemented): if value.respond_to?(:key?) arguments(ctx).each do |_name, input_obj_arg| if value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx) return false end end end # It didn't early-return false: true end def self.one_of if !one_of? if all_argument_definitions.any? { |arg| arg.type.non_null? } raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to argument definitions to use `one_of`" end directive(GraphQL::Schema::Directive::OneOf) end end def self.one_of? false # Re-defined when `OneOf` is added end def unwrap_value(value) case value when Array value.map { |item| unwrap_value(item) } when Hash value.reduce({}) do |h, (key, value)| h.merge!(key => unwrap_value(value)) end when InputObject value.to_h else value end end # Lookup a key on this object, it accepts new-style underscored symbols # Or old-style camelized identifiers. # @param key [Symbol, String] def [](key) if @ruby_style_hash.key?(key) @ruby_style_hash[key] elsif @arguments @arguments[key] else nil end end def key?(key) @ruby_style_hash.key?(key) || (@arguments && @arguments.key?(key)) || false end # A copy of the Ruby-style hash def to_kwargs @ruby_style_hash.dup end class << self def argument(*args, **kwargs, &block) argument_defn = super(*args, **kwargs, &block) if one_of? if argument_defn.type.non_null? raise ArgumentError, "Argument '#{argument_defn.path}' must be nullable because it is part of a OneOf type, add `required: false`." end if argument_defn.default_value? raise ArgumentError, "Argument '#{argument_defn.path}' cannot have a default value because it is part of a OneOf type, remove `default_value: ...`." end end # Add a method access method_name = argument_defn.keyword class_eval <<-RUBY, __FILE__, __LINE__ def #{method_name} self[#{method_name.inspect}] end RUBY argument_defn end def kind GraphQL::TypeKinds::INPUT_OBJECT end # @api private INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object." def validate_non_null_input(input, ctx, max_errors: nil) warden = ctx.warden if input.is_a?(Array) return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) end if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h)) # We're not sure it'll act like a hash, so reject it: return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) end # Inject missing required arguments missing_required_inputs = self.arguments(ctx).reduce({}) do |m, (argument_name, argument)| if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name) m[argument_name] = nil end m end result = nil [input, missing_required_inputs].each do |args_to_validate| args_to_validate.each do |argument_name, value| argument = warden.get_argument(self, argument_name) # Items in the input that are unexpected if argument.nil? result ||= Query::InputValidationResult.new result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name]) else # Items in the input that are expected, but have invalid values argument_result = argument.type.validate_input(value, ctx) result ||= Query::InputValidationResult.new if !argument_result.valid? result.merge_result!(argument_name, argument_result) end end end end if one_of? if input.size == 1 input.each do |name, value| if value.nil? result ||= Query::InputValidationResult.new result.add_problem("'#{graphql_name}' requires exactly one argument, but '#{name}' was `null`.") end end else result ||= Query::InputValidationResult.new result.add_problem("'#{graphql_name}' requires exactly one argument, but #{input.size} were provided.") end end result end def coerce_input(value, ctx) if value.nil? return nil end arguments = coerce_arguments(nil, value, ctx) ctx.query.after_lazy(arguments) do |resolved_arguments| if resolved_arguments.is_a?(GraphQL::Error) raise resolved_arguments else input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil) input_obj_instance.prepare end end end # It's funny to think of a _result_ of an input object. # This is used for rendering the default value in introspection responses. def coerce_result(value, ctx) # Allow the application to provide values as :snake_symbols, and convert them to the camelStrings value = value.reduce({}) { |memo, (k, v)| memo[Member::BuildType.camelize(k.to_s)] = v; memo } result = {} arguments(ctx).each do |input_key, input_field_defn| input_value = value[input_key] if value.key?(input_key) result[input_key] = if input_value.nil? nil else input_field_defn.type.coerce_result(input_value, ctx) end end end result end end private def overwrite_argument(key, value) # Argument keywords come in frozen from the interpreter, dup them before modifying them. if @ruby_style_hash.frozen? @ruby_style_hash = @ruby_style_hash.dup end @ruby_style_hash[key] = value end end end end graphql-ruby-2.2.17/lib/graphql/schema/interface.rb000066400000000000000000000100021476434635200221600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module Interface include GraphQL::Schema::Member::GraphQLTypeNames module DefinitionMethods include GraphQL::Schema::Member::BaseDSLMethods # ConfigurationExtension's responsibilities are in `def included` below include GraphQL::Schema::Member::TypeSystemHelpers include GraphQL::Schema::Member::HasFields include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::RelayShortcuts include GraphQL::Schema::Member::Scoped include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasUnresolvedTypeError include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasInterfaces # Methods defined in this block will be: # - Added as class methods to this interface # - Added as class methods to all child interfaces def definition_methods(&block) # Use an instance variable to tell whether it's been included previously or not; # You can't use constant detection because constants are brought into scope # by `include`, which has already happened at this point. if !defined?(@_definition_methods) defn_methods_module = Module.new @_definition_methods = defn_methods_module const_set(:DefinitionMethods, defn_methods_module) extend(self::DefinitionMethods) end self::DefinitionMethods.module_exec(&block) end # @see {Schema::Warden} hides interfaces without visible implementations def visible?(context) true end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end # Here's the tricky part. Make sure behavior keeps making its way down the inheritance chain. def included(child_class) if !child_class.is_a?(Class) # In this case, it's been included into another interface. # This is how interface inheritance is implemented # We need this before we can call `own_interfaces` child_class.extend(Schema::Interface::DefinitionMethods) child_class.type_membership_class(self.type_membership_class) child_class.ancestors.reverse_each do |ancestor| if ancestor.const_defined?(:DefinitionMethods) && ancestor != child_class child_class.extend(ancestor::DefinitionMethods) end end child_class.introspection(introspection) child_class.description(description) # If interfaces are mixed into each other, only define this class once if !child_class.const_defined?(:UnresolvedTypeError, false) add_unresolved_type_error(child_class) end elsif child_class < GraphQL::Schema::Object # This is being included into an object type, make sure it's using `implements(...)` backtrace_line = caller_locations(0, 10).find do |location| location.base_label == "implements" && location.path.end_with?("schema/member/has_interfaces.rb") end if !backtrace_line raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`" end end super end def orphan_types(*types) if types.any? @orphan_types = types else all_orphan_types = @orphan_types || [] all_orphan_types += super if defined?(super) all_orphan_types.uniq end end def kind GraphQL::TypeKinds::INTERFACE end end extend DefinitionMethods def unwrap self end end end end graphql-ruby-2.2.17/lib/graphql/schema/introspection_system.rb000066400000000000000000000125021476434635200245330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class IntrospectionSystem attr_reader :types, :possible_types def initialize(schema) @schema = schema @class_based = !!@schema.is_a?(Class) @built_in_namespace = GraphQL::Introspection @custom_namespace = if @class_based schema.introspection || @built_in_namespace else schema.introspection_namespace || @built_in_namespace end type_defns = [ load_constant(:SchemaType), load_constant(:TypeType), load_constant(:FieldType), load_constant(:DirectiveType), load_constant(:EnumValueType), load_constant(:InputValueType), load_constant(:TypeKindEnum), load_constant(:DirectiveLocationEnum) ] @types = {} @possible_types = {} type_defns.each do |t| @types[t.graphql_name] = t @possible_types[t.graphql_name] = [t] end @entry_point_fields = if schema.disable_introspection_entry_points? {} else entry_point_fields = get_fields_from_class(class_sym: :EntryPoints) entry_point_fields.delete('__schema') if schema.disable_schema_introspection_entry_point? entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point? entry_point_fields end @entry_point_fields.each { |k, v| v.dynamic_introspection = true } @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields) @dynamic_fields.each { |k, v| v.dynamic_introspection = true } end def entry_points @entry_point_fields.values end def entry_point(name:) @entry_point_fields[name] end def dynamic_fields @dynamic_fields.values end def dynamic_field(name:) @dynamic_fields[name] end # The introspection system is prepared with a bunch of LateBoundTypes. # Replace those with the objects that they refer to, since LateBoundTypes # aren't handled at runtime. # # @api private # @return void def resolve_late_bindings @types.each do |name, t| if t.kind.fields? t.fields.each do |_name, field_defn| field_defn.type = resolve_late_binding(field_defn.type) end end end @entry_point_fields.each do |name, f| f.type = resolve_late_binding(f.type) end @dynamic_fields.each do |name, f| f.type = resolve_late_binding(f.type) end nil end private def resolve_late_binding(late_bound_type) case late_bound_type when GraphQL::Schema::LateBoundType @schema.get_type(late_bound_type.name) when GraphQL::Schema::List resolve_late_binding(late_bound_type.of_type).to_list_type when GraphQL::Schema::NonNull resolve_late_binding(late_bound_type.of_type).to_non_null_type when Module # It's a normal type -- no change required late_bound_type else raise "Invariant: unexpected type: #{late_bound_type} (#{late_bound_type.class})" end end def load_constant(class_name) const = @custom_namespace.const_get(class_name) dup_type_class(const) rescue NameError # Dup the built-in so that the cached fields aren't shared dup_type_class(@built_in_namespace.const_get(class_name)) end def get_fields_from_class(class_sym:) object_type_defn = load_constant(class_sym) if object_type_defn.is_a?(Module) object_type_defn.fields else extracted_field_defns = {} object_class = object_type_defn.metadata[:type_class] object_type_defn.all_fields.each do |field_defn| inner_resolve = field_defn.resolve_proc resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve) extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate) end extracted_field_defns end end # This is probably not 100% robust -- but it has to be good enough to avoid modifying the built-in introspection types def dup_type_class(type_class) type_name = type_class.graphql_name Class.new(type_class) do # This won't be inherited like other things will graphql_name(type_name) if type_class.kind.fields? type_class.fields.each do |_name, field_defn| dup_field = field_defn.dup dup_field.owner = self add_field(dup_field) end end end end class PerFieldProxyResolve def initialize(object_class:, inner_resolve:) @object_class = object_class @inner_resolve = inner_resolve end def call(obj, args, ctx) query_ctx = ctx.query.context # Remove the QueryType wrapper if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end wrapped_object = @object_class.wrap(obj, query_ctx) @inner_resolve.call(wrapped_object, args, ctx) end end end end end graphql-ruby-2.2.17/lib/graphql/schema/invalid_type_error.rb000066400000000000000000000001721476434635200241270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class InvalidTypeError < GraphQL::Error end end end graphql-ruby-2.2.17/lib/graphql/schema/late_bound_type.rb000066400000000000000000000015041476434635200234040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # A stand-in for a type which will be resolved in a given schema, by name. # TODO: support argument types too, make this a public API somehow # @api Private class LateBoundType attr_reader :name alias :graphql_name :name def initialize(local_name) @name = local_name @to_non_null_type = nil @to_list_type = nil end def unwrap self end def to_non_null_type @to_non_null_type ||= GraphQL::Schema::NonNull.new(self) end def to_list_type @to_list_type ||= GraphQL::Schema::List.new(self) end def inspect "#" end def non_null? false end alias :to_s :inspect end end end graphql-ruby-2.2.17/lib/graphql/schema/list.rb000066400000000000000000000043561476434635200212120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Represents a list type in the schema. # Wraps a {Schema::Member} as a list type. # @see {Schema::Member::TypeSystemHelpers#to_list_type} class List < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput # @return [GraphQL::TypeKinds::LIST] def kind GraphQL::TypeKinds::LIST end # @return [true] def list? true end def to_type_signature "[#{@of_type.to_type_signature}]" end # This is for introspection, where it's expected the name will be `null` def graphql_name nil end # Also for implementing introspection def description nil end def coerce_result(value, ctx) value.map { |i| i.nil? ? nil : of_type.coerce_result(i, ctx) } end def coerce_input(value, ctx) if value.nil? nil else coerced = ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) } ctx.schema.after_any_lazies(coerced, &:itself) end end def validate_non_null_input(value, ctx, max_errors: nil) result = GraphQL::Query::InputValidationResult.new ensure_array(value).each_with_index do |item, index| item_result = of_type.validate_input(item, ctx) unless item_result.valid? if max_errors if max_errors == 0 add_max_errros_reached_message(result) break end max_errors -= 1 end result.merge_result!(index, item_result) end end result.valid? ? nil : result end private def ensure_array(value) # `Array({ a: 1 })` makes `[[:a, 1]]`, so do it manually if value.is_a?(Array) value else [value] end end def add_max_errros_reached_message(result) message = "Too many errors processing list variable, max validation error limit reached. Execution aborted" item_result = GraphQL::Query::InputValidationResult.from_problem(message) result.merge_result!(nil, item_result) end end end end graphql-ruby-2.2.17/lib/graphql/schema/loader.rb000066400000000000000000000201361476434635200214770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # You can use the result of {GraphQL::Introspection::INTROSPECTION_QUERY} # to make a schema. This schema is missing some important details like # `resolve` functions, but it does include the full type system, # so you can use it to validate queries. # # @see GraphQL::Schema.from_introspection for a public API module Loader extend self # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [Class] the schema described by `input` def load(introspection_result) schema = introspection_result.fetch("data").fetch("__schema") types = {} type_resolver = ->(type) { resolve_type(types, type) } schema.fetch("types").each do |type| next if type.fetch("name").start_with?("__") type_object = define_type(type, type_resolver) types[type["name"]] = type_object end directives = [] schema.fetch("directives", []).each do |directive| next if GraphQL::Schema.default_directives.include?(directive.fetch("name")) directives << define_directive(directive, type_resolver) end Class.new(GraphQL::Schema) do orphan_types(types.values) directives(directives) description(schema["description"]) def self.resolve_type(*) raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects") end [:query, :mutation, :subscription].each do |root| type = schema["#{root}Type"] if type type_defn = types.fetch(type.fetch("name")) self.public_send(root, type_defn) end end end end NullScalarCoerce = ->(val, _ctx) { val } class << self private def resolve_type(types, type) case kind = type.fetch("kind") when "ENUM", "INTERFACE", "INPUT_OBJECT", "OBJECT", "SCALAR", "UNION" type_name = type.fetch("name") type = types[type_name] || Schema::BUILT_IN_TYPES[type_name] if type.nil? GraphQL::Schema::LateBoundType.new(type_name) else type end when "LIST" Schema::List.new(resolve_type(types, type.fetch("ofType"))) when "NON_NULL" Schema::NonNull.new(resolve_type(types, type.fetch("ofType"))) else fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented" end end def extract_default_value(default_value_str, input_value_ast) case input_value_ast when String, Integer, Float, TrueClass, FalseClass input_value_ast when GraphQL::Language::Nodes::Enum input_value_ast.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject input_value_ast.to_h when Array input_value_ast.map { |element| extract_default_value(default_value_str, element) } else raise( "Encountered unexpected type when loading default value. "\ "input_value_ast.class is #{input_value_ast.class} "\ "default_value is #{default_value_str}" ) end end def define_type(type, type_resolver) loader = self case type.fetch("kind") when "ENUM" Class.new(GraphQL::Schema::Enum) do graphql_name(type["name"]) description(type["description"]) type["enumValues"].each do |enum_value| value( enum_value["name"], description: enum_value["description"], deprecation_reason: enum_value["deprecationReason"], ) end end when "INTERFACE" Module.new do include GraphQL::Schema::Interface graphql_name(type["name"]) description(type["description"]) loader.build_fields(self, type["fields"] || [], type_resolver) end when "INPUT_OBJECT" Class.new(GraphQL::Schema::InputObject) do graphql_name(type["name"]) description(type["description"]) loader.build_arguments(self, type["inputFields"], type_resolver) end when "OBJECT" Class.new(GraphQL::Schema::Object) do graphql_name(type["name"]) description(type["description"]) if type["interfaces"] type["interfaces"].each do |interface_type| implements(type_resolver.call(interface_type)) end end loader.build_fields(self, type["fields"], type_resolver) end when "SCALAR" type_name = type.fetch("name") if (builtin = GraphQL::Schema::BUILT_IN_TYPES[type_name]) builtin else Class.new(GraphQL::Schema::Scalar) do graphql_name(type["name"]) description(type["description"]) specified_by_url(type["specifiedByURL"]) end end when "UNION" Class.new(GraphQL::Schema::Union) do graphql_name(type["name"]) description(type["description"]) possible_types(*(type["possibleTypes"].map { |pt| type_resolver.call(pt) })) end else fail GraphQL::RequiredImplementationMissingError, "#{type["kind"]} not implemented" end end def define_directive(directive, type_resolver) loader = self Class.new(GraphQL::Schema::Directive) do graphql_name(directive["name"]) description(directive["description"]) locations(*directive["locations"].map(&:to_sym)) repeatable(directive["isRepeatable"]) loader.build_arguments(self, directive["args"], type_resolver) end end public def build_fields(type_defn, fields, type_resolver) loader = self fields.each do |field_hash| unwrapped_field_hash = field_hash while (of_type = unwrapped_field_hash["ofType"]) unwrapped_field_hash = of_type end type_defn.field( field_hash["name"], type: type_resolver.call(field_hash["type"]), description: field_hash["description"], deprecation_reason: field_hash["deprecationReason"], null: true, camelize: false, connection_extension: nil, ) do if field_hash["args"].any? loader.build_arguments(self, field_hash["args"], type_resolver) end end end end def build_arguments(arg_owner, args, type_resolver) args.each do |arg| kwargs = { type: type_resolver.call(arg["type"]), description: arg["description"], deprecation_reason: arg["deprecationReason"], required: false, camelize: false, } if arg["defaultValue"] default_value_str = arg["defaultValue"] dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }" # Returns a `GraphQL::Language::Nodes::Document`: dummy_query_ast = GraphQL.parse(dummy_query_str) # Reach into the AST for the default value: input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value kwargs[:default_value] = extract_default_value(default_value_str, input_value_ast) end arg_owner.argument(arg["name"], **kwargs) end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member.rb000066400000000000000000000023551476434635200215030ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/member/base_dsl_methods' require 'graphql/schema/member/graphql_type_names' require 'graphql/schema/member/has_ast_node' require 'graphql/schema/member/has_directives' require 'graphql/schema/member/has_deprecation_reason' require 'graphql/schema/member/has_interfaces' require 'graphql/schema/member/has_path' require 'graphql/schema/member/has_unresolved_type_error' require 'graphql/schema/member/has_validators' require 'graphql/schema/member/relay_shortcuts' require 'graphql/schema/member/scoped' require 'graphql/schema/member/type_system_helpers' require 'graphql/schema/member/validates_input' module GraphQL class Schema # The base class for things that make up the schema, # eg objects, enums, scalars. # # @api private class Member include GraphQLTypeNames extend BaseDSLMethods extend BaseDSLMethods::ConfigurationExtension introspection(false) extend TypeSystemHelpers extend Scoped extend RelayShortcuts extend HasPath extend HasAstNode extend HasDirectives end end end require 'graphql/schema/member/has_arguments' require 'graphql/schema/member/has_fields' require 'graphql/schema/member/build_type' graphql-ruby-2.2.17/lib/graphql/schema/member/000077500000000000000000000000001476434635200211515ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/member/base_dsl_methods.rb000066400000000000000000000075511476434635200250050ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/find_inherited_value" module GraphQL class Schema class Member # DSL methods shared by lots of things in the GraphQL Schema. # @api private # @see Classes that extend this, eg {GraphQL::Schema::Object} module BaseDSLMethods include GraphQL::Schema::FindInheritedValue # Call this with a new name to override the default name for this schema member; OR # call it without an argument to get the name of this schema member # # The default name is implemented in default_graphql_name # @param new_name [String] # @return [String] def graphql_name(new_name = nil) if new_name GraphQL::NameValidator.validate!(new_name) @graphql_name = new_name else @graphql_name ||= default_graphql_name end end # Just a convenience method to point out that people should use graphql_name instead def name(new_name = nil) return super() if new_name.nil? fail( "The new name override method is `graphql_name`, not `name`. Usage: "\ "graphql_name \"#{new_name}\"" ) end # Call this method to provide a new description; OR # call it without an argument to get the description # @param new_description [String] # @return [String] def description(new_description = nil) if new_description @description = new_description elsif defined?(@description) @description else @description = nil end end # This pushes some configurations _down_ the inheritance tree, # in order to prevent repetitive lookups at runtime. module ConfigurationExtension def inherited(child_class) child_class.introspection(introspection) child_class.description(description) child_class.default_graphql_name = nil if defined?(@graphql_name) && @graphql_name && (self.name.nil? || graphql_name != default_graphql_name) child_class.graphql_name(graphql_name) else child_class.graphql_name = nil end super end end # @return [Boolean] If true, this object is part of the introspection system def introspection(new_introspection = nil) if !new_introspection.nil? @introspection = new_introspection elsif defined?(@introspection) @introspection else false end end def introspection? !!@introspection end # The mutation this type was derived from, if it was derived from a mutation # @return [Class] def mutation(mutation_class = nil) if mutation_class @mutation = mutation_class elsif defined?(@mutation) @mutation else nil end end alias :unwrap :itself # Creates the default name for a schema member. # The default name is the Ruby constant name, # without any namespaces and with any `-Type` suffix removed def default_graphql_name @default_graphql_name ||= begin raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil? g_name = -name.split("::").last g_name.end_with?("Type") ? g_name.sub(/Type\Z/, "") : g_name end end def visible?(context) true end def authorized?(object, context) true end def default_relay false end protected attr_writer :default_graphql_name, :graphql_name end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/build_type.rb000066400000000000000000000145721476434635200236470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # @api private module BuildType LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported" module_function # @param type_expr [String, Class, GraphQL::BaseType] # @return [GraphQL::BaseType] def parse_type(type_expr, null:) list_type = false return_type = case type_expr when String case type_expr when "String" GraphQL::Types::String when "Int", "Integer" GraphQL::Types::Int when "Float" GraphQL::Types::Float when "Boolean" GraphQL::Types::Boolean when "ID" GraphQL::Types::ID when /\A\[.*\]\Z/ list_type = true # List members are required by default parse_type(type_expr[1..-2], null: false) when /.*!\Z/ null = false parse_type(type_expr[0..-2], null: true) else maybe_type = constantize(type_expr) case maybe_type when Module # This is a way to check that it's the right kind of module: if maybe_type.respond_to?(:kind) maybe_type else raise ArgumentError, "Unexpected class/module found for GraphQL type: #{type_expr} (must be type definition class/module)" end end end when GraphQL::Schema::LateBoundType type_expr when Array case type_expr.length when 1 list_type = true # List members are required by default parse_type(type_expr.first, null: false) when 2 inner_type, nullable_option = type_expr if nullable_option.keys != [:null] || (nullable_option[:null] != true && nullable_option[:null] != false) raise ArgumentError, LIST_TYPE_ERROR end list_type = true parse_type(inner_type, null: nullable_option[:null]) else raise ArgumentError, LIST_TYPE_ERROR end when GraphQL::Schema::NonNull, GraphQL::Schema::List type_expr when Module # This is a way to check that it's the right kind of module: if type_expr.respond_to?(:kind) type_expr else # Eg `String` => GraphQL::Types::String parse_type(type_expr.name, null: true) end when Proc parse_type(type_expr.call, null: true) when false raise ArgumentError, "Received `false` instead of a type, maybe a `!` should be replaced with `null: true` (for fields) or `required: true` (for arguments)" end if return_type.nil? raise "Unexpected type input: #{type_expr.inspect} (#{type_expr.class})" end # Apply list_type first, that way the # .to_non_null_type applies to the list type, not the inner type if list_type return_type = return_type.to_list_type end if !null return_type = return_type.to_non_null_type end return_type end def to_type_name(something) case something when GraphQL::Schema::LateBoundType something.unwrap.name when Array to_type_name(something.first) when Module if something.respond_to?(:graphql_name) something.graphql_name else to_type_name(something.name) end when String if something.include?("]") || something.include?("[") || something.include?("!") || something.include?("::") something.gsub(/\]\[\!/, "").split("::").last else something end when GraphQL::Schema::NonNull, GraphQL::Schema::List to_type_name(something.unwrap) else raise "Unhandled to_type_name input: #{something} (#{something.class})" end end def camelize(string) return string if string == '_' return string unless string.include?("_") camelized = string.split('_').each(&:capitalize!).join camelized[0] = camelized[0].downcase if string.start_with?("_") match_data = string.match(/\A(_+)/) camelized = "#{match_data[0]}#{camelized}" end camelized end # Resolves constant from string (based on Rails `ActiveSupport::Inflector.constantize`) def constantize(string) names = string.split('::') # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(string) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const end # Owner is in Object, so raise. constant.const_get(name, false) end end end def underscore(string) if string.match?(/\A[a-z_]+\Z/) return string end string2 = string.dup string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing string2.downcase! string2 end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/graphql_type_names.rb000066400000000000000000000007311476434635200253610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # These constants are interpreted as GraphQL types when defining fields or arguments # # @example # field :is_draft, Boolean, null: false # field :id, ID, null: false # field :score, Int, null: false # # @api private module GraphQLTypeNames Boolean = "Boolean" ID = "ID" Int = "Int" end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_arguments.rb000066400000000000000000000412701476434635200243420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasArguments def self.included(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) end def self.extended(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) cls.extend(ClassConfigured) end # @see {GraphQL::Schema::Argument#initialize} for parameters # @return [GraphQL::Schema::Argument] An instance of {argument_class}, created from `*args` def argument(*args, **kwargs, &block) kwargs[:owner] = self loads = kwargs[:loads] if loads name = args[0] name_as_string = name.to_s inferred_arg_name = case name_as_string when /_id$/ name_as_string.sub(/_id$/, "").to_sym when /_ids$/ name_as_string.sub(/_ids$/, "") .sub(/([^s])$/, "\\1s") .to_sym else name end kwargs[:as] ||= inferred_arg_name end arg_defn = self.argument_class.new(*args, **kwargs, &block) add_argument(arg_defn) if self.is_a?(Class) && !method_defined?(:"load_#{arg_defn.keyword}") method_owner = if self < GraphQL::Schema::InputObject || self < GraphQL::Schema::Directive "self." elsif self < GraphQL::Schema::Resolver "" else raise "Unexpected argument owner: #{self}" end if loads && arg_defn.type.list? class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method_owner}load_#{arg_defn.keyword}(values, context = nil) argument = get_argument("#{arg_defn.graphql_name}", context || self.context) (context || self.context).query.after_lazy(values) do |values2| GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, value, context || self.context) }) end end RUBY elsif loads class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method_owner}load_#{arg_defn.keyword}(value, context = nil) argument = get_argument("#{arg_defn.graphql_name}", context || self.context) load_application_object(argument, value, context || self.context) end RUBY else class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method_owner}load_#{arg_defn.keyword}(value, _context = nil) value end RUBY end end arg_defn end # Register this argument with the class. # @param arg_defn [GraphQL::Schema::Argument] # @return [GraphQL::Schema::Argument] def add_argument(arg_defn) @own_arguments ||= {} prev_defn = @own_arguments[arg_defn.name] case prev_defn when nil @own_arguments[arg_defn.name] = arg_defn when Array prev_defn << arg_defn when GraphQL::Schema::Argument @own_arguments[arg_defn.name] = [prev_defn, arg_defn] else raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}" end arg_defn end def remove_argument(arg_defn) prev_defn = @own_arguments[arg_defn.name] case prev_defn when nil # done when Array prev_defn.delete(arg_defn) when GraphQL::Schema::Argument @own_arguments.delete(arg_defn.name) else raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}" end nil end # @return [Hash GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions def arguments(context = GraphQL::Query::NullContext.instance) if own_arguments.any? own_arguments_that_apply = {} own_arguments.each do |name, args_entry| if (visible_defn = Warden.visible_entry?(:visible_argument?, args_entry, context)) own_arguments_that_apply[visible_defn.graphql_name] = visible_defn end end end # might be nil if there are actually no arguments own_arguments_that_apply || own_arguments end def any_arguments? own_arguments.any? end module ClassConfigured def inherited(child_class) super child_class.extend(InheritedArguments) end module InheritedArguments def arguments(context = GraphQL::Query::NullContext.instance) own_arguments = super inherited_arguments = superclass.arguments(context) if own_arguments.any? if inherited_arguments.any? # Local definitions override inherited ones inherited_arguments.merge(own_arguments) else own_arguments end else inherited_arguments end end def any_arguments? super || superclass.any_arguments? end def all_argument_definitions all_defns = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_arguments) all_defns.merge!(ancestor.own_arguments) end end all_defns = all_defns.values all_defns.flatten! all_defns end def get_argument(argument_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) for ancestor in ancestors if ancestor.respond_to?(:own_arguments) && (a = ancestor.own_arguments[argument_name]) && (a = Warden.visible_entry?(:visible_argument?, a, context, warden)) return a end end nil end end end module FieldConfigured def arguments(context = GraphQL::Query::NullContext.instance) own_arguments = super if @resolver_class inherited_arguments = @resolver_class.field_arguments(context) if own_arguments.any? if inherited_arguments.any? inherited_arguments.merge(own_arguments) else own_arguments end else inherited_arguments end else own_arguments end end def any_arguments? super || (@resolver_class && @resolver_class.any_field_arguments?) end def all_argument_definitions if @resolver_class all_defns = {} @resolver_class.all_field_argument_definitions.each do |arg_defn| key = arg_defn.graphql_name case (current_value = all_defns[key]) when nil all_defns[key] = arg_defn when Array current_value << arg_defn when GraphQL::Schema::Argument all_defns[key] = [current_value, arg_defn] else raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}" end end all_defns.merge!(own_arguments) all_defns = all_defns.values all_defns.flatten! all_defns else super end end end def all_argument_definitions all_defns = own_arguments.values all_defns.flatten! all_defns end # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name. def get_argument(argument_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)) visible_arg elsif defined?(@resolver_class) && @resolver_class @resolver_class.get_field_argument(argument_name, context) else nil end end # @param new_arg_class [Class] A class to use for building argument definitions def argument_class(new_arg_class = nil) self.class.argument_class(new_arg_class) end # @api private # If given a block, it will eventually yield the loaded args to the block. # # If no block is given, it will immediately dataload (but might return a Lazy). # # @param values [Hash] # @param context [GraphQL::Query::Context] # @yield [Interpreter::Arguments, Execution::Lazy] # @return [Interpreter::Arguments, Execution::Lazy] def coerce_arguments(parent_object, values, context, &block) # Cache this hash to avoid re-merging it arg_defns = context.warden.arguments(self) total_args_count = arg_defns.size finished_args = nil prepare_finished_args = -> { if total_args_count == 0 finished_args = GraphQL::Execution::Interpreter::Arguments::EMPTY if block_given? block.call(finished_args) end else argument_values = {} resolved_args_count = 0 raised_error = false arg_defns.each do |arg_defn| context.dataloader.append_job do begin arg_defn.coerce_into_values(parent_object, values, context, argument_values) rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err raised_error = true finished_args = err if block_given? block.call(finished_args) end end resolved_args_count += 1 if resolved_args_count == total_args_count && !raised_error finished_args = context.schema.after_any_lazies(argument_values.values) { GraphQL::Execution::Interpreter::Arguments.new( argument_values: argument_values, ) } if block_given? block.call(finished_args) end end end end end } if block_given? prepare_finished_args.call nil else # This API returns eagerly, gotta run it now context.dataloader.run_isolated(&prepare_finished_args) finished_args end end # Usually, this is validated statically by RequiredArgumentsArePresent, # but not for directives. # TODO apply static validations on schema definitions? def validate_directive_argument(arg_defn, value) # this is only implemented on directives. nil end module HasDirectiveArguments def validate_directive_argument(arg_defn, value) if value.nil? && arg_defn.type.non_null? raise ArgumentError, "#{arg_defn.path} is required, but no value was given" end end end def arguments_statically_coercible? if defined?(@arguments_statically_coercible) && !@arguments_statically_coercible.nil? @arguments_statically_coercible else @arguments_statically_coercible = all_argument_definitions.all?(&:statically_coercible?) end end module ArgumentClassAccessor def argument_class(new_arg_class = nil) if new_arg_class @argument_class = new_arg_class elsif defined?(@argument_class) && @argument_class @argument_class else superclass.respond_to?(:argument_class) ? superclass.argument_class : GraphQL::Schema::Argument end end end module ArgumentObjectLoader # Look up the corresponding object for a provided ID. # By default, it uses Relay-style {Schema.object_from_id}, # override this to find objects another way. # # @param type [Class, Module] A GraphQL type definition # @param id [String] A client-provided to look up # @param context [GraphQL::Query::Context] the current context def object_from_id(type, id, context) context.schema.object_from_id(id, context) end def load_application_object(argument, id, context) # See if any object can be found for this ID if id.nil? return nil end object_from_id(argument.loads, id, context) end def load_and_authorize_application_object(argument, id, context) loaded_application_object = load_application_object(argument, id, context) authorize_application_object(argument, id, context, loaded_application_object) end def authorize_application_object(argument, id, context, loaded_application_object) context.query.after_lazy(loaded_application_object) do |application_object| if application_object.nil? err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object) application_object = load_application_object_failed(err) end # Double-check that the located object is actually of this type # (Don't want to allow arbitrary access to objects this way) if application_object.nil? nil else maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context) context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result| if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 application_object_type, application_object = resolve_type_result else application_object_type = resolve_type_result # application_object is already assigned end if !( context.warden.possible_types(argument.loads).include?(application_object_type) || context.warden.loadable?(argument.loads, context) ) err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object) application_object = load_application_object_failed(err) end if application_object.nil? nil else # This object was loaded successfully # and resolved to the right type, # now apply the `.authorized?` class method if there is one context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed| if authed application_object else err = GraphQL::UnauthorizedError.new( object: application_object, type: application_object_type, context: context, ) if self.respond_to?(:unauthorized_object) err.set_backtrace(caller) unauthorized_object(err) else raise err end end end end end end end end def load_application_object_failed(err) raise err end end NO_ARGUMENTS = GraphQL::EmptyObjects::EMPTY_HASH def own_arguments @own_arguments || NO_ARGUMENTS end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_ast_node.rb000066400000000000000000000013211476434635200241220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasAstNode def self.extended(child_cls) super child_cls.ast_node = nil end def inherited(child_cls) super child_cls.ast_node = nil end # If this schema was parsed from a `.graphql` file (or other SDL), # this is the AST node that defined this part of the schema. def ast_node(new_ast_node = nil) if new_ast_node @ast_node = new_ast_node elsif defined?(@ast_node) @ast_node else nil end end attr_writer :ast_node end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_deprecation_reason.rb000066400000000000000000000014361476434635200262010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasDeprecationReason # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection) attr_reader :deprecation_reason # Set the deprecation reason for this member, or remove it by assigning `nil` # @param text [String, nil] def deprecation_reason=(text) @deprecation_reason = text if text.nil? remove_directive(GraphQL::Schema::Directive::Deprecated) else # This removes a previously-attached directive, if there is one: directive(GraphQL::Schema::Directive::Deprecated, reason: text) end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_directives.rb000066400000000000000000000077631476434635200245070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasDirectives def self.extended(child_cls) super child_cls.module_exec { self.own_directives = nil } end def inherited(child_cls) super child_cls.own_directives = nil end # Create an instance of `dir_class` for `self`, using `options`. # # It removes a previously-attached instance of `dir_class`, if there is one. # # @return [void] def directive(dir_class, **options) @own_directives ||= [] HasDirectives.add_directive(self, @own_directives, dir_class, options) nil end # Remove an attached instance of `dir_class`, if there is one # @param dir_class [Class] # @return [viod] def remove_directive(dir_class) HasDirectives.remove_directive(@own_directives, dir_class) nil end def directives HasDirectives.get_directives(self, @own_directives, :directives) end class << self def add_directive(schema_member, directives, directive_class, directive_options) remove_directive(directives, directive_class) unless directive_class.repeatable? directives << directive_class.new(schema_member, **directive_options) end def remove_directive(directives, directive_class) directives && directives.reject! { |d| d.is_a?(directive_class) } end def get_directives(schema_member, directives, directives_method) case schema_member when Class inherited_directives = if schema_member.superclass.respond_to?(directives_method) get_directives(schema_member.superclass, schema_member.superclass.public_send(directives_method), directives_method) else GraphQL::EmptyObjects::EMPTY_ARRAY end if inherited_directives.any? && directives dirs = [] merge_directives(dirs, inherited_directives) merge_directives(dirs, directives) dirs elsif directives directives elsif inherited_directives.any? inherited_directives else GraphQL::EmptyObjects::EMPTY_ARRAY end when Module dirs = nil schema_member.ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_directives) && (anc_dirs = ancestor.own_directives).any? dirs ||= [] merge_directives(dirs, anc_dirs) end end if directives dirs ||= [] merge_directives(dirs, directives) end dirs || GraphQL::EmptyObjects::EMPTY_ARRAY when HasDirectives directives || GraphQL::EmptyObjects::EMPTY_ARRAY else raise "Invariant: how could #{schema_member} not be a Class, Module, or instance of HasDirectives?" end end private # Modify `target` by adding items from `dirs` such that: # - Any name conflict is overriden by the incoming member of `dirs` # - Any other member of `dirs` is appended # @param target [Array] # @param dirs [Array] # @return [void] def merge_directives(target, dirs) dirs.each do |dir| if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name }) target.slice!(idx) target.insert(idx, dir) else target << dir end end nil end end protected attr_accessor :own_directives end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_fields.rb000066400000000000000000000223061476434635200236020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Shared code for Objects, Interfaces, Mutations, Subscriptions module HasFields # Add a field to this object or interface with the given definition # @see {GraphQL::Schema::Field#initialize} for method signature # @return [GraphQL::Schema::Field] def field(*args, **kwargs, &block) field_defn = field_class.from_options(*args, owner: self, **kwargs, &block) add_field(field_defn) field_defn end # A list of Ruby keywords. # # @api private RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__] # A list of GraphQL-Ruby keywords. # # @api private GRAPHQL_RUBY_KEYWORDS = [:context, :object, :raw_value] # A list of field names that we should advise users to pick a different # resolve method name. # # @api private CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS + Object.instance_methods) # Register this field with the class, overriding a previous one if needed. # @param field_defn [GraphQL::Schema::Field] # @return [void] def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?) # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` -- # that shows that no override value was given manually. if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym && field_defn.hash_key == NOT_CONFIGURED && field_defn.dig_keys.nil? warn(conflict_field_name_warning(field_defn)) end prev_defn = own_fields[field_defn.name] case prev_defn when nil own_fields[field_defn.name] = field_defn when Array prev_defn << field_defn when GraphQL::Schema::Field own_fields[field_defn.name] = [prev_defn, field_defn] else raise "Invariant: unexpected previous field definition for #{field_defn.name.inspect}: #{prev_defn.inspect}" end nil end # @return [Class] The class to initialize when adding fields to this kind of schema member def field_class(new_field_class = nil) if new_field_class @field_class = new_field_class elsif defined?(@field_class) && @field_class @field_class else find_inherited_value(:field_class, GraphQL::Schema::Field) end end def global_id_field(field_name, **kwargs) type = self field field_name, "ID", **kwargs, null: false define_method(field_name) do context.schema.id_from_object(object, type, context) end end # @return [Hash GraphQL::Schema::Field, Array>] Fields defined on this class _specifically_, not parent classes def own_fields @own_fields ||= {} end def all_field_definitions all_fields = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_fields) all_fields.merge!(ancestor.own_fields) end end all_fields = all_fields.values all_fields.flatten! all_fields end module InterfaceMethods def get_field(field_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) for ancestor in ancestors if ancestor.respond_to?(:own_fields) && (f_entry = ancestor.own_fields[field_name]) && (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden)) return f end end nil end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields(context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) # Local overrides take precedence over inherited fields visible_fields = {} for ancestor in ancestors if ancestor.respond_to?(:own_fields) ancestor.own_fields.each do |field_name, fields_entry| # Choose the most local definition that passes `.visible?` -- # stop checking for fields by name once one has been found. if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden)) visible_fields[field_name] = f end end end end visible_fields end end module ObjectMethods def get_field(field_name, context = GraphQL::Query::NullContext.instance) # Objects need to check that the interface implementation is visible, too warden = Warden.from_context(context) ancs = ancestors i = 0 while (ancestor = ancs[i]) if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden) && (f_entry = ancestor.own_fields[field_name]) && (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden)) return f end i += 1 end nil end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields(context = GraphQL::Query::NullContext.instance) # Objects need to check that the interface implementation is visible, too warden = Warden.from_context(context) # Local overrides take precedence over inherited fields visible_fields = {} for ancestor in ancestors if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden) ancestor.own_fields.each do |field_name, fields_entry| # Choose the most local definition that passes `.visible?` -- # stop checking for fields by name once one has been found. if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden)) visible_fields[field_name] = f end end end end visible_fields end end def self.included(child_class) # Included in an interface definition methods module child_class.include(InterfaceMethods) super end def self.extended(child_class) child_class.extend(ObjectMethods) super end private def inherited(subclass) super subclass.class_exec do @own_fields ||= nil @field_class ||= nil end end # If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible. def visible_interface_implementation?(type, context, warden) if type.respond_to?(:kind) && type.kind.interface? implements_this_interface = false implementation_is_visible = false warden.interface_type_memberships(self, context).each do |tm| if tm.abstract_type == type implements_this_interface ||= true if warden.visible_type_membership?(tm, context) implementation_is_visible = true break end end end # It's possible this interface came by way of `include` in another interface which this # object type _does_ implement, and that's ok implements_this_interface ? implementation_is_visible : true else # If there's no implementation, then we're looking at Ruby-style inheritance instead true end end # @param [GraphQL::Schema::Field] # @return [String] A warning to give when this field definition might conflict with a built-in method def conflict_field_name_warning(field_defn) "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning." end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_interfaces.rb000066400000000000000000000122701476434635200244560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasInterfaces def implements(*new_interfaces, **options) new_memberships = [] new_interfaces.each do |int| if int.is_a?(Module) unless int.include?(GraphQL::Schema::Interface) raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules." end new_memberships << int.type_membership_class.new(int, self, **options) # Include the methods here, # `.fields` will use the inheritance chain # to find inherited fields include(int) # If this interface has interfaces of its own, add those, too int.interfaces.each do |next_interface| implements(next_interface) end elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType) if options.any? raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature." end new_memberships << int else raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})" end end # Remove any String or late-bound interfaces which are being replaced own_interface_type_memberships.reject! { |old_i_m| if !(old_i_m.respond_to?(:abstract_type) && old_i_m.abstract_type.is_a?(Module)) old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m old_name = Schema::Member::BuildType.to_type_name(old_int_type) new_memberships.any? { |new_i_m| new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m new_name = Schema::Member::BuildType.to_type_name(new_int_type) new_name == old_name } end } own_interface_type_memberships.concat(new_memberships) end def own_interface_type_memberships @own_interface_type_memberships ||= [] end def interface_type_memberships own_interface_type_memberships end module ClassConfigured # This combination of extended -> inherited -> extended # means that the base class (`Schema::Object`) *won't* # have the superclass-related code in `InheritedInterfaces`, # but child classes of `Schema::Object` will have it. # That way, we don't need a `superclass.respond_to?(...)` check. def inherited(child_class) super child_class.extend(InheritedInterfaces) end module InheritedInterfaces def interfaces(context = GraphQL::Query::NullContext.instance) visible_interfaces = super inherited_interfaces = superclass.interfaces(context) if visible_interfaces.any? if inherited_interfaces.any? visible_interfaces.concat(inherited_interfaces) visible_interfaces.uniq! end visible_interfaces elsif inherited_interfaces.any? inherited_interfaces else EmptyObjects::EMPTY_ARRAY end end def interface_type_memberships own_tms = super inherited_tms = superclass.interface_type_memberships if inherited_tms.size > 0 own_tms + inherited_tms else own_tms end end end end # param context [Query::Context] If omitted, skip filtering. def interfaces(context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) visible_interfaces = nil own_interface_type_memberships.each do |type_membership| case type_membership when Schema::TypeMembership if warden.visible_type_membership?(type_membership, context) visible_interfaces ||= [] visible_interfaces << type_membership.abstract_type end when String, Schema::LateBoundType # During initialization, `type_memberships` can hold late-bound types visible_interfaces ||= [] visible_interfaces << type_membership else raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}" end end if visible_interfaces visible_interfaces.uniq! visible_interfaces else EmptyObjects::EMPTY_ARRAY end end private def self.extended(child_class) child_class.extend(ClassConfigured) end def inherited(subclass) super subclass.class_exec do @own_interface_type_memberships ||= nil end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_path.rb000066400000000000000000000011551476434635200232670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasPath # @return [String] A description of this member's place in the GraphQL schema def path path_str = if self.respond_to?(:graphql_name) self.graphql_name elsif self.class.respond_to?(:graphql_name) # Instances of resolvers self.class.graphql_name end if self.respond_to?(:owner) && owner.respond_to?(:path) path_str = "#{owner.path}.#{path_str}" end path_str end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_unresolved_type_error.rb000066400000000000000000000006241476434635200267730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Set up a type-specific error to make debugging & bug tracker integration better module HasUnresolvedTypeError private def add_unresolved_type_error(child_class) child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError)) end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/has_validators.rb000066400000000000000000000030401476434635200244760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasValidators include GraphQL::EmptyObjects # Build {GraphQL::Schema::Validator}s based on the given configuration # and use them for this schema member # @param validation_config [Hash{Symbol => Hash}] # @return [void] def validates(validation_config) new_validators = GraphQL::Schema::Validator.from_config(self, validation_config) @own_validators ||= [] @own_validators.concat(new_validators) nil end # @return [Array] def validators @own_validators || EMPTY_ARRAY end module ClassConfigured def inherited(child_cls) super child_cls.extend(ClassValidators) end module ClassValidators include GraphQL::EmptyObjects def validators inherited_validators = superclass.validators if inherited_validators.any? if @own_validators.nil? inherited_validators else inherited_validators + @own_validators end elsif @own_validators.nil? EMPTY_ARRAY else @own_validators end end end end def self.extended(child_cls) super child_cls.extend(ClassConfigured) end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/relay_shortcuts.rb000066400000000000000000000055611476434635200247370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module RelayShortcuts def edge_type_class(new_edge_type_class = nil) if new_edge_type_class initialize_relay_metadata @edge_type_class = new_edge_type_class else # Don't call `ancestor.edge_type_class` # because we don't want a fallback from any ancestors -- # only apply the fallback if _no_ ancestor has a configured value! for ancestor in self.ancestors if ancestor.respond_to?(:configured_edge_type_class, true) && (etc = ancestor.configured_edge_type_class) return etc end end Types::Relay::BaseEdge end end def connection_type_class(new_connection_type_class = nil) if new_connection_type_class initialize_relay_metadata @connection_type_class = new_connection_type_class else # Don't call `ancestor.connection_type_class` # because we don't want a fallback from any ancestors -- # only apply the fallback if _no_ ancestor has a configured value! for ancestor in self.ancestors if ancestor.respond_to?(:configured_connection_type_class, true) && (ctc = ancestor.configured_connection_type_class) return ctc end end Types::Relay::BaseConnection end end def edge_type initialize_relay_metadata @edge_type ||= begin edge_name = self.graphql_name + "Edge" node_type_class = self Class.new(edge_type_class) do graphql_name(edge_name) node_type(node_type_class) end end end def connection_type initialize_relay_metadata @connection_type ||= begin conn_name = self.graphql_name + "Connection" edge_type_class = self.edge_type Class.new(connection_type_class) do graphql_name(conn_name) edge_type(edge_type_class) end end end protected def configured_connection_type_class @connection_type_class end def configured_edge_type_class @edge_type_class end attr_writer :edge_type, :connection_type, :connection_type_class, :edge_type_class private # If one of thse values is accessed, initialize all the instance variables to retain # a consistent object shape. def initialize_relay_metadata if !defined?(@connection_type) @connection_type = nil @edge_type = nil @connection_type_class = nil @edge_type_class = nil end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/scoped.rb000066400000000000000000000022011476434635200227460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module Scoped # This is called when a field has `scope: true`. # The field's return type class receives this call. # # By default, it's a no-op. Override it to scope your objects. # # @param items [Object] Some list-like object (eg, Array, ActiveRecord::Relation) # @param context [GraphQL::Query::Context] # @return [Object] Another list-like object, scoped to the current context def scope_items(items, context) items end def reauthorize_scoped_objects(new_value = nil) if new_value.nil? if @reauthorize_scoped_objects != nil @reauthorize_scoped_objects else find_inherited_value(:reauthorize_scoped_objects, true) end else @reauthorize_scoped_objects = new_value end end def inherited(subclass) super subclass.class_exec do @reauthorize_scoped_objects = nil end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/type_system_helpers.rb000066400000000000000000000026751476434635200256170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module TypeSystemHelpers def initialize(*args, &block) super @to_non_null_type ||= nil @to_list_type ||= nil end ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true) # @return [Schema::NonNull] Make a non-null-type representation of this type def to_non_null_type @to_non_null_type ||= GraphQL::Schema::NonNull.new(self) end # @return [Schema::List] Make a list-type representation of this type def to_list_type @to_list_type ||= GraphQL::Schema::List.new(self) end # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. def non_null? false end # @return [Boolean] true if this is a list type. A non-nullable list is considered a list. def list? false end def to_type_signature graphql_name end # @return [GraphQL::TypeKinds::TypeKind] def kind raise GraphQL::RequiredImplementationMissingError, "No `.kind` defined for #{self}" end private def inherited(subclass) subclass.class_exec do @to_non_null_type ||= nil @to_list_type ||= nil end super end end end end end graphql-ruby-2.2.17/lib/graphql/schema/member/validates_input.rb000066400000000000000000000015121476434635200246700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module ValidatesInput def valid_input?(val, ctx) validate_input(val, ctx).valid? end def validate_input(val, ctx, max_errors: nil) if val.nil? Query::InputValidationResult::VALID else validate_non_null_input(val, ctx, max_errors: max_errors) || Query::InputValidationResult::VALID end end def valid_isolated_input?(v) valid_input?(v, GraphQL::Query::NullContext.instance) end def coerce_isolated_input(v) coerce_input(v, GraphQL::Query::NullContext.instance) end def coerce_isolated_result(v) coerce_result(v, GraphQL::Query::NullContext.instance) end end end end end graphql-ruby-2.2.17/lib/graphql/schema/mutation.rb000066400000000000000000000052761476434635200221010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This base class accepts configuration for a mutation root field, # then it can be hooked up to your mutation root object type. # # If you want to customize how this class generates types, in your base class, # override the various `generate_*` methods. # # @see {GraphQL::Schema::RelayClassicMutation} for an extension of this class with some conventions built-in. # # @example Creating a comment # # Define the mutation: # class Mutations::CreateComment < GraphQL::Schema::Mutation # argument :body, String, required: true # argument :post_id, ID, required: true # # field :comment, Types::Comment, null: true # field :errors, [String], null: false # # def resolve(body:, post_id:) # post = Post.find(post_id) # comment = post.comments.build(body: body, author: context[:current_user]) # if comment.save # # Successful creation, return the created object with no errors # { # comment: comment, # errors: [], # } # else # # Failed save, return the errors to the client # { # comment: nil, # errors: comment.errors.full_messages # } # end # end # end # # # Hook it up to your mutation: # class Types::Mutation < GraphQL::Schema::Object # field :create_comment, mutation: Mutations::CreateComment # end # # # Call it from GraphQL: # result = MySchema.execute <<-GRAPHQL # mutation { # createComment(postId: "1", body: "Nice Post!") { # errors # comment { # body # author { # login # } # } # } # } # GRAPHQL # class Mutation < GraphQL::Schema::Resolver extend GraphQL::Schema::Member::HasFields extend GraphQL::Schema::Resolver::HasPayloadType class << self def visible?(context) true end private def conflict_field_name_warning(field_defn) "#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `hash_key:` or `method:` to pick a different resolve behavior for this field (for example, `hash_key: :#{field_defn.resolver_method}_value`, and modify the return hash). Or use `method_conflict_warning: false` to suppress this warning." end # Override this to attach self as `mutation` def generate_payload_type payload_class = super payload_class.mutation(self) payload_class end end end end end graphql-ruby-2.2.17/lib/graphql/schema/non_null.rb000066400000000000000000000033601476434635200220550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Represents a non null type in the schema. # Wraps a {Schema::Member} when it is required. # @see {Schema::Member::TypeSystemHelpers#to_non_null_type} class NonNull < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput # @return [GraphQL::TypeKinds::NON_NULL] def kind GraphQL::TypeKinds::NON_NULL end # @return [true] def non_null? true end # @return [Boolean] True if this type wraps a list type def list? @of_type.list? end def to_type_signature "#{@of_type.to_type_signature}!" end def inspect "#<#{self.class.name} @of_type=#{@of_type.inspect}>" end def validate_input(value, ctx, max_errors: nil) if value.nil? result = GraphQL::Query::InputValidationResult.new result.add_problem("Expected value to not be null") result else of_type.validate_input(value, ctx, max_errors: max_errors) end end # This is for introspection, where it's expected the name will be `null` def graphql_name nil end def coerce_input(value, ctx) # `.validate_input` above is used for variables, but this method is used for arguments if value.nil? raise GraphQL::ExecutionError, "`null` is not a valid input for `#{to_type_signature}`, please provide a value for this argument." end of_type.coerce_input(value, ctx) end def coerce_result(value, ctx) of_type.coerce_result(value, ctx) end # This is for implementing introspection def description nil end end end end graphql-ruby-2.2.17/lib/graphql/schema/null_mask.rb000066400000000000000000000002561476434635200222170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # @api private module NullMask def self.call(member, ctx) false end end end end graphql-ruby-2.2.17/lib/graphql/schema/object.rb000066400000000000000000000107231476434635200215000ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/null_context" module GraphQL class Schema class Object < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasFields extend GraphQL::Schema::Member::HasInterfaces # @return [Object] the application object this type is wrapping attr_reader :object # @return [GraphQL::Query::Context] the context instance for this query attr_reader :context # @return [GraphQL::Dataloader] def dataloader context.dataloader end # Call this in a field method to return a value that should be returned to the client # without any further handling by GraphQL. def raw_value(obj) GraphQL::Execution::Interpreter::RawValue.new(obj) end class << self # This is protected so that we can be sure callers use the public method, {.authorized_new} # @see authorized_new to make instances protected :new def wrap_scoped(object, context) scoped_new(object, context) end # This is called by the runtime to return an object to call methods on. def wrap(object, context) authorized_new(object, context) end # Make a new instance of this type _if_ the auth check passes, # otherwise, raise an error. # # Probably only the framework should call this method. # # This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?` # hook returns some lazy value (like a Promise). # # The reason that the auth check is in this wrapper method instead of {.new} is because # of how it might return a Promise. It would be weird if `.new` returned a promise; # It would be a headache to try to maintain Promise-y state inside a {Schema::Object} # instance. So, hopefully this wrapper method will do the job. # # @param object [Object] The thing wrapped by this object # @param context [GraphQL::Query::Context] # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy] # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false` def authorized_new(object, context) maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do begin authorized?(object, context) rescue GraphQL::UnauthorizedError => err context.schema.unauthorized_object(err) rescue StandardError => err context.query.handle_or_reraise(err) end end auth_val = if context.schema.lazy?(maybe_lazy_auth_val) GraphQL::Execution::Lazy.new do context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do context.schema.sync_lazy(maybe_lazy_auth_val) end end else maybe_lazy_auth_val end context.query.after_lazy(auth_val) do |is_authorized| if is_authorized self.new(object, context) else # It failed the authorization check, so go to the schema's authorized object hook err = GraphQL::UnauthorizedError.new(object: object, type: self, context: context) # If a new value was returned, wrap that instead of the original value begin new_obj = context.schema.unauthorized_object(err) if new_obj self.new(new_obj, context) else nil end end end end end def scoped_new(object, context) self.new(object, context) end end def initialize(object, context) @object = object @context = context end class << self # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`. # It should help with debugging and bug tracker integrations. def const_missing(name) if name == :InvalidNullError custom_err_class = GraphQL::InvalidNullError.subclass_for(self) const_set(:InvalidNullError, custom_err_class) custom_err_class else super end end def kind GraphQL::TypeKinds::OBJECT end end end end end graphql-ruby-2.2.17/lib/graphql/schema/printer.rb000066400000000000000000000063351476434635200217210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Used to convert your {GraphQL::Schema} to a GraphQL schema string # # @example print your schema to standard output (via helper) # puts GraphQL::Schema::Printer.print_schema(MySchema) # # @example print your schema to standard output # puts GraphQL::Schema::Printer.new(MySchema).print_schema # # @example print a single type to standard output # class Types::Query < GraphQL::Schema::Object # description "The query root of this schema" # # field :post, Types::Post, null: true # end # # class Types::Post < GraphQL::Schema::Object # description "A blog post" # # field :id, ID, null: false # field :title, String, null: false # field :body, String, null: false # end # # class MySchema < GraphQL::Schema # query(Types::Query) # end # # printer = GraphQL::Schema::Printer.new(MySchema) # puts printer.print_type(Types::Post) # class Printer < GraphQL::Language::Printer attr_reader :schema, :warden # @param schema [GraphQL::Schema] # @param context [Hash] # @param introspection [Boolean] Should include the introspection types in the string? def initialize(schema, context: nil, introspection: false) @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, context: context, include_introspection_types: introspection, ) @document = @document_from_schema.document @schema = schema end # Return the GraphQL schema string for the introspection type system def self.print_introspection_schema query_root = Class.new(GraphQL::Schema::Object) do graphql_name "Root" field :throwaway_field, String def self.visible?(ctx) false end end schema = Class.new(GraphQL::Schema) { query(query_root) def self.visible?(member, _ctx) member.graphql_name != "Root" end } introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, include_introspection_types: true, include_built_in_directives: true, ).document introspection_schema_ast.to_query_string(printer: IntrospectionPrinter.new) end # Return a GraphQL schema string for the defined types in the schema # @param schema [GraphQL::Schema] # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] def self.print_schema(schema, **args) printer = new(schema, **args) printer.print_schema end # Return a GraphQL schema string for the defined types in the schema def print_schema print(@document) + "\n" end def print_type(type) node = @document_from_schema.build_type_definition_node(type) print(node) end class IntrospectionPrinter < GraphQL::Language::Printer def print_schema_definition(schema) print_string("schema {\n query: Root\n}") end end end end end graphql-ruby-2.2.17/lib/graphql/schema/relay_classic_mutation.rb000066400000000000000000000045211476434635200247660ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/string" module GraphQL class Schema # Mutations that extend this base class get some conventions added for free: # # - An argument called `clientMutationId` is _always_ added, but it's not passed # to the resolve method. The value is re-inserted to the response. (It's for # client libraries to manage optimistic updates.) # - The returned object type always has a field called `clientMutationId` to support that. # - The mutation accepts one argument called `input`, `argument`s defined in the mutation # class are added to that input object, which is generated by the mutation. # # These conventions were first specified by Relay Classic, but they come in handy: # # - `clientMutationId` supports optimistic updates and cache rollbacks on the client # - using a single `input:` argument makes it easy to post whole JSON objects to the mutation # using one GraphQL variable (`$input`) instead of making a separate variable for each argument. # # @see {GraphQL::Schema::Mutation} for an example, it's basically the same. # class RelayClassicMutation < GraphQL::Schema::Mutation include GraphQL::Schema::HasSingleInputArgument argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false # The payload should always include this field field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.") # Relay classic default: null(true) # Override {GraphQL::Schema::Resolver#resolve_with_support} to # delete `client_mutation_id` from the kwargs. def resolve_with_support(**inputs) input = inputs[:input].to_kwargs if input # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are. input_kwargs = input.to_h client_mutation_id = input_kwargs.delete(:client_mutation_id) inputs[:input] = input_kwargs end return_value = super(**inputs) context.query.after_lazy(return_value) do |return_hash| # It might be an error if return_hash.is_a?(Hash) return_hash[:client_mutation_id] = client_mutation_id end return_hash end end end end end graphql-ruby-2.2.17/lib/graphql/schema/resolver.rb000066400000000000000000000373631476434635200221040ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/resolver/has_payload_type" module GraphQL class Schema # A class-based container for field configuration and resolution logic. It supports: # # - Arguments, via `.argument(...)` helper, which will be applied to the field. # - Return type, via `.type(..., null: ...)`, which will be applied to the field. # - Description, via `.description(...)`, which will be applied to the field # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field. # - `#object` and `#context` accessors for use during `#resolve`. # # Resolvers can be attached with the `resolver:` option in a `field(...)` call. # # A resolver's configuration may be overridden with other keywords in the `field(...)` call. # # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`. # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function` class Resolver include Schema::Member::GraphQLTypeNames # Really we only need description from here, but: extend Schema::Member::BaseDSLMethods extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasValidators include Schema::Member::HasPath extend Schema::Member::HasPath # @param object [Object] The application object that this field is being resolved on # @param context [GraphQL::Query::Context] # @param field [GraphQL::Schema::Field] def initialize(object:, context:, field:) @object = object @context = context @field = field # Since this hash is constantly rebuilt, cache it for this call @arguments_by_keyword = {} self.class.arguments(context).each do |name, arg| @arguments_by_keyword[arg.keyword] = arg end @prepared_arguments = nil end # @return [Object] The application object this field is being resolved on attr_reader :object # @return [GraphQL::Query::Context] attr_reader :context # @return [GraphQL::Dataloader] def dataloader context.dataloader end # @return [GraphQL::Schema::Field] attr_reader :field def arguments @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)") end # This method is _actually_ called by the runtime, # it does some preparation and then eventually calls # the user-defined `#resolve` method. # @api private def resolve_with_support(**args) # First call the ready? hook which may raise raw_ready_val = if args.any? ready?(**args) else ready? end context.query.after_lazy(raw_ready_val) do |ready_val| if ready_val.is_a?(Array) is_ready, ready_early_return = ready_val if is_ready != false raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]" else ready_early_return end elsif ready_val # Then call each prepare hook, which may return a different value # for that argument, or may return a lazy object load_arguments_val = load_arguments(args) context.query.after_lazy(load_arguments_val) do |loaded_args| @prepared_arguments = loaded_args Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field) # Then call `authorized?`, which may raise or may return a lazy object raw_authorized_val = if loaded_args.any? authorized?(**loaded_args) else authorized? end context.query.after_lazy(raw_authorized_val) do |authorized_val| # If the `authorized?` returned two values, `false, early_return`, # then use the early return value instead of continuing if authorized_val.is_a?(Array) authorized_result, early_return = authorized_val if authorized_result == false early_return else raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]" end elsif authorized_val # Finally, all the hooks have passed, so resolve it if loaded_args.any? public_send(self.class.resolve_method, **loaded_args) else public_send(self.class.resolve_method) end else raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field) end end end end end end # Do the work. Everything happens here. # @return [Object] An object corresponding to the return type def resolve(**args) raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic" end # Called before arguments are prepared. # Implement this hook to make checks before doing any work. # # If it returns a lazy object (like a promise), it will be synced by GraphQL # (but the resulting value won't be used). # # @param args [Hash] The input arguments, if there are any # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def ready?(**args) true end # Called after arguments are loaded, but before resolving. # # Override it to check everything before calling the mutation. # @param inputs [Hash] The input arguments # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def authorized?(**inputs) arg_owner = @field # || self.class args = arg_owner.arguments(context) authorize_arguments(args, inputs) end # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type. # # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}. # # Any value returned here will be used _instead of_ of the loaded object. # @param err [GraphQL::UnauthorizedError] def unauthorized_object(err) raise err end private def authorize_arguments(args, inputs) args.each_value do |argument| arg_keyword = argument.keyword if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value) auth_result = argument.authorized?(self, arg_value, context) if auth_result.is_a?(Array) # only return this second value if the application returned a second value arg_auth, err = auth_result if !arg_auth return arg_auth, err end elsif auth_result == false return auth_result end else true end end end def load_arguments(args) prepared_args = {} prepare_lazies = [] args.each do |key, value| arg_defn = @arguments_by_keyword[key] if arg_defn prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context) if context.schema.lazy?(prepped_value) prepare_lazies << context.query.after_lazy(prepped_value) do |finished_prepped_value| prepared_args[key] = finished_prepped_value end end else # these are `extras:` prepared_args[key] = value end end # Avoid returning a lazy if none are needed if prepare_lazies.any? GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args } else prepared_args end end def get_argument(name, context = GraphQL::Query::NullContext.instance) self.class.get_argument(name, context) end class << self def field_arguments(context = GraphQL::Query::NullContext.instance) arguments(context) end def any_field_arguments? any_arguments? end def get_field_argument(name, context = GraphQL::Query::NullContext.instance) get_argument(name, context) end def all_field_argument_definitions all_argument_definitions end # Default `:resolve` set below. # @return [Symbol] The method to call on instances of this object to resolve the field def resolve_method(new_method = nil) if new_method @resolve_method = new_method end @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve) end # Additional info injected into {#resolve} # @see {GraphQL::Schema::Field#extras} def extras(new_extras = nil) if new_extras @own_extras = new_extras end own_extras = @own_extras || [] own_extras + (superclass.respond_to?(:extras) ? superclass.extras : []) end # If `true` (default), then the return type for this resolver will be nullable. # If `false`, then the return type is non-null. # # @see #type which sets the return type of this field and accepts a `null:` option # @param allow_null [Boolean] Whether or not the response can be null def null(allow_null = nil) if !allow_null.nil? @null = allow_null end @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null end def resolver_method(new_method_name = nil) if new_method_name @resolver_method = new_method_name else @resolver_method || :resolve_with_support end end # Call this method to get the return type of the field, # or use it as a configuration method to assign a return type # instead of generating one. # TODO unify with {#null} # @param new_type [Class, Array, nil] If a type definition class is provided, it will be used as the return type of the field # @param null [true, false] Whether or not the field may return `nil` # @return [Class] The type which this field returns. def type(new_type = nil, null: nil) if new_type if null.nil? raise ArgumentError, "required argument `null:` is missing" end @type_expr = new_type @null = null else if type_expr GraphQL::Schema::Member::BuildType.parse_type(type_expr, null: self.null) elsif superclass.respond_to?(:type) superclass.type else nil end end end # Specifies the complexity of the field. Defaults to `1` # @return [Integer, Proc] def complexity(new_complexity = nil) if new_complexity @complexity = new_complexity end @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1) end def broadcastable(new_broadcastable) @broadcastable = new_broadcastable end # @return [Boolean, nil] def broadcastable? if defined?(@broadcastable) @broadcastable else (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil) end end # Get or set the `max_page_size:` which will be configured for fields using this resolver # (`nil` means "unlimited max page size".) # @param max_page_size [Integer, nil] Set a new value # @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver def max_page_size(new_max_page_size = NOT_CONFIGURED) if new_max_page_size != NOT_CONFIGURED @max_page_size = new_max_page_size elsif defined?(@max_page_size) @max_page_size elsif superclass.respond_to?(:max_page_size) superclass.max_page_size else nil end end # @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size` def has_max_page_size? (!!defined?(@max_page_size)) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?) end # Get or set the `default_page_size:` which will be configured for fields using this resolver # (`nil` means "unlimited default page size".) # @param default_page_size [Integer, nil] Set a new value # @return [Integer, nil] The `default_page_size` assigned to fields that use this resolver def default_page_size(new_default_page_size = NOT_CONFIGURED) if new_default_page_size != NOT_CONFIGURED @default_page_size = new_default_page_size elsif defined?(@default_page_size) @default_page_size elsif superclass.respond_to?(:default_page_size) superclass.default_page_size else nil end end # @return [Boolean] `true` if this resolver or a superclass has an assigned `default_page_size` def has_default_page_size? (!!defined?(@default_page_size)) || (superclass.respond_to?(:has_default_page_size?) && superclass.has_default_page_size?) end # A non-normalized type configuration, without `null` applied def type_expr @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil) end # Add an argument to this field's signature, but # also add some preparation hook methods which will be used for this argument # @see {GraphQL::Schema::Argument#initialize} for the signature def argument(*args, **kwargs, &block) # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation # so that we can support `#load_{x}` methods below. super(*args, from_resolver: true, **kwargs) end # Registers new extension # @param extension [Class] Extension class # @param options [Hash] Optional extension options def extension(extension, **options) @own_extensions ||= [] @own_extensions << {extension => options} end # @api private def extensions own_exts = @own_extensions # Jump through some hoops to avoid creating arrays when we don't actually need them if superclass.respond_to?(:extensions) s_exts = superclass.extensions if own_exts if s_exts.any? own_exts + s_exts else own_exts end else s_exts end else own_exts || EMPTY_ARRAY end end private def own_extensions @own_extensions end end end end end graphql-ruby-2.2.17/lib/graphql/schema/resolver/000077500000000000000000000000001476434635200215435ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/resolver/has_payload_type.rb000066400000000000000000000073661476434635200254310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Resolver # Adds `field(...)` helper to resolvers so that they can # generate payload types. # # Or, an already-defined one can be attached with `payload_type(...)`. module HasPayloadType # Call this method to get the derived return type of the mutation, # or use it as a configuration method to assign a return type # instead of generating one. # @param new_payload_type [Class, nil] If a type definition class is provided, it will be used as the return type of the mutation field # @return [Class] The object type which this mutation returns. def payload_type(new_payload_type = nil) if new_payload_type @payload_type = new_payload_type end @payload_type ||= generate_payload_type end def type(new_type = nil, null: nil) if new_type payload_type(new_type) if !null.nil? self.null(null) end else super() end end alias :type_expr :payload_type def field_class(new_class = nil) if new_class @field_class = new_class elsif defined?(@field_class) && @field_class @field_class else find_inherited_value(:field_class, GraphQL::Schema::Field) end end # An object class to use for deriving return types # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object} # @return [Class] def object_class(new_class = nil) if new_class if defined?(@payload_type) raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition." end @object_class = new_class else @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object) end end NO_INTERFACES = [].freeze def field(*args, **kwargs, &block) pt = payload_type # make sure it's initialized with any inherited fields field_defn = super # Remove any inherited fields to avoid false conflicts at runtime prev_fields = pt.own_fields[field_defn.graphql_name] case prev_fields when GraphQL::Schema::Field if prev_fields.owner != self pt.own_fields.delete(field_defn.graphql_name) end when Array prev_fields.reject! { |f| f.owner != self } if prev_fields.empty? pt.own_fields.delete(field_defn.graphql_name) end end pt.add_field(field_defn, method_conflict_warning: false) field_defn end private # Build a subclass of {.object_class} based on `self`. # This value will be cached as `{.payload_type}`. # Override this hook to customize return type generation. def generate_payload_type resolver_name = graphql_name resolver_fields = all_field_definitions pt = Class.new(object_class) pt.graphql_name("#{resolver_name}Payload") pt.description("Autogenerated return type of #{resolver_name}.") resolver_fields.each do |f| # Reattach the already-defined field here # (The field's `.owner` will still point to the mutation, not the object type, I think) # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead. pt.add_field(f, method_conflict_warning: false) end pt end end end end end graphql-ruby-2.2.17/lib/graphql/schema/scalar.rb000066400000000000000000000036771476434635200215110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Scalar < GraphQL::Schema::Member extend GraphQL::Schema::Member::ValidatesInput class << self def coerce_input(val, ctx) val end def coerce_result(val, ctx) val end def kind GraphQL::TypeKinds::SCALAR end def specified_by_url(new_url = nil) if new_url directive(GraphQL::Schema::Directive::SpecifiedBy, url: new_url) elsif (directive = directives.find { |dir| dir.graphql_name == "specifiedBy" }) directive.arguments[:url] # rubocop:disable Development/ContextIsPassedCop elsif superclass.respond_to?(:specified_by_url) superclass.specified_by_url else nil end end def default_scalar(is_default = nil) if !is_default.nil? @default_scalar = is_default end @default_scalar end def default_scalar? @default_scalar ||= false end def validate_non_null_input(value, ctx, max_errors: nil) coerced_result = begin coerce_input(value, ctx) rescue GraphQL::CoercionError => err err rescue StandardError => err ctx.query.handle_or_reraise(err) end if coerced_result.nil? str_value = if value == Float::INFINITY "" else " #{GraphQL::Language.serialize(value)}" end Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}") elsif coerced_result.is_a?(GraphQL::CoercionError) Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions) else nil end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/subscription.rb000066400000000000000000000134311476434635200227550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This class can be extended to create fields on your subscription root. # # It provides hooks for the different parts of the subscription lifecycle: # # - `#authorized?`: called before initial subscription and subsequent updates # - `#subscribe`: called for the initial subscription # - `#update`: called for subsequent update # # Also, `#unsubscribe` terminates the subscription. class Subscription < GraphQL::Schema::Resolver extend GraphQL::Schema::Resolver::HasPayloadType extend GraphQL::Schema::Member::HasFields NO_UPDATE = :no_update # The generated payload type is required; If there's no payload, # propagate null. null false def initialize(object:, context:, field:) super # Figure out whether this is an update or an initial subscription @mode = context.query.subscription_update? ? :update : :subscribe end def resolve_with_support(**args) result = nil unsubscribed = true unsubscribed_result = catch :graphql_subscription_unsubscribed do result = super unsubscribed = false end if unsubscribed if unsubscribed_result context.namespace(:subscriptions)[:final_update] = true unsubscribed_result else context.skip end else result end end # Implement the {Resolve} API def resolve(**args) # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever # have an unexpected `@mode` public_send("resolve_#{@mode}", **args) end # Wrap the user-defined `#subscribe` hook def resolve_subscribe(**args) ret_val = args.any? ? subscribe(**args) : subscribe if ret_val == :no_response context.skip else ret_val end end # The default implementation returns nothing on subscribe. # Override it to return an object or # `:no_response` to (explicitly) return nothing. def subscribe(args = {}) :no_response end # Wrap the user-provided `#update` hook def resolve_update(**args) ret_val = args.any? ? update(**args) : update if ret_val == NO_UPDATE context.namespace(:subscriptions)[:no_update] = true context.skip else ret_val end end # The default implementation returns the root object. # Override it to return {NO_UPDATE} if you want to # skip updates sometimes. Or override it to return a different object. def update(args = {}) object end # If an argument is flagged with `loads:` and no object is found for it, # remove this subscription (assuming that the object was deleted in the meantime, # or that it became inaccessible). def load_application_object_failed(err) if @mode == :update unsubscribe end super end # Call this to halt execution and remove this subscription from the system # @param update_value [Object] if present, deliver this update before unsubscribing # @return [void] def unsubscribe(update_value = nil) context.namespace(:subscriptions)[:unsubscribed] = true throw :graphql_subscription_unsubscribed, update_value end READING_SCOPE = ::Object.new # Call this method to provide a new subscription_scope; OR # call it without an argument to get the subscription_scope # @param new_scope [Symbol] # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription. # @return [Symbol] def self.subscription_scope(new_scope = READING_SCOPE, optional: false) if new_scope != READING_SCOPE @subscription_scope = new_scope @subscription_scope_optional = optional elsif defined?(@subscription_scope) @subscription_scope else find_inherited_value(:subscription_scope) end end def self.subscription_scope_optional? if defined?(@subscription_scope_optional) @subscription_scope_optional else find_inherited_value(:subscription_scope_optional, false) end end # This is called during initial subscription to get a "name" for this subscription. # Later, when `.trigger` is called, this will be called again to build another "name". # Any subscribers with matching topic will begin the update flow. # # The default implementation creates a string using the field name, subscription scope, and argument keys and values. # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers. # # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope. # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an # update should be filtered out. # # @see {#update} for how to skip updates when an event comes with a matching topic. # @param arguments [Hash Object>] The arguments for this topic, in GraphQL-style (camelized strings) # @param field [GraphQL::Schema::Field] # @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions). # @return [String] An identifier corresponding to a stream of updates def self.topic_for(arguments:, field:, scope:) Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments]) end end end end graphql-ruby-2.2.17/lib/graphql/schema/timeout.rb000066400000000000000000000107671476434635200217300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This plugin will stop resolving new fields after `max_seconds` have elapsed. # After the time has passed, any remaining fields will be `nil`, with errors added # to the `errors` key. Any already-resolved fields will be in the `data` key, so # you'll get a partial response. # # You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout` # to provide custom logic when a timeout error occurs. # # Note that this will stop a query _in between_ field resolutions, but # it doesn't interrupt long-running `resolve` functions. Be sure to use # timeout options for external connections. For more info, see # www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ # # @example Stop resolving fields after 2 seconds # class MySchema < GraphQL::Schema # use GraphQL::Schema::Timeout, max_seconds: 2 # end # # @example Notifying Bugsnag and logging a timeout # class MyTimeout < GraphQL::Schema::Timeout # def handle_timeout(error, query) # Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}") # Bugsnag.notify(error, {query_string: query.query_string}) # end # end # # class MySchema < GraphQL::Schema # use MyTimeout, max_seconds: 2 # end # class Timeout def self.use(schema, max_seconds: nil) timeout = self.new(max_seconds: max_seconds) schema.trace_with(self::Trace, timeout: timeout) end def initialize(max_seconds:) @max_seconds = max_seconds end module Trace # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields def initialize(timeout:, **rest) @timeout = timeout super end def execute_multiplex(multiplex:) multiplex.queries.each do |query| timeout_duration_s = @timeout.max_seconds(query) timeout_state = if timeout_duration_s == false # if the method returns `false`, don't apply a timeout false else now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) timeout_at = now + (timeout_duration_s * 1000) { timeout_at: timeout_at, timed_out: false } end query.context.namespace(@timeout)[:state] = timeout_state end super end def execute_field(query:, field:, **_rest) timeout_state = query.context.namespace(@timeout).fetch(:state) # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query. if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at) error = GraphQL::Schema::Timeout::TimeoutError.new(field) # Only invoke the timeout callback for the first timeout if !timeout_state[:timed_out] timeout_state[:timed_out] = true @timeout.handle_timeout(error, query) end error else super end end end # Called at the start of each query. # The default implementation returns the `max_seconds:` value from installing this plugin. # # @param query [GraphQL::Query] The query that's about to run # @return [Numeric, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout. def max_seconds(query) @max_seconds end # Invoked when a query times out. # @param error [GraphQL::Schema::Timeout::TimeoutError] # @param query [GraphQL::Error] def handle_timeout(error, query) # override to do something interesting end # This error is raised when a query exceeds `max_seconds`. # Since it's a child of {GraphQL::ExecutionError}, # its message will be added to the response's `errors` key. # # To raise an error that will stop query resolution, use a custom block # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError}, # such as `RuntimeError`. class TimeoutError < GraphQL::ExecutionError def initialize(field) super("Timeout on #{field.path}") end end end end end graphql-ruby-2.2.17/lib/graphql/schema/type_expression.rb000066400000000000000000000031211476434635200234640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # @api private module TypeExpression # Fetch a type from a type map by its AST specification. # Return `nil` if not found. # @param type_owner [#get_type] A thing for looking up types by name # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List] def self.build_type(type_owner, ast_node) case ast_node when GraphQL::Language::Nodes::TypeName type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware when GraphQL::Language::Nodes::NonNullType ast_inner_type = ast_node.of_type inner_type = build_type(type_owner, ast_inner_type) wrap_type(inner_type, :to_non_null_type) when GraphQL::Language::Nodes::ListType ast_inner_type = ast_node.of_type inner_type = build_type(type_owner, ast_inner_type) wrap_type(inner_type, :to_list_type) else raise "Invariant: unexpected type from ast: #{ast_node.inspect}" end end class << self private def wrap_type(type, wrapper_method) if type.nil? nil elsif wrapper_method == :to_list_type || wrapper_method == :to_non_null_type type.public_send(wrapper_method) else raise ArgumentError, "Unexpected wrapper method: #{wrapper_method.inspect}" end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/type_membership.rb000066400000000000000000000033731476434635200234310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This class joins an object type to an abstract type (interface or union) of which # it is a member. class TypeMembership # @return [Class] attr_accessor :object_type # @return [Class, Module] attr_reader :abstract_type # @return [Hash] attr_reader :options # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types} # or {Schema::Object.implements} (for interfaces). # # @param abstract_type [Class, Module] # @param object_type [Class] # @param options [Hash] Any options passed to `.possible_types` or `.implements` def initialize(abstract_type, object_type, **options) @abstract_type = abstract_type @object_type = object_type @options = options end # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type} def visible?(ctx) warden = Warden.from_context(ctx) (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) && (@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true) end def graphql_name "#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}" end def path graphql_name end def inspect "#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>" end alias :type_class :itself end end end graphql-ruby-2.2.17/lib/graphql/schema/union.rb000066400000000000000000000063521476434635200213650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Union < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasUnresolvedTypeError class << self def inherited(child_class) add_unresolved_type_error(child_class) super end def possible_types(*types, context: GraphQL::Query::NullContext.instance, **options) if types.any? types.each do |t| assert_valid_union_member(t) type_memberships << type_membership_class.new(self, t, **options) end else visible_types = [] warden = Warden.from_context(context) type_memberships.each do |type_membership| if warden.visible_type_membership?(type_membership, context) visible_types << type_membership.object_type end end visible_types end end def all_possible_types type_memberships.map(&:object_type) end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end def kind GraphQL::TypeKinds::UNION end def type_memberships @type_memberships ||= [] end # Update a type membership whose `.object_type` is a string or late-bound type # so that the type membership's `.object_type` is the given `object_type`. # (This is used for updating the union after the schema as lazily loaded the union member.) # @api private def assign_type_membership_object_type(object_type) assert_valid_union_member(object_type) type_memberships.each { |tm| possible_type = tm.object_type if possible_type.is_a?(String) && (possible_type == object_type.name) # This is a match of Ruby class names, not graphql names, # since strings are used to refer to constants. tm.object_type = object_type elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == object_type.graphql_name tm.object_type = object_type end } nil end private def assert_valid_union_member(type_defn) case type_defn when Class if !type_defn.kind.object? raise ArgumentError, "Union possible_types can only be object types (not #{type_defn.kind.name}, #{type_defn.inspect})" end when Module # it's an interface type, defined as a module raise ArgumentError, "Union possible_types can only be object types (not interface types), remove #{type_defn.graphql_name} (#{type_defn.inspect})" when String, GraphQL::Schema::LateBoundType # Ok - assume it will get checked later else raise ArgumentError, "Union possible_types can only be class-based GraphQL types (not #{type_defn.inspect} (#{type_defn.class.name}))." end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/unique_within_type.rb000066400000000000000000000021211476434635200241540ustar00rootroot00000000000000# frozen_string_literal: true require "base64" module GraphQL class Schema module UniqueWithinType class << self attr_accessor :default_id_separator end self.default_id_separator = "-" module_function # @param type_name [String] # @param object_value [Any] # @return [String] a unique, opaque ID generated as a function of the two inputs def encode(type_name, object_value, separator: self.default_id_separator) object_value_str = object_value.to_s if type_name.include?(separator) raise "encode(#{type_name}, #{object_value_str}) contains reserved characters `#{separator}` in the type name" end Base64.strict_encode64([type_name, object_value_str].join(separator)) end # @param node_id [String] A unique ID generated by {.encode} # @return [Array<(String, String)>] The type name & value passed to {.encode} def decode(node_id, separator: self.default_id_separator) GraphQL::Schema::Base64Encoder.decode(node_id).split(separator, 2) end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator.rb000066400000000000000000000157021476434635200222210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # The thing being validated # @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class] attr_reader :validated # @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class] The argument or argument owner this validator is attached to # @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation # @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation def initialize(validated:, allow_blank: false, allow_null: false) @validated = validated @allow_blank = allow_blank @allow_null = allow_null end # @param object [Object] The application object that this argument's field is being resolved for # @param context [GraphQL::Query::Context] # @param value [Object] The client-provided value for this argument (after parsing and coercing by the input type) # @return [nil, Array, String] Error message or messages to add def validate(object, context, value) raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate" end # This is like `String#%`, but it supports the case that only some of `string`'s # values are present in `substitutions` def partial_format(string, substitutions) substitutions.each do |key, value| sub_v = value.is_a?(String) ? value : value.to_s string = string.gsub("%{#{key}}", sub_v) end string end # @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true` def permitted_empty_value?(value) (value.nil? && @allow_null) || (@allow_blank && value.respond_to?(:blank?) && value.blank?) end # @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class] # @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:` # @return [Array] def self.from_config(schema_member, validates_hash) if validates_hash.nil? || validates_hash.empty? EMPTY_ARRAY else validates_hash = validates_hash.dup default_options = {} if validates_hash[:allow_null] default_options[:allow_null] = validates_hash.delete(:allow_null) end if validates_hash[:allow_blank] default_options[:allow_blank] = validates_hash.delete(:allow_blank) end # allow_nil or allow_blank are the _only_ validations: if validates_hash.empty? validates_hash = default_options end validates_hash.map do |validator_name, options| validator_class = case validator_name when Class validator_name else all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}") end if options.is_a?(Hash) validator_class.new(validated: schema_member, **(default_options.merge(options))) else validator_class.new(options, validated: schema_member, **default_options) end end end end # Add `validator_class` to be initialized when `validates:` is given `name`. # (It's initialized with whatever options are given by the key `name`). # @param name [Symbol] # @param validator_class [Class] # @return [void] def self.install(name, validator_class) all_validators[name] = validator_class nil end # Remove whatever validator class is {.install}ed at `name`, if there is one # @param name [Symbol] # @return [void] def self.uninstall(name) all_validators.delete(name) nil end class << self attr_accessor :all_validators end self.all_validators = {} include GraphQL::EmptyObjects class ValidationFailedError < GraphQL::ExecutionError attr_reader :errors def initialize(errors:) @errors = errors super(errors.join(", ")) end end # @param validators [Array] # @param object [Object] # @param context [Query::Context] # @param value [Object] # @return [void] # @raises [ValidationFailedError] def self.validate!(validators, object, context, value, as: nil) # Assuming the default case is no errors, reduce allocations in that case. # This will be replaced with a mutable array if we actually get any errors. all_errors = EMPTY_ARRAY validators.each do |validator| validated = as || validator.validated errors = validator.validate(object, context, value) if errors && (errors.is_a?(Array) && errors != EMPTY_ARRAY) || (errors.is_a?(String)) if all_errors.frozen? # It's empty all_errors = [] end interpolation_vars = { validated: validated.graphql_name, value: value.inspect } if errors.is_a?(String) all_errors << (errors % interpolation_vars) else errors = errors.map { |e| e % interpolation_vars } all_errors.concat(errors) end end end if all_errors.any? raise ValidationFailedError.new(errors: all_errors) end nil end end end end require "graphql/schema/validator/length_validator" GraphQL::Schema::Validator.install(:length, GraphQL::Schema::Validator::LengthValidator) require "graphql/schema/validator/numericality_validator" GraphQL::Schema::Validator.install(:numericality, GraphQL::Schema::Validator::NumericalityValidator) require "graphql/schema/validator/format_validator" GraphQL::Schema::Validator.install(:format, GraphQL::Schema::Validator::FormatValidator) require "graphql/schema/validator/inclusion_validator" GraphQL::Schema::Validator.install(:inclusion, GraphQL::Schema::Validator::InclusionValidator) require "graphql/schema/validator/exclusion_validator" GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator) require "graphql/schema/validator/required_validator" GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator) require "graphql/schema/validator/allow_null_validator" GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator) require "graphql/schema/validator/allow_blank_validator" GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator) graphql-ruby-2.2.17/lib/graphql/schema/validator/000077500000000000000000000000001476434635200216675ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/schema/validator/allow_blank_validator.rb000066400000000000000000000017001476434635200265440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to specifically reject values that respond to `.blank?` and respond truthy for that method. # # @example Require a non-empty string for an argument # argument :name, String, required: true, validate: { allow_blank: false } class AllowBlankValidator < Validator def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options) @message = message super(**default_options) @allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank end def validate(_object, _context, value) if value.respond_to?(:blank?) && value.blank? if (value.nil? && @allow_null) || @allow_blank # pass else @message end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/allow_null_validator.rb000066400000000000000000000015221476434635200264310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to specifically reject or permit `nil` values (given as `null` from GraphQL). # # @example require a non-null value for an argument if it is provided # argument :name, String, required: false, validates: { allow_null: false } class AllowNullValidator < Validator MESSAGE = "%{validated} can't be null" def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options) @message = message super(**default_options) @allow_null = allow_null.nil? ? allow_null_positional : allow_null end def validate(_object, _context, value) if value.nil? && !@allow_null @message end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/exclusion_validator.rb000066400000000000000000000017021476434635200262720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to specifically reject values from an argument. # # @example disallow certain values # # argument :favorite_non_prime, Integer, required: true, # validates: { exclusion: { in: [2, 3, 5, 7, ... ]} } # class ExclusionValidator < Validator # @param message [String] # @param in [Array] The values to reject def initialize(message: "%{validated} is reserved", in:, **default_options) # `in` is a reserved word, so work around that @in_list = binding.local_variable_get(:in) @message = message super(**default_options) end def validate(_object, _context, value) if permitted_empty_value?(value) # pass elsif @in_list.include?(value) @message end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/format_validator.rb000066400000000000000000000026601476434635200255550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to assert that string values match (or don't match) the given RegExp. # # @example requiring input to match a pattern # # argument :handle, String, required: true, # validates: { format: { with: /\A[a-z0-9_]+\Z/ } } # # @example reject inputs that match a pattern # # argument :word_that_doesnt_begin_with_a_vowel, String, required: true, # validates: { format: { without: /\A[aeiou]/ } } # # # It's pretty hard to come up with a legitimate use case for `without:` # class FormatValidator < Validator # @param with [RegExp, nil] # @param without [Regexp, nil] # @param message [String] def initialize( with: nil, without: nil, message: "%{validated} is invalid", **default_options ) @with_pattern = with @without_pattern = without @message = message super(**default_options) end def validate(_object, _context, value) if permitted_empty_value?(value) # Do nothing elsif value.nil? || (@with_pattern && !value.match?(@with_pattern)) || (@without_pattern && value.match?(@without_pattern)) @message end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/inclusion_validator.rb000066400000000000000000000021141476434635200262620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # You can use this to allow certain values for an argument. # # Usually, a {GraphQL::Schema::Enum} is better for this, because it's self-documenting. # # @example only allow certain values for an argument # # argument :favorite_prime, Integer, required: true, # validates: { inclusion: { in: [2, 3, 5, 7, 11, ... ] } } # class InclusionValidator < Validator # @param message [String] # @param in [Array] The values to allow def initialize(in:, message: "%{validated} is not included in the list", **default_options) # `in` is a reserved word, so work around that @in_list = binding.local_variable_get(:in) @message = message super(**default_options) end def validate(_object, _context, value) if permitted_empty_value?(value) # pass elsif !@in_list.include?(value) @message end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/length_validator.rb000066400000000000000000000047451476434635200255540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to enforce a `.length` restriction on incoming values. It works for both Strings and Lists. # # @example Allow no more than 10 IDs # # argument :ids, [ID], required: true, validates: { length: { maximum: 10 } } # # @example Require three selections # # argument :ice_cream_preferences, [ICE_CREAM_FLAVOR], required: true, validates: { length: { is: 3 } } # class LengthValidator < Validator # @param maximum [Integer] # @param too_long [String] Used when `maximum` is exceeded or value is greater than `within` # @param minimum [Integer] # @param too_short [String] Used with value is less than `minimum` or less than `within` # @param is [Integer] Exact length requirement # @param wrong_length [String] Used when value doesn't match `is` # @param within [Range] An allowed range (becomes `minimum:` and `maximum:` under the hood) # @param message [String] def initialize( maximum: nil, too_long: "%{validated} is too long (maximum is %{count})", minimum: nil, too_short: "%{validated} is too short (minimum is %{count})", is: nil, within: nil, wrong_length: "%{validated} is the wrong length (should be %{count})", message: nil, **default_options ) if within && (minimum || maximum) raise ArgumentError, "`length: { ... }` may include `within:` _or_ `minimum:`/`maximum:`, but not both" end # Under the hood, `within` is decomposed into `minimum` and `maximum` @maximum = maximum || (within && within.max) @too_long = message || too_long @minimum = minimum || (within && within.min) @too_short = message || too_short @is = is @wrong_length = message || wrong_length super(**default_options) end def validate(_object, _context, value) return if permitted_empty_value?(value) # pass in this case length = value.nil? ? 0 : value.length if @maximum && length > @maximum partial_format(@too_long, { count: @maximum }) elsif @minimum && length < @minimum partial_format(@too_short, { count: @minimum }) elsif @is && length != @is partial_format(@wrong_length, { count: @is }) end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/numericality_validator.rb000066400000000000000000000067511476434635200267770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to assert numerical comparisons hold true for inputs. # # @example Require a number between 0 and 1 # # argument :batting_average, Float, required: true, validates: { numericality: { within: 0..1 } } # # @example Require the number 42 # # argument :the_answer, Integer, required: true, validates: { numericality: { equal_to: 42 } } # # @example Require a real number # # argument :items_count, Integer, required: true, validates: { numericality: { greater_than_or_equal_to: 0 } } # class NumericalityValidator < Validator # @param greater_than [Integer] # @param greater_than_or_equal_to [Integer] # @param less_than [Integer] # @param less_than_or_equal_to [Integer] # @param equal_to [Integer] # @param other_than [Integer] # @param odd [Boolean] # @param even [Boolean] # @param within [Range] # @param message [String] used for all validation failures def initialize( greater_than: nil, greater_than_or_equal_to: nil, less_than: nil, less_than_or_equal_to: nil, equal_to: nil, other_than: nil, odd: nil, even: nil, within: nil, message: "%{validated} must be %{comparison} %{target}", null_message: Validator::AllowNullValidator::MESSAGE, **default_options ) @greater_than = greater_than @greater_than_or_equal_to = greater_than_or_equal_to @less_than = less_than @less_than_or_equal_to = less_than_or_equal_to @equal_to = equal_to @other_than = other_than @odd = odd @even = even @within = within @message = message @null_message = null_message super(**default_options) end def validate(object, context, value) if permitted_empty_value?(value) # pass in this case elsif value.nil? # @allow_null is handled in the parent class @null_message elsif @greater_than && value <= @greater_than partial_format(@message, { comparison: "greater than", target: @greater_than }) elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to }) elsif @less_than && value >= @less_than partial_format(@message, { comparison: "less than", target: @less_than }) elsif @less_than_or_equal_to && value > @less_than_or_equal_to partial_format(@message, { comparison: "less than or equal to", target: @less_than_or_equal_to }) elsif @equal_to && value != @equal_to partial_format(@message, { comparison: "equal to", target: @equal_to }) elsif @other_than && value == @other_than partial_format(@message, { comparison: "something other than", target: @other_than }) elsif @even && !value.even? (partial_format(@message, { comparison: "even", target: "" })).strip elsif @odd && !value.odd? (partial_format(@message, { comparison: "odd", target: "" })).strip elsif @within && !@within.include?(value) partial_format(@message, { comparison: "within", target: @within }) end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/validator/required_validator.rb000066400000000000000000000056561476434635200261150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Validator # Use this validator to require _one_ of the named arguments to be present. # Or, use Arrays of symbols to name a valid _set_ of arguments. # # (This is for specifying mutually exclusive sets of arguments.) # # @example Require exactly one of these arguments # # field :update_amount, IngredientAmount, null: false do # argument :ingredient_id, ID, required: true # argument :cups, Integer, required: false # argument :tablespoons, Integer, required: false # argument :teaspoons, Integer, required: false # validates required: { one_of: [:cups, :tablespoons, :teaspoons] } # end # # @example Require one of these _sets_ of arguments # # field :find_object, Node, null: true do # argument :node_id, ID, required: false # argument :object_type, String, required: false # argument :object_id, Integer, required: false # # either a global `node_id` or an `object_type`/`object_id` pair is required: # validates required: { one_of: [:node_id, [:object_type, :object_id]] } # end # # @example require _some_ value for an argument, even if it's null # field :update_settings, AccountSettings do # # `required: :nullable` means this argument must be given, but may be `null` # argument :age, Integer, required: :nullable # end # class RequiredValidator < Validator # @param one_of [Symbol, Array] An argument, or a list of arguments, that represents a valid set of inputs for this field # @param message [String] def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options) @one_of = if one_of one_of elsif argument [argument] else raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`" end @message = message super(**default_options) end def validate(_object, _context, value) matched_conditions = 0 if !value.nil? @one_of.each do |one_of_condition| case one_of_condition when Symbol if value.key?(one_of_condition) matched_conditions += 1 end when Array if one_of_condition.all? { |k| value.key?(k) } matched_conditions += 1 break end else raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}" end end end if matched_conditions == 1 nil # OK else @message end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/warden.rb000066400000000000000000000446431476434635200215220ustar00rootroot00000000000000# frozen_string_literal: true require 'set' module GraphQL class Schema # Restrict access to a {GraphQL::Schema} with a user-defined `visible?` implementations. # # When validating and executing a query, all access to schema members # should go through a warden. If you access the schema directly, # you may show a client something that it shouldn't be allowed to see. # # @api private class Warden def self.from_context(context) context.warden || PassThruWarden rescue NoMethodError # this might be a hash which won't respond to #warden PassThruWarden end # @param visibility_method [Symbol] a Warden method to call for this entry # @param entry [Object, Array] One or more definitions for a given name in a GraphQL Schema # @param context [GraphQL::Query::Context] # @param warden [Warden] # @return [Object] `entry` or one of `entry`'s items if exactly one of them is visible for this context # @return [nil] If neither `entry` nor any of `entry`'s items are visible for this context def self.visible_entry?(visibility_method, entry, context, warden = Warden.from_context(context)) if entry.is_a?(Array) visible_item = nil entry.each do |item| if warden.public_send(visibility_method, item, context) if visible_item.nil? visible_item = item else raise DuplicateNamesError.new( duplicated_name: item.path, duplicated_definition_1: visible_item.inspect, duplicated_definition_2: item.inspect ) end end end visible_item elsif warden.public_send(visibility_method, entry, context) entry else nil end end # This is used when a caller provides a Hash for context. # We want to call the schema's hooks, but we don't have a full-blown warden. # The `context` arguments to these methods exist purely to simplify the code that # calls methods on this object, so it will have everything it needs. class PassThruWarden class << self def visible_field?(field, ctx); field.visible?(ctx); end def visible_argument?(arg, ctx); arg.visible?(ctx); end def visible_type?(type, ctx); type.visible?(ctx); end def visible_enum_value?(ev, ctx); ev.visible?(ctx); end def visible_type_membership?(tm, ctx); tm.visible?(ctx); end def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end def arguments(owner, ctx); owner.arguments(ctx); end def loadable?(type, ctx); type.visible?(ctx); end end end class NullWarden def initialize(_filter = nil, context:, schema:) @schema = schema end def visible_field?(field_defn, _ctx = nil, owner = nil); true; end def visible_argument?(arg_defn, _ctx = nil); true; end def visible_type?(type_defn, _ctx = nil); true; end def visible_enum_value?(enum_value, _ctx = nil); true; end def visible_type_membership?(type_membership, _ctx = nil); true; end def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end def get_type(type_name); @schema.get_type(type_name); end # rubocop:disable Development/ContextIsPassedCop def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end def enum_values(enum_defn); enum_defn.enum_values; end # rubocop:disable Development/ContextIsPassedCop def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end def directives; @schema.directives.values; end def fields(type_defn); type_defn.all_field_definitions; end # rubocop:disable Development/ContextIsPassedCop def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end def reachable_type?(type_name); true; end def loadable?(type, _ctx); true; end def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop def possible_types(type_defn); @schema.possible_types(type_defn); end def interfaces(obj_type); obj_type.interfaces; end end # @param context [GraphQL::Query::Context] # @param schema [GraphQL::Schema] def initialize(context:, schema:) @schema = schema # Cache these to avoid repeated hits to the inheritance chain when one isn't present @query = @schema.query @mutation = @schema.mutation @subscription = @schema.subscription @context = context @visibility_cache = read_through { |m| schema.visible?(m, context) } @visibility_cache.compare_by_identity # Initialize all ivars to improve object shape consistency: @types = @visible_types = @reachable_types = @visible_parent_fields = @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays = @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships = @visible_and_reachable_type = @unions = @unfiltered_interfaces = @reachable_type_set = nil end # @return [Hash] Visible types in the schema def types @types ||= begin vis_types = {} @schema.types(@context).each do |n, t| if visible_and_reachable_type?(t) vis_types[n] = t end end vis_types end end # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden. def loadable?(type, _ctx) !reachable_type_set.include?(type) && visible_type?(type) end # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`) def get_type(type_name) @visible_types ||= read_through do |name| type_defn = @schema.get_type(name, @context) if type_defn && visible_and_reachable_type?(type_defn) type_defn else nil end end @visible_types[type_name] end # @return [Array] Visible and reachable types in the schema def reachable_types @reachable_types ||= reachable_type_set.to_a end # @return Boolean True if the type is visible and reachable in the schema def reachable_type?(type_name) type = get_type(type_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware type && reachable_type_set.include?(type) end # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists def get_field(parent_type, field_name) @visible_parent_fields ||= read_through do |type| read_through do |f_name| field_defn = @schema.get_field(type, f_name, @context) if field_defn && visible_field?(field_defn, nil, type) field_defn else nil end end end @visible_parent_fields[parent_type][field_name] end # @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible def get_argument(parent_type, argument_name) argument = parent_type.get_argument(argument_name, @context) return argument if argument && visible_argument?(argument, @context) end # @return [Array] The types which may be member of `type_defn` def possible_types(type_defn) @visible_possible_types ||= read_through { |type_defn| pt = @schema.possible_types(type_defn, @context) pt.select { |t| visible_and_reachable_type?(t) } } @visible_possible_types[type_defn] end # @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] # @return [Array] Fields on `type_defn` def fields(type_defn) @visible_fields ||= read_through { |t| @schema.get_fields(t, @context).values } @visible_fields[type_defn] end # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType] # @return [Array] Visible arguments on `argument_owner` def arguments(argument_owner, ctx = nil) @visible_arguments ||= read_through { |o| args = o.arguments(@context) if args.any? args = args.values args.select! { |a| visible_argument?(a, @context) } args else EmptyObjects::EMPTY_ARRAY end } @visible_arguments[argument_owner] end # @return [Array] Visible members of `enum_defn` def enum_values(enum_defn) @visible_enum_arrays ||= read_through { |e| values = e.enum_values(@context) if values.size == 0 raise GraphQL::Schema::Enum::MissingValuesError.new(e) end values } @visible_enum_arrays[enum_defn] end def visible_enum_value?(enum_value, _ctx = nil) @visible_enum_values ||= read_through { |ev| visible?(ev) } @visible_enum_values[enum_value] end # @return [Array] Visible interfaces implemented by `obj_type` def interfaces(obj_type) @visible_interfaces ||= read_through { |t| ints = t.interfaces(@context) if ints.any? ints.select! { |i| visible_type?(i) } end ints } @visible_interfaces[obj_type] end def directives @schema.directives.each_value.select { |d| visible?(d) } end def root_type_for_operation(op_name) root_type = @schema.root_type_for_operation(op_name) if root_type && visible?(root_type) root_type else nil end end # @param owner [Class, Module] If provided, confirm that field has the given owner. def visible_field?(field_defn, _ctx = nil, owner = field_defn.owner) # This field is visible in its own right visible?(field_defn) && # This field's return type is visible visible_and_reachable_type?(field_defn.type.unwrap) && # This field is either defined on this object type, # or the interface it's inherited from is also visible ((field_defn.respond_to?(:owner) && field_defn.owner == owner) || field_on_visible_interface?(field_defn, owner)) end def visible_argument?(arg_defn, _ctx = nil) visible?(arg_defn) && visible_and_reachable_type?(arg_defn.type.unwrap) end def visible_type?(type_defn, _ctx = nil) @type_visibility ||= read_through { |type_defn| visible?(type_defn) } @type_visibility[type_defn] end def visible_type_membership?(type_membership, _ctx = nil) visible?(type_membership) end def interface_type_memberships(obj_type, _ctx = nil) @type_memberships ||= read_through do |obj_t| obj_t.interface_type_memberships end @type_memberships[obj_type] end private def visible_and_reachable_type?(type_defn) @visible_and_reachable_type ||= read_through do |type_defn| next false unless visible_type?(type_defn) next true if root_type?(type_defn) || type_defn.introspection? if type_defn.kind.union? possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn)) elsif type_defn.kind.interface? if possible_types(type_defn).any? true else if @context.respond_to?(:logger) && (logger = @context.logger) logger.debug { "Interface `#{type_defn.graphql_name}` hidden because it has no visible implementors" } end false end else if referenced?(type_defn) true elsif type_defn.kind.object? # Show this object if it belongs to ... interfaces(type_defn).any? { |t| referenced?(t) } || # an interface which is referenced in the schema union_memberships(type_defn).any? { |t| referenced?(t) || orphan_type?(t) } # or a union which is referenced or added via orphan_types else false end end end @visible_and_reachable_type[type_defn] end def union_memberships(obj_type) @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } } @unions[obj_type] end # We need this to tell whether a field was inherited by an interface # even when that interface is hidden from `#interfaces` def unfiltered_interfaces(type_defn) @unfiltered_interfaces ||= read_through(&:interfaces) @unfiltered_interfaces[type_defn] end # If this field was inherited from an interface, and the field on that interface is _hidden_, # then treat this inherited field as hidden. # (If it _wasn't_ inherited, then don't hide it for this reason.) def field_on_visible_interface?(field_defn, type_defn) if type_defn.kind.object? any_interface_has_field = false any_interface_has_visible_field = false ints = unfiltered_interfaces(type_defn) ints.each do |interface_type| if (iface_field_defn = interface_type.get_field(field_defn.graphql_name, @context)) any_interface_has_field = true if interfaces(type_defn).include?(interface_type) && visible_field?(iface_field_defn, nil, interface_type) any_interface_has_visible_field = true end end end if any_interface_has_field any_interface_has_visible_field else # it's the object's own field true end else true end end def root_type?(type_defn) @query == type_defn || @mutation == type_defn || @subscription == type_defn end def referenced?(type_defn) graphql_name = type_defn.unwrap.graphql_name members = @schema.references_to(graphql_name) members.any? { |m| visible?(m) } end def orphan_type?(type_defn) @schema.orphan_types.include?(type_defn) end def visible?(member) @visibility_cache[member] end def read_through h = Hash.new { |h, k| h[k] = yield(k) } h.compare_by_identity h end def reachable_type_set return @reachable_type_set if @reachable_type_set @reachable_type_set = Set.new rt_hash = {} unvisited_types = [] ['query', 'mutation', 'subscription'].each do |op_name| root_type = root_type_for_operation(op_name) unvisited_types << root_type if root_type end unvisited_types.concat(@schema.introspection_system.types.values) directives.each do |dir_class| arguments(dir_class).each do |arg_defn| arg_t = arg_defn.type.unwrap if get_type(arg_t.graphql_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware unvisited_types << arg_t end end end @schema.orphan_types.each do |orphan_type| if get_type(orphan_type.graphql_name) == orphan_type # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware unvisited_types << orphan_type end end included_interface_possible_types_set = Set.new until unvisited_types.empty? type = unvisited_types.pop visit_type(type, unvisited_types, @reachable_type_set, rt_hash, included_interface_possible_types_set, include_interface_possible_types: false) end @reachable_type_set end def visit_type(type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types:) if visited_type_set.add?(type) || (include_interface_possible_types && type.kind.interface? && included_interface_possible_types_set.add?(type)) type_by_name = type_by_name_hash[type.graphql_name] ||= type if type_by_name != type name_1, name_2 = [type.inspect, type_by_name.inspect].sort raise DuplicateNamesError.new( duplicated_name: type.graphql_name, duplicated_definition_1: name_1, duplicated_definition_2: name_2 ) end if type.kind.input_object? # recurse into visible arguments arguments(type).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end elsif type.kind.union? # recurse into visible possible types possible_types(type).each do |possible_type| unvisited_types << possible_type end elsif type.kind.fields? if type.kind.object? # recurse into visible implemented interfaces interfaces(type).each do |interface| unvisited_types << interface end elsif include_interface_possible_types possible_types(type).each do |pt| unvisited_types << pt end end # Don't visit interface possible types -- it's not enough to justify visibility # recurse into visible fields fields(type).each do |field| field_type = field.type.unwrap # In this case, if it's an interface, we want to include visit_type(field_type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types: true) # recurse into visible arguments arguments(field).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end end end end end end end end graphql-ruby-2.2.17/lib/graphql/schema/wrapper.rb000066400000000000000000000010071476434635200217050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Wrapper include GraphQL::Schema::Member::TypeSystemHelpers # @return [Class, Module] The inner type of this wrapping type, the type of which one or more objects may be present. attr_reader :of_type def initialize(of_type) @of_type = of_type end def unwrap @of_type.unwrap end def ==(other) self.class == other.class && of_type == other.of_type end end end end graphql-ruby-2.2.17/lib/graphql/static_validation.rb000066400000000000000000000011701476434635200224670ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/static_validation/error" require "graphql/static_validation/definition_dependencies" require "graphql/static_validation/validator" require "graphql/static_validation/validation_context" require "graphql/static_validation/validation_timeout_error" require "graphql/static_validation/literal_validator" require "graphql/static_validation/base_visitor" rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__) Dir.glob(rules_glob).each do |file| require(file) end require "graphql/static_validation/all_rules" require "graphql/static_validation/interpreter_visitor" graphql-ruby-2.2.17/lib/graphql/static_validation/000077500000000000000000000000001476434635200221435ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/static_validation/all_rules.rb000066400000000000000000000041031476434635200244500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Default rules for {GraphQL::StaticValidation::Validator} # # Order is important here. Some validators skip later hooks. # which stops the visit on that node. That way it doesn't try to find fields on types that # don't exist, etc. ALL_RULES = [ GraphQL::StaticValidation::NoDefinitionsArePresent, GraphQL::StaticValidation::DirectivesAreDefined, GraphQL::StaticValidation::DirectivesAreInValidLocations, GraphQL::StaticValidation::UniqueDirectivesPerLocation, GraphQL::StaticValidation::OperationNamesAreValid, GraphQL::StaticValidation::FragmentNamesAreUnique, GraphQL::StaticValidation::FragmentsAreFinite, GraphQL::StaticValidation::FragmentsAreNamed, GraphQL::StaticValidation::FragmentsAreUsed, GraphQL::StaticValidation::FragmentTypesExist, GraphQL::StaticValidation::FragmentsAreOnCompositeTypes, GraphQL::StaticValidation::FragmentSpreadsArePossible, GraphQL::StaticValidation::FieldsAreDefinedOnType, GraphQL::StaticValidation::FieldsWillMerge, GraphQL::StaticValidation::FieldsHaveAppropriateSelections, GraphQL::StaticValidation::ArgumentsAreDefined, GraphQL::StaticValidation::ArgumentLiteralsAreCompatible, GraphQL::StaticValidation::RequiredArgumentsArePresent, GraphQL::StaticValidation::RequiredInputObjectAttributesArePresent, GraphQL::StaticValidation::ArgumentNamesAreUnique, GraphQL::StaticValidation::VariableNamesAreUnique, GraphQL::StaticValidation::VariablesAreInputTypes, GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped, GraphQL::StaticValidation::VariablesAreUsedAndDefined, GraphQL::StaticValidation::VariableUsagesAreAllowed, GraphQL::StaticValidation::MutationRootExists, GraphQL::StaticValidation::QueryRootExists, GraphQL::StaticValidation::SubscriptionRootExists, GraphQL::StaticValidation::InputObjectNamesAreUnique, GraphQL::StaticValidation::OneOfInputObjectsAreValid, ] end end graphql-ruby-2.2.17/lib/graphql/static_validation/base_visitor.rb000066400000000000000000000135021476434635200251620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class BaseVisitor < GraphQL::Language::StaticVisitor def initialize(document, context) @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @context = context @schema = context.schema super(document) end attr_reader :context # @return [Array] Types whose scope we've entered attr_reader :object_types # @return [Array] The nesting of the current position in the AST def path @path.dup end # Build a class to visit the AST and perform validation, # or use a pre-built class if rules is `ALL_RULES` or empty. # @param rules [Array] # @return [Class] A class for validating `rules` during visitation def self.including_rules(rules) if rules.empty? # It's not doing _anything?!?_ BaseVisitor elsif rules == ALL_RULES InterpreterVisitor else visitor_class = Class.new(self) do include(GraphQL::StaticValidation::DefinitionDependencies) end rules.reverse_each do |r| # If it's a class, it gets attached later. if !r.is_a?(Class) visitor_class.include(r) end end visitor_class.include(ContextMethods) visitor_class end end module ContextMethods def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) push_type(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") super @object_types.pop @path.pop end def on_fragment_definition(node, parent) on_fragment_with_type(node) do @path.push("fragment #{node.name}") super end end def on_inline_fragment(node, parent) on_fragment_with_type(node) do @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}") super end end def on_field(node, parent) parent_type = @object_types.last field_definition = @schema.get_field(parent_type, node.name, @context.query.context) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap push_type(next_object_type) else push_type(nil) end @path.push(node.alias || node.name) super @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) directive_defn = @context.schema_directives[node.name] @directive_definitions.push(directive_defn) super @directive_definitions.pop end def on_argument(node, parent) argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? @context.warden.get_argument(arg_type, node.name) else nil end elsif (directive_defn = @directive_definitions.last) @context.warden.get_argument(directive_defn, node.name) elsif (field_defn = @field_definitions.last) @context.warden.get_argument(field_defn, node.name) else nil end @argument_definitions.push(argument_defn) @path.push(node.name) super @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) @path.push("... #{node.name}") super @path.pop end def on_input_object(node, parent) arg_defn = @argument_definitions.last if arg_defn && arg_defn.type.list? @path.push(parent.children.index(node)) super @path.pop else super end end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition # Don't get the _last_ one because that's the current one. # Get the second-to-last one, which is the parent of the current one. @argument_definitions[-2] end private def on_fragment_with_type(node) object_type = if node.type @context.warden.get_type(node.type.name) else @object_types.last end push_type(object_type) yield(node) @object_types.pop @path.pop end def push_type(t) @object_types.push(t) end end private def add_error(error, path: nil) if @context.too_many_errors? throw :too_many_validation_errors end error.path ||= (path || @path.dup) context.errors << error end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/definition_dependencies.rb000066400000000000000000000177171476434635200273430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Track fragment dependencies for operations # and expose the fragment definitions which # are used by a given operation module DefinitionDependencies attr_reader :dependencies def initialize(*) super @defdep_node_paths = {} # { name => [node, ...] } pairs for fragments (although duplicate-named fragments are _invalid_, they are _possible_) @defdep_fragment_definitions = Hash.new{ |h, k| h[k] = [] } # This tracks dependencies from fragment to Node where it was used # { fragment_definition_name => [dependent_node, dependent_node]} @defdep_dependent_definitions = Hash.new { |h, k| h[k] = Set.new } # First-level usages of spreads within definitions # (When a key has an empty list as its value, # we can resolve that key's dependents) # { definition_node => [node, node ...] } @defdep_immediate_dependencies = Hash.new { |h, k| h[k] = Set.new } # When we encounter a spread, # this node is the one who depends on it @defdep_current_parent = nil end def on_document(node, parent) node.definitions.each do |definition| if definition.is_a? GraphQL::Language::Nodes::FragmentDefinition @defdep_fragment_definitions[definition.name] << definition end end super @dependencies = dependency_map { |defn, spreads, frag| context.on_dependency_resolve_handlers.each { |h| h.call(defn, spreads, frag) } } end def on_operation_definition(node, prev_node) @defdep_node_paths[node.name] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_definition(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_spread(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) # Track both sides of the dependency @defdep_dependent_definitions[node.name] << @defdep_current_parent @defdep_immediate_dependencies[@defdep_current_parent] << node super end # A map of operation definitions to an array of that operation's dependencies # @return [DependencyMap] def dependency_map(&block) @dependency_map ||= resolve_dependencies(&block) end # Map definition AST nodes to the definition AST nodes they depend on. # Expose circular dependencies. class DependencyMap # @return [Array] attr_reader :cyclical_definitions # @return [Hash>] attr_reader :unmet_dependencies # @return [Array] attr_reader :unused_dependencies def initialize @dependencies = Hash.new { |h, k| h[k] = [] } @cyclical_definitions = [] @unmet_dependencies = Hash.new { |h, k| h[k] = [] } @unused_dependencies = [] end # @return [Array] dependencies for `definition_node` def [](definition_node) @dependencies[definition_node] end end class NodeWithPath extend Forwardable attr_reader :node, :path def initialize(node, path) @node = node @path = path end def_delegators :@node, :name, :eql?, :hash end private # Return a hash of { node => [node, node ... ]} pairs # Keys are top-level definitions # Values are arrays of flattened dependencies def resolve_dependencies dependency_map = DependencyMap.new # Don't allow the loop to run more times # than the number of fragments in the document max_loops = 0 @defdep_fragment_definitions.each_value do |v| max_loops += v.size end loops = 0 # Instead of tracking independent fragments _as you visit_, # determine them at the end. This way, we can treat fragments with the # same name as if they were the same name. If _any_ of the fragments # with that name has a dependency, we record it. independent_fragment_nodes = @defdep_fragment_definitions.values.flatten - @defdep_immediate_dependencies.keys visited_fragment_names = Set.new while fragment_node = independent_fragment_nodes.pop if visited_fragment_names.add?(fragment_node.name) # this is a new fragment name else # this is a duplicate fragment name next end loops += 1 if loops > max_loops raise("Resolution loops exceeded the number of definitions; infinite loop detected. (Max: #{max_loops}, Current: #{loops})") end # Since it's independent, let's remove it from here. # That way, we can use the remainder to identify cycles @defdep_immediate_dependencies.delete(fragment_node) fragment_usages = @defdep_dependent_definitions[fragment_node.name] if fragment_usages.empty? # If we didn't record any usages during the visit, # then this fragment is unused. dependency_map.unused_dependencies << @defdep_node_paths[fragment_node] else fragment_usages.each do |definition_node| # Register the dependency AND second-order dependencies dependency_map[definition_node] << fragment_node dependency_map[definition_node].concat(dependency_map[fragment_node]) # Since we've registered it, remove it from our to-do list deps = @defdep_immediate_dependencies[definition_node] # Can't find a way to _just_ delete from `deps` and return the deleted entries removed, remaining = deps.partition { |spread| spread.name == fragment_node.name } @defdep_immediate_dependencies[definition_node] = remaining if block_given? yield(definition_node, removed, fragment_node) end if remaining.empty? && definition_node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && definition_node.name != fragment_node.name # If all of this definition's dependencies have # been resolved, we can now resolve its # own dependents. # # But, it's possible to have a duplicate-named fragment here. # Skip it in that case independent_fragment_nodes << definition_node end end end end # If any dependencies were _unmet_ # (eg, spreads with no corresponding definition) # then they're still in there @defdep_immediate_dependencies.each do |defn_node, deps| deps.each do |spread| if !@defdep_fragment_definitions.key?(spread.name) dependency_map.unmet_dependencies[@defdep_node_paths[defn_node]] << @defdep_node_paths[spread] deps.delete(spread) end end if deps.empty? @defdep_immediate_dependencies.delete(defn_node) end end # Anything left in @immediate_dependencies is cyclical cyclical_nodes = @defdep_immediate_dependencies.keys.map { |n| @defdep_node_paths[n] } # @immediate_dependencies also includes operation names, but we don't care about # those. They became nil when we looked them up on `@fragment_definitions`, so remove them. cyclical_nodes.compact! dependency_map.cyclical_definitions.concat(cyclical_nodes) dependency_map end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/error.rb000066400000000000000000000022171476434635200236230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Generates GraphQL-compliant validation message. class Error # Convenience for validators module ErrorHelper # Error `error_message` is located at `node` def error(error_message, nodes, context: nil, path: nil, extensions: {}) path ||= context.path nodes = Array(nodes) GraphQL::StaticValidation::Error.new(error_message, nodes: nodes, path: path) end end attr_reader :message attr_accessor :path def initialize(message, path: nil, nodes: []) @message = message @nodes = Array(nodes) @path = path end # A hash representation of this Message def to_h { "message" => message, "locations" => locations }.tap { |h| h["path"] = path unless path.nil? } end attr_reader :nodes private def locations nodes.map do |node| h = {"line" => node.line, "column" => node.col} h["filename"] = node.filename if node.filename h end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/interpreter_visitor.rb000066400000000000000000000004701476434635200266130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class InterpreterVisitor < BaseVisitor include(GraphQL::StaticValidation::DefinitionDependencies) StaticValidation::ALL_RULES.reverse_each do |r| include(r) end include(ContextMethods) end end end graphql-ruby-2.2.17/lib/graphql/static_validation/literal_validator.rb000066400000000000000000000134421476434635200261750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Test whether `ast_value` is a valid input for `type` class LiteralValidator def initialize(context:) @context = context @warden = context.warden @invalid_response = GraphQL::Query::InputValidationResult.new(valid: false, problems: []) @valid_response = GraphQL::Query::InputValidationResult.new(valid: true, problems: []) end def validate(ast_value, type) catch(:invalid) do recursively_validate(ast_value, type) end end private def replace_nulls_in(ast_value) case ast_value when Array ast_value.map { |v| replace_nulls_in(v) } when GraphQL::Language::Nodes::InputObject ast_value.to_h when GraphQL::Language::Nodes::NullValue nil else ast_value end end def recursively_validate(ast_value, type) if type.nil? # this means we're an undefined argument, see #present_input_field_values_are_valid maybe_raise_if_invalid(ast_value) do @invalid_response end elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue) maybe_raise_if_invalid(ast_value) do type.kind.non_null? ? @invalid_response : @valid_response end elsif type.kind.non_null? maybe_raise_if_invalid(ast_value) do ast_value.nil? ? @invalid_response : recursively_validate(ast_value, type.of_type) end elsif type.kind.list? item_type = type.of_type results = ensure_array(ast_value).map { |val| recursively_validate(val, item_type) } merge_results(results) elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) @valid_response elsif type.kind.scalar? && constant_scalar?(ast_value) maybe_raise_if_invalid(ast_value) do ruby_value = replace_nulls_in(ast_value) type.validate_input(ruby_value, @context) end elsif type.kind.enum? maybe_raise_if_invalid(ast_value) do if ast_value.is_a?(GraphQL::Language::Nodes::Enum) type.validate_input(ast_value.name, @context) else # if our ast_value isn't an Enum it's going to be invalid so return false @invalid_response end end elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject) maybe_raise_if_invalid(ast_value) do merge_results([ required_input_fields_are_present(type, ast_value), present_input_field_values_are_valid(type, ast_value) ]) end else maybe_raise_if_invalid(ast_value) do @invalid_response end end end # When `error_bubbling` is false, we want to bail on the first failure that we find. # Use `throw` to escape the current call stack, returning the invalid response. def maybe_raise_if_invalid(ast_value) ret = yield if !@context.schema.error_bubbling && !ret.valid? throw(:invalid, ret) else ret end end # The GraphQL grammar supports variables embedded within scalars but graphql.js # doesn't support it so we won't either for simplicity def constant_scalar?(ast_value) if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) false elsif ast_value.is_a?(Array) ast_value.all? { |element| constant_scalar?(element) } elsif ast_value.is_a?(GraphQL::Language::Nodes::InputObject) ast_value.arguments.all? { |arg| constant_scalar?(arg.value) } else true end end def required_input_fields_are_present(type, ast_node) # TODO - would be nice to use these to create an error message so the caller knows # that required fields are missing required_field_names = @warden.arguments(type) .select { |argument| argument.type.kind.non_null? && !argument.default_value? } .map!(&:name) present_field_names = ast_node.arguments.map(&:name) missing_required_field_names = required_field_names - present_field_names if @context.schema.error_bubbling missing_required_field_names.empty? ? @valid_response : @invalid_response else results = missing_required_field_names.map do |name| arg_type = @warden.get_argument(type, name).type recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type) end if type.one_of? && ast_node.arguments.size != 1 results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})") end merge_results(results) end end def present_input_field_values_are_valid(type, ast_node) results = ast_node.arguments.map do |value| field = @warden.get_argument(type, value.name) # we want to call validate on an argument even if it's an invalid one # so that our raise exception is on it instead of the entire InputObject field_type = field && field.type recursively_validate(value.value, field_type) end merge_results(results) end def ensure_array(value) value.is_a?(Array) ? value : [value] end def merge_results(results_list) merged_result = Query::InputValidationResult.new results_list.each do |inner_result| merged_result.merge_result!([], inner_result) end merged_result end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/000077500000000000000000000000001476434635200232755ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb000066400000000000000000000045361476434635200324010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentLiteralsAreCompatible def on_argument(node, parent) # Check the child arguments first; # don't add a new error if one of them reports an error super # Don't validate variables here if node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) return end if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path } parent_defn = parent_definition(parent) if parent_defn && (arg_defn = context.warden.get_argument(parent_defn, node.name)) validation_result = context.validate_literal(node.value, arg_defn.type) if !validation_result.valid? kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) string_value = if node.value == Float::INFINITY "" else " (#{GraphQL::Language::Printer.new.print(node.value)})" end problems = validation_result.problems first_problem = problems && problems.first if first_problem message = first_problem["message"] # This is some legacy stuff from when `CoercionError` was raised thru the stack if message coerce_extensions = first_problem["extensions"] || { "code" => "argumentLiteralsIncompatible" } end end error_options = { nodes: parent, type: kind_of_node, argument_name: node.name, argument: arg_defn, value: node.value } if coerce_extensions error_options[:coerce_extensions] = coerce_extensions end message ||= "Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value#{string_value}. Expected type '#{arg_defn.type.to_type_signature}'." error = GraphQL::StaticValidation::ArgumentLiteralsAreCompatibleError.new( message, **error_options ) add_error(error) end end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb000066400000000000000000000025711476434635200336070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentLiteralsAreCompatibleError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name attr_reader :argument attr_reader :value def initialize(message, path: nil, nodes: [], type:, argument_name: nil, extensions: nil, coerce_extensions: nil, argument: nil, value: nil) super(message, path: path, nodes: nodes) @type_name = type @argument_name = argument_name @extensions = extensions @coerce_extensions = coerce_extensions @argument = argument @value = value end # A hash representation of this Message def to_h if @coerce_extensions extensions = @coerce_extensions # This is for legacy compat -- but this key is supposed to be a GraphQL type name :confounded: extensions["typeName"] = "CoercionError" else extensions = { "code" => code, "typeName" => type_name } if argument_name extensions["argumentName"] = argument_name end end extensions.merge!(@extensions) unless @extensions.nil? super.merge({ "extensions" => extensions }) end def code "argumentLiteralsIncompatible" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/argument_names_are_unique.rb000066400000000000000000000015641476434635200310520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentNamesAreUnique include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) validate_arguments(node) super end def on_directive(node, parent) validate_arguments(node) super end def validate_arguments(node) argument_defns = node.arguments if argument_defns.any? args_by_name = Hash.new { |h, k| h[k] = [] } argument_defns.each { |a| args_by_name[a.name] << a } args_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: defns, name: name)) end end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb000066400000000000000000000011271476434635200322560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "argumentNotUnique" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/arguments_are_defined.rb000066400000000000000000000042211476434635200301330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentsAreDefined def on_argument(node, parent) parent_defn = parent_definition(parent) if parent_defn && context.warden.get_argument(parent_defn, node.name) super elsif parent_defn kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new( "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", nodes: node, name: error_arg_name, type: kind_of_node, argument_name: node.name, parent: parent_defn )) else # Some other weird error super end end private # TODO smell: these methods are added to all visitors, since they're included in a module. def parent_name(parent, type_defn) case parent when GraphQL::Language::Nodes::Field parent.alias || parent.name when GraphQL::Language::Nodes::InputObject type_defn.graphql_name when GraphQL::Language::Nodes::Argument, GraphQL::Language::Nodes::Directive parent.name else raise "Invariant: Unexpected parent #{parent.inspect} (#{parent.class})" end end def node_type(parent) parent.class.name.split("::").last end def parent_definition(parent) case parent when GraphQL::Language::Nodes::InputObject arg_defn = context.argument_definition if arg_defn.nil? nil else arg_ret_type = arg_defn.type.unwrap if arg_ret_type.kind.input_object? arg_ret_type else nil end end when GraphQL::Language::Nodes::Directive context.schema_directives[parent.name] when GraphQL::Language::Nodes::Field context.field_definition else raise "Unexpected argument parent: #{parent.class} (##{parent})" end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/arguments_are_defined_error.rb000066400000000000000000000015651476434635200313540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentsAreDefinedError < StaticValidation::Error attr_reader :name attr_reader :type_name attr_reader :argument_name attr_reader :parent def initialize(message, path: nil, nodes: [], name:, type:, argument_name:, parent:) super(message, path: path, nodes: nodes) @name = name @type_name = type @argument_name = argument_name @parent = parent end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name, "typeName" => type_name, "argumentName" => argument_name } super.merge({ "extensions" => extensions }) end def code "argumentNotAccepted" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/directives_are_defined.rb000066400000000000000000000014471476434635200302760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreDefined def initialize(*) super @directive_names = context.warden.directives.map(&:graphql_name) end def on_directive(node, parent) if !@directive_names.include?(node.name) @directives_are_defined_errors_by_name ||= {} error = @directives_are_defined_errors_by_name[node.name] ||= begin err = GraphQL::StaticValidation::DirectivesAreDefinedError.new( "Directive @#{node.name} is not defined", nodes: [], directive: node.name ) add_error(err) err end error.nodes << node else super end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/directives_are_defined_error.rb000066400000000000000000000012061476434635200315000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreDefinedError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "undefinedDirective" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb000066400000000000000000000054121476434635200325340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreInValidLocations include GraphQL::Language def on_directive(node, parent) validate_location(node, parent, context.schema_directives) super end private LOCATION_MESSAGE_NAMES = { GraphQL::Schema::Directive::QUERY => "queries", GraphQL::Schema::Directive::MUTATION => "mutations", GraphQL::Schema::Directive::SUBSCRIPTION => "subscriptions", GraphQL::Schema::Directive::FIELD => "fields", GraphQL::Schema::Directive::FRAGMENT_DEFINITION => "fragment definitions", GraphQL::Schema::Directive::FRAGMENT_SPREAD => "fragment spreads", GraphQL::Schema::Directive::INLINE_FRAGMENT => "inline fragments", } SIMPLE_LOCATIONS = { Nodes::Field => GraphQL::Schema::Directive::FIELD, Nodes::InlineFragment => GraphQL::Schema::Directive::INLINE_FRAGMENT, Nodes::FragmentSpread => GraphQL::Schema::Directive::FRAGMENT_SPREAD, Nodes::FragmentDefinition => GraphQL::Schema::Directive::FRAGMENT_DEFINITION, } SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys def validate_location(ast_directive, ast_parent, directives) directive_defn = directives[ast_directive.name] case ast_parent when Nodes::OperationDefinition required_location = GraphQL::Schema::Directive.const_get(ast_parent.operation_type.upcase) assert_includes_location(directive_defn, ast_directive, required_location) when *SIMPLE_LOCATION_NODES required_location = SIMPLE_LOCATIONS[ast_parent.class] assert_includes_location(directive_defn, ast_directive, required_location) else add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "Directives can't be applied to #{ast_parent.class.name}s", nodes: ast_directive, target: ast_parent.class.name )) end end def assert_includes_location(directive_defn, directive_ast, required_location) if !directive_defn.locations.include?(required_location) location_name = LOCATION_MESSAGE_NAMES[required_location] allowed_location_names = directive_defn.locations.map { |loc| LOCATION_MESSAGE_NAMES[loc] } add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "'@#{directive_defn.graphql_name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", nodes: directive_ast, target: location_name, name: directive_defn.graphql_name )) end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb000066400000000000000000000013511476434635200337430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreInValidLocationsError < StaticValidation::Error attr_reader :target_name attr_reader :name def initialize(message, path: nil, nodes: [], target:, name: nil) super(message, path: path, nodes: nodes) @target_name = target @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "targetName" => target_name }.tap { |h| h["name"] = name unless name.nil? } super.merge({ "extensions" => extensions }) end def code "directiveCannotBeApplied" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb000066400000000000000000000017321476434635200311350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FieldsAreDefinedOnType def on_field(node, parent) parent_type = @object_types[-2] field = context.warden.get_field(parent_type, node.name) if field.nil? if parent_type.kind.union? add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( "Selections can't be made directly on unions (see selections on #{parent_type.graphql_name})", nodes: parent, node_name: parent_type.graphql_name )) else add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new( "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'", nodes: node, field: node.name, type: parent_type.graphql_name )) end else super end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb000066400000000000000000000013151476434635200323430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsAreDefinedOnTypeError < StaticValidation::Error attr_reader :type_name attr_reader :field_name def initialize(message, path: nil, nodes: [], type:, field:) super(message, path: path, nodes: nodes) @type_name = type @field_name = field end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fieldName" => field_name } super.merge({ "extensions" => extensions }) end def code "undefinedField" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb000066400000000000000000000052411476434635200327330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Scalars _can't_ have selections # Objects _must_ have selections module FieldsHaveAppropriateSelections include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) field_defn = field_definition if validate_field_selections(node, field_defn.type.unwrap) super end end def on_operation_definition(node, _parent) if validate_field_selections(node, type_definition) super end end private def validate_field_selections(ast_node, resolved_type) msg = if resolved_type.nil? nil elsif resolved_type.kind.scalar? && ast_node.selections.any? selection_strs = ast_node.selections.map do |n| case n when GraphQL::Language::Nodes::InlineFragment "\"... on #{n.type.name} { ... }\"" when GraphQL::Language::Nodes::Field "\"#{n.name}\"" when GraphQL::Language::Nodes::FragmentSpread "\"#{n.name}\"" else raise "Invariant: unexpected selection node: #{n}" end end "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{selection_strs.join(", ")}])" elsif resolved_type.kind.fields? && ast_node.selections.empty? "Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)" else nil end if msg node_name = case ast_node when GraphQL::Language::Nodes::Field "field '#{ast_node.name}'" when GraphQL::Language::Nodes::OperationDefinition if ast_node.name.nil? "anonymous query" else "#{ast_node.operation_type} '#{ast_node.name}'" end else raise("Unexpected node #{ast_node}") end extensions = { "rule": "StaticValidation::FieldsHaveAppropriateSelections", "name": node_name.to_s } unless resolved_type.nil? extensions["type"] = resolved_type.to_type_signature end add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( msg % { node_name: node_name }, nodes: ast_node, node_name: node_name.to_s, type: resolved_type.nil? ? nil : resolved_type.graphql_name, )) false else true end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb000066400000000000000000000013721476434635200341450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsHaveAppropriateSelectionsError < StaticValidation::Error attr_reader :type_name attr_reader :node_name def initialize(message, path: nil, nodes: [], node_name:, type: nil) super(message, path: path, nodes: nodes) @node_name = node_name @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "nodeName" => node_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code "selectionMismatch" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fields_will_merge.rb000066400000000000000000000357101476434635200273040ustar00rootroot00000000000000 # frozen_string_literal: true # frozen_string_literal: true module GraphQL module StaticValidation module FieldsWillMerge # Validates that a selection set is valid if all fields (including spreading any # fragments) either correspond to distinct response names or can be merged # without ambiguity. # # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH Field = Struct.new(:node, :definition, :owner_type, :parents) FragmentSpread = Struct.new(:name, :parents) def initialize(*) super @visited_fragments = {} @compared_fragments = {} @conflict_count = 0 end def on_operation_definition(node, _parent) setting_errors { conflicts_within_selection_set(node, type_definition) } super end def on_field(node, _parent) setting_errors { conflicts_within_selection_set(node, type_definition) } super end private def field_conflicts @field_conflicts ||= Hash.new do |errors, field| errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :field, field_name: field) end end def arg_conflicts @arg_conflicts ||= Hash.new do |errors, field| errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :argument, field_name: field) end end def setting_errors @field_conflicts = nil @arg_conflicts = nil yield # don't initialize these if they weren't initialized in the block: @field_conflicts && @field_conflicts.each_value { |error| add_error(error) } @arg_conflicts && @arg_conflicts.each_value { |error| add_error(error) } end def conflicts_within_selection_set(node, parent_type) return if parent_type.nil? fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: nil) # (A) Find find all conflicts "within" the fields of this selection set. find_conflicts_within(fields) fragment_spreads.each_with_index do |fragment_spread, i| are_mutually_exclusive = mutually_exclusive?( fragment_spread.parents, [parent_type] ) # (B) Then find conflicts between these fields and those represented by # each spread fragment name found. find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: are_mutually_exclusive, ) # (C) Then compare this fragment with all other fragments found in this # selection set to collect conflicts between fragments spread together. # This compares each item in the list of fragment names to every other # item in that same list (except for itself). fragment_spreads[i + 1..-1].each do |fragment_spread2| are_mutually_exclusive = mutually_exclusive?( fragment_spread.parents, fragment_spread2.parents ) find_conflicts_between_fragments( fragment_spread, fragment_spread2, mutually_exclusive: are_mutually_exclusive, ) end end end def find_conflicts_between_fragments(fragment_spread1, fragment_spread2, mutually_exclusive:) fragment_name1 = fragment_spread1.name fragment_name2 = fragment_spread2.name return if fragment_name1 == fragment_name2 cache_key = compared_fragments_key( fragment_name1, fragment_name2, mutually_exclusive, ) if @compared_fragments.key?(cache_key) return else @compared_fragments[cache_key] = true end fragment1 = context.fragments[fragment_name1] fragment2 = context.fragments[fragment_name2] return if fragment1.nil? || fragment2.nil? fragment_type1 = context.warden.get_type(fragment1.type.name) fragment_type2 = context.warden.get_type(fragment2.type.name) return if fragment_type1.nil? || fragment_type2.nil? fragment_fields1, fragment_spreads1 = fields_and_fragments_from_selection( fragment1, owner_type: fragment_type1, parents: [*fragment_spread1.parents, fragment_type1] ) fragment_fields2, fragment_spreads2 = fields_and_fragments_from_selection( fragment2, owner_type: fragment_type1, parents: [*fragment_spread2.parents, fragment_type2] ) # (F) First, find all conflicts between these two collections of fields # (not including any nested fragments). find_conflicts_between( fragment_fields1, fragment_fields2, mutually_exclusive: mutually_exclusive, ) # (G) Then collect conflicts between the first fragment and any nested # fragments spread in the second fragment. fragment_spreads2.each do |fragment_spread| find_conflicts_between_fragments( fragment_spread1, fragment_spread, mutually_exclusive: mutually_exclusive, ) end # (G) Then collect conflicts between the first fragment and any nested # fragments spread in the second fragment. fragment_spreads1.each do |fragment_spread| find_conflicts_between_fragments( fragment_spread2, fragment_spread, mutually_exclusive: mutually_exclusive, ) end end def find_conflicts_between_fields_and_fragment(fragment_spread, fields, mutually_exclusive:) fragment_name = fragment_spread.name return if @visited_fragments.key?(fragment_name) @visited_fragments[fragment_name] = true fragment = context.fragments[fragment_name] return if fragment.nil? fragment_type = context.warden.get_type(fragment.type.name) return if fragment_type.nil? fragment_fields, fragment_spreads = fields_and_fragments_from_selection(fragment, owner_type: fragment_type, parents: [*fragment_spread.parents, fragment_type]) # (D) First find any conflicts between the provided collection of fields # and the collection of fields represented by the given fragment. find_conflicts_between( fields, fragment_fields, mutually_exclusive: mutually_exclusive, ) # (E) Then collect any conflicts between the provided collection of fields # and any fragment names found in the given fragment. fragment_spreads.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: mutually_exclusive, ) end end def find_conflicts_within(response_keys) response_keys.each do |key, fields| next if fields.size < 2 # find conflicts within nodes i = 0 while i < fields.size j = i + 1 while j < fields.size find_conflict(key, fields[i], fields[j]) j += 1 end i += 1 end end end def find_conflict(response_key, field1, field2, mutually_exclusive: false) return if @conflict_count >= context.max_errors node1 = field1.node node2 = field2.node are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents) if !are_mutually_exclusive if node1.name != node2.name conflict = field_conflicts[response_key] conflict.add_conflict(node1, node1.name) conflict.add_conflict(node2, node2.name) @conflict_count += 1 end if !same_arguments?(node1, node2) conflict = arg_conflicts[response_key] conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1))) conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2))) @conflict_count += 1 end end find_conflicts_between_sub_selection_sets( field1, field2, mutually_exclusive: are_mutually_exclusive, ) end def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:) return if field1.definition.nil? || field2.definition.nil? || (field1.node.selections.empty? && field2.node.selections.empty?) return_type1 = field1.definition.type.unwrap return_type2 = field2.definition.type.unwrap parents1 = [return_type1] parents2 = [return_type2] fields, fragment_spreads = fields_and_fragments_from_selection( field1.node, owner_type: return_type1, parents: parents1 ) fields2, fragment_spreads2 = fields_and_fragments_from_selection( field2.node, owner_type: return_type2, parents: parents2 ) # (H) First, collect all conflicts between these two collections of field. find_conflicts_between(fields, fields2, mutually_exclusive: mutually_exclusive) # (I) Then collect conflicts between the first collection of fields and # those referenced by each fragment name associated with the second. fragment_spreads2.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: mutually_exclusive, ) end # (I) Then collect conflicts between the second collection of fields and # those referenced by each fragment name associated with the first. fragment_spreads.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields2, mutually_exclusive: mutually_exclusive, ) end # (J) Also collect conflicts between any fragment names by the first and # fragment names by the second. This compares each item in the first set of # names to each item in the second set of names. fragment_spreads.each do |frag1| fragment_spreads2.each do |frag2| find_conflicts_between_fragments( frag1, frag2, mutually_exclusive: mutually_exclusive ) end end end def find_conflicts_between(response_keys, response_keys2, mutually_exclusive:) response_keys.each do |key, fields| fields2 = response_keys2[key] if fields2 fields.each do |field| fields2.each do |field2| find_conflict( key, field, field2, mutually_exclusive: mutually_exclusive, ) end end end end end NO_SELECTIONS = [GraphQL::EmptyObjects::EMPTY_HASH, GraphQL::EmptyObjects::EMPTY_ARRAY].freeze def fields_and_fragments_from_selection(node, owner_type:, parents:) if node.selections.empty? NO_SELECTIONS else parents ||= [] fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: []) response_keys = fields.group_by { |f| f.node.alias || f.node.name } [response_keys, fragment_spreads] end end def find_fields_and_fragments(selections, owner_type:, parents:, fields:, fragment_spreads:) selections.each do |node| case node when GraphQL::Language::Nodes::Field definition = context.warden.get_field(owner_type, node.name) fields << Field.new(node, definition, owner_type, parents) when GraphQL::Language::Nodes::InlineFragment fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type when GraphQL::Language::Nodes::FragmentSpread fragment_spreads << FragmentSpread.new(node.name, parents) end end [fields, fragment_spreads] end def same_arguments?(field1, field2) # Check for incompatible / non-identical arguments on this node: arguments1 = field1.arguments arguments2 = field2.arguments return false if arguments1.length != arguments2.length arguments1.all? do |argument1| argument2 = arguments2.find { |argument| argument.name == argument1.name } return false if argument2.nil? serialize_arg(argument1.value) == serialize_arg(argument2.value) end end def serialize_arg(arg_value) case arg_value when GraphQL::Language::Nodes::AbstractNode arg_value.to_query_string when Array "[#{arg_value.map { |a| serialize_arg(a) }.join(", ")}]" else GraphQL::Language.serialize(arg_value) end end def serialize_field_args(field) serialized_args = {} field.arguments.each do |argument| serialized_args[argument.name] = serialize_arg(argument.value) end serialized_args end def compared_fragments_key(frag1, frag2, exclusive) # Cache key to not compare two fragments more than once. # The key includes both fragment names sorted (this way we # avoid computing "A vs B" and "B vs A"). It also includes # "exclusive" since the result may change depending on the parent_type "#{[frag1, frag2].sort.join('-')}-#{exclusive}" end # Given two list of parents, find out if they are mutually exclusive # In this context, `parents` represends the "self scope" of the field, # what types may be found at this point in the query. def mutually_exclusive?(parents1, parents2) if parents1.empty? || parents2.empty? false elsif parents1.length == parents2.length parents1.length.times.any? do |i| type1 = parents1[i - 1] type2 = parents2[i - 1] if type1 == type2 # If the types we're comparing are the same type, # then they aren't mutually exclusive false else # Check if these two scopes have _any_ types in common. possible_right_types = context.query.possible_types(type1) possible_left_types = context.query.possible_types(type2) (possible_right_types & possible_left_types).empty? end end else true end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fields_will_merge_error.rb000066400000000000000000000020411476434635200305040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsWillMergeError < StaticValidation::Error attr_reader :field_name attr_reader :kind def initialize(kind:, field_name:) super(nil) @field_name = field_name @kind = kind @conflicts = [] end def message "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?" end def path [] end def conflicts @conflicts.join(' or ') end def add_conflict(node, conflict_str) return if nodes.include?(node) @nodes << node @conflicts << conflict_str end # A hash representation of this Message def to_h extensions = { "code" => code, "fieldName" => field_name, "conflicts" => conflicts } super.merge({ "extensions" => extensions }) end def code "fieldConflict" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragment_names_are_unique.rb000066400000000000000000000013301476434635200310220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentNamesAreUnique def initialize(*) super @fragments_by_name = Hash.new { |h, k| h[k] = [] } end def on_fragment_definition(node, parent) @fragments_by_name[node.name] << node super end def on_document(_n, _p) super @fragments_by_name.each do |name, fragments| if fragments.length > 1 add_error(GraphQL::StaticValidation::FragmentNamesAreUniqueError.new( %|Fragment name "#{name}" must be unique|, nodes: fragments, name: name )) end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb000066400000000000000000000011711476434635200322360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentNamesAreUniqueError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "fragmentNotUnique" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb000066400000000000000000000046771476434635200317130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentSpreadsArePossible def initialize(*) super @spreads_to_validate = [] end def on_inline_fragment(node, parent) fragment_parent = context.object_types[-2] fragment_child = context.object_types.last if fragment_child validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path) end super end def on_fragment_spread(node, parent) fragment_parent = context.object_types.last @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path) super end def on_document(node, parent) super @spreads_to_validate.each do |frag_spread| frag_node = context.fragments[frag_spread.node.name] if frag_node fragment_child_name = frag_node.type.name fragment_child = context.warden.get_type(fragment_child_name) # Might be non-existent type name if fragment_child validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path) end end end end private def validate_fragment_in_scope(parent_type, child_type, node, context, path) if !child_type.kind.fields? # It's not a valid fragment type, this error was handled someplace else return end parent_types = context.warden.possible_types(parent_type.unwrap) child_types = context.warden.possible_types(child_type.unwrap) if child_types.none? { |c| parent_types.include?(c) } name = node.respond_to?(:name) ? " #{node.name}" : "" add_error(GraphQL::StaticValidation::FragmentSpreadsArePossibleError.new( "Fragment#{name} on #{child_type.graphql_name} can't be spread inside #{parent_type.graphql_name}", nodes: node, path: path, fragment_name: name.empty? ? "unknown" : name, type: child_type.graphql_name, parent: parent_type.graphql_name )) end end class FragmentSpread attr_reader :node, :parent_type, :path def initialize(node:, parent_type:, path:) @node = node @parent_type = parent_type @path = path end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb000066400000000000000000000015401476434635200331060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentSpreadsArePossibleError < StaticValidation::Error attr_reader :type_name attr_reader :fragment_name attr_reader :parent_name def initialize(message, path: nil, nodes: [], type:, fragment_name:, parent:) super(message, path: path, nodes: nodes) @type_name = type @fragment_name = fragment_name @parent_name = parent end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fragmentName" => fragment_name, "parentName" => parent_name } super.merge({ "extensions" => extensions }) end def code "cannotSpreadFragment" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragment_types_exist.rb000066400000000000000000000016431476434635200300710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentTypesExist def on_fragment_definition(node, _parent) if validate_type_exists(node) super end end def on_inline_fragment(node, _parent) if validate_type_exists(node) super end end private def validate_type_exists(fragment_node) if !fragment_node.type true else type_name = fragment_node.type.name type = context.warden.get_type(type_name) if type.nil? add_error(GraphQL::StaticValidation::FragmentTypesExistError.new( "No such type #{type_name}, so it can't be a fragment condition", nodes: fragment_node, type: type_name )) false else true end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragment_types_exist_error.rb000066400000000000000000000011411476434635200312730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentTypesExistError < StaticValidation::Error attr_reader :type_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "undefinedType" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_finite.rb000066400000000000000000000011661476434635200300010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreFinite def on_document(_n, _p) super dependency_map = context.dependencies dependency_map.cyclical_definitions.each do |defn| if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new( "Fragment #{defn.name} contains an infinite loop", nodes: defn.node, path: defn.path, name: defn.name )) end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_finite_error.rb000066400000000000000000000011601476434635200312040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreFiniteError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "infiniteLoop" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_named.rb000066400000000000000000000006031476434635200276020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreNamed def on_fragment_definition(node, _parent) if node.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreNamedError.new( "Fragment definition has no name", nodes: node )) end super end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_named_error.rb000066400000000000000000000010041476434635200310070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreNamedError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "anonymousFragment" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb000066400000000000000000000020021476434635200324330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreOnCompositeTypes def on_fragment_definition(node, parent) validate_type_is_composite(node) && super end def on_inline_fragment(node, parent) validate_type_is_composite(node) && super end private def validate_type_is_composite(node) node_type = node.type if node_type.nil? # Inline fragment on the same type true else type_name = node_type.to_query_string type_def = context.warden.get_type(type_name) if type_def.nil? || !type_def.kind.composite? add_error(GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError.new( "Invalid fragment on type #{type_name} (must be Union, Interface or Object)", nodes: node, type: type_name )) false else true end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb000066400000000000000000000012311476434635200336470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreOnCompositeTypesError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "fragmentOnNonCompositeType" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_used.rb000066400000000000000000000020461476434635200274610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreUsed def on_document(node, parent) super dependency_map = context.dependencies dependency_map.unmet_dependencies.each do |op_defn, spreads| spreads.each do |fragment_spread| add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment_spread.name} was used, but not defined", nodes: fragment_spread.node, path: fragment_spread.path, fragment: fragment_spread.name )) end end dependency_map.unused_dependencies.each do |fragment| if fragment && !fragment.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment.name} was defined, but not used", nodes: fragment.node, path: fragment.path, fragment: fragment.name )) end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/fragments_are_used_error.rb000066400000000000000000000011761476434635200306750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreUsedError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], fragment:) super(message, path: path, nodes: nodes) @fragment_name = fragment end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "useAndDefineFragment" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/input_object_names_are_unique.rb000066400000000000000000000014651476434635200317150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module InputObjectNamesAreUnique def on_input_object(node, parent) validate_input_fields(node) super end private def validate_input_fields(node) input_field_defns = node.arguments input_fields_by_name = Hash.new { |h, k| h[k] = [] } input_field_defns.each { |a| input_fields_by_name[a.name] << a } input_fields_by_name.each do |name, defns| if defns.size > 1 error = GraphQL::StaticValidation::InputObjectNamesAreUniqueError.new( "There can be only one input field named \"#{name}\"", nodes: defns, name: name ) add_error(error) end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb000066400000000000000000000011341476434635200331170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class InputObjectNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "inputFieldNotUnique" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/mutation_root_exists.rb000066400000000000000000000007521476434635200301300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module MutationRootExists def on_operation_definition(node, _parent) if node.operation_type == 'mutation' && context.warden.root_type_for_operation("mutation").nil? add_error(GraphQL::StaticValidation::MutationRootExistsError.new( 'Schema is not configured for mutations', nodes: node )) else super end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/mutation_root_exists_error.rb000066400000000000000000000010201476434635200313260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class MutationRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingMutationConfiguration" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/no_definitions_are_present.rb000066400000000000000000000027111476434635200312210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module NoDefinitionsArePresent include GraphQL::StaticValidation::Error::ErrorHelper def initialize(*) super @schema_definition_nodes = [] end def on_invalid_node(node, parent) @schema_definition_nodes << node nil end alias :on_directive_definition :on_invalid_node alias :on_schema_definition :on_invalid_node alias :on_scalar_type_definition :on_invalid_node alias :on_object_type_definition :on_invalid_node alias :on_input_object_type_definition :on_invalid_node alias :on_interface_type_definition :on_invalid_node alias :on_union_type_definition :on_invalid_node alias :on_enum_type_definition :on_invalid_node alias :on_schema_extension :on_invalid_node alias :on_scalar_type_extension :on_invalid_node alias :on_object_type_extension :on_invalid_node alias :on_input_object_type_extension :on_invalid_node alias :on_interface_type_extension :on_invalid_node alias :on_union_type_extension :on_invalid_node alias :on_enum_type_extension :on_invalid_node def on_document(node, parent) super if @schema_definition_nodes.any? add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes)) end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb000066400000000000000000000010261476434635200324300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class NoDefinitionsArePresentError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "queryContainsSchemaDefinitions" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb000066400000000000000000000042151476434635200320270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module OneOfInputObjectsAreValid def on_input_object(node, parent) return super unless parent.is_a?(GraphQL::Language::Nodes::Argument) parent_type = get_parent_type(context, parent) return super unless parent_type && parent_type.kind.input_object? && parent_type.one_of? validate_one_of_input_object(node, context, parent_type) super end private def validate_one_of_input_object(ast_node, context, parent_type) present_fields = ast_node.arguments.map(&:name) input_object_type = parent_type.to_type_signature if present_fields.count != 1 add_error( OneOfInputObjectsAreValidError.new( "OneOf Input Object '#{input_object_type}' must specify exactly one key.", path: context.path, nodes: ast_node, input_object_type: input_object_type ) ) return end field = present_fields.first value = ast_node.arguments.first.value if value.is_a?(GraphQL::Language::Nodes::NullValue) add_error( OneOfInputObjectsAreValidError.new( "Argument '#{input_object_type}.#{field}' must be non-null.", path: [*context.path, field], nodes: ast_node.arguments.first, input_object_type: input_object_type ) ) return end if value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) variable_name = value.name variable_type = @declared_variables[variable_name].type unless variable_type.is_a?(GraphQL::Language::Nodes::NonNullType) add_error( OneOfInputObjectsAreValidError.new( "Variable '#{variable_name}' must be non-nullable to be used for OneOf Input Object '#{input_object_type}'.", path: [*context.path, field], nodes: ast_node, input_object_type: input_object_type ) ) end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb000066400000000000000000000012441476434635200332370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class OneOfInputObjectsAreValidError < StaticValidation::Error attr_reader :input_object_type def initialize(message, path:, nodes:, input_object_type:) super(message, path: path, nodes: nodes) @input_object_type = input_object_type end # A hash representation of this Message def to_h extensions = { "code" => code, "inputObjectType" => input_object_type } super.merge({ "extensions" => extensions }) end def code "invalidOneOfInputObject" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/operation_names_are_valid.rb000066400000000000000000000020211476434635200310060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module OperationNamesAreValid def initialize(*) super @operation_names = Hash.new { |h, k| h[k] = [] } end def on_operation_definition(node, parent) @operation_names[node.name] << node super end def on_document(node, parent) super op_count = @operation_names.values.inject(0) { |m, v| m + v.size } @operation_names.each do |name, nodes| if name.nil? && op_count > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name is required when multiple operations are present|, nodes: nodes )) elsif nodes.length > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name "#{name}" must be unique|, nodes: nodes, name: name )) end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb000066400000000000000000000012451476434635200322260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class OperationNamesAreValidError < StaticValidation::Error attr_reader :operation_name def initialize(message, path: nil, nodes: [], name: nil) super(message, path: path, nodes: nodes) @operation_name = name end # A hash representation of this Message def to_h extensions = { "code" => code }.tap { |h| h["operationName"] = operation_name unless operation_name.nil? } super.merge({ "extensions" => extensions }) end def code "uniquelyNamedOperations" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/query_root_exists.rb000066400000000000000000000007721476434635200274370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module QueryRootExists def on_operation_definition(node, _parent) if (node.operation_type == 'query' || node.operation_type.nil?) && context.warden.root_type_for_operation("query").nil? add_error(GraphQL::StaticValidation::QueryRootExistsError.new( 'Schema is not configured for queries', nodes: node )) else super end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/query_root_exists_error.rb000066400000000000000000000010121476434635200306340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class QueryRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingQueryConfiguration" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/required_arguments_are_present.rb000066400000000000000000000025221476434635200321170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module RequiredArgumentsArePresent def on_field(node, _parent) assert_required_args(node, field_definition) super end def on_directive(node, _parent) directive_defn = context.schema_directives[node.name] assert_required_args(node, directive_defn) super end private def assert_required_args(ast_node, defn) args = defn.arguments(context.query.context) return if args.empty? present_argument_names = ast_node.arguments.map(&:name) required_argument_names = context.warden.arguments(defn) .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) } .map!(&:name) missing_names = required_argument_names - present_argument_names if missing_names.any? add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new( "#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", nodes: ast_node, class_name: ast_node.class.name.split("::").last, name: ast_node.name, arguments: "#{missing_names.join(", ")}" )) end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb000066400000000000000000000014771476434635200333400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class RequiredArgumentsArePresentError < StaticValidation::Error attr_reader :class_name attr_reader :name attr_reader :arguments def initialize(message, path: nil, nodes: [], class_name:, name:, arguments:) super(message, path: path, nodes: nodes) @class_name = class_name @name = name @arguments = arguments end # A hash representation of this Message def to_h extensions = { "code" => code, "className" => class_name, "name" => name, "arguments" => arguments } super.merge({ "extensions" => extensions }) end def code "missingRequiredArguments" end end end end required_input_object_attributes_are_present.rb000066400000000000000000000043071476434635200347710ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation module RequiredInputObjectAttributesArePresent def on_input_object(node, parent) if parent.is_a? GraphQL::Language::Nodes::Argument validate_input_object(node, context, parent) end super end private def get_parent_type(context, parent) # If argument_definition is defined we're at nested object # and need to refer to the containing input object type rather # than the field_definition. # h/t @rmosolgo arg_defn = context.argument_definition # Double checking that arg_defn is an input object as nested # scalars, namely JSON, can make it to this branch defn = if arg_defn && arg_defn.type.unwrap.kind.input_object? arg_defn.type.unwrap else context.directive_definition || context.field_definition end parent_type = context.warden.get_argument(defn, parent_name(parent, defn)) parent_type ? parent_type.type.unwrap : nil end def validate_input_object(ast_node, context, parent) parent_type = get_parent_type(context, parent) return unless parent_type && parent_type.kind.input_object? required_fields = context.warden.arguments(parent_type) .select{ |arg| arg.type.kind.non_null? && !arg.default_value? } .map!(&:graphql_name) present_fields = ast_node.arguments.map(&:name) missing_fields = required_fields - present_fields missing_fields.each do |missing_field| path = [*context.path, missing_field] missing_field_type = context.warden.get_argument(parent_type, missing_field).type add_error(RequiredInputObjectAttributesArePresentError.new( "Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}", argument_name: missing_field, argument_type: missing_field_type.to_type_signature, input_object_type: parent_type.to_type_signature, path: path, nodes: ast_node, )) end end end end end required_input_object_attributes_are_present_error.rb000066400000000000000000000017051476434635200362010ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation class RequiredInputObjectAttributesArePresentError < StaticValidation::Error attr_reader :argument_type attr_reader :argument_name attr_reader :input_object_type def initialize(message, path:, nodes:, argument_type:, argument_name:, input_object_type:) super(message, path: path, nodes: nodes) @argument_type = argument_type @argument_name = argument_name @input_object_type = input_object_type end # A hash representation of this Message def to_h extensions = { "code" => code, "argumentName" => argument_name, "argumentType" => argument_type, "inputObjectType" => input_object_type, } super.merge({ "extensions" => extensions }) end def code "missingRequiredInputObjectAttribute" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/subscription_root_exists.rb000066400000000000000000000007761476434635200310220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module SubscriptionRootExists def on_operation_definition(node, _parent) if node.operation_type == "subscription" && context.warden.root_type_for_operation("subscription").nil? add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new( 'Schema is not configured for subscriptions', nodes: node )) else super end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/subscription_root_exists_error.rb000066400000000000000000000010301476434635200322130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class SubscriptionRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingSubscriptionConfiguration" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/unique_directives_per_location.rb000066400000000000000000000035201476434635200321070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module UniqueDirectivesPerLocation DIRECTIVE_NODE_HOOKS = [ :on_fragment_definition, :on_fragment_spread, :on_inline_fragment, :on_operation_definition, :on_scalar_type_definition, :on_object_type_definition, :on_input_value_definition, :on_field_definition, :on_interface_type_definition, :on_union_type_definition, :on_enum_type_definition, :on_enum_value_definition, :on_input_object_type_definition, :on_field, ] DIRECTIVE_NODE_HOOKS.each do |method_name| define_method(method_name) do |node, parent| if node.directives.any? validate_directive_location(node) end super(node, parent) end end private def validate_directive_location(node) used_directives = {} node.directives.each do |ast_directive| directive_name = ast_directive.name if (first_node = used_directives[directive_name]) @directives_are_unique_errors_by_first_node ||= {} err = @directives_are_unique_errors_by_first_node[first_node] ||= begin error = GraphQL::StaticValidation::UniqueDirectivesPerLocationError.new( "The directive \"#{directive_name}\" can only be used once at this location.", nodes: [used_directives[directive_name]], directive: directive_name, ) add_error(error) error end err.nodes << ast_directive elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?) used_directives[directive_name] = ast_directive end end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb000066400000000000000000000012301476434635200333140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class UniqueDirectivesPerLocationError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "directiveNotUniqueForLocation" end end end end variable_default_values_are_correctly_typed.rb000066400000000000000000000033631476434635200345420ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation module VariableDefaultValuesAreCorrectlyTyped def on_variable_definition(node, parent) if !node.default_value.nil? value = node.default_value if node.type.is_a?(GraphQL::Language::Nodes::NonNullType) add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( "Non-null variable $#{node.name} can't have a default value", nodes: node, name: node.name, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_ON_NON_NULL] )) else type = context.schema.type_from_ast(node.type, context: context) if type.nil? # This is handled by another validator else validation_result = context.validate_literal(value, type) if !validation_result.valid? problems = validation_result.problems first_problem = problems && problems.first if first_problem error_message = first_problem["explanation"] end error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}" add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( error_message, nodes: node, name: node.name, type: type.to_type_signature, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE], )) end end end end super end end end end variable_default_values_are_correctly_typed_error.rb000066400000000000000000000021141476434635200357440ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation class VariableDefaultValuesAreCorrectlyTypedError < StaticValidation::Error attr_reader :variable_name attr_reader :type_name attr_reader :violation VIOLATIONS = { :INVALID_TYPE => "defaultValueInvalidType", :INVALID_ON_NON_NULL => "defaultValueInvalidOnNonNullVariable", } def initialize(message, path: nil, nodes: [], name:, type: nil, error_type:) super(message, path: path, nodes: nodes) @variable_name = name @type_name = type raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code @violation end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variable_names_are_unique.rb000066400000000000000000000013111476434635200310030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariableNamesAreUnique def on_operation_definition(node, parent) var_defns = node.variables if var_defns.any? vars_by_name = Hash.new { |h, k| h[k] = [] } var_defns.each { |v| vars_by_name[v.name] << v } vars_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::VariableNamesAreUniqueError.new( "There can only be one variable named \"#{name}\"", nodes: defns, name: name )) end end end super end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb000066400000000000000000000011711476434635200322200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariableNamesAreUniqueError < StaticValidation::Error attr_reader :variable_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableNotUnique" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb000066400000000000000000000127351476434635200313240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariableUsagesAreAllowed def initialize(*) super # holds { name => ast_node } pairs @declared_variables = {} end def on_operation_definition(node, parent) @declared_variables = node.variables.each_with_object({}) { |var, memo| memo[var.name] = var } super end def on_argument(node, parent) node_values = if node.value.is_a?(Array) node.value else [node.value] end node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier } if node_values.any? argument_owner = case parent when GraphQL::Language::Nodes::Field context.field_definition when GraphQL::Language::Nodes::Directive context.directive_definition when GraphQL::Language::Nodes::InputObject arg_type = context.argument_definition.type.unwrap if arg_type.kind.input_object? arg_type else # This is some kind of error nil end else raise("Unexpected argument parent: #{parent}") end node_values.each do |node_value| var_defn_ast = @declared_variables[node_value.name] # Might be undefined :( # VariablesAreUsedAndDefined can't finalize its search until the end of the document. var_defn_ast && argument_owner && validate_usage(argument_owner, node, var_defn_ast) end end super end private def validate_usage(argument_owner, arg_node, ast_var) var_type = context.schema.type_from_ast(ast_var.type, context: context) if var_type.nil? return end if !ast_var.default_value.nil? unless var_type.kind.non_null? # If the value is required, but the argument is not, # and yet there's a non-nil default, then we impliclty # make the argument also a required type. var_type = var_type.to_non_null_type end end arg_defn = context.warden.get_argument(argument_owner, arg_node.name) arg_defn_type = arg_defn.type # If the argument is non-null, but it was given a default value, # then treat it as nullable in practice, see https://github.com/rmosolgo/graphql-ruby/issues/3793 if arg_defn_type.non_null? && arg_defn.default_value? arg_defn_type = arg_defn_type.of_type end var_inner_type = var_type.unwrap arg_inner_type = arg_defn_type.unwrap var_type = wrap_var_type_with_depth_of_arg(var_type, arg_node) if var_inner_type != arg_inner_type create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node) elsif list_dimension(var_type) != list_dimension(arg_defn_type) create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node) elsif !non_null_levels_match(arg_defn_type, var_type) create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node) end end def create_error(error_message, var_type, ast_var, arg_defn, arg_node) add_error(GraphQL::StaticValidation::VariableUsagesAreAllowedError.new( "#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_type_signature} / #{arg_defn.type.to_type_signature})", nodes: arg_node, name: ast_var.name, type: var_type.to_type_signature, argument: arg_node.name, error: error_message )) end def wrap_var_type_with_depth_of_arg(var_type, arg_node) arg_node_value = arg_node.value return var_type unless arg_node_value.is_a?(Array) new_var_type = var_type depth_of_array(arg_node_value).times do # Since the array _is_ present, treat it like a non-null type # (It satisfies a non-null requirement AND a nullable requirement) new_var_type = new_var_type.to_list_type.to_non_null_type end new_var_type end # @return [Integer] Returns the max depth of `array`, or `0` if it isn't an array at all def depth_of_array(array) case array when Array max_child_depth = 0 array.each do |item| item_depth = depth_of_array(item) if item_depth > max_child_depth max_child_depth = item_depth end end 1 + max_child_depth else 0 end end def list_dimension(type) if type.kind.list? 1 + list_dimension(type.of_type) elsif type.kind.non_null? list_dimension(type.of_type) else 0 end end def non_null_levels_match(arg_type, var_type) if arg_type.kind.non_null? && !var_type.kind.non_null? false elsif arg_type.kind.wraps? && var_type.kind.wraps? # If var_type is a non-null wrapper for a type, and arg_type is nullable, peel off the wrapper # That way, a var_type of `[DairyAnimal]!` works with an arg_type of `[DairyAnimal]` if var_type.kind.non_null? && !arg_type.kind.non_null? var_type = var_type.of_type end non_null_levels_match(arg_type.of_type, var_type.of_type) else true end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb000066400000000000000000000017071476434635200325320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariableUsagesAreAllowedError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name attr_reader :argument_name attr_reader :error_message def initialize(message, path: nil, nodes: [], type:, name:, argument:, error:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name @argument_name = argument @error_message = error end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name, "typeName" => type_name, "argumentName" => argument_name, "errorMessage" => error_message } super.merge({ "extensions" => extensions }) end def code "variableMismatch" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variables_are_input_types.rb000066400000000000000000000020351476434635200310640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariablesAreInputTypes def on_variable_definition(node, parent) type_name = get_type_name(node.type) type = context.warden.get_type(type_name) if type.nil? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type_name} isn't a defined input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) elsif !type.kind.input? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type.graphql_name} isn't a valid input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) end super end private def get_type_name(ast_type) if ast_type.respond_to?(:of_type) get_type_name(ast_type.of_type) else ast_type.name end end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variables_are_input_types_error.rb000066400000000000000000000013421476434635200322750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreInputTypesError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name def initialize(message, path: nil, nodes: [], type:, name:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableRequiresValidType" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb000066400000000000000000000137031476434635200317450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # The problem is # - Variable $usage must be determined at the OperationDefinition level # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document) # # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops. # # `graphql-js` solves this problem by: # - re-visiting the AST for each validator # - allowing validators to say `followSpreads: true` # module VariablesAreUsedAndDefined class VariableUsage attr_accessor :ast_node, :used_by, :declared_by, :path def used? !!@used_by end def declared? !!@declared_by end end def initialize(*) super @variable_usages_for_context = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = VariableUsage.new } } @spreads_for_context = Hash.new {|hash, key| hash[key] = [] } @variable_context_stack = [] end def on_operation_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) # mark variables as defined: var_hash = @variable_usages_for_context[node] node.variables.each { |var| var_usage = var_hash[var.name] var_usage.declared_by = node var_usage.path = context.path } super @variable_context_stack.pop end def on_fragment_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) super @variable_context_stack.pop end # For FragmentSpreads: # - find the context on the stack # - mark the context as containing this spread def on_fragment_spread(node, parent) variable_context = @variable_context_stack.last @spreads_for_context[variable_context] << node.name super end # For VariableIdentifiers: # - mark the variable as used # - assign its AST node def on_variable_identifier(node, parent) usage_context = @variable_context_stack.last declared_variables = @variable_usages_for_context[usage_context] usage = declared_variables[node.name] usage.used_by = usage_context usage.ast_node = node usage.path = context.path super end def on_document(node, parent) super fragment_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) } operation_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) } operation_definitions.each do |node, node_variables| follow_spreads(node, node_variables, @spreads_for_context, fragment_definitions, []) create_errors(node_variables) end end private # Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`. # Use those fragments to update {VariableUsage}s in `parent_variables`. # Avoid infinite loops by skipping anything in `visited_fragments`. def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) spreads = spreads_for_context[node] - visited_fragments spreads.each do |spread_name| def_node = nil variables = nil # Implement `.find` by hand to avoid Ruby's internal allocations fragment_definitions.each do |frag_def_node, vars| if frag_def_node.name == spread_name def_node = frag_def_node variables = vars break end end next if !def_node visited_fragments << spread_name variables.each do |name, child_usage| parent_usage = parent_variables[name] if child_usage.used? parent_usage.ast_node = child_usage.ast_node parent_usage.used_by = child_usage.used_by parent_usage.path = child_usage.path end end follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) end end # Determine all the error messages, # Then push messages into the validation context def create_errors(node_variables) # Declared but not used: node_variables .select { |name, usage| usage.declared? && !usage.used? } .each { |var_name, usage| declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is declared by #{declared_by_error_name} but not used", nodes: usage.declared_by, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_USED] )) } # Used but not declared: node_variables .select { |name, usage| usage.used? && !usage.declared? } .each { |var_name, usage| used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is used by #{used_by_error_name} but not declared", nodes: usage.ast_node, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_DEFINED] )) } end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb000066400000000000000000000016521476434635200331560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreUsedAndDefinedError < StaticValidation::Error attr_reader :variable_name attr_reader :violation VIOLATIONS = { :VARIABLE_NOT_USED => "variableNotUsed", :VARIABLE_NOT_DEFINED => "variableNotDefined", } def initialize(message, path: nil, nodes: [], name:, error_type:) super(message, path: path, nodes: nodes) @variable_name = name raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code @violation end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/validation_context.rb000066400000000000000000000032021476434635200263630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # The validation context gets passed to each validator. # # It exposes a {GraphQL::Language::Visitor} where validators may add hooks. ({Language::Visitor#visit} is called in {Validator#validate}) # # It provides access to the schema & fragments which validators may read from. # # It holds a list of errors which each validator may add to. class ValidationContext extend Forwardable attr_reader :query, :errors, :visitor, :on_dependency_resolve_handlers, :max_errors, :warden, :schema def_delegators :@query, :document, :fragments, :operations def initialize(query, visitor_class, max_errors) @query = query @warden = query.warden @schema = query.schema @literal_validator = LiteralValidator.new(context: query.context) @errors = [] @max_errors = max_errors || Float::INFINITY @on_dependency_resolve_handlers = [] @visitor = visitor_class.new(document, self) end def_delegators :@visitor, :path, :type_definition, :field_definition, :argument_definition, :parent_type_definition, :directive_definition, :object_types, :dependencies def on_dependency_resolve(&handler) @on_dependency_resolve_handlers << handler end def validate_literal(ast_value, type) @literal_validator.validate(ast_value, type) end def too_many_errors? @errors.length >= @max_errors end def schema_directives @schema_directives ||= schema.directives end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/validation_timeout_error.rb000066400000000000000000000010021476434635200275720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ValidationTimeoutError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code } super.merge({ "extensions" => extensions }) end def code "validationTimeout" end end end end graphql-ruby-2.2.17/lib/graphql/static_validation/validator.rb000066400000000000000000000060371476434635200244630ustar00rootroot00000000000000# frozen_string_literal: true require "timeout" module GraphQL module StaticValidation # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema. # # By default, it's used by {GraphQL::Query} # # @example Validate a query # validator = GraphQL::StaticValidation::Validator.new(schema: MySchema) # query = GraphQL::Query.new(MySchema, query_string) # errors = validator.validate(query)[:errors] # class Validator # @param schema [GraphQL::Schema] # @param rules [Array<#validate(context)>] a list of rules to use when validating def initialize(schema:, rules: GraphQL::StaticValidation::ALL_RULES) @schema = schema @rules = rules end # Validate `query` against the schema. Returns an array of message hashes. # @param query [GraphQL::Query] # @param validate [Boolean] # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds. # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit. # @return [Array] def validate(query, validate: true, timeout: nil, max_errors: nil) query.current_trace.validate(validate: validate, query: query) do begin_t = Time.now errors = if validate == false [] else rules_to_use = validate ? @rules : [] visitor_class = BaseVisitor.including_rules(rules_to_use) context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors) begin # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached. # A timeout value of 0 or nil will execute the block without any timeout. Timeout::timeout(timeout) do catch(:too_many_validation_errors) do context.visitor.visit end end rescue Timeout::Error handle_timeout(query, context) end context.errors end { remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil, errors: errors, } end rescue GraphQL::ExecutionError => e { remaining_timeout: nil, errors: [e], } end # Invoked when static validation times out. # @param query [GraphQL::Query] # @param context [GraphQL::StaticValidation::ValidationContext] def handle_timeout(query, context) context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new( "Timeout on validation of query" ) end end end end graphql-ruby-2.2.17/lib/graphql/string_encoding_error.rb000066400000000000000000000011161476434635200233530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class StringEncodingError < GraphQL::RuntimeTypeError attr_reader :string, :field, :path def initialize(str, context:) @string = str @field = context[:current_field] @path = context[:current_path] message = "String #{str.inspect} was encoded as #{str.encoding}".dup if @path message << " @ #{@path.join(".")}" end if @field message << " (#{@field.path})" end message << ". GraphQL requires an encoding compatible with UTF-8." super(message) end end end graphql-ruby-2.2.17/lib/graphql/subscriptions.rb000066400000000000000000000270661476434635200217110ustar00rootroot00000000000000# frozen_string_literal: true require "securerandom" require "graphql/subscriptions/broadcast_analyzer" require "graphql/subscriptions/event" require "graphql/subscriptions/serialize" require "graphql/subscriptions/action_cable_subscriptions" 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 # - one or more arguments don't match the field arguments class InvalidTriggerError < GraphQL::Error end # Raised when either: # - An initial subscription didn't have a value for `context[subscription_scope]` # - Or, an update didn't pass `.trigger(..., scope:)` # When raised, the initial subscription or update fails completely. class SubscriptionScopeMissingError < GraphQL::Error end # @see {Subscriptions#initialize} for options, concrete implementations may add options. def self.use(defn, options = {}) schema = defn.is_a?(Class) ? defn : defn.target if schema.subscriptions(inherited: false) raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}" end 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 # @param validate_update [Boolean] If false, then validation is skipped when executing updates def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest) if broadcast schema.query_analyzer(Subscriptions::BroadcastAnalyzer) end @default_broadcastable = default_broadcastable @schema = schema @validate_update = validate_update 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 Object] # @param object [Object] # @param scope [Symbol, String] # @param context [Hash] # @return [void] def trigger(event_name, args, object, scope: nil, context: {}) # Make something as context-like as possible, even though there isn't a current query: dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context) context = dummy_query.context event_name = event_name.to_s # Try with the verbatim input first: field = @schema.get_field(@schema.subscription, event_name, context) if field.nil? # And if it wasn't found, normalize it: normalized_event_name = normalize_name(event_name) field = @schema.get_field(@schema.subscription, normalized_event_name, context) if field.nil? raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})" end else # Since we found a field, the original input was already normalized normalized_event_name = event_name end # Normalize symbol-keyed args to strings, try camelizing them # Should this accept a real context somehow? normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance) event = Subscriptions::Event.new( name: normalized_event_name, arguments: normalized_args, field: field, scope: scope, context: context, ) execute_all(event, object) 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 return the result. # # @param subscription_id [String] # @param event [GraphQL::Subscriptions::Event] The event which was triggered # @param object [Object] The value for the subscription field # @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? delete_subscription(subscription_id) return nil end # Fetch the required keys from the saved data query_string = query_data.fetch(:query_string) variables = query_data.fetch(:variables) context = query_data.fetch(:context) operation_name = query_data.fetch(:operation_name) execute_options = { query: query_string, context: context, subscription_topic: event.topic, operation_name: operation_name, variables: variables, root_value: object, } # merge event's and query's context together context.merge!(event.context) unless event.context.nil? || context.nil? execute_options[:validate] = validate_update?(**execute_options) result = @schema.execute(**execute_options) subscriptions_context = result.context.namespace(:subscriptions) if subscriptions_context[:no_update] result = nil end if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update] # `unsubscribe` was called, clean up on our side # The transport should also send `{more: false}` to client delete_subscription(subscription_id) result = nil end result end # Define this method to customize whether to validate # this subscription when executing an update. # # @return [Boolean] defaults to `true`, or false if `validate: false` is provided. def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:) @validate_update 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) if res.context.namespace(:subscriptions)[:unsubscribed] # `unsubscribe` was called, clean up on our side # The transport should also send `{more: false}` to client delete_subscription(subscription_id) end end end # Event `event` occurred on `object`, # Update all subscribers. # @param event [Subscriptions::Event] # @param object [Object] # @return [void] def execute_all(event, object) raise GraphQL::RequiredImplementationMissingError end # The system wants to send an update to this subscription. # Read its data and return it. # @param subscription_id [String] # @return [Hash] Containing required keys def read_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # A subscription query was re-evaluated, returning `result`. # The result should be send to `subscription_id`. # @param subscription_id [String] # @param result [Hash] # @return [void] def deliver(subscription_id, result) raise GraphQL::RequiredImplementationMissingError end # `query` was executed and found subscriptions to `events`. # Update the database to reflect this new state. # @param query [GraphQL::Query] # @param events [Array] # @return [void] def write_subscription(query, events) raise GraphQL::RequiredImplementationMissingError end # A subscription was terminated server-side. # Clean up the database. # @param subscription_id [String] # @return void. def delete_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # @return [String] A new unique identifier for a subscription def build_id SecureRandom.uuid end # Convert a user-provided event name or argument # to the equivalent in GraphQL. # # By default, it converts the identifier to camelcase. # Override this in a subclass to change the transformation. # # @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 = @schema.query_class.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`: # - convert symbols to strings, # - if needed, camelize the string (using {#normalize_name}) # @param arg_owner [GraphQL::Field, GraphQL::BaseType] # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner` # @return [Any] normalized arguments value def normalize_arguments(event_name, arg_owner, args, context) case arg_owner when GraphQL::Schema::Field, Class if arg_owner.is_a?(Class) && !arg_owner.kind.input_object? # it's a type, but not an input object return args end normalized_args = {} missing_arg_names = [] args.each do |k, v| arg_name = k.to_s arg_defn = arg_owner.get_argument(arg_name, context) if arg_defn normalized_arg_name = arg_name else normalized_arg_name = normalize_name(arg_name) arg_defn = arg_owner.get_argument(normalized_arg_name, context) end if arg_defn if arg_defn.loads normalized_arg_name = arg_defn.keyword.to_s end normalized = normalize_arguments(event_name, arg_defn.type, v, context) normalized_args[normalized_arg_name] = normalized else # Couldn't find a matching argument definition missing_arg_names << arg_name end end # Backfill default values so that trigger arguments # match query arguments. arg_owner.arguments(context).each do |_name, arg_defn| if arg_defn.default_value? && !normalized_args.key?(arg_defn.name) default_value = arg_defn.default_value # We don't have an underlying "object" here, so it can't call methods. # This is broken. normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: context) end end if missing_arg_names.any? arg_owner_name = if arg_owner.is_a?(GraphQL::Schema::Field) arg_owner.path elsif arg_owner.is_a?(Class) arg_owner.graphql_name else arg_owner.to_s end raise InvalidTriggerError, "Can't trigger Subscription.#{event_name}, received undefined arguments: #{missing_arg_names.join(", ")}. (Should match arguments of #{arg_owner_name}.)" end normalized_args when GraphQL::Schema::List args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a, context) } when GraphQL::Schema::NonNull normalize_arguments(event_name, arg_owner.of_type, args, context) else args end end end end graphql-ruby-2.2.17/lib/graphql/subscriptions/000077500000000000000000000000001476434635200213515ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/subscriptions/action_cable_subscriptions.rb000066400000000000000000000241351476434635200272750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # A subscriptions implementation that sends data # as ActionCable broadcastings. # # Some things to keep in mind: # # - No queueing system; ActiveJob should be added # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?}) # - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console. # # @example Adding ActionCableSubscriptions to your schema # class MySchema < GraphQL::Schema # # ... # use GraphQL::Subscriptions::ActionCableSubscriptions # end # # @example Implementing a channel for GraphQL Subscriptions # class GraphqlChannel < ApplicationCable::Channel # def subscribed # @subscription_ids = [] # end # # def execute(data) # query = data["query"] # variables = ensure_hash(data["variables"]) # operation_name = data["operationName"] # context = { # # Re-implement whatever context methods you need # # in this channel or ApplicationCable::Channel # # current_user: current_user, # # Make sure the channel is in the context # channel: self, # } # # result = MySchema.execute( # query, # context: context, # variables: variables, # operation_name: operation_name # ) # # payload = { # result: result.to_h, # more: result.subscription?, # } # # # Track the subscription here so we can remove it # # on unsubscribe. # if result.context[:subscription_id] # @subscription_ids << result.context[:subscription_id] # end # # transmit(payload) # end # # def unsubscribed # @subscription_ids.each { |sid| # MySchema.subscriptions.delete_subscription(sid) # } # end # # private # # def ensure_hash(ambiguous_param) # case ambiguous_param # when String # if ambiguous_param.present? # ensure_hash(JSON.parse(ambiguous_param)) # else # {} # end # when Hash, ActionController::Parameters # ambiguous_param # when nil # {} # else # raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" # end # end # end # class ActionCableSubscriptions < GraphQL::Subscriptions SUBSCRIPTION_PREFIX = "graphql-subscription:" EVENT_PREFIX = "graphql-event:" # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)` # @param namespace [string] Used to namespace events and subscriptions (default: '') def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest) # A per-process map of subscriptions to deliver. # This is provided by Rails, so let's use it @subscriptions = Concurrent::Map.new @events = Concurrent::Map.new do |h, k| h.compute_if_absent(k) do Concurrent::Map.new do |h2, k2| h2.compute_if_absent(k2) { Concurrent::Array.new } end end end @action_cable = action_cable @action_cable_coder = action_cable_coder @serializer = serializer @serialize_with_context = case @serializer.method(:load).arity when 1 false when 2 true else raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments" end @transmit_ns = namespace super end # An event was triggered; Push the data over ActionCable. # Subscribers will re-evaluate locally. def execute_all(event, object) stream = stream_event_name(event) message = @serializer.dump(object) @action_cable.server.broadcast(stream, message) end # This subscription was re-evaluated. # Send it to the specific stream where this client was waiting. def deliver(subscription_id, result) has_more = !result.context.namespace(:subscriptions)[:final_update] payload = { result: result.to_h, more: has_more } @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload) end # A query was run where these events were subscribed to. # Store them in memory in _this_ ActionCable frontend. # It will receive notifications when events come in # and re-evaluate the query locally. def write_subscription(query, events) unless (channel = query.context[:channel]) raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\ "by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\ "Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\ "Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\ "GraphiQL via `graphiql-rails` may not work out of box (#1051)." end subscription_id = query.context[:subscription_id] ||= build_id stream = stream_subscription_name(subscription_id) channel.stream_from(stream) @subscriptions[subscription_id] = query events.each do |event| # Setup a new listener to run all events with this topic in this process setup_stream(channel, event) # Add this event to the list of events to be updated @events[event.topic][event.fingerprint] << event end end # Every subscribing channel is listening here, but only one of them takes any action. # This is so we can reuse payloads when possible, and make one payload to send to # all subscribers. # # But the problem is, any channel could close at any time, so each channel has to # be ready to take over the primary position. # # To make sure there's always one-and-only-one channel building payloads, # let the listener belonging to the first event on the list be # the one to build and publish payloads. # def setup_stream(channel, initial_event) topic = initial_event.topic channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message| events_by_fingerprint = @events[topic] object = nil events_by_fingerprint.each do |_fingerprint, events| if events.any? && events.first == initial_event # The fingerprint has told us that this response should be shared by all subscribers, # so just run it once, then deliver the result to every subscriber first_event = events.first first_subscription_id = first_event.context.fetch(:subscription_id) object ||= load_action_cable_message(message, first_event.context) result = execute_update(first_subscription_id, first_event, object) if !result.nil? # Having calculated the result _once_, send the same payload to all subscribers events.each do |event| subscription_id = event.context.fetch(:subscription_id) deliver(subscription_id, result) end end end end nil end end # This is called to turn an ActionCable-broadcasted string (JSON) # into a query-ready application object. # @param message [String] n ActionCable-broadcasted string (JSON) # @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint def load_action_cable_message(message, context) if @serialize_with_context @serializer.load(message, context) else @serializer.load(message) end end # Return the query from "storage" (in memory) def read_subscription(subscription_id) query = @subscriptions[subscription_id] if query.nil? # This can happen when a subscription is triggered from an unsubscribed channel, # see https://github.com/rmosolgo/graphql-ruby/issues/2478. # (This `nil` is handled by `#execute_update`) nil else { query_string: query.query_string, variables: query.provided_variables, context: query.context.to_h, operation_name: query.operation_name, } end end # The channel was closed, forget about it. def delete_subscription(subscription_id) query = @subscriptions.delete(subscription_id) # In case this came from the server, tell the client to unsubscribe: @action_cable.server.broadcast(stream_subscription_name(subscription_id), { more: false }) # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel, # see https://github.com/rmosolgo/graphql-ruby/issues/2478 if query events = query.context.namespace(:subscriptions)[:events] events.each do |event| ev_by_fingerprint = @events[event.topic] ev_for_fingerprint = ev_by_fingerprint[event.fingerprint] ev_for_fingerprint.delete(event) if ev_for_fingerprint.empty? ev_by_fingerprint.delete(event.fingerprint) end end end end private def stream_subscription_name(subscription_id) [SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join end def stream_event_name(event) [EVENT_PREFIX, @transmit_ns, event.topic].join end end end end graphql-ruby-2.2.17/lib/graphql/subscriptions/broadcast_analyzer.rb000066400000000000000000000061651476434635200255550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # Detect whether the current operation: # - Is a subscription operation # - Is completely broadcastable # # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]` # @api private # @see Subscriptions#broadcastable? for a public API class BroadcastAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(subject) super @default_broadcastable = subject.schema.subscriptions.default_broadcastable # Maybe this will get set to false while analyzing @subscription_broadcastable = true end # Only analyze subscription operations def analyze? @query.subscription? end def on_enter_field(node, parent, visitor) if (@subscription_broadcastable == false) || visitor.skipping? return end current_field = visitor.field_definition apply_broadcastable(current_field) current_type = visitor.parent_type_definition if current_type.kind.interface? pt = @query.possible_types(current_type) pt.each do |object_type| ot_field = @query.get_field(object_type, current_field.graphql_name) # Inherited fields would be exactly the same object; # only check fields that are overrides of the inherited one if ot_field && ot_field != current_field apply_broadcastable(ot_field) end end end end # Assign the result to context. # (This method is allowed to return an error, but we don't need to) # @return [void] def result query.context.namespace(:subscriptions)[:subscription_broadcastable] = @subscription_broadcastable nil end private # Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value) def apply_broadcastable(field_defn) current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable? case current_field_broadcastable when nil # If the value wasn't set, mix in the default value: # - If the default is false and the current value is true, make it false # - If the default is true and the current value is true, it stays true # - If the default is false and the current value is false, keep it false # - If the default is true and the current value is false, keep it false @subscription_broadcastable = @subscription_broadcastable && @default_broadcastable when false # One non-broadcastable field is enough to make the whole subscription non-broadcastable @subscription_broadcastable = false when true # Leave `@broadcastable_query` true if it's already true, # but don't _set_ it to true if it was set to false by something else. # Actually, just leave it! else raise ArgumentError, "Unexpected `.broadcastable?` value for #{field_defn.path}: #{current_field_broadcastable}" end end end end end graphql-ruby-2.2.17/lib/graphql/subscriptions/default_subscription_resolve_extension.rb000066400000000000000000000036031476434635200317630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions class DefaultSubscriptionResolveExtension < GraphQL::Schema::FieldExtension def resolve(context:, object:, arguments:) has_override_implementation = @field.resolver || object.respond_to?(@field.resolver_method) if !has_override_implementation if context.query.subscription_update? object.object else context.skip end else yield(object, arguments) end end def after_resolve(value:, context:, object:, arguments:, **rest) if value.is_a?(GraphQL::ExecutionError) value elsif (events = context.namespace(:subscriptions)[:events]) # This is the first execution, so gather an Event # for the backend to register: event = Subscriptions::Event.new( name: field.name, arguments: arguments_without_field_extras(arguments: arguments), context: context, field: field, ) events << event value elsif context.query.subscription_topic == Subscriptions::Event.serialize( field.name, arguments_without_field_extras(arguments: arguments), field, scope: (field.subscription_scope ? context[field.subscription_scope] : nil), ) # This is a subscription update. The resolver returned `skip` if it should be skipped, # or else it returned an object to resolve the update. value else # This is a subscription update, but this event wasn't triggered. context.skip end end private def arguments_without_field_extras(arguments:) arguments.dup.tap do |event_args| field.extras.each { |k| event_args.delete(k) } end end end end end graphql-ruby-2.2.17/lib/graphql/subscriptions/event.rb000066400000000000000000000137431476434635200230270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # This thing can be: # - Subscribed to by `subscription { ... }` # - Triggered by `MySchema.subscriber.trigger(name, arguments, obj)` # class Event # @return [String] Corresponds to the Subscription root field name attr_reader :name # @return [GraphQL::Execution::Interpreter::Arguments] attr_reader :arguments # @return [GraphQL::Query::Context] attr_reader :context # @return [String] An opaque string which identifies this event, derived from `name` and `arguments` attr_reader :topic def initialize(name:, arguments:, field: nil, context: nil, scope: nil) @name = name @arguments = arguments @context = context field ||= context.field scope_key = field.subscription_scope scope_val = scope || (context && scope_key && context[scope_key]) if scope_key && (subscription = field.resolver) && (subscription.respond_to?(:subscription_scope_optional?)) && !subscription.subscription_scope_optional? && scope_val.nil? raise Subscriptions::SubscriptionScopeMissingError, "#{field.path} (#{subscription}) requires a `scope:` value to trigger updates (Set `subscription_scope ..., optional: true` to disable this requirement)" end @topic = self.class.serialize(name, arguments, field, scope: scope_val, context: context) end # @return [String] an identifier for this unit of subscription def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance) subscription = field.resolver || GraphQL::Schema::Subscription normalized_args = stringify_args(field, arguments.to_h, context) subscription.topic_for(arguments: normalized_args, field: field, scope: scope) end # @return [String] a logical identifier for this event. (Stable when the query is broadcastable.) def fingerprint @fingerprint ||= begin # When this query has been flagged as broadcastable, # use a generalized, stable fingerprint so that # duplicate subscriptions can be evaluated and distributed in bulk. # (`@topic` includes field, args, and subscription scope already.) if @context.namespace(:subscriptions)[:subscription_broadcastable] "#{@topic}/#{@context.query.fingerprint}" else # not broadcastable, build a unique ID for this event @context.schema.subscriptions.build_id end end end class << self private # This method does not support cyclic references in the Hash, # nor does it support Hashes whose keys are not sortable # with respect to their peers ( cases where a <=> b might throw an error ) def deep_sort_hash_keys(hash_to_sort) raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash) hash_to_sort.keys.sort.map do |k| if hash_to_sort[k].is_a?(Hash) [k, deep_sort_hash_keys(hash_to_sort[k])] elsif hash_to_sort[k].is_a?(Array) [k, deep_sort_array_hashes(hash_to_sort[k])] else [k, hash_to_sort[k]] end end.to_h end def deep_sort_array_hashes(array_to_inspect) raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array) array_to_inspect.map do |v| if v.is_a?(Hash) deep_sort_hash_keys(v) elsif v.is_a?(Array) deep_sort_array_hashes(v) else v end end end def stringify_args(arg_owner, args, context) arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers case args when Hash next_args = {} args.each do |k, v| arg_name = k.to_s camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name) arg_defn = get_arg_definition(arg_owner, camelized_arg_name, context) arg_defn ||= get_arg_definition(arg_owner, arg_name, context) normalized_arg_name = arg_defn.graphql_name arg_base_type = arg_defn.type.unwrap # In the case where the value being emitted is seen as a "JSON" # type, treat the value as one atomic unit of serialization is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON if is_json_definition sorted_value = if v.is_a?(Hash) deep_sort_hash_keys(v) elsif v.is_a?(Array) deep_sort_array_hashes(v) else v end next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value else next_args[normalized_arg_name] = stringify_args(arg_base_type, v, context) end end # Make sure they're deeply sorted next_args.sort.to_h when Array args.map { |a| stringify_args(arg_owner, a, context) } when GraphQL::Schema::InputObject stringify_args(arg_owner, args.to_h, context) else if arg_owner.is_a?(Class) && arg_owner < GraphQL::Schema::Enum # `prepare:` may have made the value something other than # a defined value of this enum -- use _that_ in this case. arg_owner.coerce_isolated_input(args) || args else args end end end def get_arg_definition(arg_owner, arg_name, context) arg_owner.get_argument(arg_name, context) || arg_owner.arguments(context).each_value.find { |v| v.keyword.to_s == arg_name } end end end end end graphql-ruby-2.2.17/lib/graphql/subscriptions/serialize.rb000066400000000000000000000140341476434635200236670ustar00rootroot00000000000000# frozen_string_literal: true require "set" module GraphQL class Subscriptions # Serialization helpers for passing subscription data around. # @api private module Serialize GLOBALID_KEY = "__gid__" SYMBOL_KEY = "__sym__" SYMBOL_KEYS_KEY = "__sym_keys__" TIMESTAMP_KEY = "__timestamp__" TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00' OPEN_STRUCT_KEY = "__ostruct__" module_function # @param str [String] A serialized object from {.dump} # @return [Object] An object equivalent to the one passed to {.dump} def load(str) parsed_obj = JSON.parse(str) load_value(parsed_obj) end # @param obj [Object] Some subscription-related data to dump # @return [String] The stringified object def dump(obj) JSON.generate(dump_value(obj), quirks_mode: true) end # This is for turning objects into subscription scopes. # It's a one-way transformation, can't reload this :'( # @param obj [Object] # @return [String] def dump_recursive(obj) case when obj.is_a?(Array) obj.map { |i| dump_recursive(i) }.join(':') when obj.is_a?(Hash) obj.map { |k, v| "#{dump_recursive(k)}:#{dump_recursive(v)}" }.join(":") when obj.is_a?(GraphQL::Schema::InputObject) dump_recursive(obj.to_h) when obj.respond_to?(:to_gid_param) obj.to_gid_param when obj.respond_to?(:to_param) obj.to_param else obj.to_s end end class << self private # @param value [Object] A parsed JSON object # @return [Object] An object that load Global::Identification recursive def load_value(value) if value.is_a?(Array) is_gids = (v1 = value[0]).is_a?(Hash) && v1.size == 1 && v1[GLOBALID_KEY] if is_gids # Assume it's an array of global IDs ids = value.map { |v| v[GLOBALID_KEY] } GlobalID::Locator.locate_many(ids) else value.map { |item| load_value(item) } end elsif value.is_a?(Hash) if value.size == 1 case value.keys.first # there's only 1 key when GLOBALID_KEY GlobalID::Locator.locate(value[GLOBALID_KEY]) when SYMBOL_KEY value[SYMBOL_KEY].to_sym when TIMESTAMP_KEY timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY] timestamp_class = Object.const_get(timestamp_class_name) if defined?(ActiveSupport::TimeWithZone) && timestamp_class <= ActiveSupport::TimeWithZone zone_name, timestamp_s = timestamp_args zone = ActiveSupport::TimeZone[zone_name] raise "Zone #{zone_name} not found, unable to deserialize" unless zone zone.strptime(timestamp_s, TIMESTAMP_FORMAT) else timestamp_s = timestamp_args.first timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT) end when OPEN_STRUCT_KEY ostruct_values = load_value(value[OPEN_STRUCT_KEY]) OpenStruct.new(ostruct_values) else key = value.keys.first { key => load_value(value[key]) } end else loaded_h = {} sym_keys = value.fetch(SYMBOL_KEYS_KEY, []) value.each do |k, v| if k == SYMBOL_KEYS_KEY next end if sym_keys.include?(k) k = k.to_sym end loaded_h[k] = load_value(v) end loaded_h end else value end end # @param obj [Object] Some subscription-related data to dump # @return [Object] The object that converted Global::Identification def dump_value(obj) if obj.is_a?(Array) obj.map{|item| dump_value(item)} elsif obj.is_a?(Hash) symbol_keys = nil dumped_h = {} obj.each do |k, v| dumped_h[k.to_s] = dump_value(v) if k.is_a?(Symbol) symbol_keys ||= Set.new symbol_keys << k.to_s end end if symbol_keys dumped_h[SYMBOL_KEYS_KEY] = symbol_keys.to_a end dumped_h elsif obj.is_a?(Symbol) { SYMBOL_KEY => obj.to_s } elsif obj.respond_to?(:to_gid_param) {GLOBALID_KEY => obj.to_gid_param} elsif defined?(ActiveSupport::TimeWithZone) && obj.is_a?(ActiveSupport::TimeWithZone) && obj.class.name != Time.name # This handles a case where Rails prior to 7 would # make the class ActiveSupport::TimeWithZone return "Time" for # its name. In Rails 7, it will now return "ActiveSupport::TimeWithZone", # which happens to be incompatible with expectations we have # with what a Time class supports ( notably, strptime in `load_value` ). # # This now passes along the name of the zone, such that a future deserialization # of this string will use the correct time zone from the ActiveSupport TimeZone # list to produce the time. # { TIMESTAMP_KEY => [obj.class.name, obj.time_zone.name, obj.strftime(TIMESTAMP_FORMAT)] } elsif obj.is_a?(Date) || obj.is_a?(Time) # DateTime extends Date; for TimeWithZone, call `.utc` first. { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] } elsif obj.is_a?(OpenStruct) { OPEN_STRUCT_KEY => dump_value(obj.to_h) } elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation) dump_value(obj.to_a) else obj end end end end end end graphql-ruby-2.2.17/lib/graphql/testing.rb000066400000000000000000000001001476434635200204330ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/testing/helpers" graphql-ruby-2.2.17/lib/graphql/testing/000077500000000000000000000000001476434635200201175ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/testing/helpers.rb000066400000000000000000000110271476434635200221070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Testing module Helpers # @param schema_class [Class] # @return [Module] A helpers module which always uses the given schema def self.for(schema_class) SchemaHelpers.for(schema_class) end class Error < GraphQL::Error end class TypeNotVisibleError < Error def initialize(type_name:) message = "`#{type_name}` should be `visible?` this field resolution and `context`, but it was not" super(message) end end class FieldNotVisibleError < Error def initialize(type_name:, field_name:) message = "`#{type_name}.#{field_name}` should be `visible?` for this resolution, but it was not" super(message) end end class TypeNotDefinedError < Error def initialize(type_name:) message = "No type named `#{type_name}` is defined; choose another type name or define this type." super(message) end end class FieldNotDefinedError < Error def initialize(type_name:, field_name:) message = "`#{type_name}` has no field named `#{field_name}`; pick another name or define this field." super(message) end end def run_graphql_field(schema, field_path, object, arguments: {}, context: {}) type_name, *field_names = field_path.split(".") dummy_query = GraphQL::Query.new(schema, context: context) query_context = dummy_query.context object_type = dummy_query.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop if object_type graphql_result = object field_names.each do |field_name| inner_object = graphql_result graphql_result = object_type.wrap(inner_object, query_context) if graphql_result.nil? return nil end visible_field = dummy_query.get_field(object_type, field_name) if visible_field dummy_query.context.dataloader.run_isolated { field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context) field_args = schema.sync_lazy(field_args) graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context) graphql_result = schema.sync_lazy(graphql_result) } object_type = visible_field.type.unwrap elsif object_type.all_field_definitions.any? { |f| f.graphql_name == field_name } raise FieldNotVisibleError.new(field_name: field_name, type_name: type_name) else raise FieldNotDefinedError.new(type_name: type_name, field_name: field_name) end end graphql_result elsif schema.has_defined_type?(type_name) raise TypeNotVisibleError.new(type_name: type_name) else raise TypeNotDefinedError.new(type_name: type_name) end end def with_resolution_context(schema, type:, object:, context:{}) resolution_context = ResolutionAssertionContext.new( self, schema: schema, type_name: type, object: object, context: context ) yield(resolution_context) end class ResolutionAssertionContext def initialize(test, type_name:, object:, schema:, context:) @test = test @type_name = type_name @object = object @schema = schema @context = context end def run_graphql_field(field_name, arguments: {}) if @schema @test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context) else @test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context) end end end module SchemaHelpers include Helpers def run_graphql_field(field_path, object, arguments: {}, context: {}) super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context) end def with_resolution_context(*args, **kwargs, &block) # schema will be added later super(nil, *args, **kwargs, &block) end def self.for(schema_class) Module.new do include SchemaHelpers @@schema_class_for_helpers = schema_class end end end end end end graphql-ruby-2.2.17/lib/graphql/tracing.rb000066400000000000000000000045751476434635200204310ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/tracing/trace" require "graphql/tracing/legacy_trace" require "graphql/tracing/legacy_hooks_trace" # Legacy tracing: require "graphql/tracing/active_support_notifications_tracing" require "graphql/tracing/platform_tracing" require "graphql/tracing/appoptics_tracing" require "graphql/tracing/appsignal_tracing" require "graphql/tracing/data_dog_tracing" require "graphql/tracing/new_relic_tracing" require "graphql/tracing/scout_tracing" require "graphql/tracing/statsd_tracing" require "graphql/tracing/prometheus_tracing" # New Tracing: require "graphql/tracing/active_support_notifications_trace" require "graphql/tracing/platform_trace" require "graphql/tracing/appoptics_trace" require "graphql/tracing/appsignal_trace" require "graphql/tracing/data_dog_trace" require "graphql/tracing/new_relic_trace" require "graphql/tracing/notifications_trace" require "graphql/tracing/sentry_trace" require "graphql/tracing/scout_trace" require "graphql/tracing/statsd_trace" require "graphql/tracing/prometheus_trace" if defined?(PrometheusExporter::Server) require "graphql/tracing/prometheus_trace/graphql_collector" end module GraphQL module Tracing NullTrace = Trace.new # Objects may include traceable to gain a `.trace(...)` method. # The object must have a `@tracers` ivar of type `Array<<#trace(k, d, &b)>>`. # @api private module Traceable # @param key [String] The name of the event in GraphQL internals # @param metadata [Hash] Event-related metadata (can be anything) # @return [Object] Must return the value of the block def trace(key, metadata, &block) return yield if @tracers.empty? call_tracers(0, key, metadata, &block) end private # If there's a tracer at `idx`, call it and then increment `idx`. # Otherwise, yield. # # @param idx [Integer] Which tracer to call # @param key [String] The current event name # @param metadata [Object] The current event object # @return Whatever the block returns def call_tracers(idx, key, metadata, &block) if idx == @tracers.length yield else @tracers[idx].trace(key, metadata) { call_tracers(idx + 1, key, metadata, &block) } end end end module NullTracer module_function def trace(k, v) yield end end end end graphql-ruby-2.2.17/lib/graphql/tracing/000077500000000000000000000000001476434635200200715ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/tracing/active_support_notifications_trace.rb000066400000000000000000000006201476434635200275720ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/tracing/notifications_trace' module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications # with a `graphql` suffix. module ActiveSupportNotificationsTrace include NotificationsTrace def initialize(engine: ActiveSupport::Notifications, **rest) super end end end end graphql-ruby-2.2.17/lib/graphql/tracing/active_support_notifications_tracing.rb000066400000000000000000000012361476434635200301270ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/tracing/notifications_tracing' module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications # with a `graphql` suffix. # # @see KEYS for event names module ActiveSupportNotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = NotificationsTracing::KEYS NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport::Notifications) def self.trace(key, metadata, &blk) NOTIFICATIONS_ENGINE.trace(key, metadata, &blk) end end end end graphql-ruby-2.2.17/lib/graphql/tracing/appoptics_trace.rb000066400000000000000000000204721476434635200236030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create # traces for GraphQL. # # There are 4 configurations available. They can be set in the # appoptics_apm config file or in code. Please see: # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure} # # AppOpticsAPM::Config[:graphql][:enabled] = true|false # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false module AppOpticsTrace # These GraphQL events will show up as 'graphql.prep' spans PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze # These GraphQL events will show up as 'graphql.execute' spans EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze # During auto-instrumentation this version of AppOpticsTracing is compared # with the version provided in the appoptics_apm gem, so that the newer # version of the class can be used def self.version Gem::Version.new('1.0.0') end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time [ 'lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex', 'execute_multiplex', 'execute_query', 'execute_query_lazy', ].each do |trace_method| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = span_name("#{trace_method}") kvs = metadata(data, layer) kvs[:Key] = "#{trace_method}" if (PREP_KEYS + EXEC_KEYS).include?("#{trace_method}") transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute' ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end RUBY end # rubocop:enable Development/NoEvalCop def execute_field(query:, field:, ast_node:, arguments:, object:) return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field @platform_key_cache[AppOpticsTrace].platform_field_key_cache[field] else nil end if platform_key && trace_field return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = platform_key kvs = metadata({query: query, field: field, ast_node: ast_node, arguments: arguments, object: object}, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end else super end end def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object) end def authorized(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_authorized_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end def authorized_lazy(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_authorized_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end def resolve_type(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_resolve_type_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end def resolve_type_lazy(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_resolve_type_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end include PlatformTrace def platform_field_key(field) "graphql.#{field.owner.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end private def gql_config ::AppOpticsAPM::Config[:graphql] ||= {} end def transaction_name(query) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name split_query = query.strip.split(/\W+/, 3) split_query[0] = 'query' if split_query[0].empty? name = "graphql.#{split_query[0..1].join('.')}" ::AppOpticsAPM::SDK.set_transaction_name(name) end def multiplex_transaction_name(names) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name name = "graphql.multiplex.#{names.join('.')}" name = "#{name[0..251]}..." if name.length > 254 ::AppOpticsAPM::SDK.set_transaction_name(name) end def span_name(key) return 'graphql.prep' if PREP_KEYS.include?(key) return 'graphql.execute' if EXEC_KEYS.include?(key) key[/^graphql\./] ? key : "graphql.#{key}" end # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def metadata(data, layer) data.keys.map do |key| case key when :context graphql_context(data[key], layer) when :query graphql_query(data[key]) when :query_string graphql_query_string(data[key]) when :multiplex graphql_multiplex(data[key]) when :path [key, data[key].join(".")] else [key, data[key]] end end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql') end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength def graphql_context(context, layer) context.errors && context.errors.each do |err| AppOpticsAPM::API.log_exception(layer, err) end [[:Path, context.path.join('.')]] end def graphql_query(query) return [] unless query query_string = query.query_string query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [[:InboundQuery, query_string], [:Operation, query.selected_operation_name]] end def graphql_query_string(query_string) query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [:InboundQuery, query_string] end def graphql_multiplex(data) names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!) multiplex_transaction_name(names) if names.size > 1 [:Operations, names.join(', ')] end def sanitize(query) return unless query # remove arguments query.gsub(/"[^"]*"/, '"?"') # strings .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats .gsub(/\[[^\]]*\]/, '[?]') # arrays end def remove_comments(query) return unless query query.gsub(/#[^\n\r]*/, '') end end end end graphql-ruby-2.2.17/lib/graphql/tracing/appoptics_tracing.rb000066400000000000000000000131531476434635200241320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create # traces for GraphQL. # # There are 4 configurations available. They can be set in the # appoptics_apm config file or in code. Please see: # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure} # # AppOpticsAPM::Config[:graphql][:enabled] = true|false # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false class AppOpticsTracing < GraphQL::Tracing::PlatformTracing # These GraphQL events will show up as 'graphql.prep' spans PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze # These GraphQL events will show up as 'graphql.execute' spans EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze # During auto-instrumentation this version of AppOpticsTracing is compared # with the version provided in the appoptics_apm gem, so that the newer # version of the class can be used def self.version Gem::Version.new('1.0.0') end self.platform_keys = { 'lex' => 'lex', 'parse' => 'parse', 'validate' => 'validate', 'analyze_query' => 'analyze_query', 'analyze_multiplex' => 'analyze_multiplex', 'execute_multiplex' => 'execute_multiplex', 'execute_query' => 'execute_query', 'execute_query_lazy' => 'execute_query_lazy' } def platform_trace(platform_key, _key, data) return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = span_name(platform_key) kvs = metadata(data, layer) kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key) transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute' ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end private def gql_config ::AppOpticsAPM::Config[:graphql] ||= {} end def transaction_name(query) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name split_query = query.strip.split(/\W+/, 3) split_query[0] = 'query' if split_query[0].empty? name = "graphql.#{split_query[0..1].join('.')}" ::AppOpticsAPM::SDK.set_transaction_name(name) end def multiplex_transaction_name(names) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name name = "graphql.multiplex.#{names.join('.')}" name = "#{name[0..251]}..." if name.length > 254 ::AppOpticsAPM::SDK.set_transaction_name(name) end def span_name(key) return 'graphql.prep' if PREP_KEYS.include?(key) return 'graphql.execute' if EXEC_KEYS.include?(key) key[/^graphql\./] ? key : "graphql.#{key}" end # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def metadata(data, layer) data.keys.map do |key| case key when :context graphql_context(data[key], layer) when :query graphql_query(data[key]) when :query_string graphql_query_string(data[key]) when :multiplex graphql_multiplex(data[key]) when :path [key, data[key].join(".")] else [key, data[key]] end end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql') end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength def graphql_context(context, layer) context.errors && context.errors.each do |err| AppOpticsAPM::API.log_exception(layer, err) end [[:Path, context.path.join('.')]] end def graphql_query(query) return [] unless query query_string = query.query_string query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [[:InboundQuery, query_string], [:Operation, query.selected_operation_name]] end def graphql_query_string(query_string) query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [:InboundQuery, query_string] end def graphql_multiplex(data) names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!) multiplex_transaction_name(names) if names.size > 1 [:Operations, names.join(', ')] end def sanitize(query) return unless query # remove arguments query.gsub(/"[^"]*"/, '"?"') # strings .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats .gsub(/\[[^\]]*\]/, '[?]') # arrays end def remove_comments(query) return unless query query.gsub(/#[^\n\r]*/, '') end end end end graphql-ruby-2.2.17/lib/graphql/tracing/appsignal_trace.rb000066400000000000000000000046761476434635200235670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module AppsignalTrace include PlatformTrace # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_appsignal_action_name]`. def initialize(set_action_name: false, **rest) @set_action_name = set_action_name super end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) #{ if trace_method == "execute_query" <<-RUBY set_this_txn_name = data[:query].context[:set_appsignal_action_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name) Appsignal::Transaction.current.set_action(transaction_name(data[:query])) end RUBY end } Appsignal.instrument("#{platform_key}") do super end end RUBY end # rubocop:enable Development/NoEvalCop def platform_execute_field(platform_key) Appsignal.instrument(platform_key) do yield end end def platform_authorized(platform_key) Appsignal.instrument(platform_key) do yield end end def platform_resolve_type(platform_key) Appsignal.instrument(platform_key) do yield end end def platform_field_key(field) "#{field.owner.graphql_name}.#{field.graphql_name}.graphql" end def platform_authorized_key(type) "#{type.graphql_name}.authorized.graphql" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type.graphql" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/appsignal_tracing.rb000066400000000000000000000033221476434635200241030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class AppsignalTracing < PlatformTracing self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_appsignal_action_name]`. def initialize(options = {}) @set_action_name = options.fetch(:set_action_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_appsignal_action_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name) Appsignal::Transaction.current.set_action(transaction_name(data[:query])) end end Appsignal.instrument(platform_key) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}.graphql" end def platform_authorized_key(type) "#{type.graphql_name}.authorized.graphql" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type.graphql" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/data_dog_trace.rb000066400000000000000000000154041476434635200233420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module DataDogTrace # @param tracer [#trace] Deprecated # @param analytics_enabled [Boolean] Deprecated # @param analytics_sample_rate [Float] Deprecated def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest) if tracer.nil? tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer end @tracer = tracer @analytics_enabled = analytics_enabled @analytics_sample_rate = analytics_sample_rate @service_name = service @has_prepare_span = respond_to?(:prepare_span) super end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { 'lex' => 'lex.graphql', 'parse' => 'parse.graphql', 'validate' => 'validate.graphql', 'analyze_query' => 'analyze.graphql', 'analyze_multiplex' => 'analyze.graphql', 'execute_multiplex' => 'execute.graphql', 'execute_query' => 'execute.graphql', 'execute_query_lazy' => 'execute.graphql', }.each do |trace_method, trace_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) @tracer.trace("#{trace_key}", service: @service_name, type: 'custom') do |span| span.set_tag('component', 'graphql') span.set_tag('operation', '#{trace_method}') #{ if trace_method == 'execute_multiplex' <<-RUBY operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ') resource = if operations.empty? first_query = data[:multiplex].queries.first fallback_transaction_name(first_query && first_query.context) else operations end span.resource = resource if resource # [Deprecated] will be removed in the future span.set_metric('_dd1.sr.eausr', @analytics_sample_rate) if @analytics_enabled RUBY elsif trace_method == 'execute_query' <<-RUBY span.set_tag(:selected_operation_name, data[:query].selected_operation_name) span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type) span.set_tag(:query_string, data[:query].query_string) RUBY end } if @has_prepare_span prepare_span("#{trace_method.sub("platform_", "")}", data, span) end super end end RUBY end # rubocop:enable Development/NoEvalCop def execute_field_span(span_key, query, field, ast_node, arguments, object) return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field @platform_key_cache[DataDogTrace].platform_field_key_cache[field] else nil end if platform_key && trace_field @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span| span.set_tag('component', 'graphql') span.set_tag('operation', span_key) if @has_prepare_span prepare_span_data = { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object } prepare_span(span_key, prepare_span_data, span) end yield end else yield end end def execute_field(query:, field:, ast_node:, arguments:, object:) execute_field_span("execute_field", query, field, ast_node, arguments, object) do super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object) end end def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) execute_field_span("execute_field_lazy", query, field, ast_node, arguments, object) do super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object) end end def authorized(query:, type:, object:) authorized_span("authorized", object, type, query) do super(query: query, type: type, object: object) end end def authorized_span(span_key, object, type, query) platform_key = @platform_key_cache[DataDogTrace].platform_authorized_key_cache[type] @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span| span.set_tag('component', 'graphql') span.set_tag('operation', span_key) if @has_prepare_span prepare_span(span_key, {object: object, type: type, query: query}, span) end yield end end def authorized_lazy(object:, type:, query:) authorized_span("authorized_lazy", object, type, query) do super(query: query, type: type, object: object) end end def resolve_type(object:, type:, query:) resolve_type_span("resolve_type", object, type, query) do super(object: object, query: query, type: type) end end def resolve_type_lazy(object:, type:, query:) resolve_type_span("resolve_type_lazy", object, type, query) do super(object: object, query: query, type: type) end end def resolve_type_span(span_key, object, type, query) platform_key = @platform_key_cache[DataDogTrace].platform_resolve_type_key_cache[type] @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span| span.set_tag('component', 'graphql') span.set_tag('operation', span_key) if @has_prepare_span prepare_span(span_key, {object: object, type: type, query: query}, span) end yield end end include PlatformTrace # Implement this method in a subclass to apply custom tags to datadog spans # @param key [String] The event being traced # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event) # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event # def prepare_span(key, data, span) # end def platform_field_key(field) field.path end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/data_dog_tracing.rb000066400000000000000000000056261476434635200237000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class DataDogTracing < PlatformTracing self.platform_keys = { 'lex' => 'lex.graphql', 'parse' => 'parse.graphql', 'validate' => 'validate.graphql', 'analyze_query' => 'analyze.graphql', 'analyze_multiplex' => 'analyze.graphql', 'execute_multiplex' => 'execute.graphql', 'execute_query' => 'execute.graphql', 'execute_query_lazy' => 'execute.graphql', } def platform_trace(platform_key, key, data) tracer.trace(platform_key, service: options[:service], type: 'custom') do |span| span.set_tag('component', 'graphql') span.set_tag('operation', key) if key == 'execute_multiplex' operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ') resource = if operations.empty? first_query = data[:multiplex].queries.first fallback_transaction_name(first_query && first_query.context) else operations end span.resource = resource if resource # [Deprecated] will be removed in the future span.set_metric('_dd1.sr.eausr', analytics_sample_rate) if analytics_enabled? end if key == 'execute_query' span.set_tag(:selected_operation_name, data[:query].selected_operation_name) span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type) span.set_tag(:query_string, data[:query].query_string) end prepare_span(key, data, span) yield end end # Implement this method in a subclass to apply custom tags to datadog spans # @param key [String] The event being traced # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event) # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event def prepare_span(key, data, span) end def tracer default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer # [Deprecated] options[:tracer] will be removed in the future options.fetch(:tracer, default_tracer) end def analytics_enabled? # [Deprecated] options[:analytics_enabled] will be removed in the future options.fetch(:analytics_enabled, false) end def analytics_sample_rate # [Deprecated] options[:analytics_sample_rate] will be removed in the future options.fetch(:analytics_sample_rate, 1.0) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/legacy_hooks_trace.rb000066400000000000000000000050311476434635200242420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module LegacyHooksTrace def execute_multiplex(multiplex:) multiplex_instrumenters = multiplex.schema.instrumenters[:multiplex] query_instrumenters = multiplex.schema.instrumenters[:query] # First, run multiplex instrumentation, then query instrumentation for each query RunHooks.call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do RunHooks.each_query_call_hooks(query_instrumenters, multiplex.queries) do super end end end module RunHooks module_function # Call the before_ hooks of each query, # Then yield if no errors. # `call_hooks` takes care of appropriate cleanup. def each_query_call_hooks(instrumenters, queries, i = 0) if i >= queries.length yield else query = queries[i] call_hooks(instrumenters, query, :before_query, :after_query) { each_query_call_hooks(instrumenters, queries, i + 1) { yield } } end end # Call each before hook, and if they all succeed, yield. # If they don't all succeed, call after_ for each one that succeeded. def call_hooks(instrumenters, object, before_hook_name, after_hook_name) begin successful = [] instrumenters.each do |instrumenter| instrumenter.public_send(before_hook_name, object) successful << instrumenter end # if any before hooks raise an exception, quit calling before hooks, # but call the after hooks on anything that succeeded but also # raise the exception that came from the before hook. rescue GraphQL::ExecutionError => err object.context.errors << err rescue => e raise call_after_hooks(successful, object, after_hook_name, e) end begin yield # Call the user code ensure ex = call_after_hooks(successful, object, after_hook_name, nil) raise ex if ex end end def call_after_hooks(instrumenters, object, after_hook_name, ex) instrumenters.reverse_each do |instrumenter| begin instrumenter.public_send(after_hook_name, object) rescue => e ex = e end end ex end end end end end graphql-ruby-2.2.17/lib/graphql/tracing/legacy_trace.rb000066400000000000000000000054221476434635200230430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This trace class calls legacy-style tracer with payload hashes. # New-style `trace_with` modules significantly reduce the overhead of tracing, # but that advantage is lost when legacy-style tracers are also used (since the payload hashes are still constructed). module CallLegacyTracers def lex(query_string:) (@multiplex || @query).trace("lex", { query_string: query_string }) { super } end def parse(query_string:) (@multiplex || @query).trace("parse", { query_string: query_string }) { super } end def validate(query:, validate:) query.trace("validate", { validate: validate, query: query }) { super } end def analyze_multiplex(multiplex:) multiplex.trace("analyze_multiplex", { multiplex: multiplex }) { super } end def analyze_query(query:) query.trace("analyze_query", { query: query }) { super } end def execute_multiplex(multiplex:) multiplex.trace("execute_multiplex", { multiplex: multiplex }) { super } end def execute_query(query:) query.trace("execute_query", { query: query }) { super } end def execute_query_lazy(query:, multiplex:) multiplex.trace("execute_query_lazy", { multiplex: multiplex, query: query }) { super } end def execute_field(field:, query:, ast_node:, arguments:, object:) query.trace("execute_field", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super } end def execute_field_lazy(field:, query:, ast_node:, arguments:, object:) query.trace("execute_field_lazy", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super } end def authorized(query:, type:, object:) query.trace("authorized", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end def authorized_lazy(query:, type:, object:) query.trace("authorized_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end def resolve_type(query:, type:, object:) query.trace("resolve_type", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end def resolve_type_lazy(query:, type:, object:) query.trace("resolve_type_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end end class LegacyTrace < Trace include CallLegacyTracers end end end graphql-ruby-2.2.17/lib/graphql/tracing/new_relic_trace.rb000066400000000000000000000046461476434635200235550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module NewRelicTrace include PlatformTrace # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_new_relic_transaction_name]`. def initialize(set_transaction_name: false, **_rest) @set_transaction_name = set_transaction_name super end def execute_query(query:) set_this_txn_name = query.context[:set_new_relic_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) NewRelic::Agent.set_transaction_name(transaction_name(query)) end NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped("GraphQL/execute") do super end end { "lex" => "GraphQL/lex", "parse" => "GraphQL/parse", "validate" => "GraphQL/validate", "analyze_query" => "GraphQL/analyze", "analyze_multiplex" => "GraphQL/analyze", "execute_multiplex" => "GraphQL/execute", "execute_query_lazy" => "GraphQL/execute", }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**_keys) NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped("#{platform_key}") do super end end RUBY end def platform_execute_field(platform_key) NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_authorized(platform_key) NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_resolve_type(platform_key) NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_field_key(field) "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}" end def platform_authorized_key(type) "GraphQL/Authorize/#{type.graphql_name}" end def platform_resolve_type_key(type) "GraphQL/ResolveType/#{type.graphql_name}" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/new_relic_tracing.rb000066400000000000000000000034171476434635200241010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class NewRelicTracing < PlatformTracing self.platform_keys = { "lex" => "GraphQL/lex", "parse" => "GraphQL/parse", "validate" => "GraphQL/validate", "analyze_query" => "GraphQL/analyze", "analyze_multiplex" => "GraphQL/analyze", "execute_multiplex" => "GraphQL/execute", "execute_query" => "GraphQL/execute", "execute_query_lazy" => "GraphQL/execute", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_new_relic_transaction_name]`. def initialize(options = {}) @set_transaction_name = options.fetch(:set_transaction_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_new_relic_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) NewRelic::Agent.set_transaction_name(transaction_name(data[:query])) end end NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_field_key(type, field) "GraphQL/#{type.graphql_name}/#{field.graphql_name}" end def platform_authorized_key(type) "GraphQL/Authorize/#{type.graphql_name}" end def platform_resolve_type_key(type) "GraphQL/ResolveType/#{type.graphql_name}" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/notifications_trace.rb000066400000000000000000000033001476434635200244410ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/tracing/platform_trace" module GraphQL module Tracing # This implementation forwards events to a notification handler (i.e. # ActiveSupport::Notifications or Dry::Monitor::Notifications) # with a `graphql` suffix. module NotificationsTrace # Initialize a new NotificationsTracing instance # # @param engine [#instrument(key, metadata, block)] The notifications engine to use def initialize(engine:, **rest) @notifications_engine = engine super end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_multiplex" => "analyze_multiplex.graphql", "analyze_query" => "analyze_query.graphql", "execute_multiplex" => "execute_multiplex.graphql", "execute_query" => "execute_query.graphql", "execute_query_lazy" => "execute_query_lazy.graphql", "execute_field" => "execute_field.graphql", "execute_field_lazy" => "execute_field_lazy.graphql", "authorized" => "authorized.graphql", "authorized_lazy" => "authorized_lazy.graphql", "resolve_type" => "resolve_type.graphql", "resolve_type_lazy" => "resolve_type.graphql", }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**metadata, &blk) @notifications_engine.instrument("#{platform_key}", metadata, &blk) end RUBY end # rubocop:enable Development/NoEvalCop include PlatformTrace end end end graphql-ruby-2.2.17/lib/graphql/tracing/notifications_tracing.rb000066400000000000000000000041361476434635200250020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This implementation forwards events to a notification handler (i.e. # ActiveSupport::Notifications or Dry::Monitor::Notifications) # with a `graphql` suffix. # # @see KEYS for event names class NotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_multiplex" => "analyze_multiplex.graphql", "analyze_query" => "analyze_query.graphql", "execute_query" => "execute_query.graphql", "execute_query_lazy" => "execute_query_lazy.graphql", "execute_field" => "execute_field.graphql", "execute_field_lazy" => "execute_field_lazy.graphql", "authorized" => "authorized.graphql", "authorized_lazy" => "authorized_lazy.graphql", "resolve_type" => "resolve_type.graphql", "resolve_type_lazy" => "resolve_type.graphql", } MAX_KEYS_SIZE = 100 # Initialize a new NotificationsTracing instance # # @param [Object] notifications_engine The notifications engine to use def initialize(notifications_engine) @notifications_engine = notifications_engine end # Sends a GraphQL tracing event to the notification handler # # @example # . notifications_engine = Dry::Monitor::Notifications.new(:graphql) # . tracer = GraphQL::Tracing::NotificationsTracing.new(notifications_engine) # . tracer.trace("lex") { ... } # # @param [string] key The key for the event # @param [Hash] metadata The metadata for the event # @yield The block to execute for the event def trace(key, metadata, &blk) prefixed_key = KEYS[key] || "#{key}.graphql" # Cache the new keys while making sure not to induce a memory leak if KEYS.size < MAX_KEYS_SIZE KEYS[key] ||= prefixed_key end @notifications_engine.instrument(prefixed_key, metadata, &blk) end end end end graphql-ruby-2.2.17/lib/graphql/tracing/platform_trace.rb000066400000000000000000000101641476434635200234220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module PlatformTrace def initialize(trace_scalars: false, **_options) @trace_scalars = trace_scalars @platform_key_cache = Hash.new { |h, mod| h[mod] = mod::KeyCache.new } super end module BaseKeyCache def initialize @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) } @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) } @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) } end attr_reader :platform_field_key_cache, :platform_authorized_key_cache, :platform_resolve_type_key_cache end def platform_execute_field_lazy(*args, &block) platform_execute_field(*args, &block) end def platform_authorized_lazy(key, &block) platform_authorized(key, &block) end def platform_resolve_type_lazy(key, &block) platform_resolve_type(key, &block) end def self.included(child_class) key_methods_class = Class.new { include(child_class) include(BaseKeyCache) } child_class.const_set(:KeyCache, key_methods_class) # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time [:execute_field, :execute_field_lazy].each do |field_trace_method| if !child_class.method_defined?(field_trace_method) child_class.module_eval <<-RUBY, __FILE__, __LINE__ def #{field_trace_method}(query:, field:, ast_node:, arguments:, object:) return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field @platform_key_cache[#{child_class}].platform_field_key_cache[field] else nil end if platform_key && trace_field platform_#{field_trace_method}(platform_key) do super end else super end end RUBY end end [:authorized, :authorized_lazy].each do |auth_trace_method| if !child_class.method_defined?(auth_trace_method) child_class.module_eval <<-RUBY, __FILE__, __LINE__ def #{auth_trace_method}(type:, query:, object:) platform_key = @platform_key_cache[#{child_class}].platform_authorized_key_cache[type] platform_#{auth_trace_method}(platform_key) do super end end RUBY end end [:resolve_type, :resolve_type_lazy].each do |rt_trace_method| if !child_class.method_defined?(rt_trace_method) child_class.module_eval <<-RUBY, __FILE__, __LINE__ def #{rt_trace_method}(query:, type:, object:) platform_key = @platform_key_cache[#{child_class}].platform_resolve_type_key_cache[type] platform_#{rt_trace_method}(platform_key) do super end end RUBY end # rubocop:enable Development/NoEvalCop end end private # Get the transaction name based on the operation type and name if possible, or fall back to a user provided # one. Useful for anonymous queries. def transaction_name(query) selected_op = query.selected_operation txn_name = if selected_op op_type = selected_op.operation_type op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous" "#{op_type}.#{op_name}" else "query.anonymous" end "GraphQL/#{txn_name}" end def fallback_transaction_name(context) context[:tracing_fallback_transaction_name] end end end end graphql-ruby-2.2.17/lib/graphql/tracing/platform_tracing.rb000066400000000000000000000114221476434635200237510ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # Each platform provides: # - `.platform_keys` # - `#platform_trace` # - `#platform_field_key(type, field)` # @api private class PlatformTracing class << self attr_accessor :platform_keys def inherited(child_class) child_class.platform_keys = self.platform_keys end end def initialize(options = {}) @options = options @platform_keys = self.class.platform_keys @trace_scalars = options.fetch(:trace_scalars, false) end def trace(key, data) case key when "lex", "parse", "validate", "analyze_query", "analyze_multiplex", "execute_query", "execute_query_lazy", "execute_multiplex" platform_key = @platform_keys.fetch(key) platform_trace(platform_key, key, data) do yield end when "execute_field", "execute_field_lazy" field = data[:field] return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field context = data.fetch(:query).context cached_platform_key(context, field, :field) { platform_field_key(field.owner, field) } else nil end if platform_key && trace_field platform_trace(platform_key, key, data) do yield end else yield end when "authorized", "authorized_lazy" type = data.fetch(:type) context = data.fetch(:context) platform_key = cached_platform_key(context, type, :authorized) { platform_authorized_key(type) } platform_trace(platform_key, key, data) do yield end when "resolve_type", "resolve_type_lazy" type = data.fetch(:type) context = data.fetch(:context) platform_key = cached_platform_key(context, type, :resolve_type) { platform_resolve_type_key(type) } platform_trace(platform_key, key, data) do yield end else # it's a custom key yield end end def self.use(schema_defn, options = {}) if options[:legacy_tracing] tracer = self.new(**options) schema_defn.tracer(tracer) else tracing_name = self.name.split("::").last trace_name = tracing_name.sub("Tracing", "Trace") if GraphQL::Tracing.const_defined?(trace_name, false) trace_module = GraphQL::Tracing.const_get(trace_name) warn("`use(#{self.name})` is deprecated, use the equivalent `trace_with(#{trace_module.name})` instead. More info: https://graphql-ruby.org/queries/tracing.html") schema_defn.trace_with(trace_module, **options) else warn("`use(#{self.name})` and `Tracing::PlatformTracing` are deprecated. Use a `trace_with(...)` module instead. More info: https://graphql-ruby.org/queries/tracing.html. Please open an issue on the GraphQL-Ruby repo if you want to discuss further!") tracer = self.new(**options) schema_defn.tracer(tracer) end end end private # Get the transaction name based on the operation type and name if possible, or fall back to a user provided # one. Useful for anonymous queries. def transaction_name(query) selected_op = query.selected_operation txn_name = if selected_op op_type = selected_op.operation_type op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous" "#{op_type}.#{op_name}" else "query.anonymous" end "GraphQL/#{txn_name}" end def fallback_transaction_name(context) context[:tracing_fallback_transaction_name] end attr_reader :options # Different kind of schema objects have different kinds of keys: # # - Object types: `.authorized` # - Union/Interface types: `.resolve_type` # - Fields: execution # # So, they can all share one cache. # # If the key isn't present, the given block is called and the result is cached for `key`. # # @param ctx [GraphQL::Query::Context] # @param key [Class, GraphQL::Field] A part of the schema # @param trace_phase [Symbol] The stage of execution being traced (used by OpenTelementry tracing) # @return [String] def cached_platform_key(ctx, key, trace_phase) cache = ctx.namespace(self.class)[:platform_key_cache] ||= {} cache.fetch(key) { cache[key] = yield } end end end end graphql-ruby-2.2.17/lib/graphql/tracing/prometheus_trace.rb000066400000000000000000000053511476434635200237730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module PrometheusTrace include PlatformTrace def initialize(client: PrometheusExporter::Client.default, keys_whitelist: ["execute_field", "execute_field_lazy"], collector_type: "graphql", **rest) @client = client @keys_whitelist = keys_whitelist @collector_type = collector_type super(**rest) end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze", 'analyze_multiplex' => "graphql.analyze", 'execute_multiplex' => "graphql.execute", 'execute_query' => "graphql.execute", 'execute_query_lazy' => "graphql.execute", }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data, &block) instrument_execution("#{platform_key}", "#{trace_method}", &block) end RUBY end # rubocop:enable Development/NoEvalCop def platform_execute_field(platform_key, &block) instrument_execution(platform_key, "execute_field", &block) end def platform_execute_field_lazy(platform_key, &block) instrument_execution(platform_key, "execute_field_lazy", &block) end def platform_authorized(platform_key, &block) instrument_execution(platform_key, "authorized", &block) end def platform_authorized_lazy(platform_key, &block) instrument_execution(platform_key, "authorized_lazy", &block) end def platform_resolve_type(platform_key, &block) instrument_execution(platform_key, "resolve_type", &block) end def platform_resolve_type_lazy(platform_key, &block) instrument_execution(platform_key, "resolve_type_lazy", &block) end def platform_field_key(field) field.path end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end private def instrument_execution(platform_key, key, &block) if @keys_whitelist.include?(key) start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC result = block.call duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start @client.send_json( type: @collector_type, duration: duration, platform_key: platform_key, key: key ) result else yield end end end end end graphql-ruby-2.2.17/lib/graphql/tracing/prometheus_trace/000077500000000000000000000000001476434635200234425ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/tracing/prometheus_trace/graphql_collector.rb000066400000000000000000000016621476434635200275000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module PrometheusTrace class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector def initialize @graphql_gauge = PrometheusExporter::Metric::Base.default_aggregation.new( 'graphql_duration_seconds', 'Time spent in GraphQL operations, in seconds' ) end def type 'graphql' end def collect(object) default_labels = { key: object['key'], platform_key: object['platform_key'] } custom = object['custom_labels'] labels = custom.nil? ? default_labels : default_labels.merge(custom) @graphql_gauge.observe object['duration'], labels end def metrics [@graphql_gauge] end end end # Backwards-compat: PrometheusTracing::GraphQLCollector = PrometheusTrace::GraphQLCollector end end graphql-ruby-2.2.17/lib/graphql/tracing/prometheus_tracing.rb000066400000000000000000000037471476434635200243330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class PrometheusTracing < PlatformTracing DEFAULT_WHITELIST = ['execute_field', 'execute_field_lazy'].freeze DEFAULT_COLLECTOR_TYPE = 'graphql'.freeze self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze", 'analyze_multiplex' => "graphql.analyze", 'execute_multiplex' => "graphql.execute", 'execute_query' => "graphql.execute", 'execute_query_lazy' => "graphql.execute", 'execute_field' => "graphql.execute", 'execute_field_lazy' => "graphql.execute" } def initialize(opts = {}) @client = opts[:client] || PrometheusExporter::Client.default @keys_whitelist = opts[:keys_whitelist] || DEFAULT_WHITELIST @collector_type = opts[:collector_type] || DEFAULT_COLLECTOR_TYPE super opts end def platform_trace(platform_key, key, _data, &block) return yield unless @keys_whitelist.include?(key) instrument_execution(platform_key, key, &block) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end private def instrument_execution(platform_key, key, &block) start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC result = block.call duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start observe platform_key, key, duration result end def observe(platform_key, key, duration) @client.send_json( type: @collector_type, duration: duration, platform_key: platform_key, key: key ) end end end end graphql-ruby-2.2.17/lib/graphql/tracing/scout_trace.rb000066400000000000000000000046551476434635200227430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module ScoutTrace include PlatformTrace INSTRUMENT_OPTS = { scope: true } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_scout_transaction_name]`. def initialize(set_transaction_name: false, **_rest) self.class.include(ScoutApm::Tracer) @set_transaction_name = set_transaction_name super end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) #{ if trace_method == "execute_query" <<-RUBY set_this_txn_name = data[:query].context[:set_scout_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) ScoutApm::Transaction.rename(transaction_name(data[:query])) end RUBY end } self.class.instrument("GraphQL", "#{platform_key}", INSTRUMENT_OPTS) do super end end RUBY end # rubocop:enable Development/NoEvalCop def platform_execute_field(platform_key, &block) self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block) end def platform_authorized(platform_key, &block) self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS, &block) end alias :platform_resolve_type :platform_authorized def platform_field_key(field) field.path end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/scout_tracing.rb000066400000000000000000000034711476434635200232670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class ScoutTracing < PlatformTracing INSTRUMENT_OPTS = { scope: true } self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_scout_transaction_name]`. def initialize(options = {}) self.class.include ScoutApm::Tracer @set_transaction_name = options.fetch(:set_transaction_name, false) super(options) end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_scout_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) ScoutApm::Transaction.rename(transaction_name(data[:query])) end end self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/sentry_trace.rb000066400000000000000000000100711476434635200231170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module SentryTrace include PlatformTrace # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_sentry_transaction_name]`. def initialize(set_transaction_name: false, **_rest) @set_transaction_name = set_transaction_name super end def execute_query(**data) set_this_txn_name = data[:query].context[:set_sentry_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) Sentry.configure_scope do |scope| scope.set_transaction_name(transaction_name(data[:query])) end end instrument_execution("graphql.execute", "execute_query", data) { super } end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { "lex" => "graphql.lex", "parse" => "graphql.parse", "validate" => "graphql.validate", "analyze_query" => "graphql.analyze", "analyze_multiplex" => "graphql.analyze_multiplex", "execute_multiplex" => "graphql.execute_multiplex", "execute_query_lazy" => "graphql.execute" }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) instrument_execution("#{platform_key}", "#{trace_method}", data) { super } end RUBY end # rubocop:enable Development/NoEvalCop def platform_execute_field(platform_key, &block) instrument_execution(platform_key, "execute_field", &block) end def platform_execute_field_lazy(platform_key, &block) instrument_execution(platform_key, "execute_field_lazy", &block) end def platform_authorized(platform_key, &block) instrument_execution(platform_key, "authorized", &block) end def platform_authorized_lazy(platform_key, &block) instrument_execution(platform_key, "authorized_lazy", &block) end def platform_resolve_type(platform_key, &block) instrument_execution(platform_key, "resolve_type", &block) end def platform_resolve_type_lazy(platform_key, &block) instrument_execution(platform_key, "resolve_type_lazy", &block) end def platform_field_key(field) "graphql.field.#{field.path}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end private def instrument_execution(platform_key, trace_method, data=nil, &block) return yield unless Sentry.initialized? Sentry.with_child_span(op: platform_key, start_timestamp: Sentry.utc_now.to_f) do |span| result = yield return result unless span span.finish if trace_method == "execute_multiplex" && data.key?(:multiplex) operation_names = data[:multiplex].queries.map{|q| operation_name(q) } span.set_description(operation_names.join(", ")) elsif trace_method == "execute_query" && data.key?(:query) span.set_description(operation_name(data[:query])) span.set_data('graphql.document', data[:query].query_string) span.set_data('graphql.operation.name', data[:query].selected_operation_name) if data[:query].selected_operation_name span.set_data('graphql.operation.type', data[:query].selected_operation.operation_type) end result end end def operation_name(query) selected_op = query.selected_operation if selected_op [selected_op.operation_type, selected_op.name].compact.join(' ') else 'GraphQL Operation' end end end end end graphql-ruby-2.2.17/lib/graphql/tracing/statsd_trace.rb000066400000000000000000000031061476434635200230760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing module StatsdTrace include PlatformTrace # @param statsd [Object] A statsd client def initialize(statsd:, **rest) @statsd = statsd super(**rest) end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze_query", 'analyze_multiplex' => "graphql.analyze_multiplex", 'execute_multiplex' => "graphql.execute_multiplex", 'execute_query' => "graphql.execute_query", 'execute_query_lazy' => "graphql.execute_query_lazy", }.each do |trace_method, platform_key| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) @statsd.time("#{platform_key}") do super end end RUBY end # rubocop:enable Development/NoEvalCop def platform_execute_field(platform_key, &block) @statsd.time(platform_key, &block) end def platform_authorized(key, &block) @statsd.time(key, &block) end alias :platform_resolve_type :platform_authorized def platform_field_key(field) "graphql.#{field.path}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/statsd_tracing.rb000066400000000000000000000021721476434635200234310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class StatsdTracing < PlatformTracing self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze_query", 'analyze_multiplex' => "graphql.analyze_multiplex", 'execute_multiplex' => "graphql.execute_multiplex", 'execute_query' => "graphql.execute_query", 'execute_query_lazy' => "graphql.execute_query_lazy", } # @param statsd [Object] A statsd client def initialize(statsd:, **rest) @statsd = statsd super(**rest) end def platform_trace(platform_key, key, data) @statsd.time(platform_key) do yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end end end end graphql-ruby-2.2.17/lib/graphql/tracing/trace.rb000066400000000000000000000034001476434635200215110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This is the base class for a `trace` instance whose methods are called during query execution. # "Trace modes" are subclasses of this with custom tracing modules mixed in. # # A trace module may implement any of the methods on `Trace`, being sure to call `super` # to continue any tracing hooks and call the actual runtime behavior. See {GraphQL::Backtrace::Trace} for example. # class Trace # @param multiplex [GraphQL::Execution::Multiplex, nil] # @param query [GraphQL::Query, nil] def initialize(multiplex: nil, query: nil, **_options) @multiplex = multiplex @query = query end # The Ruby parser doesn't call this method (`graphql/c_parser` does.) def lex(query_string:) yield end def parse(query_string:) yield end def validate(query:, validate:) yield end def analyze_multiplex(multiplex:) yield end def analyze_query(query:) yield end def execute_multiplex(multiplex:) yield end def execute_query(query:) yield end def execute_query_lazy(query:, multiplex:) yield end def execute_field(field:, query:, ast_node:, arguments:, object:) yield end def execute_field_lazy(field:, query:, ast_node:, arguments:, object:) yield end def authorized(query:, type:, object:) yield end def authorized_lazy(query:, type:, object:) yield end def resolve_type(query:, type:, object:) yield end def resolve_type_lazy(query:, type:, object:) yield end end end end graphql-ruby-2.2.17/lib/graphql/type_kinds.rb000066400000000000000000000060131476434635200211400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Type kinds are the basic categories which a type may belong to (`Object`, `Scalar`, `Union`...) module TypeKinds # These objects are singletons, eg `GraphQL::TypeKinds::UNION`, `GraphQL::TypeKinds::SCALAR`. class TypeKind attr_reader :name, :description def initialize(name, abstract: false, leaf: false, fields: false, wraps: false, input: false, description: nil) @name = name @abstract = abstract @fields = fields @wraps = wraps @input = input @leaf = leaf @composite = fields? || abstract? @description = description end # Does this TypeKind have multiple possible implementors? # @deprecated Use `abstract?` instead of `resolves?`. def resolves?; @abstract; end # Is this TypeKind abstract? def abstract?; @abstract; end # Does this TypeKind have queryable fields? def fields?; @fields; end # Does this TypeKind modify another type? def wraps?; @wraps; end # Is this TypeKind a valid query input? def input?; @input; end def to_s; @name; end # Is this TypeKind a primitive value? def leaf?; @leaf; end # Is this TypeKind composed of many values? def composite?; @composite; end def scalar? self == TypeKinds::SCALAR end def object? self == TypeKinds::OBJECT end def interface? self == TypeKinds::INTERFACE end def union? self == TypeKinds::UNION end def enum? self == TypeKinds::ENUM end def input_object? self == TypeKinds::INPUT_OBJECT end def list? self == TypeKinds::LIST end def non_null? self == TypeKinds::NON_NULL end end TYPE_KINDS = [ SCALAR = TypeKind.new("SCALAR", input: true, leaf: true, description: 'Indicates this type is a scalar.'), OBJECT = TypeKind.new("OBJECT", fields: true, description: 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'), INTERFACE = TypeKind.new("INTERFACE", abstract: true, fields: true, description: 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'), UNION = TypeKind.new("UNION", abstract: true, description: 'Indicates this type is a union. `possibleTypes` is a valid field.'), ENUM = TypeKind.new("ENUM", input: true, leaf: true, description: 'Indicates this type is an enum. `enumValues` is a valid field.'), INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true, description: 'Indicates this type is an input object. `inputFields` is a valid field.'), LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'), NON_NULL = TypeKind.new("NON_NULL", wraps: true, description: 'Indicates this type is a non-null. `ofType` is a valid field.'), ] end end graphql-ruby-2.2.17/lib/graphql/types.rb000066400000000000000000000006101476434635200201300ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/boolean" require "graphql/types/big_int" require "graphql/types/float" require "graphql/types/id" require "graphql/types/int" require "graphql/types/iso_8601_date" require "graphql/types/iso_8601_date_time" require "graphql/types/iso_8601_duration" require "graphql/types/json" require "graphql/types/string" require "graphql/types/relay" graphql-ruby-2.2.17/lib/graphql/types/000077500000000000000000000000001476434635200176065ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/types/big_int.rb000066400000000000000000000011071476434635200215450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class BigInt < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string." def self.coerce_input(value, _ctx) value && parse_int(value) rescue ArgumentError nil end def self.coerce_result(value, _ctx) value.to_i.to_s end def self.parse_int(value) value.is_a?(Numeric) ? value : Integer(value, 10) end end end end graphql-ruby-2.2.17/lib/graphql/types/boolean.rb000066400000000000000000000005771476434635200215630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class Boolean < GraphQL::Schema::Scalar description "Represents `true` or `false` values." def self.coerce_input(value, _ctx) (value == true || value == false) ? value : nil end def self.coerce_result(value, _ctx) !!value end default_scalar true end end end graphql-ruby-2.2.17/lib/graphql/types/float.rb000066400000000000000000000007301476434635200212400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class Float < GraphQL::Schema::Scalar description "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)." def self.coerce_input(value, _ctx) value.is_a?(Numeric) ? value.to_f : nil end def self.coerce_result(value, _ctx) value.to_f end default_scalar true end end end graphql-ruby-2.2.17/lib/graphql/types/id.rb000066400000000000000000000015051476434635200205300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class ID < GraphQL::Schema::Scalar graphql_name "ID" description "Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"VXNlci0xMA==\"`) or integer (such as `4`) input value will be accepted as an ID." default_scalar true def self.coerce_result(value, _ctx) value.is_a?(::String) ? value : value.to_s end def self.coerce_input(value, _ctx) case value when ::String value when Integer value.to_s else nil end end end end end graphql-ruby-2.2.17/lib/graphql/types/int.rb000066400000000000000000000016521476434635200207310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # @see {Types::BigInt} for handling integers outside 32-bit range. class Int < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." MIN = -(2**31) MAX = (2**31) - 1 def self.coerce_input(value, ctx) return if !value.is_a?(Integer) if value >= MIN && value <= MAX value else err = GraphQL::IntegerDecodingError.new(value) ctx.schema.type_error(err, ctx) end end def self.coerce_result(value, ctx) value = value.to_i if value >= MIN && value <= MAX value else err = GraphQL::IntegerEncodingError.new(value, context: ctx) ctx.schema.type_error(err, ctx) end end default_scalar true end end end graphql-ruby-2.2.17/lib/graphql/types/iso_8601_date.rb000066400000000000000000000024551476434635200224060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # This scalar takes `Date`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :published_at, GraphQL::Types::ISO8601Date, null: false # # argument :deliver_at, GraphQL::Types::ISO8601Date, null: false # # Alternatively, use this built-in scalar as inspiration for your # own Date type. class ISO8601Date < GraphQL::Schema::Scalar description "An ISO 8601-encoded date" specified_by_url "https://tools.ietf.org/html/rfc3339" # @param value [Date,Time,DateTime,String] # @return [String] def self.coerce_result(value, _ctx) Date.parse(value.to_s).iso8601 end # @param str_value [String, Date, DateTime, Time] # @return [Date, nil] def self.coerce_input(value, ctx) if value.is_a?(::Date) value elsif value.is_a?(::DateTime) value.to_date elsif value.is_a?(::Time) value.to_date elsif value.nil? nil else Date.iso8601(value) end rescue ArgumentError, TypeError err = GraphQL::DateEncodingError.new(value) ctx.schema.type_error(err, ctx) end end end end graphql-ruby-2.2.17/lib/graphql/types/iso_8601_date_time.rb000066400000000000000000000047031476434635200234220ustar00rootroot00000000000000# frozen_string_literal: true require 'time' module GraphQL module Types # This scalar takes `Time`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :created_at, GraphQL::Types::ISO8601DateTime, null: false # # argument :deliver_at, GraphQL::Types::ISO8601DateTime, null: false # # Alternatively, use this built-in scalar as inspiration for your # own DateTime type. class ISO8601DateTime < GraphQL::Schema::Scalar description "An ISO 8601-encoded datetime" specified_by_url "https://tools.ietf.org/html/rfc3339" # It's not compatible with Rails' default, # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default) DEFAULT_TIME_PRECISION = 0 # @return [Integer] def self.time_precision @time_precision || DEFAULT_TIME_PRECISION end # @param [Integer] value def self.time_precision=(value) @time_precision = value end # @param value [Time,Date,DateTime,String] # @return [String] def self.coerce_result(value, _ctx) case value when Date return value.to_time.iso8601(time_precision) when ::String return Time.parse(value).iso8601(time_precision) else # Time, DateTime or compatible is given: return value.iso8601(time_precision) end rescue StandardError => error raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only Times, Dates, DateTimes, and well-formatted Strings are used with this type. (#{error.message})" end # @param str_value [String] # @return [Time] def self.coerce_input(str_value, _ctx) Time.iso8601(str_value) rescue ArgumentError, TypeError begin dt = Date.iso8601(str_value).to_time # For compatibility, continue accepting dates given without times # But without this, it would zero out given any time part of `str_value` (hours and/or minutes) if dt.iso8601.start_with?(str_value) dt elsif str_value.length == 8 && str_value.match?(/\A\d{8}\Z/) # Allow dates that are missing the "-". eg. "20220404" dt else nil end rescue ArgumentError, TypeError # Invalid input nil end end end end end graphql-ruby-2.2.17/lib/graphql/types/iso_8601_duration.rb000066400000000000000000000054701476434635200233160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # This scalar takes `Duration`s and transmits them as strings, # using ISO 8601 format. ActiveSupport >= 5.0 must be loaded to use # this scalar. # # Use it for fields or arguments as follows: # # field :age, GraphQL::Types::ISO8601Duration, null: false # # argument :interval, GraphQL::Types::ISO8601Duration, null: false # # Alternatively, use this built-in scalar as inspiration for your # own Duration type. class ISO8601Duration < GraphQL::Schema::Scalar description "An ISO 8601-encoded duration" # @return [Integer, nil] def self.seconds_precision # ActiveSupport::Duration precision defaults to whatever input was given @seconds_precision end # @param [Integer, nil] value def self.seconds_precision=(value) @seconds_precision = value end # @param value [ActiveSupport::Duration, String] # @return [String] # @raise [GraphQL::Error] if ActiveSupport::Duration is not defined or if an incompatible object is passed def self.coerce_result(value, _ctx) unless defined?(ActiveSupport::Duration) raise GraphQL::Error, "ActiveSupport >= 5.0 must be loaded to use the built-in ISO8601Duration type." end begin case value when ActiveSupport::Duration value.iso8601(precision: seconds_precision) when ::String ActiveSupport::Duration.parse(value).iso8601(precision: seconds_precision) else # Try calling as ActiveSupport::Duration compatible as a fallback value.iso8601(precision: seconds_precision) end rescue StandardError => error raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only ActiveSupport::Durations and well-formatted Strings are used with this type. (#{error.message})" end end # @param value [String, ActiveSupport::Duration] # @return [ActiveSupport::Duration, nil] # @raise [GraphQL::Error] if ActiveSupport::Duration is not defined # @raise [GraphQL::DurationEncodingError] if duration cannot be parsed def self.coerce_input(value, ctx) unless defined?(ActiveSupport::Duration) raise GraphQL::Error, "ActiveSupport >= 5.0 must be loaded to use the built-in ISO8601Duration type." end begin if value.is_a?(ActiveSupport::Duration) value elsif value.nil? nil else ActiveSupport::Duration.parse(value) end rescue ArgumentError, TypeError err = GraphQL::DurationEncodingError.new(value) ctx.schema.type_error(err, ctx) end end end end end graphql-ruby-2.2.17/lib/graphql/types/json.rb000066400000000000000000000013141476434635200211030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # An untyped JSON scalar that maps to Ruby hashes, arrays, strings, integers, floats, booleans and nils. # This should be used judiciously because it subverts the GraphQL type system. # # Use it for fields or arguments as follows: # # field :template_parameters, GraphQL::Types::JSON, null: false # # argument :template_parameters, GraphQL::Types::JSON, null: false # class JSON < GraphQL::Schema::Scalar description "Represents untyped JSON" def self.coerce_input(value, _context) value end def self.coerce_result(value, _context) value end end end end graphql-ruby-2.2.17/lib/graphql/types/relay.rb000066400000000000000000000027251476434635200212550ustar00rootroot00000000000000# frozen_string_literal: true # behavior modules: require "graphql/types/relay/connection_behaviors" require "graphql/types/relay/edge_behaviors" require "graphql/types/relay/node_behaviors" require "graphql/types/relay/page_info_behaviors" require "graphql/types/relay/has_node_field" require "graphql/types/relay/has_nodes_field" # concrete classes based on the gem defaults: require "graphql/types/relay/page_info" require "graphql/types/relay/base_connection" require "graphql/types/relay/base_edge" require "graphql/types/relay/node" module GraphQL module Types # This module contains some types and fields that could support Relay conventions in GraphQL. # # You can use these classes out of the box if you want, but if you want to use your _own_ # GraphQL extensions along with the features in this code, you could also # open up the source files and copy the relevant methods and configuration into # your own classes. # # For example, the provided object types extend {Types::Relay::BaseObject}, # but you might want to: # # 1. Migrate the extensions from {Types::Relay::BaseObject} into _your app's_ base object # 2. Copy {Relay::BaseConnection}, {Relay::BaseEdge}, etc into _your app_, and # change them to extend _your_ base object. # # Similarly, `BaseField`'s extensions could be migrated to your app # and `Node` could be implemented to mix in your base interface module. module Relay end end end graphql-ruby-2.2.17/lib/graphql/types/relay/000077500000000000000000000000001476434635200207225ustar00rootroot00000000000000graphql-ruby-2.2.17/lib/graphql/types/relay/base_connection.rb000066400000000000000000000032121476434635200243760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # Use this to implement Relay connections, or take it as inspiration # for Relay classes in your own app. # # You may wish to copy this code into your own base class, # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`. # # @example Implementation a connection and edge # class BaseObject < GraphQL::Schema::Object; end # # # Given some object in your app ... # class Types::Post < BaseObject # end # # # Make a couple of base classes: # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end # class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end # # # Then extend them for the object in your app # class Types::PostEdge < Types::BaseEdge # node_type Types::Post # end # # class Types::PostConnection < Types::BaseConnection # edge_type Types::PostEdge, # edges_nullable: true, # edge_nullable: true, # node_nullable: true, # nodes_field: true # # # Alternatively, you can call the class methods followed by your edge type # # edges_nullable true # # edge_nullable true # # node_nullable true # # has_nodes_field true # # edge_type Types::PostEdge # end # # @see Relay::BaseEdge for edge types class BaseConnection < Schema::Object include ConnectionBehaviors end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/base_edge.rb000066400000000000000000000015701476434635200231500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # A class-based definition for Relay edges. # # Use this as a parent class in your app, or use it as inspiration for your # own base `Edge` class. # # For example, you may want to extend your own `BaseObject` instead of the # built-in `GraphQL::Schema::Object`. # # @example Making a UserEdge type # # Make a base class for your app # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge # end # # # Then extend your own base class # class Types::UserEdge < Types::BaseEdge # node_type(Types::User) # end # # @see {Relay::BaseConnection} for connection types class BaseEdge < GraphQL::Schema::Object include Types::Relay::EdgeBehaviors end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/connection_behaviors.rb000066400000000000000000000172051476434635200254550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay module ConnectionBehaviors extend Forwardable def_delegators :@object, :cursor_from_node, :parent def self.included(child_class) child_class.extend(ClassMethods) child_class.has_nodes_field(true) child_class.node_nullable(true) child_class.edges_nullable(true) child_class.edge_nullable(true) child_class.module_exec { self.edge_type = nil self.node_type = nil self.edge_class = nil } add_page_info_field(child_class) end module ClassMethods def inherited(child_class) super child_class.has_nodes_field(has_nodes_field) child_class.node_nullable(node_nullable) child_class.edges_nullable(edges_nullable) child_class.edge_nullable(edge_nullable) child_class.edge_type = nil child_class.node_type = nil child_class.edge_class = nil end def default_relay? true end # @return [Class] attr_reader :node_type # @return [Class] attr_reader :edge_class # Configure this connection to return `edges` and `nodes` based on `edge_type_class`. # # This method will use the inputs to create: # - `edges` field # - `nodes` field # - description # # It's called when you subclass this base connection, trying to use the # class name to set defaults. You can call it again in the class definition # to override the default (or provide a value, if the default lookup failed). # @param field_options [Hash] Any extra keyword arguments to pass to the `field :edges, ...` and `field :nodes, ...` configurations def edge_type(edge_type_class, edge_class: GraphQL::Pagination::Connection::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable, field_options: nil) # Set this connection's graphql name node_type_name = node_type.graphql_name @node_type = node_type @edge_type = edge_type_class @edge_class = edge_class base_field_options = { name: :edges, type: [edge_type_class, null: edge_nullable], null: edges_nullable, description: "A list of edges.", scope: false, # Assume that the connection was already scoped. connection: false, } if field_options base_field_options.merge!(field_options) end field(**base_field_options) define_nodes_field(node_nullable, field_options: field_options) if nodes_field description("The connection type for #{node_type_name}.") end # Filter this list according to the way its node type would scope them def scope_items(items, context) node_type.scope_items(items, context) end # The connection will skip auth on its nodes if the node_type is configured for that def reauthorize_scoped_objects(new_value = nil) if new_value.nil? if @reauthorize_scoped_objects != nil @reauthorize_scoped_objects else node_type.reauthorize_scoped_objects end else @reauthorize_scoped_objects = new_value end end # Add the shortcut `nodes` field to this connection and its subclasses def nodes_field(node_nullable: self.node_nullable, field_options: nil) define_nodes_field(node_nullable, field_options: field_options) end def authorized?(obj, ctx) true # Let nodes be filtered out end def visible?(ctx) # if this is an abstract base class, there may be no `node_type` node_type ? node_type.visible?(ctx) : super end # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.) # Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields. def node_nullable(new_value = nil) if new_value.nil? defined?(@node_nullable) ? @node_nullable : superclass.node_nullable else @node_nullable = new_value end end # Set the default `edges_nullable` for this class and its child classes. (Defaults to `true`.) # Use `edges_nullable(false)` in your base class to make non-null `edges` fields. def edges_nullable(new_value = nil) if new_value.nil? defined?(@edges_nullable) ? @edges_nullable : superclass.edges_nullable else @edges_nullable = new_value end end # Set the default `edge_nullable` for this class and its child classes. (Defaults to `true`.) # Use `edge_nullable(false)` in your base class to make non-null `edge` fields. def edge_nullable(new_value = nil) if new_value.nil? defined?(@edge_nullable) ? @edge_nullable : superclass.edge_nullable else @edge_nullable = new_value end end # Set the default `nodes_field` for this class and its child classes. (Defaults to `true`.) # Use `nodes_field(false)` in your base class to prevent adding of a nodes field. def has_nodes_field(new_value = nil) if new_value.nil? defined?(@nodes_field) ? @nodes_field : superclass.has_nodes_field else @nodes_field = new_value end end protected attr_writer :edge_type, :node_type, :edge_class private def define_nodes_field(nullable, field_options: nil) base_field_options = { name: :nodes, type: [@node_type, null: nullable], null: nullable, description: "A list of nodes.", connection: false, # Assume that the connection was scoped before this step: scope: false, } if field_options base_field_options.merge!(field_options) end field(**base_field_options) end end class << self def add_page_info_field(obj_type) obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination." end end def edges # Assume that whatever authorization needed to happen # already happened at the connection level. current_runtime_state = Thread.current[:__graphql_runtime_info] query_runtime_state = current_runtime_state[context.query] query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items? @object.edges end def nodes # Assume that whatever authorization needed to happen # already happened at the connection level. current_runtime_state = Thread.current[:__graphql_runtime_info] query_runtime_state = current_runtime_state[context.query] query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items? @object.nodes end end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/edge_behaviors.rb000066400000000000000000000050661476434635200242240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay module EdgeBehaviors def self.included(child_class) child_class.description("An edge in a connection.") child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.") child_class.extend(ClassMethods) child_class.class_exec { self.node_type = nil } child_class.node_nullable(true) end def node current_runtime_state = Thread.current[:__graphql_runtime_info] query_runtime_state = current_runtime_state[context.query] query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items? @object.node end module ClassMethods def inherited(child_class) super child_class.node_type = nil child_class.node_nullable = nil end def default_relay? true end # Get or set the Object type that this edge wraps. # # @param node_type [Class] A `Schema::Object` subclass # @param null [Boolean] # @param field_options [Hash] Any extra arguments to pass to the `field :node` configuration def node_type(node_type = nil, null: self.node_nullable, field_options: nil) if node_type @node_type = node_type # Add a default `node` field base_field_options = { name: :node, type: node_type, null: null, description: "The item at the end of the edge.", connection: false, } if field_options base_field_options.merge!(field_options) end field(**base_field_options) end @node_type end def authorized?(obj, ctx) true end def visible?(ctx) node_type.visible?(ctx) end # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.) # Use `node_nullable(false)` in your base class to make non-null `node` field. def node_nullable(new_value = nil) if new_value.nil? @node_nullable != nil ? @node_nullable : superclass.node_nullable else @node_nullable = new_value end end protected attr_writer :node_type, :node_nullable end end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/has_node_field.rb000066400000000000000000000021011476434635200241640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # Include this module to your root Query type to get a Relay-compliant `node(id: ID!): Node` field that uses the schema's `object_from_id` hook. module HasNodeField def self.included(child_class) child_class.field(**field_options, &field_block) end class << self def field_options { name: "node", type: GraphQL::Types::Relay::Node, null: true, description: "Fetches an object given its ID.", relay_node_field: true, } end def field_block Proc.new { argument :id, "ID!", description: "ID of the object." def resolve(obj, args, ctx) ctx.schema.object_from_id(args[:id], ctx) end def resolve_field(obj, args, ctx) resolve(obj, args, ctx) end } end end end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/has_nodes_field.rb000066400000000000000000000021701476434635200243550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # Include this module to your root Query type to get a Relay-style `nodes(id: ID!): [Node]` field that uses the schema's `object_from_id` hook. module HasNodesField def self.included(child_class) child_class.field(**field_options, &field_block) end class << self def field_options { name: "nodes", type: [GraphQL::Types::Relay::Node, null: true], null: false, description: "Fetches a list of objects given a list of IDs.", relay_nodes_field: true, } end def field_block Proc.new { argument :ids, "[ID!]!", description: "IDs of the objects." def resolve(obj, args, ctx) args[:ids].map { |id| ctx.schema.object_from_id(id, ctx) } end def resolve_field(obj, args, ctx) resolve(obj, args, ctx) end } end end end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/node.rb000066400000000000000000000005611476434635200221760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for Relay's `Node` interface, # or you can take it as inspiration for your own implementation # of the `Node` interface. module Node include GraphQL::Schema::Interface include Types::Relay::NodeBehaviors end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/node_behaviors.rb000066400000000000000000000011601476434635200242340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay module NodeBehaviors def self.included(child_module) child_module.extend(ClassMethods) child_module.description("An object with an ID.") child_module.field(:id, ID, null: false, description: "ID of the object.", resolver_method: :default_global_id) end def default_global_id context.schema.id_from_object(object, self.class, context) end module ClassMethods def default_relay? true end end end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/page_info.rb000066400000000000000000000003651476434635200232020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # The return type of a connection's `pageInfo` field class PageInfo < GraphQL::Schema::Object include PageInfoBehaviors end end end end graphql-ruby-2.2.17/lib/graphql/types/relay/page_info_behaviors.rb000066400000000000000000000017131476434635200252420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay module PageInfoBehaviors def self.included(child_class) child_class.extend ClassMethods child_class.description "Information about pagination in a connection." child_class.field :has_next_page, Boolean, null: false, description: "When paginating forwards, are there more items?" child_class.field :has_previous_page, Boolean, null: false, description: "When paginating backwards, are there more items?" child_class.field :start_cursor, String, null: true, description: "When paginating backwards, the cursor to continue." child_class.field :end_cursor, String, null: true, description: "When paginating forwards, the cursor to continue." end end module ClassMethods def default_relay? true end end end end end graphql-ruby-2.2.17/lib/graphql/types/string.rb000066400000000000000000000014611476434635200214430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class String < GraphQL::Schema::Scalar description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text." def self.coerce_result(value, ctx) str = value.to_s if str.encoding == Encoding::UTF_8 || str.ascii_only? str elsif str.frozen? str.encode(Encoding::UTF_8) else str.encode!(Encoding::UTF_8) end rescue EncodingError err = GraphQL::StringEncodingError.new(str, context: ctx) ctx.schema.type_error(err, ctx) end def self.coerce_input(value, _ctx) value.is_a?(::String) ? value : nil end default_scalar true end end end graphql-ruby-2.2.17/lib/graphql/unauthorized_error.rb000066400000000000000000000021161476434635200227210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # When an `authorized?` hook returns false, this error is used to communicate the failure. # It's passed to {Schema.unauthorized_object}. # # Alternatively, custom code in `authorized?` may raise this error. It will be routed the same way. class UnauthorizedError < GraphQL::Error # @return [Object] the application object that failed the authorization check attr_reader :object # @return [Class] the GraphQL object type whose `.authorized?` method was called (and returned false) attr_reader :type # @return [GraphQL::Query::Context] the context for the current query attr_accessor :context def initialize(message = nil, object: nil, type: nil, context: nil) if message.nil? && object.nil? && type.nil? raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @object = object @type = type @context = context message ||= "An instance of #{object.class} failed #{type.graphql_name}'s authorization check" super(message) end end end graphql-ruby-2.2.17/lib/graphql/unauthorized_field_error.rb000066400000000000000000000014421476434635200240650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class UnauthorizedFieldError < GraphQL::UnauthorizedError # @return [Field] the field that failed the authorization check attr_accessor :field def initialize(message = nil, object: nil, type: nil, context: nil, field: nil) if message.nil? && [field, type].any?(&:nil?) raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @field = field message ||= begin if object "An instance of #{object.class} failed #{type.name}'s authorization check on field #{field.name}" else "Failed #{type.name}'s authorization check on field #{field.name}" end end super(message, object: object, type: type, context: context) end end end graphql-ruby-2.2.17/lib/graphql/unresolved_type_error.rb000066400000000000000000000031501476434635200234260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Error raised when the value provided for a field # can't be resolved to one of the possible types for the field. class UnresolvedTypeError < GraphQL::RuntimeTypeError # @return [Object] The runtime value which couldn't be successfully resolved with `resolve_type` attr_reader :value # @return [GraphQL::Field] The field whose value couldn't be resolved (`field.type` is type which couldn't be resolved) attr_reader :field # @return [GraphQL::BaseType] The owner of `field` attr_reader :parent_type # @return [Object] The return of {Schema#resolve_type} for `value` attr_reader :resolved_type # @return [Array] The allowed options for resolving `value` to `field.type` attr_reader :possible_types def initialize(value, field, parent_type, resolved_type, possible_types) @value = value @field = field @parent_type = parent_type @resolved_type = resolved_type @possible_types = possible_types message = "The value from \"#{field.graphql_name}\" on \"#{parent_type.graphql_name}\" could not be resolved to \"#{field.type.to_type_signature}\". " \ "(Received: `#{resolved_type.inspect}`, Expected: [#{possible_types.map(&:graphql_name).join(", ")}]) " \ "Make sure you have defined a `resolve_type` proc on your schema and that value `#{value.inspect}` " \ "gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an " \ "interface but isn't a return type of any other field." super(message) end end end graphql-ruby-2.2.17/lib/graphql/version.rb000066400000000000000000000001061476434635200204510ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL VERSION = "2.2.17" end graphql-ruby-2.2.17/readme.md000066400000000000000000000047361476434635200160270ustar00rootroot00000000000000# graphql graphql-ruby [![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml) [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql) A Ruby implementation of [GraphQL](https://graphql.org/). - [Website](https://graphql-ruby.org/) - [API Documentation](https://www.rubydoc.info/github/rmosolgo/graphql-ruby) - [Newsletter](https://buttondown.email/graphql-ruby) ## Installation Install from RubyGems by adding it to your `Gemfile`, then bundling. ```ruby # Gemfile gem 'graphql' ``` ``` $ bundle install ``` ## Getting Started ``` $ rails generate graphql:install ``` After this, you may need to run `bundle install` again, as by default graphiql-rails is added on installation. Or, see ["Getting Started"](https://graphql-ruby.org/getting_started.html). ## Upgrade I also sell [GraphQL::Pro](https://graphql.pro) which provides several features on top of the GraphQL runtime, including: - [Persisted queries](https://graphql-ruby.org/operation_store/overview) - [API versioning](https://graphql-ruby.org/changesets/overview) - [Streaming payloads](https://graphql-ruby.org/defer/overview) - [Server-side caching](https://graphql-ruby.org/object_cache/overview) - [Rate limiters](https://graphql-ruby.org/limiters/overview) - Subscriptions backends for [Pusher](https://graphql-ruby.org/subscriptions/pusher_implementation) and [Ably](https://graphql-ruby.org/subscriptions/ably_implementation) - Authorization plugins for [Pundit](https://graphql-ruby.org/authorization/pundit_integration) and [CanCan](https://graphql-ruby.org/authorization/can_can_integration) Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development! ## Goals - Implement the GraphQL spec & support a Relay front end - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible - Support Ruby on Rails and Relay ## Getting Involved - __Say hi & ask questions__ in the #graphql-ruby channel on [Discord](https://discord.com/invite/xud7bH9). - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues). - __Start hacking__ with the [Development guide](https://graphql-ruby.org/development). graphql-ruby-2.2.17/spec/000077500000000000000000000000001476434635200151705ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/000077500000000000000000000000001476434635200163235ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/.gitignore000066400000000000000000000006651476434635200203220ustar00rootroot00000000000000# See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle # Ignore all logfiles and tempfiles. /log/* /tmp/* /node_modules /yarn-error.log .byebug_history graphql-ruby-2.2.17/spec/dummy/Gemfile000066400000000000000000000004061476434635200176160ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gem 'bootsnap' gem 'rails', '~>7.0' gem 'sprockets-rails' gem 'puma' gem 'capybara' gem 'selenium-webdriver' gem 'graphql', path: File.expand_path('../../', __dir__) gem "listen", group: :development graphql-ruby-2.2.17/spec/dummy/README.md000066400000000000000000000005661476434635200176110ustar00rootroot00000000000000# README This README would normally document whatever steps are necessary to get the application up and running. Things you may want to cover: * Ruby version * System dependencies * Configuration * Database creation * Database initialization * How to run the test suite * Services (job queues, cache servers, search engines, etc.) * Deployment instructions * ... graphql-ruby-2.2.17/spec/dummy/Rakefile000066400000000000000000000004011476434635200177630ustar00rootroot00000000000000# frozen_string_literal: true # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require_relative 'config/application' Rails.application.load_tasks graphql-ruby-2.2.17/spec/dummy/app/000077500000000000000000000000001476434635200171035ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/assets/000077500000000000000000000000001476434635200204055ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/assets/config/000077500000000000000000000000001476434635200216525ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/assets/config/manifest.js000066400000000000000000000000461476434635200240160ustar00rootroot00000000000000//= link_directory ../javascripts .js graphql-ruby-2.2.17/spec/dummy/app/assets/javascripts/000077500000000000000000000000001476434635200227365ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/assets/javascripts/application.js000066400000000000000000000046211476434635200256020ustar00rootroot00000000000000// This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's // vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. // // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // //= require rails-ujs // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. // //= require action_cable //= require_self (function() { this.App || (this.App = {}); App.cable = ActionCable.createConsumer(); App.subscribe = function(options) { var query = options.query var variables = options.variables var receivedCallback = options.received // Unique-ish var uuid = Math.round(Date.now() + Math.random() * 100000).toString(16) return { subscription: App.cable.subscriptions.create({ channel: "GraphqlChannel", id: uuid, }, { connected: function() { this.perform("execute", { query: query, variables: variables, }) console.log("Connected", query, variables) }, received: function(payload) { console.log("received", query, variables, payload) if (payload.result) { receivedCallback(payload) } if (!payload.more) { this.unsubscribe() App.logToBody("Remaining ActionCable subscriptions: " + App.cable.subscriptions.subscriptions.length) } } } ), trigger: function(options) { this.subscription.perform("make_trigger", options) }, unsubscribe: function() { this.subscription.unsubscribe() }, } } // Add `text` to the HTML body, for debugging App.logToBody = function(text) { var bodyLog = document.getElementById("body-log") var logEntry = document.createElement("p") logEntry.innerText = text bodyLog.appendChild(logEntry) } }).call(this); graphql-ruby-2.2.17/spec/dummy/app/channels/000077500000000000000000000000001476434635200206765ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/channels/application_cable/000077500000000000000000000000001476434635200243275ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/channels/application_cable/channel.rb000066400000000000000000000001551476434635200262650ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationCable class Channel < ActionCable::Channel::Base end end graphql-ruby-2.2.17/spec/dummy/app/channels/application_cable/connection.rb000066400000000000000000000001631476434635200270130ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationCable class Connection < ActionCable::Connection::Base end end graphql-ruby-2.2.17/spec/dummy/app/channels/graphql_channel.rb000066400000000000000000000064431476434635200243600ustar00rootroot00000000000000# frozen_string_literal: true class GraphqlChannel < ActionCable::Channel::Base class QueryType < GraphQL::Schema::Object field :value, Integer, null: false def value 3 end end class PayloadType < GraphQL::Schema::Object field :value, Integer, null: false end class CounterIncremented < GraphQL::Schema::Subscription def self.reset_call_count @@call_count = 0 end reset_call_count field :new_value, Integer, null: false def update if object if object.value == "server-unsubscribe" unsubscribe elsif object.value == "server-unsubscribe-with-message" unsubscribe({ new_value: 9999 }) end end result = { new_value: @@call_count += 1 } puts " -> CounterIncremented#update: #{result}" result end end class SubscriptionType < GraphQL::Schema::Object field :payload, PayloadType, null: false do argument :id, ID end field :counter_incremented, subscription: CounterIncremented end # Wacky behavior around the number 4 # so we can confirm it's used by the UI module CustomSerializer def self.load(value) if value == "4x" ExamplePayload.new(400) else GraphQL::Subscriptions::Serialize.load(value) end end def self.dump(obj) if obj.is_a?(ExamplePayload) && obj.value == 4 "4x" else GraphQL::Subscriptions::Serialize.dump(obj) end end end class GraphQLSchema < GraphQL::Schema query(QueryType) subscription(SubscriptionType) use GraphQL::Subscriptions::ActionCableSubscriptions, serializer: CustomSerializer, broadcast: true, default_broadcastable: true end def subscribed @subscription_ids = [] end def execute(data) query = data["query"] variables = data["variables"] || {} operation_name = data["operationName"] context = { # Make sure the channel is in the context channel: self, } puts "[GraphQLSchema.execute] #{query} || #{variables}" result = GraphQLSchema.execute( query: query, context: context, variables: variables, operation_name: operation_name ) payload = { result: result.to_h, more: result.subscription?, } # Track the subscription here so we can remove it # on unsubscribe. if result.context[:subscription_id] @subscription_ids << result.context[:subscription_id] end puts " -> [transmit(#{result.context[:subscription_id]})] #{payload.inspect}" transmit(payload) end def make_trigger(data) field = data["field"] args = data["arguments"] value = data["value"] value = value && ExamplePayload.new(value) puts "[make_trigger] #{[field, args, value]}" GraphQLSchema.subscriptions.trigger(field, args, value) end def unsubscribed @subscription_ids.each { |sid| puts "[delete_subscription] #{sid}" GraphQLSchema.subscriptions.delete_subscription(sid) } end # This is to make sure that GlobalID is used to load and dump this object class ExamplePayload include GlobalID::Identification def initialize(value) @value = value end def self.find(value) self.new(value) end attr_reader :value alias :id :value end end graphql-ruby-2.2.17/spec/dummy/app/controllers/000077500000000000000000000000001476434635200214515ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/controllers/application_controller.rb000066400000000000000000000001771476434635200265510ustar00rootroot00000000000000# frozen_string_literal: true class ApplicationController < ActionController::Base protect_from_forgery with: :exception end graphql-ruby-2.2.17/spec/dummy/app/controllers/pages_controller.rb000066400000000000000000000001411476434635200253340ustar00rootroot00000000000000# frozen_string_literal: true class PagesController < ApplicationController def show end end graphql-ruby-2.2.17/spec/dummy/app/helpers/000077500000000000000000000000001476434635200205455ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/helpers/application_helper.rb000066400000000000000000000000731476434635200247340ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationHelper end graphql-ruby-2.2.17/spec/dummy/app/jobs/000077500000000000000000000000001476434635200200405ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/jobs/application_job.rb000066400000000000000000000001111476434635200235130ustar00rootroot00000000000000# frozen_string_literal: true class ApplicationJob < ActiveJob::Base end graphql-ruby-2.2.17/spec/dummy/app/views/000077500000000000000000000000001476434635200202405ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/views/layouts/000077500000000000000000000000001476434635200217405ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/views/layouts/application.html.erb000066400000000000000000000002721476434635200257010ustar00rootroot00000000000000 Dummy <%= csrf_meta_tags %> <%= javascript_include_tag 'application' %> <%= yield %> graphql-ruby-2.2.17/spec/dummy/app/views/pages/000077500000000000000000000000001476434635200213375ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/app/views/pages/show.html000066400000000000000000000102401476434635200232020ustar00rootroot00000000000000

ActionCable Test Page

ActionCable updates:


Fingerprint test


graphql-ruby-2.2.17/spec/dummy/bin/000077500000000000000000000000001476434635200170735ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/bin/bundle000077500000000000000000000002371476434635200202740ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) load Gem.bin_path('bundler', 'bundle') graphql-ruby-2.2.17/spec/dummy/bin/rails000077500000000000000000000002531476434635200201330ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' graphql-ruby-2.2.17/spec/dummy/bin/rake000077500000000000000000000001701476434635200177410ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require_relative '../config/boot' require 'rake' Rake.application.run graphql-ruby-2.2.17/spec/dummy/bin/setup000077500000000000000000000014411476434635200201610ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'pathname' require 'fileutils' include FileUtils # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end chdir APP_ROOT do # This script is a starting point to setup your application. # Add necessary setup steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') # Install JavaScript dependencies if using Yarn # system('bin/yarn') puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" system! 'bin/rails restart' end graphql-ruby-2.2.17/spec/dummy/bin/update000077500000000000000000000013471476434635200203100ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'pathname' require 'fileutils' include FileUtils # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end chdir APP_ROOT do # This script is a way to update your development environment automatically. # Add necessary update steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" system! 'bin/rails restart' end graphql-ruby-2.2.17/spec/dummy/bin/yarn000077500000000000000000000005361476434635200177760ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true VENDOR_PATH = File.expand_path('..', __dir__) Dir.chdir(VENDOR_PATH) do begin exec "yarnpkg #{ARGV.join(" ")}" rescue Errno::ENOENT $stderr.puts "Yarn executable was not detected in the system." $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" exit 1 end end graphql-ruby-2.2.17/spec/dummy/config.ru000066400000000000000000000002401476434635200201340ustar00rootroot00000000000000# frozen_string_literal: true # This file is used by Rack-based servers to start the application. require_relative 'config/environment' run Rails.application graphql-ruby-2.2.17/spec/dummy/config/000077500000000000000000000000001476434635200175705ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/config/application.rb000066400000000000000000000016661476434635200224310ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'boot' require "rails" # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" require "action_controller/railtie" require "action_view/railtie" require "action_cable/engine" require "sprockets/railtie" require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Dummy class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.1 # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Don't generate system test files. config.generators.system_tests = nil end end graphql-ruby-2.2.17/spec/dummy/config/boot.rb000066400000000000000000000002751476434635200210640ustar00rootroot00000000000000# frozen_string_literal: true ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. require 'bootsnap/setup' graphql-ruby-2.2.17/spec/dummy/config/cable.yml000066400000000000000000000002271476434635200213620ustar00rootroot00000000000000development: adapter: async test: adapter: async production: adapter: redis url: redis://localhost:6379/1 channel_prefix: dummy_production graphql-ruby-2.2.17/spec/dummy/config/environment.rb000066400000000000000000000002361476434635200224620ustar00rootroot00000000000000# frozen_string_literal: true # Load the Rails application. require_relative 'application' # Initialize the Rails application. Rails.application.initialize! graphql-ruby-2.2.17/spec/dummy/config/environments/000077500000000000000000000000001476434635200223175ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/config/environments/development.rb000066400000000000000000000025541476434635200251740ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports. config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" } else config.action_controller.perform_caching = false config.cache_store = :null_store end # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raises error for missing translations # config.action_view.raise_on_missing_translations = true # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. config.file_watcher = ActiveSupport::EventedFileUpdateChecker end graphql-ruby-2.2.17/spec/dummy/config/environments/production.rb000066400000000000000000000060461476434635200250400ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Attempt to read encrypted secrets from `config/secrets.yml.enc`. # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or # `config/secrets.yml.key`. config.read_encrypted_secrets = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. config.log_level = :debug # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "dummy_#{Rails.env}" # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Use a different logger for distributed setups. # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end end graphql-ruby-2.2.17/spec/dummy/config/environments/test.rb000066400000000000000000000027771476434635200236400ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. config.eager_load = false # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end graphql-ruby-2.2.17/spec/dummy/config/initializers/000077500000000000000000000000001476434635200222765ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/config/initializers/application_controller_renderer.rb000066400000000000000000000003661476434635200312640ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # ActiveSupport::Reloader.to_prepare do # ApplicationController.renderer.defaults.merge!( # http_host: 'example.org', # https: false # ) # end graphql-ruby-2.2.17/spec/dummy/config/initializers/backtrace_silencers.rb000066400000000000000000000006621476434635200266150ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! graphql-ruby-2.2.17/spec/dummy/config/initializers/cookies_serializer.rb000066400000000000000000000004221476434635200265060ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Specify a serializer for the signed and encrypted cookie jars. # Valid options are :json, :marshal, and :hybrid. Rails.application.config.action_dispatch.cookies_serializer = :json graphql-ruby-2.2.17/spec/dummy/config/initializers/filter_parameter_logging.rb000066400000000000000000000003401476434635200276530ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. Rails.application.config.filter_parameters += [:password] graphql-ruby-2.2.17/spec/dummy/config/initializers/inflections.rb000066400000000000000000000012451476434635200251420ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end graphql-ruby-2.2.17/spec/dummy/config/initializers/mime_types.rb000066400000000000000000000002721476434635200247770ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf graphql-ruby-2.2.17/spec/dummy/config/initializers/wrap_parameters.rb000066400000000000000000000005611476434635200260210ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] end graphql-ruby-2.2.17/spec/dummy/config/locales/000077500000000000000000000000001476434635200212125ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/config/locales/en.yml000066400000000000000000000015211476434635200223360ustar00rootroot00000000000000# Files in the config/locales directory are used for internationalization # and are automatically loaded by Rails. If you want to use locales other # than English, add the necessary files in this directory. # # To use the locales, use `I18n.t`: # # I18n.t 'hello' # # In views, this is aliased to just `t`: # # <%= t('hello') %> # # To use a different locale, set it with `I18n.locale`: # # I18n.locale = :es # # This would use the information in config/locales/es.yml. # # The following keys must be escaped otherwise they will not be retrieved by # the default I18n backend: # # true, false, on, off, yes, no # # Instead, surround them with single quotes. # # en: # 'true': 'foo' # # To learn more, please read the Rails Internationalization guide # available at https://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" graphql-ruby-2.2.17/spec/dummy/config/puma.rb000066400000000000000000000044401476434635200210610ustar00rootroot00000000000000# frozen_string_literal: true # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 3000 } # Specifies the `environment` that Puma will run in. # environment ENV.fetch("RAILS_ENV") { "development" } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). # # workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. If you use this option # you need to make sure to reconnect any threads in the `on_worker_boot` # block. # # preload_app! # If you are preloading your application and using Active Record, it's # recommended that you close any connections to the database before workers # are forked to prevent connection leakage. # # before_fork do # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) # end # The code in the `on_worker_boot` will be called if you are using # clustered mode by specifying a number of `workers`. After each worker # process is booted, this block will be run. If you are using the `preload_app!` # option, you will want to use this block to reconnect to any threads # or connections that may have been created at application boot, as Ruby # cannot share connections between processes. # # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end # # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart graphql-ruby-2.2.17/spec/dummy/config/routes.rb000066400000000000000000000001331476434635200214330ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.routes.draw do root to: "pages#show" end graphql-ruby-2.2.17/spec/dummy/config/secrets.yml000066400000000000000000000023751476434635200217720ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. # You can use `rails secret` to generate a secure secret key. # Make sure the secrets in this file are kept private # if you're sharing your code publicly. # Shared secrets are available across all environments. # shared: # api_key: a1B2c3D4e5F6 # Environmental secrets are only available for that specific environment. development: secret_key_base: 32be83294ab8a3b21fce26b6e48db6c5d84cfa3c89e686dcb39369806e8daba53215caed302b89ce4a54b5c67ff2fa6be797f0f5c937a71fbb9f396ef108e867 test: secret_key_base: 88c4ea138f1ad39c9eaa3196361a539e8aba1fcbffa3c2383045799cefe150fadaa7256ea680a912d27a878fdff55debd3f920388de68aa4f81828b0bd2ac072 # Do not keep production secrets in the unencrypted secrets file. # Instead, either read values from the environment. # Or, use `bin/rails secrets:setup` to configure encrypted secrets # and move the `production:` environment over there. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> graphql-ruby-2.2.17/spec/dummy/package.json000066400000000000000000000000771476434635200206150ustar00rootroot00000000000000{ "name": "dummy", "private": true, "dependencies": {} } graphql-ruby-2.2.17/spec/dummy/public/000077500000000000000000000000001476434635200176015ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/public/404.html000066400000000000000000000032721476434635200210020ustar00rootroot00000000000000 The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.

graphql-ruby-2.2.17/spec/dummy/public/422.html000066400000000000000000000032511476434635200207770ustar00rootroot00000000000000 The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.

graphql-ruby-2.2.17/spec/dummy/public/500.html000066400000000000000000000031431476434635200207740ustar00rootroot00000000000000 We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.

graphql-ruby-2.2.17/spec/dummy/public/apple-touch-icon-precomposed.png000066400000000000000000000000001476434635200257620ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/public/apple-touch-icon.png000066400000000000000000000000001476434635200234440ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/public/favicon.ico000066400000000000000000000000001476434635200217100ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/public/robots.txt000066400000000000000000000001421476434635200216470ustar00rootroot00000000000000# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file graphql-ruby-2.2.17/spec/dummy/test/000077500000000000000000000000001476434635200173025ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/test/application_system_test_case.rb000066400000000000000000000003041476434635200255650ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400] end graphql-ruby-2.2.17/spec/dummy/test/system/000077500000000000000000000000001476434635200206265ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/dummy/test/system/action_cable_subscription_test.rb000066400000000000000000000154141476434635200274260ustar00rootroot00000000000000# frozen_string_literal: true require "application_system_test_case" class ActionCableSubscriptionsTest < ApplicationSystemTestCase # This test covers a lot of ground! test "it handles subscriptions" do # Load the page and let the subscriptions happen visit "/" # make sure they connect successfully assert_selector "#updates-1-connected" assert_selector "#updates-2-connected" # Trigger a few updates, make sure we get a client update: click_on("Trigger 1") click_on("Trigger 1") click_on("Trigger 1") assert_selector "#updates-1-3", text: "3" # Make sure there aren't any unexpected elements: refute_selector "#updates-1-4" refute_selector "#updates-2-1" # Now, trigger updates to a different stream # and make sure the previous stream is not affected click_on("Trigger 2") click_on("Trigger 2") assert_selector "#updates-2-1", text: "1" assert_selector "#updates-2-2", text: "2" refute_selector "#updates-2-3" refute_selector "#updates-1-4" # Now unsubscribe one, it should not receive updates but the other should click_on("Unsubscribe 1") click_on("Trigger 1") # This should not have changed refute_selector "#updates-1-4" click_on("Trigger 2") assert_selector "#updates-2-3", text: "3" refute_selector "#updates-1-4" # wacky behavior to make sure the custom serializer is used: click_on("Trigger 2") assert_selector "#updates-2-400", text: "400" end # Wrap an `assert_selector` call in a debugging check def debug_assert_selector(selector) if !page.has_css?(selector) puts "[debug_assert_selector(#{selector.inspect})] Failed to find #{selector.inspect} in:" puts page.html else puts "[debug_assert_selector(#{selector.inspect})] Found #{selector.inspect}" end assert_selector(selector) end # It seems like ActionCable's order of evaluation here is non-deterministic, # so detect which order to make the assertions. # (They still both have to pass, but we don't know exactly what order the evaluations went in.) def detect_update_values(possibility_1, possibility_2) if page.has_css?("#fingerprint-updates-1-update-1-value-#{possibility_1}") [possibility_1, possibility_2] else [possibility_2, possibility_1] end end test "it only re-runs queries once for subscriptions with matching fingerprints" do GraphqlChannel::CounterIncremented.reset_call_count visit "/" using_wait_time 10 do sleep 1 # Make 3 subscriptions to the same payload click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-1" click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-2" click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-3" # And two to the next payload click_on("Subscribe with fingerprint 2") debug_assert_selector "#fingerprint-updates-2-connected-1" click_on("Subscribe with fingerprint 2") debug_assert_selector "#fingerprint-updates-2-connected-2" # Now trigger. We expect a total of two updates: # - One is built & delivered to the first three subscribers # - Another is built & delivered to the next two click_on("Trigger with fingerprint 2") # The order here is random, I think depending on ActionCable's internal storage: fingerprint_1_value, fingerprint_2_value = detect_update_values(1, 2) # These all share the first value: debug_assert_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value}" debug_assert_selector "#fingerprint-updates-1-update-2-value-#{fingerprint_1_value}" debug_assert_selector "#fingerprint-updates-1-update-3-value-#{fingerprint_1_value}" # and these share the second value: debug_assert_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value}" debug_assert_selector "#fingerprint-updates-2-update-2-value-#{fingerprint_2_value}" click_on("Unsubscribe with fingerprint 2") click_on("Trigger with fingerprint 1") fingerprint_1_value_2, fingerprint_2_value_2 = detect_update_values(3, 4) # These get an update debug_assert_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2}" debug_assert_selector "#fingerprint-updates-1-update-2-value-#{fingerprint_1_value_2}" debug_assert_selector "#fingerprint-updates-1-update-3-value-#{fingerprint_1_value_2}" # But these are unsubscribed: refute_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value_2}" refute_selector "#fingerprint-updates-2-update-2-value-#{fingerprint_2_value_2}" click_on("Unsubscribe with fingerprint 1") # Make a new subscription and make sure it's updated: click_on("Subscribe with fingerprint 2") click_on("Trigger with fingerprint 2") debug_assert_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value_2}" # But this one was unsubscribed: refute_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2 + 1}" refute_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2 + 2}" end end test "it unsubscribes from the server" do GraphqlChannel::CounterIncremented.reset_call_count visit "/" using_wait_time 10 do sleep 1 # Establish the connection click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-1" # Trigger once click_on("Trigger with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-update-1-value-1" # Server unsubscribe click_on("Server-side unsubscribe with fingerprint 1") # Subsequent updates should fail click_on("Trigger with fingerprint 1") refute_selector "#fingerprint-updates-1-update-2-value-2" # The client has only 2 connections (from the initial 2) assert_text "Remaining ActionCable subscriptions: 2" end end test "it unsubscribes with a message" do GraphqlChannel::CounterIncremented.reset_call_count visit "/" using_wait_time 10 do sleep 1 # Establish the connection click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-1" # Trigger once click_on("Trigger with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-update-1-value-1" # Server unsubscribe click_on("Unsubscribe with message with fingerprint 1") # Magic value from unsubscribe hook: debug_assert_selector "#fingerprint-updates-1-update-1-value-9999" # The client has only 2 connections (from the initial 2) assert_text "Remaining ActionCable subscriptions: 2" end end end graphql-ruby-2.2.17/spec/dummy/test/test_helper.rb000066400000000000000000000001671476434635200221510ustar00rootroot00000000000000# frozen_string_literal: true require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' graphql-ruby-2.2.17/spec/fixtures/000077500000000000000000000000001476434635200170415ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/fixtures/cop/000077500000000000000000000000001476434635200176225ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/fixtures/cop/.rubocop.yml000066400000000000000000000002621476434635200220740ustar00rootroot00000000000000require: - graphql/rubocop AllCops: TargetRubyVersion: 2.4 DisabledByDefault: true GraphQL/DefaultNullTrue: Enabled: true GraphQL/DefaultRequiredTrue: Enabled: true graphql-ruby-2.2.17/spec/fixtures/cop/null_true.rb000066400000000000000000000004771476434635200221700ustar00rootroot00000000000000class Types::Something < Types::BaseObject field :name, String, null: true field :other_name, String, null: true, description: "Here's a description" field :described, [String, null: true], null: true, description: "Something" field :ok_field, String field :also_ok_field, String, null: false end graphql-ruby-2.2.17/spec/fixtures/cop/null_true_corrected.rb000066400000000000000000000004271476434635200242150ustar00rootroot00000000000000class Types::Something < Types::BaseObject field :name, String field :other_name, String, description: "Here's a description" field :described, [String, null: true], description: "Something" field :ok_field, String field :also_ok_field, String, null: false end graphql-ruby-2.2.17/spec/fixtures/cop/required_true.rb000066400000000000000000000007171476434635200230330ustar00rootroot00000000000000class Types::Something < Types::BaseObject field :name, String do argument :id_1, ID, required: true argument :id_2, ID, required: true, description: "Described" argument :id_3, ID, other_config: { something: false, required: true }, required: true, description: "Something" argument :id_4, ID, required: false argument :id_5, ID end field :name2, String do |f| f.argument(:id_1, ID, required: true) end end graphql-ruby-2.2.17/spec/fixtures/cop/required_true_corrected.rb000066400000000000000000000006111476434635200250560ustar00rootroot00000000000000class Types::Something < Types::BaseObject field :name, String do argument :id_1, ID argument :id_2, ID, description: "Described" argument :id_3, ID, other_config: { something: false, required: true }, description: "Something" argument :id_4, ID, required: false argument :id_5, ID end field :name2, String do |f| f.argument(:id_1, ID) end end graphql-ruby-2.2.17/spec/graphql/000077500000000000000000000000001476434635200166265ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/analysis/000077500000000000000000000000001476434635200204515ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/analysis/ast/000077500000000000000000000000001476434635200212405ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/analysis/ast/field_usage_spec.rb000066400000000000000000000160511476434635200250510ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::FieldUsage do let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::FieldUsage]).first } let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables) } let(:variables) { {} } describe "query with deprecated fields" do let(:query_string) {%| query { cheese(id: 1) { id fatContent } } |} it "keeps track of used fields" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "keeps track of deprecated fields" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated fields used more than once" do let(:query_string) {%| query { cheese1: cheese(id: 1) { id fatContent } cheese2: cheese(id: 2) { id fatContent } } |} it "omits duplicate usage of a field" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "omits duplicate usage of a deprecated field" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated fields in a fragment" do let(:query_string) {%| query { cheese(id: 1) { id ...CheeseSelections } } fragment CheeseSelections on Cheese { fatContent } |} it "keeps track of fields used in the fragment" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "keeps track of deprecated fields used in the fragment" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated fields in an inline fragment" do let(:query_string) {%| query { cheese(id: 1) { id ... on Cheese { fatContent } } } |} it "keeps track of fields used in the fragment" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "keeps track of deprecated fields used in the fragment" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated arguments" do let(:query_string) {%| query { fromSource(oldSource: "deprecated") { id } } |} it "keeps track of deprecated arguments" do assert_equal ['Query.fromSource.oldSource'], result[:used_deprecated_arguments] end end describe "query with deprecated arguments used more than once" do let(:query_string) {%| query { fromSource(oldSource: "deprecated1") { id } fromSource(oldSource: "deprecated2") { id } } |} it "omits duplicate usage of a deprecated argument" do assert_equal ['Query.fromSource.oldSource'], result[:used_deprecated_arguments] end end describe "query with deprecated arguments nested in an array argument" do let(:query_string) {%| query { searchDairy(product: [{ oldSource: "deprecated" }]) { __typename } } |} it "keeps track of nested deprecated arguments" do assert_equal ['DairyProductInput.oldSource'], result[:used_deprecated_arguments] end end describe "query with deprecated enum argument" do let(:query_string) {%| query { fromSource(source: YAK) { id } } |} it "keeps track of deprecated arguments" do assert_equal ['DairyAnimal.YAK'], result[:used_deprecated_enum_values] end describe "tracks non-null/list enums" do let(:query_string) {%| query { cheese(id: 1) { similarCheese(source: [YAK]) { id } } } |} it "keeps track of deprecated arguments" do assert_equal ['DairyAnimal.YAK'], result[:used_deprecated_enum_values] end end end describe "query with an array argument sent as null" do let(:query_string) {%| query { searchDairy(product: null) { __typename } } |} it "tolerates null for array argument" do result end end describe "query with an input object sent in as null" do let(:query_string) {%| query { cheese(id: 1) { id dairyProduct(input: null) { __typename } } } |} it "tolerates null for object argument" do result end end describe "query with deprecated arguments nested in an argument" do let(:query_string) {%| query { searchDairy(singleProduct: { oldSource: "deprecated" }) { __typename } } |} it "keeps track of nested deprecated arguments" do assert_equal ['DairyProductInput.oldSource'], result[:used_deprecated_arguments] end end describe "query with arguments nested in a deprecated argument" do let(:query_string) {%| query { searchDairy(oldProduct: [{ source: "sheep" }]) { __typename } } |} it "keeps track of top-level deprecated arguments" do assert_equal ['Query.searchDairy.oldProduct'], result[:used_deprecated_arguments] end end describe "query with scalar arguments nested in a deprecated argument" do let(:query_string) {%| query { searchDairy(productIds: ["123"]) { __typename } } |} it "keeps track of top-level deprecated arguments" do assert_equal ['Query.searchDairy.productIds'], result[:used_deprecated_arguments] end end describe "mutation with deprecated argument" do let(:query_string) {%| mutation { pushValue(deprecatedTestInput: { oldSource: "deprecated" }) } |} it "keeps track of nested deprecated arguments" do assert_equal ['DairyProductInput.oldSource'], result[:used_deprecated_arguments] end end describe "when an argument prepare raises a GraphQL::ExecutionError" do class ArgumentErrorFieldUsageSchema < GraphQL::Schema class FieldUsage < GraphQL::Analysis::AST::FieldUsage def result values = super query.context[:field_usage] = values nil end end class Query < GraphQL::Schema::Object field :f, Int do argument :i, Int, prepare: ->(*) { raise GraphQL::ExecutionError.new("boom!") } end end query(Query) query_analyzer(FieldUsage) end it "skips analysis of those arguments" do res = ArgumentErrorFieldUsageSchema.execute("{ f(i: 1) }") assert_equal ["boom!"], res["errors"].map { |e| e["message"] } assert_equal({used_fields: ["Query.f"], used_deprecated_arguments: [], used_deprecated_fields: [], used_deprecated_enum_values: []}, res.context[:field_usage]) end end end graphql-ruby-2.2.17/spec/graphql/analysis/ast/max_query_complexity_spec.rb000066400000000000000000000120541476434635200270700ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::MaxQueryComplexity do let(:schema) { Class.new(Dummy::Schema) } let(:query_string) {%| { a: cheese(id: 1) { id } b: cheese(id: 1) { id } c: cheese(id: 1) { id } d: cheese(id: 1) { id } e: cheese(id: 1) { id } } |} let(:query) { GraphQL::Query.new(schema, query_string, variables: {}, max_complexity: max_complexity) } let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::MaxQueryComplexity]).first } describe "when a query goes over max complexity" do let(:max_complexity) { 9 } it "returns an error" do assert_equal GraphQL::AnalysisError, result.class assert_equal "Query has complexity of 10, which exceeds max complexity of 9", result.message end end describe "when there is no max complexity" do let(:max_complexity) { nil } it "doesn't error" do assert_nil result end end describe "when the query is less than the max complexity" do let(:max_complexity) { 99 } it "doesn't error" do assert_nil result end end describe "when max_complexity is decreased at query-level" do before do schema.max_complexity(100) end let(:max_complexity) { 7 } it "is applied" do assert_equal GraphQL::AnalysisError, result.class assert_equal "Query has complexity of 10, which exceeds max complexity of 7", result.message end end describe "when max_complexity is increased at query-level" do before do schema.max_complexity(1) end let(:max_complexity) { 10 } it "doesn't error" do assert_nil result end end describe "when max_complexity is nil at query-level" do let(:max_complexity) { nil } before do schema.max_complexity(1) end it "is applied" do assert_nil result end end describe "when used with the max_depth plugin" do let(:schema) do Class.new(GraphQL::Schema) do query Dummy::DairyAppQuery max_depth 3 max_complexity 1 end end let(:query_string) {%| { a: cheese(id: 1) { ...cheeseFields } b: cheese(id: 1) { ...cheeseFields } c: cheese(id: 1) { ...cheeseFields } d: cheese(id: 1) { ...cheeseFields } e: cheese(id: 1) { ...cheeseFields } } fragment cheeseFields on Cheese { id } |} let(:result) { schema.execute(query_string) } it "returns a complexity error" do assert_equal "Query has complexity of 10, which exceeds max complexity of 1", result["errors"].first["message"] end end describe "across a multiplex" do before do schema.analysis_engine = GraphQL::Analysis::AST end let(:queries) { 5.times.map { |n| GraphQL::Query.new(schema, "{ cheese(id: #{n}) { id } }", variables: {}) } } let(:max_complexity) { 9 } let(:multiplex) { GraphQL::Execution::Multiplex.new(schema: schema, queries: queries, context: {}, max_complexity: max_complexity) } let(:analyze_multiplex) { GraphQL::Analysis::AST.analyze_multiplex(multiplex, [GraphQL::Analysis::AST::MaxQueryComplexity]) } it "returns errors for all queries" do analyze_multiplex err_msg = "Query has complexity of 10, which exceeds max complexity of 9" queries.each do |query| assert_equal err_msg, query.analysis_errors[0].message end end describe "with a local override" do let(:max_complexity) { 10 } it "uses the override" do analyze_multiplex queries.each do |query| assert query.analysis_errors.empty? end end end end describe "when an argument is unauthorized by type" do class AuthorizedTypeSchema < GraphQL::Schema class Thing < GraphQL::Schema::Object def self.authorized?(obj, ctx) !!ctx[:authorized] && super end field :name, String end class Query < GraphQL::Schema::Object field :things, Thing.connection_type do argument :thing_id, ID, loads: Thing end def things(thing:) [thing] end end query(Query) def self.resolve_type(abs_type, object, ctx) Thing end def self.object_from_id(id, ctx) { name: "Loaded thing #{id}" } end def self.unauthorized_object(err) raise GraphQL::ExecutionError, "Unauthorized Object: #{err.object[:name].inspect}" end default_max_page_size 30 max_complexity 10 end it "when the arg is unauthorized, returns an authorization error, not a complexity error" do query_str = "{ things(thingId: \"123\", first: 1) { nodes { name } } }" res = AuthorizedTypeSchema.execute(query_str, context: { authorized: true }) assert_equal "Loaded thing 123", res["data"]["things"]["nodes"].first["name"] res2 = AuthorizedTypeSchema.execute(query_str) assert_equal ["Unauthorized Object: \"Loaded thing 123\""], res2["errors"].map { |e| e["message"] } end end end graphql-ruby-2.2.17/spec/graphql/analysis/ast/max_query_depth_spec.rb000066400000000000000000000104631476434635200260010ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::MaxQueryDepth do let(:schema) { schema = Class.new(Dummy::Schema) schema.analysis_engine = GraphQL::Analysis::AST schema } let(:query_string) { " { cheese(id: 1) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { id } } } } } } } "} let(:max_depth) { nil } let(:query) { # Don't override `schema.max_depth` with `nil` options = max_depth ? { max_depth: max_depth } : {} GraphQL::Query.new( schema, query_string, variables: {}, **options ) } let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::MaxQueryDepth]).first } let(:multiplex) { GraphQL::Execution::Multiplex.new( schema: schema, queries: [query.dup, query.dup], context: {}, max_complexity: nil ) } let(:multiplex_result) { GraphQL::Analysis::AST.analyze_multiplex(multiplex, [GraphQL::Analysis::AST::MaxQueryDepth]).first } describe "when the query is deeper than max depth" do let(:max_depth) { 5 } it "adds an error message for a too-deep query" do assert_equal "Query has depth of 7, which exceeds max depth of 5", result.message end end describe "when a multiplex queries is deeper than max depth" do before do schema.max_depth = 5 end it "adds an error message for a too-deep query on from multiplex analyzer" do assert_equal "Query has depth of 7, which exceeds max depth of 5", multiplex_result.message end end describe "when the query specifies a different max_depth" do let(:max_depth) { 100 } it "obeys that max_depth" do assert_nil result end end describe "When the query is not deeper than max_depth" do before do schema.max_depth = 100 end it "doesn't add an error" do assert_nil result end end describe "when the max depth isn't set" do before do schema.max_depth = nil end it "doesn't add an error message" do assert_nil result end end describe "when a fragment exceeds max depth" do before do schema.max_depth = 4 end let(:query_string) { " { cheese(id: 1) { ...moreFields } } fragment moreFields on Cheese { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { ...evenMoreFields } } } } fragment evenMoreFields on Cheese { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { id } } } "} it "adds an error message for a too-deep query" do assert_equal "Query has depth of 7, which exceeds max depth of 4", result.message end end describe "when the query would cause a stack error" do let(:query_string) { str = "query { cheese(id: 1) { ".dup n = 10_000 n.times { str << "similarCheese(source: SHEEP) { " } str << "id " n.times { str << "} " } str << "} }" str } it "returns an error" do assert_equal ["This query is too large to execute."], query.result["errors"].map { |err| err["message"] } # Make sure `Schema.execute` works too execute_result = schema.execute(query_string) assert_equal ["This query is too large to execute."], execute_result["errors"].map { |err| err["message"] } end end it "counts introspection fields by default, but can be set to skip" do schema.max_depth = 3 query_str = <<-GRAPHQL { __type(name: \"Abc\") { fields { type { ofType { name } } } } } GRAPHQL result = schema.execute(query_str) assert_equal ["Query has depth of 5, which exceeds max depth of 3"], result["errors"].map { |e| e["message"] } schema.max_depth(3, count_introspection_fields: false) result = schema.execute(query_str) assert_equal({ "__type" => nil }, result["data"]) end end graphql-ruby-2.2.17/spec/graphql/analysis/ast/query_complexity_spec.rb000066400000000000000000000403431476434635200262250ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::QueryComplexity do let(:schema) { Dummy::Schema } let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::QueryComplexity]) } let(:reduce_multiplex_result) { GraphQL::Analysis::AST.analyze_multiplex(multiplex, [GraphQL::Analysis::AST::QueryComplexity]) } let(:variables) { {} } let(:query_context) { {} } let(:query) { GraphQL::Query.new(schema, query_string, context: query_context, variables: variables) } let(:multiplex) { GraphQL::Execution::Multiplex.new( schema: schema, queries: [query.dup, query.dup], context: {}, max_complexity: 10 ) } describe "simple queries" do let(:query_string) {%| query cheeses($isSkipped: Boolean = false){ # complexity of 3 cheese1: cheese(id: 1) { id flavor } # complexity of 4 cheese2: cheese(id: 2) @skip(if: $isSkipped) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} it "sums the complexity" do complexities = reduce_result.first assert_equal 7, complexities end describe "when skipped by directives" do let(:variables) { { "isSkipped" => true } } it "doesn't include skipped fields" do complexity = reduce_result.first assert_equal 3, complexity end end end describe "query with fragments" do let(:query_string) {%| { # complexity of 3 cheese1: cheese(id: 1) { id flavor } # complexity of 7 cheese2: cheese(id: 2) { ... cheeseFields1 ... cheeseFields2 } } fragment cheeseFields1 on Cheese { similarCow: similarCheese(source: COW) { id ... cheeseFields2 } } fragment cheeseFields2 on Cheese { similarSheep: similarCheese(source: SHEEP) { id } } |} it "counts all fragment usages, not the definitions" do complexity = reduce_result.first assert_equal 10, complexity end describe "mutually exclusive types" do let(:query_string) {%| { favoriteEdible { # 1 for everybody fatContent # 1 for everybody ... on Edible { origin } # 1 for honey, aspartame ... on Sweetener { sweetness } # 2 for milk ... milkFields # 1 for cheese ... cheeseFields # 1 for honey ... honeyFields # 1 for milk + cheese ... dairyProductFields } } fragment milkFields on Milk { id source } fragment cheeseFields on Cheese { source } fragment honeyFields on Honey { flowerType } fragment dairyProductFields on DairyProduct { ... on Cheese { flavor } ... on Milk { flavors } } |} it "gets the max among options" do complexity = reduce_result.first assert_equal 6, complexity end end describe "when there are no selections on any object types" do let(:query_string) {%| { # 1 for everybody favoriteEdible { # 1 for everybody fatContent # 1 for everybody ... on Edible { origin } # 1 for honey, aspartame ... on Sweetener { sweetness } } } |} it "gets the max among interface types" do complexity = reduce_result.first assert_equal 4, complexity end end describe "redundant fields" do let(:query_string) {%| { favoriteEdible { fatContent # this is executed separately and counts separately: aliasedFatContent: fatContent ... on Edible { fatContent } ... edibleFields } } fragment edibleFields on Edible { fatContent } |} it "only counts them once" do complexity = reduce_result.first assert_equal 3, complexity end end describe "redundant fields not within a fragment" do let(:query_string) {%| { cheese { id } cheese { id } } |} it "only counts them once" do complexity = reduce_result.first assert_equal 2, complexity end end end describe "relay types" do let(:query) { GraphQL::Query.new(StarWars::Schema, query_string) } let(:query_string) {%| { rebels { ships(first: 1) { edges { node { id } } nodes { id } pageInfo { hasNextPage } } } } |} it "gets the complexity" do complexity = reduce_result.first expected_complexity = 1 + # rebels 1 + # ships 1 + # edges 1 + # nodes 1 + 1 + # pageInfo, hasNextPage 1 + 1 + 1 # node, id, id assert_equal expected_complexity, complexity end describe "first/last" do let(:query_string) {%| { rebels { s1: ships(first: 5) { edges { node { id } } pageInfo { hasNextPage } } s2: ships(last: 3) { nodes { id } } } } |} it "uses first/last for calculating complexity" do complexity = reduce_result.first expected_complexity = ( 1 + # rebels (1 + 1 + (5 * 2) + 2) + # s1 (1 + 1 + (3 * 1) + 0) # s2 ) assert_equal expected_complexity, complexity end end describe "Field-level max_page_size" do let(:query_string) {%| { rebels { ships { nodes { id } } } } |} it "uses field max_page_size" do complexity = reduce_result.first assert_equal 1 + 1 + 1 + (1000 * 1), complexity end end describe "Schema-level default_max_page_size" do let(:query_string) {%| { rebels { bases { nodes { id } totalCount } } } |} it "uses schema default_max_page_size" do complexity = reduce_result.first assert_equal 1 + 1 + 1 + (3 * 1) + 1, complexity end end describe "Field-level default_page_size" do let(:query_string) {%| { rebels { shipsWithDefaultPageSize { nodes { id } } } } |} it "uses field default_page_size" do complexity = reduce_result.first assert_equal 1 + 1 + 1 + (500 * 1), complexity end end describe "Schema-level default_page_size" do let(:query) { GraphQL::Query.new(StarWars::SchemaWithDefaultPageSize, query_string) } let(:query_string) {%| { rebels { bases { nodes { id } totalCount } } } |} it "uses schema default_page_size" do complexity = reduce_result.first assert_equal 1 + 1 + 1 + (2 * 1) + 1, complexity end end end describe "calucation complexity for a multiplex" do let(:query_string) {%| query cheeses { cheese(id: 1) { id flavor source } } |} it "sums complexity for both queries" do complexity = reduce_multiplex_result.first assert_equal 8, complexity end describe "abstract type" do let(:query_string) {%| query Edible { allEdible { origin fatContent } } |} it "sums complexity for both queries" do complexity = reduce_multiplex_result.first assert_equal 6, complexity end end end describe "custom complexities" do class CustomComplexitySchema < GraphQL::Schema module ComplexityInterface include GraphQL::Schema::Interface field :value, Int end class SingleComplexity < GraphQL::Schema::Object field :value, Int, complexity: 0.1 field :complexity, SingleComplexity do argument :int_value, Int, required: false complexity(->(ctx, args, child_complexity) { args[:int_value] + child_complexity }) end implements ComplexityInterface end class DoubleComplexity < GraphQL::Schema::Object field :value, Int, complexity: 4 implements ComplexityInterface end class Query < GraphQL::Schema::Object field :complexity, SingleComplexity do argument :int_value, Int, required: false, prepare: ->(val, ctx) { if ctx[:raise_prepare_error] raise GraphQL::ExecutionError, "Boom" else val end } complexity ->(ctx, args, child_complexity) { args[:int_value] + child_complexity } end def complexity(int_value:) { value: int_value } end field :inner_complexity, ComplexityInterface do argument :value, Int, required: false end end query(Query) orphan_types(DoubleComplexity) end let(:query) { GraphQL::Query.new(complexity_schema, query_string, context: query_context) } let(:complexity_schema) { CustomComplexitySchema } let(:query_string) {%| { a: complexity(intValue: 3) { value } b: complexity(intValue: 6) { value complexity(intValue: 1) { value } } } |} it "sums the complexity" do complexity = reduce_result.first # 10 from `complexity`, `0.3` from `value` assert_equal 10.3, complexity end describe "same field on multiple types" do let(:query_string) {%| { innerComplexity(intValue: 2) { ... on SingleComplexity { value } ... on DoubleComplexity { value } } } |} it "picks them max for those fields" do complexity = reduce_result.first # 1 for innerComplexity + 4 for DoubleComplexity.value assert_equal 5, complexity end end describe "when prepare raises an error" do let(:query_string) { "{ complexity(intValue: 3) { value } }"} let(:query_context) { { raise_prepare_error: true } } it "handles it nicely" do result = query.result assert_equal ["Boom"], result["errors"].map { |e| e["message"] } complexity = reduce_result.first assert_equal 0.1, complexity end end end describe "custom complexities by complexity_for(...)" do class CustomComplexityByMethodSchema < GraphQL::Schema module ComplexityInterface include GraphQL::Schema::Interface field :value, Int end class SingleComplexity < GraphQL::Schema::Object field :value, Int, complexity: 0.1 field :complexity, SingleComplexity do argument :int_value, Int, required: false def complexity_for(query:, child_complexity:, lookahead:) lookahead.arguments[:int_value] + child_complexity end end implements ComplexityInterface end class ComplexityFourField < GraphQL::Schema::Field def complexity_for(query:, lookahead:, child_complexity:) 4 end end class DoubleComplexity < GraphQL::Schema::Object field_class ComplexityFourField field :value, Int implements ComplexityInterface end class Thing < GraphQL::Schema::Object field :name, String end class CustomThingConnection < GraphQL::Types::Relay::BaseConnection edge_type Thing.edge_type field :something_special, String, complexity: 3 end class Query < GraphQL::Schema::Object field :complexity, SingleComplexity do argument :int_value, Int, required: false def complexity_for(query:, child_complexity:, lookahead:) lookahead.arguments[:int_value] + child_complexity end end field :inner_complexity, ComplexityInterface do argument :value, Int, required: false end field :things, Thing.connection_type, max_page_size: 100 do argument :count, Int, validates: { numericality: { less_than: 50 } } end def things(count:) count.times.map {|t| {name: t.to_s}} end class ThingsCustom < GraphQL::Schema::Resolver type CustomThingConnection, null: false complexity 100 def resolve 5.times { |t| { name: "Thing #{t}" } } end end field :things_custom, resolver: ThingsCustom end query(Query) orphan_types(DoubleComplexity) end let(:query) { GraphQL::Query.new(complexity_schema, query_string) } let(:complexity_schema) { CustomComplexityByMethodSchema } let(:query_string) {%| { a: complexity(intValue: 3) { value } b: complexity(intValue: 6) { value complexity(intValue: 1) { value } } } |} it "sums the complexity" do complexity = reduce_result.first # 10 from `complexity`, `0.3` from `value` assert_equal 10.3, complexity end describe "same field on multiple types" do let(:query_string) {%| { innerComplexity(value: 2) { ... on SingleComplexity { value } ... on DoubleComplexity { value } } } |} it "picks them max for those fields" do complexity = reduce_result.first # 1 for innerComplexity + 4 for DoubleComplexity.value assert_equal 5, complexity end end describe "when the query fails validation" do let(:query_string) {%| { things(count: 200, first: 5) { nodes { name } } } |} it "handles the error" do res = GraphQL::Query.new(complexity_schema, query_string).result assert_equal ["count must be less than 50"], res["errors"].map { |e| e["message"] } complexity = reduce_result.first assert_equal 102, complexity, "It uses max page size" end end describe "when connection fields have custom complexity" do let(:query_string) { "{ thingsCustom(first: 2) { somethingSpecial nodes { name } } }"} it "uses the custom configured value" do complexity = reduce_result.first assert_equal 106, complexity end end end describe "field_complexity hook" do class CustomComplexityAnalyzer < GraphQL::Analysis::AST::QueryComplexity def initialize(query) super @field_complexities_by_query = {} end def result super @field_complexities_by_query[@query] end private def field_complexity(scoped_type_complexity, max_complexity:, child_complexity:) @field_complexities_by_query[scoped_type_complexity.query] ||= {} @field_complexities_by_query[scoped_type_complexity.query][scoped_type_complexity.response_path] = { max_complexity: max_complexity, child_complexity: child_complexity, } end end let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, [CustomComplexityAnalyzer]) } let(:query_string) {%| { cheese { id } cheese { id flavor } } |} it "gets called for each field with complexity data" do field_complexities = reduce_result.first assert_equal({ ['cheese', 'id'] => { max_complexity: 1, child_complexity: nil }, ['cheese', 'flavor'] => { max_complexity: 1, child_complexity: nil }, ['cheese'] => { max_complexity: 3, child_complexity: 2 }, }, field_complexities) end end end graphql-ruby-2.2.17/spec/graphql/analysis/ast/query_depth_spec.rb000066400000000000000000000044541476434635200251370ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::QueryDepth do let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::QueryDepth]) } let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables) } let(:variables) { {} } describe "multiple operations" do let(:query_string) {%| query Cheese1 { cheese1: cheese(id: 1) { id flavor } } query Cheese2 { cheese(id: 2) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, operation_name: "Cheese1") } it "analyzes the selected operation only" do depth = result.first assert_equal 2, depth end end describe "simple queries" do let(:query_string) {%| query cheeses($isIncluded: Boolean = true){ # depth of 2 cheese1: cheese(id: 1) { id flavor } # depth of 4 cheese2: cheese(id: 2) @include(if: $isIncluded) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} it "finds the max depth" do depth = result.first assert_equal 4, depth end describe "with directives" do let(:variables) { { "isIncluded" => false } } it "doesn't count skipped fields" do assert_equal 2, result.first end end end describe "query with fragments" do let(:query_string) {%| { # depth of 2 cheese1: cheese(id: 1) { id flavor } # depth of 4 cheese2: cheese(id: 2) { ... cheeseFields1 } } fragment cheeseFields1 on Cheese { similarCheese(source: COW) { id ... cheeseFields2 } } fragment cheeseFields2 on Cheese { similarCheese(source: SHEEP) { id } } |} it "finds the max depth" do assert_equal 4, result.first end end end graphql-ruby-2.2.17/spec/graphql/analysis/ast_spec.rb000066400000000000000000000322461476434635200226060ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST do class AstTypeCollector < GraphQL::Analysis::AST::Analyzer def initialize(query) super @types = [] end def on_enter_operation_definition(node, parent, visitor) @types << visitor.type_definition end def on_enter_field(memo, node, visitor) @types << visitor.field_definition.type.unwrap end def result @types end end class AstNodeCounter < GraphQL::Analysis::AST::Analyzer def initialize(query) super @nodes = Hash.new { |h,k| h[k] = 0 } end def on_enter_abstract_node(node, parent, _visitor) @nodes[node.class] += 1 end alias :on_enter_operation_definition :on_enter_abstract_node alias :on_enter_field :on_enter_abstract_node alias :on_enter_argument :on_enter_abstract_node def result @nodes end end class AstConditionalAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query) super @i_have_been_called = false end def analyze? !!query.context[:analyze] end def on_operation_definition(node, parent, visitor) @i_have_been_called = true end def result @i_have_been_called end end class AstPrecomputedAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query) super @i_have_been_visited = false end def visit? query.context[:precomputed_result].nil? end def on_enter_field(node, parent, visitor) @i_have_been_visited = true end def result return query.context[:precomputed_result], @i_have_been_visited end end class AstErrorAnalyzer < GraphQL::Analysis::AST::Analyzer def result GraphQL::AnalysisError.new("An Error!") end end class AstPreviousField < GraphQL::Analysis::AST::Analyzer def on_enter_field(node, parent, visitor) @previous_field = visitor.previous_field_definition end def result @previous_field end end class AstArguments < GraphQL::Analysis::AST::Analyzer def on_enter_argument(node, parent, visitor) @argument = visitor.argument_definition @previous_argument = visitor.previous_argument_definition end def result [@argument, @previous_argument] end end describe "using the AST analysis engine" do let(:schema) do query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foobar, Integer, null: false def foobar 1337 end end Class.new(GraphQL::Schema) do query query_type query_analyzer AstErrorAnalyzer end end let(:query_string) {%| query { foobar } |} let(:query) { GraphQL::Query.new(schema, query_string, variables: {}) } it "runs the AST analyzers correctly" do res = query.result refute res.key?("data") assert_equal ["An Error!"], res["errors"].map { |e| e["message"] } end end describe ".analyze_query" do let(:analyzers) { [AstTypeCollector, AstNodeCounter] } let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, analyzers) } let(:variables) { {} } let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables) } let(:query_string) {%| { cheese(id: 1) { id flavor } } |} describe "without a valid operation" do let(:query_string) {%| # A comment # is an invalid operation # Should break |} it "bails early when there is no selected operation to be executed" do assert_equal 2, reduce_result.size end end describe "conditional analysis" do let(:analyzers) { [AstTypeCollector, AstConditionalAnalyzer] } describe "when analyze? returns false" do let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, context: { analyze: false }) } it "does not run the analyzer" do # Only type_collector ran assert_equal 1, reduce_result.size end end describe "when analyze? returns true" do let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, context: { analyze: true }) } it "it runs the analyzer" do # Both analyzers ran assert_equal 2, reduce_result.size end end describe "Visitor#previous_field_definition" do let(:analyzers) { [AstPreviousField] } let(:query) { GraphQL::Query.new(Dummy::Schema, "{ __schema { types { name } } }") } it "it runs the analyzer" do prev_field = reduce_result.first assert_equal "__Schema.types", prev_field.path end end describe "Visitor#argument_definition" do let(:analyzers) { [AstArguments] } let(:query) do GraphQL::Query.new( Dummy::Schema, '{ searchDairy(product: [{ source: "SHEEP" }]) { ... on Cheese { id } } }' ) end it "it runs the analyzer" do argument, prev_argument = reduce_result.first assert_equal "DairyProductInput.source", argument.path assert_equal "Query.searchDairy.product", prev_argument.path end end end describe "precomputed analysis" do let(:analyzers) { [AstPrecomputedAnalyzer] } describe "when visit? returns true" do let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, context: {}) } it "runs the analyzer with visitation" do assert_equal [nil, true], reduce_result.first end end describe "when visit? returns false" do let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, context: { precomputed_result: 23 }) } it "runs the analyzer without visitation" do assert_equal [23, false], reduce_result.first end end end it "calls the defined analyzers" do collected_types, node_counts = reduce_result expected_visited_types = [ Dummy::DairyAppQuery, Dummy::Cheese, GraphQL::Types::Int, GraphQL::Types::String ] assert_equal expected_visited_types, collected_types expected_node_counts = { GraphQL::Language::Nodes::OperationDefinition => 1, GraphQL::Language::Nodes::Field => 3, GraphQL::Language::Nodes::Argument => 1 } assert_equal expected_node_counts, node_counts end class FinishedSchema < GraphQL::Schema class FinishedAnalyzer < GraphQL::Analysis::AST::Analyzer def on_enter_field(node, parent, visitor) if query.context[:force_prepare] visitor.arguments_for(node, visitor.field_definition) end end def result query.context[:analysis_finished] = true end end class Query < GraphQL::Schema::Object field :f1, Int do argument :arg, String, prepare: ->(val, ctx) { ctx[:analysis_finished] ? val.to_i : raise("Prepared too soon!") } end def f1(arg:) arg end end query(Query) query_analyzer(FinishedAnalyzer) end it "doesn't call prepare hooks by default" do res = FinishedSchema.execute("{ f1(arg: \"5\") }") assert_equal 5, res["data"]["f1"] err = assert_raises RuntimeError do FinishedSchema.execute("{ f1(arg: \"5\") }", context: { force_prepare: true }) end assert_equal "Prepared too soon!", err.message end describe "tracing" do let(:query_string) { "{ t: __typename }"} it "emits traces" do traces = TestTracing.with_trace do ctx = { tracers: [TestTracing] } Dummy::Schema.execute(query_string, context: ctx) end # The query_trace is on the list _first_ because it finished first if USING_C_PARSER _lex, _parse, _validate, query_trace, multiplex_trace, *_rest = traces else _parse, _validate, query_trace, multiplex_trace, *_rest = traces end assert_equal "analyze_multiplex", multiplex_trace[:key] assert_instance_of GraphQL::Execution::Multiplex, multiplex_trace[:multiplex] assert_equal "analyze_query", query_trace[:key] assert_instance_of GraphQL::Query, query_trace[:query] end end class AstConnectionCounter < GraphQL::Analysis::AST::Analyzer def initialize(query) super @fields = 0 @connections = 0 end def on_enter_field(node, parent, visitor) if visitor.field_definition.connection? @connections += 1 else @fields += 1 end end def result { fields: @fields, connections: @connections } end end describe "when processing fields" do let(:analyzers) { [AstConnectionCounter] } let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, analyzers) } let(:query) { GraphQL::Query.new(StarWars::Schema, query_string, variables: variables) } let(:query_string) {%| query getBases { empire { basesByName(first: 30) { edges { cursor } } bases(first: 30) { edges { cursor } } } } |} it "knows which fields are connections" do connection_counts = reduce_result.first expected_connection_counts = { :fields => 5, :connections => 2 } assert_equal expected_connection_counts, connection_counts end end end describe "Detecting all-introspection queries" do class AllIntrospectionSchema < GraphQL::Schema class Query < GraphQL::Schema::Object field :int, Int end query(Query) end class AllIntrospectionAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query) @is_introspection = true super end def on_enter_field(node, parent, visitor) @is_introspection &= (visitor.field_definition.introspection? || ((owner = visitor.field_definition.owner) && owner.introspection?)) end def result @is_introspection end end def is_introspection?(query_str) query = GraphQL::Query.new(AllIntrospectionSchema, query_str) result = GraphQL::Analysis::AST.analyze_query(query, [AllIntrospectionAnalyzer]) result.first end it "returns true for queries containing only introspection types and fields" do assert is_introspection?("{ __typename }") refute is_introspection?("{ int }") assert is_introspection?(GraphQL::Introspection::INTROSPECTION_QUERY) assert is_introspection?("{ __type(name: \"Something\") { fields { name } } }") refute is_introspection?("{ int __type(name: \"Thing\") { name } }") end end describe "when there's a hidden field" do class HiddenAnalyzedFieldSchema < GraphQL::Schema class DoNothingAnalyzer < GraphQL::Analysis::AST::Analyzer def on_enter_field(node, parent, visitor) @result ||= [] @result << [node.name, visitor.field_definition.class] super end def result @result end end class BaseField < GraphQL::Schema::Field def initialize(*args, visible: true, **kwargs, &block) @visible = visible super(*args, **kwargs, &block) end def visible?(context) return @visible end end class BaseObject < GraphQL::Schema::Object field_class BaseField end class Article < BaseObject field :title, String, null: false end class Query < BaseObject field :article, String, visible: false do |f| f.argument(:id, Integer) end def article(id:) { title: "hello world" } end end query Query end it "uses nil for the field definition" do gql = <<~GQL { article(id: 1) { title } } GQL query = GraphQL::Query.new(HiddenAnalyzedFieldSchema, gql) result = GraphQL::Analysis::AST.analyze_query(query, [HiddenAnalyzedFieldSchema::DoNothingAnalyzer]) assert_equal [[["article", NilClass], ["title", NilClass]]], result end end describe ".validate_timeout" do class AnalysisTimeoutSchema < GraphQL::Schema class SlowAnalyzer < GraphQL::Analysis::AST::Analyzer def on_enter_field(node, parent, visitor) sleep 0.1 super end def result nil end end class Query < GraphQL::Schema::Object field :f1, Int def f1 context[:int] ||= 0 context[:int] += 1 end end query(Query) query_analyzer(SlowAnalyzer) validate_timeout 0.5 end it "covers analysis too" do res = AnalysisTimeoutSchema.execute("{ f1: f1 f2: f1 }") assert_equal({ "f1" => 1, "f2" => 2}, res["data"]) res2 = AnalysisTimeoutSchema.execute("{ f1: f1, f2: f1, f3: f1, f4: f1, f5: f1, f6: f1}") assert_equal ["Timeout on validation of query"], res2["errors"].map { |e| e["message"]} end end end graphql-ruby-2.2.17/spec/graphql/authorization_spec.rb000066400000000000000000000765131476434635200231010ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Authorization" do module AuthTest class Box attr_reader :value def initialize(value:) @value = value end end class BaseArgument < GraphQL::Schema::Argument def visible?(context) super && (context[:hide] ? @name != "hidden" : true) end def authorized?(parent_object, value, context) super && parent_object != :hide2 end end class BaseInputObjectArgument < BaseArgument def authorized?(parent_object, value, context) super && parent_object != :hide3 end end class BaseInputObject < GraphQL::Schema::InputObject argument_class BaseInputObjectArgument end class BaseField < GraphQL::Schema::Field argument_class BaseArgument def visible?(context) super && (context[:hide] ? @name != "hidden" : true) end def authorized?(object, args, context) if object == :raise raise GraphQL::UnauthorizedFieldError.new("raised authorized field error", object: object) end return Box.new(value: context[:lazy_field_authorized]) if context.key?(:lazy_field_authorized) super && object != :hide && object != :replace end end class BaseObject < GraphQL::Schema::Object field_class BaseField end module BaseInterface include GraphQL::Schema::Interface end class BaseEnumValue < GraphQL::Schema::EnumValue def initialize(*args, role: nil, **kwargs) @role = role super(*args, **kwargs) end def visible?(context) super && (context[:hide] ? @role != :hidden : true) end end class BaseEnum < GraphQL::Schema::Enum enum_value_class(BaseEnumValue) end module HiddenInterface include BaseInterface def self.visible?(ctx) super && !ctx[:hide] end def self.resolve_type(obj, ctx) HiddenObject end end module HiddenDefaultInterface include BaseInterface # visible? will call the super method def self.resolve_type(obj, ctx) HiddenObject end end class HiddenObject < BaseObject implements HiddenInterface implements HiddenDefaultInterface def self.visible?(ctx) super && !ctx[:hide] end field :some_field, String end class RelayObject < BaseObject def self.visible?(ctx) super && !ctx[:hidden_relay] end def self.authorized?(_val, ctx) super && !ctx[:unauthorized_relay] end field :some_field, String end class UnauthorizedObject < BaseObject def self.authorized?(value, context) if context[:raise] raise GraphQL::UnauthorizedError.new("raised authorized object error", object: value.object) end super && !context[:hide] end field :value, String, null: false, method: :itself end class UnauthorizedBox < BaseObject # Hide `"a"` def self.authorized?(value, context) super && value != "a" end field :value, String, null: false, method: :itself end module UnauthorizedInterface include BaseInterface def self.resolve_type(obj, ctx) if obj.is_a?(String) UnauthorizedCheckBox else raise "Unexpected value: #{obj.inspect}" end end end class UnauthorizedCheckBox < BaseObject implements UnauthorizedInterface # This authorized check returns a lazy object, it should be synced by the runtime. def self.authorized?(value, context) if !value.is_a?(String) raise "Unexpected box value: #{value.inspect}" end is_authed = super && value != "a" # Make it many levels nested just to make sure we support nested lazy objects Box.new(value: Box.new(value: Box.new(value: Box.new(value: is_authed)))) end field :value, String, null: false, method: :itself end class IntegerObject < BaseObject def self.authorized?(obj, ctx) if !obj.is_a?(Integer) raise "Unexpected IntegerObject: #{obj}" end is_allowed = !(ctx[:unauthorized_relay] || obj == ctx[:exclude_integer]) Box.new(value: Box.new(value: is_allowed)) end field :value, Integer, null: false, method: :itself end class IntegerObjectEdge < GraphQL::Types::Relay::BaseEdge node_type(IntegerObject) end class IntegerObjectConnection < GraphQL::Types::Relay::BaseConnection edge_type(IntegerObjectEdge) end # This object responds with `replaced => false`, # but if its replacement value is used, it gives `replaced => true` class Replaceable def replacement { replaced: true } end def replaced false end end class ReplacedObject < BaseObject def self.authorized?(obj, ctx) super && !ctx[:replace_me] end field :replaced, Boolean, null: false end class LandscapeFeature < BaseEnum value "MOUNTAIN" value "STREAM", role: :unauthorized value "FIELD" value "TAR_PIT", role: :hidden end class Query < BaseObject def self.authorized?(obj, ctx) !ctx[:query_unauthorized] end field :hidden, Integer, null: false field :unauthorized, Integer, method: :itself field :int2, Integer do argument :int, Integer, required: false argument :hidden, Integer, required: false argument :unauthorized, Integer, required: false end def int2(**args) args[:unauthorized] || 1 end field :landscape_feature, LandscapeFeature, null: false do argument :string, String, required: false argument :enum, LandscapeFeature, required: false end def landscape_feature(string: nil, enum: nil) string || enum end field :landscape_features, [LandscapeFeature], null: false do argument :strings, [String], required: false argument :enums, [LandscapeFeature], required: false end def landscape_features(strings: [], enums: []) strings + enums end def empty_array; []; end field :hidden_object, HiddenObject, null: false, resolver_method: :itself field :hidden_interface, HiddenInterface, null: false, resolver_method: :itself field :hidden_default_interface, HiddenDefaultInterface, null: false, resolver_method: :itself field :hidden_connection, RelayObject.connection_type, null: :false, resolver_method: :empty_array field :hidden_edge, RelayObject.edge_type, null: :false, resolver_method: :edge_object field :unauthorized_object, UnauthorizedObject, resolver_method: :itself field :unauthorized_connection, RelayObject.connection_type, null: false, resolver_method: :array_with_item field :unauthorized_edge, RelayObject.edge_type, null: false, resolver_method: :edge_object def edge_object OpenStruct.new(node: 100) end def array_with_item [1] end field :unauthorized_lazy_box, UnauthorizedBox do argument :value, String end def unauthorized_lazy_box(value:) # Make it extra nested, just for good measure. Box.new(value: Box.new(value: value)) end field :unauthorized_list_items, [UnauthorizedObject] def unauthorized_list_items [self, self] end field :unauthorized_lazy_check_box, UnauthorizedCheckBox, resolver_method: :unauthorized_lazy_box do argument :value, String end field :unauthorized_interface, UnauthorizedInterface, resolver_method: :unauthorized_lazy_box do argument :value, String end field :unauthorized_lazy_list_interface, [UnauthorizedInterface, null: true] def unauthorized_lazy_list_interface ["z", Box.new(value: Box.new(value: "z2")), "a", Box.new(value: "a")] end field :integers, IntegerObjectConnection, null: false def integers [1,2,3] end field :lazy_integers, IntegerObjectConnection, null: false def lazy_integers Box.new(value: Box.new(value: [1,2,3])) end field :replaced_object, ReplacedObject, null: false def replaced_object Replaceable.new end end class DoHiddenStuff < GraphQL::Schema::RelayClassicMutation def self.visible?(ctx) super && (ctx[:hidden_mutation] ? false : true) end end class DoHiddenStuff2 < GraphQL::Schema::Mutation def self.visible?(ctx) super && !ctx[:hidden_mutation] end field :some_return_field, String end class DoUnauthorizedStuff < GraphQL::Schema::RelayClassicMutation def self.authorized?(obj, ctx) super && (ctx[:unauthorized_mutation] ? false : true) end end class Mutation < BaseObject field :do_hidden_stuff, mutation: DoHiddenStuff field :do_hidden_stuff2, mutation: DoHiddenStuff2 field :do_unauthorized_stuff, mutation: DoUnauthorizedStuff end class Nothing < GraphQL::Schema::Directive locations(FIELD) def self.visible?(ctx) !!ctx[:show_nothing_directive] end end class Schema < GraphQL::Schema query(Query) mutation(Mutation) directive(Nothing) lazy_resolve(Box, :value) def self.unauthorized_object(err) if err.object.respond_to?(:replacement) err.object.replacement elsif err.object == :replace 33 elsif err.object == :raise_from_object raise GraphQL::ExecutionError, err.message else raise GraphQL::ExecutionError, "Unauthorized #{err.type.graphql_name}: #{err.object.inspect}" end end # use GraphQL::Backtrace end class SchemaWithFieldHook < GraphQL::Schema query(Query) lazy_resolve(Box, :value) def self.unauthorized_field(err) if err.object == :replace 42 elsif err.object == :raise raise GraphQL::ExecutionError, "#{err.message} in field #{err.field.graphql_name}" else raise GraphQL::ExecutionError, "Unauthorized field #{err.field.graphql_name} on #{err.type.graphql_name}: #{err.object}" end end end end def auth_execute(*args, **kwargs) AuthTest::Schema.execute(*args, **kwargs) end describe "applying the visible? method" do it "works in queries" do res = auth_execute(" { int int2 } ", context: { hide: true }) assert_equal 1, res["errors"].size end it "applies return type visibility to fields" do error_queries = { "hiddenObject" => "{ hiddenObject { __typename } }", "hiddenInterface" => "{ hiddenInterface { __typename } }", "hiddenDefaultInterface" => "{ hiddenDefaultInterface { __typename } }", } error_queries.each do |name, q| hidden_res = auth_execute(q, context: { hide: true}) assert_equal ["Field '#{name}' doesn't exist on type 'Query'"], hidden_res["errors"].map { |e| e["message"] } visible_res = auth_execute(q) # Both fields exist; the interface resolves to the object type, though assert_equal "HiddenObject", visible_res["data"][name]["__typename"] end end it "uses the mutation for derived fields, inputs and outputs" do query = "mutation { doHiddenStuff(input: {}) { __typename } }" res = auth_execute(query, context: { hidden_mutation: true }) assert_equal ["Field 'doHiddenStuff' doesn't exist on type 'Mutation'"], res["errors"].map { |e| e["message"] } # `#resolve` isn't implemented, so this errors out: assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end introspection_q = <<-GRAPHQL { t1: __type(name: "DoHiddenStuffInput") { name } t2: __type(name: "DoHiddenStuffPayload") { name } } GRAPHQL hidden_introspection_res = auth_execute(introspection_q, context: { hidden_mutation: true }) assert_nil hidden_introspection_res["data"]["t1"] assert_nil hidden_introspection_res["data"]["t2"] visible_introspection_res = auth_execute(introspection_q) assert_equal "DoHiddenStuffInput", visible_introspection_res["data"]["t1"]["name"] assert_equal "DoHiddenStuffPayload", visible_introspection_res["data"]["t2"]["name"] end it "works with Schema::Mutation" do query = "mutation { doHiddenStuff2 { __typename } }" res = auth_execute(query, context: { hidden_mutation: true }) assert_equal ["Field 'doHiddenStuff2' doesn't exist on type 'Mutation'"], res["errors"].map { |e| e["message"] } # `#resolve` isn't implemented, so this errors out: assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end end it "uses the base type for edges and connections" do query = <<-GRAPHQL { hiddenConnection { __typename } hiddenEdge { __typename } } GRAPHQL hidden_res = auth_execute(query, context: { hidden_relay: true }) assert_equal 2, hidden_res["errors"].size visible_res = auth_execute(query) assert_equal "RelayObjectConnection", visible_res["data"]["hiddenConnection"]["__typename"] assert_equal "RelayObjectEdge", visible_res["data"]["hiddenEdge"]["__typename"] end it "treats hidden enum values as non-existant, even in lists" do hidden_res_1 = auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeature(enum: TAR_PIT) } GRAPHQL assert_equal ["Argument 'enum' on Field 'landscapeFeature' has an invalid value (TAR_PIT). Expected type 'LandscapeFeature'."], hidden_res_1["errors"].map { |e| e["message"] } hidden_res_2 = auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeatures(enums: [STREAM, TAR_PIT]) } GRAPHQL assert_equal ["Argument 'enums' on Field 'landscapeFeatures' has an invalid value ([STREAM, TAR_PIT]). Expected type '[LandscapeFeature!]'."], hidden_res_2["errors"].map { |e| e["message"] } success_res = auth_execute <<-GRAPHQL, context: { hide: false } { landscapeFeature(enum: TAR_PIT) landscapeFeatures(enums: [STREAM, TAR_PIT]) } GRAPHQL assert_equal "TAR_PIT", success_res["data"]["landscapeFeature"] assert_equal ["STREAM", "TAR_PIT"], success_res["data"]["landscapeFeatures"] end it "refuses to resolve to hidden enum values" do expected_class = AuthTest::LandscapeFeature::UnresolvedValueError assert_raises(expected_class) do auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeature(string: "TAR_PIT") } GRAPHQL end assert_raises(expected_class) do auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeatures(strings: ["STREAM", "TAR_PIT"]) } GRAPHQL end end it "works in introspection" do res = auth_execute <<-GRAPHQL, context: { hide: true, hidden_mutation: true } { query: __type(name: "Query") { fields { name args { name } } } hiddenObject: __type(name: "HiddenObject") { name } hiddenInterface: __type(name: "HiddenInterface") { name } landscapeFeatures: __type(name: "LandscapeFeature") { enumValues { name } } } GRAPHQL query_field_names = res["data"]["query"]["fields"].map { |f| f["name"] } refute_includes query_field_names, "int" int2_arg_names = res["data"]["query"]["fields"].find { |f| f["name"] == "int2" }["args"].map { |a| a["name"] } assert_equal ["int", "unauthorized"], int2_arg_names assert_nil res["data"]["hiddenObject"] assert_nil res["data"]["hiddenInterface"] visible_landscape_features = res["data"]["landscapeFeatures"]["enumValues"].map { |v| v["name"] } assert_equal ["MOUNTAIN", "STREAM", "FIELD"], visible_landscape_features end it "works when printing the SDL" do full_sdl = AuthTest::Schema.to_definition restricted_sdl = AuthTest::Schema.to_definition(context: { hide: true, hidden_mutation: true, hidden_relay: true }) assert_includes full_sdl, 'Hidden' assert_includes full_sdl, 'hidden' refute_includes restricted_sdl, 'Hidden' refute_includes restricted_sdl, 'hidden' end it "works with directives" do query_str = "{ __typename @nothing }" visible_response = auth_execute(query_str, context: { show_nothing_directive: true }) assert_equal "Query", visible_response["data"]["__typename"] hidden_response = auth_execute(query_str) assert_equal ["Directive @nothing is not defined"], hidden_response["errors"].map { |e| e["message"] } end end describe "applying the authorized? method" do it "halts on unauthorized objects, replacing the object with nil" do query = "{ unauthorizedObject { __typename } }" hidden_response = auth_execute(query, context: { hide: true }) assert_nil hidden_response["data"].fetch("unauthorizedObject") visible_response = auth_execute(query, context: {}) assert_equal({ "__typename" => "UnauthorizedObject" }, visible_response["data"]["unauthorizedObject"]) end it "halts on unauthorized mutations" do query = "mutation { doUnauthorizedStuff(input: {}) { __typename } }" res = auth_execute(query, context: { unauthorized_mutation: true }) assert_nil res["data"].fetch("doUnauthorizedStuff") assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end end describe "field level authorization" do describe "unauthorized field" do describe "with an unauthorized field hook configured" do describe "when the hook returns a value" do it "replaces the response with the return value of the unauthorized field hook" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :replace) assert_equal 42, response["data"].fetch("unauthorized") end end describe "when the field hook raises an error" do it "returns nil" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :hide) assert_nil response["data"].fetch("unauthorized") end it "adds the error to the errors key" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :hide) assert_equal ["Unauthorized field unauthorized on Query: hide"], response["errors"].map { |e| e["message"] } end end describe "when the field authorization resolves lazily" do it "returns value if authorized" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 34, context: { lazy_field_authorized: true }) assert_equal 34, response["data"].fetch("unauthorized") end it "returns nil if not authorized" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 34, context: { lazy_field_authorized: false }) assert_nil response["data"].fetch("unauthorized") assert_equal ["Unauthorized field unauthorized on Query: 34"], response["errors"].map { |e| e["message"] } end end describe "when the field authorization raises an UnauthorizedFieldError" do it "receives the raised error" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :raise) assert_equal ["raised authorized field error in field unauthorized"], response["errors"].map { |e| e["message"] } end end end describe "with an unauthorized field hook not configured" do describe "When the object hook replaces the field" do it "delegates to the unauthorized object hook, which replaces the object" do query = "{ unauthorized }" response = AuthTest::Schema.execute(query, root_value: :replace) assert_equal 33, response["data"].fetch("unauthorized") end end describe "When the object hook raises an error" do it "returns nil" do query = "{ unauthorized }" response = AuthTest::Schema.execute(query, root_value: :hide) assert_nil response["data"].fetch("unauthorized") end it "adds the error to the errors key" do query = "{ unauthorized }" response = AuthTest::Schema.execute(query, root_value: :hide) assert_equal ["Unauthorized Query: :hide"], response["errors"].map { |e| e["message"] } end end end end describe "authorized field" do it "returns the field data" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 1) assert_equal 1, response["data"].fetch("unauthorized") end end end it "halts on unauthorized fields, using the parent object" do query = "{ unauthorized }" hidden_response = auth_execute(query, root_value: :hide) assert_nil hidden_response["data"].fetch("unauthorized") visible_response = auth_execute(query, root_value: 1) assert_equal 1, visible_response["data"]["unauthorized"] end it "halts on unauthorized arguments, using the parent object" do query = "{ int2(unauthorized: 5) }" hidden_response = auth_execute(query, root_value: :hide2) assert_nil hidden_response["data"].fetch("int2") visible_response = auth_execute(query) assert_equal 5, visible_response["data"]["int2"] end it "works with edges and connections" do query = <<-GRAPHQL { unauthorizedConnection { __typename edges { __typename node { __typename } } nodes { __typename } } unauthorizedEdge { __typename node { __typename } } } GRAPHQL unauthorized_res = auth_execute(query, context: { unauthorized_relay: true }) conn = unauthorized_res["data"].fetch("unauthorizedConnection") assert_equal "RelayObjectConnection", conn.fetch("__typename") # This is tricky: the previous behavior was to replace the _whole_ # list with `nil`. This was due to an implementation detail: # The list field's return value (an array of integers) was wrapped # _before_ returning, and during this wrapping, a cascading error # caused the entire field to be nilled out. # # In the interpreter, each list item is contained and the error doesn't propagate # up to the whole list. # # Originally, I thought that this was a _feature_ that obscured list entries. # But really, look at the test below: you don't get this "feature" if # you use `edges { node }`, so it can't be relied on in any way. # # All that to say, in the interpreter, `nodes` and `edges { node }` behave # the same. # # TODO revisit the docs for this. failed_nodes_value = [nil] assert_equal failed_nodes_value, conn.fetch("nodes") assert_equal [{"node" => nil, "__typename" => "RelayObjectEdge"}], conn.fetch("edges") edge = unauthorized_res["data"].fetch("unauthorizedEdge") assert_nil edge.fetch("node") assert_equal "RelayObjectEdge", edge["__typename"] unauthorized_object_paths = [ ["unauthorizedConnection", "edges", 0, "node"], ["unauthorizedConnection", "nodes", 0], ["unauthorizedEdge", "node"] ] assert_equal unauthorized_object_paths, unauthorized_res["errors"].map { |e| e["path"] } authorized_res = auth_execute(query) conn = authorized_res["data"].fetch("unauthorizedConnection") assert_equal "RelayObjectConnection", conn.fetch("__typename") assert_equal [{"__typename"=>"RelayObject"}], conn.fetch("nodes") assert_equal [{"node" => {"__typename" => "RelayObject"}, "__typename" => "RelayObjectEdge"}], conn.fetch("edges") edge = authorized_res["data"].fetch("unauthorizedEdge") assert_equal "RelayObject", edge.fetch("node").fetch("__typename") assert_equal "RelayObjectEdge", edge["__typename"] end it "authorizes _after_ resolving lazy objects" do query = <<-GRAPHQL { a: unauthorizedLazyBox(value: "a") { value } b: unauthorizedLazyBox(value: "b") { value } } GRAPHQL unauthorized_res = auth_execute(query) assert_nil unauthorized_res["data"].fetch("a") assert_equal "b", unauthorized_res["data"]["b"]["value"] end it "authorizes items in a list" do query = <<-GRAPHQL { unauthorizedListItems { __typename } } GRAPHQL unauthorized_res = auth_execute(query, context: { hide: true }) assert_nil unauthorized_res["data"]["unauthorizedListItems"] authorized_res = auth_execute(query, context: { hide: false }) assert_equal 2, authorized_res["data"]["unauthorizedListItems"].size end it "syncs lazy objects from authorized? checks" do query = <<-GRAPHQL { a: unauthorizedLazyCheckBox(value: "a") { value } b: unauthorizedLazyCheckBox(value: "b") { value } } GRAPHQL unauthorized_res = auth_execute(query) assert_nil unauthorized_res["data"].fetch("a") assert_equal "b", unauthorized_res["data"]["b"]["value"] # Also, the custom handler was called: assert_equal ["Unauthorized UnauthorizedCheckBox: \"a\""], unauthorized_res["errors"].map { |e| e["message"] } end it "Works for lazy connections" do query = <<-GRAPHQL { lazyIntegers { edges { node { value } } } } GRAPHQL res = auth_execute(query) assert_equal [1,2,3], res["data"]["lazyIntegers"]["edges"].map { |e| e["node"]["value"] } end it "Works for eager connections" do query = <<-GRAPHQL { integers { edges { node { value } } } } GRAPHQL res = auth_execute(query) assert_equal [1,2,3], res["data"]["integers"]["edges"].map { |e| e["node"]["value"] } end it "filters out individual nodes by value" do query = <<-GRAPHQL { integers { edges { node { value } } } } GRAPHQL res = auth_execute(query, context: { exclude_integer: 1 }) assert_equal [nil,2,3], res["data"]["integers"]["edges"].map { |e| e["node"] && e["node"]["value"] } assert_equal ["Unauthorized IntegerObject: 1"], res["errors"].map { |e| e["message"] } end it "works with lazy values / interfaces" do query = <<-GRAPHQL query($value: String!){ unauthorizedInterface(value: $value) { ... on UnauthorizedCheckBox { value } } } GRAPHQL res = auth_execute(query, variables: { value: "a"}) assert_nil res["data"]["unauthorizedInterface"] res2 = auth_execute(query, variables: { value: "b"}) assert_equal "b", res2["data"]["unauthorizedInterface"]["value"] end it "works with lazy values / lists of interfaces" do query = <<-GRAPHQL { unauthorizedLazyListInterface { ... on UnauthorizedCheckBox { value } } } GRAPHQL res = auth_execute(query) # An error from two, values from the others assert_equal ["Unauthorized UnauthorizedCheckBox: \"a\"", "Unauthorized UnauthorizedCheckBox: \"a\""], res["errors"].map { |e| e["message"] } assert_equal [{"value" => "z"}, {"value" => "z2"}, nil, nil], res["data"]["unauthorizedLazyListInterface"] end describe "with an unauthorized field hook configured" do it "replaces objects from the unauthorized_object hook" do query = "{ replacedObject { replaced } }" res = auth_execute(query, context: { replace_me: true }) assert_equal true, res["data"]["replacedObject"]["replaced"] res = auth_execute(query, context: { replace_me: false }) assert_equal false, res["data"]["replacedObject"]["replaced"] end it "works when the query hook returns false and there's no root object" do query = "{ __typename }" res = auth_execute(query) assert_equal "Query", res["data"]["__typename"] unauth_res = auth_execute(query, context: { query_unauthorized: true }) assert_nil unauth_res["data"] assert_equal [{"message"=>"Unauthorized Query: nil"}], unauth_res["errors"] end describe "when the object authorization raises an UnauthorizedFieldError" do it "receives the raised error" do query = "{ unauthorizedObject { value } }" response = auth_execute(query, context: { raise: true }, root_value: :raise_from_object) assert_equal ["raised authorized object error"], response["errors"].map { |e| e["message"] } end end end end describe "returning false" do class FalseSchema < GraphQL::Schema class Query < GraphQL::Schema::Object def self.authorized?(obj, ctx) false end field :int, Integer, null: false def int 1 end end query(Query) end it "works out-of-the-box" do res = FalseSchema.execute("{ int }") assert_nil res.fetch("data") refute res.key?("errors") end end describe "overriding authorized_new" do class AuthorizedNewOverrideSchema < GraphQL::Schema module LogTrace def trace(key, data) if ((q = data[:query]) && (c = q.context)) c[:log] << key end yield end ["parse", "lex", "validate", "analyze_query", "analyze_multiplex", "execute_query", "execute_multiplex", "execute_field", "execute_field_lazy", "authorized", "authorized_lazy", "resolve_type", "resolve_type_lazy", "execute_query_lazy"].each do |method_name| define_method(method_name) do |**data, &block| trace(method_name, data, &block) end end end module CustomIntrospection class DynamicFields < GraphQL::Introspection::DynamicFields def self.authorized_new(obj, ctx) new(obj, ctx) end end end class Query < GraphQL::Schema::Object def self.authorized_new(obj, ctx) new(obj, ctx) end field :int, Integer, null: false def int; 1; end end query(Query) introspection(CustomIntrospection) trace_with(LogTrace) end it "avoids calls to Object.authorized?" do log = [] res = AuthorizedNewOverrideSchema.execute("{ __typename int }", context: { log: log }) assert_equal "Query", res["data"]["__typename"] assert_equal 1, res["data"]["int"] expected_log = [ "validate", "analyze_query", "execute_query", "execute_field", "execute_field", "execute_query_lazy" ] assert_equal expected_log, log end end end graphql-ruby-2.2.17/spec/graphql/backtrace_spec.rb000066400000000000000000000230011476434635200221000ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Backtrace do class LazyError def raise_err raise "Lazy Boom" end end class ErrorAnalyzer < GraphQL::Analysis::AST::Analyzer def on_enter_operation_definition(node, parent_node, visitor) if node.name == "raiseError" raise GraphQL::AnalysisError, "this should not be wrapped by a backtrace, but instead, returned to the client" end end def result end end class NilInspectObject # Oops, this is evil, but it happens and we should handle it. def inspect; nil; end end module ErrorTrace def initialize(required_arg:, **_rest) super(**_rest) end def execute_multiplex(multiplex:) super raise "Instrumentation Boom" end end let(:resolvers) { { "Query" => { "field1" => Proc.new { :something }, "field2" => Proc.new { :something }, "nilInspect" => Proc.new { NilInspectObject.new }, "nestedList" => Proc.new { [ { thing: { name: "abc" } }, { thing: { name: :boom } } ] }, }, "Thing" => { "name" => Proc.new { |obj| obj[:name] == :boom ? raise("Boom!") : obj[:name] }, "listField" => Proc.new { :not_a_list }, "raiseField" => Proc.new { |o, a| raise("This is broken: #{a[:message]}") }, }, "ThingWrapper" => { "thing" => Proc.new { |obj| obj[:thing] }, }, "OtherThing" => { "strField" => Proc.new { LazyError.new }, }, } } let(:schema) { defn = <<-GRAPHQL type Query { field1: Thing field2: OtherThing nilInspect: Thing nestedList: [ThingWrapper] } type Thing { name: String listField: [OtherThing] raiseField(message: String!): Int } type ThingWrapper { thing: Thing } type OtherThing { strField: String } GRAPHQL schema_class = GraphQL::Schema.from_definition(defn, default_resolve: resolvers) schema_class.class_exec { lazy_resolve(LazyError, :raise_err) query_analyzer(ErrorAnalyzer) } schema_class } let(:backtrace_schema) { Class.new(schema) do use GraphQL::Backtrace end } describe "GraphQL backtrace helpers" do it "raises a TracedError when enabled" do assert_raises(GraphQL::Backtrace::TracedError) { backtrace_schema.execute("query BrokenList { field1 { listField { strField } } }") } assert_raises(GraphQL::Execution::Interpreter::ListResultFailedError) { schema.execute("query BrokenList { field1 { listField { strField } } }") } end it "works for objects inside lists" do assert_raises(GraphQL::Backtrace::TracedError) do backtrace_schema.execute("{ nestedList { thing { name } } }") end end it "annotates crashes from user code" do err = assert_raises(GraphQL::Backtrace::TracedError) { backtrace_schema.execute <<-GRAPHQL, root_value: "Root" query($msg: String = \"Boom\") { field1 { boomError: raiseField(message: $msg) } } GRAPHQL } # The original error info is present assert_instance_of RuntimeError, err.cause b = err.cause.backtrace assert_backtrace_includes(b, file: "backtrace_spec.rb", method: "block") assert_backtrace_includes(b, file: "field.rb", method: "resolve") assert_backtrace_includes(b, file: "runtime.rb", method: "evaluate_selections") assert_backtrace_includes(b, file: "interpreter.rb", method: "run_all") # GraphQL backtrace is present expected_graphql_backtrace = [ "3:13: Thing.raiseField as boomError", "2:11: Query.field1", "1:9: query", ] assert_equal expected_graphql_backtrace, err.graphql_backtrace # The message includes the GraphQL context rendered_table = [ 'Loc | Field | Object | Arguments | Result', '3:13 | Thing.raiseField as boomError | :something | {:message=>"Boom"} | #', '2:11 | Query.field1 | "Root" | {} | {}', '1:9 | query | "Root" | {"msg"=>"Boom"} | {field1: {...}}', ].join("\n") assert_includes err.message, "\n" + rendered_table # The message includes the original error message assert_includes err.message, "This is broken: Boom" assert_includes err.message, "spec/graphql/backtrace_spec.rb:49", "It includes the original backtrace" assert_includes err.message, "more lines" end it "annotates errors from Query#result" do query_str = "query StrField { field2 { strField } __typename }" context = { backtrace: true } query = GraphQL::Query.new(schema, query_str, context: context) err = assert_raises(GraphQL::Backtrace::TracedError) { query.result } assert_instance_of RuntimeError, err.cause end it "annotates errors inside lazy resolution" do # Test context-based flag err = assert_raises(GraphQL::Backtrace::TracedError) { schema.execute("query StrField { field2 { strField } __typename }", context: { backtrace: true }) } assert_instance_of RuntimeError, err.cause b = err.cause.backtrace assert_backtrace_includes(b, file: "backtrace_spec.rb", method: "raise_err") assert_backtrace_includes(b, file: "schema.rb", method: "sync_lazy") assert_backtrace_includes(b, file: "interpreter.rb", method: "run_all") expected_graphql_backtrace = [ "1:27: OtherThing.strField", "1:18: Query.field2", "1:1: query StrField", ] assert_equal(expected_graphql_backtrace, err.graphql_backtrace) rendered_table = [ 'Loc | Field | Object | Arguments | Result', '1:27 | OtherThing.strField | :something | {} | #', '1:18 | Query.field2 | nil | {} | {strField: (unresolved)}', '1:1 | query StrField | nil | {} | {field2: {...}, __typename: "Query"}', ].join("\n") assert_includes err.message, rendered_table end it "returns analysis errors to the client" do res = backtrace_schema.execute("query raiseError { __typename }") assert_equal "this should not be wrapped by a backtrace, but instead, returned to the client", res["errors"].first["message"] end it "always stringifies the #inspect response" do # test the schema plugin err = assert_raises(GraphQL::Backtrace::TracedError) { backtrace_schema.execute("query { nilInspect { raiseField(message: \"pop!\") } }") } rendered_table = [ 'Loc | Field | Object | Arguments | Result', '1:22 | Thing.raiseField | | {:message=>"pop!"} | #', '1:9 | Query.nilInspect | nil | {} | {}', '1:1 | query | nil | {} | {nilInspect: {...}}', ].join("\n") assert_includes(err.message, rendered_table) end it "raises original exception instead of a TracedError when error does not occur during resolving" do instrumentation_schema = Class.new(schema) do trace_with(ErrorTrace, required_arg: true) end assert_raises(RuntimeError) { instrumentation_schema.execute(GraphQL::Introspection::INTROSPECTION_QUERY, context: { backtrace: true }) } end end # This will get brittle when execution code moves between files # but I'm not sure how to be sure that the backtrace contains the right stuff! def assert_backtrace_includes(backtrace, file:, method:) includes_tag = backtrace.any? { |s| s.include?(file) && s.include?("`" + method) } assert includes_tag, "Backtrace should include #{file} inside method #{method}\n\n#{backtrace.join("\n")}" end it "works with stand-alone validation" do res = backtrace_schema.validate("{ __typename }") assert_equal [], res end it "works with stand-alone analysis" do example_analyzer = Class.new(GraphQL::Analysis::AST::Analyzer) do def result :finished end end query = GraphQL::Query.new(backtrace_schema, "{ __typename }") result = GraphQL::Analysis::AST.analyze_query(query, [example_analyzer]) assert_equal [:finished], result end it "works with multiplex analysis" do example_analyzer = Class.new(GraphQL::Analysis::AST::Analyzer) do def result :finished end end query = GraphQL::Query.new(backtrace_schema, "{ __typename }") multiplex = GraphQL::Execution::Multiplex.new( schema: schema, queries: [query], context: {}, max_complexity: nil, ) result = GraphQL::Analysis::AST.analyze_multiplex(multiplex, [example_analyzer]) assert_equal [:finished], result end it "works with multiplex queries" do res = backtrace_schema.multiplex([ { query: 'query { __typename }' }, { query: 'query { __typename }' }, ]) expected_res = [ {"data" => { "__typename" => "Query" }}, {"data" => { "__typename" => "Query" }}, ] assert_equal expected_res, res end it "includes other trace modules when backtrace is active" do custom_trace = Module.new schema = Class.new(GraphQL::Schema) do trace_with(custom_trace) end query = GraphQL::Query.new(schema, "{ __typename }", context: { backtrace: true }) assert_includes query.current_trace.class.ancestors, custom_trace end end graphql-ruby-2.2.17/spec/graphql/cop/000077500000000000000000000000001476434635200174075ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/cop/default_null_true_spec.rb000066400000000000000000000013561476434635200244700ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe "GraphQL::Cop::DefaultNullFalse" do include RubocopTestHelpers it "finds and autocorrects `null: true` field configurations" do result = run_rubocop_on("spec/fixtures/cop/null_true.rb") assert_equal 3, rubocop_errors(result) assert_includes result, <<-RUBY field :name, String, null: true ^^^^^^^^^^ RUBY assert_includes result, <<-RUBY null: true, ^^^^^^^^^^ RUBY assert_includes result, <<-RUBY field :described, [String, null: true], null: true, description: "Something" ^^^^^^^^^^ RUBY assert_rubocop_autocorrects_all("spec/fixtures/cop/null_true.rb") end end graphql-ruby-2.2.17/spec/graphql/cop/default_required_true_spec.rb000066400000000000000000000017451476434635200253400ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe "GraphQL::Cop::DefaultRequiredTrue" do include RubocopTestHelpers it "finds and autocorrects `required: true` argument configurations" do result = run_rubocop_on("spec/fixtures/cop/required_true.rb") assert_equal 4, rubocop_errors(result) assert_includes result, <<-RUBY argument :id_1, ID, required: true ^^^^^^^^^^^^^^ RUBY assert_includes result, <<-RUBY required: true, ^^^^^^^^^^^^^^ RUBY assert_includes result, <<-RUBY argument :id_3, ID, other_config: { something: false, required: true }, required: true, description: \"Something\" ^^^^^^^^^^^^^^ RUBY assert_includes result, <<-RUBY f.argument(:id_1, ID, required: true) ^^^^^^^^^^^^^^ RUBY assert_rubocop_autocorrects_all("spec/fixtures/cop/required_true.rb") end end graphql-ruby-2.2.17/spec/graphql/dataloader/000077500000000000000000000000001476434635200207265ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/dataloader/async_dataloader_spec.rb000066400000000000000000000211721476434635200255650ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" if RUBY_VERSION >= "3.1.1" require "async" describe GraphQL::Dataloader::AsyncDataloader do class AsyncSchema < GraphQL::Schema class SleepSource < GraphQL::Dataloader::Source def initialize(tag = nil) @tag = tag end def fetch(keys) max_sleep = keys.max # t1 = Time.now # puts "----- SleepSource => #{max_sleep} (from: #{keys})" sleep(max_sleep) # puts "----- SleepSource done #{max_sleep} after #{Time.now - t1}" keys.map { |_k| max_sleep } end end class WaitForSource < GraphQL::Dataloader::Source def initialize(tag) @tag = tag end def fetch(waits) max_wait = waits.max # puts "[#{Time.now.to_f}] Waiting #{max_wait} for #{@tag}" `sleep #{max_wait}` # puts "[#{Time.now.to_f}] Finished for #{@tag}" waits.map { |_w| @tag } end end class KeyWaitForSource < GraphQL::Dataloader::Source class << self attr_accessor :fetches def reset @fetches = [] end end def initialize(wait) @wait = wait end def fetch(keys) self.class.fetches << keys sleep(@wait) keys end end class Sleeper < GraphQL::Schema::Object field :sleeper, Sleeper, null: false, resolver_method: :sleep do argument :duration, Float end def sleep(duration:) context[:key_i] ||= 0 new_key = context[:key_i] += 1 dataloader.with(SleepSource, new_key).load(duration) duration end field :duration, Float, null: false def duration; object; end end class Waiter < GraphQL::Schema::Object field :wait_for, Waiter, null: false do argument :tag, String argument :wait, Float end def wait_for(tag:, wait:) dataloader.with(WaitForSource, tag).load(wait) end field :tag, String, null: false def tag object end end class Query < GraphQL::Schema::Object field :sleep, Float, null: false do argument :duration, Float end field :sleeper, Sleeper, null: false, resolver_method: :sleep do argument :duration, Float end def sleep(duration:) context[:key_i] ||= 0 new_key = context[:key_i] += 1 dataloader.with(SleepSource, new_key).load(duration) duration end field :wait_for, Waiter, null: false do argument :tag, String argument :wait, Float end def wait_for(tag:, wait:) dataloader.with(WaitForSource, tag).load(wait) end class ListWaiter < GraphQL::Schema::Object field :waiter, Waiter def waiter dataloader.with(KeyWaitForSource, object[:wait]).load(object[:tag]) end end field :list_waiters, [ListWaiter] do argument :wait, Float argument :tags, [String] end def list_waiters(wait:, tags:) Kernel.sleep(0.1) tags.map { |t| { tag: t, wait: wait }} end end query(Query) use GraphQL::Dataloader::AsyncDataloader end module AsyncDataloaderAssertions def self.included(child_class) child_class.class_eval do before do AsyncSchema::KeyWaitForSource.reset end it "works with sources" do dataloader = GraphQL::Dataloader::AsyncDataloader.new r1 = dataloader.with(AsyncSchema::SleepSource, :s1).request(0.1) r2 = dataloader.with(AsyncSchema::SleepSource, :s2).request(0.2) r3 = dataloader.with(AsyncSchema::SleepSource, :s3).request(0.3) v1 = nil dataloader.append_job { v1 = r1.load } started_at = Time.now dataloader.run ended_at = Time.now assert_equal 0.1, v1 started_at_2 = Time.now # These should take no time at all since they're already resolved v2 = r2.load v3 = r3.load ended_at_2 = Time.now assert_equal 0.2, v2 assert_equal 0.3, v3 assert_in_delta 0.0, started_at_2 - ended_at_2, 0.05, "Already-loaded values returned instantly" assert_in_delta 0.3, ended_at - started_at, 0.05, "IO ran in parallel" end it "works with GraphQL" do started_at = Time.now res = AsyncSchema.execute("{ s1: sleep(duration: 0.1) s2: sleep(duration: 0.2) s3: sleep(duration: 0.3) }") ended_at = Time.now assert_equal({"s1"=>0.1, "s2"=>0.2, "s3"=>0.3}, res["data"]) assert_in_delta 0.3, ended_at - started_at, 0.05, "IO ran in parallel" end it "runs fields by depth" do query_str = <<-GRAPHQL { s1: sleeper(duration: 0.1) { sleeper(duration: 0.1) { sleeper(duration: 0.1) { duration } } } s2: sleeper(duration: 0.2) { sleeper(duration: 0.1) { duration } } s3: sleeper(duration: 0.3) { duration } } GRAPHQL started_at = Time.now res = AsyncSchema.execute(query_str) ended_at = Time.now expected_data = { "s1" => { "sleeper" => { "sleeper" => { "duration" => 0.1 } } }, "s2" => { "sleeper" => { "duration" => 0.1 } }, "s3" => { "duration" => 0.3 } } assert_equal expected_data, res["data"] assert_in_delta 0.5, ended_at - started_at, 0.06, "Each depth ran in parallel" end it "runs dataloaders in parallel across branches" do query_str = <<-GRAPHQL { w1: waitFor(tag: "a", wait: 0.2) { waitFor(tag: "b", wait: 0.2) { waitFor(tag: "c", wait: 0.2) { tag } } } # After the first, these are returned eagerly from cache w2: waitFor(tag: "a", wait: 0.2) { waitFor(tag: "a", wait: 0.2) { waitFor(tag: "a", wait: 0.2) { tag } } } w3: waitFor(tag: "a", wait: 0.2) { waitFor(tag: "b", wait: 0.2) { waitFor(tag: "d", wait: 0.2) { tag } } } w4: waitFor(tag: "e", wait: 0.6) { tag } } GRAPHQL started_at = Time.now res = AsyncSchema.execute(query_str) ended_at = Time.now expected_data = { "w1" => { "waitFor" => { "waitFor" => { "tag" => "c" } } }, "w2" => { "waitFor" => { "waitFor" => { "tag" => "a" } } }, "w3" => { "waitFor" => { "waitFor" => { "tag" => "d" } } }, "w4" => { "tag" => "e" } } assert_equal expected_data, res["data"] # We've basically got two options here: # - Put all jobs in the same queue (fields and sources), but then you don't get predictable batching. # - Work one-layer-at-a-time, but then layers can get stuck behind one another. That's what's implemented here. assert_in_delta 1.0, ended_at - started_at, 0.06, "Sources were executed in parallel" end it "groups across list items" do query_str = <<-GRAPHQL { listWaiters(wait: 0.2, tags: ["a", "b", "c"]) { waiter { tag } } } GRAPHQL t1 = Time.now result = AsyncSchema.execute(query_str) t2 = Time.now assert_equal ["a", "b", "c"], result["data"]["listWaiters"].map { |lw| lw["waiter"]["tag"]} # The field itself waits 0.1 assert_in_delta 0.3, t2 - t1, 0.06, "Wait was parallel" assert_equal [["a", "b", "c"]], AsyncSchema::KeyWaitForSource.fetches, "All keys were fetched at once" end end end end describe "with async" do include AsyncDataloaderAssertions end end end graphql-ruby-2.2.17/spec/graphql/dataloader/nonblocking_dataloader_spec.rb000066400000000000000000000203541476434635200267540ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" if Fiber.respond_to?(:scheduler) # Ruby 3+ describe GraphQL::Dataloader::NonblockingDataloader do class NonblockingSchema < GraphQL::Schema class SleepSource < GraphQL::Dataloader::Source def fetch(keys) max_sleep = keys.max # t1 = Time.now # puts "----- SleepSource => #{max_sleep} " sleep(max_sleep) # puts "----- SleepSource done #{max_sleep} after #{Time.now - t1}" keys.map { |_k| max_sleep } end end class WaitForSource < GraphQL::Dataloader::Source def initialize(tag) @tag = tag end def fetch(waits) max_wait = waits.max # puts "[#{Time.now.to_f}] Waiting #{max_wait} for #{@tag}" `sleep #{max_wait}` # puts "[#{Time.now.to_f}] Finished for #{@tag}" waits.map { |_w| @tag } end end class Sleeper < GraphQL::Schema::Object field :sleeper, Sleeper, null: false, resolver_method: :sleep do argument :duration, Float end def sleep(duration:) `sleep #{duration}` duration end field :duration, Float, null: false def duration; object; end end class Waiter < GraphQL::Schema::Object field :wait_for, Waiter, null: false do argument :tag, String argument :wait, Float end def wait_for(tag:, wait:) dataloader.with(WaitForSource, tag).load(wait) end field :tag, String, null: false def tag object end end class Query < GraphQL::Schema::Object field :sleep, Float, null: false do argument :duration, Float end field :sleeper, Sleeper, null: false, resolver_method: :sleep do argument :duration, Float end def sleep(duration:) `sleep #{duration}` duration end field :wait_for, Waiter, null: false do argument :tag, String argument :wait, Float end def wait_for(tag:, wait:) dataloader.with(WaitForSource, tag).load(wait) end end query(Query) use GraphQL::Dataloader::NonblockingDataloader end def with_scheduler Fiber.set_scheduler(scheduler_class.new) yield ensure Fiber.set_scheduler(nil) end module NonblockingDataloaderAssertions def self.included(child_class) child_class.class_eval do it "runs IO in parallel by default" do dataloader = GraphQL::Dataloader::NonblockingDataloader.new results = {} dataloader.append_job { sleep(0.1); results[:a] = 1 } dataloader.append_job { sleep(0.2); results[:b] = 2 } dataloader.append_job { sleep(0.3); results[:c] = 3 } assert_equal({}, results, "Nothing ran yet") started_at = Time.now with_scheduler { dataloader.run } ended_at = Time.now assert_equal({ a: 1, b: 2, c: 3 }, results, "All the jobs ran") assert_in_delta 0.3, ended_at - started_at, 0.05, "IO ran in parallel" end it "works with sources" do dataloader = GraphQL::Dataloader::NonblockingDataloader.new r1 = dataloader.with(NonblockingSchema::SleepSource).request(0.1) r2 = dataloader.with(NonblockingSchema::SleepSource).request(0.2) r3 = dataloader.with(NonblockingSchema::SleepSource).request(0.3) v1 = nil dataloader.append_job { v1 = r1.load } started_at = Time.now with_scheduler { dataloader.run } ended_at = Time.now assert_equal 0.3, v1 started_at_2 = Time.now # These should take no time at all since they're already resolved v2 = r2.load v3 = r3.load ended_at_2 = Time.now assert_equal 0.3, v2 assert_equal 0.3, v3 assert_in_delta 0.0, started_at_2 - ended_at_2, 0.05, "Already-loaded values returned instantly" assert_in_delta 0.3, ended_at - started_at, 0.05, "IO ran in parallel" end it "works with GraphQL" do started_at = Time.now res = with_scheduler { NonblockingSchema.execute("{ s1: sleep(duration: 0.1) s2: sleep(duration: 0.2) s3: sleep(duration: 0.3) }") } ended_at = Time.now assert_equal({"s1"=>0.1, "s2"=>0.2, "s3"=>0.3}, res["data"]) assert_in_delta 0.3, ended_at - started_at, 0.05, "IO ran in parallel" end it "nested fields don't wait for slower higher-level fields" do query_str = <<-GRAPHQL { s1: sleeper(duration: 0.1) { sleeper(duration: 0.1) { sleeper(duration: 0.1) { duration } } } s2: sleeper(duration: 0.2) { sleeper(duration: 0.1) { duration } } s3: sleeper(duration: 0.3) { duration } } GRAPHQL started_at = Time.now res = with_scheduler { NonblockingSchema.execute(query_str) } ended_at = Time.now expected_data = { "s1" => { "sleeper" => { "sleeper" => { "duration" => 0.1 } } }, "s2" => { "sleeper" => { "duration" => 0.1 } }, "s3" => { "duration" => 0.3 } } assert_equal expected_data, res["data"] assert_in_delta 0.3, ended_at - started_at, 0.05, "Fields ran without any waiting" end it "runs dataloaders in parallel across branches" do query_str = <<-GRAPHQL { w1: waitFor(tag: "a", wait: 0.2) { waitFor(tag: "b", wait: 0.2) { waitFor(tag: "c", wait: 0.2) { tag } } } # After the first, these are returned eagerly from cache w2: waitFor(tag: "a", wait: 0.2) { waitFor(tag: "a", wait: 0.2) { waitFor(tag: "a", wait: 0.2) { tag } } } w3: waitFor(tag: "a", wait: 0.2) { waitFor(tag: "b", wait: 0.2) { waitFor(tag: "d", wait: 0.2) { tag } } } w4: waitFor(tag: "e", wait: 0.6) { tag } } GRAPHQL started_at = Time.now res = with_scheduler do NonblockingSchema.execute(query_str) end ended_at = Time.now expected_data = { "w1" => { "waitFor" => { "waitFor" => { "tag" => "c" } } }, "w2" => { "waitFor" => { "waitFor" => { "tag" => "a" } } }, "w3" => { "waitFor" => { "waitFor" => { "tag" => "d" } } }, "w4" => { "tag" => "e" } } assert_equal expected_data, res["data"] # We've basically got two options here: # - Put all jobs in the same queue (fields and sources), but then you don't get predictable batching. # - Work one-layer-at-a-time, but then layers can get stuck behind one another. That's what's implemented here. assert_in_delta 1.0, ended_at - started_at, 0.5, "Sources were executed in parallel" end end end end describe "With the toy scheduler from Ruby's tests" do let(:scheduler_class) { ::DummyScheduler } include NonblockingDataloaderAssertions end if RUBY_ENGINE == "ruby" && !ENV["GITHUB_ACTIONS"] describe "With libev_scheduler" do require "libev_scheduler" let(:scheduler_class) { Libev::Scheduler } include NonblockingDataloaderAssertions end end describe "with evt" do require "evt" let(:scheduler_class) { Evt::Scheduler } include NonblockingDataloaderAssertions end end end graphql-ruby-2.2.17/spec/graphql/dataloader/source_spec.rb000066400000000000000000000047571476434635200236020ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Dataloader::Source do class FailsToLoadSource < GraphQL::Dataloader::Source def fetch(keys) dataloader.with(FailsToLoadSource).load_all(keys) end end it "raises an error when it tries too many times to sync" do dl = GraphQL::Dataloader.new dl.append_job { dl.with(FailsToLoadSource).load(1) } err = assert_raises RuntimeError do dl.run end expected_message = "FailsToLoadSource#sync tried 1000 times to load pending keys ([1]), but they still weren't loaded. There is likely a circular dependency." assert_equal expected_message, err.message end it "is pending when waiting for false and nil" do dl = GraphQL::Dataloader.new dl.with(FailsToLoadSource).request(nil) source_cache = dl.instance_variable_get(:@source_cache) assert source_cache[FailsToLoadSource][[{}]].pending? end class CustomKeySource < GraphQL::Dataloader::Source def result_key_for(record) record[:id] end def fetch(records) records.map { |r| r[:value] * 10 } end end it "uses a custom key when configured" do values = nil GraphQL::Dataloader.with_dataloading do |dl| first_req = dl.with(CustomKeySource).request({ id: 1, value: 10 }) second_rec = dl.with(CustomKeySource).request({ id: 2, value: 20 }) third_rec = dl.with(CustomKeySource).request({id: 1, value: 30 }) values = [ first_req.load, second_rec.load, third_rec.load ] end # There wasn't a `300` because the third requested value was de-duped to the first one. assert_equal [100, 200, 100], values end class NoDataloaderSchema < GraphQL::Schema class ThingSource < GraphQL::Dataloader::Source def fetch(ids) ids.map { |id| { name: "Thing-#{id}" } } end end class Thing < GraphQL::Schema::Object field :name, String end class Query < GraphQL::Schema::Object field :thing, Thing do argument :id, ID end def thing(id:) context.dataloader.with(ThingSource).load(id) end end query(Query) end it "raises an error when used without a dataloader" do err = assert_raises GraphQL::Error do NoDataloaderSchema.execute("{ thing(id: 1) { name } }") end expected_message = "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources." assert_equal expected_message, err.message end end graphql-ruby-2.2.17/spec/graphql/dataloader_spec.rb000066400000000000000000001242011476434635200222650ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require "fiber" if defined?(Console) && defined?(Async) Console.logger.disable(Async::Task) end describe GraphQL::Dataloader do class BatchedCallsCounter def initialize @count = 0 end def increment @count += 1 end attr_reader :count end class FiberSchema < GraphQL::Schema module Database extend self DATA = {} [ { id: "1", name: "Wheat", type: "Grain" }, { id: "2", name: "Corn", type: "Grain" }, { id: "3", name: "Butter", type: "Dairy" }, { id: "4", name: "Baking Soda", type: "LeaveningAgent" }, { id: "5", name: "Cornbread", type: "Recipe", ingredient_ids: ["1", "2", "3", "4"] }, { id: "6", name: "Grits", type: "Recipe", ingredient_ids: ["2", "3", "7"] }, { id: "7", name: "Cheese", type: "Dairy" }, ].each { |d| DATA[d[:id]] = d } def log @log ||= [] end def mget(ids) log << [:mget, ids.sort] ids.map { |id| DATA[id] } end def find_by(attribute, values) log << [:find_by, attribute, values.sort] values.map { |v| DATA.each_value.find { |dv| dv[attribute] == v } } end end class DataObject < GraphQL::Dataloader::Source def initialize(column = :id) @column = column end def fetch(keys) if @column == :id Database.mget(keys) else Database.find_by(@column, keys) end end end class ToString < GraphQL::Dataloader::Source def fetch(keys) keys.map(&:to_s) end end class NestedDataObject < GraphQL::Dataloader::Source def fetch(ids) @dataloader.with(DataObject).load_all(ids) end end class SlowDataObject < GraphQL::Dataloader::Source def initialize(batch_key) # This is just so that I can force different instances in test @batch_key = batch_key end def fetch(keys) t = Thread.new { sleep 0.5 Database.mget(keys) } dataloader.yield t.value end end class CustomBatchKeySource < GraphQL::Dataloader::Source def initialize(batch_key) @batch_key = batch_key end def self.batch_key_for(batch_key) Database.log << [:batch_key_for, batch_key] # Ignore it altogether :all_the_same end def fetch(keys) Database.mget(keys) end end class KeywordArgumentSource < GraphQL::Dataloader::Source def initialize(column:) @column = column end def fetch(keys) if @column == :id Database.mget(keys) else Database.find_by(@column, keys) end end end class AuthorizedSource < GraphQL::Dataloader::Source def initialize(counter) @counter = counter end def fetch(recipes) @counter && @counter.increment recipes.map { true } end end class ErrorSource < GraphQL::Dataloader::Source def fetch(ids) raise GraphQL::Error, "Source error on: #{ids.inspect}" end end module Ingredient include GraphQL::Schema::Interface field :name, String, null: false field :id, ID, null: false field :name_by_scoped_context, String def name_by_scoped_context context[:ingredient_name] end end class Grain < GraphQL::Schema::Object implements Ingredient end class LeaveningAgent < GraphQL::Schema::Object implements Ingredient end class Dairy < GraphQL::Schema::Object implements Ingredient end class Recipe < GraphQL::Schema::Object def self.authorized?(obj, ctx) ctx.dataloader.with(AuthorizedSource, ctx[:batched_calls_counter]).load(obj) end field :name, String, null: false field :ingredients, [Ingredient], null: false def ingredients ingredients = dataloader.with(DataObject).load_all(object[:ingredient_ids]) ingredients end field :slow_ingredients, [Ingredient], null: false def slow_ingredients # Use `object[:id]` here to force two different instances of the loader in the test dataloader.with(SlowDataObject, object[:id]).load_all(object[:ingredient_ids]) end end class Query < GraphQL::Schema::Object field :recipes, [Recipe], null: false def recipes Database.mget(["5", "6"]) end field :ingredient, Ingredient do argument :id, ID end def ingredient(id:) dataloader.with(DataObject).load(id) end field :ingredient_by_name, Ingredient do argument :name, String end def ingredient_by_name(name:) ing = dataloader.with(DataObject, :name).load(name) context.scoped_set!(:ingredient_name, "Scoped:#{name}") ing end field :nested_ingredient, Ingredient do argument :id, ID end def nested_ingredient(id:) dataloader.with(NestedDataObject).load(id) end field :slow_recipe, Recipe do argument :id, ID end def slow_recipe(id:) dataloader.with(SlowDataObject, id).load(id) end field :recipe, Recipe do argument :id, ID, loads: Recipe, as: :recipe end def recipe(recipe:) recipe end field :key_ingredient, Ingredient do argument :id, ID end def key_ingredient(id:) dataloader.with(KeywordArgumentSource, column: :id).load(id) end class RecipeIngredientInput < GraphQL::Schema::InputObject argument :id, ID argument :ingredient_number, Int end field :recipe_ingredient, Ingredient do argument :recipe, RecipeIngredientInput end def recipe_ingredient(recipe:) recipe_object = dataloader.with(DataObject).load(recipe[:id]) ingredient_idx = recipe[:ingredient_number] - 1 ingredient_id = recipe_object[:ingredient_ids][ingredient_idx] dataloader.with(DataObject).load(ingredient_id) end field :common_ingredients, [Ingredient] do argument :recipe_1_id, ID argument :recipe_2_id, ID end def common_ingredients(recipe_1_id:, recipe_2_id:) req1 = dataloader.with(DataObject).request(recipe_1_id) req2 = dataloader.with(DataObject).request(recipe_2_id) recipe1 = req1.load recipe2 = req2.load common_ids = recipe1[:ingredient_ids] & recipe2[:ingredient_ids] dataloader.with(DataObject).load_all(common_ids) end field :common_ingredients_with_load, [Ingredient], null: false do argument :recipe_1_id, ID, loads: Recipe argument :recipe_2_id, ID, loads: Recipe end def common_ingredients_with_load(recipe_1:, recipe_2:) common_ids = recipe_1[:ingredient_ids] & recipe_2[:ingredient_ids] dataloader.with(DataObject).load_all(common_ids) end field :common_ingredients_from_input_object, [Ingredient], null: false do class CommonIngredientsInput < GraphQL::Schema::InputObject argument :recipe_1_id, ID, loads: Recipe argument :recipe_2_id, ID, loads: Recipe end argument :input, CommonIngredientsInput end def common_ingredients_from_input_object(input:) recipe_1 = input[:recipe_1] recipe_2 = input[:recipe_2] common_ids = recipe_1[:ingredient_ids] & recipe_2[:ingredient_ids] dataloader.with(DataObject).load_all(common_ids) end field :ingredient_with_custom_batch_key, Ingredient do argument :id, ID argument :batch_key, String end def ingredient_with_custom_batch_key(id:, batch_key:) dataloader.with(CustomBatchKeySource, batch_key).load(id) end field :recursive_ingredient_name, String do argument :id, ID end def recursive_ingredient_name(id:) res = context.schema.execute("{ ingredient(id: #{id}) { name } }") res["data"]["ingredient"]["name"] end field :test_error, String do argument :source, Boolean, required: false, default_value: false end def test_error(source:) if source dataloader.with(ErrorSource).load(1) else raise GraphQL::Error, "Field error" end end class LookaheadInput < GraphQL::Schema::InputObject argument :id, ID argument :batch_key, String end field :lookahead_ingredient, Ingredient, extras: [:lookahead] do argument :input, LookaheadInput end def lookahead_ingredient(input:, lookahead:) lookahead.arguments # forces a datalaoder.run_isolated call dataloader.with(CustomBatchKeySource, input[:batch_key]).load(input[:id]) end end query(Query) class Mutation1 < GraphQL::Schema::Mutation argument :argument_1, String, prepare: ->(val, ctx) { raise FieldTestError } def resolve(argument_1:) argument_1 end end class Mutation2 < GraphQL::Schema::Mutation argument :argument_2, String, prepare: ->(val, ctx) { raise FieldTestError } def resolve(argument_2:) argument_2 end end class Mutation3 < GraphQL::Schema::Mutation argument :label, String type String def resolve(label:) log = context[:mutation_log] ||= [] log << "begin #{label}" dataloader.with(DataObject).load(1) log << "end #{label}" label end end class GetCache < GraphQL::Schema::Mutation type String def resolve dataloader.with(ToString).load(1) end end class Mutation < GraphQL::Schema::Object field :mutation_1, mutation: Mutation1 field :mutation_2, mutation: Mutation2 field :mutation_3, mutation: Mutation3 field :set_cache, String do argument :input, String end def set_cache(input:) dataloader.with(ToString).merge({ 1 => input }) input end field :get_cache, mutation: GetCache end mutation(Mutation) def self.object_from_id(id, ctx) ctx.dataloader.with(DataObject).load(id) end def self.resolve_type(type, obj, ctx) get_type(obj[:type]) end orphan_types(Grain, Dairy, Recipe, LeaveningAgent) use GraphQL::Dataloader class FieldTestError < StandardError; end rescue_from(FieldTestError) do |err, obj, args, ctx, field| errs = ctx[:errors] ||= [] errs << "FieldTestError @ #{ctx[:current_path]}, #{field.path} / #{ctx[:current_field].path}" nil end end class UsageAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query) @query = query @fields = Set.new end def on_enter_field(node, parent, visitor) args = @query.arguments_for(node, visitor.field_definition) # This bug has been around for a while, # see https://github.com/rmosolgo/graphql-ruby/issues/3321 if args.is_a?(GraphQL::Execution::Lazy) args = args.value end @fields << [node.name, args.keys] end def result @fields end end def database_log FiberSchema::Database.log end before do database_log.clear end ALL_FIBERS = [] class PartsSchema < GraphQL::Schema class FieldSource < GraphQL::Dataloader::Source DATA = [ {"id" => 1, "name" => "a"}, {"id" => 2, "name" => "b"}, {"id" => 3, "name" => "c"}, {"id" => 4, "name" => "d"}, ] def fetch(fields) @previously_fetched ||= Set.new fields.each do |f| if !@previously_fetched.add?(f) raise "Duplicate fetch for #{f.inspect}" end end Array.new(fields.size, DATA) end end class StringFilter < GraphQL::Schema::InputObject argument :equal_to_any_of, [String] end class ComponentFilter < GraphQL::Schema::InputObject argument :name, StringFilter end class FetchObjects < GraphQL::Schema::Resolver argument :filter, ComponentFilter, required: false def resolve(**_kwargs) context.dataloader.with(FieldSource).load("#{field.path}/#{object&.fetch("id")}") end end class Component < GraphQL::Schema::Object field :name, String end class Part < GraphQL::Schema::Object field :components, [Component], resolver: FetchObjects end class Manufacturer < GraphQL::Schema::Object field :parts, [Part], resolver: FetchObjects end class Query < GraphQL::Schema::Object field :manufacturers, [Manufacturer], resolver: FetchObjects end query(Query) use GraphQL::Dataloader end module DataloaderAssertions def self.included(child_class) child_class.class_eval do let(:schema) { make_schema_from(FiberSchema) } let(:parts_schema) { make_schema_from(PartsSchema) } it "Works with request(...)" do res = schema.execute <<-GRAPHQL { commonIngredients(recipe1Id: 5, recipe2Id: 6) { name } } GRAPHQL expected_data = { "data" => { "commonIngredients" => [ { "name" => "Corn" }, { "name" => "Butter" }, ] } } assert_equal expected_data, res assert_equal [[:mget, ["5", "6"]], [:mget, ["2", "3"]]], database_log end it "runs mutations sequentially" do res = schema.execute <<-GRAPHQL mutation { first: mutation3(label: "first") second: mutation3(label: "second") } GRAPHQL assert_equal({ "first" => "first", "second" => "second" }, res["data"]) assert_equal ["begin first", "end first", "begin second", "end second"], res.context[:mutation_log] end it "clears the cache between mutations" do res = schema.execute <<-GRAPHQL mutation { setCache(input: "Salad") getCache } GRAPHQL assert_equal({"setCache" => "Salad", "getCache" => "1"}, res["data"]) end it "batch-loads" do res = schema.execute <<-GRAPHQL { i1: ingredient(id: 1) { id name } i2: ingredient(id: 2) { name } r1: recipe(id: 5) { # This loads Ingredients 3 and 4 ingredients { name } } # This loads Ingredient 7 ri1: recipeIngredient(recipe: { id: 6, ingredientNumber: 3 }) { name } } GRAPHQL expected_data = { "i1" => { "id" => "1", "name" => "Wheat" }, "i2" => { "name" => "Corn" }, "r1" => { "ingredients" => [ { "name" => "Wheat" }, { "name" => "Corn" }, { "name" => "Butter" }, { "name" => "Baking Soda" }, ], }, "ri1" => { "name" => "Cheese", }, } assert_equal(expected_data, res["data"]) expected_log = [ [:mget, [ "1", "2", # The first 2 ingredients "5", # The first recipe "6", # recipeIngredient recipeId ]], [:mget, [ "7", # recipeIngredient ingredient_id ]], [:mget, [ "3", "4", # The two unfetched ingredients the first recipe ]], ] assert_equal expected_log, database_log end it "caches and batch-loads across a multiplex" do context = {} result = schema.multiplex([ { query: "{ i1: ingredient(id: 1) { name } i2: ingredient(id: 2) { name } }", }, { query: "{ i2: ingredient(id: 2) { name } r1: recipe(id: 5) { ingredients { name } } }", }, { query: "{ i1: ingredient(id: 1) { name } ri1: recipeIngredient(recipe: { id: 5, ingredientNumber: 2 }) { name } }", }, ], context: context) expected_result = [ {"data"=>{"i1"=>{"name"=>"Wheat"}, "i2"=>{"name"=>"Corn"}}}, {"data"=>{"i2"=>{"name"=>"Corn"}, "r1"=>{"ingredients"=>[{"name"=>"Wheat"}, {"name"=>"Corn"}, {"name"=>"Butter"}, {"name"=>"Baking Soda"}]}}}, {"data"=>{"i1"=>{"name"=>"Wheat"}, "ri1"=>{"name"=>"Corn"}}}, ] assert_equal expected_result, result expected_log = [ [:mget, ["1", "2", "5"]], [:mget, ["3", "4"]], ] assert_equal expected_log, database_log end it "works with calls within sources" do res = schema.execute <<-GRAPHQL { i1: nestedIngredient(id: 1) { name } i2: nestedIngredient(id: 2) { name } } GRAPHQL expected_data = { "i1" => { "name" => "Wheat" }, "i2" => { "name" => "Corn" } } assert_equal expected_data, res["data"] assert_equal [[:mget, ["1", "2"]]], database_log end it "works with batch parameters" do res = schema.execute <<-GRAPHQL { i1: ingredientByName(name: "Butter") { id } i2: ingredientByName(name: "Corn") { id } i3: ingredientByName(name: "Gummi Bears") { id } } GRAPHQL expected_data = { "i1" => { "id" => "3" }, "i2" => { "id" => "2" }, "i3" => nil, } assert_equal expected_data, res["data"] assert_equal [[:find_by, :name, ["Butter", "Corn", "Gummi Bears"]]], database_log end it "works with manual parallelism" do start = Time.now.to_f schema.execute <<-GRAPHQL { i1: slowRecipe(id: 5) { slowIngredients { name } } i2: slowRecipe(id: 6) { slowIngredients { name } } } GRAPHQL finish = Time.now.to_f # For some reason Async adds some overhead to this manual parallelism. # But who cares, you wouldn't use Thread#join in that case delta = schema.dataloader_class == GraphQL::Dataloader ? 0.1 : 0.5 # Each load slept for 0.5 second, so sequentially, this would have been 2s sequentially assert_in_delta 1, finish - start, delta, "Load threads are executed in parallel" expected_log = [ # These were separated because of different recipe IDs: [:mget, ["5"]], [:mget, ["6"]], # These were cached separately because of different recipe IDs: [:mget, ["2", "3", "7"]], [:mget, ["1", "2", "3", "4"]], ] # Sort them because threads may have returned in slightly different order assert_equal expected_log.sort, database_log.sort end it "Works with multiple-field selections and __typename" do query_str = <<-GRAPHQL { ingredient(id: 1) { __typename name } } GRAPHQL res = schema.execute(query_str) expected_data = { "ingredient" => { "__typename" => "Grain", "name" => "Wheat", } } assert_equal expected_data, res["data"] end it "Works when the parent field didn't yield" do query_str = <<-GRAPHQL { recipes { ingredients { name } } } GRAPHQL res = schema.execute(query_str) expected_data = { "recipes" =>[ { "ingredients" => [ {"name"=>"Wheat"}, {"name"=>"Corn"}, {"name"=>"Butter"}, {"name"=>"Baking Soda"} ]}, { "ingredients" => [ {"name"=>"Corn"}, {"name"=>"Butter"}, {"name"=>"Cheese"} ]}, ] } assert_equal expected_data, res["data"] expected_log = [ [:mget, ["5", "6"]], [:mget, ["1", "2", "3", "4", "7"]], ] assert_equal expected_log, database_log end it "loads arguments in batches, even with request" do query_str = <<-GRAPHQL { commonIngredientsWithLoad(recipe1Id: 5, recipe2Id: 6) { name } } GRAPHQL res = schema.execute(query_str) expected_data = { "commonIngredientsWithLoad" => [ {"name"=>"Corn"}, {"name"=>"Butter"}, ] } assert_equal expected_data, res["data"] expected_log = [ [:mget, ["5", "6"]], [:mget, ["2", "3"]], ] assert_equal expected_log, database_log end it "works with sources that use keyword arguments in the initializer" do query_str = <<-GRAPHQL { keyIngredient(id: 1) { __typename name } } GRAPHQL res = schema.execute(query_str) expected_data = { "keyIngredient" => { "__typename" => "Grain", "name" => "Wheat", } } assert_equal expected_data, res["data"] end it "Works with analyzing arguments with `loads:`, even with .request" do query_str = <<-GRAPHQL { commonIngredientsWithLoad(recipe1Id: 5, recipe2Id: 6) { name } } GRAPHQL query = GraphQL::Query.new(schema, query_str) results = GraphQL::Analysis::AST.analyze_query(query, [UsageAnalyzer]) expected_results = [ ["commonIngredientsWithLoad", [:recipe_1, :recipe_2]], ["name", []], ] normalized_results = results.first.to_a normalized_results.each do |key, values| values.sort! end assert_equal expected_results, results.first.to_a end it "Works with input objects, load and request" do query_str = <<-GRAPHQL { commonIngredientsFromInputObject(input: { recipe1Id: 5, recipe2Id: 6 }) { name } } GRAPHQL res = schema.execute(query_str) expected_data = { "commonIngredientsFromInputObject" => [ {"name"=>"Corn"}, {"name"=>"Butter"}, ] } assert_equal expected_data, res["data"] expected_log = [ [:mget, ["5", "6"]], [:mget, ["2", "3"]], ] assert_equal expected_log, database_log end it "batches calls in .authorized?" do query_str = "{ r1: recipe(id: 5) { name } r2: recipe(id: 6) { name } }" context = { batched_calls_counter: BatchedCallsCounter.new } schema.execute(query_str, context: context) assert_equal 1, context[:batched_calls_counter].count query_str = "{ recipes { name } }" context = { batched_calls_counter: BatchedCallsCounter.new } schema.execute(query_str, context: context) assert_equal 1, context[:batched_calls_counter].count end it "Works with input objects using variables, load and request" do query_str = <<-GRAPHQL query($input: CommonIngredientsInput!) { commonIngredientsFromInputObject(input: $input) { name } } GRAPHQL res = schema.execute(query_str, variables: { input: { recipe1Id: 5, recipe2Id: 6 }}) expected_data = { "commonIngredientsFromInputObject" => [ {"name"=>"Corn"}, {"name"=>"Butter"}, ] } assert_equal expected_data, res["data"] expected_log = [ [:mget, ["5", "6"]], [:mget, ["2", "3"]], ] assert_equal expected_log, database_log end it "supports general usage" do a = b = c = nil res = GraphQL::Dataloader.with_dataloading { |dataloader| dataloader.append_job { a = dataloader.with(FiberSchema::DataObject).load("1") } dataloader.append_job { b = dataloader.with(FiberSchema::DataObject).load("1") } dataloader.append_job { r1 = dataloader.with(FiberSchema::DataObject).request("2") r2 = dataloader.with(FiberSchema::DataObject).request("3") c = [ r1.load, r2.load ] } :finished } assert_equal :finished, res assert_equal [[:mget, ["1", "2", "3"]]], database_log assert_equal "Wheat", a[:name] assert_equal "Wheat", b[:name] assert_equal ["Corn", "Butter"], c.map { |d| d[:name] } end it "works with scoped context" do query_str = <<-GRAPHQL { i1: ingredientByName(name: "Corn") { nameByScopedContext } i2: ingredientByName(name: "Wheat") { nameByScopedContext } i3: ingredientByName(name: "Butter") { nameByScopedContext } } GRAPHQL expected_data = { "i1" => { "nameByScopedContext" => "Scoped:Corn" }, "i2" => { "nameByScopedContext" => "Scoped:Wheat" }, "i3" => { "nameByScopedContext" => "Scoped:Butter" }, } result = schema.execute(query_str) assert_equal expected_data, result["data"] end it "works when the schema calls itself" do result = schema.execute("{ recursiveIngredientName(id: 1) }") assert_equal "Wheat", result["data"]["recursiveIngredientName"] end it "uses .batch_key_for in source classes" do query_str = <<-GRAPHQL { i1: ingredientWithCustomBatchKey(id: 1, batchKey: "abc") { name } i2: ingredientWithCustomBatchKey(id: 2, batchKey: "def") { name } i3: ingredientWithCustomBatchKey(id: 3, batchKey: "ghi") { name } } GRAPHQL res = schema.execute(query_str) expected_data = { "i1" => { "name" => "Wheat" }, "i2" => { "name" => "Corn" }, "i3" => { "name" => "Butter" } } assert_equal expected_data, res["data"] expected_log = [ # Each batch key is given to the source class: [:batch_key_for, "abc"], [:batch_key_for, "def"], [:batch_key_for, "ghi"], # But since they return the same value, # all keys are fetched in the same call: [:mget, ["1", "2", "3"]] ] assert_equal expected_log, database_log end it "uses cached values from .merge" do query_str = "{ ingredient(id: 1) { id name } }" assert_equal "Wheat", schema.execute(query_str)["data"]["ingredient"]["name"] assert_equal [[:mget, ["1"]]], database_log database_log.clear dataloader = schema.dataloader_class.new data_source = dataloader.with(FiberSchema::DataObject) data_source.merge({ "1" => { name: "Kamut", id: "1", type: "Grain" } }) assert_equal "Kamut", data_source.load("1")[:name] res = schema.execute(query_str, context: { dataloader: dataloader }) assert_equal [], database_log assert_equal "Kamut", res["data"]["ingredient"]["name"] end it "raises errors from fields" do err = assert_raises GraphQL::Error do schema.execute("{ testError }") end assert_equal "Field error", err.message end it "raises errors from sources" do err = assert_raises GraphQL::Error do schema.execute("{ testError(source: true) }") end assert_equal "Source error on: [1]", err.message end it "works with very very large queries" do query_str = "{".dup fields = 1100 fields.times do |i| query_str << "\n field#{i}: lookaheadIngredient(input: { id: 1, batchKey: \"key-#{i}\"}) { name }" end query_str << "\n}" GC.start GC.disable res = schema.execute(query_str) assert_equal fields, res["data"].keys.size all_fibers = [] ObjectSpace.each_object(Fiber) do |f| all_fibers << f end all_fibers.delete(Fiber.current) if all_fibers.any?(&:alive?) puts <<~ERR Alive fibers: - #{all_fibers.select(&:alive?).join("\n - ")} ERR end assert_equal [false], all_fibers.map(&:alive?).uniq ensure GC.enable end it "doesn't perform duplicate source fetches" do query = <<~QUERY query { manufacturers { parts { components(filter: {name: {equalToAnyOf: ["c1", "c2", "c3"]}}) { name } } } } QUERY response = parts_schema.execute(query).to_h assert_equal [4, 4, 4, 4], response["data"]["manufacturers"].map { |parts_obj| parts_obj["parts"].size } end end end end def make_schema_from(schema) schema end include DataloaderAssertions if RUBY_VERSION >= "3.1.1" require "async" describe "AsyncDataloader" do def make_schema_from(schema) Class.new(schema) { use GraphQL::Dataloader::AsyncDataloader } end include DataloaderAssertions end end if Fiber.respond_to?(:scheduler) describe "nonblocking: true" do def make_schema_from(schema) Class.new(schema) do use GraphQL::Dataloader, nonblocking: true end end before do Fiber.set_scheduler(::DummyScheduler.new) end after do Fiber.set_scheduler(nil) end include DataloaderAssertions end if RUBY_ENGINE == "ruby" && !ENV["GITHUB_ACTIONS"] describe "nonblocking: true with libev" do require "libev_scheduler" def make_schema_from(schema) Class.new(schema) do use GraphQL::Dataloader, nonblocking: true end end before do Fiber.set_scheduler(Libev::Scheduler.new) end after do Fiber.set_scheduler(nil) end include DataloaderAssertions end end end describe "example from #3314" do module Example class FooType < GraphQL::Schema::Object field :id, ID, null: false end class FooSource < GraphQL::Dataloader::Source def fetch(ids) ids.map { |id| OpenStruct.new(id: id) } end end class QueryType < GraphQL::Schema::Object field :foo, Example::FooType do argument :foo_id, GraphQL::Types::ID, required: false, loads: Example::FooType end def foo(foo: nil) dataloader.with(Example::FooSource).load("load") end end class Schema < GraphQL::Schema query Example::QueryType use GraphQL::Dataloader def self.object_from_id(id, ctx) ctx.dataloader.with(Example::FooSource).load(id) end def self.resolve_type(type, obj, ctx) type end end end it "loads properly" do result = Example::Schema.execute(<<-GRAPHQL) { fooWithLoad: foo(fooId: "Other") { __typename id } } GRAPHQL # This should not have a Lazy in it expected_result = { "data" => { "fooWithLoad" => { "id" => "load", "__typename" => "Foo" }, } } assert_equal expected_result, result.to_h end end class FiberErrorSchema < GraphQL::Schema class ErrorObject < GraphQL::Dataloader::Source def fetch(_) raise ArgumentError, "Nope" end end class Query < GraphQL::Schema::Object field :load, String, null: false field :load_all, String, null: false field :request, String, null: false field :request_all, String, null: false def load dataloader.with(ErrorObject).load(123) end def load_all dataloader.with(ErrorObject).load_all([123]) end def request req = dataloader.with(ErrorObject).request(123) req.load end def request_all req = dataloader.with(ErrorObject).request_all([123]) req.load end end use GraphQL::Dataloader query(Query) rescue_from(StandardError) do |err, obj, args, ctx, field| ctx[:errors] << "#{err.message} (#{field.owner.name}.#{field.graphql_name}, #{obj.inspect}, #{args.inspect})" nil end end it "Works with error handlers" do context = { errors: [] } res = FiberErrorSchema.execute("{ load loadAll request requestAll }", context: context) expected_errors = [ "Nope (FiberErrorSchema::Query.load, nil, {})", "Nope (FiberErrorSchema::Query.loadAll, nil, {})", "Nope (FiberErrorSchema::Query.request, nil, {})", "Nope (FiberErrorSchema::Query.requestAll, nil, {})", ] assert_nil(res["data"]) assert_equal(expected_errors, context[:errors].sort) end it "has proper context[:current_field]" do res = FiberSchema.execute("mutation { mutation1(argument1: \"abc\") { __typename } mutation2(argument2: \"def\") { __typename } }") assert_equal({"mutation1"=>nil, "mutation2"=>nil}, res["data"]) expected_errors = [ "FieldTestError @ [\"mutation1\"], Mutation.mutation1 / Mutation.mutation1", "FieldTestError @ [\"mutation2\"], Mutation.mutation2 / Mutation.mutation2", ] assert_equal expected_errors, res.context[:errors] end it "passes along throws" do value = catch(:hello) do dataloader = GraphQL::Dataloader.new dataloader.append_job do throw(:hello, :world) end dataloader.run end assert :world, value end class CanaryDataloader < GraphQL::Dataloader::NullDataloader end it "uses context[:dataloader] when given" do res = Class.new(GraphQL::Schema) do query_type = Class.new(GraphQL::Schema::Object) do graphql_name "Query" end query(query_type) end.execute("{ __typename }") assert_instance_of GraphQL::Dataloader::NullDataloader, res.context.dataloader res = FiberSchema.execute("{ __typename }") assert_instance_of GraphQL::Dataloader, res.context.dataloader refute res.context.dataloader.nonblocking? res = FiberSchema.execute("{ __typename }", context: { dataloader: CanaryDataloader.new } ) assert_instance_of CanaryDataloader, res.context.dataloader if Fiber.respond_to?(:scheduler) Fiber.set_scheduler(::DummyScheduler.new) res = FiberSchema.execute("{ __typename }", context: { dataloader: GraphQL::Dataloader.new(nonblocking: true) }) assert res.context.dataloader.nonblocking? res = FiberSchema.multiplex([{ query: "{ __typename }" }], context: { dataloader: GraphQL::Dataloader.new(nonblocking: true) }) assert res[0].context.dataloader.nonblocking? Fiber.set_scheduler(nil) end end describe "#run_isolated" do module RunIsolated class CountSource < GraphQL::Dataloader::Source def fetch(ids) @count ||= 0 @count += ids.size ids.map { |_id| @count } end end end it "uses its own queue" do dl = GraphQL::Dataloader.new result = {} dl.append_job { result[:a] = 1 } dl.append_job { result[:b] = 2 } dl.append_job { result[:c] = 3 } dl.run_isolated { result[:d] = 4 } assert_equal({ d: 4 }, result) dl.run_isolated { _r1 = dl.with(RunIsolated::CountSource).request(1) _r2 = dl.with(RunIsolated::CountSource).request(2) r3 = dl.with(RunIsolated::CountSource).request(3) # This is going to `Fiber.yield` result[:e] = r3.load } assert_equal({ d: 4, e: 3 }, result) dl.run assert_equal({ a: 1, b: 2, c: 3, d: 4, e: 3 }, result) end it "shares a cache" do dl = GraphQL::Dataloader.new result = {} dl.run_isolated { _r1 = dl.with(RunIsolated::CountSource).request(1) _r2 = dl.with(RunIsolated::CountSource).request(2) r3 = dl.with(RunIsolated::CountSource).request(3) # Run all three of the above requests: result[:a] = r3.load } dl.append_job { # This should return cached from above result[:b] = dl.with(RunIsolated::CountSource).load(1) } dl.append_job { # This one is run by itself result[:c] = dl.with(RunIsolated::CountSource).load(4) } assert_equal({ a: 3 }, result) dl.run assert_equal({ a: 3, b: 3, c: 4 }, result) end end describe "thread local variables" do module ThreadVariable class Type < GraphQL::Schema::Object field :key, String, null: false field :value, String, null: false end class Source < GraphQL::Dataloader::Source def fetch(keys) keys.map { |key| OpenStruct.new(key: key, value: Thread.current[key.to_sym]) } end end class QueryType < GraphQL::Schema::Object field :thread_var, ThreadVariable::Type do argument :key, GraphQL::Types::String end def thread_var(key:) dataloader.with(ThreadVariable::Source).load(key) end end class Schema < GraphQL::Schema query ThreadVariable::QueryType use GraphQL::Dataloader end end it "sets the parent thread locals in the execution fiber" do Thread.current[:test_thread_var] = 'foobarbaz' result = ThreadVariable::Schema.execute(<<-GRAPHQL) { threadVar(key: "test_thread_var") { key value } } GRAPHQL expected_result = { "data" => { "threadVar" => { "key" => "test_thread_var", "value" => "foobarbaz" } } } assert_equal expected_result, result.to_h end end describe "thread-local variables with custom dataloader" do module CustomThreadVariable class Type < GraphQL::Schema::Object field :key, String, null: false field :value, String, null: false end class CustomDataloader < GraphQL::Dataloader def get_fiber_variables { test_thread_var: "bazbarfoo" } end end class Source < GraphQL::Dataloader::Source def fetch(keys) keys.map { |key| OpenStruct.new(key: key, value: Thread.current[key.to_sym]) } end end class QueryType < GraphQL::Schema::Object field :thread_var, CustomThreadVariable::Type do argument :key, GraphQL::Types::String end def thread_var(key:) dataloader.with(CustomThreadVariable::Source).load(key) end end class Schema < GraphQL::Schema query CustomThreadVariable::QueryType use CustomDataloader end end it "sets the parent thread locals in the execution fiber" do result = CustomThreadVariable::Schema.execute(<<-GRAPHQL) { threadVar(key: "test_thread_var") { key value } } GRAPHQL expected_result = { "data" => { "threadVar" => { "key" => "test_thread_var", "value" => "bazbarfoo" } } } assert_equal expected_result, result.to_h end end describe "dataloader calls from inside sources" do class NestedDataloaderCallsSchema < GraphQL::Schema class Echo < GraphQL::Dataloader::Source def fetch(keys) keys end end class Nested < GraphQL::Dataloader::Source def fetch(keys) dataloader.with(Echo).load_all(keys) end end class Nested2 < GraphQL::Dataloader::Source def fetch(keys) dataloader.with(Nested).load_all(keys) end end class QueryType < GraphQL::Schema::Object field :nested, String field :nested2, String def nested dataloader.with(Nested).load("nested") end def nested2 dataloader.with(Nested2).load("nested2") end end query QueryType use GraphQL::Dataloader end end it "loads data from inside source methods" do assert_equal({ "data" => { "nested" => "nested" } }, NestedDataloaderCallsSchema.execute("{ nested }")) assert_equal({ "data" => { "nested2" => "nested2" } }, NestedDataloaderCallsSchema.execute("{ nested2 }")) assert_equal({ "data" => { "nested" => "nested", "nested2" => "nested2" } }, NestedDataloaderCallsSchema.execute("{ nested nested2 }")) end describe "with lazy authorization hooks" do class LazyAuthHookSchema < GraphQL::Schema class Source < ::GraphQL::Dataloader::Source def fetch(ids) return ids.map {|i| i * 2} end end class BarType < GraphQL::Schema::Object field :id, Integer def id object end def self.authorized?(object, context) -> { true } end end class FooType < GraphQL::Schema::Object field :dataloader_value, BarType def self.authorized?(object, context) -> { true } end def dataloader_value dataloader.with(Source).load(1) end end class QueryType < GraphQL::Schema::Object field :foo, FooType def foo {} end end use GraphQL::Dataloader query QueryType lazy_resolve Proc, :call end it "resolves everything" do dataloader_query = """ query { foo { dataloaderValue { id } } } """ dataloader_result = LazyAuthHookSchema.execute(dataloader_query) assert_equal 2, dataloader_result["data"]["foo"]["dataloaderValue"]["id"] end end end graphql-ruby-2.2.17/spec/graphql/directive_spec.rb000066400000000000000000000216141476434635200221470ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Directive" do let(:variables) { {"t" => true, "f" => false} } let(:result) { Dummy::Schema.execute(query_string, variables: variables) } describe "on fields" do let(:query_string) { %|query directives($t: Boolean!, $f: Boolean!) { cheese(id: 1) { # plain fields: skipFlavor: flavor @skip(if: true) dontSkipFlavor: flavor @skip(if: false) dontSkipDontIncludeFlavor: flavor @skip(if: false), @include(if: false) skipAndInclude: flavor @skip(if: true), @include(if: true) includeFlavor: flavor @include(if: $t) dontIncludeFlavor: flavor @include(if: $f) # fields in fragments ... includeIdField ... dontIncludeIdField ... skipIdField ... dontSkipIdField } } fragment includeIdField on Cheese { includeId: id @include(if: true) } fragment dontIncludeIdField on Cheese { dontIncludeId: id @include(if: false) } fragment skipIdField on Cheese { skipId: id @skip(if: true) } fragment dontSkipIdField on Cheese { dontSkipId: id @skip(if: false) } | } describe "child fields" do let(:query_string) { <<-GRAPHQL { __type(name: """ Cheese """) { fields { name } fields @skip(if: true) { isDeprecated } } } GRAPHQL } it "skips child fields too" do first_field = result["data"]["__type"]["fields"].first assert first_field.key?("name") assert !first_field.key?("isDeprecated") end end describe "when directive uses argument with default value" do describe "with false" do let(:query_string) { <<-GRAPHQL query($f: Boolean = false) { cheese(id: 1) { dontIncludeFlavor: flavor @include(if: $f) dontSkipFlavor: flavor @skip(if: $f) } } GRAPHQL } it "is not included" do assert !result["data"]["cheese"].key?("dontIncludeFlavor") end it "is not skipped" do assert result["data"]["cheese"].key?("dontSkipFlavor") end end describe "with true" do let(:query_string) { <<-GRAPHQL query($t: Boolean = true) { cheese(id: 1) { includeFlavor: flavor @include(if: $t) skipFlavor: flavor @skip(if: $t) } } GRAPHQL } it "is included" do assert result["data"]["cheese"].key?("includeFlavor") end it "is skipped" do assert !result["data"]["cheese"].key?("skipFlavor") end end end it "intercepts fields" do expected = { "data" =>{ "cheese" => { "dontSkipFlavor" => "Brie", "includeFlavor" => "Brie", "includeId" => 1, "dontSkipId" => 1, }, }} assert_equal(expected, result) end end describe "on fragments spreads and inline fragments" do let(:query_string) { %|query directives { cheese(id: 1) { ... skipFlavorField @skip(if: true) ... dontSkipFlavorField @skip(if: false) ... includeFlavorField @include(if: true) ... dontIncludeFlavorField @include(if: false) ... on Cheese @skip(if: true) { skipInlineId: id } ... on Cheese @skip(if: false) { dontSkipInlineId: id } ... on Cheese @include(if: true) { includeInlineId: id } ... on Cheese @include(if: false) { dontIncludeInlineId: id } ... @skip(if: true) { skipNoType: id } ... @skip(if: false) { dontSkipNoType: id } } } fragment includeFlavorField on Cheese { includeFlavor: flavor } fragment dontIncludeFlavorField on Cheese { dontIncludeFlavor: flavor } fragment skipFlavorField on Cheese { skipFlavor: flavor } fragment dontSkipFlavorField on Cheese { dontSkipFlavor: flavor } |} it "intercepts fragment spreads" do expected = { "data" => { "cheese" => { "dontSkipFlavor" => "Brie", "includeFlavor" => "Brie", "dontSkipInlineId" => 1, "includeInlineId" => 1, "dontSkipNoType" => 1, }, }} assert_equal(expected, result) end end describe "merging @skip and @include" do let(:field_included?) { r = result["data"]["cheese"]; r.has_key?('flavor') && r.has_key?('withVariables') } let(:skip?) { false } let(:include?) { true } let(:variables) { {"skip" => skip?, "include" => include?} } let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { flavor @include(if: #{include?}) @skip(if: #{skip?}), withVariables: flavor @include(if: $include) @skip(if: $skip) } } "} # behavior as defined in # https://github.com/facebook/graphql/blob/master/spec/Section%203%20--%20Type%20System.md#include describe "when @skip=false and @include=true" do let(:skip?) { false } let(:include?) { true } it "is included" do assert field_included? end end describe "when @skip=false and @include=false" do let(:skip?) { false } let(:include?) { false } it "is not included" do assert !field_included? end end describe "when @skip=true and @include=true" do let(:skip?) { true } let(:include?) { true } it "is not included" do assert !field_included? end end describe "when @skip=true and @include=false" do let(:skip?) { true } let(:include?) { false } it "is not included" do assert !field_included? end end describe "when evaluating skip on query selection and fragment" do describe "with @skip" do let(:query_string) {" query getCheese ($skip: Boolean!) { cheese(id: 1) { flavor, withVariables: flavor, ...F0 } } fragment F0 on Cheese { flavor @skip(if: #{skip?}) withVariables: flavor @skip(if: $skip) } "} describe "and @skip=false" do let(:skip?) { false } it "is included" do assert field_included? end end describe "and @skip=true" do let(:skip?) { true } it "is included" do assert field_included? end end end end describe "when evaluating conflicting @skip and @include on query selection and fragment" do let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { ... on Cheese @include(if: #{include?}) { flavor } withVariables: flavor @include(if: $include), ...F0 } } fragment F0 on Cheese { flavor @skip(if: #{skip?}), withVariables: flavor @skip(if: $skip) } "} describe "when @skip=false and @include=true" do let(:skip?) { false } let(:include?) { true } it "is included" do assert field_included? end end describe "when @skip=false and @include=false" do let(:skip?) { false } let(:include?) { false } it "is included" do assert field_included? end end describe "when @skip=true and @include=true" do let(:skip?) { true } let(:include?) { true } it "is included" do assert field_included? end end describe "when @skip=true and @include=false" do let(:skip?) { true } let(:include?) { false } it "is not included" do assert !field_included? end end end describe "when handling multiple fields at the same level" do describe "when at least one occurrence would be included" do let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { ... on Cheese { flavor } flavor @include(if: #{include?}), flavor @skip(if: #{skip?}), withVariables: flavor, withVariables: flavor @include(if: $include), withVariables: flavor @skip(if: $skip) } } "} let(:skip?) { true } let(:include?) { false } it "is included" do assert field_included? end end describe "when no occurrence would be included" do let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { flavor @include(if: #{include?}), flavor @skip(if: #{skip?}), withVariables: flavor @include(if: $include), withVariables: flavor @skip(if: $skip) } } "} let(:skip?) { true } let(:include?) { false } it "is not included" do assert !field_included? end end end end end graphql-ruby-2.2.17/spec/graphql/execution/000077500000000000000000000000001476434635200206315ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/execution/errors_spec.rb000066400000000000000000000227671476434635200235220ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Execution::Errors" do class ParentErrorsTestSchema < GraphQL::Schema class ErrorD < RuntimeError; end rescue_from(ErrorD) do |err, obj, args, ctx, field| raise GraphQL::ExecutionError, "ErrorD on #{obj.inspect} at #{field ? "#{field.path}(#{args})" : "boot"}" end end class ErrorsTestSchema < ParentErrorsTestSchema ErrorD = ParentErrorsTestSchema::ErrorD class ErrorA < RuntimeError; end class ErrorB < RuntimeError; end class ErrorC < RuntimeError attr_reader :value def initialize(value:) @value = value super end end class ErrorASubclass < ErrorA; end class ErrorBChildClass < ErrorB; end class ErrorBGrandchildClass < ErrorBChildClass; end rescue_from(ErrorA) do |err, obj, args, ctx, field| ctx[:errors] << "#{err.message} (#{field.owner.name}.#{field.graphql_name}, #{obj.inspect}, #{args.inspect})" nil end rescue_from(ErrorBChildClass) do |*| "Handled ErrorBChildClass" end # Trying to assert that _specificity_ takes priority # over sequence, but the stability of that assertion # depends on the underlying implementation. rescue_from(ErrorBGrandchildClass) do |*| "Handled ErrorBGrandchildClass" end rescue_from(ErrorB) do |*| raise GraphQL::ExecutionError, "boom!" end rescue_from(ErrorC) do |err, *| err.value end class ErrorList < Array def each raise ErrorB end end class Thing < GraphQL::Schema::Object def self.authorized?(obj, ctx) if ctx[:authorized] == false raise ErrorD end true end field :string, String, null: false def string "a string" end end class ValuesInput < GraphQL::Schema::InputObject argument :value, Int, loads: Thing def self.object_from_id(type, value, ctx) if value == 1 :thing else raise ErrorD end end end class PickyString < GraphQL::Schema::Scalar def self.coerce_input(value, ctx) if value == "picky" value else raise ErrorB, "The string wasn't \"picky\"" end end end class Query < GraphQL::Schema::Object field :f1, Int do argument :a1, Int, required: false end def f1(a1: nil) raise ErrorA, "f1 broke" end field :f2, Int def f2 -> { raise ErrorA, "f2 broke" } end field :f3, Int def f3 raise ErrorB end field :f4, Int, null: false def f4 raise ErrorC.new(value: 20) end field :f5, Int def f5 raise ErrorASubclass, "raised subclass" end field :f6, Int def f6 -> { raise ErrorB } end field :f7, String def f7 raise ErrorBGrandchildClass end field :f8, String do argument :input, PickyString end def f8(input:) input end field :f9, String do argument :thing_id, ID, loads: Thing end def f9(thing:) thing[:id] end field :thing, Thing def thing :thing end field :input_field, Int do argument :values, ValuesInput end field :non_nullable_array, [String], null: false def non_nullable_array [nil] end field :error_in_each, [Int] def error_in_each ErrorList.new end end query(Query) lazy_resolve(Proc, :call) def self.object_from_id(id, ctx) if id == "boom" raise ErrorB end { thing: true, id: id } end def self.resolve_type(type, obj, ctx) Thing end end class ErrorsTestSchemaWithoutInterpreter < GraphQL::Schema class Query < GraphQL::Schema::Object field :non_nullable_array, [String], null: false def non_nullable_array [nil] end end query(Query) end describe "rescue_from handling" do it "can replace values with `nil`" do ctx = { errors: [] } res = ErrorsTestSchema.execute "{ f1(a1: 1) }", context: ctx, root_value: :abc assert_equal({ "data" => { "f1" => nil } }, res) assert_equal ["f1 broke (ErrorsTestSchema::Query.f1, :abc, {:a1=>1})"], ctx[:errors] end it "rescues errors from lazy code" do ctx = { errors: [] } res = ErrorsTestSchema.execute("{ f2 }", context: ctx) assert_equal({ "data" => { "f2" => nil } }, res) assert_equal ["f2 broke (ErrorsTestSchema::Query.f2, nil, {})"], ctx[:errors] end it "picks the most specific handler and uses the return value from it" do res = ErrorsTestSchema.execute("{ f7 }") assert_equal({ "data" => { "f7" => "Handled ErrorBGrandchildClass" } }, res) end it "rescues errors from lazy code with handlers that re-raise" do res = ErrorsTestSchema.execute("{ f6 }") expected_error = { "message"=>"boom!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["f6"] } assert_equal({ "data" => { "f6" => nil }, "errors" => [expected_error] }, res) end it "can raise new errors" do res = ErrorsTestSchema.execute("{ f3 }") expected_error = { "message"=>"boom!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["f3"] } assert_equal({ "data" => { "f3" => nil }, "errors" => [expected_error] }, res) end it "can replace values with non-nil" do res = ErrorsTestSchema.execute("{ f4 }") assert_equal({ "data" => { "f4" => 20 } }, res) end it "rescues subclasses" do context = { errors: [] } res = ErrorsTestSchema.execute("{ f5 }", context: context) assert_equal({ "data" => { "f5" => nil } }, res) assert_equal ["raised subclass (ErrorsTestSchema::Query.f5, nil, {})"], context[:errors] end describe "errors raised when coercing inputs" do it "rescues them" do res1 = ErrorsTestSchema.execute("{ f8(input: \"picky\") }") assert_equal "picky", res1["data"]["f8"] res2 = ErrorsTestSchema.execute("{ f8(input: \"blah\") }") assert_equal ["errors"], res2.keys assert_equal ["boom!"], res2["errors"].map { |e| e["message"] } res3 = ErrorsTestSchema.execute("query($v: PickyString!) { f8(input: $v) }", variables: { v: "blah" }) assert_equal ["errors"], res3.keys assert_equal ["Variable $v of type PickyString! was provided invalid value"], res3["errors"].map { |e| e["message"] } assert_equal [["boom!"]], res3["errors"].map { |e| e["extensions"]["problems"].map { |pr| pr["explanation"] } } end end describe "errors raised when loading objects from ID" do it "rescues them" do res1 = ErrorsTestSchema.execute("{ f9(thingId: \"abc\") }") assert_equal "abc", res1["data"]["f9"] res2 = ErrorsTestSchema.execute("{ f9(thingId: \"boom\") }") assert_equal ["boom!"], res2["errors"].map { |e| e["message"] } end end describe "errors raised in authorized hooks" do it "rescues them" do context = { authorized: false } res = ErrorsTestSchema.execute(" { thing { string } } ", context: context) assert_equal ["ErrorD on nil at Query.thing({})"], res["errors"].map { |e| e["message"] } end end describe "errors raised in input_object loads" do it "rescues them from literal values" do context = { authorized: false } res = ErrorsTestSchema.execute(" { inputField(values: { value: 2 }) } ", root_value: :root, context: context) # It would be better to have the arguments here, but since this error was raised during _creation_ of keywords, # so the runtime arguments aren't available now. assert_equal ["ErrorD on :root at Query.inputField()"], res["errors"].map { |e| e["message"] } end it "rescues them from variable values" do context = { authorized: false } res = ErrorsTestSchema.execute( "query($values: ValuesInput!) { inputField(values: $values) } ", variables: { values: { "value" => 2 } }, context: context, ) assert_equal ["ErrorD on nil at Query.inputField()"], res["errors"].map { |e| e["message"] } end end describe "errors raised in non_nullable_array loads" do it "outputs the appropriate error message when using non-interpreter schema" do res = ErrorsTestSchemaWithoutInterpreter.execute("{ nonNullableArray }") expected_error = { "message" => "Cannot return null for non-nullable field Query.nonNullableArray" } assert_equal({ "data" => nil, "errors" => [expected_error] }, res) end it "outputs the appropriate error message when using interpreter schema" do res = ErrorsTestSchema.execute("{ nonNullableArray }") expected_error = { "message" => "Cannot return null for non-nullable field Query.nonNullableArray" } assert_equal({ "data" => nil, "errors" => [expected_error] }, res) end end describe "when .each on a list type raises an error" do it "rescues it properly" do res = ErrorsTestSchema.execute("{ __typename errorInEach }") expected_error = { "message" => "boom!", "locations"=>[{"line"=>1, "column"=>14}], "path"=>["errorInEach"] } assert_equal({ "data" => { "__typename" => "Query", "errorInEach" => nil }, "errors" => [expected_error] }, res) end end end end graphql-ruby-2.2.17/spec/graphql/execution/instrumentation_spec.rb000066400000000000000000000154741476434635200254460ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Schema do describe "instrumentation teardown bug" do # This instrumenter records that it ran, # or raises an error if instructed to do so class InstrumenterError < StandardError attr_reader :key def initialize(key) @key = key super() end end module LogInstrumenter def self.generate(context_key_sym) hook_method = :"#{context_key_sym}_run_hook" mod = Module.new mod.define_method(:execute_query) do |query:, &block| public_send(hook_method, query, "begin") result = nil begin result = super(query: query, &block) ensure public_send(hook_method, query, "end") end result end mod.define_method(:execute_multiplex) do |multiplex:, &block| public_send(hook_method, multiplex, "begin") result = nil begin result = super(multiplex: multiplex, &block) ensure public_send(hook_method, multiplex, "end") end result end mod.define_method(hook_method) do |unit_of_work, event_name| log_key = :"#{context_key_sym}_did_#{event_name}" error_key = :"#{context_key_sym}_should_raise_#{event_name}" unit_of_work.context[log_key] = true if unit_of_work.context[error_key] raise InstrumenterError.new(log_key) end end mod end end module ExecutionErrorTrace def execute_query(query:) if query.context[:raise_execution_error] raise GraphQL::ExecutionError, "Raised from trace execute_query" end super end end # This is how you might add queries from a persisted query backend module QueryStringTrace def execute_multiplex(multiplex:) multiplex.queries.each do |query| if query.context[:extra_query_string] && query.query_string.nil? query.query_string = query.context[:extra_query_string] end end super end end let(:query_type) { Class.new(GraphQL::Schema::Object) do graphql_name "Query" field :int, Integer do argument :value, Integer, required: false end def int(value:) value end end } let(:schema) { spec = self Class.new(GraphQL::Schema) do query(spec.query_type) trace_with(LogInstrumenter.generate(:second_instrumenter)) trace_with(LogInstrumenter.generate(:first_instrumenter)) trace_with(ExecutionErrorTrace) trace_with(QueryStringTrace) end } describe "query instrumenters" do it "before_query of the 2nd instrumenter does not run but after_query does" do context = {second_instrumenter_should_raise_begin: true} assert_raises InstrumenterError do schema.execute(" { int(value: 2) } ", context: context) end assert context[:first_instrumenter_did_begin] assert context[:first_instrumenter_did_end] assert context[:second_instrumenter_did_begin] refute context[:second_instrumenter_did_end] end it "runs after_query even if a previous after_query raised an error" do context = {second_instrumenter_should_raise_end: true} err = assert_raises InstrumenterError do schema.execute(" { int(value: 2) } ", context: context) end # The error came from the second instrumenter: assert_equal :second_instrumenter_did_end, err.key # But the first instrumenter still got a chance to teardown assert context[:first_instrumenter_did_begin] assert context[:first_instrumenter_did_end] assert context[:second_instrumenter_did_begin] assert context[:second_instrumenter_did_end] end it "rescues execution errors from execute_query" do context = {raise_execution_error: true} res = schema.execute(" { int(value: 2) } ", context: context) assert_equal "Raised from trace execute_query", res["errors"].first["message"] refute res.key?("data"), "The query doesn't run" end it "can assign a query string there" do context = { extra_query_string: "{ __typename }"} res = schema.execute(nil, context: context) assert_equal "Query", res["data"]["__typename"] end end describe "within a multiplex" do let(:multiplex_schema) { Class.new(schema) { trace_with(LogInstrumenter.generate(:second_instrumenter)) trace_with(LogInstrumenter.generate(:first_instrumenter)) } } it "only runs after_multiplex if before_multiplex finished" do multiplex_ctx = {second_instrumenter_should_raise_begin: true} query_1_ctx = {} query_2_ctx = {} assert_raises InstrumenterError do multiplex_schema.multiplex( [ {query: "{int(value: 1)}", context: query_1_ctx}, {query: "{int(value: 2)}", context: query_2_ctx}, ], context: multiplex_ctx ) end assert multiplex_ctx[:first_instrumenter_did_begin] assert multiplex_ctx[:first_instrumenter_did_end] assert multiplex_ctx[:second_instrumenter_did_begin] refute multiplex_ctx[:second_instrumenter_did_end] # No query instrumentation was run at all assert_equal 0, query_1_ctx.size assert_equal 0, query_2_ctx.size end it "does full and partial query runs" do multiplex_ctx = {} query_1_ctx = {} query_2_ctx = {second_instrumenter_should_raise_begin: true} assert_raises InstrumenterError do multiplex_schema.multiplex( [ { query: " { int(value: 2) } ", context: query_1_ctx }, { query: " { int(value: 2) } ", context: query_2_ctx }, ], context: multiplex_ctx ) end # multiplex got a full run assert multiplex_ctx[:first_instrumenter_did_begin] assert multiplex_ctx[:first_instrumenter_did_end] assert multiplex_ctx[:second_instrumenter_did_begin] assert multiplex_ctx[:second_instrumenter_did_end] # query 1 got a full run assert query_1_ctx[:first_instrumenter_did_begin] assert query_1_ctx[:first_instrumenter_did_end] assert query_1_ctx[:second_instrumenter_did_begin] assert query_1_ctx[:second_instrumenter_did_end] # query 2 got a partial run assert query_2_ctx[:first_instrumenter_did_begin] assert query_2_ctx[:first_instrumenter_did_end] assert query_2_ctx[:second_instrumenter_did_begin] refute query_2_ctx[:second_instrumenter_did_end] end end end end graphql-ruby-2.2.17/spec/graphql/execution/interpreter/000077500000000000000000000000001476434635200231745ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/execution/interpreter/arguments_spec.rb000066400000000000000000000027141476434635200265440ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Execution::Interpreter::Arguments" do class InterpreterArgsTestSchema < GraphQL::Schema class SearchParams < GraphQL::Schema::InputObject argument :query, String, required: false end class Query < GraphQL::Schema::Object field :search, [String], null: false do argument :params, SearchParams, required: false argument :limit, Int end end query(Query) end def arguments(query_str) query = GraphQL::Query.new(InterpreterArgsTestSchema, query_str) ast_node = query.document.definitions.first.selections.first query_type = query.get_type("Query") field = query.get_field(query_type, "search") query.arguments_for(ast_node, field) end it "provides .dig" do query_str = <<-GRAPHQL { search(limit: 10, params: { query: "abcde" } ) } GRAPHQL args = arguments(query_str) assert_equal 10, args.dig(:limit) assert_equal "abcde", args.dig(:params, :query) assert_nil args.dig(:nothing) assert_nil args.dig(:params, :nothing) assert_nil args.dig(:nothing, :nothing, :nothing) end it "is frozen, and so are its constituent hashes" do query_str = <<-GRAPHQL { search(limit: 10, params: { query: "abcde" } ) } GRAPHQL args = arguments(query_str) assert args.frozen? assert args.argument_values.frozen? assert args.keyword_arguments.frozen? end end graphql-ruby-2.2.17/spec/graphql/execution/interpreter_spec.rb000066400000000000000000000626341476434635200245460ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require_relative "../subscriptions_spec" describe GraphQL::Execution::Interpreter do module InterpreterTest class Box def initialize(value: nil, &block) @value = value @block = block end def value if @block @value = @block.call @block = nil end @value end end class Expansion < GraphQL::Schema::Object field :sym, String, null: false field :lazy_sym, String, null: false field :name, String, null: false field :cards, ["InterpreterTest::Card"], null: false def self.authorized?(expansion, ctx) if expansion.sym == "NOPE" false else true end end def cards Query::CARDS.select { |c| c.expansion_sym == @object.sym } end def lazy_sym Box.new(value: object.sym) end field :null_union_field_test, Integer, null: false def null_union_field_test 1 end field :always_cached_value, Integer, null: false def always_cached_value raise "should never be called" end end class Card < GraphQL::Schema::Object field :name, String, null: false field :colors, "[InterpreterTest::Color]", null: false field :expansion, Expansion, null: false def expansion Query::EXPANSIONS.find { |e| e.sym == @object.expansion_sym } end field :null_union_field_test, Integer def null_union_field_test nil end field :parent_class_name, String, null: false, extras: [:parent] def parent_class_name(parent:) parent.class.name end end class Color < GraphQL::Schema::Enum value "WHITE" value "BLUE" value "BLACK" value "RED" value "GREEN" end class Entity < GraphQL::Schema::Union possible_types Card, Expansion def self.resolve_type(obj, ctx) obj.sym ? Expansion : Card end end class FieldCounter < GraphQL::Schema::Object implements GraphQL::Types::Relay::Node field :field_counter, FieldCounter, null: false def field_counter; self.class.generate_tag(context); end field :calls, Integer, null: false do argument :expected, Integer end def calls(expected:) c = context[:calls] += 1 if c != expected raise "Expected #{expected} calls but had #{c} so far" else c end end field :runtime_info, String, null: false do argument :a, Integer, required: false argument :b, Integer, required: false end def runtime_info(a: nil, b: nil) inspect_context end field :lazy_runtime_info, String, null: false do argument :a, Integer, required: false argument :b, Integer, required: false end def lazy_runtime_info(a: nil, b: nil) Box.new { inspect_context } end def self.generate_tag(context) context[:field_counters_count] ||= 0 current_count = context[:field_counters_count] += 1 "field_counter_#{current_count}" end private def inspect_context "<#{interpreter_context_for(:current_object).object.inspect}> #{interpreter_context_for(:current_path)} -> #{interpreter_context_for(:current_field).path}(#{interpreter_context_for(:current_arguments).size})" end def interpreter_context_for(key) # Make sure it's set in query context and interpreter namespace. base_ctx_value = context[key] interpreter_ctx_value = context.namespace(:interpreter)[key] if base_ctx_value != interpreter_ctx_value raise "Context mismatch for #{key} -> #{base_ctx_value} / intepreter: #{interpreter_ctx_value}" else base_ctx_value end end end class Query < GraphQL::Schema::Object # Try a root-level authorized hook that returns a lazy value def self.authorized?(obj, ctx) Box.new(value: true) end field :card, Card do argument :name, String end def card(name:) Box.new(value: CARDS.find { |c| c.name == name }) end field :expansion, Expansion do argument :sym, String end def expansion(sym:) EXPANSIONS.find { |e| e.sym == sym } end field :expansion_raw, Expansion, null: false def expansion_raw raw_value(sym: "RAW", name: "Raw expansion", always_cached_value: 42) end field :expansion_mixed, [Expansion], null: false def expansion_mixed expansions + [expansion_raw] end field :expansions, [Expansion], null: false def expansions EXPANSIONS end class ExpansionData < OpenStruct end CARDS = [ OpenStruct.new(name: "Dark Confidant", colors: ["BLACK"], expansion_sym: "RAV"), ] EXPANSIONS = [ ExpansionData.new(name: "Ravnica, City of Guilds", sym: "RAV"), # This data has an error, for testing null propagation ExpansionData.new(name: nil, sym: "XYZ"), # This is not allowed by .authorized?, ExpansionData.new(name: nil, sym: "NOPE"), ] field :find, [Entity], null: false do argument :id, [ID] end def find(id:) id.map do |ent_id| Query::EXPANSIONS.find { |e| e.sym == ent_id } || Query::CARDS.find { |c| c.name == ent_id } end end field :find_many, [Entity, null: true], null: false do argument :ids, [ID] end def find_many(ids:) find(id: ids).map { |e| Box.new(value: e) } end field :field_counter, FieldCounter, null: false def field_counter; FieldCounter.generate_tag(context) ; end include GraphQL::Types::Relay::HasNodeField include GraphQL::Types::Relay::HasNodesField class NestedQueryResult < GraphQL::Schema::Object field :result, String field :current_path, [String] end field :nested_query, NestedQueryResult do argument :query, String end def nested_query(query:) result = context.schema.multiplex([{query: query}], context: { allow_pending_thread_state: true }).first { result: JSON.dump(result), current_path: context[:current_path], } end end class Counter < GraphQL::Schema::Object field :value, Integer, null: false field :lazy_value, Integer, null: false def lazy_value Box.new { object.value } end field :increment, Counter, null: false def increment object.value += 1 object end end class Mutation < GraphQL::Schema::Object field :increment_counter, Counter, null: false def increment_counter counter = context[:counter] counter.value += 1 counter end end class Schema < GraphQL::Schema query(Query) mutation(Mutation) lazy_resolve(Box, :value) use GraphQL::Schema::AlwaysVisible def self.object_from_id(id, ctx) OpenStruct.new(id: id) end def self.id_from_object(obj, type, ctx) obj.id end def self.resolve_type(type, obj, ctx) FieldCounter end class EnsureArgsAreObject def self.trace(event, data) case event when "execute_field", "execute_field_lazy" args = data[:query].context[:current_arguments] if !args.is_a?(GraphQL::Execution::Interpreter::Arguments) raise "Expected arguments object, got #{args.class}: #{args.inspect}" end end yield end end tracer EnsureArgsAreObject module EnsureThreadCleanedUp def execute_multiplex(multiplex:) res = super runtime_info = Thread.current[:__graphql_runtime_info] if !runtime_info.nil? && runtime_info != {} if !multiplex.context[:allow_pending_thread_state] # `nestedQuery` can allow this raise "Query did not clean up runtime state, found: #{runtime_info.inspect}" end end res end end trace_with(EnsureThreadCleanedUp) end end it "runs a query" do query_string = <<-GRAPHQL query($expansion: String!, $id1: ID!, $id2: ID!){ card(name: "Dark Confidant") { colors expansion { ... { name } cards { name } } } expansion(sym: $expansion) { ... ExpansionFields } find(id: [$id1, $id2]) { __typename ... on Card { name } ... on Expansion { sym } } } fragment ExpansionFields on Expansion { cards { name } } GRAPHQL vars = {expansion: "RAV", id1: "Dark Confidant", id2: "RAV"} result = InterpreterTest::Schema.execute(query_string, variables: vars) assert_equal ["BLACK"], result["data"]["card"]["colors"] assert_equal "Ravnica, City of Guilds", result["data"]["card"]["expansion"]["name"] assert_equal [{"name" => "Dark Confidant"}], result["data"]["card"]["expansion"]["cards"] assert_equal [{"name" => "Dark Confidant"}], result["data"]["expansion"]["cards"] expected_abstract_list = [ {"__typename" => "Card", "name" => "Dark Confidant"}, {"__typename" => "Expansion", "sym" => "RAV"}, ] assert_equal expected_abstract_list, result["data"]["find"] assert_nil Thread.current[:__graphql_runtime_info] end it "runs a nested query and maintains proper state" do query_str = "query($queryStr: String!) { nestedQuery(query: $queryStr) { result currentPath } }" result = InterpreterTest::Schema.execute(query_str, variables: { queryStr: "{ __typename }" }) assert_equal '{"data":{"__typename":"Query"}}', result["data"]["nestedQuery"]["result"] assert_equal ["nestedQuery"], result["data"]["nestedQuery"]["currentPath"] assert_nil Thread.current[:__graphql_runtime_info] end it "runs mutation roots atomically and sequentially" do query_str = <<-GRAPHQL mutation { i1: incrementCounter { value lazyValue i2: increment { value lazyValue } i3: increment { value lazyValue } } i4: incrementCounter { value lazyValue } i5: incrementCounter { value lazyValue } } GRAPHQL result = InterpreterTest::Schema.execute(query_str, context: { counter: OpenStruct.new(value: 0) }) expected_data = { "i1" => { "value" => 1, # All of these get `3` as lazy value. They're resolved together, # since they aren't _root_ mutation fields. "lazyValue" => 3, "i2" => { "value" => 2, "lazyValue" => 3 }, "i3" => { "value" => 3, "lazyValue" => 3 }, }, "i4" => { "value" => 4, "lazyValue" => 4}, "i5" => { "value" => 5, "lazyValue" => 5}, } assert_equal expected_data, result["data"] end it "runs skip and include" do query_str = <<-GRAPHQL query($truthy: Boolean!, $falsey: Boolean!){ exp1: expansion(sym: "RAV") @skip(if: true) { name } exp2: expansion(sym: "RAV") @skip(if: false) { name } exp3: expansion(sym: "RAV") @include(if: true) { name } exp4: expansion(sym: "RAV") @include(if: false) { name } exp5: expansion(sym: "RAV") @include(if: $truthy) { name } exp6: expansion(sym: "RAV") @include(if: $falsey) { name } } GRAPHQL vars = {truthy: true, falsey: false} result = InterpreterTest::Schema.execute(query_str, variables: vars) expected_data = { "exp2" => {"name" => "Ravnica, City of Guilds"}, "exp3" => {"name" => "Ravnica, City of Guilds"}, "exp5" => {"name" => "Ravnica, City of Guilds"}, } assert_equal expected_data, result["data"] assert_nil Thread.current[:__graphql_runtime_info] end describe "temporary interpreter flag" do it "is set" do # This can be removed later, just a sanity check during migration res = InterpreterTest::Schema.execute("{ __typename }") assert_equal true, res.context.interpreter? end end describe "runtime info in context" do it "is available" do res = InterpreterTest::Schema.execute <<-GRAPHQL { fieldCounter { runtimeInfo(a: 1, b: 2) fieldCounter { runtimeInfo lazyRuntimeInfo(a: 1) } } } GRAPHQL assert_equal '<"field_counter_1"> ["fieldCounter", "runtimeInfo"] -> FieldCounter.runtimeInfo(2)', res["data"]["fieldCounter"]["runtimeInfo"] # These are both `field_counter_2`, but one is lazy assert_equal '<"field_counter_2"> ["fieldCounter", "fieldCounter", "runtimeInfo"] -> FieldCounter.runtimeInfo(0)', res["data"]["fieldCounter"]["fieldCounter"]["runtimeInfo"] assert_equal '<"field_counter_2"> ["fieldCounter", "fieldCounter", "lazyRuntimeInfo"] -> FieldCounter.lazyRuntimeInfo(1)', res["data"]["fieldCounter"]["fieldCounter"]["lazyRuntimeInfo"] end end describe "null propagation" do it "propagates nulls" do query_str = <<-GRAPHQL { expansion(sym: "XYZ") { name sym lazySym } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) # Although the expansion was found, its name of `nil` # propagated to here assert_nil res["data"].fetch("expansion") assert_equal ["Cannot return null for non-nullable field Expansion.name"], res["errors"].map { |e| e["message"] } assert_nil Thread.current[:__graphql_runtime_info] end it "places errors ahead of data in the response" do query_str = <<-GRAPHQL { expansion(sym: "XYZ") { name } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) assert_equal ["errors", "data"], res.keys end it "propagates nulls in lists" do query_str = <<-GRAPHQL { expansions { name sym lazySym } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) # A null in one of the list items removed the whole list assert_nil(res["data"]) end it "works with unions that fail .authorized?" do res = InterpreterTest::Schema.execute <<-GRAPHQL { find(id: "NOPE") { ... on Expansion { sym } } } GRAPHQL assert_equal ["Cannot return null for non-nullable field Query.find"], res["errors"].map { |e| e["message"] } end it "works with lists of unions" do res = InterpreterTest::Schema.execute <<-GRAPHQL { findMany(ids: ["RAV", "NOPE", "BOGUS"]) { ... on Expansion { sym } } } GRAPHQL assert_equal 3, res["data"]["findMany"].size assert_equal "RAV", res["data"]["findMany"][0]["sym"] assert_equal nil, res["data"]["findMany"][1] assert_equal nil, res["data"]["findMany"][2] assert_equal false, res.key?("errors") assert_equal Hash, res["data"].class assert_equal Array, res["data"]["findMany"].class end it "works with union lists that have members of different kinds, with different nullabilities" do res = InterpreterTest::Schema.execute <<-GRAPHQL { findMany(ids: ["RAV", "Dark Confidant"]) { ... on Expansion { nullUnionFieldTest } ... on Card { nullUnionFieldTest } } } GRAPHQL assert_equal [1, nil], res["data"]["findMany"].map { |f| f["nullUnionFieldTest"] } end end describe "duplicated fields" do it "doesn't run them multiple times" do query_str = <<-GRAPHQL { fieldCounter { calls(expected: 1) # This should not be called since it matches the above calls(expected: 1) fieldCounter { calls(expected: 2) } ...ExtraFields } } fragment ExtraFields on FieldCounter { fieldCounter { # This should not be called since it matches the inline field: calls(expected: 2) # This _should_ be called c3: calls(expected: 3) } } GRAPHQL # It will raise an error if it doesn't match the expectation res = InterpreterTest::Schema.execute(query_str, context: { calls: 0 }) assert_equal 3, res["data"]["fieldCounter"]["fieldCounter"]["c3"] end end describe "backwards compatibility" do it "handles a legacy nodes field" do res = InterpreterTest::Schema.execute('{ node(id: "abc") { id } }') assert_equal "abc", res["data"]["node"]["id"] res = InterpreterTest::Schema.execute('{ nodes(ids: ["abc", "xyz"]) { id } }') assert_equal ["abc", "xyz"], res["data"]["nodes"].map { |n| n["id"] } end end describe "returning raw values" do it "returns raw value" do query_str = <<-GRAPHQL { expansionRaw { name sym alwaysCachedValue } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) assert_equal({ sym: "RAW", name: "Raw expansion", always_cached_value: 42 }, res["data"]["expansionRaw"]) end end describe "returning raw values and resolved fields" do it "returns raw value" do query_str = <<-GRAPHQL { expansionRaw { name sym alwaysCachedValue } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) assert_equal({ sym: "RAW", name: "Raw expansion", always_cached_value: 42 }, res["data"]["expansionRaw"]) end end describe "Lazy skips" do class LazySkipSchema < GraphQL::Schema class Query < GraphQL::Schema::Object def self.authorized?(obj, ctx) -> { true } end field :skip, String def skip context.skip end field :lazy_skip, String def lazy_skip -> { context.skip } end field :mixed_skips, [String] def mixed_skips [ "a", context.skip, "c", -> { context.skip }, "e", ] end end class NothingSubscription < GraphQL::Schema::Subscription field :nothing, String def authorized?(*) -> { true } end def update { nothing: object } end end class Subscription < GraphQL::Schema::Object field :nothing, subscription: NothingSubscription end query Query subscription Subscription use InMemoryBackend::Subscriptions, extra: nil lazy_resolve Proc, :call end it "skips properly" do res = LazySkipSchema.execute("{ skip }") assert_equal({}, res["data"]) refute res.key?("errors") res = LazySkipSchema.execute("{ mixedSkips }") assert_equal({ "mixedSkips" => ["a", "c", "e"] }, res["data"]) refute res.key?("errors") res = LazySkipSchema.execute("{ lazySkip }") assert_equal({}, res["data"]) refute res.key?("errors") res = LazySkipSchema.execute("subscription { nothing { nothing } }") assert_equal({}, res["data"]) refute res.key?("errors") # Make sure an update works properly LazySkipSchema.subscriptions.trigger(:nothing, {}, :nothing_at_all) _key, updates = LazySkipSchema.subscriptions.deliveries.first assert_equal "nothing_at_all", updates[0]["data"]["nothing"]["nothing"] end end describe "GraphQL::ExecutionErrors from connection fields" do module ConnectionErrorTest class BaseField < GraphQL::Schema::Field def authorized?(obj, args, ctx) ctx[:authorized_calls] ||= 0 ctx[:authorized_calls] += 1 raise GraphQL::ExecutionError, "#{name} is not authorized" end end class BaseConnection < GraphQL::Types::Relay::BaseConnection node_nullable(false) edge_nullable(false) edges_nullable(false) end class BaseEdge < GraphQL::Types::Relay::BaseEdge node_nullable(false) end class Thing < GraphQL::Schema::Object field_class BaseField connection_type_class BaseConnection edge_type_class BaseEdge field :title, String, null: false field :body, String, null: false end class Query < GraphQL::Schema::Object field :things, Thing.connection_type, null: false def things [{title: "a"}, {title: "b"}, {title: "c"}] end field :thing, Thing, null: false def thing { title: "a", body: "b", } end end class Schema < GraphQL::Schema query Query end end it "returns only 1 error and stops resolving fields after that" do res = ConnectionErrorTest::Schema.execute("{ things { nodes { title } } }") assert_equal 1, res["errors"].size assert_equal 1, res.context[:authorized_calls] res = ConnectionErrorTest::Schema.execute("{ things { edges { node { title } } } }") assert_equal 1, res["errors"].size assert_equal 1, res.context[:authorized_calls] res = ConnectionErrorTest::Schema.execute("{ thing { title body } }") assert_equal 1, res["errors"].size assert_equal 1, res.context[:authorized_calls] end end describe "GraphQL::ExecutionErrors from non-null list fields" do module ListErrorTest class BaseField < GraphQL::Schema::Field def authorized?(*) raise GraphQL::ExecutionError, "#{name} is not authorized" end end class Thing < GraphQL::Schema::Object field_class BaseField field :title, String, null: false end class Query < GraphQL::Schema::Object field :things, [Thing], null: false def things [{title: "a"}, {title: "b"}, {title: "c"}] end end class Schema < GraphQL::Schema query Query end end it "returns only 1 error" do res = ListErrorTest::Schema.execute("{ things { title } }") assert_equal 1, res["errors"].size end end describe "Invalid null from raised execution error doesn't halt parent fields" do class RaisedErrorSchema < GraphQL::Schema module Iface include GraphQL::Schema::Interface field :bar, String, null: false end class Txn < GraphQL::Schema::Object field :fails, String, null: false def fails raise GraphQL::ExecutionError, "boom" end end class Concrete < GraphQL::Schema::Object implements Iface field :txn, Txn def txn {} end field :msg, String def msg "THIS SHOULD SHOW UP" end end class Query < GraphQL::Schema::Object field :iface, Iface def iface {} end end query(Query) orphan_types([Concrete]) def self.resolve_type(type, obj, ctx) Concrete end end it "resolves fields on the parent object" do querystring = """ { iface { ... on Concrete { txn { fails } msg } } } """ result = RaisedErrorSchema.execute(querystring) expected_result = { "data" => { "iface" => { "txn" => nil, "msg" => "THIS SHOULD SHOW UP" }, }, "errors" => [ { "message"=>"boom", "locations"=>[{"line"=>6, "column"=>15}], "path"=>["iface", "txn", "fails"] }, ], } assert_equal expected_result, result.to_h end end it "supports extras: [:parent]" do query_str = <<-GRAPHQL { card(name: "Dark Confidant") { parentClassName } expansion(sym: "RAV") { cards { parentClassName } } } GRAPHQL res = InterpreterTest::Schema.execute(query_str, context: { calls: 0 }) assert_equal "NilClass", res["data"]["card"].fetch("parentClassName") assert_equal "InterpreterTest::Query::ExpansionData", res["data"]["expansion"]["cards"].first["parentClassName"] end describe "fragment used twice in different ways" do class FragmentBugSchema < GraphQL::Schema class ProductVariant < GraphQL::Schema::Object field :product, "FragmentBugSchema::Product" end class Product < GraphQL::Schema::Object field :id, ID field :variants, [ProductVariant] def variants [{ product: { id: "1" } }] end end class Query < GraphQL::Schema::Object field :variant, ProductVariant def variant { product: { id: "1" } } end end query(Query) end it "executes successfully" do query_str = <<-GRAPHQL { variant { ...variantFields ... on ProductVariant { product { variants { ...variantFields } } } } } fragment variantFields on ProductVariant { product { id } } GRAPHQL res = FragmentBugSchema.execute(query_str).to_h expected_result = { "variant" => { "product" => { "id" => "1", "variants" => [ { "product" => { "id" => "1" } } ] } } } assert_equal(expected_result, res["data"]) end end end graphql-ruby-2.2.17/spec/graphql/execution/lazy/000077500000000000000000000000001476434635200216105ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/execution/lazy/lazy_method_map_spec.rb000066400000000000000000000030751476434635200263300ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Lazy::LazyMethodMap do def self.test_lazy_method_map it "handles multithreaded access" do a = Class.new b = Class.new(a) c = Class.new(b) lazy_method_map.set(a, :a) threads = 1000.times.map do |i| Thread.new { d = Class.new(c) assert_equal :a, lazy_method_map.get(d.new) } end threads.map(&:join) end it "dups" do a = Class.new b = Class.new(a) c = Class.new(b) lazy_method_map.set(a, :a) lazy_method_map.get(b.new) lazy_method_map.get(c.new) dup_map = lazy_method_map.dup assert_equal 3, dup_map.instance_variable_get(:@storage).size assert_equal :a, dup_map.get(a.new) assert_equal :a, dup_map.get(b.new) assert_equal :a, dup_map.get(c.new) end end describe "with a plain hash" do let(:lazy_method_map) { GraphQL::Execution::Lazy::LazyMethodMap.new(use_concurrent: false) } test_lazy_method_map it "has a Ruby Hash inside" do storage = lazy_method_map .instance_variable_get(:@storage) .instance_variable_get(:@storage) assert_instance_of Hash, storage end end describe "with a Concurrent::Map" do let(:lazy_method_map) { GraphQL::Execution::Lazy::LazyMethodMap.new(use_concurrent: true) } test_lazy_method_map it "has a Concurrent::Map inside" do storage = lazy_method_map.instance_variable_get(:@storage) assert_instance_of Concurrent::Map, storage end end end graphql-ruby-2.2.17/spec/graphql/execution/lazy_spec.rb000066400000000000000000000144321476434635200231530ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Lazy do include LazyHelpers describe "resolving" do it "calls value handlers" do res = run_query('{ int(value: 2, plus: 1) }') assert_equal 3, res["data"]["int"] end it "Works with Query.new" do query_str = '{ int(value: 2, plus: 1) }' query = GraphQL::Query.new(LazyHelpers::LazySchema, query_str) res = query.result assert_equal 3, res["data"]["int"] end it "can do nested lazy values" do res = run_query %| { a: nestedSum(value: 3) { value nestedSum(value: 7) { value nestedSum(value: 1) { value nestedSum(value: -50) { value } } } } b: nestedSum(value: 2) { value nestedSum(value: 11) { value nestedSum(value: 2) { value nestedSum(value: -50) { value } } } } c: listSum(values: [1,2]) { nestedSum(value: 3) { value } } } | expected_data = { "a"=>{"value"=>14, "nestedSum"=>{ "value"=>46, "nestedSum"=>{ "value"=>95, "nestedSum"=>{"value"=>90} } }}, "b"=>{"value"=>14, "nestedSum"=>{ "value"=>46, "nestedSum"=>{ "value"=>95, "nestedSum"=>{"value"=>90} } }}, "c"=>[ {"nestedSum"=>{"value"=>14}}, {"nestedSum"=>{"value"=>14}} ], } assert_equal expected_data, res["data"] end [ [1, 2, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK], [2, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1], [LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1, 2], ].each do |ordered_values| it "resolves each field at one depth before proceeding to the next depth (using #{ordered_values})" do res = run_query <<-GRAPHQL, variables: { values: ordered_values } query($values: [Int!]!) { listSum(values: $values) { nestedSum(value: 3) { value } } } GRAPHQL # Even though magic number `44`'s `.authorized?` hook returns a lazy value, # these fields should be resolved together and return the same value. assert_equal 56, res["data"]["listSum"][0]["nestedSum"]["value"] assert_equal 56, res["data"]["listSum"][1]["nestedSum"]["value"] assert_equal 56, res["data"]["listSum"][2]["nestedSum"]["value"] end end it "Handles fields that return nil" do values = [ LazyHelpers::MAGIC_NUMBER_THAT_RETURNS_NIL, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1, 2, ] res = run_query <<-GRAPHQL, variables: { values: values } query($values: [Int!]!) { listSum(values: $values) { nullableNestedSum(value: 3) { value } } } GRAPHQL values = res["data"]["listSum"].map { |s| s && s["nullableNestedSum"]["value"] } assert_equal [nil, 56, 56, 56], values end it "propagates nulls to the root" do res = run_query %| { nestedSum(value: 1) { value nestedSum(value: 2) { nestedSum(value: 13) { value } } } }| assert_nil(res["data"]) assert_equal 1, res["errors"].length end it "propagates partial nulls" do res = run_query %| { nullableNestedSum(value: 1) { value nullableNestedSum(value: 2) { ns: nestedSum(value: 13) { value } } } }| expected_data = { "nullableNestedSum" => { "value" => 1, "nullableNestedSum" => nil, } } assert_equal(expected_data, res["data"]) assert_equal 1, res["errors"].length end it "handles raised errors" do res = run_query %| { a: nullableNestedSum(value: 1) { value } b: nullableNestedSum(value: 13) { value } c: nullableNestedSum(value: 2) { value } }| expected_data = { "a" => { "value" => 3 }, "b" => nil, "c" => { "value" => 3 }, } assert_equal expected_data, res["data"] expected_errors = [{ "message"=>"13 is unlucky", "locations"=>[{"line"=>4, "column"=>9}], "path"=>["b"], }] assert_equal expected_errors, res["errors"] end it "resolves mutation fields right away" do res = run_query %| { a: nestedSum(value: 2) { value } b: nestedSum(value: 4) { value } c: nestedSum(value: 6) { value } }| assert_equal [12, 12, 12], res["data"].values.map { |d| d["value"] } res = run_query %| mutation { a: nestedSum(value: 2) { value } b: nestedSum(value: 4) { value } c: nestedSum(value: 6) { value } } | assert_equal [2, 4, 6], res["data"].values.map { |d| d["value"] } end end describe "Schema#sync_lazy(object)" do it "Passes objects to that hook at runtime" do res = run_query <<-GRAPHQL { a: nullableNestedSum(value: 1001) { value } b: nullableNestedSum(value: 1013) { value } c: nullableNestedSum(value: 1002) { value } } GRAPHQL # This odd, non-adding behavior is hacked into `#sync_lazy` assert_equal 101, res["data"]["a"]["value"] assert_equal 113, res["data"]["b"]["value"] assert_equal 102, res["data"]["c"]["value"] end end describe "LazyMethodMap" do class SubWrapper < LazyHelpers::Wrapper; end let(:map) { GraphQL::Execution::Lazy::LazyMethodMap.new } it "finds methods for classes and subclasses" do map.set(LazyHelpers::Wrapper, :item) map.set(LazyHelpers::SumAll, :value) b = LazyHelpers::Wrapper.new(1) sub_b = LazyHelpers::Wrapper.new(2) s = LazyHelpers::SumAll.new(3) assert_equal(:item, map.get(b)) assert_equal(:item, map.get(sub_b)) assert_equal(:value, map.get(s)) end end end graphql-ruby-2.2.17/spec/graphql/execution/lookahead_spec.rb000066400000000000000000000630341476434635200241250ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Lookahead do module LookaheadTest DATA = [ OpenStruct.new(name: "Cardinal", is_waterfowl: false, similar_species_names: ["Scarlet Tanager"], genus: OpenStruct.new(latin_name: "Piranga")), OpenStruct.new(name: "Scarlet Tanager", is_waterfowl: false, similar_species_names: ["Cardinal"], genus: OpenStruct.new(latin_name: "Cardinalis")), OpenStruct.new(name: "Great Egret", is_waterfowl: false, similar_species_names: ["Great Blue Heron"], genus: OpenStruct.new(latin_name: "Ardea")), OpenStruct.new(name: "Great Blue Heron", is_waterfowl: true, similar_species_names: ["Great Egret"], genus: OpenStruct.new(latin_name: "Ardea")), ] def DATA.find_by_name(name) DATA.find { |b| b.name == name } end module Node include GraphQL::Schema::Interface field :id, ID, null: false end class BirdGenus < GraphQL::Schema::Object implements Node field :name, String, null: false field :latin_name, String, null: false field :id, ID, null: false, method: :latin_name end class BirdSpecies < GraphQL::Schema::Object implements Node field :name, String, null: false field :id, ID, null: false, method: :name field :is_waterfowl, Boolean, null: false field :similar_species, [BirdSpecies], null: false def similar_species object.similar_species_names.map { |n| DATA.find_by_name(n) } end field :genus, BirdGenus, null: false, extras: [:lookahead] def genus(lookahead:) if lookahead.selects?(:latin_name) context[:lookahead_latin_name] += 1 end object.genus end end class PlantSpecies < GraphQL::Schema::Object implements Node field :name, String, null: false field :id, ID, null: false, method: :name field :is_edible, Boolean, null: false end class Species < GraphQL::Schema::Union possible_types BirdSpecies, PlantSpecies end class Query < GraphQL::Schema::Object field :find_bird_species, BirdSpecies do argument :by_name, String end def find_bird_species(by_name:) DATA.find_by_name(by_name) end field :node, Node do argument :id, ID end def node(id:) if (node = DATA.find_by_name(id)) node else DATA.map { |d| d.genus }.select { |g| g.name == id } end end field :species, Species do argument :id, ID end def species(id:) DATA.find_by_name(id) end end module LookaheadInstrumenter def execute_query(query:) query.context[:root_lookahead_selections] = query.lookahead.selections super end end class Schema < GraphQL::Schema query(Query) trace_with LookaheadInstrumenter end class AlwaysVisibleSchema < Schema use GraphQL::Schema::AlwaysVisible end end describe "looking ahead" do let(:document) { GraphQL.parse <<-GRAPHQL query($name: String!){ findBirdSpecies(byName: $name) { name similarSpecies { likesWater: isWaterfowl } } t: __typename } GRAPHQL } let(:schema) { LookaheadTest::Schema } let(:query) { GraphQL::Query.new(schema, document: document, variables: { name: "Cardinal" }) } it "has a good test setup" do res = query.result assert_equal [false], res["data"]["findBirdSpecies"]["similarSpecies"].map { |s| s["likesWater"] } end it "can detect fields on objects with symbol or string" do lookahead = query.lookahead.selection("findBirdSpecies") assert_equal true, lookahead.selects?("similarSpecies") assert_equal true, lookahead.selects?(:similar_species) assert_equal false, lookahead.selects?("isWaterfowl") assert_equal false, lookahead.selects?(:is_waterfowl) end it "detects by name, not by alias" do assert_equal true, query.lookahead.selects?("__typename") end describe "with a NullWarden" do let(:schema) { LookaheadTest::AlwaysVisibleSchema } it "works" do lookahead = query.lookahead.selection("findBirdSpecies") assert_equal true, lookahead.selects?("similarSpecies") assert_equal true, lookahead.selects?(:similar_species) assert_equal false, lookahead.selects?("isWaterfowl") assert_equal false, lookahead.selects?(:is_waterfowl) end end describe "on unions" do let(:document) { GraphQL.parse <<-GRAPHQL { species(id: "Cardinal") { ... on BirdSpecies { name isWaterfowl } ... on PlantSpecies { name isEdible } } } GRAPHQL } it "works" do lookahead = query.lookahead.selection(:species) assert lookahead.selects?(:name) assert_equal [:name, :is_waterfowl, :name, :is_edible], lookahead.selections.map(&:name) end it "works with different selected types" do lookahead = query.lookahead.selection(:species) # Both have `name` assert lookahead.selects?(:name, selected_type: LookaheadTest::BirdSpecies) assert lookahead.selects?(:name, selected_type: LookaheadTest::PlantSpecies) # Only birds have `isWaterfowl` assert lookahead.selects?(:is_waterfowl, selected_type: LookaheadTest::BirdSpecies) refute lookahead.selects?(:is_waterfowl, selected_type: LookaheadTest::PlantSpecies) # Only plants have `isEdible` refute lookahead.selects?(:is_edible, selected_type: LookaheadTest::BirdSpecies) assert lookahead.selects?(:is_edible, selected_type: LookaheadTest::PlantSpecies) end end describe "fields on interfaces" do let(:document) { GraphQL.parse <<-GRAPHQL query { node(id: "Cardinal") { id ... on BirdSpecies { name } ...Other } } fragment Other on BirdGenus { latinName } GRAPHQL } it "finds fields on object types and interface types" do node_lookahead = query.lookahead.selection("node") assert_equal [:id, :name, :latin_name], node_lookahead.selections.map(&:name) end end describe "inspect" do it "works for root lookaheads" do assert_includes query.lookahead.inspect, "# "Cardinal" }) end it "is true when no constraints are given" do assert_equal true, lookahead.selects?(:find_bird_species, arguments: {}) assert_equal true, lookahead.selects?("__typename", arguments: {}) end it "is false when some given constraints aren't satisfied" do assert_equal false, lookahead.selects?(:find_bird_species, arguments: { by_name: "Chickadee" }) assert_equal false, lookahead.selects?(:find_bird_species, arguments: { by_name: "Cardinal", other: "Nonsense" }) end describe "with literal values" do let(:document) { GraphQL.parse <<-GRAPHQL { findBirdSpecies(byName: "Great Blue Heron") { isWaterfowl } } GRAPHQL } it "works" do assert_equal true, lookahead.selects?(:find_bird_species, arguments: { by_name: "Great Blue Heron" }) end end end it "can do a chained lookahead" do next_lookahead = query.lookahead.selection(:find_bird_species, arguments: { by_name: "Cardinal" }) assert_equal true, next_lookahead.selected? nested_selection = next_lookahead.selection(:similar_species).selection(:is_waterfowl, arguments: {}) assert_equal true, nested_selection.selected? assert_equal false, next_lookahead.selection(:similar_species).selection(:name).selected? end it "can detect fields on lists with symbol or string" do assert_equal true, query.lookahead.selection(:find_bird_species).selection(:similar_species).selection(:is_waterfowl).selected? assert_equal true, query.lookahead.selection("findBirdSpecies").selection("similarSpecies").selection("isWaterfowl").selected? end describe "merging branches and fragments" do let(:document) { GraphQL.parse <<-GRAPHQL { findBirdSpecies(name: "Cardinal") { similarSpecies { __typename } } ...F ... { findBirdSpecies(name: "Cardinal") { similarSpecies { isWaterfowl } } } } fragment F on Query { findBirdSpecies(name: "Cardinal") { similarSpecies { name } } } GRAPHQL } it "finds selections using merging" do merged_lookahead = query.lookahead.selection(:find_bird_species).selection(:similar_species) assert merged_lookahead.selects?(:__typename) assert merged_lookahead.selects?(:is_waterfowl) assert merged_lookahead.selects?(:name) end end end describe "in queries" do it "can be an extra" do query_str = <<-GRAPHQL { cardinal: findBirdSpecies(byName: "Cardinal") { genus { __typename } } scarletTanager: findBirdSpecies(byName: "Scarlet Tanager") { genus { latinName } } greatBlueHeron: findBirdSpecies(byName: "Great Blue Heron") { genus { latinName } } } GRAPHQL context = {lookahead_latin_name: 0} res = LookaheadTest::Schema.execute(query_str, context: context) refute res.key?("errors") assert_equal 2, context[:lookahead_latin_name] assert_equal [:find_bird_species], context[:root_lookahead_selections].map(&:name).uniq assert_equal( [{ by_name: "Cardinal" }, { by_name: "Scarlet Tanager" }, { by_name: "Great Blue Heron" }], context[:root_lookahead_selections].map(&:arguments) ) end it "works for invalid queries" do context = {lookahead_latin_name: 0} res = LookaheadTest::Schema.execute("{ doesNotExist }", context: context) assert res.key?("errors") assert_equal 0, context[:lookahead_latin_name] end end describe '#selections' do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { name similarSpecies { likesWater: isWaterfowl } } } GRAPHQL } def query(doc = document) GraphQL::Query.new(LookaheadTest::Schema, document: doc) end it "provides a list of all selections" do ast_node = document.definitions.first.selections.first field = LookaheadTest::Query.fields["findBirdSpecies"] lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field) assert_equal [:name, :similar_species], lookahead.selections.map(&:name) end it "filters outs selections which do not match arguments" do ast_node = document.definitions.first lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query) arguments = { by_name: "Cardinal" } assert_equal lookahead.selections(arguments: arguments).map(&:name), [] end it "includes selections which match arguments" do ast_node = document.definitions.first lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query) arguments = { by_name: "Laughing Gull" } assert_equal lookahead.selections(arguments: arguments).map(&:name), [:find_bird_species] end it 'handles duplicate selections across fragments' do doc = GraphQL.parse <<-GRAPHQL query { ... on Query { ...MoreFields } } fragment MoreFields on Query { findBirdSpecies(byName: "Laughing Gull") { name } findBirdSpecies(byName: "Laughing Gull") { ...EvenMoreFields } } fragment EvenMoreFields on BirdSpecies { similarSpecies { likesWater: isWaterfowl } } GRAPHQL lookahead = query(doc).lookahead root_selections = lookahead.selections assert_equal [:find_bird_species], root_selections.map(&:name), "Selections are merged" assert_equal 2, root_selections.first.ast_nodes.size, "It represents both nodes" assert_equal [:name, :similar_species], root_selections.first.selections.map(&:name), "Subselections are merged" end it "avoids merging selections for same field name on distinct types" do query = GraphQL::Query.new(LookaheadTest::Schema, <<-GRAPHQL) query { node(id: "Cardinal") { ... on BirdSpecies { name } ... on BirdGenus { name } id } } GRAPHQL node_lookahead = query.lookahead.selection("node") assert_equal( [[LookaheadTest::Node, :id], [LookaheadTest::BirdSpecies, :name], [LookaheadTest::BirdGenus, :name]], node_lookahead.selections.map { |s| [s.owner_type, s.name] } ) end it "works for missing selections" do ast_node = document.definitions.first.selections.first field = LookaheadTest::Query.fields["findBirdSpecies"] lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field) null_lookahead = lookahead.selection(:genus) # This is an implementation detail, but I want to make sure the test is set up right assert_instance_of GraphQL::Execution::Lookahead::NullLookahead, null_lookahead assert_equal [], null_lookahead.selections end it "excludes fields skipped by directives" do document = GraphQL.parse <<-GRAPHQL query($skipName: Boolean!, $includeGenus: Boolean!){ findBirdSpecies(byName: "Cardinal") { id name @skip(if: $skipName) genus @include(if: $includeGenus) } } GRAPHQL query = GraphQL::Query.new(LookaheadTest::Schema, document: document, variables: { skipName: false, includeGenus: true }) lookahead = query.lookahead.selection("findBirdSpecies") assert_equal [:id, :name, :genus], lookahead.selections.map(&:name) assert_equal true, lookahead.selects?(:name) query = GraphQL::Query.new(LookaheadTest::Schema, document: document, variables: { skipName: true, includeGenus: false }) lookahead = query.lookahead.selection("findBirdSpecies") assert_equal [:id], lookahead.selections.map(&:name) assert_equal false, lookahead.selects?(:name) end end def assert_selection_exists(selection) assert GraphQL::Execution::Lookahead::NULL_LOOKAHEAD != selection end def assert_selection_is_null(selection) assert_equal GraphQL::Execution::Lookahead::NULL_LOOKAHEAD, selection end describe "#selection" do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { name similarSpecies { likesWater: isWaterfowl } } } GRAPHQL } def query(doc = document) GraphQL::Query.new(LookaheadTest::Schema, document: doc) end it "returns selection by field name" do ast_node = document.definitions.first.selections.first field = LookaheadTest::Query.fields["findBirdSpecies"] lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field) assert_selection_exists lookahead.selection("similarSpecies") end describe "when same field is selected twice" do let(:document) { GraphQL.parse <<-GRAPHQL query { gull: findBirdSpecies(byName: "Laughing Gull") { name } tanager: findBirdSpecies(byName: "Scarlet Tanager") { name } } GRAPHQL } let(:graphql_query) do GraphQL::Query.new(LookaheadTest::Schema, document: document) end it "returns lookahead with two ast_nodes" do assert_equal 2, graphql_query.lookahead.selection("findBirdSpecies").ast_nodes.length end end describe "when query has alias" do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { name similar: similarSpecies { likesWater: isWaterfowl } } } GRAPHQL } let(:graphql_query) do GraphQL::Query.new(LookaheadTest::Schema, document: document) end let(:species_lookahead) do graphql_query.lookahead.selection("findBirdSpecies") end it "returns selection when field name is passed" do assert_selection_exists species_lookahead.selection("similarSpecies") end it "returns null when alias name is passed" do assert_selection_is_null species_lookahead.selection("similar") end describe "when alias has arguments" do let(:document) { GraphQL.parse <<-GRAPHQL query { gull: findBirdSpecies(byName: "Laughing Gull") { name } } GRAPHQL } it "returns selection when field name is passed" do assert_selection_exists graphql_query.lookahead.selection("findBirdSpecies") end it "returns null when alias name is passed" do assert_selection_is_null graphql_query.lookahead.selection("gull") end describe "when same field is selected twice" do let(:document) { GraphQL.parse <<-GRAPHQL query { gull: findBirdSpecies(byName: "Laughing Gull") { name } tanager: findBirdSpecies(byName: "Scarlet Tanager") { name } } GRAPHQL } it "returns null when alias name is passed" do assert_selection_is_null graphql_query.lookahead.selection("gull") assert_selection_is_null graphql_query.lookahead.selection("tanager") end end end end end describe "#alias_selection" do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { name similar: similarSpecies { likesWater: isWaterfowl } } } GRAPHQL } def query(doc = document) GraphQL::Query.new(LookaheadTest::Schema, document: doc) end let(:graphql_query) do GraphQL::Query.new(LookaheadTest::Schema, document: document) end let(:species_lookahead) do graphql_query.lookahead.selection("findBirdSpecies") end describe "when alias name is passed" do it "returns selection" do assert_selection_exists species_lookahead.alias_selection("similar") end it "returns true from selects_alias?" do assert true, species_lookahead.selects_alias?("similar") end describe "when the aliased field is deeply nested" do it "not finds the deeply-nested alias" do assert_equal [:name, :similar_species], species_lookahead.selections.map(&:name) assert_equal false, species_lookahead.selects_alias?("likesWater") end end end describe "when the same field is executed with the same arguments but different aliases" do let(:document) { GraphQL.parse <<-GRAPHQL query { egret: findBirdSpecies(byName: "Great Egret") { isWaterfowl } otherEgret: findBirdSpecies(byName: "Great Egret") { name } findBirdSpecies(byName: "Great Egret") { __typename } } GRAPHQL } it "distinguishes between the aliased fields" do lookahead = query.lookahead assert_equal [:is_waterfowl], lookahead.alias_selection("egret").selections.map(&:name) assert_equal [:name], lookahead.alias_selection("otherEgret").selections.map(&:name) assert_equal [], lookahead.alias_selection("findBirdSpecies").selections.map(&:name) end it "filters aliased fields by arguments" do lookahead = query.lookahead # No `arguments:` performs no filtering assert_equal [:is_waterfowl], lookahead.alias_selection("egret").selections.map(&:name) # Matching arguments filters to the expected field: assert_equal [:is_waterfowl], lookahead.alias_selection("egret", arguments: {by_name: "Great Egret"}).selections.map(&:name) # Empty `arguments:` matches nothing: assert_equal [], lookahead.alias_selection("egret", arguments: {}).selections.map(&:name) # Mismatching `arguments:` filters to nothing: assert_equal [], lookahead.alias_selection("egret", arguments: {by_name: "Macaw"}).selections.map(&:name) end end describe "when field name is passed" do it "returns null_lookahead" do assert_selection_is_null species_lookahead.alias_selection("similarSpecies") end it "returns false from selects_alias?" do assert_equal false, species_lookahead.selects_alias?("similarSpecies") end end describe "when alias is inside fragment" do let(:document) { GraphQL.parse <<-GRAPHQL fragment BirdSpeciesFragment on BirdSpecies { name similar: similarSpecies { likesWater: isWaterfowl } } query { findBirdSpecies(byName: "Laughing Gull") { ...BirdSpeciesFragment } } GRAPHQL } it "returns selection" do assert_selection_exists species_lookahead.alias_selection("similar") end it "returns true from selects_alias?" do assert true, species_lookahead.selects_alias?("similar") end describe "when fragment name is wrong" do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { ...WrongFragment } } GRAPHQL } it "raises error" do assert_raises(RuntimeError) { species_lookahead.selects_alias?("similar") } end end end describe "when alias is inside inline fragment" do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { ...on BirdSpecies { name similar: similarSpecies { likesWater: isWaterfowl } } } } GRAPHQL } it "returns selection" do assert_selection_exists species_lookahead.alias_selection("similar") end it "returns true from selects_alias?" do assert true, species_lookahead.selects_alias?("similar") end end describe "when alias has arguments" do let(:document) { GraphQL.parse <<-GRAPHQL query { gull: findBirdSpecies(byName: "Laughing Gull") { name } } GRAPHQL } it "returns selection" do assert_selection_exists graphql_query.lookahead.alias_selection("gull") end it "returns true from selects_alias?" do assert true, graphql_query.lookahead.selects_alias?("gull") end describe "when same field is selected twice" do let(:document) { GraphQL.parse <<-GRAPHQL query { gull: findBirdSpecies(byName: "Laughing Gull") { name } tanager: findBirdSpecies(byName: "Scarlet Tanager") { name } } GRAPHQL } it "returns selection when alias name is passed" do graphql_query.lookahead.alias_selection("gull", arguments: { by_name: "Laughing Gull" }).tap do |selection| assert_selection_exists selection assert_equal({ by_name: "Laughing Gull" }, selection.arguments) assert_equal 1, selection.ast_nodes.length end graphql_query.lookahead.alias_selection("tanager", arguments: { by_name: "Scarlet Tanager" }).tap do |selection| assert_selection_exists selection assert_equal({ by_name: "Scarlet Tanager" }, selection.arguments) assert_equal 1, selection.ast_nodes.length end end end end end end graphql-ruby-2.2.17/spec/graphql/execution/multiplex_spec.rb000066400000000000000000000174161476434635200242240ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Multiplex do def multiplex(*a, **kw) LazyHelpers::LazySchema.multiplex(*a, **kw) end let(:q1) { <<-GRAPHQL query Q1 { nestedSum(value: 3) { value nestedSum(value: 7) { value } } } GRAPHQL } let(:q2) { <<-GRAPHQL query Q2 { nestedSum(value: 2) { value nestedSum(value: 11) { value } } } GRAPHQL } let(:q3) { <<-GRAPHQL query Q3 { listSum(values: [1,2]) { nestedSum(value: 3) { value } } } GRAPHQL } let(:queries) { [{query: q1}, {query: q2}, {query: q3}] } describe "multiple queries in the same lazy context" do it "runs multiple queries in the same lazy context" do expected_data = [ {"data"=>{"nestedSum"=>{"value"=>14, "nestedSum"=>{"value"=>46}}}}, {"data"=>{"nestedSum"=>{"value"=>14, "nestedSum"=>{"value"=>46}}}}, {"data"=>{"listSum"=>[{"nestedSum"=>{"value"=>14}}, {"nestedSum"=>{"value"=>14}}]}}, ] res = multiplex(queries) assert_equal expected_data, res end it "returns responses in the same order as their respective requests" do queries = 2000.times.map do |index| case index % 3 when 0 {query: q1} when 1 {query: q2} when 2 {query: q3} end end responses = multiplex(queries) responses.each.with_index do |response, index| case index % 3 when 0 assert_equal "Q1", response.query.operation_name when 1 assert_equal "Q2", response.query.operation_name when 2 assert_equal "Q3", response.query.operation_name end end end end describe "when some have validation errors or runtime errors" do let(:q1) { " { success: nullableNestedSum(value: 1) { value } }" } let(:q2) { " { runtimeError: nullableNestedSum(value: 13) { value } }" } let(:q3) { "{ invalidNestedNull: nullableNestedSum(value: 1) { value nullableNestedSum(value: 2) { nestedSum(value: 13) { value } # This field will never get executed ns2: nestedSum(value: 13) { value } } } }" } let(:q4) { " { validationError: nullableNestedSum(value: true) }"} it "returns a mix of errors and values" do expected_res = [ { "data"=>{"success"=>{"value"=>2}} }, { "data"=>{"runtimeError"=>nil}, "errors"=>[{ "message"=>"13 is unlucky", "locations"=>[{"line"=>1, "column"=>4}], "path"=>["runtimeError"] }] }, { "data"=>{"invalidNestedNull"=>{"value" => 2,"nullableNestedSum" => nil}}, "errors"=>[{"message"=>"Cannot return null for non-nullable field LazySum.nestedSum"}], }, { "errors" => [{ "message"=>"Field must have selections (field 'nullableNestedSum' returns LazySum but has no selections. Did you mean 'nullableNestedSum { ... }'?)", "locations"=>[{"line"=>1, "column"=>4}], "path"=>["query", "validationError"], "extensions"=>{"code"=>"selectionMismatch", "nodeName"=>"field 'nullableNestedSum'", "typeName"=>"LazySum"} }] }, ] res = multiplex([ {query: q1}, {query: q2}, {query: q3}, {query: q4}, ]) assert_equal expected_res, res.map(&:to_h) end end describe "context shared by a multiplex run" do it "is provided as context:" do checks = [] multiplex(queries, context: { instrumentation_checks: checks }) assert_equal ["before multiplex 1", "before multiplex 2", "after multiplex 2", "after multiplex 1"], checks end end describe "instrumenting a multiplex run" do it "runs query instrumentation for each query and multiplex-level instrumentation" do checks = [] queries_with_context = queries.map { |q| q.merge(context: { instrumentation_checks: checks }) } multiplex(queries_with_context, context: { instrumentation_checks: checks }) assert_equal [ "before multiplex 1", "before multiplex 2", "before Q1", "before Q2", "before Q3", "after Q3", "after Q2", "after Q1", "after multiplex 2", "after multiplex 1", ], checks end end describe "max_complexity" do it "can successfully calculate complexity" do message = "Query has complexity of 11, which exceeds max complexity of 10" results = multiplex(queries, max_complexity: 10) results.each do |res| assert_equal message, res["errors"][0]["message"] end end end describe "execute_query when errors are raised" do module InspectQueryInstrumentation def execute_multiplex(multiplex:) super ensure InspectQueryInstrumentation.last_json = multiplex.queries.first.result.to_json end class << self attr_accessor :last_json end end class InspectSchema < GraphQL::Schema class Query < GraphQL::Schema::Object field :raise_execution_error, String def raise_execution_error raise GraphQL::ExecutionError, "Whoops" end field :raise_error, String def raise_error raise GraphQL::Error, "Crash" end field :raise_syntax_error, String def raise_syntax_error raise SyntaxError end field :raise_exception, String def raise_exception raise Exception end end query(Query) trace_with(InspectQueryInstrumentation) end unhandled_err_json = '{}' it "can access the query results" do InspectSchema.execute("{ raiseExecutionError }") handled_err_json = '{"errors":[{"message":"Whoops","locations":[{"line":1,"column":3}],"path":["raiseExecutionError"]}],"data":{"raiseExecutionError":null}}' assert_equal handled_err_json, InspectQueryInstrumentation.last_json assert_raises(GraphQL::Error) do InspectSchema.execute("{ raiseError }") end assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json end it "can access the query results when the error is not a StandardError" do assert_raises(SyntaxError) do InspectSchema.execute("{ raiseSyntaxError }") end assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json assert_raises(Exception) do InspectSchema.execute("{ raiseException }") end assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json end end describe "context[:trace]" do class MultiplexTraceSchema < GraphQL::Schema class Query < GraphQL::Schema::Object field :int, Integer def int; 1; end end class Trace < GraphQL::Tracing::Trace def execute_multiplex(multiplex:) @execute_multiplex_count ||= 0 @execute_multiplex_count += 1 super end def execute_query(query:) @execute_query_count ||= 0 @execute_query_count += 1 super end attr_reader :execute_multiplex_count, :execute_query_count end query(Query) end it "uses it instead of making a new trace" do query_str = "{ int }" trace_instance = MultiplexTraceSchema::Trace.new res = MultiplexTraceSchema.multiplex([{query: query_str}, {query: query_str}], context: { trace: trace_instance }) assert_equal [1, 1], res.map { |r| r["data"]["int"]} assert_equal 1, trace_instance.execute_multiplex_count assert_equal 2, trace_instance.execute_query_count end end end graphql-ruby-2.2.17/spec/graphql/execution_error_spec.rb000066400000000000000000000342301476434635200234030ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::ExecutionError do let(:result) { Dummy::Schema.execute(query_string) } describe "when returned from a field" do let(:query_string) {%| { cheese(id: 1) { id error1: similarCheese(source: [YAK]) { ... similarCheeseFields } error2: similarCheese(source: [YAK]) { ... similarCheeseFields } nonError: similarCheese(source: [SHEEP]) { ... similarCheeseFields } flavor } allDairy { ... on Cheese { flavor } ... on Milk { source executionError } } dairyErrors: allDairy(executionErrorAtIndex: 1) { __typename } dairy { milks { source executionError allDairy { __typename ... on Milk { origin executionError } } } } executionError valueWithExecutionError } fragment similarCheeseFields on Cheese { id, flavor } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{ "dairy" => { "milks" => [ { "source" => "COW", "executionError" => nil, "allDairy" => [ { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil } ] } ] }, "executionError" => nil, "valueWithExecutionError" => 0, "cheese"=>{ "id" => 1, "flavor" => "Brie", "error1"=> nil, "error2"=> nil, "nonError"=> { "id" => 3, "flavor" => "Manchego", }, }, "allDairy" => [ { "flavor" => "Brie" }, { "flavor" => "Gouda" }, { "flavor" => "Manchego" }, { "source" => "COW", "executionError" => nil } ], "dairyErrors" => [ { "__typename" => "Cheese" }, nil, { "__typename" => "Cheese" }, { "__typename" => "Milk" } ], }, "errors"=>[ { "message"=>"There was an execution error", "locations"=>[{"line"=>41, "column"=>5}], "path"=>["executionError"] }, { "message"=>"Could not fetch latest value", "locations"=>[{"line"=>42, "column"=>5}], "path"=>["valueWithExecutionError"] }, { "message"=>"missing dairy", "locations"=>[{"line"=>25, "column"=>5}], "path"=>["dairyErrors", 1] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>31, "column"=>9}], "path"=>["dairy", "milks", 0, "executionError"] }, { "message"=>"No cheeses are made from Yak milk!", "locations"=>[{"line"=>5, "column"=>7}], "path"=>["cheese", "error1"] }, { "message"=>"No cheeses are made from Yak milk!", "locations"=>[{"line"=>8, "column"=>7}], "path"=>["cheese", "error2"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>22, "column"=>9}], "path"=>["allDairy", 3, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>36, "column"=>13}], "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"] }, ] } assert_equal(expected_result, result.to_h) end end describe "named query when returned from a field" do let(:query_string) {%| query MilkQuery { dairy { milks { source executionError allDairy { __typename ... on Milk { origin executionError } } } } } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{ "dairy" => { "milks" => [ { "source" => "COW", "executionError" => nil, "allDairy" => [ { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil } ] } ] } }, "errors"=>[ { "message"=>"There was an execution error", "locations"=>[{"line"=>6, "column"=>11}], "path"=>["dairy", "milks", 0, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>11, "column"=>15}], "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"] } ] } assert_equal(expected_result, result) end end describe "minimal lazy non-error case" do let(:query_string) {%| { cheese(id: 1) { nonError: similarCheese(source: [SHEEP]) { id } } } |} it "does lazy non-errors right" do # This is extracted from the test above -- it kept breaking # when working on dataloader, so I isolated it to keep an eye # on the minimal reproduction # # It's `def self.authorized?` is lazy, and it requires # _both_ a lazy resolution and a dataloader run # in order to resolve properly. expected_result = { "data"=>{ "cheese"=>{ "nonError"=> { "id" => 3, }, }, } } assert_equal(expected_result, result.to_h) end end describe "fragment query when returned from a field" do let(:query_string) {%| query MilkQuery { dairy { ...Dairy } } fragment Dairy on Dairy { milks { source executionError allDairy { __typename ...Milk } } } fragment Milk on Milk { origin executionError } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{ "dairy" => { "milks" => [ { "source" => "COW", "executionError" => nil, "allDairy" => [ { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil } ] } ] } }, "errors"=>[ { "message"=>"There was an execution error", "locations"=>[{"line"=>11, "column"=>9}], "path"=>["dairy", "milks", 0, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>21, "column"=>7}], "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"] } ] } assert_equal(expected_result, result) end end describe "options in ExecutionError" do let(:query_string) {%| { executionErrorWithOptions } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{"executionErrorWithOptions"=>nil}, "errors"=> [{"message"=>"Permission Denied!", "locations"=>[{"line"=>3, "column"=>7}], "path"=>["executionErrorWithOptions"], "code"=>"permission_denied"}] } assert_equal(expected_result, result) end end describe "extensions in ExecutionError" do let(:query_string) {%| { executionErrorWithExtensions } |} it "the error is inserted into the errors key with custom data set in `extensions`" do expected_result = { "data"=>{"executionErrorWithExtensions"=>nil}, "errors"=> [{"message"=>"Permission Denied!", "locations"=>[{"line"=>3, "column"=>7}], "path"=>["executionErrorWithExtensions"], "extensions"=>{"code"=>"permission_denied"}}] } assert_equal(expected_result, result) end end describe "more than one ExecutionError" do let(:query_string) { %|{ multipleErrorsOnNonNullableField} |} it "the errors are inserted into the errors key and the data is nil even for a NonNullable field" do expected_result = { "data"=>nil, "errors"=> [{"message"=>"This is an error message for some error.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableField"]}, {"message"=>"This is another error message for a different error.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableField"]}], } assert_equal(expected_result, result) end describe "more than one ExecutionError on a field defined to return a list" do let(:query_string) { %|{ multipleErrorsOnNonNullableListField} |} it "the errors are inserted into the errors key and the data is nil even for a NonNullable field" do expected_result = { "data"=>{"multipleErrorsOnNonNullableListField"=>[nil, nil]}, "errors"=> [{"message"=>"The first error message for a field defined to return a list of strings.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableListField", 0]}, {"message"=>"The second error message for a field defined to return a list of strings.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableListField", 1]}], } assert_equal(expected_result, result) end end end it "supports arrays containing only execution errors for list fields" do schema = GraphQL::Schema.from_definition <<-GRAPHQL type Query { testArray: [String]! } GRAPHQL root_value = OpenStruct.new(testArray: [GraphQL::ExecutionError.new("boom!"), GraphQL::ExecutionError.new("bang!"), "OK"]) result = schema.execute("{ testArray }", root_value: root_value) assert_equal({ "testArray" => [nil, nil, "OK"]}, result["data"]) expected_errors = [ { "message"=>"boom!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["testArray", 0] }, { "message"=>"bang!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["testArray", 1] } ] assert_equal(expected_errors, result["errors"]) root_value_errors_only = OpenStruct.new(testArray: [GraphQL::ExecutionError.new("zing!"), GraphQL::ExecutionError.new("fizz!")]) result = schema.execute("{ testArray }", root_value: root_value_errors_only) assert_equal({ "testArray" => [nil, nil] }, result["data"]) expected_errors = [ { "message"=>"zing!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["testArray", 0] }, { "message"=>"fizz!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["testArray", 1] } ] assert_equal(expected_errors, result["errors"]) end describe "when using DataLoaders" do let(:schema) do item_error_loader = Class.new(GraphQL::Dataloader::Source) do def fetch(keys) keys.map { |key| GraphQL::ExecutionError.new("Error for #{key}") } end end query_type = Class.new(GraphQL::Schema::Object) do graphql_name "Query" field :item, String do argument :key, String end define_method(:item) do |key:| dataloader.with(item_error_loader).load(key) end end Class.new(GraphQL::Schema) do query query_type use GraphQL::Dataloader end end let(:result) { schema.execute(query_string) } describe "when querying for unique items" do let(:query_string) { <<-GRAPHQL query { query0: item(key: "a") query1: item(key: "b") } GRAPHQL } it "returns unique execution errors locations and paths" do expected_result = { "data" => { "query0" => nil, "query1" => nil }, "errors" => [ { "message" => "Error for a", "locations" => [{"line" => 2, "column" => 13}], "path" => ["query0"] }, { "message" => "Error for b", "locations" => [{"line" => 3, "column" => 13}], "path" => ["query1"] } ] } assert_equal(expected_result, result.to_h) end end describe "when querying for duplicate items" do let(:query_string) { <<-GRAPHQL query { query0: item(key: "a") query1: item(key: "a") } GRAPHQL } it "returns execution errors for duplicate items" do expected_result = { "data" => { "query0" => nil, "query1" => nil }, "errors" => [ { "message" => "Error for a", "locations" => [{"line" => 2, "column" => 13}], "path" => ["query0"] }, { "message" => "Error for a", "locations" => [{"line" => 3, "column" => 13}], "path" => ["query1"] } ] } assert_equal(expected_result, result.to_h) end end end end graphql-ruby-2.2.17/spec/graphql/introspection/000077500000000000000000000000001476434635200215265ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/introspection/directive_type_spec.rb000066400000000000000000000107511476434635200261100ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::DirectiveType do let(:query_string) {%| query getDirectives { __schema { directives { name, args { name, type { kind, name, ofType { name } } }, locations isRepeatable # Deprecated fields: onField onFragment onOperation } } } |} let(:directive_with_deprecated_arg) do Class.new(GraphQL::Schema::Directive) do graphql_name "customTransform" locations GraphQL::Schema::Directive::FIELD argument :old_way, String, required: false, deprecation_reason: "Use the newWay" argument :new_way, String, required: false end end let(:schema) { Class.new(Dummy::Schema) { directive(Class.new(GraphQL::Schema::Directive) { graphql_name("doStuff"); repeatable(true) })}} let(:result) { schema.execute(query_string) } before do schema.max_depth(100) end it "shows directive info " do expected = { "data" => { "__schema" => { "directives" => [ { "name" => "include", "args" => [ {"name"=>"if", "type"=>{"kind"=>"NON_NULL", "name"=>nil, "ofType"=>{"name"=>"Boolean"}}} ], "locations"=>["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "isRepeatable" => false, "onField" => true, "onFragment" => true, "onOperation" => false, }, { "name" => "skip", "args" => [ {"name"=>"if", "type"=>{"kind"=>"NON_NULL", "name"=>nil, "ofType"=>{"name"=>"Boolean"}}} ], "locations"=>["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "isRepeatable" => false, "onField" => true, "onFragment" => true, "onOperation" => false, }, { "name" => "deprecated", "args" => [ {"name"=>"reason", "type"=>{"kind"=>"SCALAR", "name"=>"String", "ofType"=>nil}} ], "locations"=>["FIELD_DEFINITION", "ENUM_VALUE", "ARGUMENT_DEFINITION", "INPUT_FIELD_DEFINITION"], "isRepeatable" => false, "onField" => false, "onFragment" => false, "onOperation" => false, }, { "name" => "oneOf", "args" => [], "locations"=>["INPUT_OBJECT"], "isRepeatable" => false, "onField" => false, "onFragment" => false, "onOperation" => false, }, { "name" => "specifiedBy", "args" => [ {"name"=>"url", "type"=>{"kind"=>"NON_NULL", "name"=>nil, "ofType"=>{"name"=>"String"}}} ], "locations"=>["SCALAR"], "isRepeatable" => false, "onField" => false, "onFragment" => false, "onOperation" => false, }, { "name"=>"doStuff", "args"=>[], "locations"=>[], "isRepeatable"=>true, "onField"=>false, "onFragment"=>false, "onOperation"=>false, } ] } }} assert_equal(expected, result) end it "hides deprecated arguments by default" do schema.directive(directive_with_deprecated_arg) result = schema.execute <<-GRAPHQL { __schema { directives { name args { name } } } } GRAPHQL directive_result = result["data"]["__schema"]["directives"].find { |d| d["name"] == "customTransform" } expected = [ {"name" => "newWay"} ] assert_equal(expected, directive_result["args"]) end it "can expose deprecated arguments" do schema.directive(directive_with_deprecated_arg) result = schema.execute <<-GRAPHQL { __schema { directives { name args(includeDeprecated: true) { name isDeprecated deprecationReason } } } } GRAPHQL directive_result = result["data"]["__schema"]["directives"].find { |d| d["name"] == "customTransform" } expected = [ {"name" => "oldWay", "isDeprecated" => true, "deprecationReason" => "Use the newWay"}, {"name" => "newWay", "isDeprecated" => false, "deprecationReason" => nil} ] assert_equal(expected, directive_result["args"]) end end graphql-ruby-2.2.17/spec/graphql/introspection/entry_points_spec.rb000066400000000000000000000031121476434635200256170ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::EntryPoints do describe "#__type" do let(:schema) do nested_invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'NestedInvisible' field :foo, String, null: false end invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Invisible' field :foo, String, null: false field :nested_invisible, nested_invisible_type, null: false def self.visible?(context) false end end visible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Visible' field :foo, String, null: false end query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foo, String, null: false field :invisible, invisible_type, null: false field :visible, visible_type, null: false end Class.new(GraphQL::Schema) do query query_type end end let(:query_string) {%| query getType($name: String!) { __type(name: $name) { name } } |} it "returns reachable types" do result = schema.execute(query_string, variables: { name: 'Visible' }) type_name = result['data']['__type']['name'] assert_equal('Visible', type_name) end it "returns nil for unreachable types" do result = schema.execute(query_string, variables: { name: 'NestedInvisible' }) type_name = result['data']['__type'] assert_nil(type_name) end end end graphql-ruby-2.2.17/spec/graphql/introspection/input_value_type_spec.rb000066400000000000000000000066701476434635200264720ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::InputValueType do let(:query_string) {%| { __type(name: "DairyProductInput") { name description kind inputFields { name type { kind, name } defaultValue description } } } |} let(:result) { Dummy::Schema.execute(query_string) } it "exposes metadata about input objects, giving extra quotes for strings" do expected = { "data" => { "__type" => { "name"=>"DairyProductInput", "description"=>"Properties for finding a dairy product", "kind"=>"INPUT_OBJECT", "inputFields"=>[ {"name"=>"source", "type"=>{"kind"=>"NON_NULL", "name" => nil}, "defaultValue"=>nil, "description" => "Where it came from"}, {"name"=>"originDairy", "type"=>{"kind"=>"SCALAR", "name" => "String"}, "defaultValue"=>"\"Sugar Hollow Dairy\"", "description" => "Dairy which produced it"}, {"name"=>"fatContent", "type"=>{"kind"=>"SCALAR", "name" => "Float"}, "defaultValue"=>"0.3", "description" => "How much fat it has"}, {"name"=>"organic", "type"=>{"kind"=>"SCALAR", "name" => "Boolean"}, "defaultValue"=>"false", "description" => nil}, {"name"=>"order_by", "type"=>{"kind"=>"INPUT_OBJECT", "name"=>"ResourceOrderType"}, "defaultValue"=>"{direction: \"ASC\"}", "description" => nil}, ] } }} assert_equal(expected, result.to_h) end let(:cheese_type) { Dummy::Schema.execute(%| { __type(name: "Cheese") { fields { name args { name defaultValue } } } } |) } it "converts default values to GraphQL values" do field = cheese_type['data']['__type']['fields'].detect { |f| f['name'] == 'similarCheese' } arg = field['args'].detect { |a| a['name'] == 'nullableSource' } assert_equal('[COW]', arg['defaultValue']) end it "supports list of enum default values" do schema = GraphQL::Schema.from_definition(%| type Query { hello(enums: [MyEnum] = [A, B]): String } enum MyEnum { A B } |) result = schema.execute(%| { __type(name: "Query") { fields { args { defaultValue } } } } |) expected = { "data" => { "__type" => { "fields" => [{ "args" => [{ "defaultValue" => "[A, B]" }] }] } } } assert_equal expected, result end it "supports null default values" do schema = GraphQL::Schema.from_definition(%| type Query { hello(person: Person): String } input Person { firstName: String! lastName: String = null } |) result = schema.execute(%| { __type(name: "Person") { inputFields { name defaultValue } } } |) expected = { "data" => { "__type" => { "inputFields" => [ { "name" => "firstName", "defaultValue" => nil}, { "name" => "lastName", "defaultValue" => "null"} ] } } } assert_equal expected, result end end graphql-ruby-2.2.17/spec/graphql/introspection/introspection_query_spec.rb000066400000000000000000000030501476434635200272100ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Introspection::INTROSPECTION_QUERY" do let(:schema) { Class.new(Dummy::Schema) do max_depth(15) end } let(:query_string) { GraphQL::Introspection::INTROSPECTION_QUERY } let(:result) { schema.execute(query_string) } it "runs" do assert(result["data"]) end it "is limited to the max query depth" do query_type = Class.new(GraphQL::Schema::Object) do graphql_name "DeepQuery" field :foo, [[[Float]]], null: false end deep_schema = Class.new(GraphQL::Schema) do query query_type end result = deep_schema.execute(query_string) assert(GraphQL::Schema::Loader.load(result)) end it "doesn't handle too deeply nested (< 8) schemas" do query_type = Class.new(GraphQL::Schema::Object) do graphql_name "DeepQuery" field :foo, [[[[Float]]]], null: false end deep_schema = Class.new(GraphQL::Schema) do query query_type end result = deep_schema.execute(query_string) assert_raises(KeyError) { GraphQL::Schema::Loader.load(result) } end it "doesn't contain blank lines" do int_query = GraphQL::Introspection.query refute_includes int_query, "\n\n" int_query_with_options = GraphQL::Introspection.query( include_deprecated_args: true, include_schema_description: true, include_is_repeatable: true, include_specified_by_url: true, include_is_one_of: true ) refute_includes int_query_with_options, "\n\n" end end graphql-ruby-2.2.17/spec/graphql/introspection/schema_type_spec.rb000066400000000000000000000132461476434635200253740ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::SchemaType do let(:schema) { Class.new(Dummy::Schema) { description("Cool schema") }} let(:query_string) {%| query getSchema { __schema { description types { name } queryType { fields { name }} mutationType { fields { name }} } } |} let(:result) { schema.execute(query_string) } it "exposes the schema" do expected = { "data" => { "__schema" => { "description" => "Cool schema", "types" => schema.types.values.sort_by(&:graphql_name).map { |t| t.graphql_name.nil? ? (p t; raise("no name for #{t}")) : {"name" => t.graphql_name} }, "queryType"=>{ "fields"=>[ {"name"=>"allAnimal"}, {"name"=>"allAnimalAsCow"}, {"name"=>"allDairy"}, {"name"=>"allEdible"}, {"name"=>"allEdibleAsMilk"}, {"name"=>"cheese"}, {"name"=>"cow"}, {"name"=>"dairy"}, {"name"=>"deepNonNull"}, {"name"=>"error"}, {"name"=>"executionError"}, {"name"=>"executionErrorWithExtensions"}, {"name"=>"executionErrorWithOptions"}, {"name"=>"favoriteEdible"}, {"name"=>"fromSource"}, {"name"=>"hugeInteger"}, {"name"=>"maybeNull"}, {"name"=>"milk"}, {"name"=>"multipleErrorsOnNonNullableField"}, {"name"=>"multipleErrorsOnNonNullableListField"}, {"name"=>"root"}, {"name"=>"searchDairy"}, {"name"=>"tracingScalar"}, {"name"=>"valueWithExecutionError"}, ] }, "mutationType"=> { "fields"=>[ {"name"=>"pushValue"}, {"name"=>"replaceValues"}, ] }, } }} assert_equal(expected, result.to_h) end describe "when the schema has types that are only reachable through hidden types" do let(:schema) do nested_invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'NestedInvisible' field :foo, String, null: false end invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Invisible' field :foo, String, null: false field :nested_invisible, nested_invisible_type, null: false def self.visible?(context) false end end invisible_input_type = Class.new(GraphQL::Schema::InputObject) do graphql_name 'InvisibleInput' argument :foo, String, required: false def self.visible?(context) false end end visible_input_type = Class.new(GraphQL::Schema::InputObject) do graphql_name 'VisibleInput' argument :foo, String, required: false end visible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Visible' field :foo, String, null: false end invisible_orphan_type = Class.new(GraphQL::Schema::Object) do graphql_name 'InvisibleOrphan' field :foo, String, null: false def self.visible?(context) false end end query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foo, String, null: false field :invisible, invisible_type, null: false field :visible, visible_type, null: false field :with_invisible_args, String, null: false do argument :invisible, invisible_input_type, required: false argument :visible, visible_input_type end end Class.new(GraphQL::Schema) do query query_type orphan_types invisible_orphan_type end end let(:query_string) {%| query getSchema { __schema { types { name } } } |} it "only returns reachable types" do expected_types = [ 'Boolean', 'Query', 'String', 'Visible', 'VisibleInput', '__Directive', '__DirectiveLocation', '__EnumValue', '__Field', '__InputValue', '__Schema', '__Type', '__TypeKind' ] types = result['data']['__schema']['types'].map { |type| type.fetch('name') } assert_equal(expected_types, types) end end describe "when the schema has hidden directives" do let(:schema) do invisible_directive = Class.new(GraphQL::Schema::Directive) do graphql_name 'invisibleDirective' locations(GraphQL::Schema::Directive::QUERY) argument(:val, Integer, "Initial integer value.", required: false) def self.visible?(context) false end end visible_directive = Class.new(GraphQL::Schema::Directive) do graphql_name 'visibleDirective' locations(GraphQL::Schema::Directive::QUERY) argument(:val, Integer, "Initial integer value.", required: false) def self.visible?(context) true end end query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foo, String, null: false end Class.new(GraphQL::Schema) do query query_type directives invisible_directive, visible_directive end end let(:query_string) {%| query getSchema { __schema { directives { name } } } |} it "only returns visible directives" do expected_dirs = ['deprecated', 'include', 'skip', 'oneOf', 'specifiedBy', 'visibleDirective'] directives = result['data']['__schema']['directives'].map { |dir| dir.fetch('name') } assert_equal(expected_dirs.sort, directives.sort) end end end graphql-ruby-2.2.17/spec/graphql/introspection/type_type_spec.rb000066400000000000000000000224471476434635200251200ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::TypeType do let(:query_string) {%| query introspectionQuery { cheeseType: __type(name: "Cheese") { name, kind, fields { name, isDeprecated, type { kind, name, ofType { name } } } } milkType: __type(name: "Milk") { interfaces { name }, fields { type { kind, name, ofType { name } } } } dairyAnimal: __type(name: "DairyAnimal") { name, kind, enumValues(includeDeprecated: false) { name, isDeprecated } } dairyProduct: __type(name: "DairyProduct") { name, kind, possibleTypes { name } } animalProduct: __type(name: "AnimalProduct") { name, kind, specifiedByURL, possibleTypes { name }, fields { name } } missingType: __type(name: "NotAType") { name } timeType: __type(name: "Time") { specifiedByURL } } |} let(:result) { Dummy::Schema.execute(query_string, context: {}, variables: {"cheeseId" => 2}) } let(:cheese_fields) {[ {"name"=>"dairyProduct", "isDeprecated" => false, "type"=>{"kind"=>"UNION", "name"=>"DairyProduct", "ofType"=>nil}}, {"name"=>"deeplyNullableCheese", "isDeprecated" => false, "type"=>{ "kind" => "OBJECT", "name" => "Cheese", "ofType" => nil}}, {"name"=>"flavor", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "String"}}}, {"name"=>"id", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "Int"}}}, {"name"=>"nullableCheese", "isDeprecated"=>false, "type"=>{ "kind" => "OBJECT", "name" => "Cheese", "ofType"=>nil}}, {"name"=>"origin", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "String"}}}, {"name"=>"selfAsEdible", "isDeprecated"=>false, "type"=>{"kind"=>"INTERFACE", "name"=>"Edible", "ofType"=>nil}}, {"name"=>"similarCheese", "isDeprecated"=>false, "type"=>{ "kind" => "OBJECT", "name"=>"Cheese", "ofType"=>nil}}, {"name"=>"source", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "DairyAnimal"}}}, ]} let(:dairy_animals) {[ {"name"=>"NONE", "isDeprecated"=> false }, {"name"=>"COW", "isDeprecated"=> false }, {"name"=>"DONKEY", "isDeprecated"=> false }, {"name"=>"GOAT", "isDeprecated"=> false }, {"name"=>"REINDEER", "isDeprecated"=> false }, {"name"=>"SHEEP", "isDeprecated"=> false }, ]} it "exposes metadata about types" do expected = {"data"=> { "cheeseType" => { "name"=> "Cheese", "kind" => "OBJECT", "fields"=> cheese_fields }, "milkType"=>{ "interfaces"=>[ {"name"=>"AnimalProduct"}, {"name"=>"Edible"}, {"name"=>"EdibleAsMilk"}, {"name"=>"LocalProduct"}, ], "fields"=>[ {"type"=>{"kind"=>"LIST","name"=>nil, "ofType"=>{"name"=>"DairyProduct"}}}, {"type"=>{"kind"=>"SCALAR","name"=>"String", "ofType"=>nil}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"Float"}}}, {"type"=>{"kind"=>"LIST","name"=>nil, "ofType"=>{"name"=>"String"}}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"ID"}}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"String"}}}, {"type"=>{"kind"=>"INTERFACE", "name"=>"Edible", "ofType"=>nil}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil,"ofType"=>{"name"=>"DairyAnimal"}}}, ] }, "dairyAnimal"=>{ "name"=>"DairyAnimal", "kind"=>"ENUM", "enumValues"=> dairy_animals, }, "dairyProduct"=>{ "name"=>"DairyProduct", "kind"=>"UNION", "possibleTypes"=>[{"name"=>"Cheese"}, {"name"=>"Milk"}], }, "animalProduct" => { "name"=>"AnimalProduct", "kind"=>"INTERFACE", "specifiedByURL" => nil, "possibleTypes"=>[{"name"=>"Cheese"}, {"name"=>"Honey"}, {"name"=>"Milk"}], "fields"=>[ {"name"=>"source"}, ] }, "missingType" => nil, "timeType" => { "specifiedByURL" => "https://time.graphql"} }} assert_equal(expected, result.to_h) end describe "deprecated fields" do let(:query_string) {%| query introspectionQuery { cheeseType: __type(name: "Cheese") { name, kind, fields(includeDeprecated: true) { name, isDeprecated, type { kind, name, ofType { name } } } } dairyAnimal: __type(name: "DairyAnimal") { name, kind, enumValues(includeDeprecated: true) { name, isDeprecated } } } |} let(:deprecated_fields) { {"name"=>"fatContent", "isDeprecated"=>true, "type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"Float"}}} } it "can expose deprecated fields" do new_cheese_fields = ([deprecated_fields] + cheese_fields).sort_by { |f| f["name"] } expected = { "data" => { "cheeseType" => { "name"=> "Cheese", "kind" => "OBJECT", "fields"=> new_cheese_fields }, "dairyAnimal"=>{ "name"=>"DairyAnimal", "kind"=>"ENUM", "enumValues"=> dairy_animals + [{"name" => "YAK", "isDeprecated" => true}], }, }} assert_equal(expected, result) end it "hides deprecated field arguments by default" do result = Dummy::Schema.execute <<-GRAPHQL { __type(name: "Query") { fields { name args { name } } } } GRAPHQL from_source_field = result['data']['__type']['fields'].find { |f| f['name'] == 'fromSource' } expected = [ {"name" => "source"} ] assert_equal(expected, from_source_field['args']) end it "can expose deprecated field arguments" do result = Dummy::Schema.execute <<-GRAPHQL { __type(name: "Query") { fields { name args(includeDeprecated: true) { name isDeprecated deprecationReason } } } } GRAPHQL from_source_field = result['data']['__type']['fields'].find { |f| f['name'] == 'fromSource' } expected = [ {"name" => "source", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "oldSource", "isDeprecated" => true, "deprecationReason" => "No longer supported"} ] assert_equal(expected, from_source_field['args']) end end describe "input objects" do let(:query_string) {%| query introspectionQuery { __type(name: "DairyProductInput") { name, description, kind, inputFields { name, type { kind, name }, defaultValue } } } |} it "exposes metadata about input objects" do expected = { "data" => { "__type" => { "name"=>"DairyProductInput", "description"=>"Properties for finding a dairy product", "kind"=>"INPUT_OBJECT", "inputFields"=>[ {"name"=>"source", "type"=>{"kind"=>"NON_NULL","name"=>nil, }, "defaultValue"=>nil}, {"name"=>"originDairy", "type"=>{"kind"=>"SCALAR","name"=>"String"}, "defaultValue"=>"\"Sugar Hollow Dairy\""}, {"name"=>"fatContent", "type"=>{"kind"=>"SCALAR","name" => "Float"}, "defaultValue"=>"0.3"}, {"name"=>"organic", "type"=>{"kind"=>"SCALAR","name" => "Boolean"}, "defaultValue"=>"false"}, {"name"=>"order_by", "type"=>{"kind"=>"INPUT_OBJECT", "name"=>"ResourceOrderType"}, "defaultValue"=>"{direction: \"ASC\"}"}, ] } }} assert_equal(expected, result) end it "can expose deprecated input fields" do result = Dummy::Schema.execute <<-GRAPHQL { __type(name: "DairyProductInput") { inputFields(includeDeprecated: true) { name isDeprecated deprecationReason } } } GRAPHQL expected = { "data" => { "__type" => { "inputFields" => [ {"name" => "source", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "originDairy", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "fatContent", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "organic", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "order_by", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "oldSource", "isDeprecated" => true, "deprecationReason" => "No longer supported"}, ] } } } assert_equal(expected, result) end it "includes Relay fields" do res = StarWars::Schema.execute <<-GRAPHQL { __schema { types { name fields { name args { name } } } } } GRAPHQL type_result = res["data"]["__schema"]["types"].find { |t| t["name"] == "Faction" } field_result = type_result["fields"].find { |f| f["name"] == "bases" } all_arg_names = ["after", "before", "first", "last", "nameIncludes", "complexOrder"] returned_arg_names = field_result["args"].map { |a| a["name"] } assert_equal all_arg_names.sort, returned_arg_names.sort end end end graphql-ruby-2.2.17/spec/graphql/language/000077500000000000000000000000001476434635200204115ustar00rootroot00000000000000graphql-ruby-2.2.17/spec/graphql/language/block_string_spec.rb000066400000000000000000000042751476434635200244400ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::BlockString do describe "trimming whitespace" do def trim_whitespace(str) GraphQL::Language::BlockString.trim_whitespace(str) end it "matches the examples in graphql-js" do # these are taken from: # https://github.com/graphql/graphql-js/blob/36ec0e9d34666362ff0e2b2b18edeb98e3c9abee/src/language/__tests__/blockStringValue-test.js#L12 # A set of [before, after] pairs: examples = [ [ # Removes common whitespace: " Hello, World! Yours, GraphQL. ", "Hello,\n World!\n\nYours,\n GraphQL." ], [ # Removes leading and trailing newlines: " Hello, World! Yours, GraphQL. ", "Hello,\n World!\n\nYours,\n GraphQL." ], [ # Removes blank lines (with whitespace _and_ newlines:) "\n \n Hello, World! Yours, GraphQL. \n \n", "Hello,\n World!\n\nYours,\n GraphQL." ], [ # Retains indentation from the first line " Hello,\n World!\n\n Yours,\n GraphQL.", " Hello,\n World!\n\nYours,\n GraphQL.", ], [ # Doesn't alter trailing spaces "\n \n Hello, \n World! \n\n Yours, \n GraphQL. ", "Hello, \n World! \n\nYours, \n GraphQL. ", ], [ # Doesn't crash when the string is only a newline "\n", "" ], [ # Removes long blank lines " \n \n Hello, World! Yours, GraphQL. \n \n", "Hello,\n World!\n\nYours,\n GraphQL." ] ] examples.each_with_index do |(before, after), idx| transformed_str = trim_whitespace(before) assert_equal(after, transformed_str, "Example ##{idx + 1}") end end end end graphql-ruby-2.2.17/spec/graphql/language/clexer_spec.rb000066400000000000000000000031511476434635200232320ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require_relative "./lexer_examples" if defined?(GraphQL::CParser::Lexer) describe GraphQL::CParser::Lexer do subject { GraphQL::CParser::Lexer } def assert_bad_unicode(string, _message = nil) assert_equal :BAD_UNICODE_ESCAPE, subject.tokenize(string).first[0] end it "makes tokens like the other lexer" do str = "{ f1(type: \"str\") ...F2 }\nfragment F2 on SomeType { f2 }" # Don't include prev_token here tokens = GraphQL.scan_with_c(str).map { |t| [*t.first(4), t[3].encoding] } old_tokens = GraphQL.scan_with_ruby(str).map { |t| [*t.first(4), t[3].encoding] } assert_equal [ [:LCURLY, 1, 1, "{", Encoding::UTF_8], [:IDENTIFIER, 1, 3, "f1", Encoding::UTF_8], [:LPAREN, 1, 5, "(", Encoding::UTF_8], [:TYPE, 1, 6, "type", Encoding::UTF_8], [:COLON, 1, 10, ":", Encoding::UTF_8], [:STRING, 1, 12, "str", Encoding::UTF_8], [:RPAREN, 1, 17, ")", Encoding::UTF_8], [:ELLIPSIS, 1, 19, "...", Encoding::UTF_8], [:IDENTIFIER, 1, 22, "F2", Encoding::UTF_8], [:RCURLY, 1, 25, "}", Encoding::UTF_8], [:FRAGMENT, 2, 1, "fragment", Encoding::UTF_8], [:IDENTIFIER, 2, 10, "F2", Encoding::UTF_8], [:ON, 2, 13, "on", Encoding::UTF_8], [:IDENTIFIER, 2, 16, "SomeType", Encoding::UTF_8], [:LCURLY, 2, 25, "{", Encoding::UTF_8], [:IDENTIFIER, 2, 27, "f2", Encoding::UTF_8], [:RCURLY, 2, 30, "}", Encoding::UTF_8] ], tokens assert_equal(old_tokens, tokens) end include LexerExamples end end graphql-ruby-2.2.17/spec/graphql/language/definition_slice_spec.rb000066400000000000000000000123721476434635200252640ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::DefinitionSlice do let(:document) { GraphQL.parse(query_string) } describe "anonymous query with no dependencies" do let(:query_string) {%| { version } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition(nil).to_query_string end end describe "anonymous mutation with no dependencies" do let(:query_string) {%| mutation { ping { message } } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition(nil).to_query_string end end describe "anonymous fragment with no dependencies" do let(:query_string) {%| fragment on User { name } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition(nil).to_query_string end end describe "named query with no dependencies" do let(:query_string) {%| query getVersion { version } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition("getVersion").to_query_string end end describe "named fragment with no dependencies" do let(:query_string) {%| fragment profileFields on User { firstName lastName } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition("profileFields").to_query_string end end describe "document with multiple queries but no subdependencies" do let(:query_string) {%| query getVersion { version } query getTime { time } |} it "returns just the query definition" do assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[0]]).to_query_string, document.slice_definition("getVersion").to_query_string assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[1]]).to_query_string, document.slice_definition("getTime").to_query_string end end describe "document with multiple fragments but no subdependencies" do let(:query_string) {%| fragment profileFields on User { firstName lastName } fragment avatarFields on User { avatarURL(size: 80) } |} it "returns just the fragment definition" do assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[0]]).to_query_string, document.slice_definition("profileFields").to_query_string assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[1]]).to_query_string, document.slice_definition("avatarFields").to_query_string end end describe "query with missing spread" do let(:query_string) {%| query getUser { viewer { ...profileFields } } |} it "is ignored" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "query and fragment subdependency" do let(:query_string) {%| query getUser { viewer { ...profileFields } } fragment profileFields on User { firstName lastName } |} it "returns query and fragment dependency" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "query and fragment nested subdependencies" do let(:query_string) {%| query getUser { viewer { ...viewerInfo } } fragment viewerInfo on User { ...profileFields } fragment profileFields on User { firstName lastName ...avatarFields } fragment avatarFields on User { avatarURL(size: 80) } |} it "returns query and all fragment dependencies" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "fragment subdependency referenced multiple times" do let(:query_string) {%| query getUser { viewer { ...viewerInfo ...moreViewerInfo } } fragment viewerInfo on User { ...profileFields } fragment moreViewerInfo on User { ...profileFields } fragment profileFields on User { firstName lastName } |} it "is only returned once" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "query and unused fragment" do let(:query_string) {%| query getUser { viewer { id } } fragment profileFields on User { firstName lastName } |} it "returns just the query definition" do assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[0]]).to_query_string, document.slice_definition("getUser").to_query_string end end end graphql-ruby-2.2.17/spec/graphql/language/document_from_schema_definition_spec.rb000066400000000000000000000573761476434635200303630ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::DocumentFromSchemaDefinition do let(:subject) { GraphQL::Language::DocumentFromSchemaDefinition } describe "#document" do let(:schema_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType schema { query: QueryType mutation: MutationType } GRAPHQL } let(:schema) { GraphQL::Schema.from_definition(schema_idl) } let(:expected_document) { GraphQL.parse(expected_idl) } describe "when schemas have enums and directives" do let(:schema_idl) { <<-GRAPHQL directive @locale(lang: LangEnum!) on FIELD directive @secret(top: Boolean = false) on FIELD_DEFINITION enum LangEnum { en ru } type Query { i: Int @secret ssn: String @secret(top: true) } GRAPHQL } class DirectiveSchema < GraphQL::Schema class Secret < GraphQL::Schema::Directive argument :top, Boolean, required: false, default_value: false locations FIELD_DEFINITION end class Query < GraphQL::Schema::Object field :i, Int do directive Secret end field :ssn, String do directive Secret, top: true end end class Locale < GraphQL::Schema::Directive class LangEnum < GraphQL::Schema::Enum value "en" value "ru" end locations GraphQL::Schema::Directive::FIELD argument :lang, LangEnum end query(Query) directive(Locale) end it "dumps them into the string" do assert_equal schema_idl, DirectiveSchema.to_definition end end describe "when it has an enum_value with an adjacent custom directive" do let(:schema_idl) { <<-GRAPHQL directive @customEnumValueDirective(fakeArgument: String!) on ENUM_VALUE enum FakeEnum { VALUE1 VALUE2 @customEnumValueDirective(fakeArgument: "Value1 is better...") } type Query { fakeQueryField: FakeEnum! } GRAPHQL } class EnumValueDirectiveSchema < GraphQL::Schema class CustomEnumValueDirective < GraphQL::Schema::Directive locations GraphQL::Schema::Directive::ENUM_VALUE argument :fake_argument, String end class FakeEnum < GraphQL::Schema::Enum value "VALUE1" value "VALUE2" do directive CustomEnumValueDirective, fake_argument: "Value1 is better..." end end class Query < GraphQL::Schema::Object field :fake_query_field, FakeEnum, null: false end query(Query) end it "dumps the custom directive definition to the IDL" do assert_equal schema_idl, EnumValueDirectiveSchema.to_definition end end describe "when printing and schema respects root name conventions" do let(:schema_idl) { <<-GRAPHQL type Query { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type Mutation { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | Query schema { query: Query mutation: Mutation } GRAPHQL } let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType GRAPHQL } let(:document) { subject.new( schema ).document } it "returns the IDL without introspection, built ins and schema root" do assert equivalent_node?(expected_document, document) end end describe "with defaults" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema ).document } it "returns the IDL without introspection, built ins and schema if it doesnt respect name conventions" do assert equivalent_node?(expected_document, document) end end describe "with a visiblity check" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): Site } interface Bar { one: Type four(argument: String = "string"): String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } schema { query: QueryType mutation: MutationType } GRAPHQL } let(:schema) { Class.new(GraphQL::Schema.from_definition(schema_idl)) do def self.visible?(m, ctx) m.graphql_name != "Type" end end } let(:document) { doc_schema = Class.new(schema) do def self.visible?(m, _ctx) m.respond_to?(:graphql_name) && m.graphql_name != "Type" end end subject.new(doc_schema).document } it "returns the IDL minus the filtered members" do assert equivalent_node?(expected_document, document) end end describe "with an only filter" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): Site } interface Bar { one: Type four(argument: String = "string"): String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } enum Site { DESKTOP MOBILE } schema { query: QueryType mutation: MutationType } GRAPHQL } let(:schema) { Class.new(GraphQL::Schema.from_definition(schema_idl)) do def self.visible?(m, ctx) !(m.respond_to?(:kind) && m.kind.scalar? && m.name == "CustomScalar") end end } let(:document) { doc_schema = Class.new(schema) do def self.visible?(m, _ctx) !(m.respond_to?(:kind) && m.kind.scalar? && m.name == "CustomScalar") end end subject.new(doc_schema).document } it "returns the IDL minus the filtered members" do assert equivalent_node?(expected_document, document) end end describe "when excluding built ins and introspection types" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, always_include_schema: true ).document } it "returns the schema idl besides introspection types and built ins" do assert equivalent_node?(expected_document, document) end end describe "when printing excluding only introspection types" do let(:expected_idl) { <<-GRAPHQL # Represents `true` or `false` values. scalar Boolean # Represents textual data as UTF-8 character sequences. This type is most often # used by GraphQL to represent free-form human-readable text. scalar String type QueryType { foo: Foo } type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } # Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. scalar Int type MutationType { a(input: InputType): String } # Represents signed double-precision fractional values as specified by [IEEE # 754](https://en.wikipedia.org/wiki/IEEE_floating_point). scalar Float # Represents a unique identifier that is Base64 obfuscated. It is often used to # refetch an object or as key for a cache. The ID type appears in a JSON response # as a String; however, it is not intended to be human-readable. When expected as # an input type, any string (such as `"VXNlci0xMA=="`) or integer (such as `4`) # input value will be accepted as an ID. scalar ID # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT # Marks an element of a GraphQL schema as no longer supported. directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, include_built_in_scalars: true, include_built_in_directives: true, ).document } it "returns the schema IDL including only the built ins and not introspection types" do assert equivalent_node?(expected_document, document) end end describe "when printing the full schema" do let(:expected_idl) { <<-GRAPHQL # Represents `true` or `false` values. scalar Boolean # Represents textual data as UTF-8 character sequences. This type is most often # used by GraphQL to represent free-form human-readable text. scalar String # The fundamental unit of any GraphQL Schema is the type. There are many kinds of # types in GraphQL as represented by the `__TypeKind` enum. # # Depending on the kind of a type, certain fields describe information about that # type. Scalar types provide no information beyond a name and description, while # Enum types provide their values. Object and Interface types provide the fields # they describe. Abstract types, Union and Interface, provide the Object types # possible at runtime. List and NonNull types compose other types. type __Type { kind: __TypeKind! name: String description: String fields(includeDeprecated: Boolean = false): [__Field!] interfaces: [__Type!] possibleTypes: [__Type!] enumValues(includeDeprecated: Boolean = false): [__EnumValue!] inputFields: [__InputValue!] ofType: __Type } # An enum describing what kind of type a given `__Type` is. enum __TypeKind { # Indicates this type is a scalar. SCALAR # Indicates this type is an object. `fields` and `interfaces` are valid fields. OBJECT # Indicates this type is an interface. `fields` and `possibleTypes` are valid fields. INTERFACE # Indicates this type is a union. `possibleTypes` is a valid field. UNION # Indicates this type is an enum. `enumValues` is a valid field. ENUM # Indicates this type is an input object. `inputFields` is a valid field. INPUT_OBJECT # Indicates this type is a list. `ofType` is a valid field. LIST # Indicates this type is a non-null. `ofType` is a valid field. NON_NULL } # Object and Interface types are described by a list of Fields, each of which has # a name, potentially a list of arguments, and a return type. type __Field { name: String! description: String args: [__InputValue!]! type: __Type! isDeprecated: Boolean! deprecationReason: String } # Arguments provided to Fields or Directives and the input fields of an # InputObject are represented as Input Values which describe their type and # optionally a default value. type __InputValue { name: String! description: String type: __Type! # A GraphQL-formatted string representing the default value for this input value. defaultValue: String } # One possible value for a given Enum. Enum values are unique values, not a # placeholder for a string or numeric value. However an Enum value is returned in # a JSON response as a string. type __EnumValue { name: String! description: String isDeprecated: Boolean! deprecationReason: String } type QueryType { foo: Foo } type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } # Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. scalar Int type MutationType { a(input: InputType): String } # A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all # available types and directives on the server, as well as the entry points for # query, mutation, and subscription operations. type __Schema { # A list of all types supported by this server. types: [__Type!]! # The type that query operations will be rooted at. queryType: __Type! # If this server supports mutation, the type that mutation operations will be rooted at. mutationType: __Type # If this server support subscription, the type that subscription operations will be rooted at. subscriptionType: __Type # A list of all directives supported by this server. directives: [__Directive!]! } # A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. # # In some cases, you need to provide options to alter GraphQL's execution behavior # in ways field arguments will not suffice, such as conditionally including or # skipping a field. Directives provide this by describing additional information # to the executor. type __Directive { name: String! description: String locations: [__DirectiveLocation!]! args: [__InputValue!]! onOperation: Boolean! onFragment: Boolean! onField: Boolean! } # A Directive can be adjacent to many parts of the GraphQL language, a # __DirectiveLocation describes one such possible adjacencies. enum __DirectiveLocation { # Location adjacent to a query operation. QUERY # Location adjacent to a mutation operation. MUTATION # Location adjacent to a subscription operation. SUBSCRIPTION # Location adjacent to a field. FIELD # Location adjacent to a fragment definition. FRAGMENT_DEFINITION # Location adjacent to a fragment spread. FRAGMENT_SPREAD # Location adjacent to an inline fragment. INLINE_FRAGMENT # Location adjacent to a schema definition. SCHEMA # Location adjacent to a scalar definition. SCALAR # Location adjacent to an object type definition. OBJECT # Location adjacent to a field definition. FIELD_DEFINITION # Location adjacent to an argument definition. ARGUMENT_DEFINITION # Location adjacent to an interface definition. INTERFACE # Location adjacent to a union definition. UNION # Location adjacent to an enum definition. ENUM # Location adjacent to an enum value definition. ENUM_VALUE # Location adjacent to an input object type definition. INPUT_OBJECT # Location adjacent to an input object field definition. INPUT_FIELD_DEFINITION } # Represents signed double-precision fractional values as specified by [IEEE # 754](https://en.wikipedia.org/wiki/IEEE_floating_point). scalar Float # Represents a unique identifier that is Base64 obfuscated. It is often used to # refetch an object or as key for a cache. The ID type appears in a JSON response # as a String; however, it is not intended to be human-readable. When expected as # an input type, any string (such as `"VXNlci0xMA=="`) or integer (such as `4`) # input value will be accepted as an ID. scalar ID # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT # Marks an element of a GraphQL schema as no longer supported. directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, include_introspection_types: true, include_built_in_directives: true, include_built_in_scalars: true, always_include_schema: true, ).document } it "returns the full document AST from the given schema including built ins and introspection" do assert equivalent_node?(expected_document, document) end end end private def equivalent_node?(expected, node) return false unless expected.is_a?(node.class) if expected.respond_to?(:children) && expected.respond_to?(:scalars) children_equal = expected.children.all? do |expected_child| node.children.find { |child| equivalent_node?(expected_child, child) } end scalars_equal = expected.children.all? do |expected_child| node.children.find { |child| equivalent_node?(expected_child, child) } end children_equal && scalars_equal else expected == node end end describe "custom SDL directives" do class CustomSDLDirectiveSchema < GraphQL::Schema class CustomThing < GraphQL::Schema::Directive locations(FIELD_DEFINITION) argument :stuff, String end directive CustomThing end it "prints them out" do assert_equal "directive @customThing(stuff: String!) on FIELD_DEFINITION\n", CustomSDLDirectiveSchema.to_definition end end end graphql-ruby-2.2.17/spec/graphql/language/equality_spec.rb000066400000000000000000000042131476434635200236050ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::Nodes::AbstractNode do describe ".eql?" do let(:document1) { GraphQL.parse(query_string1) } let(:document2) { GraphQL.parse(query_string2) } describe "large identical document" do let(:query_string1) {%| query getStuff($someVar: Int = 1, $anotherVar: [String!], $skipNested: Boolean! = false) @skip(if: false) { myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever") anotherField(someArg: [1, 2, 3]) { nestedField ...moreNestedFields @skip(if: $skipNested) } ... on OtherType @include(unless: false) { field(arg: [{ key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER }]) anotherField } ... { id } } fragment moreNestedFields on NestedType @or(something: "ok") { anotherNestedField } |} let(:query_string2) { query_string1 } it "should be equal" do assert document1 == document2 assert document2 == document1 end end describe "different operations" do let(:query_string1) { "query { field }" } let(:query_string2) { "mutation { setField }" } it "should not be equal" do refute document1 == document2 refute document2 == document1 end end describe "different query fields" do let(:query_string1) { "query { foo }" } let(:query_string2) { "query { bar }" } it "should not be equal" do refute document1 == document2 refute document2 == document1 end end describe "different schemas" do let(:query_string1) {%| schema { query: Query } type Query { field: String! } |} let(:query_string2) {%| schema { query: Query } type Query { field: Int! } |} it "should not be equal" do refute document1.eql?(document2) refute document2.eql?(document1) end end end end graphql-ruby-2.2.17/spec/graphql/language/generation_spec.rb000066400000000000000000000015301476434635200241020ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::Generation do describe "#to_query_tring" do let(:document) { GraphQL.parse('type Query { a: String! }') } let(:custom_printer_class) { Class.new(GraphQL::Language::Printer) { def print_field_definition(print_field_definition) print_string("