graph_weaver 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef7de8e2cd115b4c757a3dac2e8c41a0e87865914e246131c1037a231f7a60af
4
- data.tar.gz: fa89ee78a04a2a07b7c173c82685490ca501a2a29045ef487aae3dd6f3d915d8
3
+ metadata.gz: 7673fd6efc0459bdb3097e73e1c3f8fe265910bfdaf349bf7a358adc1ece1830
4
+ data.tar.gz: f5ce9f4c484e1fb8f5eb074eaaac502ca62fb3ecf6ab90d9157a2a055e88c3b3
5
5
  SHA512:
6
- metadata.gz: 4ae73ed74389baa1f08a42e38b7bf2b0bca2f95ccbe247ccb68fda733c9fee849aed3d17dbba424bdb13104344771d9789401710a5add195c71873edc379530b
7
- data.tar.gz: 24e15e7a6213c0cd5dee16aa73e675649748b84993be11387f638aeca21c15db77a59b1fa5a85ae406af7182be0f9f6a17164b842add3627a7547ab1670616d1
6
+ metadata.gz: a75dd5a723dd52108e2f449bd1c5f0e1dc9a71065106614c57854f318024f700e16596cd4764cf9768f02d536cc040bd0554bdd75762eeba23c09b71a7051cf5
7
+ data.tar.gz: 377c07a20d2cf47873425c72ad84709c36555396fea1a2102c7bdc3f79e07e8e823ed9639238aecac8066f352e65599a10ae15a71013a7de3a0a4d3675bf169c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,52 @@
1
+ ### v0.2.2 (2026-07-22)
2
+ - Generated modules expose from_response / from_response! alongside
3
+ execute / execute!: deserialize a raw GraphQL response (fetched by any
4
+ client) into the typed envelope without going through the transport.
5
+ execute now delegates to from_response
6
+
7
+ ### v0.2.1 (2026-07-13)
8
+ - Conventional paths are appendable lists: queries_paths /
9
+ generated_paths (singular accessors read the first entry, so existing
10
+ config keeps working); load_generated!, Client#load_queries!, and the
11
+ Railtie walk every entry — append spec/support/graphql/* from a spec
12
+ helper to load test-only queries. Entries may be globs, and the
13
+ generated default includes app/graphql/*/generated so per-schema
14
+ layouts auto-load
15
+ - inputs_module derives from the output path: multi-schema layouts name
16
+ each schema's module after its directory
17
+ (app/graphql/github/generated -> GithubInputs), the conventional
18
+ layout keeps GraphQLInputs; GraphWeaver.inputs_module= and
19
+ generate!(inputs_module:) still override
20
+ - Shared types split one-file-per-type: generated/inputs/ holds each
21
+ input struct/enum in its own small file (PokeAPI: 573 files, median
22
+ 24 lines vs one 11.5k-line blob) with inputs.rb as the manifest
23
+ (forward declarations make load order irrelevant); regeneration
24
+ prunes files for types the schema dropped, verify flags strays;
25
+ generate!/verify take inputs_module: per invocation (multi-schema
26
+ apps generate into different modules)
27
+ - Shared input types: generate! emits every variable type (input
28
+ structs + their enums + mapped-enum tables) ONCE per schema into
29
+ generated/inputs.rb (module GraphQLInputs; GraphWeaver.inputs_module=
30
+ renames, shared_inputs: false opts out), with query modules aliasing
31
+ only what their own surface references — AdoptQuery::AdoptionInput
32
+ keeps working and shared types gain one identity across modules.
33
+ Three filtered Hasura queries: 34,684 lines inline -> 11,754 shared
34
+ (~90 lines per query module)
35
+ - BREAKING (vs 0.2.0): auto_fake is opt-in again — require
36
+ "graph_weaver/rspec" no longer swaps every example onto a fake;
37
+ set config.auto_fake = true explicitly (the schema still auto-locates
38
+ once you do). Less magic, no unexpected behavior
39
+ - Generated input structs are table-driven: typed consts + a per-field
40
+ FIELDS table (conversions as lambdas) interpreted by the
41
+ GraphWeaver::InputStruct runtime, replacing unrolled
42
+ serialize/coerce/value_at per struct — a bool_exp-heavy PokeAPI module
43
+ shrinks 29k -> 11.5k lines (-60%) with identical behavior (nil
44
+ omission, wire-value enums, nested/recursive coercion, spellchecked
45
+ unknown keys all covered by the existing suite)
46
+ - Internal: Node base class for the codegen IR protocol; module
47
+ assembly moved from Codegen#generate into Emit#emit_module
48
+ (byte-identical output)
49
+
1
50
  ### v0.2.0 (2026-07-12)
