graph_weaver 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +537 -0
- data/Gemfile.lock +19 -19
- data/README.md +74 -53
- data/docs/cassettes.md +29 -4
- data/docs/editors.md +3 -1
- data/docs/errors.md +75 -16
- data/docs/federation.md +206 -155
- data/docs/generated_modules.md +223 -166
- data/docs/getting_started.md +106 -82
- data/docs/logging.md +35 -5
- data/docs/scalars.md +119 -24
- data/docs/testing.md +196 -155
- data/docs/transports.md +47 -19
- data/docs/upgrading.md +243 -22
- data/graph_weaver.gemspec +16 -2
- data/lib/generators/graph_weaver/install_generator.rb +31 -16
- data/lib/graph_weaver/client.rb +52 -15
- data/lib/graph_weaver/codegen/aliases.rb +15 -8
- data/lib/graph_weaver/codegen/emit.rb +107 -42
- data/lib/graph_weaver/codegen/enum_type.rb +4 -3
- data/lib/graph_weaver/codegen/nodes.rb +42 -21
- data/lib/graph_weaver/codegen/scalar_type.rb +87 -83
- data/lib/graph_weaver/codegen/type_helpers.rb +2 -3
- data/lib/graph_weaver/codegen.rb +382 -105
- data/lib/graph_weaver/coerce.rb +113 -0
- data/lib/graph_weaver/errors.rb +57 -13
- data/lib/graph_weaver/federation.rb +10 -22
- data/lib/graph_weaver/hints.rb +76 -2
- data/lib/graph_weaver/in_process.rb +11 -8
- data/lib/graph_weaver/inflect.rb +2 -0
- data/lib/graph_weaver/input_struct.rb +115 -12
- data/lib/graph_weaver/internal/overrides.rb +101 -0
- data/lib/graph_weaver/internal/planner.rb +868 -0
- data/lib/graph_weaver/internal/schemas.rb +50 -0
- data/lib/graph_weaver/internal/selection.rb +127 -0
- data/lib/graph_weaver/{testing → internal}/subgraphs.rb +45 -43
- data/lib/graph_weaver/internal/values.rb +181 -0
- data/lib/graph_weaver/internal.rb +206 -0
- data/lib/graph_weaver/logging.rb +108 -20
- data/lib/graph_weaver/parsing.rb +6 -13
- data/lib/graph_weaver/query_module.rb +2 -0
- data/lib/graph_weaver/railtie.rb +113 -14
- data/lib/graph_weaver/representation.rb +30 -2
- data/lib/graph_weaver/response.rb +15 -0
- data/lib/graph_weaver/retry.rb +54 -22
- data/lib/graph_weaver/rspec.rb +63 -18
- data/lib/graph_weaver/schema_diff.rb +293 -0
- data/lib/graph_weaver/schema_loader.rb +126 -35
- data/lib/graph_weaver/tasks.rb +88 -36
- data/lib/graph_weaver/testing/cassette.rb +131 -78
- data/lib/graph_weaver/testing/coverage.rb +11 -15
- data/lib/graph_weaver/testing/failure.rb +14 -8
- data/lib/graph_weaver/testing/fake_client.rb +253 -60
- data/lib/graph_weaver/testing/fake_subgraph.rb +19 -8
- data/lib/graph_weaver/testing/router.rb +147 -840
- data/lib/graph_weaver/testing.rb +40 -83
- data/lib/graph_weaver/transport/faraday.rb +1 -1
- data/lib/graph_weaver/transport/http.rb +29 -12
- data/lib/graph_weaver/transport.rb +11 -34
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +221 -118
- metadata +17 -13
- data/CLAUDE.md +0 -161
- data/DECISIONS.md +0 -309
- data/Makefile +0 -23
- data/NOTES.md +0 -182
- data/PLAN.md +0 -115
- data/REVIEW.md +0 -946
- data/lib/graph_weaver/schemas.rb +0 -46
- data/lib/graph_weaver/selection.rb +0 -120
- data/lib/graph_weaver/testing/values.rb +0 -98
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3847d3890b566828d7bfe496a6523b1032036d6aad4df0959f0b940bddb14d9b
|
|
4
|
+
data.tar.gz: d2a38677d13f8a0f2a92c67c9082a1a9523add9429bf4388419dbabc8959302f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 118114092cf2cd436e8189fb79dd5be733aa25e34d0aa727da9247e87bc5e309b2b3692248ffa9e40832bf743d672859db444c3fcffc4fc1cbb263b6c395b7e4
|
|
7
|
+
data.tar.gz: c0846cca1819d0e2b75d98f4b8d6a74af2f29113686998bd9e616a7d7d1e9a3a70edbe985412c4750a8ec63f0f0c4389e6363c1bc9d58b251a10b2f652d10e0b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,540 @@
|
|
|
1
|
+
### v0.6.0 (2026-09-09)
|
|
2
|
+
- **One rule for relative path settings: they resolve against
|
|
3
|
+
`GraphWeaver.root`** — `Rails.root` in a Rails app, the working directory
|
|
4
|
+
otherwise. There were two: cassettes resolved against `Rails.root` while
|
|
5
|
+
the schema dump, queries and generated directories resolved against the
|
|
6
|
+
process's working directory, and the railtie patched three of the four
|
|
7
|
+
places that mattered. A Rails app started anywhere but its own root — a
|
|
8
|
+
`bin/rails server` or an `rspec` run from a subdirectory — loaded no
|
|
9
|
+
generated modules at all. An absolute setting is untouched, and the settings
|
|
10
|
+
keep returning what you configured, so `graphql.config.yml` stays portable.
|
|
11
|
+
Set `GraphWeaver.root` to override. The rule runs both ways: a path is
|
|
12
|
+
resolved against the root on access and reported relative to it on output,
|
|
13
|
+
so `generate!`'s return value, `changed_files`, `check_queries`' keys, the
|
|
14
|
+
rake tasks' `wrote`/`pruned` lines and the file an error names all come back
|
|
15
|
+
in the short form you configured. `cache:` also accepts a `Pathname` now,
|
|
16
|
+
as `schema:` and `query:` always have.
|
|
17
|
+
- **One spelling for the generated module's name: `name:`.** `GraphWeaver.parse`
|
|
18
|
+
took `name:` while `Codegen.generate` took `module_name:`. **Breaking:**
|
|
19
|
+
`module_name:` now raises naming `name:`. A parsed module also names itself
|
|
20
|
+
in errors — `GraphWeaver.parse::PersonQuery::Result::Person` instead of a
|
|
21
|
+
hex address — and assigning it to a constant upgrades every nested struct
|
|
22
|
+
to the real path.
|
|
23
|
+
- **`generate!` leaves a file alone when its content hasn't changed**, so the
|
|
24
|
+
rake task prints `wrote` only for files it wrote and `N already up to date`
|
|
25
|
+
for the rest, and a watching dev server reloads only what moved.
|
|
26
|
+
`GraphWeaver.changed_files` says which. A generated file now says which
|
|
27
|
+
release wrote it (`# Generated by GraphWeaver 0.6.0 — do not edit.`), so the
|
|
28
|
+
first `verify` after an upgrade reports the tree as stale. **Regenerate.**
|
|
29
|
+
- **A fake refuses an option it doesn't take**, naming the accepted ones and a
|
|
30
|
+
did-you-mean, at every door (`FakeClient.new`, `graphql_fake`,
|
|
31
|
+
`Router.new(fake:)`, `graphql_router(fake:)`) — it was a raw
|
|
32
|
+
`unknown keyword` from inside the fabricator. A scalar the fake can't
|
|
33
|
+
fabricate is now reported at its path (`at reader.orders.0.total`) with the
|
|
34
|
+
coordinate to override (`"Order.total"`).
|
|
35
|
+
- **A supergraph using federation 2.8's `@context`/`@fromContext` is refused
|
|
36
|
+
per query rather than routed with the argument unset.** `@join__field` was
|
|
37
|
+
on the routing table's known list, so its `contextArguments:` was read and
|
|
38
|
+
dropped: `Testing::Router` planned a fetch for the contextual field with the
|
|
39
|
+
argument empty. Apollo composes such a graph even when the context-setting
|
|
40
|
+
type and the contextual field sit in different subgraphs, so nothing
|
|
41
|
+
upstream caught it. The refusal fires only where the router would plan that
|
|
42
|
+
fetch itself; a subtree one subgraph answers whole still runs. New
|
|
43
|
+
`Unplannable` category `:context_argument`.
|
|
44
|
+
- **The local router makes one entity fetch where it made two** when an entity
|
|
45
|
+
has two `@requires` fields crossing into the same subgraph on the same
|
|
46
|
+
`@key`. Both sent the identical representation; Apollo makes one. Specs
|
|
47
|
+
asserting on `#trace` for such a query see one fewer entry. A missing `@key`
|
|
48
|
+
on a representation now names the entity type in `InputError#struct`, as a
|
|
49
|
+
coercion failure already did.
|
|
50
|
+
- **Three rake and generator polish items.** `rake -T` no longer prints a
|
|
51
|
+
queries path it can't know — a task description is baked before
|
|
52
|
+
`:environment`, so it presented the default as your setting; it now names
|
|
53
|
+
the defaults as defaults. `cassettes:anonymize` prints `no recordings in
|
|
54
|
+
<dir>` instead of nothing. `rails g graph_weaver:install` scaffolds
|
|
55
|
+
`app/graphql/fragments/.keep`, the directory its editor config already
|
|
56
|
+
globbed; the getting-started note names `rails g graphql:install
|
|
57
|
+
--skip-query-logs` for an app without Active Record.
|
|
58
|
+
- **`filter_parameters` scrubs error messages, not just the debug log.** A
|
|
59
|
+
rejected value reached the log twice: on the `variables=` line at debug, and
|
|
60
|
+
in the message every error writes at `warn` — above the gate, as free text.
|
|
61
|
+
One rule now covers both: a message the library composes about a value you
|
|
62
|
+
supplied names it only when the key it arrived under isn't filtered. So
|
|
63
|
+
`$password of Login: [FILTERED]`, while `$count of Compute: expected an Int,
|
|
64
|
+
got "lots"` is unchanged — the value is usually the whole diagnosis. Applies
|
|
65
|
+
to execute kwargs, input fields at any depth, federation `@key` fields, and
|
|
66
|
+
the missing-cassette report. A wrong-typed input field with no coercer now
|
|
67
|
+
says which field (`cents: expected BigDecimal, got 5`) instead of sorbet's
|
|
68
|
+
sentence, and the local federation router no longer logs variables
|
|
69
|
+
unscrubbed.
|
|
70
|
+
- **The public surface is what the docs name, what generated code calls, and
|
|
71
|
+
the `execute` slot — 421 names, down from about 700.** Everything else moved
|
|
72
|
+
under `GraphWeaver::Internal`, whose one rule is that nothing in it is API,
|
|
73
|
+
or went `private`. `spec/public_surface_spec.rb` diffs what's reachable from
|
|
74
|
+
`GraphWeaver` against a checked-in list, so the next accidental promotion
|
|
75
|
+
fails CI instead of shipping. **Breaking** only for code reaching past the
|
|
76
|
+
documented door: the federation query planner and its IR are
|
|
77
|
+
`Internal::Planner`; the fake-value engine is `Internal::Values`; the
|
|
78
|
+
selection walk is `Internal::Selection` (so `FakeClient` no longer answers
|
|
79
|
+
to `each_field` or `gather`); the cassette matching rules are
|
|
80
|
+
`Internal::RequestKey`; `GraphWeaver.log`/`.instrument`/`.filter_variables`
|
|
81
|
+
are `Internal::Log` (`logger=`, `instrumenter=`, `filter_parameters=` are
|
|
82
|
+
unchanged); `Transport.operation_name`/`.mutation?`/`.log_tag` left the
|
|
83
|
+
class you subclass for `Internal::Wire`. `SchemaDiff::Change`,
|
|
84
|
+
`Cassette::Check`, `Coverage::Result` and `InputStruct::Field` are `Data`,
|
|
85
|
+
not `Struct`, so they no longer hand out writers. `Codegen::ScalarType` is
|
|
86
|
+
private from load rather than from the first `reset_scalars!` — the
|
|
87
|
+
`private_constant` had landed inside the method body. Generated modules also
|
|
88
|
+
stop exposing their plumbing: `DEFAULT_CLIENT`, `FIELDS` and `ONE_OF` are
|
|
89
|
+
emitted as `private_constant`. **Regenerate**; nothing you call changes.
|
|
90
|
+
- **The retry options are flat.** `retries:` is how many attempts follow the
|
|
91
|
+
first; every other `Retry` option now sits beside it on the client —
|
|
92
|
+
`GraphWeaver.new(url, retries: 5, backoff: :linear, retry_codes:
|
|
93
|
+
["THROTTLED"])`. **Breaking:** `retries:` no longer takes a Hash (`retries:
|
|
94
|
+
{ retries: 5 }` read as a key nested in itself), and three options are
|
|
95
|
+
spelled out now that they sit next to a count: `on:` is `retry_on:`,
|
|
96
|
+
`base:`/`max:` are `base_delay:`/`max_delay:` — `retries: 5, max: 30`
|
|
97
|
+
invited reading `max:` as a second attempt count. The Hash form names its
|
|
98
|
+
flat replacement, and a retry option passed without `retries:` says so.
|
|
99
|
+
- **A representation's key fields are coerced, like an `execute` kwarg.**
|
|
100
|
+
`Representations.user(id: params[:id])` typechecked statically and then
|
|
101
|
+
raised sorbet-runtime's unbranded `TypeError` at runtime. The generated
|
|
102
|
+
builders now carry `.checked(:never)` and run each key field through the
|
|
103
|
+
registered scalar's own conversion; a value that converts to nothing raises
|
|
104
|
+
`GraphWeaver::InputError` naming the representation and the field.
|
|
105
|
+
**Regenerate.**
|
|
106
|
+
- **The library's internals are private.** Everything not documented and not
|
|
107
|
+
named by generated code is now `private` / `private_class_method` /
|
|
108
|
+
`private_constant`: codegen's IR and walk, the schema loader's detection
|
|
109
|
+
tables, the transport's log patterns, `Retry`'s default predicate, the
|
|
110
|
+
file-naming helpers. **Breaking:** `GraphWeaver::Selection`'s methods are
|
|
111
|
+
private in every class that mixes it in, so `Testing::FakeClient` no longer
|
|
112
|
+
answers to `each_field`, `gather` or `load_operation` — never a supported
|
|
113
|
+
door. The runtime that emitted source calls into (`cast_data`,
|
|
114
|
+
`check_envelope!`, `Coerce`, `Hints`, `InputStruct`, `QueryModule`,
|
|
115
|
+
`Representation`) stays public and says in its comments that it is
|
|
116
|
+
generated code's, not yours.
|
|
117
|
+
- **A registration another schema owns warns instead of failing generation.**
|
|
118
|
+
One registry serves a whole graph — federation composes scalars and types
|
|
119
|
+
by name — but validation ran against the single schema in hand, so the
|
|
120
|
+
natural multi-subgraph pattern (register once, generate each query against
|
|
121
|
+
its own subgraph) hard-failed. Generation now fails only on what the schema
|
|
122
|
+
can *disprove*: a name it declares as something else, or a coordinate whose
|
|
123
|
+
field it declares as a composite. Everything else warns, naming the schema
|
|
124
|
+
and suggesting a corrected name. `rake graph_weaver:generate` and `verify`
|
|
125
|
+
print that list once per run after the files;
|
|
126
|
+
`GraphWeaver.unmatched_registrations` is the same list for a Rakefile.
|
|
127
|
+
**Breaking**: a typo'd registration that used to stop generation is now a
|
|
128
|
+
printed warning. `docs/federation.md` gains "Generating for a federated
|
|
129
|
+
graph": through the gateway, generate against the supergraph; calling
|
|
130
|
+
subgraphs directly, one client each with `reset_registrations!` between.
|
|
131
|
+
- **Variables coerce inside `execute` now, and the sig stays as narrow as the
|
|
132
|
+
schema.** `execute(first: params[:first])` works — the String becomes an
|
|
133
|
+
Integer, an ISO-8601 string a `Date`, a model's Integer primary key an `ID` —
|
|
134
|
+
while `execute(first: "20")` written literally in a typed file is still an
|
|
135
|
+
`srb tc` error. The only way to accept loose input used to be `coerce: true`
|
|
136
|
+
/ `GraphWeaver.auto_coerce`, which bought it by *widening* the emitted kwarg
|
|
137
|
+
and so switched off the static check at every call site. The generated sigs
|
|
138
|
+
are now `.checked(:never)` so an untyped value reaches the body; coercion is
|
|
139
|
+
the check that replaces sorbet-runtime's, and it refuses more than sorbet
|
|
140
|
+
did. Bad input raises `GraphWeaver::InputError` naming the variable, the
|
|
141
|
+
operation and the value — the same 422 rescue point as an input-object
|
|
142
|
+
field. **Breaking:** `GraphWeaver.auto_coerce` and `register_scalar(coerce:)`
|
|
143
|
+
are gone; a scalar that had `coerce: true` keeps its behaviour but its kwarg
|
|
144
|
+
narrows, so a call site passing a literal String is now an `srb tc` error.
|
|
145
|
+
`cast:` is what a loose value converts through. **Regenerate.**
|
|
146
|
+
- **Numeric strings are read as a wire format, not as Ruby source.** `"010"`
|
|
147
|
+
is ten, not eight, and `"0x1f"` and `"1_0"` are refused, in variables and
|
|
148
|
+
responses alike — `Kernel#Integer` accepted all three, which made a
|
|
149
|
+
zero-padded form field silently mean something else. An `Int` refuses a
|
|
150
|
+
`Float` it can't hold losslessly (`2.5`), and a `Boolean` refuses a String:
|
|
151
|
+
Ruby has no `Kernel#Boolean`, so every rule for `"0"`/`"off"` is a
|
|
152
|
+
convention the library declines to pick — convert at the call site.
|
|
153
|
+
- **A faked subgraph takes the options every other fake takes, and
|
|
154
|
+
`graphql_router` says them per example.** `subgraphs: { "reviews" => :fake }`
|
|
155
|
+
built its fake with nothing, so `overrides:`, `list_size:` and `null_chance:`
|
|
156
|
+
had nowhere to go. `fake:` carries them, on `Router.new`, on
|
|
157
|
+
`Testing.config.router`, and per example through `graphql_router(fake:)`,
|
|
158
|
+
which mirrors `graphql_fake`. One `fake:` covers every faked subgraph —
|
|
159
|
+
coordinate-keyed overrides already say which type they mean. A `fail_at:`
|
|
160
|
+
inside an `_entities` fetch used to null the field and report no error; it
|
|
161
|
+
now comes back as an error on the caller's path.
|
|
162
|
+
- **A subgraph two loaded schema classes fit is refused by the query that
|
|
163
|
+
reaches it, not by `Router.new`.** In an app where several loaded schemas
|
|
164
|
+
each satisfy a foundational subgraph, building a router raised for
|
|
165
|
+
subgraphs the query under test never touched — and only when eager loading
|
|
166
|
+
happened to be on. The router now builds, every query that avoids the
|
|
167
|
+
subgraph runs, and one that reaches it raises naming the candidates, saying
|
|
168
|
+
they came from loaded schema classes, and showing the `subgraphs:` form that
|
|
169
|
+
pins one. A class named explicitly still fails at construction.
|
|
170
|
+
`router.ambiguous` lists them. `Testing::Subgraphs.resolve` now returns a
|
|
171
|
+
`Resolution` (`#served`, `#ambiguous`).
|
|
172
|
+
- **A `.graphql` edit reaches the next request in development.** The query
|
|
173
|
+
directories and the schema dump join Rails' own reloaders, and the
|
|
174
|
+
`to_prepare` that loads the generated modules now regenerates first — after
|
|
175
|
+
your registrations, so an `extend_type` in a `to_prepare` is in place before
|
|
176
|
+
codegen reads it. A query that doesn't compile is logged at `error` with its
|
|
177
|
+
file and position while the modules already loaded keep serving. Development
|
|
178
|
+
only, while the server is running; `config.graph_weaver.watch = false` turns
|
|
179
|
+
it off, and `rake graph_weaver:verify` still gates CI on the committed files.
|
|
180
|
+
`GraphWeaver.reload_generated!` does the same by hand after regenerating in
|
|
181
|
+
another terminal, and generated files are now written to a temp file and
|
|
182
|
+
renamed, so a `rake graph_weaver:generate` beside a watching dev server can't
|
|
183
|
+
hand the running app a prefix that no longer parses.
|
|
184
|
+
- **A variable passed `nil` now sends `null`; one left out is still left
|
|
185
|
+
out.** GraphQL tells an absent variable from an explicit null — `bio: null`
|
|
186
|
+
clears a bio, omitting it does nothing — and a Ruby kwarg with a nil default
|
|
187
|
+
cannot, so both spellings omitted the key and no generated mutation could
|
|
188
|
+
clear a field. An optional nullable variable now records whether the keyword
|
|
189
|
+
was supplied. Input objects get the same distinction where a Hash can express
|
|
190
|
+
it: `coerce({nickname: nil})` sends null, `coerce({})` omits; a struct built
|
|
191
|
+
with `.new` can't tell, so nil there still means omit. A non-null variable
|
|
192
|
+
can't carry null, so nil there also still omits and its schema default
|
|
193
|
+
applies. **Breaking**: `bio: params[:bio]` with a missing param now sends
|
|
194
|
+
`null` where it used to omit — pass the keyword only when you mean to.
|
|
195
|
+
**Regenerate.**
|
|
196
|
+
- **A response that won't cast says which field, and whose bug it is.** A
|
|
197
|
+
leaf's cast raises about the value alone — "invalid date" on a struct holding
|
|
198
|
+
four dates located nothing — so each casting leaf now carries its response
|
|
199
|
+
key. An `ID` the server sent unquoted is out of spec but read like a
|
|
200
|
+
graph_weaver bug in sorbet's words; the message now says which it is and how
|
|
201
|
+
to take it anyway. An enum value the generated enum doesn't hold now names
|
|
202
|
+
the legal values and says the likely cause is drift, matching the input
|
|
203
|
+
side. **Regenerate.**
|
|
204
|
+
- **`register_scalar` with a type the wire can't build is refused at
|
|
205
|
+
generation.** `register_scalar("Money", BigDecimal)` produced a `BigDecimal`
|
|
206
|
+
prop and no cast, so every response failed its prop check at runtime, far
|
|
207
|
+
from the initializer that caused it. Generation now refuses it where a query
|
|
208
|
+
reads that scalar back, naming the field and how to give it a `cast:`.
|
|
209
|
+
`docs/scalars.md` now tabulates what the wire carries in both directions.
|
|
210
|
+
- **A fake takes pins, keyed by a scalar type, an object type, or a field.**
|
|
211
|
+
A pin says what the fake uses instead of inventing a value: a wire value,
|
|
212
|
+
an object it reads the selected fields off — a FactoryBot build, a model, a
|
|
213
|
+
`Struct`; a field the object doesn't answer is still fabricated, and a Ruby
|
|
214
|
+
value goes on the wire the way its scalar registration serializes it — or a
|
|
215
|
+
proc handed the seeded `Random`. Pins lead and options follow, in one call;
|
|
216
|
+
suite-wide, the same hash is `Testing.config.overrides`, and a router's
|
|
217
|
+
`fake:` takes it for the subgraphs it fakes.
|
|
218
|
+
|
|
219
|
+
graphql_fake("Money" => "12.00", "Person" => build(:person), "Order.total" => "999.00")
|
|
220
|
+
|
|
221
|
+
A scalar registered as your own class — `register_scalar("Money", Money,
|
|
222
|
+
cast: :parse)` — told codegen how to read the wire value but left the
|
|
223
|
+
testing harness guessing what to write, so every fake response touching it
|
|
224
|
+
died inside `from_h` blaming `Money.parse` for a `"Money-1"` placeholder.
|
|
225
|
+
The type pin is the answer, from every door including cassette
|
|
226
|
+
anonymization; without one, fabrication refuses, naming the scalar, the
|
|
227
|
+
field, and the pin to add. Scalars registered as `Time`, `Date`, `Integer`,
|
|
228
|
+
`Float`, `String` or `T::Boolean` need nothing. Fakes also now honor a
|
|
229
|
+
per-field `register_scalar("User.birthday", Date)`, matching codegen's
|
|
230
|
+
resolution. `graphql_fake` and `graphql_router(fake:)` refuse a per-example
|
|
231
|
+
`seed:` — `rspec --seed` is the mechanism, and `config.seed` the suite-wide
|
|
232
|
+
one for a harness that isn't rspec.
|
|
233
|
+
- **`Testing.config.null_chance` and `Testing.config.mode` are gone; both
|
|
234
|
+
are per-fake now.** A suite-wide answer to a per-example question nils an
|
|
235
|
+
unrelated field one run in ten. **Move them:** `graphql_fake(null_chance:
|
|
236
|
+
0.3)` and `graphql_fake(values: :literal)` (the per-fake `mode:` is renamed
|
|
237
|
+
`values:` so it can't be confused with `graphql: :fake` /
|
|
238
|
+
`config.default_mode`, which stay). The auto-detect — faker when the gem is
|
|
239
|
+
loaded, else literal — is the only suite-wide value style.
|
|
240
|
+
`Testing::MODES` is now `VALUE_STYLES`.
|
|
241
|
+
- **`retries:` takes the count you'd write, and means the same thing
|
|
242
|
+
everywhere.** `GraphWeaver.new(url, retries: 3)` raised and pointed at
|
|
243
|
+
`retries: { tries: 3 }` — a second word for the same number that disagreed
|
|
244
|
+
about whether it counted the first attempt. One word now: `retries:` is how
|
|
245
|
+
many attempts follow the first, on the client and on `Retry` alike, so
|
|
246
|
+
`retries: 0` is one attempt. **Rename** `Retry.new(tries: n)` to
|
|
247
|
+
`Retry.new(retries: n - 1)`; the default is unchanged in effect.
|
|
248
|
+
- **`Response#to_h`.** Every error class answered `#to_h`; the envelope didn't.
|
|
249
|
+
It returns `{"data" =>, "errors" =>, "extensions" =>}` with each error as its
|
|
250
|
+
JSON-ready hash. `data` stays the typed struct rather than re-serialized:
|
|
251
|
+
`T::Struct#serialize` gives snake_case keys, drops nulls, and leaves a
|
|
252
|
+
registered scalar as its Ruby object — output that would pass for the
|
|
253
|
+
server's response without being one.
|
|
254
|
+
- **Sensitive variables are scrubbed from the debug log.** A `login(password:)`
|
|
255
|
+
mutation's variables were one log-level switch away from the log. Matching
|
|
256
|
+
values are replaced with `[FILTERED]` at any depth, on the wire and
|
|
257
|
+
in-process paths. Rails apps configure nothing: the railtie adopts the app's
|
|
258
|
+
own `config.filter_parameters`. Elsewhere,
|
|
259
|
+
`GraphWeaver.filter_parameters = [:password, /token/]`; the default list is
|
|
260
|
+
`[:password, :token, :secret, :authorization]`, matched as case-insensitive
|
|
261
|
+
substrings, and `[]` turns filtering off.
|
|
262
|
+
- **`schema:diff` names what changed, not just that something did.** It
|
|
263
|
+
reported `schema.json is stale` and stopped, so learning what moved meant
|
|
264
|
+
refreshing and reading a `git diff` of a 3 MB dump. It now prints one line
|
|
265
|
+
per change at its schema coordinate, breaking ones first — types, fields,
|
|
266
|
+
arguments, nullability, enum values, union members, interfaces and
|
|
267
|
+
deprecations — and still exits non-zero on any drift. Breaking is judged from
|
|
268
|
+
the client's side, so nullability is directional: an output losing `!` breaks
|
|
269
|
+
a generated struct, an input gaining one breaks a query that omits it.
|
|
270
|
+
`SchemaLoader.stale?` is now `SchemaLoader.diff`, returning the summary;
|
|
271
|
+
`diff(path).empty?` is what `stale?` answered.
|
|
272
|
+
- **A Rails app whose generated code includes an autoloaded helper now
|
|
273
|
+
boots.** `extend_type("Pet", PetHelpers)` and `register_enum("Species",
|
|
274
|
+
PetKind)` name constants your app autoloads, so the library tells you to
|
|
275
|
+
register them from a `to_prepare` block — but the railtie required the
|
|
276
|
+
generated files before Zeitwerk was set up and before `to_prepare` ran, so
|
|
277
|
+
the `include PetHelpers` those registrations emit raised `NameError` at every
|
|
278
|
+
boot, in every environment. They now load from a `to_prepare` block of their
|
|
279
|
+
own: after the autoloader, after your registrations, before eager loading.
|
|
280
|
+
- **A fragment on the abstract type no longer narrows the field away.**
|
|
281
|
+
`named { __typename ... on Named { name } ... on Pet { species } }` read the
|
|
282
|
+
`Named` fragment as a type condition and narrowed to Pet, so a Person came
|
|
283
|
+
back as `nil` and the `name` the server sent went on the floor — while the
|
|
284
|
+
same selection spelled bare dispatched correctly. Two ways of writing one
|
|
285
|
+
query disagreed, and the fragment is how anyone shares interface fields.
|
|
286
|
+
**Regenerate** to pick it up.
|
|
287
|
+
- **A `Float` field reads the whole number a JSON encoder writes.** JSON has
|
|
288
|
+
one number type, so `1.0` reaches Ruby as an `Integer` from graphql-js and
|
|
289
|
+
Go's `encoding/json` alike, and `const :amount, Float` refused a response the
|
|
290
|
+
spec calls legal. Widened with `Kernel#Float`, which still refuses what
|
|
291
|
+
`.to_f` would quietly turn into `0.0`. **Regenerate** to pick it up.
|
|
292
|
+
- **A guarded `__typename` no longer makes a narrowing miss look like a
|
|
293
|
+
match.** Narrowing without a readable tag decides on emptiness, but a
|
|
294
|
+
`__typename` behind `@skip`/`@include` still arrives for the member narrowing
|
|
295
|
+
means to filter, so the wrong member was cast. That query now refuses at
|
|
296
|
+
generation, with the message that already names the fix.
|
|
297
|
+
- **Generation refuses what Ruby would misread.** A result key that would
|
|
298
|
+
shadow a constant the file uses (`date` beside a `Date` scalar nested a
|
|
299
|
+
`class Date < T::Struct` and typed the scalar prop as that struct) is refused
|
|
300
|
+
naming both keys; so is an enum value that camelizes to nothing (`_`), which
|
|
301
|
+
emitted a file that died at load with a syntax error.
|
|
302
|
+
- **A bad enum value in a variable says what the enum accepts**, as an
|
|
303
|
+
`InputError` — it raised a bare `KeyError` naming an anonymous module as a
|
|
304
|
+
top-level variable while the same mistake inside an input object was already
|
|
305
|
+
branded. A missing required input field now lists every missing key instead
|
|
306
|
+
of sorbet's symptom for the first prop it reached. **Regenerate** to pick up
|
|
307
|
+
the new emitted coercion.
|
|
308
|
+
- **A named fragment now crosses a subgraph boundary.** A stitched fetch is
|
|
309
|
+
built from selections alone, so a spread that survived into one named a
|
|
310
|
+
fragment the subgraph had never seen and the whole subtree came back
|
|
311
|
+
`Fragment X was used, but not defined`. Every spread a fetch carries is now
|
|
312
|
+
spelled as the inline fragment it is.
|
|
313
|
+
- **A fake's value for a custom scalar comes from what you registered it
|
|
314
|
+
as.** It was picked off the schema's *name* for the scalar, so a `Timestamp`
|
|
315
|
+
registered as `Time` got `"Timestamp-1"` and every fake response touching it
|
|
316
|
+
died inside the codec codegen had emitted for it. A scalar registered as
|
|
317
|
+
`Time` now gets iso8601, one registered as `Integer` an Integer; an
|
|
318
|
+
unregistered one keeps its placeholder. Cassette anonymization runs through
|
|
319
|
+
the same engine and had the same hole, which made `cassettes:check` fail on
|
|
320
|
+
a cassette the anonymizer had just written. **Re-run
|
|
321
|
+
`rake graph_weaver:cassettes:anonymize`** on a cassette holding a registered
|
|
322
|
+
custom scalar.
|
|
323
|
+
- **A mutation is no longer retried.** A read timeout, a 502 or a reset socket
|
|
324
|
+
does not say whether the server applied the request, and a second `charge`
|
|
325
|
+
is worse than a failed one — so `Retry` gives a mutation one attempt and says
|
|
326
|
+
so on the logger. Pass `retry_mutations: true` for an API whose mutations are
|
|
327
|
+
idempotent. Each retry now logs the wait and the attempt number.
|
|
328
|
+
- **A connection that dies mid-body is a dropped connection.** net/http's
|
|
329
|
+
`ignore_eof` default handed back the bytes that arrived when a socket closed
|
|
330
|
+
short of its `Content-Length`, so a half-sent response reached you as a 200
|
|
331
|
+
with a truncated body — a permanent `ServerError` that `Retry` would not
|
|
332
|
+
retry. It now raises the retriable `TransportError` it is.
|
|
333
|
+
- **An in-process query gets its own context.** graphql-ruby writes a
|
|
334
|
+
resolver's `context[...] =` into the hash it is handed, and one `InProcess`
|
|
335
|
+
is normally the whole app's client — so a flag set by one request was still
|
|
336
|
+
there for the next, and racing between them under Puma.
|
|
337
|
+
- **A response that carries neither `data` nor `errors` is refused.** A client
|
|
338
|
+
returning `nil`, one keying the envelope by symbol, one that typo'd `"dat"`:
|
|
339
|
+
each produced a `Response` reporting `success?` with `data` nil.
|
|
340
|
+
`from_response` is documented public API and symbolized keys are the
|
|
341
|
+
likeliest mistake at that seam, so it now brands, naming the keys it found;
|
|
342
|
+
a client answering with a String brands too, instead of dying on `#to_h`.
|
|
343
|
+
**Regenerate** to pick that up.
|
|
344
|
+
- **`@skip`/`@include` are evaluated by the fake against the variables you
|
|
345
|
+
passed**, declared defaults included — the way a server and
|
|
346
|
+
`Testing::Router` already did, so one query no longer carries a key under
|
|
347
|
+
`graphql: :fake` and not under `graphql: :router`. A faked subgraph does the
|
|
348
|
+
same for directives inside its `_entities` selection. A `first:`/`last:`
|
|
349
|
+
arriving as a variable caps the fabricated list like a literal, a cap below
|
|
350
|
+
zero reads as a page of none, and `null_chance` reaches list positions.
|
|
351
|
+
- **An alias spelling a response key the router carries a `@key` under is
|
|
352
|
+
refused.** The router injects the `@key` it crosses on under a reserved key
|
|
353
|
+
and strips it from the answer, so `_gw_weight: weight` came back without
|
|
354
|
+
`_gw_weight` at all. It joins the `shadowed_key` refusal, which already
|
|
355
|
+
covered Apollo's half of the same collision.
|
|
356
|
+
- **A `@requires` field set excluded by `@skip`/`@include` no longer
|
|
357
|
+
prefetches.** The router ran a subgraph fetch a real router never makes —
|
|
358
|
+
which matters for a test double, where a resolver that runs is one your
|
|
359
|
+
example can observe.
|
|
360
|
+
- **`verify_generated!` fails over zero query documents**, and `generate!`
|
|
361
|
+
warns. A mistyped `queries_paths` left `verify` returning true having
|
|
362
|
+
compared nothing, so a CI gate stayed green forever.
|
|
363
|
+
- **`rake graph_weaver:generate` says what it pruned, and where it looked when
|
|
364
|
+
there was nothing to generate.** Deleting a `.graphql` deletes the checked-in
|
|
365
|
+
file it produced, and the task printed nothing and exited 0; so did a run
|
|
366
|
+
with no queries, the state every install starts in.
|
|
367
|
+
- **`Failure.server` takes the headers a backoff branches on.**
|
|
368
|
+
`ServerError#retry_after` and `#throttled?` read `Retry-After`, and there was
|
|
369
|
+
no way to set one: `Failure.server(status: 429, headers: { "retry-after" =>
|
|
370
|
+
"2" })`. `Failure.transport`'s message now names the class it caught, the
|
|
371
|
+
way the bundled transports do.
|
|
372
|
+
- **Cassette recording is safe across threads.** `record` was
|
|
373
|
+
read-modify-write; two threads recording through one cassette each saved a
|
|
374
|
+
snapshot missing the other's entry.
|
|
375
|
+
- **A `.json` schema dump that isn't JSON says which file and what it holds.**
|
|
376
|
+
A truncated download or a login page saved over the dump raised a bare
|
|
377
|
+
`JSON::ParserError`, outside `GraphWeaver::Error` and naming neither.
|
|
378
|
+
`rake graph_weaver:schema:refresh` — the fix — no longer trips over it.
|
|
379
|
+
- **Setup mistakes say what to do.** A schema source that looks like a url
|
|
380
|
+
(`localhost:4000/graphql` included) gets the url advice first; a non-token
|
|
381
|
+
`auth:`, a `retries:` that is neither true nor a Hash, a url
|
|
382
|
+
`Transport::HTTP` cannot POST to, a `parse` path without a `.graphql`
|
|
383
|
+
extension, a cache directory that can't be written, `schema:refresh URL=`
|
|
384
|
+
with a file path, and `schema:diff` with no dump all name the fix. A
|
|
385
|
+
`ServerError` carries the advice its status implies: a 3xx names where the
|
|
386
|
+
server pointed (redirects are not followed) and a 401/403 names `auth:`. A
|
|
387
|
+
spec with a `graphql:` tag but no `graph_weaver/rspec` is told to require
|
|
388
|
+
it, a misspelled tag reports one failure instead of two, `InputError` no
|
|
389
|
+
longer prints sorbet's `Caller:` frame, a generated file whose registered
|
|
390
|
+
constant is gone names the registration, and `rails g graph_weaver:install`
|
|
391
|
+
names a retry that works when introspection fails.
|
|
392
|
+
- **The GitHub example's checked-in modules were regenerated**, and a spec now
|
|
393
|
+
keeps them in step with the emitter.
|
|
394
|
+
- **A cold process no longer introspects once per in-flight thread.** A
|
|
395
|
+
url-built client fetches its schema lazily, and Puma serves its first
|
|
396
|
+
requests concurrently — so eight threads arriving together meant eight full
|
|
397
|
+
introspection round trips, each of them also writing the schema cache. One
|
|
398
|
+
now, whoever asks first.
|
|
399
|
+
- **The schema cache and cassettes are written atomically.** `File.write`
|
|
400
|
+
truncates before it writes, so an interrupted run — or a second writer, a
|
|
401
|
+
rake task beside a running app — could leave a half-written file where a
|
|
402
|
+
committed artifact used to be. Both now write beside the target and rename,
|
|
403
|
+
which is atomic: a reader sees the old file or the new one.
|
|
404
|
+
- **The connection pool keeps its ceiling under an interrupt.** An async raise
|
|
405
|
+
landing between taking a permit and the `ensure` that returns it would have
|
|
406
|
+
leaked one for the life of the process, and `Rack::Timeout` raises exactly
|
|
407
|
+
that way; the gap is closed. Its saturation warning is also once now rather
|
|
408
|
+
than once per racing thread.
|
|
409
|
+
|
|
410
|
+
### v0.5.1 (2026-09-07)
|
|
411
|
+
- **A union's catch-all struct keeps the fields an interface fragment asked
|
|
412
|
+
for.** `... on Named { name }` under a union types `name` on every member the
|
|
413
|
+
query named, but the catch-all — the member it didn't name, including one the
|
|
414
|
+
schema grows later — carried only `__typename`, so a `name` the server
|
|
415
|
+
actually sent was dropped on the floor. Those fields are `T.nilable`: the
|
|
416
|
+
member that arrives needn't implement the interface. **Regenerate** to pick
|
|
417
|
+
it up.
|
|
418
|
+
- **`schema_stale?` now recognises the drift a graphql-ruby server reports.**
|
|
419
|
+
It matched Apollo's one flat code plus a message pattern, but graphql-ruby —
|
|
420
|
+
which this library ships as its in-process client — names the rule that fired
|
|
421
|
+
in `extensions.code`. So a renamed type, a removed argument, an argument that
|
|
422
|
+
became required and four other drift shapes all raised without the one
|
|
423
|
+
sentence telling you to refresh the dump and regenerate, and `to_h` reported
|
|
424
|
+
`"schema_stale": false`.
|
|
425
|
+
- **A cast failure keeps the server's own explanation.** A field that came back
|
|
426
|
+
null *with a reason* — a permission rule, a partial outage — raised only
|
|
427
|
+
Sorbet's nil complaint, because the generated envelope built `data` before it
|
|
428
|
+
read `errors`. The reason is now in the message. **Regenerate** to pick it up.
|
|
429
|
+
- **Cassette anonymization missed `errors` and `extensions`.** It walked
|
|
430
|
+
`data` and nothing else, so a rejected request's error message — which
|
|
431
|
+
routinely quotes the input that caused it — and whatever the server hangs
|
|
432
|
+
off `extensions` went to disk verbatim, under a flag that says real data
|
|
433
|
+
never reaches it. **Re-run `rake graph_weaver:cassettes:anonymize` on any
|
|
434
|
+
cassette you've already committed**, or re-record it. Neither key has a
|
|
435
|
+
schema behind it, so both are now walked by shape: keys, nesting, nulls and
|
|
436
|
+
booleans survive and every string and number is replaced, with `path`,
|
|
437
|
+
`locations` and an error's `extensions.code` kept because they describe the
|
|
438
|
+
request rather than the data.
|
|
439
|
+
- **Recording flags a credential that lands in the file.** A cassette gets
|
|
440
|
+
committed as written, and its query and variables can't be anonymized —
|
|
441
|
+
they're the key replay matches on. So the bytes headed for disk are checked
|
|
442
|
+
for the shapes that are unmistakable (a JWT, `AKIA…`, `ghp_…`, `xox…`,
|
|
443
|
+
`sk_live_…`, a PEM block, a `Bearer` header) and named on stderr once per
|
|
444
|
+
cassette. Nothing is rewritten; a password like `hunter2` has no shape, so a
|
|
445
|
+
quiet run still isn't a clean bill of health.
|
|
446
|
+
- **An anonymized cassette no longer drops a key selected twice.** The
|
|
447
|
+
anonymizer kept the last occurrence's sub-selection where codegen merges
|
|
448
|
+
them, so `a { x } a { y }` came back holding only `y` — a recording that
|
|
449
|
+
then failed its own `cassettes:check`.
|
|
450
|
+
- **Three generated types disagreed with the schema.** `srb tc` proves the
|
|
451
|
+
generated code is self-consistent, not that it is true, so each of these was a
|
|
452
|
+
lie the typechecker endorsed. **Regenerate** to pick them up:
|
|
453
|
+
- A field reached only through a `@skip`/`@include`-guarded occurrence, where
|
|
454
|
+
the same key is also selected unguarded, was typed as guaranteed. The server
|
|
455
|
+
legitimately omits it, so `from_h` raised `key not found` on a valid
|
|
456
|
+
response — and a union whose `__typename` arrived that way crashed its own
|
|
457
|
+
dispatch. Those children are nilable now, and the `__typename` case refuses
|
|
458
|
+
at generation as it already did for the plainer spelling.
|
|
459
|
+
- A narrowed abstract inside a non-null list made the *list* nilable
|
|
460
|
+
(`[Thing!]!` with `... on Widget` → `T.nilable(T::Array[…])`). Only the
|
|
461
|
+
elements can be nil; the array is always there.
|
|
462
|
+
- An input field the schema gives a default is optional, but a non-null one
|
|
463
|
+
emitted `const :x, String, default: nil` — so `x.upcase` typechecked and
|
|
464
|
+
was a `NoMethodError`.
|
|
465
|
+
- **`rake graph_weaver:federation:coverage` no longer counts a subgraph as
|
|
466
|
+
served here when two loaded schemas fit it.** `Router.new` refuses that case
|
|
467
|
+
— picking either would be a coin flip — so the report was promising a run
|
|
468
|
+
the suite can't have. It now lands in the "nothing here serves what they
|
|
469
|
+
reach" list, whose advice (name a schema for those subgraphs) is the fix.
|
|
470
|
+
- **A supergraph that renamed the join spec is now refused instead of read as
|
|
471
|
+
empty.** `@link(url: ".../join/v0.3", as: "j")` spells every marker `j__`,
|
|
472
|
+
which the routing table doesn't follow — so it reported a composed graph with
|
|
473
|
+
*no subgraphs* and nothing unsupported, and `Testing::Router` built happily on
|
|
474
|
+
top of that. It now lands in `unsupported`, so the router and
|
|
475
|
+
`federation:coverage` refuse at construction and say why.
|
|
476
|
+
- **`rake graph_weaver:cassettes:anonymize` now finds an SDL schema dump.** It
|
|
477
|
+
opened `GraphWeaver.schema_path` directly instead of locating the dump the
|
|
478
|
+
way every sibling task does, so an app whose committed dump is
|
|
479
|
+
`schema.graphql` (what the generator writes for a supergraph) got a raw
|
|
480
|
+
`Errno::ENOENT`. A missing dump now says so.
|
|
481
|
+
- **`rake graph_weaver:queries:check` now authenticates with the token your
|
|
482
|
+
app uses.** It re-introspects the url the dump records, but read
|
|
483
|
+
`GRAPHWEAVER_AUTH` rather than the ENV var the dump named — so an app
|
|
484
|
+
installed with `--auth MY_TOKEN` reached its private API while the check
|
|
485
|
+
401'd. Every re-introspection now goes through `SchemaLoader.source_transport`,
|
|
486
|
+
which is also now public.
|
|
487
|
+
- **A `graphql: :router` example now starts from the same fabricated data
|
|
488
|
+
whether it runs alone or after two hundred others.** The router is built once
|
|
489
|
+
for the suite, and a faked subgraph kept fabricating from wherever the last
|
|
490
|
+
example left its sequence — so `rspec spec/foo_spec.rb:12` disagreed with the
|
|
491
|
+
same example in a full run, which is the opposite of what `--seed` promises.
|
|
492
|
+
`Router#reset!` is the example boundary (`reset_trace` still clears only the
|
|
493
|
+
trace); the rspec tag calls it for you.
|
|
494
|
+
- **An override key given as a Symbol now pins.** `graphql_fake(overrides: { name: "Ada" })`
|
|
495
|
+
validated clean and silently fabricated a random value, because lookup is by
|
|
496
|
+
String — the exact silent-green failure the override validation exists to
|
|
497
|
+
prevent.
|
|
498
|
+
- **A helper no longer contradicts `config.default_mode`.** With
|
|
499
|
+
`default_mode = :fake`, an untagged example calling `graphql_in_process`
|
|
500
|
+
raised *"this example is tagged graphql: :fake"* — naming a tag that wasn't
|
|
501
|
+
there, and blocking the untagged form the docs recommend. Only an explicit
|
|
502
|
+
tag can disagree with a helper now.
|
|
503
|
+
- **A variable default that isn't a Boolean no longer reaches a subgraph.** An
|
|
504
|
+
enum or input-object default is a parser AST node; sending one put a
|
|
505
|
+
back-pointer to the lexer on the wire, and an input object raised
|
|
506
|
+
`NoMethodError` outside any rescue. Only `@skip`/`@include` read these, and
|
|
507
|
+
they read Booleans; a subgraph applies its own defaults from the variable
|
|
508
|
+
declaration it already receives.
|
|
509
|
+
- The local router asks the schema for one type by name rather than rebuilding
|
|
510
|
+
the whole type map, at four sites — one of them per response row. On a
|
|
511
|
+
1200-type supergraph a stitched query over 200 rows was **5x** slower than it
|
|
512
|
+
needed to be; the cost is now flat in schema size.
|
|
513
|
+
- **`graphql: :in_process` ran the committed schema dump instead of your live
|
|
514
|
+
schema class.** A dump loads as an anonymous `GraphQL::Schema` subclass, which
|
|
515
|
+
looks like a runnable class and has no resolvers — so every app that followed
|
|
516
|
+
`getting_started.md` (the generator always commits a dump) got a graphql-ruby
|
|
517
|
+
500 blaming its own resolver, and the error that was written to explain this
|
|
518
|
+
was unreachable. Nothing to do; if you worked around it with
|
|
519
|
+
`Testing.config.schema = MySchema`, that still works and still wins.
|
|
520
|
+
- **`GraphWeaver.parse(query:)` accepts a `Pathname`.** `schema:` already did,
|
|
521
|
+
so `GraphWeaver.parse(schema: Rails.root.join("schema.graphql"), query:
|
|
522
|
+
Rails.root.join("app/graphql/queries/person.graphql"))` died on the query
|
|
523
|
+
argument with `undefined method 'end_with?'`. Same for `client.parse(path)`.
|
|
524
|
+
- **`rake graph_weaver:cassettes:check` and `:anonymize` now find cassettes
|
|
525
|
+
from any working directory.** Both read `config.cassette_dir` raw, while
|
|
526
|
+
`Cassette.new` resolves it against `Rails.root` — so run from anywhere but
|
|
527
|
+
the app root, `check` aborted with "this checked nothing, so it proved
|
|
528
|
+
nothing" and `anonymize` silently did nothing, both while a spec run found
|
|
529
|
+
the same files fine. They now use the same resolution the recordings do.
|
|
530
|
+
- **An `extend_type(alias:)` name a struct instance already answers to now
|
|
531
|
+
refuses** instead of silently overriding it. `alias: { hash: "…" }` emitted
|
|
532
|
+
`def hash`, which breaks every `Hash` and `Set` holding that struct;
|
|
533
|
+
`inspect`, `to_s`, `method` and `class` were the same story. A *wire field*
|
|
534
|
+
by any of those names was already refused, so this is the same rule reaching
|
|
535
|
+
the same case from the other side. **If generation now refuses an alias you
|
|
536
|
+
had**, rename it — the accessor it generated was overriding a Ruby method.
|
|
537
|
+
|
|
1
538
|
### v0.5.0 (2026-09-07)
|
|
2
539
|
- **`graphql_in_process(SomeSchema)`** runs one example against that schema
|
|
3
540
|
class's real resolvers — the sibling of `graphql_fake`, and how a federated
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
graph_weaver (0.
|
|
4
|
+
graph_weaver (0.6.0)
|
|
5
5
|
graphql (>= 2.6.7)
|
|
6
6
|
sorbet-runtime
|
|
7
7
|
|
|
@@ -99,16 +99,16 @@ GEM
|
|
|
99
99
|
rubydex (0.2.7-arm64-darwin)
|
|
100
100
|
rubydex (0.2.7-x86_64-darwin)
|
|
101
101
|
rubydex (0.2.7-x86_64-linux)
|
|
102
|
-
simplecov (1.
|
|
103
|
-
sorbet (0.6.
|
|
104
|
-
sorbet-static (= 0.6.
|
|
105
|
-
sorbet-runtime (0.6.
|
|
106
|
-
sorbet-static (0.6.
|
|
107
|
-
sorbet-static (0.6.
|
|
108
|
-
sorbet-static (0.6.
|
|
109
|
-
sorbet-static-and-runtime (0.6.
|
|
110
|
-
sorbet (= 0.6.
|
|
111
|
-
sorbet-runtime (= 0.6.
|
|
102
|
+
simplecov (1.2.0)
|
|
103
|
+
sorbet (0.6.13485)
|
|
104
|
+
sorbet-static (= 0.6.13485)
|
|
105
|
+
sorbet-runtime (0.6.13485)
|
|
106
|
+
sorbet-static (0.6.13485-aarch64-linux)
|
|
107
|
+
sorbet-static (0.6.13485-universal-darwin)
|
|
108
|
+
sorbet-static (0.6.13485-x86_64-linux)
|
|
109
|
+
sorbet-static-and-runtime (0.6.13485)
|
|
110
|
+
sorbet (= 0.6.13485)
|
|
111
|
+
sorbet-runtime (= 0.6.13485)
|
|
112
112
|
spoom (1.8.3)
|
|
113
113
|
erubi (>= 1.10.0)
|
|
114
114
|
prism (>= 0.28.0)
|
|
@@ -176,7 +176,7 @@ CHECKSUMS
|
|
|
176
176
|
google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
|
|
177
177
|
google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
|
|
178
178
|
google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
|
|
179
|
-
graph_weaver (0.
|
|
179
|
+
graph_weaver (0.6.0)
|
|
180
180
|
graphql (2.6.10) sha256=9b7c8633767f516ff9d48a8d6305b2a00a2101c82aa871b92e69086944f9f83e
|
|
181
181
|
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
|
|
182
182
|
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
@@ -206,13 +206,13 @@ CHECKSUMS
|
|
|
206
206
|
rubydex (0.2.7-arm64-darwin) sha256=f0d28bbf4153568be79b671642424750053e0bea971b60ddf5cec19bf4563990
|
|
207
207
|
rubydex (0.2.7-x86_64-darwin) sha256=b002b259d118ac69de44470eff1597143318402c45630c47371f9542631447dc
|
|
208
208
|
rubydex (0.2.7-x86_64-linux) sha256=dacfade9fa42ce4469618da6dac07e69d5f3ac6a313b4caced5234c8f052419a
|
|
209
|
-
simplecov (1.
|
|
210
|
-
sorbet (0.6.
|
|
211
|
-
sorbet-runtime (0.6.
|
|
212
|
-
sorbet-static (0.6.
|
|
213
|
-
sorbet-static (0.6.
|
|
214
|
-
sorbet-static (0.6.
|
|
215
|
-
sorbet-static-and-runtime (0.6.
|
|
209
|
+
simplecov (1.2.0) sha256=ea6acd05eece5a41990e2a5171c57d15700d329326c7666c85ee8c6a0dd0977e
|
|
210
|
+
sorbet (0.6.13485) sha256=b885d6a2fcde57bc46c3cdd597b9014b82619dee0f811a6fdf36f57c8f82c380
|
|
211
|
+
sorbet-runtime (0.6.13485) sha256=2982504e662eb424e515b3aa1c8139070451a014b6c1af2302250ed82495b4cc
|
|
212
|
+
sorbet-static (0.6.13485-aarch64-linux) sha256=5d802793633363442f189615fdb6c6d1be726b402d78a3d63eab96ca74b98d40
|
|
213
|
+
sorbet-static (0.6.13485-universal-darwin) sha256=6c7c4551872d556b37bf1f1a3495509b9fd9479b4a080bd57f9c5db0e8116658
|
|
214
|
+
sorbet-static (0.6.13485-x86_64-linux) sha256=e22d88df0bba5a97e664a329a2212ea59e6a2020626597a162c60c5ddfb4f9b4
|
|
215
|
+
sorbet-static-and-runtime (0.6.13485) sha256=b893346f9e1a244133071ad3c3d06b8ddf70c1b8246bb594c9aaa5cfc778df5f
|
|
216
216
|
spoom (1.8.3) sha256=32871fa189bbfa49cf557a50f819f23cc9a6ceefd0346caa7a6adc193becd5dd
|
|
217
217
|
tapioca (0.19.2) sha256=938731b07811aee8d23871b1aee8861d464fbaf2cfffbf79a62b0c869a5120ec
|
|
218
218
|
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|