graph_weaver 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/README.md +40 -88
- data/docs/alternatives.md +1 -7
- data/docs/cassettes.md +54 -59
- data/docs/editors.md +32 -47
- data/docs/errors.md +261 -369
- data/docs/federation.md +650 -837
- data/docs/generated_modules.md +370 -459
- data/docs/getting_started.md +211 -428
- data/docs/i18n.md +114 -177
- data/docs/logging.md +127 -116
- data/docs/real_world.md +26 -39
- data/docs/scalars.md +277 -310
- data/docs/testing.md +340 -486
- data/docs/transports.md +191 -263
- data/docs/upgrading.md +188 -560
- data/examples/README.md +38 -0
- data/examples/countries.rb +39 -0
- data/examples/federation.rb +62 -0
- data/examples/github/generate.rb +20 -0
- data/examples/github/generated/star_mutation.rb +126 -0
- data/examples/github/generated/stargazers_query.rb +232 -0
- data/examples/github/generated/starred_query.rb +151 -0
- data/examples/github/queries/star.graphql +8 -0
- data/examples/github/queries/stargazers.graphql +22 -0
- data/examples/github/queries/starred.graphql +11 -0
- data/examples/github/run.rb +43 -0
- data/examples/github/setup.rb +18 -0
- data/examples/rick_and_morty.rb +57 -0
- data/graph_weaver.gemspec +12 -3
- data/lib/graph_weaver/client.rb +22 -1
- data/lib/graph_weaver/codegen.rb +5 -1
- data/lib/graph_weaver/context_seam.rb +54 -0
- data/lib/graph_weaver/errors.rb +23 -15
- data/lib/graph_weaver/federation.rb +11 -2
- data/lib/graph_weaver/in_process.rb +15 -9
- data/lib/graph_weaver/internal/endpoint.rb +7 -5
- data/lib/graph_weaver/internal/headers.rb +19 -0
- data/lib/graph_weaver/internal.rb +66 -13
- data/lib/graph_weaver/log_subscriber.rb +10 -2
- data/lib/graph_weaver/logging.rb +33 -13
- data/lib/graph_weaver/query_module.rb +8 -0
- data/lib/graph_weaver/retry.rb +12 -8
- data/lib/graph_weaver/schema_loader.rb +52 -14
- data/lib/graph_weaver/testing/cassette.rb +28 -5
- data/lib/graph_weaver/testing/endpoint.rb +14 -13
- data/lib/graph_weaver/testing/fake_client.rb +33 -3
- data/lib/graph_weaver/testing/router.rb +7 -3
- data/lib/graph_weaver/transport/http.rb +2 -2
- data/lib/graph_weaver/transport.rb +47 -23
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +22 -1
- metadata +16 -3
- data/CHANGELOG.md +0 -3801
data/docs/upgrading.md
CHANGED
|
@@ -1,34 +1,51 @@
|
|
|
1
1
|
# Upgrading
|
|
2
2
|
|
|
3
|
-
[Regenerate](#regenerate-on-every-upgrade) whichever version you're on, then
|
|
4
|
-
|
|
5
|
-
[0.
|
|
6
|
-
|
|
3
|
+
[Regenerate](#regenerate-on-every-upgrade) whichever version you're on, then read
|
|
4
|
+
the one section that is yours: from [0.7.0](#upgrading-from-070) or from
|
|
5
|
+
[0.6.1](#upgrading-from-061). Coming from 0.6.0 or older, the path is that
|
|
6
|
+
version's own upgrade notes — read them at the tag they shipped under
|
|
7
|
+
(`git show v0.7.1:docs/upgrading.md`), then this page from 0.6.1 down.
|
|
7
8
|
|
|
8
9
|
## Regenerate on every upgrade
|
|
9
10
|
|
|
10
|
-
**Any release can change what codegen emits
|
|
11
|
+
**Any release can change what codegen emits**, patch releases included — most of
|
|
11
12
|
them are fixes to a generated type, and a fix to a type is a change to the bytes.
|
|
12
|
-
0.5.1 was a patch and moved three of them.
|
|
13
|
-
|
|
14
13
|
So `rake graph_weaver:generate` is part of upgrading the gem, every time, and
|
|
15
14
|
`rake graph_weaver:verify` is the detector: it fails when the checked-in Ruby
|
|
16
|
-
isn't what this version would write. Nothing beyond that is promised — there is
|
|
17
|
-
|
|
18
|
-
changed
|
|
15
|
+
isn't what this version would write. Nothing beyond that is promised — there is no
|
|
16
|
+
"generated output is stable within a minor" rule to lean on, and what each release
|
|
17
|
+
changed is in the [changelog](../CHANGELOG.md).
|
|
19
18
|
|
|
20
19
|
A generated file's header names the release that wrote it, so the first `verify`
|
|
21
|
-
after an upgrade reports the tree as stale whether or not codegen actually
|
|
22
|
-
|
|
20
|
+
after an upgrade reports the tree as stale whether or not codegen actually moved.
|
|
21
|
+
That's the reminder working, not a false alarm. Generation is deterministic, so
|
|
22
|
+
the diff is exactly what the new version emits differently and nothing else —
|
|
23
|
+
worth reading rather than rubber-stamping.
|
|
24
|
+
|
|
25
|
+
## Upgrading from 0.7.0
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
A patch release of fixes, and a typical app ticks none of these. Read the left
|
|
28
|
+
column and skip what isn't yours; the [changelog](../CHANGELOG.md) says why each
|
|
29
|
+
one moved.
|
|
30
|
+
|
|
31
|
+
| applies if you… | what changed |
|
|
32
|
+
|---|---|
|
|
33
|
+
| read `payload[:code]` in an alert or a dashboard expecting an HTTP status | it is a GraphQL error code or nothing now — the number is on `:http_status`, where it already was |
|
|
34
|
+
| assert a fabricated `userErrors` is non-empty under `graphql: :fake` | a list field whose name ends in `errors` fabricates `[]` — pin it (`{ "userErrors" => [{ "message" => "…" }] }`) to fabricate failures |
|
|
35
|
+
| grep or parse the debug log for `[req 3 …]` | the tag names the process: `[req 4123-3 …]` |
|
|
36
|
+
| run more than one graph with `cache: true` | the second graph caches to `schema-<url digest>.json` of its own instead of sharing the first's dump — `cache: "<path>"` names it yourself |
|
|
37
|
+
| commit your cassette directory | recording takes a `<cassette>.yml.lock` sidecar — gitignore `*.yml.lock` |
|
|
38
|
+
| `rescue ArgumentError` around the union-dispatch refusal | it is a `GraphWeaver::Error` naming the query file now |
|
|
39
|
+
| wrote a client of your own with a bare `attr_accessor :context` and mount it behind `Testing::Endpoint` | include `GraphWeaver::ContextSeam` — the lock lives on whoever owns the field |
|
|
40
|
+
| read `CHANGELOG.md` out of the installed gem | it isn't packaged any more; `changelog_uri` points at `blob/v<version>` |
|
|
41
|
+
| send a `File`, `IO`, `Pathname` or plain object as a variable under `graphql: :in_process` or `:fake` | refused there too now, as it already was over the wire — **a test that "proved" an upload works starts failing** |
|
|
42
|
+
| retry a 429/503 that arrives **with** a GraphQL errors body | a `Retry-After` header wins over the configured backoff now, as it already did for a raised `ServerError` — **nothing raises** |
|
|
26
43
|
|
|
27
44
|
## Upgrading from 0.6.1
|
|
28
45
|
|
|
29
|
-
Mostly mechanical. Everything that wants your hands, or changes under you, is
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
Mostly mechanical. Everything that wants your hands, or changes under you, is one
|
|
47
|
+
row below; read the left column and skip what isn't yours. A typical app ticks two
|
|
48
|
+
or three.
|
|
32
49
|
|
|
33
50
|
| applies if you… | what changed |
|
|
34
51
|
|---|---|
|
|
@@ -87,9 +104,9 @@ bundle exec rspec
|
|
|
87
104
|
rake graph_weaver:verify
|
|
88
105
|
```
|
|
89
106
|
|
|
90
|
-
**Two kinds of file answer that first grep, and only one needs your hands.**
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
**Two kinds of file answer that first grep, and only one needs your hands.** Hits
|
|
108
|
+
under your generated directory (`app/graphql/generated/` by default) are the old
|
|
109
|
+
names in machine-written code — step 3 rewrites them. Hits anywhere else are
|
|
93
110
|
yours: `CastError` and `QueryValidationError`, renamed by hand.
|
|
94
111
|
|
|
95
112
|
### Renames
|
|
@@ -104,555 +121,166 @@ yours: `CastError` and `QueryValidationError`, renamed by hand.
|
|
|
104
121
|
|
|
105
122
|
### Behavior that changed under you
|
|
106
123
|
|
|
107
|
-
The first one reaches every Rails app that never configured logging, and it is
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- **A Rails app logs one line per GraphQL call, and emits one notification.**
|
|
111
|
-
|
|
112
|
-
`
|
|
113
|
-
`GraphWeaver
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
a
|
|
123
|
-
`
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`InputError`'s `#field` is a place to decide which of the two you meant.
|
|
155
|
-
- **The instrumentation payload's `:status` is a Symbol, and the HTTP status
|
|
156
|
-
moved to `:http_status`.** `:status` is now `:ok`, `:errors` (the response
|
|
157
|
-
came back carrying GraphQL errors) or `:failed` (it raised) — a 200 carrying
|
|
158
|
-
errors is not a success, and only a symbol says that on both sides of the
|
|
159
|
-
seam. Nothing raises: a subscriber comparing it to an Integer just stops
|
|
160
|
-
matching. **A subscriber that branched on `payload[:status] == 200`, or on a
|
|
161
|
-
4xx/5xx, reads `:http_status` now** — which is nil in-process, where
|
|
162
|
-
`:status` used to be a fabricated 200 so one subscriber could read both
|
|
163
|
-
sides. The whole payload is a documented contract now; see
|
|
164
|
-
[logging](logging.md#the-payload). **Self-check:** nothing subscribing to
|
|
165
|
-
`execute.graph_weaver` means nothing to change — this reaches subscribers
|
|
166
|
-
only.
|
|
167
|
-
- **`respond_to?` on a result struct no longer answers true for a name that
|
|
168
|
-
doesn't exist.** It used to say true for any near miss, which broke the
|
|
169
|
-
standard duck-typing guard — `obj.pet if obj.respond_to?(:pet)` raised the
|
|
170
|
-
very `NoMethodError` the hint exists to explain. **A branch that read the old
|
|
171
|
-
answer now takes the other path**, and `struct.method(:nmae)` raises Ruby's
|
|
172
|
-
bare `NameError` rather than a hinted one; `struct.nmae` still hints.
|
|
173
|
-
- **A `graphql:` tag reaches a module generated with `client:`.** The baked
|
|
174
|
-
client used to sit above the slot a tag swaps, so a bound module ran against
|
|
175
|
-
its real endpoint under `graphql: :fake`. **If a spec relied on that**, it now
|
|
176
|
-
runs against the fake — pass `client:` on the call, set `MyQuery.client =`, or
|
|
177
|
-
tag the example `graphql: :live`.
|
|
124
|
+
The first one reaches every Rails app that never configured logging, and it is the
|
|
125
|
+
only one here that shows up in production rather than in your code.
|
|
126
|
+
|
|
127
|
+
- **A Rails app logs one line per GraphQL call, and emits one notification.** The
|
|
128
|
+
railtie sets `GraphWeaver.instrumenter` to the `ActiveSupport::Notifications`
|
|
129
|
+
adapter and attaches `GraphWeaver::LogSubscriber`, so an app that configured
|
|
130
|
+
neither gets `GraphWeaver billing/InvoicesQuery (12.3ms) ok` at **info** — the
|
|
131
|
+
query and variables stay at debug. **To opt out, set `GraphWeaver.logger = nil`
|
|
132
|
+
or `GraphWeaver.instrumenter = nil` in `config/initializers`**, which now takes
|
|
133
|
+
effect (an app that worked around that with `config.after_initialize` can drop
|
|
134
|
+
it). In-process calls are in scope too: a bare schema class in a client slot goes
|
|
135
|
+
through the same wrapper, so it produces events and log lines where it produced
|
|
136
|
+
none. See [logging](logging.md).
|
|
137
|
+
- **A `Retry` in front of a gateway starts actually retrying.** It read only the
|
|
138
|
+
failures that *raised*, and Apollo Router answers everything it decides itself
|
|
139
|
+
with a GraphQL errors body, so `retries: 3` made one attempt. A response retries
|
|
140
|
+
now when its status is one a `ServerError` retries on (5xx, 408, 429), or when
|
|
141
|
+
its error codes are named in `retry_codes:`. **This is real traffic you weren't
|
|
142
|
+
sending** — if the inert policy was what you wanted, `retries: 0`.
|
|
143
|
+
- **A task that can't honour `SUPERGRAPH=` refuses instead of ignoring it.** The
|
|
144
|
+
flag reaches the `federation:*` tasks and nothing else, so
|
|
145
|
+
`SUPERGRAPH=public.graphql rake graph_weaver:queries:check` used to report every
|
|
146
|
+
query valid against a supergraph missing fields they select. **A CI step that
|
|
147
|
+
passes it to `generate`, `verify` or `queries:check` goes red**; drop the flag,
|
|
148
|
+
or declare the supergraph on a graph.
|
|
149
|
+
- **`InputError#field` names the input field, not the variable.** It is `#path`'s
|
|
150
|
+
last *named* segment — the slot that actually held the bad value — where it used
|
|
151
|
+
to be re-branded with the *variable* name. Nothing raises; the value just differs
|
|
152
|
+
once a refusal happens inside an input object. **Read `error.path.first` wherever
|
|
153
|
+
you wanted the variable.** An index is a position rather than a field, so
|
|
154
|
+
`execute(ids: [1, 2, "x"])` reports `#path` `["ids", 2]` and `#field` `"ids"`.
|
|
155
|
+
- **The instrumentation payload's `:status` is a Symbol, and the HTTP status moved
|
|
156
|
+
to `:http_status`.** `:status` is `:ok`, `:errors` or `:failed` — a 200 carrying
|
|
157
|
+
errors is not a success, and only a symbol says that on both sides of the seam.
|
|
158
|
+
Nothing raises: a subscriber comparing it to an Integer just stops matching. **A
|
|
159
|
+
subscriber that branched on a 4xx/5xx reads `:http_status` now**, which is nil
|
|
160
|
+
in-process. The whole payload is a documented contract —
|
|
161
|
+
[logging](logging.md#the-payload).
|
|
162
|
+
- **`respond_to?` on a result struct no longer answers true for a name that doesn't
|
|
163
|
+
exist.** It used to say true for any near miss, which broke the standard
|
|
164
|
+
duck-typing guard. **A branch that read the old answer now takes the other path**,
|
|
165
|
+
and `struct.method(:nmae)` raises Ruby's bare `NameError`; `struct.nmae` still
|
|
166
|
+
hints.
|
|
167
|
+
- **A `graphql:` tag reaches a module generated with `client:`.** The baked client
|
|
168
|
+
used to sit above the slot a tag swaps, so a bound module ran against its real
|
|
169
|
+
endpoint under `graphql: :fake`. **If a spec relied on that**, pass `client:` on
|
|
170
|
+
the call, set `MyQuery.client =`, or tag the example `graphql: :live`.
|
|
178
171
|
- **`config.context`, `config.schema` and `config.router` are suite setup.**
|
|
179
172
|
Setting any of the three once an example is running refuses, naming the
|
|
180
173
|
per-example helper (`graphql_context`, `graphql_fake(schema:)`,
|
|
181
|
-
`graphql_router(fake:)`). The tag builds an example's clients in a `before`
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
`
|
|
187
|
-
`
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
pushed.**
|
|
207
|
-
- **`verify` fails when the dump has fallen behind the schema class it was
|
|
208
|
-
built from.** For an app that serves its own schema the dump is an artifact
|
|
209
|
-
derived from code in the same repo, and everything downstream reads it, so
|
|
210
|
-
`generate` and `verify` both called a tree up to date while the live
|
|
211
|
-
resolvers had already moved. **A dump you deliberately keep behind your own
|
|
212
|
-
schema is a red gate now** — `rake graph_weaver:schema:refresh`, or ask about
|
|
213
|
-
no dump at all with `verify_generated!(schema:)`. It costs one in-process
|
|
214
|
-
introspection per graph and never a network call.
|
|
174
|
+
`graphql_router(fake:)`). The tag builds an example's clients in a `before` hook
|
|
175
|
+
of its own, which rspec runs ahead of any group `before`, so a set there was read
|
|
176
|
+
too late and silently changed nothing. **Move it to an `around`, or to
|
|
177
|
+
`GraphWeaver::Testing.configure` in the spec helper.**
|
|
178
|
+
- **`result.to_json` is real JSON, and it is the wire shape.** It used to be Ruby's
|
|
179
|
+
`Object#to_json` — the `#inspect` string, quoted — while under Rails
|
|
180
|
+
`render json: result` shipped the *Ruby* prop names. Both now produce the response
|
|
181
|
+
keys, each leaf back through its scalar registration's `serialize:`, so
|
|
182
|
+
`Result.from_h(JSON.parse(result.to_json)) == result`. `#to_h` is unchanged and
|
|
183
|
+
still the Ruby view. **Anything that parsed the old output is reading something
|
|
184
|
+
different now** — and `as_json` is emitted code, so a struct generated by 0.6.1
|
|
185
|
+
raises `GraphWeaver::Error` naming this until you regenerate.
|
|
186
|
+
- **A schema dump introspected through a credentialed url still holds the token.**
|
|
187
|
+
The provenance stamp wrote the transport's url verbatim. It records the endpoint
|
|
188
|
+
bare now — userinfo and any query parameter `filter_parameters` filters are
|
|
189
|
+
dropped — and re-introspection still authenticates from the dump's `auth_env`.
|
|
190
|
+
**Run `rake graph_weaver:schema:refresh` once, and rotate the token if that file
|
|
191
|
+
was ever pushed.**
|
|
192
|
+
- **`verify` fails when the dump has fallen behind the schema class it was built
|
|
193
|
+
from.** For an app that serves its own schema the dump is an artifact derived
|
|
194
|
+
from code in the same repo, so `generate` and `verify` both called a tree up to
|
|
195
|
+
date while the live resolvers had already moved. **A dump you deliberately keep
|
|
196
|
+
behind your own schema is a red gate now** — `rake graph_weaver:schema:refresh`,
|
|
197
|
+
or ask about no dump at all with `verify_generated!(schema:)`. It costs one
|
|
198
|
+
in-process introspection per graph and never a network call.
|
|
215
199
|
- **`@oneOf` starts being enforced if your dump is `.json`.** graphql-ruby's
|
|
216
|
-
introspection query omits `isOneOf` unless asked,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- **Regenerate**, as ever — generated modules carry a private `GRAPH` naming the
|
|
234
|
-
graph they were generated from, and a [multi-schema](getting_started.md#more-than-one-schema)
|
|
235
|
-
app whose modules predate it refuses rather than guessing which schema a
|
|
236
|
-
module belongs to. A generated `execute` also makes its request through the
|
|
237
|
-
gem now (`from_response(dispatch(variables, client:))`), which is what lets
|
|
238
|
-
an event name the graph; 0.6.1's modules keep working as they are, but `rake
|
|
239
|
-
graph_weaver:verify` reports the tree out of date until you regenerate.
|
|
240
|
-
Result structs also gained `==`/`eql?`/`hash`, `deconstruct_keys`, `#to_h`
|
|
241
|
-
and `#as_json`, and the emitted guard in front of a `cast:` changed (below).
|
|
242
|
-
- **Check that your `require "graph_weaver/rspec"` actually runs.** The old
|
|
243
|
-
setup put it in `spec/support/graph_weaver.rb`, and rspec-rails ships the
|
|
244
|
-
`spec/support` glob **commented out** — so if you never uncommented it, the
|
|
245
|
-
tag did nothing and every `graphql: :fake` example has been hitting the real
|
|
246
|
-
client. `rails g graph_weaver:install` now writes the line into
|
|
247
|
-
`spec/rails_helper.rb` instead; **move yours there** if the glob isn't live.
|
|
248
|
-
- **`graphql: :wire`, if you adopt it, needs webmock *enabled*** — `require
|
|
249
|
-
"webmock/rspec"` in the spec helper. Having it in the Gemfile is not enough:
|
|
250
|
-
`Bundler.require` loads webmock without installing its adapters, and the tag
|
|
251
|
-
refuses before the first request rather than letting it leave the suite.
|
|
252
|
-
- **A `DateTime` given for a `Date` variable is refused.** `DateTime` is a
|
|
253
|
-
`Date` to Ruby, so it used to pass the cast untouched and go on the wire as
|
|
254
|
-
`"2024-01-15T10:20:30+00:00"` where the schema said `ISO8601Date` — a lenient
|
|
255
|
-
server truncated it, a strict one refused it. Truncating it here would be the
|
|
256
|
-
same guess made silently, so it now raises an `InputError` naming the class
|
|
257
|
-
and the fix: `$d of On: expected a Date, got a DateTime — pass .to_date if
|
|
258
|
-
dropping the time of day is what you meant`. **Pass `.to_date` where a
|
|
259
|
-
`DateTime` reaches a `Date` variable.** The pairings that already raised —
|
|
260
|
-
a `Time` for a date, a `Date` for a timestamp — now raise that branded
|
|
261
|
-
`InputError` rather than Ruby's *"no implicit conversion of Time into
|
|
200
|
+
introspection query omits `isOneOf` unless asked, so every dump this gem had
|
|
201
|
+
written said "not @oneOf" for every input object. **Regenerate and the emitted
|
|
202
|
+
`ONE_OF` starts refusing calls that set two fields** — which your server was
|
|
203
|
+
refusing all along, so the failure moves from the wire into `execute`. SDL dumps,
|
|
204
|
+
inline SDL and a live class were always correct.
|
|
205
|
+
- **A fake pin is told from an option by a schema lookup, not by casing.** A
|
|
206
|
+
lowercase type could not be pinned at all (`graphql_fake("pokemon_v2_pokemon" =>
|
|
207
|
+
…)` against a Hasura API); those pins work now. The other side of it: **a keyword
|
|
208
|
+
that is a near-miss for a pin (`Persn: "Ada"`) raises `ArgumentError` from the
|
|
209
|
+
fake** rather than `GraphWeaver::Error` from the override check — the same key
|
|
210
|
+
written in the leading positional hash is unchanged, and is the spelling for a
|
|
211
|
+
schema whose vocabulary collides with an option name.
|
|
212
|
+
- **A `DateTime` given for a `Date` variable is refused.** `DateTime` is a `Date`
|
|
213
|
+
to Ruby, so it used to pass the cast untouched and go on the wire as a full
|
|
214
|
+
timestamp where the schema said `ISO8601Date`. **Pass `.to_date`.** The pairings
|
|
215
|
+
that already raised — a `Time` for a date, a `Date` for a timestamp — now raise a
|
|
216
|
+
branded `InputError` rather than Ruby's *"no implicit conversion of Time into
|
|
262
217
|
String"*, and a `DateTime` or `Time.zone.now` for a *timestamp* converts
|
|
263
218
|
losslessly where it used to raise.
|
|
264
|
-
- **A `cast:` of your own gets the same guard and the same verdict.** A
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
`"BigDecimal"`, and an input object reads its schema name rather than the
|
|
277
|
-
class generated for it; the *message* still names the Ruby you may pass.
|
|
278
|
-
**A spec matching the old
|
|
279
|
-
message, or branching on `:unparseable` for a wrong class, needs updating**
|
|
280
|
-
— and the guard is emitted into your generated files, so a checked-in one
|
|
281
|
-
keeps the old behavior until you regenerate.
|
|
219
|
+
- **A `cast:` of your own gets the same guard and the same verdict.** A registration
|
|
220
|
+
like `register_scalar("Date", Date, cast: :iso8601, serialize: :iso8601)` emitted
|
|
221
|
+
a bare `value.is_a?(Date)` pass-through, so a `DateTime` went by untouched and
|
|
222
|
+
your `serialize:` wrote a full timestamp into a date field — **pass `.to_date`
|
|
223
|
+
there too**. Anything else wrong used to arrive as Ruby's own sentence under
|
|
224
|
+
`kind: :unparseable`; the verdict is the library's now and splits the way Ruby
|
|
225
|
+
does — a `TypeError` from a codec reads `expected a Date, got 5` under
|
|
226
|
+
`:type_mismatch`, an `ArgumentError` keeps the parser's words under
|
|
227
|
+
`:unparseable`. **`#details[:type]` is the GraphQL type now, never a Ruby class.**
|
|
228
|
+
A spec matching the old message, or branching on `:unparseable` for a wrong class,
|
|
229
|
+
needs updating — and the guard is emitted into your generated files, so a
|
|
230
|
+
checked-in one keeps the old behavior until you regenerate.
|
|
282
231
|
- **A field whose name a struct already answers to now generates as `name_`.**
|
|
283
|
-
`class` becomes
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
a list the gem owns, rather than whatever `T::Struct` answered to in the
|
|
299
|
-
generating process: deriving them made generation depend on require order, so
|
|
300
|
-
with ActiveSupport loaded first a key named `asJson` was refused and loaded
|
|
301
|
-
second it became a prop that shadowed the real `#as_json`. The list is what a
|
|
302
|
-
struct answers — `T::Struct` and `Object`'s public instance methods, the
|
|
303
|
-
hooks Ruby and Rails call on an object that doesn't define one (`initialize`,
|
|
304
|
-
`to_ary`, `to_hash`, `to_json`, `as_json`, `to_param`, `try`, `presence`,
|
|
305
|
-
`each`, `deconstruct_keys`), and the methods the gem's own mixins define — so
|
|
306
|
-
a few more names move than 0.6.1 touched. Kernel's *private* methods are not
|
|
307
|
-
on it: `format`, `select`, `test`, `open`, `load` and `pp` are ordinary
|
|
308
|
-
column names, and the gem's mixins qualify their own calls (`Kernel.raise`)
|
|
309
|
-
so a prop may take one. A federation `@key` on such a field follows the same
|
|
310
|
-
rule instead of being refused: the kwarg takes the underscore
|
|
311
|
-
(`Representations.room(class_: …)`) and `"class"` still goes on the wire, so
|
|
312
|
-
**regenerate if a `@key` of yours names one**. Generated source marks each
|
|
313
|
-
rename on the line above the prop — `# wire: class — reserved as a prop
|
|
314
|
-
name` — so **read the regenerate diff** rather than grepping for the names
|
|
315
|
-
yourself.
|
|
232
|
+
`class` becomes `class_`, and so on for `hash`, `display`, `to_json`, `each` and
|
|
233
|
+
(on an input) `supplied`. Only the Ruby name moves: the wire keeps the schema's
|
|
234
|
+
spelling in both directions, so a refusal on that field still reports `#path`
|
|
235
|
+
`["class"]`, and an input struct's `#to_h` is still the wire hash. A key you
|
|
236
|
+
aliased in the query to get past the old refusal still generates from that alias
|
|
237
|
+
— **drop the alias and regenerate** if you want the field's own name back. The
|
|
238
|
+
names that take an underscore are a list the gem owns (`T::Struct` and `Object`'s
|
|
239
|
+
public instance methods, the hooks Ruby and Rails call on an object, and the
|
|
240
|
+
gem's own mixins) rather than whatever the generating process happened to have
|
|
241
|
+
loaded, so a few more names move than 0.6.1 touched; Kernel's *private* methods
|
|
242
|
+
are not on it, so `format`, `select`, `open` and `load` stay ordinary props. A
|
|
243
|
+
federation `@key` on such a field follows the same rule instead of being refused
|
|
244
|
+
— the kwarg takes the underscore and `"class"` still goes on the wire, so
|
|
245
|
+
**regenerate if a `@key` of yours names one**. Generated source marks each rename
|
|
246
|
+
on the line above the prop, so **read the regenerate diff**.
|
|
316
247
|
- **If you adopt `GraphWeaver.graph`, every queries directory needs a graph.**
|
|
317
|
-
Declaring one replaces the implicit graph your top-level settings describe,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
(`queries`/`output`), declare a graph for it, or delete it.** An app that
|
|
323
|
-
declares no graph is unaffected.
|
|
248
|
+
Declaring one replaces the implicit graph your top-level settings describe, so a
|
|
249
|
+
graph declared beside an existing `app/graphql/queries` used to leave that
|
|
250
|
+
directory unread. `generate!`, `verify_generated!` and `check_queries` refuse
|
|
251
|
+
now, naming the stray files. **Name the directory in a graph, declare a graph for
|
|
252
|
+
it, or delete it.** An app that declares no graph is unaffected.
|
|
324
253
|
- **A `client` that isn't a constant is refused at generation.** Its value is
|
|
325
|
-
spelled into every module the graph generates, so `client` given an endpoint
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
`client "CLIENT"` — which is what the message says.
|
|
254
|
+
spelled into every module the graph generates, so `client` given an endpoint url
|
|
255
|
+
emitted a file that doesn't parse, from a run that reported success. Declare the
|
|
256
|
+
constant and name it — `CLIENT = GraphWeaver.new(url)`, then `client "CLIENT"`.
|
|
329
257
|
- **A `cast:` or `serialize:` proc that returns a value is refused at
|
|
330
258
|
registration.** A proc there builds *source* for the generated file, so
|
|
331
|
-
`cast: ->(v) { v.to_sym }` interpolated to nothing and every response failed
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
name a method instead (`cast: :parse`).
|
|
259
|
+
`cast: ->(v) { v.to_sym }` interpolated to nothing and every response failed far
|
|
260
|
+
from the registration. It is probed once when registered now: return the source
|
|
261
|
+
(`cast: ->(v) { "Money.parse(#{v})" }`) or name a method instead.
|
|
335
262
|
- **`config.graph_weaver` refuses a key the railtie doesn't read**, at boot. It
|
|
336
|
-
takes `watch`; `config.graph_weaver.queries_paths = …` was taken silently and
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
`execute(first: params[:first])` converts the String to an `Integer` — for every
|
|
362
|
-
variable and every input-object field, with nothing to switch on. The old way of
|
|
363
|
-
buying that was `GraphWeaver.auto_coerce` or `register_scalar(…, coerce: true)`,
|
|
364
|
-
and both paid for it by **widening the emitted kwarg**, which switched off the
|
|
365
|
-
static check at every call site. Delete them:
|
|
366
|
-
|
|
367
|
-
```ruby
|
|
368
|
-
GraphWeaver.auto_coerce = true # gone
|
|
369
|
-
GraphWeaver.register_scalar("Money", Money, coerce: true) # drop the coerce:
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
Behavior is unchanged; the kwarg is not. It is now typed exactly as the schema
|
|
373
|
-
types it, so a call site passing a **literal** of the wrong type is a new
|
|
374
|
-
`srb tc` error — which is the point, since a literal is one you can just spell
|
|
375
|
-
right:
|
|
376
|
-
|
|
377
|
-
```ruby
|
|
378
|
-
StargazersQuery.execute(first: "10") # srb tc error now
|
|
379
|
-
StargazersQuery.execute(first: params[:first]) # fine, and "10" becomes 10
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
`cast:` is what a loose value converts through, so a custom scalar needs nothing
|
|
383
|
-
beyond the registration it already has. Bad input raises
|
|
384
|
-
`GraphWeaver::InputError` naming the variable, the operation and the value.
|
|
385
|
-
|
|
386
|
-
Two conversions got **stricter** at the same time, and either can bite an app
|
|
387
|
-
that was passing. A numeric string is now read as a wire format rather than as
|
|
388
|
-
Ruby source, so `"010"` is ten rather than eight and `"0x1f"` and `"1_0"` are
|
|
389
|
-
refused. And a `Boolean` refuses a String outright — every rule for `"0"` and
|
|
390
|
-
`"off"` is somebody's convention, so convert at the call site.
|
|
391
|
-
|
|
392
|
-
### `nil` sends `null`
|
|
393
|
-
|
|
394
|
-
A variable passed `nil` now sends an explicit `null`; one left out is still left
|
|
395
|
-
out. That's what lets a mutation clear a field — and it changes what a kwarg fed
|
|
396
|
-
a possibly-missing value means:
|
|
397
|
-
|
|
398
|
-
```ruby
|
|
399
|
-
UpdateProfile.execute!(bio: params[:bio]) # a missing param used to omit; now it clears the bio
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
**Grep for kwargs fed straight from `params` or an optional attribute**, and
|
|
403
|
-
pass the keyword only when you mean it:
|
|
404
|
-
|
|
405
|
-
```ruby
|
|
406
|
-
UpdateProfile.execute!(**(params[:bio] ? { bio: params[:bio] } : {}))
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
Non-null variables are unaffected: they can't carry `null`, so `nil` there still
|
|
410
|
-
omits and the schema default applies. Input objects get the distinction only
|
|
411
|
-
where a Hash can express it — `coerce({nickname: nil})` sends null, `coerce({})`
|
|
412
|
-
omits, and a struct built with `.new` can't tell the two apart, so `nil` there
|
|
413
|
-
still means omit.
|
|
414
|
-
|
|
415
|
-
### Renames
|
|
416
|
-
|
|
417
|
-
| before | after |
|
|
418
|
-
|---|---|
|
|
419
|
-
| `Retry.new(tries: n)`, `retries: { tries: n }` | `retries: n - 1` — one word everywhere, counting the attempts *after* the first, so `retries: 0` is one attempt and `GraphWeaver.new(url, retries: 3)` is four |
|
|
420
|
-
| `GraphWeaver.new(url, retries: { retries: 5, retry_codes: […] })` | `GraphWeaver.new(url, retries: 5, retry_codes: […])` — the other retry options sit beside the count; the Hash form read as a key nested in itself |
|
|
421
|
-
| `Retry.new(t, on: […])` | `Retry.new(t, retry_on: […])` |
|
|
422
|
-
| `Retry.new(t, base: 0.5, max: 30)` | `Retry.new(t, base_delay: 0.5, max_delay: 30)` — beside a count, `max: 30` read as a second, larger attempt count |
|
|
423
|
-
| `Codegen.generate(module_name:)` | `name:` — the spelling `GraphWeaver.parse` already used; `module_name:` now raises, naming its replacement |
|
|
424
|
-
| `Testing.config.null_chance = 0.3` | `graphql_fake(null_chance: 0.3)`, on the example that wants it |
|
|
425
|
-
| `Testing.config.mode = :literal` | `graphql_fake(values: :literal)`, likewise |
|
|
426
|
-
| `Testing::MODES` | `Testing::VALUE_STYLES` |
|
|
427
|
-
| `SchemaLoader.stale?(path)` | `SchemaLoader.diff(path).empty?` — and `diff` also names what moved |
|
|
428
|
-
|
|
429
|
-
The two `Testing.config` deletions are the ones worth a sentence. A suite-wide
|
|
430
|
-
`null_chance` answers a per-example question, so it nils an unrelated field one
|
|
431
|
-
run in ten, on a seed the failure doesn't name; move it onto the examples that
|
|
432
|
-
are *about* an empty state. (`config.default_mode` and the `graphql: :fake` tag
|
|
433
|
-
are untouched — the per-fake `mode:` became `values:` so the two can't be
|
|
434
|
-
confused for each other.) Every retry misspelling raises rather than being
|
|
435
|
-
ignored: the Hash form names its flat replacement, and a retry option passed
|
|
436
|
-
without `retries:` says so.
|
|
437
|
-
|
|
438
|
-
### The internals moved behind `Internal`
|
|
439
|
-
|
|
440
|
-
The public surface is now what the docs name, what generated code calls, and the
|
|
441
|
-
`execute` slot; everything else sits under `GraphWeaver::Internal` or went
|
|
442
|
-
`private`, and a spec diffs the two so the next accidental promotion fails CI.
|
|
443
|
-
Nothing documented moved — skip this section unless `srb tc` or a
|
|
444
|
-
`NoMethodError` says otherwise.
|
|
445
|
-
|
|
446
|
-
What a suite might plausibly have reached for: the federation query planner and
|
|
447
|
-
its IR (`Internal::Planner`), the fake-value engine (`Internal::Values`), the
|
|
448
|
-
selection walk (`Internal::Selection` — so `FakeClient` no longer answers
|
|
449
|
-
`each_field` or `gather`), the cassette matching rules (`Internal::RequestKey`),
|
|
450
|
-
subgraph detection (was `Testing::Subgraphs`), `GraphWeaver.log` /
|
|
451
|
-
`.instrument` / `.filter_variables` (`Internal::Log` — `logger=`,
|
|
452
|
-
`instrumenter=` and `filter_parameters=` are unchanged), and
|
|
453
|
-
`Transport.operation_name` / `.mutation?` / `.log_tag`, which left the class you
|
|
454
|
-
subclass for `Internal::Wire`.
|
|
455
|
-
|
|
456
|
-
Two smaller edges. `SchemaDiff::Change`, `Cassette::Check`, `Coverage::Result`
|
|
457
|
-
and `InputStruct::Field` are `Data` now rather than `Struct`, so they hand out
|
|
458
|
-
no writers — read one, build a new one to change a field. And generated modules
|
|
459
|
-
keep their own plumbing to themselves: `DEFAULT_CLIENT`, `FIELDS` and `ONE_OF`
|
|
460
|
-
are emitted `private_constant`, so **regenerate**.
|
|
461
|
-
|
|
462
|
-
### Behavior that changed under you
|
|
463
|
-
|
|
464
|
-
- **A mutation is no longer retried.** A timeout doesn't say whether the server
|
|
465
|
-
applied it, and a second `charge` is worse than a failed one. Pass
|
|
466
|
-
`retry_mutations: true` for an API whose mutations are idempotent.
|
|
467
|
-
- **A registration this schema can't match warns instead of failing
|
|
468
|
-
generation.** One registry serves a whole federated graph, so a name the
|
|
469
|
-
schema in hand doesn't declare may belong to the subgraph next door — see
|
|
470
|
-
[federation](federation.md#generating-for-a-federated-graph). Your typo is now
|
|
471
|
-
in the list `rake graph_weaver:generate` prints after the files, so read it.
|
|
472
|
-
- **`verify_generated!` fails when it finds no query documents.** A mistyped
|
|
473
|
-
`queries_paths` used to leave a CI gate green forever.
|
|
474
|
-
- **The local router refuses a `@fromContext` argument** rather than fetching
|
|
475
|
-
the field with it unset. Federation 2.8's `@context` machinery was on the
|
|
476
|
-
routing table's known list, so the argument was read and dropped. Per query,
|
|
477
|
-
like `@interfaceObject`: a subtree one subgraph answers whole still runs.
|
|
478
|
-
- **A `#trace` assertion may see one entry fewer.** Two `@requires` field sets
|
|
479
|
-
crossing into the same subgraph on the same `@key` now ride one entity fetch,
|
|
480
|
-
the way Apollo's do.
|
|
481
|
-
- **Fabricating a custom scalar registered as a class of your own needs a pin
|
|
482
|
-
for the type** — `Testing.config.overrides = { "Money" => "12.00" }`, or the
|
|
483
|
-
same key on one example's `graphql_fake`. Without one, `FakeClient` and
|
|
484
|
-
cassette anonymization refuse rather than feeding your cast a `"Money-1"`
|
|
485
|
-
placeholder. Scalars registered as `BigDecimal`, `Time`, `Date`, `Integer`,
|
|
486
|
-
`Float`, `String` or `T::Boolean` need nothing.
|
|
487
|
-
- **Re-run `rake graph_weaver:cassettes:anonymize`** on any committed cassette
|
|
488
|
-
holding a registered custom scalar: the anonymizer used to write a value the
|
|
489
|
-
generated codec couldn't read back.
|
|
490
|
-
- **Generation refuses four more things**, each naming its fix — a
|
|
491
|
-
`register_scalar` whose Ruby type nothing can build out of JSON (a value
|
|
492
|
-
object of your own: give it a `cast:`), a result key that would shadow a constant the
|
|
493
|
-
file uses, an enum value that camelizes to nothing, and a narrowed fragment
|
|
494
|
-
whose `__typename` sits behind `@skip`/`@include`.
|
|
495
|
-
|
|
496
|
-
## Upgrading to 0.5.0
|
|
497
|
-
|
|
498
|
-
0.5.0 is one large breaking release. Almost all of it is caught mechanically,
|
|
499
|
-
in this order:
|
|
500
|
-
|
|
501
|
-
```sh
|
|
502
|
-
# 1. rename the path settings first — generate won't load without them
|
|
503
|
-
# (queries_path -> queries_paths, generated_path -> generated_paths,
|
|
504
|
-
# fragments_path -> fragments_paths; see "Path settings are lists" below)
|
|
505
|
-
|
|
506
|
-
bundle exec tapioca gem graph_weaver # 2. regenerate the RBI
|
|
507
|
-
rake graph_weaver:generate # 3. the emitted call shape changed
|
|
508
|
-
srb tc # 4. every call site that moved is an error
|
|
509
|
-
rake graph_weaver:verify # 5. fails until the tree is regenerated
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
**Step 2 is not optional.** Against the 0.4.6 RBI, `srb tc` reports errors
|
|
513
|
-
pointing into your `generated/` directory — `QueryModule`, `client_for`,
|
|
514
|
-
`check_envelope!` — which read as though codegen emitted broken Ruby. It
|
|
515
|
-
didn't; sorbet is checking new generated code against the old gem's types.
|
|
516
|
-
Regenerate the RBI and what remains is only your own call sites.
|
|
517
|
-
|
|
518
|
-
Generated code is `# typed: strict`, so step 4 finds those for you. The rest of
|
|
519
|
-
this page is what a typechecker can't see.
|
|
520
|
-
|
|
521
|
-
### `execute` means one thing now
|
|
522
|
-
|
|
523
|
-
Every client answers the same call — `execute(query, variables:, operation_name:)`,
|
|
524
|
-
returning the raw response hash. `Client` used to spell something else under
|
|
525
|
-
that name, which is why `Retry.new(client)` and `Sequence.new(client, fake)`
|
|
526
|
-
raised `ArgumentError`. They work now.
|
|
527
|
-
|
|
528
|
-
The one-shot sugar moved to `run`:
|
|
529
|
-
|
|
530
|
-
```ruby
|
|
531
|
-
client.execute!("query { … }", id: "1") # before
|
|
532
|
-
client.run!("query { … }", id: "1") # after (and #run for the envelope)
|
|
533
|
-
|
|
534
|
-
GraphWeaver.execute(source, query, **vars) # before
|
|
535
|
-
GraphWeaver.run(source, query, **vars) # after
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
**This one is worth grepping for.** `Client#execute` still exists, so a stale
|
|
539
|
-
call fails at runtime rather than at typecheck — as do `GraphWeaver.execute`
|
|
540
|
-
and `GraphWeaver.reset_scalars!`, which are simply gone and will not be flagged
|
|
541
|
-
until the RBI is regenerated (step 2): `rg '\.execute!?\(' --type ruby`
|
|
542
|
-
and check each hit is passing `variables:` rather than loose kwargs.
|
|
543
|
-
|
|
544
|
-
A generated module takes its per-call client as a **keyword**:
|
|
545
|
-
|
|
546
|
-
```ruby
|
|
547
|
-
PersonQuery.execute(some_client, id: "1") # before
|
|
548
|
-
PersonQuery.execute(client: some_client, id: "1") # after
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
`GraphWeaver.resolve_transport` is gone; nothing needs unwrapping any more.
|
|
552
|
-
|
|
553
|
-
### Path settings are lists
|
|
554
|
-
|
|
555
|
-
`queries_paths`, `generated_paths`, `fragments_paths` — every entry is read.
|
|
556
|
-
Assigning a String still works, so the change is the name:
|
|
557
|
-
|
|
558
|
-
```ruby
|
|
559
|
-
GraphWeaver.queries_path = "app/graphql/queries" # before
|
|
560
|
-
GraphWeaver.queries_paths = "app/graphql/queries" # after
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
`schema_path` stays singular: one run reads one schema.
|
|
564
|
-
|
|
565
|
-
### One reset
|
|
566
|
-
|
|
567
|
-
`GraphWeaver.reset_registrations!` is the clean slate between tests, or between
|
|
568
|
-
generations for different schemas. The four
|
|
569
|
-
narrow ones moved to where they live:
|
|
570
|
-
|
|
571
|
-
```ruby
|
|
572
|
-
GraphWeaver.reset_scalars! # before
|
|
573
|
-
GraphWeaver::Codegen.reset_scalars! # after (also reset_enums!, clear_scalars!,
|
|
574
|
-
# reset_type_helpers!)
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
### Generated names come from the response key, not the type
|
|
578
|
-
|
|
579
|
-
Nested structs used to be named for the GraphQL *type* they were cast from;
|
|
580
|
-
they are now named for the **response key that selects them**, camelized, and
|
|
581
|
-
the constant path reads like the query. The typechecker finds the call sites in
|
|
582
|
-
a `# typed: true` file (an unresolved constant is an `srb tc` error); in a
|
|
583
|
-
`# typed: false` file it is `uninitialized constant` at runtime, so grep for
|
|
584
|
-
`::Result::` there.
|
|
585
|
-
|
|
586
|
-
| selection | before (type) | after (key) |
|
|
587
|
-
|---|---|---|
|
|
588
|
-
| `person { pets { name } }` | `PersonQuery::Result::Person::Pet` | `PersonQuery::Result::Person::Pets` |
|
|
589
|
-
| `payrollRisk { score }` | `…::Result::RiskAssessment` | `…::Result::PayrollRisk` |
|
|
590
|
-
| `_entities(…) { ... on Product { … } }` | `…::Result::Product` | `…::Result::Entities::Product` |
|
|
591
|
-
|
|
592
|
-
The key is used verbatim — no pluralization, so a list field `pets` is `Pets`.
|
|
593
|
-
To pick the name yourself, alias the field: `pet: pets { name }` generates
|
|
594
|
-
`Pet`. Union and interface members keep their type-condition names, nested in
|
|
595
|
-
the container the field names. The payoff is that adding, removing or
|
|
596
|
-
reordering an unrelated selection can never rename a struct you reference.
|
|
597
|
-
|
|
598
|
-
**Enums moved out of the result tree.** Every schema enum a query touches is one
|
|
599
|
-
Ruby type in the shared module, `GraphQLTypes::Species`, so a value read from
|
|
600
|
-
one query hands straight into another's variable. A query module aliases the
|
|
601
|
-
enums its *variables* use (`AddPetMutation::Species` still works); an enum
|
|
602
|
-
reached only through a result is no longer nested under the struct that
|
|
603
|
-
carries it — `SearchQuery::Result::Search::Species` is `GraphQLTypes::Species`.
|
|
604
|
-
|
|
605
|
-
### Smaller renames
|
|
606
|
-
|
|
607
|
-
| before | after |
|
|
608
|
-
|---|---|
|
|
609
|
-
| `Testing.config.auto_fake = true` | `Testing.config.default_mode = :fake` |
|
|
610
|
-
| a mutation's `…Query` module | `…Mutation` |
|
|
611
|
-
| `graphql: :none` (rspec tag) | `graphql: :live` |
|
|
612
|
-
|
|
613
|
-
**The shared types module was three, and is now one.** `GraphQLInputs`,
|
|
614
|
-
`GraphQLEnums` and `GraphQLUnions` are all `GraphQLTypes`, and the files move
|
|
615
|
-
with them — `generated/inputs/` becomes `generated/types/`. The three settings
|
|
616
|
-
that named them (`inputs_module=`, `enums_module=`, `unions_module=`) are one
|
|
617
|
-
`types_module=`. Regenerating writes the new tree; delete the old directory,
|
|
618
|
-
which pruning leaves behind empty.
|
|
619
|
-
|
|
620
|
-
If your specs run one schema class in-process while your client points at a
|
|
621
|
-
different API, name it — per example, since a federated suite runs more than
|
|
622
|
-
one:
|
|
623
|
-
|
|
624
|
-
```ruby
|
|
625
|
-
graphql_in_process(MySchema) # in the example
|
|
626
|
-
GraphWeaver::Testing.config.schema = MySchema # or once, for the whole suite
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
### Registering from Rails
|
|
630
|
-
|
|
631
|
-
A registration naming one of your own constants belongs in a `to_prepare` block
|
|
632
|
-
— the same place the in-process client goes, and for the same reason:
|
|
633
|
-
autoloading is set up after `config/initializers` run.
|
|
634
|
-
|
|
635
|
-
```ruby
|
|
636
|
-
Rails.application.config.to_prepare do
|
|
637
|
-
GraphWeaver.register_enum("Species", PetKind, fallback: PetKind::Unknown)
|
|
638
|
-
GraphWeaver.extend_type("Pet", PetHelpers)
|
|
639
|
-
end
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
Generation depends on `:environment`, which runs `to_prepare` too, so the
|
|
643
|
-
registration is in place before it emits — and at boot the generated files
|
|
644
|
-
load from a `to_prepare` block of their own, after yours.
|
|
645
|
-
|
|
646
|
-
### If you use the federation router
|
|
647
|
-
|
|
648
|
-
Detection only sees *loaded* schema classes, and Rails does not eager load for
|
|
649
|
-
rake or in the default test environment. Both are one line:
|
|
650
|
-
|
|
651
|
-
```ruby
|
|
652
|
-
config.eager_load = true # config/environments/test.rb
|
|
653
|
-
config.rake_eager_load = true # config/application.rb
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
Without them the `federation:*` tasks silently see nothing — and
|
|
657
|
-
`federation:diff` now **fails** rather than reporting a green "matches" over
|
|
658
|
-
zero subgraphs.
|
|
263
|
+
takes `watch`; `config.graph_weaver.queries_paths = …` was taken silently and did
|
|
264
|
+
nothing. **Move it to `GraphWeaver.queries_paths =`.**
|
|
265
|
+
- **A router's `fake:` refuses `seed:`**, as `graphql_fake` already did. A router is
|
|
266
|
+
built once for the suite, so a seed there would pin every example to one run —
|
|
267
|
+
`rspec --seed 1234` reproduces the fabricated data along with the test order, and
|
|
268
|
+
`GraphWeaver::Testing.config.seed` is the override for a harness that isn't rspec.
|
|
269
|
+
- **Check that your `require "graph_weaver/rspec"` actually runs.** The old setup
|
|
270
|
+
put it in `spec/support/graph_weaver.rb`, and rspec-rails ships the `spec/support`
|
|
271
|
+
glob **commented out** — so if you never uncommented it, the tag did nothing and
|
|
272
|
+
every `graphql: :fake` example has been hitting the real client.
|
|
273
|
+
`rails g graph_weaver:install` writes the line into `spec/rails_helper.rb`
|
|
274
|
+
instead; **move yours there** if the glob isn't live.
|
|
275
|
+
- **`graphql: :wire`, if you adopt it, needs webmock *enabled*** — `require
|
|
276
|
+
"webmock/rspec"` in the spec helper. Having it in the Gemfile is not enough:
|
|
277
|
+
`Bundler.require` loads webmock without installing its adapters, and the tag
|
|
278
|
+
refuses before the first request.
|
|
279
|
+
- **Regenerate**, as ever. Generated modules carry a private `GRAPH` naming the
|
|
280
|
+
graph they were generated from, and a
|
|
281
|
+
[multi-schema](getting_started.md#more-than-one-schema) app whose modules predate
|
|
282
|
+
it refuses rather than guessing which schema a module belongs to. A generated
|
|
283
|
+
`execute` also makes its request through the gem now, which is what lets an event
|
|
284
|
+
name the graph; 0.6.1's modules keep working, but `verify` reports the tree out of
|
|
285
|
+
date until you regenerate. Result structs also gained `==`/`eql?`/`hash`,
|
|
286
|
+
`deconstruct_keys`, `#to_h` and `#as_json`.
|