Skip to content

Commit

Permalink
Merge branch 'master' into 2.0-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Dec 30, 2021
1 parent 4f8d1e1 commit 991dd52
Show file tree
Hide file tree
Showing 75 changed files with 1,311 additions and 232 deletions.
16 changes: 14 additions & 2 deletions CHANGELOG-pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@

### Bug Fix

# 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: performance improvements: only validate newly-added operations, reduce allocations when normalizing incoming query strings
- 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: fix when operations are re-synced with new aliases
- Operation Store sync: fix when operations are re-synced with new aliases

# 1.20.0 (5 November 2021)

Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,36 @@

### Bug fixes

# 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.
Expand Down
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ namespace :bench do
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
Expand Down
11 changes: 11 additions & 0 deletions benchmark/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ def self.run(task)
end
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)
Expand Down
2 changes: 1 addition & 1 deletion guides/fields/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The second argument to `field(...)` is the return type. This can be:
- 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 required `null:` keyword:
{% 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.
Expand Down
2 changes: 1 addition & 1 deletion guides/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ <h2>Install the Gem</h2>
<div class="teaser">
<p>
<a href="{{ site.baseurl}}/getting_started">Get going fast</a> with the <code><a href="https://rubygems.org/gems/graphql">graphql</a></code> gem,
battle-tested and trusted by <a href="https://githubengineering.com/the-github-graphql-api/#open-source">GitHub</a>, <a href="https://www.graphql.com/articles/graphql-at-shopify">Shopify</a> and <a href="https://www.kickstarter.com/">Kickstarter</a>.
battle-tested and trusted by <a href="https://githubengineering.com/the-github-graphql-api/#open-source">GitHub</a>, <a href="https://www.graphql.com/articles/graphql-at-shopify">Shopify</a>, <a href="https://www.chime.com">Chime</a>, and <a href="https://www.kickstarter.com/">Kickstarter</a>.
</p>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions guides/pro/checksums/graphql-pro-1.20.3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d75f4a170d3763ed3f01c241bdb01e21bfb5fabf607c13a45ed4815ac7d4f98ff44e4e6bc2ab7b91d380d5a510a21c4224d95ca61ee52e3d10992fe022605570
1 change: 1 addition & 0 deletions guides/pro/checksums/graphql-pro-1.20.4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ce98318463a74c268ca618f46cf441859c2a374abd5908f1f25c788f934d87a9ab97377c3422882e8ad86da4d926cfbc30356856f6d2312c793a168abea8069f
37 changes: 37 additions & 0 deletions guides/queries/response_extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Response Extensions
layout: guide
doc_stub: false
search: true
section: Queries
desc: Adding "extensions" to the response hash
index: 12
---

During query execution, you can add to the response's `"extensions" => { ... }` 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.
26 changes: 26 additions & 0 deletions guides/type_definitions/field_extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ 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.
Expand Down Expand Up @@ -101,3 +115,15 @@ def after_resolve(value:, **rest)
value.limit(options[:limit])
end
```

### 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.)
4 changes: 4 additions & 0 deletions javascript_client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# graphql-ruby-client

# 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
Expand Down
4 changes: 2 additions & 2 deletions javascript_client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-ruby-client",
"version": "1.10.4",
"version": "1.10.5",
"description": "JavaScript client for graphql-ruby",
"main": "index.js",
"types": "index.d.ts",
Expand All @@ -10,7 +10,7 @@
"bin": "./cli.js",
"devDependencies": {
"@apollo/client": "^3.3.13",
"@types/actioncable": "^5.2.3",
"@types/rails__actioncable": "^6.1.6",
"@types/glob": "^7.1.1",
"@types/jest": "^25.1.2",
"@types/minimist": "^1.2.0",
Expand Down
6 changes: 3 additions & 3 deletions javascript_client/src/subscriptions/ActionCableLink.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { ApolloLink, Observable, FetchResult, Operation, NextLink } from "@apollo/client/core"
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"
import { print } from "graphql"

type RequestResult = FetchResult<{ [key: string]: any; }, Record<string, any>, Record<string, any>>
type ConnectionParams = object | ((operation: Operation) => object)

class ActionCableLink extends ApolloLink {
cable: Cable
cable: Consumer
channelName: string
actionName: string
connectionParams: ConnectionParams

constructor(options: {
cable: Cable, channelName?: string, actionName?: string, connectionParams?: ConnectionParams
cable: Consumer, channelName?: string, actionName?: string, connectionParams?: ConnectionParams
}) {
super()
this.cable = options.cable
Expand Down
6 changes: 3 additions & 3 deletions javascript_client/src/subscriptions/ActionCableSubscriber.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import printer from "graphql/language/printer"
import registry from "./registry"
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"

interface ApolloNetworkInterface {
applyMiddlewares: Function
Expand All @@ -9,10 +9,10 @@ interface ApolloNetworkInterface {
}

class ActionCableSubscriber {
_cable: Cable
_cable: Consumer
_networkInterface: ApolloNetworkInterface

constructor(cable: Cable, networkInterface: ApolloNetworkInterface) {
constructor(cable: Consumer, networkInterface: ApolloNetworkInterface) {
this._cable = cable
this._networkInterface = networkInterface
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ActionCableLink from "../ActionCableLink"
import { parse } from "graphql"
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"
import { Operation } from "@apollo/client/core"

describe("ActionCableLink", () => {
Expand Down Expand Up @@ -46,7 +46,7 @@ describe("ActionCableLink", () => {
}
}
options = {
cable: (cable as unknown) as Cable
cable: (cable as unknown) as Consumer
}

query = parse("subscription { foo { bar } }")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createActionCableHandler } from "../createActionCableHandler"
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"
describe("createActionCableHandler", () => {
it("returns a function producing a disposable subscription", () => {
var wasDisposed = false
Expand All @@ -14,7 +14,7 @@ describe("createActionCableHandler", () => {
}

var options = {
cable: (dummyActionCableConsumer as unknown) as Cable
cable: (dummyActionCableConsumer as unknown) as Consumer
}
var producer = createActionCableHandler(options)
producer({text: "", name: ""}, {}, {}, { onError: () => {}, onNext: () => {}, onCompleted: () => {} }).dispose()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import createRelaySubscriptionHandler from "../createRelaySubscriptionHandler"
import { createLegacyRelaySubscriptionHandler } from "../createRelaySubscriptionHandler"
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"
import { Network} from 'relay-runtime'

describe("createRelaySubscriptionHandler", () => {
Expand All @@ -12,7 +12,7 @@ describe("createRelaySubscriptionHandler", () => {
}

var options = {
cable: (dummyActionCableConsumer as unknown) as Cable
cable: (dummyActionCableConsumer as unknown) as Consumer
}

var handler = createRelaySubscriptionHandler(options)
Expand All @@ -31,7 +31,7 @@ describe("createLegacyRelaySubscriptionHandler", () => {
}

var options = {
cable: (dummyActionCableConsumer as unknown) as Cable
cable: (dummyActionCableConsumer as unknown) as Consumer
}

expect(createLegacyRelaySubscriptionHandler(options)).toBeInstanceOf(Function)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ActionCableSubscriber from "./ActionCableSubscriber"
import PusherSubscriber from "./PusherSubscriber"
import Pusher from "pusher-js"
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"

interface Subscriber {
subscribe: Function
Expand All @@ -16,7 +16,7 @@ interface Subscriber {
* to the provided networkInterface.
* @example Adding ActionCable subscriptions to a HTTP network interface
* // Load ActionCable and create a consumer
* var ActionCable = require('actioncable')
* var ActionCable = require('@rails/actioncable')
* var cable = ActionCable.createConsumer()
* window.cable = cable
*
Expand Down Expand Up @@ -48,7 +48,7 @@ interface Subscriber {
* @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?: Cable, subscriber?: Subscriber, decompress?: (compressed: string) => any}) {
function addGraphQLSubscriptions(networkInterface: any, options: { pusher?: Pusher, cable?: Consumer, subscriber?: Subscriber, decompress?: (compressed: string) => any}) {
if (!options) {
options = {}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Cable } from "actioncable"
import type { Consumer } from "@rails/actioncable"

/**
* Create a Relay Modern-compatible subscription handler.
Expand All @@ -8,7 +8,7 @@ import { Cable } from "actioncable"
* @return {Function}
*/
interface ActionCableHandlerOptions {
cable: Cable
cable: Consumer
operations?: { getOperationId: Function}
}

Expand Down
4 changes: 3 additions & 1 deletion lib/generators/graphql/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def insert_root_type(type, name)
sentinel = /< GraphQL::Schema\s*\n/m

in_root do
inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false
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

Expand Down
11 changes: 9 additions & 2 deletions lib/generators/graphql/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,15 @@ def create_folder_structure
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] && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails")
gem("graphiql-rails", group: :development)
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'
Expand Down
Loading

0 comments on commit 991dd52

Please sign in to comment.