2
51
  - Cleanup pass (staff-engineer review): scalar registrations get the
3
52
  same typo validation as enums/types; cassette replay stops recomputing
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graph_weaver (0.2.0)
4
+ graph_weaver (0.2.2)
5
5
  graphql (>= 2)
6
6
  sorbet-runtime
7
7
 
@@ -19,7 +19,6 @@ GEM
19
19
  irb (~> 1.10)
20
20
  reline (>= 0.3.8)
21
21
  diff-lcs (1.6.2)
22
- docile (1.4.1)
23
22
  erb (6.0.4)
24
23
  erubi (1.13.1)
25
24
  faker (3.8.0)
@@ -43,7 +42,7 @@ GEM
43
42
  google-protobuf (4.35.1-x86_64-linux-gnu)
44
43
  bigdecimal
45
44
  rake (~> 13.3)
46
- graphql (2.6.5)
45
+ graphql (2.6.6)
47
46
  base64
48
47
  fiber-storage
49
48
  logger
@@ -100,21 +99,16 @@ GEM
100
99
  rubydex (0.2.7-arm64-darwin)
101
100
  rubydex (0.2.7-x86_64-darwin)
102
101
  rubydex (0.2.7-x86_64-linux)
103
- simplecov (0.22.0)
104
- docile (~> 1.1)
105
- simplecov-html (~> 0.11)
106
- simplecov_json_formatter (~> 0.1)
107
- simplecov-html (0.13.2)
108
- simplecov_json_formatter (0.1.4)
109
- sorbet (0.6.13327)
110
- sorbet-static (= 0.6.13327)
111
- sorbet-runtime (0.6.13327)
112
- sorbet-static (0.6.13327-aarch64-linux)
113
- sorbet-static (0.6.13327-universal-darwin)
114
- sorbet-static (0.6.13327-x86_64-linux)
115
- sorbet-static-and-runtime (0.6.13327)
116
- sorbet (= 0.6.13327)
117
- sorbet-runtime (= 0.6.13327)
102
+ simplecov (1.0.2)
103
+ sorbet (0.6.13347)
104
+ sorbet-static (= 0.6.13347)
105
+ sorbet-runtime (0.6.13347)
106
+ sorbet-static (0.6.13347-aarch64-linux)
107
+ sorbet-static (0.6.13347-universal-darwin)
108
+ sorbet-static (0.6.13347-x86_64-linux)
109
+ sorbet-static-and-runtime (0.6.13347)
110
+ sorbet (= 0.6.13347)
111
+ sorbet-runtime (= 0.6.13347)
118
112
  spoom (1.8.3)
119
113
  erubi (>= 1.10.0)
120
114
  prism (>= 0.28.0)
@@ -172,7 +166,6 @@ CHECKSUMS
172
166
  concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
173
167
  debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
174
168
  diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
175
- docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
176
169
  erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
177
170
  erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
178
171
  faker (3.8.0) sha256=c147b308df73a90f27a4fc84f18d4c22ef0ad9c2a64b2b61c86fd0ca71753efc
@@ -183,8 +176,8 @@ CHECKSUMS
183
176
  google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
184
177
  google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
185
178
  google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
