graphql-stitching 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -8
  3. data/Gemfile +1 -0
  4. data/README.md +60 -20
  5. data/docs/client.md +6 -0
  6. data/docs/composer.md +7 -6
  7. data/docs/federation_entities.md +1 -1
  8. data/docs/mechanics.md +1 -43
  9. data/docs/request.md +10 -10
  10. data/docs/subscriptions.md +11 -11
  11. data/docs/supergraph.md +5 -5
  12. data/docs/{resolver.md → type_resolver.md} +3 -3
  13. data/examples/subscriptions/app/graphql/subscriptions_schema.rb +3 -3
  14. data/gemfiles/graphql_2.0.0.gemfile +4 -1
  15. data/gemfiles/graphql_2.1.0.gemfile +4 -1
  16. data/gemfiles/graphql_2.2.0.gemfile +4 -1
  17. data/gemfiles/graphql_2.3.0.gemfile +9 -0
  18. data/graphql-stitching.gemspec +1 -1
  19. data/lib/graphql/stitching/client.rb +5 -7
  20. data/lib/graphql/stitching/composer/{resolver_config.rb → type_resolver_config.rb} +2 -2
  21. data/lib/graphql/stitching/composer/{validate_resolvers.rb → validate_type_resolvers.rb} +1 -1
  22. data/lib/graphql/stitching/composer.rb +28 -20
  23. data/lib/graphql/stitching/executor/shaper.rb +4 -4
  24. data/lib/graphql/stitching/executor/{resolver_source.rb → type_resolver_source.rb} +4 -4
  25. data/lib/graphql/stitching/executor.rb +5 -5
  26. data/lib/graphql/stitching/planner/step.rb +1 -1
  27. data/lib/graphql/stitching/planner.rb +16 -20
  28. data/lib/graphql/stitching/request/skip_include.rb +1 -1
  29. data/lib/graphql/stitching/request.rb +3 -7
  30. data/lib/graphql/stitching/supergraph/to_definition.rb +3 -3
  31. data/lib/graphql/stitching/supergraph.rb +1 -6
  32. data/lib/graphql/stitching/{resolver → type_resolver}/arguments.rb +6 -6
  33. data/lib/graphql/stitching/{resolver → type_resolver}/keys.rb +1 -1
  34. data/lib/graphql/stitching/{resolver.rb → type_resolver.rb} +4 -4
  35. data/lib/graphql/stitching/version.rb +1 -1
  36. data/lib/graphql/stitching.rb +20 -3
  37. metadata +12 -12
  38. data/gemfiles/graphql_1.13.9.gemfile +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b71b1d9e65a84356466a0ff822e177ef5faccce1e2a0cbd91d930b06ea50923
4
- data.tar.gz: 8c73c1bcc78d23ea3b0c3f09da2659d84b7877000ccba86f79fa9f83f5801c60
3
+ metadata.gz: cf60ae0ef85426a3011223bcc146a74a1664762e5488d8266d0afbf1c2143457
4
+ data.tar.gz: d20da5d4817193de7e156441cab88b0855f3fa93aeabe5e760f8fd66ac5b3547
5
5
  SHA512:
6
- metadata.gz: 0e46b25856e61dc033ba0e2640d30133463c550af0071ac0e8bb3836b2fa8f4d0690018359b5b3b086861a907b5c66ea55eece9d4bfebe85296e723aedd96415
7
- data.tar.gz: 79180fc8944adde3ee0abf79a6fefa3487724cfbf9fb55e00adf29aa42d637c1c53331aad34a0c9c32ed7e3120fdaa6dd26fba9644df86b0feb774a84a8876b2
6
+ metadata.gz: b89843bfdd353a6b4aec47fce17a6bd6524b29f7d5fa5dc0452570dc1b4bec63691171f7cd3f841c393015df763e20e1be7cf907cc660fcc576cc93e08c3d4dd
7
+ data.tar.gz: a20f68f8bac0a52271fdc76ec4f173347669acda7f49b18e177746c83df1ec42dc878e8d3af37545a99f5456acc6d9f7577600d79c4568925cd23837fe5c2d0a
@@ -13,22 +13,20 @@ jobs:
13
13
  matrix:
14
14
  include:
15
15
  - gemfile: Gemfile
16
+ ruby: 3.3
17
+ - gemfile: gemfiles/graphql_2.3.0.gemfile
16
18
  ruby: 3.2
17
- - gemfile: Gemfile
18
- ruby: 3.1
19
- - gemfile: Gemfile
20
- ruby: 2.7
21
19
  - gemfile: gemfiles/graphql_2.2.0.gemfile
22
20
  ruby: 3.1
23
21
  - gemfile: gemfiles/graphql_2.1.0.gemfile
24
22
  ruby: 3.1
25
23
  - gemfile: gemfiles/graphql_2.0.0.gemfile
26
24
  ruby: 3.1
27
- - gemfile: gemfiles/graphql_1.13.9.gemfile
28
- ruby: 3.1
29
-
25
+ - gemfile: gemfiles/graphql_2.0.0.gemfile
26
+ ruby: 2.7
30
27
  steps:
31
- - uses: actions/checkout@v2
28
+ - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV
29
+ - uses: actions/checkout@v4
32
30
  - name: Setup Ruby
33
31
  uses: ruby/setup-ruby@v1
34
32
  with:
data/Gemfile CHANGED
@@ -6,3 +6,4 @@ gemspec
6
6
  gem 'pry'
7
7
  gem 'pry-byebug'
8
8
  gem 'warning'
9
+ gem 'minitest-stub-const'
data/README.md CHANGED
@@ -1,23 +1,22 @@
1
1
  ## GraphQL Stitching for Ruby
2
2
 
3
- GraphQL stitching composes a single schema from multiple underlying GraphQL resources, then smartly proxies portions of incoming requests to their respective locations in dependency order and returns the merged results. This allows an entire location graph to be queried through one combined GraphQL surface area.
3
+ GraphQL stitching composes a single schema from multiple underlying GraphQL resources, then smartly proxies portions of incoming requests to their respective locations in dependency order and returns the merged results. This allows an entire graph of locations to be queried through one combined GraphQL surface area.
4
4
 
5
5
  ![Stitched graph](./docs/images/stitching.png)
6
6
 
7
7
  **Supports:**
8
- - All operation types: query, mutation, and subscription.
9
- - Merged object and abstract types.
8
+ - All operation types: query, mutation, and [subscription](./docs/subscriptions.md).
9
+ - Merged object and abstract types joining though multiple keys.
10
10
  - Shared objects, fields, enums, and inputs across locations.
11
- - Multiple and composite type keys.
12
11
  - Combining local and remote schemas.