186
- graph_weaver (0.2.0)
187
- graphql (2.6.5) sha256=1051825bfb4aedb4293cdbcd9726c3150e69e3512556609a32c2ca19d4be3d00
179
+ graph_weaver (0.2.2)
180
+ graphql (2.6.6) sha256=7438e2036b571c884377eb1529225470ba959365f26dc47152953c213bc38c31
188
181
  i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
189
182
  io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
190
183
  irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
@@ -213,15 +206,13 @@ CHECKSUMS
213
206
  rubydex (0.2.7-arm64-darwin) sha256=f0d28bbf4153568be79b671642424750053e0bea971b60ddf5cec19bf4563990
214
207
  rubydex (0.2.7-x86_64-darwin) sha256=b002b259d118ac69de44470eff1597143318402c45630c47371f9542631447dc
215
208
  rubydex (0.2.7-x86_64-linux) sha256=dacfade9fa42ce4469618da6dac07e69d5f3ac6a313b4caced5234c8f052419a
216
- simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
217
- simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246
218
- simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
219
- sorbet (0.6.13327) sha256=4b56cb04542661c6bf5075f1ca38ca00506045325fd689f460aee8466dd3d7a3
220
- sorbet-runtime (0.6.13327) sha256=86f32b2df6d266bf5c0e0eb8ea81d0376ba36eece4c8610938174e403222948d
221
- sorbet-static (0.6.13327-aarch64-linux) sha256=da8a575785d34641095631c50fe1d1f645ee8782c8e2aa5a0ecfd12d1fa22a8a
222
- sorbet-static (0.6.13327-universal-darwin) sha256=a2a8c6b624aada3f6ef68852398de756248f71b3ea691119873515847010dcda
223
- sorbet-static (0.6.13327-x86_64-linux) sha256=f3a915bab4af1979145c793a08d3b1db207526d5e2f332513da10a95e70f9028
224
- sorbet-static-and-runtime (0.6.13327) sha256=7b039be023b6a7d2d17b1f751191f32307d9ace4170b6a192bb0e005d222e00e
209
+ simplecov (1.0.2) sha256=c6459434efe4b948b46477cc2df2faa73ab365f83a33c7c17f81262f4f7f1244
210
+ sorbet (0.6.13347) sha256=2830946e6efda8dc732c5d703350bed17f437fbea1ccfbef543a65e33ae445c1
211
+ sorbet-runtime (0.6.13347) sha256=54e7221d9f4b63f58aec79f3043752a9ba7810347c6ae0d903531591cc2d5f01
212
+ sorbet-static (0.6.13347-aarch64-linux) sha256=9bb60509bf9a8715d93e77cd55f96659575d81a5f47b52bd4f66b3dfe6f2182e
213
+ sorbet-static (0.6.13347-universal-darwin) sha256=d12678a45369dba9db724103d8badda995e66a83ae35798f68d804c46a6d3e45
214
+ sorbet-static (0.6.13347-x86_64-linux) sha256=5a4803d2ba4fc1964e23f3aa4073b224c345c2a72a5f5317623a4bae7ce2c8da
215
+ sorbet-static-and-runtime (0.6.13347) sha256=2130e3c20326948a3edd20385b444345cb7327eea9b141f373486e7c087a498d
225
216
  spoom (1.8.3) sha256=32871fa189bbfa49cf557a50f819f23cc9a6ceefd0346caa7a6adc193becd5dd
226
217
  tapioca (0.19.2) sha256=938731b07811aee8d23871b1aee8861d464fbaf2cfffbf79a62b0c869a5120ec
227
218
  thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
data/PLAN.md CHANGED
@@ -105,6 +105,13 @@ Response envelope; execute! for raise-or-result).
105
105
 
106
106
  ## From the field-test experiments (2026-07-12)
107
107
 