13
- - File uploads via [multipart form spec](https://github.com/jaydenseric/graphql-multipart-request-spec).
12
+ - [File uploads](./docs/http_executable.md) via multipart forms.
14
13
  - Tested with all minor versions of `graphql-ruby`.
15
14
 
16
15
  **NOT Supported:**
17
16
  - Computed fields (ie: federation-style `@requires`).
18
17
  - Defer/stream.
19
18
 
20
- This Ruby implementation is a sibling to [GraphQL Tools](https://the-guild.dev/graphql/stitching) (JS) and [Bramble](https://movio.github.io/bramble/) (Go), and its capabilities fall somewhere in between them. GraphQL stitching is similar in concept to [Apollo Federation](https://www.apollographql.com/docs/federation/), though more generic. The opportunity here is for a Ruby application to stitch its local schemas together or onto remote sources without requiring an additional proxy service running in another language. If your goal is to build a purely high-throughput federated reverse proxy, consider not using Ruby.
19
+ This Ruby implementation is designed as a generic library to join basic spec-compliant GraphQL schemas using their existing types and fields in a [DIY](https://dictionary.cambridge.org/us/dictionary/english/diy) capacity. The opportunity here is for a Ruby application to stitch its local schemas together or onto remote sources without requiring an additional proxy service running in another language. If your goal is a purely high-throughput federation gateway with managed schema deployments, consider more opinionated frameworks such as [Apollo Federation](https://www.apollographql.com/docs/federation/).
21
20
 
22
21
  ## Getting started
23
22
 
@@ -35,7 +34,7 @@ require "graphql/stitching"
35
34
 
36
35
  ## Usage
37
36
 
38
- The quickest way to start is to use the provided [`Client`](./docs/client.md) component that wraps a stitched graph in an executable workflow (with optional query plan caching hooks):
37
+ The [`Client`](./docs/client.md) component builds a stitched graph wrapped in an executable workflow (with optional query plan caching hooks):
39
38
 
40
39
  ```ruby
41
40
  movies_schema = <<~GRAPHQL
@@ -75,7 +74,7 @@ result = client.execute(
75
74
 
76
75
  Schemas provided in [location settings](./docs/composer.md#performing-composition) may be class-based schemas with local resolvers (locally-executable schemas), or schemas built from SDL strings (schema definition language parsed using `GraphQL::Schema.from_definition`) and mapped to remote locations via [executables](#executables).
77
76
 
78
- While the `Client` constructor is an easy quick start, the library also has several discrete components that can be assembled into custom workflows:
77
+ While `Client` is sufficient for most usecases, the library offers several discrete components that can be assembled into tailored workflows:
79
78
 
80
79
  - [Composer](./docs/composer.md) - merges and validates many schemas into one supergraph.
81
80
  - [Supergraph](./docs/supergraph.md) - manages the combined schema, location routing maps, and executable resources. Can be exported, cached, and rehydrated.
@@ -88,17 +87,59 @@ While the `Client` constructor is an easy quick start, the library also has seve
88
87
 
89
88
  ![Merging types](./docs/images/merging.png)
90
89
 
91
- To facilitate this merging of types, stitching must know how to cross-reference and fetch each variant of a type from its source location using [resolver queries](#merged-type-resolver-queries). For those in an Apollo ecosystem, there's also _limited_ support for merging types though a [federation `_entities` protocol](./docs/federation_entities.md).
90
+ To facilitate this, schemas should be designed around [merged type keys](#merged-type-keys) that stitching can cross-reference and fetch across locations using [type resolver queries](#merged-type-resolver-queries). For those in an Apollo ecosystem, there's also _limited_ support for merging types though a [federation `_entities` protocol](./docs/federation_entities.md).
91
+
92
+ ### Merged type keys
93
+
94
+ Foreign keys in a GraphQL schema frequently look like the `Product.imageId` field here:
95
+
96
+ ```graphql
97
+ # -- Products schema:
98
+
99
+ type Product {
100
+ id: ID!
101
+ imageId: ID!
102
+ }
103
+
104
+ # -- Images schema:
105
+
106
+ type Image {
107
+ id: ID!
108
+ url: String!
109
+ }
110
+ ```
111
+
112
+ However, this design does not lend itself to merging types across locations. A simple schema refactor makes this foreign key more expressive as an entity type, and turns the key into an _object_ that will merge with analogous objects in other locations:
113
+
114
+ ```graphql
115
+ # -- Products schema:
116
+
117
+ type Product {
118
+ id: ID!
119
+ image: Image!
120
+ }
121
+
122
+ type Image {
123
+ id: ID!
124
+ }
125
+
126
+ # -- Images schema:
127
+
128
+ type Image {
129
+ id: ID!
130
+ url: String!
131
+ }
132
+ ```
92
133
 
93
134
  ### Merged type resolver queries
94
135
 
95
- Types merge through resolver queries identified by a `@stitch` directive:
136
+ Each location that provides a unique variant of a type must provide at least one _resolver query_ for accessing it. Type resolvers are root queries identified by a `@stitch` directive:
96
137
 
97
138
  ```graphql
98
139
  directive @stitch(key: String!, arguments: String) repeatable on FIELD_DEFINITION
99
140
  ```
100
141
 
101
- This directive (or [static configuration](#sdl-based-schemas)) is applied to root queries where a merged type may be accessed in each location, and a `key` argument specifies a field needed from other locations to be used as a query argument.
142
+ This directive tells stitching how to cross-reference and fetch types from across locations, for example:
102
143
 
103
144
  ```ruby
104
145
  products_schema = <<~GRAPHQL
@@ -151,10 +192,10 @@ type Query {
151
192
  }
152
193
  ```
153
194
 
154
- * The `@stitch` directive is applied to a root query where the merged type may be accessed. The merged type identity is inferred from the field return.
155
- * The `key: "id"` parameter indicates that an `{ id }` must be selected from prior locations so it may be submitted as an argument to this query. The query argument used to send the key is inferred when possible ([more on arguments](#argument-shapes) later).
195
+ * The `@stitch` directive marks a root query where the merged type may be accessed. The merged type identity is inferred from the field return. This identifier can also be provided as [static configuration](#sdl-based-schemas).
196
+ * The `key: "id"` parameter indicates that an `{ id }` must be selected from prior locations so it can be submitted as an argument to this query. The query argument used to send the key is inferred when possible ([more on arguments](#argument-shapes) later).
156
197
 
157
- Each location that provides a unique variant of a type must provide at least one resolver query for the type. The exception to this requirement are [outbound-only types](./docs/mechanics.md#outbound-only-merged-types) and/or [foreign key types](./docs/mechanics.md##modeling-foreign-keys-for-stitching) that contain no exclusive data:
198
+ Merged types must have a resolver query in each of their possible locations. The one exception to this requirement are [outbound-only types](./docs/mechanics.md#outbound-only-merged-types) that contain no exclusive data, such as foreign keys:
158
199
 
159
200
  ```graphql
160
201
  type Product {
@@ -162,7 +203,7 @@ type Product {
162
203
  }
163
204
  ```
164
205
 
165
- The above representation of a `Product` type contains nothing but a key that is available in other locations. Therefore, this representation will never require an inbound request to fetch it, and its resolver query may be omitted.
206
+ The above type contains nothing but a key field that is available in other locations. Therefore, this variant will never require an inbound request to fetch it, and its resolver query may be omitted from this location.
166
207
 
167
208
  #### List queries
168
209
 
@@ -249,7 +290,7 @@ type Query {
249
290
  }
250
291
  ```
251
292
 
252
- See [resolver arguments](./docs/resolver.md#arguments) for full documentation on shaping input.
293
+ See [resolver arguments](./docs/type_resolver.md#arguments) for full documentation on shaping input.
253
294
 
254
295
  #### Composite type keys
255
296
 
@@ -258,7 +299,6 @@ Resolver keys may make composite selections for multiple key fields and/or neste
258
299
  ```graphql
259
300
  interface FieldOwner {
260
301
  id: ID!
261
- type: String!
262
302
  }
263
303
  type CustomField {
264
304
  owner: FieldOwner!
@@ -273,8 +313,8 @@ input CustomFieldLookup {
273
313
 
274
314
  type Query {
275
315
  customFields(lookups: [CustomFieldLookup!]!): [CustomField]! @stitch(
276
- key: "owner { id type } key",
277
- arguments: "lookups: { ownerId: $.owner.id, ownerType: $.owner.type, key: $.key }"
316
+ key: "owner { id __typename } key",
317
+ arguments: "lookups: { ownerId: $.owner.id, ownerType: $.owner.__typename, key: $.key }"
278
318
  )
279
319
  }
280
320
  ```
@@ -443,7 +483,6 @@ The [Executor](./docs/executor.md) component builds atop the Ruby fiber-based im
443
483
 
444
484
  ## Additional topics
445
485
 
446
- - [Modeling foreign keys for stitching](./docs/mechanics.md##modeling-foreign-keys-for-stitching)
447
486
  - [Deploying a stitched schema](./docs/mechanics.md#deploying-a-stitched-schema)
448
487
  - [Schema composition merge patterns](./docs/composer.md#merge-patterns)
449
488
  - [Subscriptions tutorial](./docs/subscriptions.md)
@@ -458,6 +497,7 @@ This repo includes working examples of stitched schemas running across small Rac
458
497
 
459
498
  - [Merged types](./examples/merged_types)
460
499
  - [File uploads](./examples/file_uploads)
500
+ - [Subscriptions](./examples/subscriptions)
461
501
 
462
502
  ## Tests
463
503
 
data/docs/client.md CHANGED
@@ -68,6 +68,12 @@ client.on_cache_write do |request, payload|
68
68
  end
69
69
  ```
70
70
 
71
+ All request digests use SHA2 by default. You can swap in [a faster algorithm](https://github.com/Shopify/blake3-rb) and/or add base scoping by reconfiguring the stitching library:
72
+
73
+ ```ruby
74
+ GraphQL::Stitching.digest { |str| Digest::MD5.hexdigest("v2/#{str}") }
75
+ ```
76
+
71
77
  Note that inlined input data works against caching, so you should _avoid_ these input literals when possible:
72
78
 
73
79
  ```graphql
data/docs/composer.md CHANGED
@@ -10,6 +10,7 @@ A `Composer` may be constructed with optional settings that tune how it builds a
10
10
  composer = GraphQL::Stitching::Composer.new(
11
11
  query_name: "Query",
12
12
  mutation_name: "Mutation",
13
+ subscription_name: "Subscription",
13
14
  description_merger: ->(values_by_location, info) { values_by_location.values.join("\n") },
14
15
  deprecation_merger: ->(values_by_location, info) { values_by_location.values.first },
15
16
  default_value_merger: ->(values_by_location, info) { values_by_location.values.first },
@@ -24,6 +25,8 @@ Constructor arguments:
24
25
 
25
26
  - **`mutation_name:`** _optional_, the name of the root mutation type in the composed schema; `Mutation` by default. The root mutation types from all location schemas will be merged into this type, regardless of their local names.
26
27
 
28
+ - **`subscription_name:`** _optional_, the name of the root subscription type in the composed schema; `Subscription` by default. The root subscription types from all location schemas will be merged into this type, regardless of their local names.
29
+
27
30
  - **`description_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging element description strings from across locations.
28
31
 
29
32
  - **`deprecation_merger:`** _optional_, a [value merger function](#value-merger-functions) for merging element deprecation strings from across locations.
@@ -98,17 +101,16 @@ Location settings have top-level keys that specify arbitrary location names, eac
98
101
 
99
102
  The strategy used to merge source schemas into the combined schema is based on each element type:
100
103
 
104
+ - Arguments of fields, directives, and `InputObject` types intersect for each parent element across locations (an element's arguments must appear in all locations):
105
+ - Arguments must share a value type, and the strictest nullability across locations is used.
106
+ - Composition fails if argument intersection would eliminate a non-null argument.
107
+
101
108
  - `Object` and `Interface` types merge their fields and directives together:
102
109
  - Common fields across locations must share a value type, and the weakest nullability is used.
103
- - Field and directive arguments merge using the same rules as `InputObject`.
104
110
  - Objects with unique fields across locations must implement [`@stitch` accessors](../README.md#merged-types).
105
111
  - Shared object types without `@stitch` accessors must contain identical fields.
106
112
  - Merged interfaces must remain compatible with all underlying implementations.
107
113
 
108
- - `InputObject` types intersect arguments from across locations (arguments must appear in all locations):
109
- - Arguments must share a value type, and the strictest nullability across locations is used.
110
- - Composition fails if argument intersection would eliminate a non-null argument.
111
-
112
114
  - `Enum` types merge their values based on how the enum is used:
113
115
  - Enums used anywhere as an argument will intersect their values (common values across all locations).
114
116
  - Enums used exclusively in read contexts will provide a union of values (all values across all locations).
@@ -118,7 +120,6 @@ The strategy used to merge source schemas into the combined schema is based on e
118
120
  - `Scalar` types are added for all scalar names across all locations.
119
121
 
120
122
  - `Directive` definitions are added for all distinct names across locations:
121
- - Arguments merge using the same rules as `InputObject`.
122
123
  - Stitching directives (both definitions and assignments) are omitted.
123
124
 
124
125
  Note that the structure of a composed schema may change based on new schema additions and/or element usage (ie: changing input object arguments in one service may cause the intersection of arguments to change). Therefore, it's highly recommended that you use a [schema comparator](https://github.com/xuorig/graphql-schema_comparator) to flag regressions across composed schema versions.
@@ -65,6 +65,6 @@ It's perfectly fine to mix and match schemas that implement an `_entities` query
65
65
 
66
66
  ### Federation features that will most definitly break
67
67
 
68
- - `@external` fields will confuse the stitching query planner.
68
+ - `@external` fields will confuse the stitching query planner (as the fields aren't natively resolvable at the location).
69
69
  - `@requires` fields will not be sent any dependencies.
70
70
  - No support for Apollo composition directives.
data/docs/mechanics.md CHANGED
@@ -1,47 +1,5 @@
1
1
  ## Schema Stitching, mechanics
2
2
 
3
- ### Modeling foreign keys for stitching
4
-
5
- Foreign keys in a GraphQL schema typically look like the `Product.imageId` field here:
6
-
7
- ```graphql
8
- # -- Products schema:
9
-
10
- type Product {
11
- id: ID!
12
- imageId: ID!
13
- }
14
-
15
- # -- Images schema:
16
-
17
- type Image {
18
- id: ID!
19
- url: String!
20
- }
21
- ```
22
-
23
- However, this design does not lend itself to stitching where types need to _merge_ across locations. A simple schema refactor makes this foreign key more expressive as an entity type, and turns the key into an _object_ that will merge with analogous object types in other locations:
24
-
25
- ```graphql
26
- # -- Products schema:
27
-
28
- type Product {
29
- id: ID!
30
- image: Image!
31
- }
32
-
33
- type Image {
34
- id: ID!
35
- }
36
-
37
- # -- Images schema:
38
-
39
- type Image {
40
- id: ID!
41
- url: String!
42
- }
43
- ```
44
-
45
3
  ### Deploying a stitched schema
46
4
 
47
5
  Among the simplest and most effective ways to manage a stitched schema is to compose it locally, write the composed SDL as a `.graphql` file in your repo, and then load the composed schema into a stitching client at runtime. For example, setup a `rake` task that loads/fetches subgraph schemas, composes them, and then writes the composed schema definition as a file committed to the repo:
@@ -94,7 +52,7 @@ class GraphQlController
94
52
  end
95
53
  ```
96
54
 
97
- This process assures that composition always happens before deployment where failures can be detected. Hot reloading of the supergraph can also be accommodated by uploading the composed schema to a sync location (cloud storage, etc) that is polled by the application runtime. When the schema changes, load it into a new stitching client and swap that into the application.
55
+ This process assures that composition always happens before deployment where failures can be detected. Use CI to verify that the repo's supergraph output is always up to date. Hot reloading of the supergraph can also be accommodated by uploading the composed schema to a sync location (cloud storage, etc) that is polled by the application runtime. When the schema changes, load it into a new stitching client and swap that into the application.
98
56
 
99
57
  ### Field selection routing
100
58
 
data/docs/request.md CHANGED
@@ -15,15 +15,15 @@ request = GraphQL::Stitching::Request.new(
15
15
 
16
16
  A `Request` provides the following information:
17
17
 
18
- - `req.document`: parsed AST of the GraphQL source
19
- - `req.variables`: a hash of user-submitted variables
20
- - `req.string`: the original GraphQL source string, or printed document
21
- - `req.digest`: a SHA2 of the request string
22
- - `req.normalized_string`: printed document string with consistent whitespace
23
- - `req.normalized_digest`: a SHA2 of the normalized string
24
- - `req.operation`: the operation definition selected for the request
25
- - `req.variable_definitions`: a mapping of variable names to their type definitions
26
- - `req.fragment_definitions`: a mapping of fragment names to their fragment definitions
18
+ - `req.document`: parsed AST of the GraphQL source.
19
+ - `req.variables`: a hash of user-submitted variables.
20
+ - `req.string`: the original GraphQL source string, or printed document.
21
+ - `req.digest`: a digest of the request string, hashed by the `Stitching.digest` implementation.
22
+ - `req.normalized_string`: printed document string with consistent whitespace.
23
+ - `req.normalized_digest`: a digest of the normalized string, hashed by the `Stitching.digest` implementation.
24
+ - `req.operation`: the operation definition selected for the request.
25
+ - `req.variable_definitions`: a mapping of variable names to their type definitions.
26
+ - `req.fragment_definitions`: a mapping of fragment names to their fragment definitions.
27
27
 
28
28
  ### Request lifecycle
29
29
 
@@ -32,5 +32,5 @@ component, or you may invoke them manually:
32
32
 
33
33
  1. `request.validate`: runs static validations on the request using the combined schema.
34
34
  2. `request.prepare!`: inserts variable defaults and pre-renders skip/include conditional shaping.
35
- 3. `request.plan`: builds a plan for the request. May act as a setting for plans pulled from cache.
35
+ 3. `request.plan`: builds a plan for the request. May act as a setter for plans pulled from cache.
36
36
  4. `request.execute`: executes the request, and returns the resulting data.
@@ -4,7 +4,7 @@ Stitching is an interesting prospect for subscriptions because socket-based inte
4
4
 
5
5
  ### Composing a subscriptions schema
6
6
 
7
- For simplicity, subscription resolvers should exist together in a single schema (multiple schemas with subscriptions probably aren't worth the confusion). This subscriptions schema may provide basic entity types that will merge with other locations. For example, here's a bare-bones subscriptions schema:
7
+ For simplicity, subscription resolvers are best kept together in a single schema (multiple schemas with subscriptions probably aren't worth the confusion). This subscriptions schema may provide basic entity types that will merge with other locations. For example, here's a bare-bones subscriptions schema:
8
8
 
9
9
  ```ruby
10
10
  class SubscriptionSchema < GraphQL::Schema
@@ -88,7 +88,7 @@ class EntitiesSchema < GraphQL::Schema
88
88
  end
89
89
  ```
90
90
 
91
- These schemas can be composed as normal into a stitching client. The subscriptions schema must be locally-executable while other entity schema(s) may be served from anywhere:
91
+ These schemas can be composed as normal into a stitching client. The subscriptions schema must be locally-executable while the other entity schema(s) may be served from anywhere:
92
92
 
93
93
  ```ruby
94
94
  StitchedSchema = GraphQL::Stitching::Client.new(locations: {
@@ -104,7 +104,7 @@ StitchedSchema = GraphQL::Stitching::Client.new(locations: {
104
104
 
105
105
  ### Serving stitched subscriptions
106
106
 
107
- Once you've stitched a schema with subscriptions, it gets called as part of three workflows:
107
+ Once you've composed a schema with subscriptions, it gets called as part of three workflows:
108
108
 
109
109
  1. Controller - handles normal query and mutation requests recieved via HTTP.
110
110
  2. Channel - handles subscription-create requests recieved through a socket connection.
@@ -122,7 +122,7 @@ class GraphqlController < ApplicationController
122
122
  def execute
123
123
  result = StitchedSchema.execute(
124
124
  params[:query],
125
- context: {},
125
+ context: {},
126
126
  variables: params[:variables],
127
127
  operation_name: params[:operationName],
128
128
  )
@@ -164,7 +164,7 @@ class GraphqlChannel < ApplicationCable::Channel
164
164
 
165
165
  def unsubscribed
166
166
  @subscription_ids.each { |sid|
167
- # Go directly through the subscriptions subschema
167
+ # Go directly through the subscriptions subschema
168
168
  # when managing/triggering subscriptions:
169
169
  SubscriptionSchema.subscriptions.delete_subscription(sid)
170
170
  }
@@ -172,7 +172,7 @@ class GraphqlChannel < ApplicationCable::Channel
172
172
  end
173
173
  ```
174
174
 
175
- What happens behind the scenes here is that stitching filters the `execute` request down to just subscription selections, and passes those through to the subscriptions subschema where they register an event binding. The subscriber response gets stitched while passing back up through the stitching client.
175
+ What happens behind the scenes here is that stitching filters the `execute` request down to just subscription selections, and passes those through to the subscriptions subschema where they register an event binding. The subscriber response gets stitched while passing back out through the stitching client. The `unsubscribed` method works directly with the subschema where subscriptions are managed.
176
176
 
177
177
  #### Plugin
178
178
 
@@ -181,14 +181,14 @@ Lastly, update events trigger with the filtered subscriptions selection, so must
181
181
  ```ruby
182
182
  class StitchedActionCableSubscriptions < GraphQL::Subscriptions::ActionCableSubscriptions
183
183
  def execute_update(subscription_id, event, object)
184
- super(subscription_id, event, object).tap do |result|
185
- result.context[:stitch_subscription_update]&.call(result)
186
- end
184
+ result = super(subscription_id, event, object)
185
+ result.context[:stitch_subscription_update]&.call(result)
186
+ result
187
187
  end
188
188
  end
189
189
 
190
190
  class SubscriptionSchema < GraphQL::Schema
191
- # switch the plugin on the subscriptions schema to use the patched class...
191
+ # switch the plugin on the subscriptions schema to use the patched class...
192
192
  use StitchedActionCableSubscriptions
193
193
  end
194
194
  ```
@@ -200,7 +200,7 @@ Subscription update events are triggered as normal directly through the subscrip
200
200
  ```ruby
201
201
  class Comment < ApplicationRecord
202
202
  after_create :trigger_subscriptions
203
-
203
+
204
204
  def trigger_subscriptions
205
205
  SubscriptionsSchema.subscriptions.trigger(:comment_added_to_post, { post_id: post_id }, self)
206
206
  end
data/docs/supergraph.md CHANGED
@@ -9,17 +9,17 @@ A Supergraph is designed to be composed, cached, and restored. Calling `to_defin
9
9
  ```ruby
10
10
  supergraph_sdl = supergraph.to_definition
11
11
 
12
- # stash this composed schema in a cache...
13
- $cache.set("cached_supergraph_sdl", supergraph_sdl)
14
-
15
- # or, write the composed schema as a file into your repo...
12
+ # write the composed schema as a file into your repo...
16
13
  File.write("supergraph/schema.graphql", supergraph_sdl)
14
+
15
+ # or, stash this composed schema in a cache...
16
+ $cache.set("cached_supergraph_sdl", supergraph_sdl)
17
17
  ```
18
18
 
19
19
  To restore a Supergraph, call `from_definition` providing the cached SDL string and a hash of executables keyed by their location names:
20
20
 
21
21
  ```ruby
22
- supergraph_sdl = $cache.get("cached_supergraph_sdl")
22
+ supergraph_sdl = File.read("supergraph/schema.graphql")
23
23
 
24
24
  supergraph = GraphQL::Stitching::Supergraph.from_definition(
25
25
  supergraph_sdl,
@@ -1,10 +1,10 @@
1
- ## GraphQL::Stitching::Resolver
1
+ ## GraphQL::Stitching::TypeResolver
2
2
 
3
- A `Resolver` contains all information about a root query used by stitching to fetch location-specific variants of a merged type. Specifically, resolvers manage parsed keys and argument structures.
3
+ A `TypeResolver` contains all information about a root query used by stitching to fetch location-specific variants of a merged type. Specifically, resolvers manage parsed keys and argument structures.
4
4
 
5
5
  ### Arguments
6
6
 
7
- Resolvers configure arguments through a template string of [GraphQL argument literal syntax](https://spec.graphql.org/October2021/#sec-Language.Arguments). This allows sending multiple arguments that intermix stitching keys with complex object shapes and other static values.
7
+ Type resolvers configure arguments through a template string of [GraphQL argument literal syntax](https://spec.graphql.org/October2021/#sec-Language.Arguments). This allows sending multiple arguments that intermix stitching keys with complex object shapes and other static values.
8
8
 
9
9
  #### Key insertions
10
10
 
@@ -1,9 +1,9 @@
1
1
  class SubscriptionsSchema < GraphQL::Schema
2
2
  class StitchedActionCableSubscriptions < GraphQL::Subscriptions::ActionCableSubscriptions
3
3
  def execute_update(subscription_id, event, object)
4
- super(subscription_id, event, object).tap do |result|
5
- result.context[:stitch_subscription_update]&.call(result)
6
- end
4
+ result = super(subscription_id, event, object)
5
+ result.context[:stitch_subscription_update]&.call(result)
6
+ result
7
7
  end
8
8
  end
9
9
 
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
- gemspec
5
4
 
6
5
  gem 'graphql', '~> 2.0.0'
6
+ gem 'warning'
7
+ gem 'minitest-stub-const'
8
+
9
+ gemspec path: "../"
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
- gemspec
5
4
 
6
5
  gem 'graphql', '~> 2.1.0'
6
+ gem 'warning'
7
+ gem 'minitest-stub-const'
8
+
9
+ gemspec path: "../"
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
- gemspec
5
4
 
6
5
  gem 'graphql', '~> 2.2.0'
6
+ gem 'warning'
7
+ gem 'minitest-stub-const'
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'graphql', '~> 2.3.0'
6
+ gem 'warning'
7
+ gem 'minitest-stub-const'
8
+
9
+ gemspec path: "../"
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  end
27
27
  spec.require_paths = ['lib']
28
28
 
29
- spec.add_runtime_dependency 'graphql', '>= 1.13.9'
29
+ spec.add_runtime_dependency 'graphql', '>= 2.0'
30
30
 
31
31
  spec.add_development_dependency 'bundler', '~> 2.0'
32
32
  spec.add_development_dependency 'rake', '~> 12.0'
@@ -7,8 +7,6 @@ module GraphQL
7
7
  # Client is an out-of-the-box helper that assembles all
8
8
  # stitching components into a workflow that executes requests.
9
9
  class Client
10
- class ClientError < StitchingError; end
11
-
12
10
  # @return [Supergraph] composed supergraph that services incoming requests.
13
11
  attr_reader :supergraph
14
12
 
@@ -18,9 +16,9 @@ module GraphQL
18
16
  # @param composer [Composer] optional, a pre-configured composer instance for use with `locations` configuration.
19
17
  def initialize(locations: nil, supergraph: nil, composer: nil)
20
18
  @supergraph = if locations && supergraph
21
- raise ClientError, "Cannot provide both locations and a supergraph."
19
+ raise ArgumentError, "Cannot provide both locations and a supergraph."
22
20
  elsif supergraph && !supergraph.is_a?(Supergraph)
23
- raise ClientError, "Provided supergraph must be a GraphQL::Stitching::Supergraph instance."
21
+ raise ArgumentError, "Provided supergraph must be a GraphQL::Stitching::Supergraph instance."
24
22
  elsif supergraph
25
23
  supergraph
26
24
  else
@@ -58,17 +56,17 @@ module GraphQL
58
56
  end
59
57
 
60
58
  def on_cache_read(&block)
61
- raise ClientError, "A cache read block is required." unless block_given?
59
+ raise ArgumentError, "A cache read block is required." unless block_given?
62
60
  @on_cache_read = block
63
61
  end
64
62
 
65
63
  def on_cache_write(&block)
66
- raise ClientError, "A cache write block is required." unless block_given?
64
+ raise ArgumentError, "A cache write block is required." unless block_given?
67
65
  @on_cache_write = block
68
66
  end
69
67
 
70
68
  def on_error(&block)
71
- raise ClientError, "An error handler block is required." unless block_given?
69
+ raise ArgumentError, "An error handler block is required." unless block_given?
72
70
  @on_error = block
73
71
  end
74
72
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GraphQL::Stitching
4
4
  class Composer
5
- class ResolverConfig
5
+ class TypeResolverConfig
6
6
  ENTITY_TYPENAME = "_Entity"
7
7
  ENTITIES_QUERY = "_entities"
8
8
 
@@ -30,7 +30,7 @@ module GraphQL::Stitching
30
30
  entity_type.directives.each do |directive|
31
31
  next unless directive.graphql_name == "key"
32
32
 
33
- key = Resolver.parse_key(directive.arguments.keyword_arguments.fetch(:fields))
33
+ key = TypeResolver.parse_key(directive.arguments.keyword_arguments.fetch(:fields))
34
34
  key_fields = key.map { "#{_1.name}: $.#{_1.name}" }
35
35
  field_path = "#{location}._entities"
36
36
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GraphQL::Stitching
4
4
  class Composer
5
- class ValidateResolvers < BaseValidator
5
+ class ValidateTypeResolvers < BaseValidator
6
6
 
7
7
  def perform(supergraph, composer)
8
8
  root_types = [