108
+ Generated-module size (2026-07-12, post-0.2.0): input structs are now
109
+ table-driven (InputStruct runtime + FIELDS) — the PokeAPI filtered-query
110
+ module dropped 29,233 -> 11,562 lines. The floor is ~2 lines/field
111
+ (typed const + FIELDS entry). Remaining lever if it matters again:
112
+ shared input structs emitted once per schema instead of per module.
113
+
114
+
108
115
  A junior + senior agent pair exercised the repo cold (clone, examples,
109
116
  extensions, a Pokedex app against Hasura's 4,441-type PokeAPI schema).
110
117
  Fixed same-day: snake_case type names generated invalid constants
@@ -21,7 +21,10 @@ fails when the two drift. The conventional layout (configurable via
21
21
  app/graphql/
22
22
  schema.json # introspection dump (or schema.graphql SDL)
23
23
  queries/ # *.graphql — hand-written, reviewed
24
- generated/ # *_query.rb — generated, checked in, never edited
24
+ generated/
25
+ inputs.rb # manifest: requires + forward declarations
26
+ inputs/ # one file per shared type (input structs, enums)
27
+ *_query.rb # one module per query — generated, checked in, never edited
25
28
  ```
26
29
 
27
30
  The schema dump is step 0 — codegen reads it, never a live endpoint.
@@ -58,6 +61,20 @@ first). Elsewhere it's explicit, factory_bot-style:
58
61
  GraphWeaver.load_generated! # require every file under generated_path
59
62
  ```
60
63
 
64
+ The conventional paths are lists (entries may be globs — the default
65
+ includes `app/graphql/*/generated`, so per-schema layouts load too).
66
+ Append extra locations (a test-only schema, an engine's queries) and
67
+ every loader walks them all:
68
+
69
+ ```ruby
70
+ # e.g. in spec/support/graph_weaver.rb
71
+ GraphWeaver.generated_paths << "spec/support/graphql/generated"
72
+ GraphWeaver.queries_paths << "spec/support/graphql/queries"
73
+ ```
74
+
75
+ The singular accessors (`generated_path` etc.) read and replace the
76
+ first entry — the default target for `generate!` and the rake tasks.
77
+
61
78
  (Plain requires, not Zeitwerk: Zeitwerk would expect
62
79
  `Generated::PersonQuery` from `generated/person_query.rb`, and generated
63
80
  code only changes on regeneration — restart, like a schema migration.)
@@ -102,6 +119,9 @@ module PersonQuery
102
119
  def self.client ... # default client (see below)
103
120
  def self.execute(client = nil, id:) # -> GraphWeaver::Response[Result]
104
121
  def self.execute!(client = nil, id:) # -> Result, or raises QueryError
122
+
123
+ def self.from_response(response) # deserialize a raw hash -> Response[Result]
124
+ def self.from_response!(response) # -> Result, or raises QueryError
105
125
  end
106
126
  ```
107
127
 
@@ -110,6 +130,38 @@ end
110
130
  cost/throttle metadata survive.
111
131
  - `execute!` is the shortcut: the typed result or a raised
112
132
  `GraphWeaver::QueryError`.
133
+ - `from_response` / `from_response!` are the **network-free half** of the
134
+ pair — same envelope, but from a response hash you already have (see below).
135
+
136
+ ## Deserializing a response from another client
137
+
138
+ `execute` is two steps: make the request, then cast the JSON into the typed
139
+ structs. Only the second step is GraphWeaver-specific, and it's exposed on its
140
+ own — so you can fetch with any GraphQL client (Apollo, a raw `Net::HTTP` post,
141
+ a batching layer, a recorded fixture) and hand the result to GraphWeaver:
142
+
143
+ ```ruby
144
+ raw = my_graphql_client.post(PersonQuery::QUERY, id: "1")
145
+ # => {"data" => {"person" => {...}}, "errors" => [...], "extensions" => {...}}
146
+
147
+ response = PersonQuery.from_response(raw) # GraphWeaver::Response[Result]
148
+ person = response.data!.person # typed, no network
149
+
150
+ # or skip the envelope:
151
+ person = PersonQuery.from_response!(raw).person
152
+ ```
153
+
154
+ `from_response` builds the exact same `GraphWeaver::Response[Result]` envelope
155
+ `execute` does — in fact `execute` *is* `from_response(transport.execute(...))`.
156
+ Errors and extensions are preserved; `#data!` / `from_response!` raise
157
+ `QueryError` on top-level errors.
158
+
159
+ The one requirement: pass the response **verbatim** — a hash (or anything with
160
+ `#to_h`) with the standard GraphQL shape and **wire-cased string keys**
161
+ (`"person"`, `"nameWithOwner"`), the top-level `"data"` / `"errors"` /
162
+ `"extensions"` keys included. Don't symbolize or snake_case it first;
163
+ `from_response` reads `raw["data"]` and the casting reads camelCase field keys.
164
+ The module must, of course, be generated for the query you ran.
113
165
 
114
166
  ## Variables become typed kwargs
115
167
 
@@ -152,9 +204,31 @@ hint rather than silently dropping):
152
204
  AdoptQuery.execute!(input: { name: "Rex", species: "DOG" }, detail: true)
153
205
  ```
154
206
 
155
- The structs themselves are module-level (`AdoptQuery::AdoptionInput`) with
156
- `serialize` (aliased as `to_h`) producing the wire hash optional fields
157
- default nil and stay off the wire. Nested inputs work (dependencies emit
207
+ In the generate! workflow, input types (and the enums they use) are
208
+ emitted **once per schema** one file per type under
209
+ `generated/inputs/`, with `inputs.rb` as the manifest. The module is
210
+ named from the output path: the conventional layout gets
211
+ `GraphQLInputs`, while a multi-schema layout names each schema's module
212
+ after its directory (`app/graphql/github/generated` → `GithubInputs`).
213
+ Override globally with `GraphWeaver.inputs_module=` or per run with
214
+ `generate!(inputs_module:)`; opt out with
215
+ `generate!(shared_inputs: false)`. Per-type files keep schema drift
216
+ reviewable: a migration diffs exactly the types it touched, and types
217
+ the schema drops are pruned on regeneration (`verify` flags strays).
218
+ Query modules alias what they touch,
219
+ so `AdoptQuery::AdoptionInput` still works and shared types keep one
220
+ identity across modules — three filtered Hasura queries cost one ~11k-line
221
+ inputs file plus ~90 lines each, instead of ~35k lines of duplicates.
222
+ Deeply nested types live unaliased in the shared module
223
+ (`GraphQLInputs::PetFilter`). Dynamic `parse` stays self-contained.
224
+
225
+ The structs themselves are module-level (`AdoptQuery::AdoptionInput`):
226
+ typed consts plus a compact per-field `FIELDS` table that the
227
+ `GraphWeaver::InputStruct` runtime drives — `serialize` (aliased `to_h`)
228
+ produces the wire hash with nil optionals omitted, `coerce` builds from
229
+ plain hashes. The conversions ship in the generated file as data
230
+ (lambdas in the table), so a Hasura `bool_exp` pulling hundreds of input
231
+ types stays ~2 lines per field instead of unrolled methods. Nested inputs work (dependencies emit
158
232
  first), including recursive ones — Hasura's self-referential `bool_exp`
159
233
  filters generate cleanly (`_and:`/`_not:` fields typed as the struct
160
234
  itself), so variable-driven filtering works:
@@ -91,14 +91,16 @@ PersonQuery.execute!(id: "1").person&.name # typed, via GraphWeaver.client
91
91
  ```ruby
92
92
  # spec/support/graph_weaver.rb
93
93
  require "graph_weaver/rspec"
94
+
95
+ GraphWeaver::Testing.configure { |config| config.auto_fake = true }
94
96
  ```
95
97
 
96
- That's the whole setup: the schema auto-locates from the committed dump
97
- and `auto_fake` defaults on, so every query in every example executes
98
- against a seeded, schema-correct `FakeClient` — no server, no stubs,
99
- and `rspec --seed 1234` reproduces the fake data along with test order.
100
- Pin values with `overrides:`, simulate failures with `Failure.*`, opt
101
- out with `config.auto_fake = false` — see [testing](testing.md).
98
+ The opt-in is deliberate (no surprise fakes); once on, the schema
99
+ auto-locates from the committed dump and every query in every example
100
+ executes against a seeded, schema-correct `FakeClient` — no server, no
101
+ stubs, and `rspec --seed 1234` reproduces the fake data along with test
102
+ order. Pin values with `overrides:`, simulate failures with `Failure.*`
103
+ — see [testing](testing.md).
102
104
 
103
105
  ## 7. Verify in CI
104
106
 
data/docs/testing.md CHANGED
@@ -31,24 +31,22 @@ GraphWeaver::Testing::FakeClient.new(schema:, overrides: {
31
31
  })
32
32
  ```
33
33
 
34
- With rspec, one line in `spec/support/graph_weaver.rb` is the whole
35
- default setup the schema auto-locates from the committed dump at
36
- `GraphWeaver.schema_path`, and `auto_fake` defaults on:
34
+ With rspec, the setup is two lines in `spec/support/graph_weaver.rb`
35
+ the require, plus an explicit opt-in to per-example fakes (deliberately
36
+ not a default: silently swapping every example onto a fake would be
37
+ surprising). The schema auto-locates from the committed dump at
38
+ `GraphWeaver.schema_path`:
37
39
 
38
40
  ```ruby
39
- require "graph_weaver/rspec" # seed follows --seed; every example auto-fakes
40
- ```
41
-
42
- Configure to override any of it:
41
+ require "graph_weaver/rspec" # seed follows --seed
43
42
 
44
- ```ruby
45
43
  GraphWeaver::Testing.configure do |config|
46
- config.schema = MySchema # an in-process class instead of the located dump
47
- config.auto_fake = false # opt out of per-example fakes
48
- config.mode = :faker # or :literal (plain typed values); nil = auto
49
- config.overrides = { "Person.name" => "Daniel" }
50
- config.list_size = 1..3
51
- config.null_chance = 0.1 # nullable fields go nil sometimes
44
+ config.auto_fake = true # every example runs against a fresh fake
45
+ # config.schema = MySchema # optional: an in-process class instead of the dump
46
+ # config.mode = :faker # or :literal (plain typed values); nil = auto
47
+ # config.overrides = { "Person.name" => "Daniel" }
48
+ # config.list_size = 1..3
49
+ # config.null_chance = 0.1 # nullable fields go nil sometimes
52
50
  end
53
51
  ```
54
52
 
@@ -59,6 +57,15 @@ they consult `GraphWeaver.client`). `mode:` picks value fabrication: `:faker`
59
57
  (semantic, field-name matched — raises if the gem is missing),
60
58
  `:literal` (plain type-derived), or nil to auto-detect faker.
61
59
 
60
+ Test-only queries don't have to live in `app/` — the conventional paths
61
+ are appendable lists, so the same support file can register a
62
+ spec-local set that `load_generated!` (and the Railtie) pick up:
63
+
64
+ ```ruby
65
+ GraphWeaver.generated_paths << "spec/support/graphql/generated"
66
+ GraphWeaver.queries_paths << "spec/support/graphql/queries"
67
+ ```
68
+
62
69
  **Simulating failures** — every failure mode is just a client, so
63
70
  error-handling paths are testable without a server that misbehaves on cue:
64
71
 
@@ -127,8 +127,9 @@ class GraphWeaver::Client
127
127
  #
128
128
  # Reloadable (constants are replaced), so it suits consoles and dev.
129
129
  # Returns the modules.
130
- def load_queries!(dir = GraphWeaver.queries_path, namespace: Object)
131
- Dir[File.join(dir, "*.graphql")].sort.map do |path|
130
+ def load_queries!(dir = nil, namespace: Object)
131
+ dirs = dir ? [dir] : GraphWeaver.queries_paths
132
+ dirs.flat_map { |d| Dir[File.join(d, "*.graphql")].sort }.map do |path|
132
133
  name = "#{GraphWeaver::Inflect.camelize(File.basename(path, ".graphql"))}Query"
133
134
  namespace.send(:remove_const, name) if namespace.const_defined?(name, false)
134
135
  GraphWeaver.log(:info) { "loaded #{name} from #{path}" }