graph_weaver 0.6.1 → 0.7.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1447 -1
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +151 -2
  5. data/README.md +20 -6
  6. data/docs/alternatives.md +201 -0
  7. data/docs/cassettes.md +17 -1
  8. data/docs/errors.md +382 -17
  9. data/docs/federation.md +469 -63
  10. data/docs/generated_modules.md +231 -15
  11. data/docs/getting_started.md +497 -104
  12. data/docs/i18n.md +234 -0
  13. data/docs/logging.md +160 -24
  14. data/docs/real_world.md +28 -0
  15. data/docs/scalars.md +190 -26
  16. data/docs/testing.md +457 -58
  17. data/docs/transports.md +164 -19
  18. data/docs/upgrading.md +328 -3
  19. data/graph_weaver.gemspec +7 -0
  20. data/lib/generators/graph_weaver/install_generator.rb +138 -4
  21. data/lib/graph_weaver/client.rb +47 -10
  22. data/lib/graph_weaver/codegen/aliases.rb +7 -5
  23. data/lib/graph_weaver/codegen/emit.rb +98 -29
  24. data/lib/graph_weaver/codegen/enum_type.rb +2 -1
  25. data/lib/graph_weaver/codegen/nodes.rb +39 -6
  26. data/lib/graph_weaver/codegen/registry.rb +175 -0
  27. data/lib/graph_weaver/codegen/scalar_type.rb +123 -30
  28. data/lib/graph_weaver/codegen/type_helpers.rb +56 -11
  29. data/lib/graph_weaver/codegen.rb +404 -197
  30. data/lib/graph_weaver/coerce.rb +155 -26
  31. data/lib/graph_weaver/errors.rb +264 -34
  32. data/lib/graph_weaver/federation.rb +119 -26
  33. data/lib/graph_weaver/graph.rb +315 -0
  34. data/lib/graph_weaver/hints.rb +100 -24
  35. data/lib/graph_weaver/in_process.rb +17 -11
  36. data/lib/graph_weaver/input_struct.rb +119 -32
  37. data/lib/graph_weaver/internal/endpoint.rb +78 -0
  38. data/lib/graph_weaver/internal/headers.rb +51 -0
  39. data/lib/graph_weaver/internal/overrides.rb +67 -5
  40. data/lib/graph_weaver/internal/planner.rb +45 -15
  41. data/lib/graph_weaver/internal/refusal.rb +49 -0
  42. data/lib/graph_weaver/internal/schemas.rb +23 -9
  43. data/lib/graph_weaver/internal/selection.rb +34 -0
  44. data/lib/graph_weaver/internal/server_input.rb +251 -0
  45. data/lib/graph_weaver/internal/test_clients.rb +276 -0
  46. data/lib/graph_weaver/internal/unused.rb +287 -0
  47. data/lib/graph_weaver/internal/values.rb +40 -4
  48. data/lib/graph_weaver/internal.rb +183 -1
  49. data/lib/graph_weaver/log_subscriber.rb +66 -0
  50. data/lib/graph_weaver/logging.rb +136 -12
  51. data/lib/graph_weaver/query_module.rb +36 -3
  52. data/lib/graph_weaver/railtie.rb +237 -17
  53. data/lib/graph_weaver/representation.rb +55 -17
  54. data/lib/graph_weaver/result_struct.rb +90 -0
  55. data/lib/graph_weaver/retry.rb +33 -5
  56. data/lib/graph_weaver/rspec.rb +404 -93
  57. data/lib/graph_weaver/schema_loader.rb +221 -49
  58. data/lib/graph_weaver/tasks.rb +380 -89
  59. data/lib/graph_weaver/testing/cassette.rb +6 -5
  60. data/lib/graph_weaver/testing/endpoint.rb +106 -0
  61. data/lib/graph_weaver/testing/failure.rb +69 -12
  62. data/lib/graph_weaver/testing/fake_client.rb +133 -44
  63. data/lib/graph_weaver/testing/router.rb +58 -11
  64. data/lib/graph_weaver/testing.rb +200 -58
  65. data/lib/graph_weaver/transport/faraday.rb +41 -8
  66. data/lib/graph_weaver/transport/http.rb +46 -4
  67. data/lib/graph_weaver/transport.rb +109 -26
  68. data/lib/graph_weaver/version.rb +1 -1
  69. data/lib/graph_weaver.rb +474 -106
  70. metadata +56 -1
data/Gemfile CHANGED
@@ -1,3 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ # Mutation testing — not in the gemspec: it is a local quality tool, not a
6
+ # development dependency contributors need to install the gem's suite.
7
+ # Proprietary EULA, free for opensource projects (see `usage:` in mutant.yml).
8
+ group :development do
9
+ gem "mutant", "0.16.3"
10
+ gem "mutant-rspec", "0.16.3"
11
+ end
data/Gemfile.lock CHANGED
@@ -1,24 +1,63 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graph_weaver (0.6.1)
4
+ graph_weaver (0.7.0)
5
5
  graphql (>= 2.6.7)
6
6
  sorbet-runtime
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
+ actionpack (8.1.3.1)
12
+ actionview (= 8.1.3.1)
13
+ activesupport (= 8.1.3.1)
14
+ nokogiri (>= 1.8.5)
15
+ rack (>= 2.2.4)
16
+ rack-session (>= 1.0.1)
17
+ rack-test (>= 0.6.3)
18
+ rails-dom-testing (~> 2.2)
19
+ rails-html-sanitizer (~> 1.6)
20
+ useragent (~> 0.16)
21
+ actionview (8.1.3.1)
22
+ activesupport (= 8.1.3.1)
23
+ builder (~> 3.1)
24
+ erubi (~> 1.11)
25
+ rails-dom-testing (~> 2.2)
26
+ rails-html-sanitizer (~> 1.6)
27
+ activesupport (8.1.3.1)
28
+ base64
29
+ bigdecimal
30
+ concurrent-ruby (~> 1.0, >= 1.3.1)
31
+ connection_pool (>= 2.2.5)
32
+ drb
33
+ i18n (>= 1.6, < 2)
34
+ json
35
+ logger (>= 1.4.2)
36
+ minitest (>= 5.1)
37
+ securerandom (>= 0.3)
38
+ tzinfo (~> 2.0, >= 2.0.5)
39
+ uri (>= 0.13.1)
40
+ addressable (2.9.0)
41
+ public_suffix (>= 2.0.2, < 8.0)
11
42
  apollo-federation (3.10.3)
12
43
  google-protobuf (< 5)
13
44
  graphql
45
+ ast (2.4.3)
14
46
  base64 (0.3.0)
15
47
  benchmark (0.5.0)
16
48
  bigdecimal (4.1.2)
49
+ builder (3.3.0)
17
50
  concurrent-ruby (1.3.7)
51
+ connection_pool (3.0.2)
52
+ crack (1.0.1)
53
+ bigdecimal
54
+ rexml
55
+ crass (1.0.7)
18
56
  debug (1.11.1)
19
57
  irb (~> 1.10)
20
58
  reline (>= 0.3.8)
21
59
  diff-lcs (1.6.2)
60
+ drb (2.2.3)
22
61
  erb (6.0.4)
23
62
  erubi (1.13.1)
24
63
  faker (3.8.0)
@@ -46,6 +85,7 @@ GEM
46
85
  base64
47
86
  fiber-storage
48
87
  logger
88
+ hashdiff (1.2.1)
49
89
  i18n (1.15.2)
50
90
  concurrent-ruby (~> 1.0)
51
91
  io-console (0.8.2)
@@ -56,14 +96,68 @@ GEM
56
96
  reline (>= 0.4.2)
57
97
  json (2.21.2)
58
98
  logger (1.7.0)
99
+ loofah (2.25.2)
100
+ crass (~> 1.0.2)
101
+ nokogiri (>= 1.12.0)
102
+ minitest (6.0.6)
103
+ drb (~> 2.0)
104
+ prism (~> 1.5)
105
+ mutant (0.16.3)
106
+ diff-lcs (>= 1.6, < 3)
107
+ irb (~> 1.15)
108
+ parser (~> 3.3.10)
109
+ regexp_parser (~> 2.10)
110
+ securerandom (>= 0.3)
111
+ sorbet-runtime (~> 0.6.0)
112
+ unparser (>= 0.8.2, < 0.10)
113
+ mutant-rspec (0.16.3)
114
+ mutant (= 0.16.3)
115
+ rspec-core (>= 3.8.0, < 5.0.0)
59
116
  net-http (0.9.1)
60
117
  uri (>= 0.11.1)
61
118
  netrc (0.11.0)
119
+ nokogiri (1.19.4-aarch64-linux-gnu)
120
+ racc (~> 1.4)
121
+ nokogiri (1.19.4-arm64-darwin)
122
+ racc (~> 1.4)
123
+ nokogiri (1.19.4-x86_64-darwin)
124
+ racc (~> 1.4)
125
+ nokogiri (1.19.4-x86_64-linux-gnu)
126
+ racc (~> 1.4)
62
127
  parallel (2.1.0)
128
+ parser (3.3.12.0)
129
+ ast (~> 2.4.1)
130
+ racc
63
131
  pp (0.6.4)
64
132
  prettyprint
65
133
  prettyprint (0.2.0)
66
134
  prism (1.9.0)
135
+ public_suffix (7.0.5)
136
+ racc (1.8.1)
137
+ rack (3.2.7)
138
+ rack-session (2.1.2)
139
+ base64 (>= 0.1.0)
140
+ rack (>= 3.0.0)
141
+ rack-test (2.2.0)
142
+ rack (>= 1.3)
143
+ rackup (2.3.1)
144
+ rack (>= 3)
145
+ rails-dom-testing (2.3.0)
146
+ activesupport (>= 5.0.0)
147
+ minitest
148
+ nokogiri (>= 1.6)
149
+ rails-html-sanitizer (1.7.1)
150
+ loofah (~> 2.25, >= 2.25.2)
151
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
152
+ railties (8.1.3.1)
153
+ actionpack (= 8.1.3.1)
154
+ activesupport (= 8.1.3.1)
155
+ irb (~> 1.13)
156
+ rackup (>= 1.0.0)
157
+ rake (>= 12.2)
158
+ thor (~> 1.0, >= 1.2.2)
159
+ tsort (>= 0.2)
160
+ zeitwerk (~> 2.6)
67
161
  rake (13.4.2)
68
162
  rbi (0.3.14)
69
163
  prism (~> 1.0)
@@ -78,6 +172,7 @@ GEM
78
172
  rbs (>= 4.0.0)
79
173
  tsort
80
174
  redcarpet (3.6.1)
175
+ regexp_parser (2.12.0)
81
176
  reline (0.6.3)
82
177
  io-console (~> 0.5)
83
178
  require-hooks (0.4.0)
@@ -99,6 +194,7 @@ GEM
99
194
  rubydex (0.2.7-arm64-darwin)
100
195
  rubydex (0.2.7-x86_64-darwin)
101
196
  rubydex (0.2.7-x86_64-linux)
197
+ securerandom (0.4.1)
102
198
  simplecov (1.2.0)
103
199
  sorbet (0.6.13485)
104
200
  sorbet-static (= 0.6.13485)
@@ -131,9 +227,21 @@ GEM
131
227
  tsort
132
228
  thor (1.5.0)
133
229
  tsort (0.2.0)
230
+ tzinfo (2.0.6)
231
+ concurrent-ruby (~> 1.0)
232
+ unparser (0.9.0)
233
+ diff-lcs (>= 1.6, < 3)
234
+ parser (>= 3.3.0)
235
+ prism (>= 1.5.1)
134
236
  uri (1.1.1)
237
+ useragent (0.16.11)
238
+ webmock (3.26.4)
239
+ addressable (>= 2.8.0)
240
+ crack (>= 0.3.2)
241
+ hashdiff (>= 0.4.0, < 2.0.0)
135
242
  webrick (1.9.2)
136
243
  yard (0.9.44)
244
+ zeitwerk (2.8.3)
137
245
 
138
246
  PLATFORMS
139
247
  aarch64-linux
@@ -149,23 +257,38 @@ DEPENDENCIES
149
257
  faker
150
258
  faraday
151
259
  graph_weaver!
260
+ mutant (= 0.16.3)
261
+ mutant-rspec (= 0.16.3)
262
+ rack
263
+ railties
152
264
  rake
153
265
  redcarpet
154
266
  rspec
155
267
  simplecov
156
268
  sorbet
157
269
  tapioca
270
+ webmock
158
271
  webrick
159
272
  yard
160
273
 
161
274
  CHECKSUMS
275
+ actionpack (8.1.3.1) sha256=974cb7154548e81f470b1b0f247b99cb38e87825899dca58610596e2817723d0
276
+ actionview (8.1.3.1) sha256=2da68b8414c47b43bfbed1ce69c5afe1c04f78c267aacb5660a4cab5ca12cfb6
277
+ activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376
278
+ addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
162
279
  apollo-federation (3.10.3) sha256=61e6d8e1fd695ee45bab5d84f86f586d99c296ad77a86c6f1e7b964ac17be3d7
280
+ ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
163
281
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
164
282
  benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
165
283
  bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
284
+ builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
166
285
  concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
286
+ connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
287
+ crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
288
+ crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
167
289
  debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
168
290
  diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
291
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
169
292
  erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
170
293
  erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
171
294
  faker (3.8.0) sha256=c147b308df73a90f27a4fc84f18d4c22ef0ad9c2a64b2b61c86fd0ca71753efc
@@ -176,24 +299,44 @@ CHECKSUMS
176
299
  google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
177
300
  google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
178
301
  google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
179
- graph_weaver (0.6.1)
302
+ graph_weaver (0.7.0)
180
303
  graphql (2.6.10) sha256=9b7c8633767f516ff9d48a8d6305b2a00a2101c82aa871b92e69086944f9f83e
304
+ hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
181
305
  i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
182
306
  io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
183
307
  irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
184
308
  json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
185
309
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
310
+ loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918
311
+ minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
312
+ mutant (0.16.3) sha256=7e10fb09b80a78d82f8e38283fc1e4d562cbb1392451e2a21611ea2bc44b5023
313
+ mutant-rspec (0.16.3) sha256=006ee9640800f205278604bb2160ca4b1d0013464fb216a48b7c09571a1fc547
186
314
  net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
187
315
  netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f
316
+ nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
317
+ nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527
318
+ nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551
319
+ nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
188
320
  parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
321
+ parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
189
322
  pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570
190
323
  prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
191
324
  prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
325
+ public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
326
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
327
+ rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
328
+ rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
329
+ rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
330
+ rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
331
+ rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
332
+ rails-html-sanitizer (1.7.1) sha256=e797a7c9b01e567307e317c576b49ab4168017e63eea4dba9ce3cb587e2f22c2
333
+ railties (8.1.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487
192
334
  rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
193
335
  rbi (0.3.14) sha256=932ed904adbb5ca645c285c8b36da1b7ff6321ec1870d187d6325248cb637658
194
336
  rbs (4.0.3) sha256=5a7bf70e2628549d9a1f44eae447b2cfe55968a9c60cfff52693a4bdcc020e14
195
337
  rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
196
338
  redcarpet (3.6.1) sha256=d444910e6aa55480c6bcdc0cdb057626e8a32c054c29e793fa642ba2f155f445
339
+ regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
197
340
  reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
198
341
  require-hooks (0.4.0) sha256=005f4c6435b4edae73e358cdbaba48370a4121f9ce893d5d2a3c66fce855677d
199
342
  rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
@@ -206,6 +349,7 @@ CHECKSUMS
206
349
  rubydex (0.2.7-arm64-darwin) sha256=f0d28bbf4153568be79b671642424750053e0bea971b60ddf5cec19bf4563990
207
350
  rubydex (0.2.7-x86_64-darwin) sha256=b002b259d118ac69de44470eff1597143318402c45630c47371f9542631447dc
208
351
  rubydex (0.2.7-x86_64-linux) sha256=dacfade9fa42ce4469618da6dac07e69d5f3ac6a313b4caced5234c8f052419a
352
+ securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
209
353
  simplecov (1.2.0) sha256=ea6acd05eece5a41990e2a5171c57d15700d329326c7666c85ee8c6a0dd0977e
210
354
  sorbet (0.6.13485) sha256=b885d6a2fcde57bc46c3cdd597b9014b82619dee0f811a6fdf36f57c8f82c380
211
355
  sorbet-runtime (0.6.13485) sha256=2982504e662eb424e515b3aa1c8139070451a014b6c1af2302250ed82495b4cc
@@ -217,9 +361,14 @@ CHECKSUMS
217
361
  tapioca (0.19.2) sha256=938731b07811aee8d23871b1aee8861d464fbaf2cfffbf79a62b0c869a5120ec
218
362
  thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
219
363
  tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
364
+ tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
365
+ unparser (0.9.0) sha256=4331f174a73a23b69250b13d47da3794ed1449711ee0f9ed8947dc020ba76067
220
366
  uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
367
+ useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
368
+ webmock (3.26.4) sha256=8d8da206d217ebe6968cfb09c77f4533c23074e1432bad865f3994eacbaad50d
221
369
  webrick (1.9.2) sha256=beb4a15fc474defed24a3bda4ffd88a490d517c9e4e6118c3edce59e45864131
222
370
  yard (0.9.44) sha256=eb087e9b631ccd887b049f303d489963945452d5e2a7eb49a5a74a7cf6887f28
371
+ zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5
223
372
 
224
373
  BUNDLED WITH
225
374
  4.0.10
data/README.md CHANGED
@@ -41,6 +41,10 @@ result.person&.nmae
41
41
  `person` is `T.nilable` because the schema says the field is nullable — the `&.`
42
42
  isn't defensive, it's the schema talking. A field you misspelled, or never
43
43
  selected, is a typecheck error rather than a `NoMethodError` in production.
44
+ Static Sorbet is optional: `sorbet-runtime` is the only Sorbet gem this one
45
+ needs, so if your app doesn't run `srb tc`, that same typo surfaces as a
46
+ `NoMethodError` the first time the line runs instead of in CI — [Sorbet, with or
47
+ without](docs/getting_started.md#sorbet-with-or-without).
44
48
 
45
49
  ## Start here
46
50
 
@@ -63,6 +67,11 @@ You then rarely type `rake graph_weaver:generate` again: while the dev server is
63
67
  up, a `.graphql` edit regenerates and reloads before the next request, the way a
64
68
  route change does. Run it when you're ready to commit the Ruby.
65
69
 
70
+ A second schema is a second `GraphWeaver.graph` — its own queries, output,
71
+ client, namespace and scalar registrations — and the same one command generates
72
+ and verifies the app: **[more than one
73
+ schema](docs/getting_started.md#more-than-one-schema)**.
74
+
66
75
  Or skip the build step and poke at an API from a console —
67
76
  anything holding a schema parses, and the module runs on what parsed it:
68
77
 
@@ -94,7 +103,7 @@ then one tag says what an example runs against:
94
103
  it "shows the profile", graphql: :fake do
95
104
  person = PersonQuery.execute!(id: "1").person
96
105
 
97
- person.name # => "Shakita Stark" fabricated from your schema
106
+ person.name # => "Shakita Stark" with faker installed; "name-1" without
98
107
  person.birthday # => #<Date: 2024-12-16> custom scalars included
99
108
  person.pets.size # => 2
100
109
  end
@@ -119,7 +128,8 @@ reads the selected fields off your factory's object and fabricates the rest. The
119
128
  are checked and spellchecked, so a typo raises instead of leaving the example
120
129
  green against random data. The tag also picks a *real* client
121
130
  when you want one: `:in_process` runs your resolvers, `:router` runs them across a
122
- federated graph. Field-level failure simulation and record/replay cassettes with
131
+ federated graph, and `:wire` serves either at your own endpoint, so the transport
132
+ you ship runs too. Field-level failure simulation and record/replay cassettes with
123
133
  anonymization are in [testing](docs/testing.md).
124
134
 
125
135
  ## Federation without a gateway
@@ -150,8 +160,10 @@ See [federation](docs/federation.md).
150
160
 
151
161
  The lifecycle is rake tasks, not a CI pipeline you assemble yourself:
152
162
  `schema:refresh` re-introspects the committed dump, `schema:diff` names what
153
- changed when the server has drifted, `queries:check` names the queries that
154
- drift broke and where, and `verify` fails when the checked-in Ruby is stale.
163
+ changed when whatever that dump came from — an endpoint, your own schema class
164
+ has moved past it, `queries:check` names the queries that drift broke and where,
165
+ `unused` names the selections your app stopped reading, and `verify` fails when
166
+ the checked-in Ruby is stale.
155
167
  Generation is deterministic — same schema and queries, byte-identical files — so
156
168
  regenerating never shows a diff you didn't earn. See
157
169
  [getting started](docs/getting_started.md#5-verify-in-ci).
@@ -162,7 +174,7 @@ upgrade, and `verify` is what tells you when you've skipped it.
162
174
 
163
175
  #### Also in the box
164
176
 
165
- - **Queries and mutations** with typed variable kwargs — enums as `T::Enum`s, input objects as `T::Struct`s, required vs optional falling out of nullability and defaults
177
+ - **Queries and mutations** with typed variable kwargs — enums as `T::Enum`s, input objects as `T::Struct`s, required vs optional falling out of nullability and defaults. Results are generated per selection set; an input object has no selection set, so [its whole closure is](docs/generated_modules.md#an-input-object-generates-its-whole-closure)
166
178
  - **Fragments** (inline, named, type conditions), **unions and interfaces** (member structs, `__typename` dispatch), `@skip`/`@include` nullability
167
179
  - **Any transport**: in-process execution, a zero-dependency HTTP client, or Faraday with your own middleware — plus a composable `Retry` with backoff and jitter
168
180
  - **Structured errors**: a typed envelope that keeps partial data and extensions, an error hierarchy split by failure site, field-level reports with entity ids, and stale-schema detection
@@ -175,12 +187,14 @@ upgrade, and `verify` is what tells you when you've skipped it.
175
187
  - **[Federation](docs/federation.md)** — supergraph vs API schema, the local router, what it refuses
176
188
  - **[Transports](docs/transports.md)** — the execute contract, Faraday, retries and backoff
177
189
  - **[Errors](docs/errors.md)** — the Response envelope, the error hierarchy, field-level reports
190
+ - **[Translating input errors](docs/i18n.md)** — stable keys and data for bad input, on either side of the wire
178
191
  - **[Custom scalars](docs/scalars.md)** — what's registered already, codec inference, input coercion
179
192
  - **[Cassettes](docs/cassettes.md)** — capture and replay real responses, anonymized
180
193
  - **[Editor support](docs/editors.md)** — five lines of YAML for schema autocomplete in `.graphql` files, no JS project
181
194
  - **[Against a real API](docs/real_world.md)** — introspecting a live endpoint, GitHub end to end
182
195
  - **[Logging](docs/logging.md)** — point `GraphWeaver.logger` at any Logger
183
- - **[Upgrading](docs/upgrading.md)** — regenerate on every bump, and what 0.5.0 moved
196
+ - **[Upgrading](docs/upgrading.md)** — regenerate on every bump, and what each release moved
197
+ - **[Alternatives](docs/alternatives.md)** — graphql-client, graphlient, artemis, hand-rolled: what each is good at, and where this one loses
184
198
 
185
199
  ----
186
200
  ## Development
@@ -0,0 +1,201 @@
1
+ # Alternatives
2
+
3
+ Why another Ruby GraphQL client, what else exists, and where each one wins.
4
+ Read it before adopting — including the last section, which is where this gem
5
+ loses.
6
+
7
+ *Figures checked 2026-09-12. Every claim carries its source in an HTML comment
8
+ beside it; a maintainer re-checking this page should follow those, not trust the
9
+ prose.*
10
+
11
+ ## Why another one
12
+
13
+ Ruby has **one** real GraphQL client — GitHub's `graphql-client` — and the two
14
+ maintained alternatives are wrappers around it.<!-- graphlient.gemspec deps: faraday ~>2.0, graphql-client; artemis.gemspec deps include graphql-client >= 0.13.0 — https://rubygems.org/api/v1/gems/graphlient.json, https://rubygems.org/api/v1/gems/artemis.json -->
15
+ All three share one design: the result object is built at runtime by
16
+ metaprogramming against an introspected schema, so a field you misspelled is a
17
+ `NoMethodError` on a production request rather than a red build.<!-- graphql-client lib/graphql/client/schema/object_type.rb:231-266 — method_missing raises UnimplementedFieldError/UnfetchedFieldError at runtime; a camelCase typo re-raises plain NoMethodError at :248-250 -->
18
+ And none of them ship anything to test with.<!-- graphql-client: no fakes/stubs/fixtures anywhere in lib/, no testing guide among its 14 guides — https://github.com/github-community-projects/graphql-client/tree/master/guides -->
19
+
20
+ GraphWeaver makes a different structural bet: **the schema is known at
21
+ generation time**, so result types can be real files on disk that `srb tc`
22
+ reads, and the same knowledge that makes them exact is what lets the gem
23
+ fabricate them for your tests. That second half is the part every code generator
24
+ in every language skips — and it is the reason precise types usually feel
25
+ expensive.
26
+
27
+ The honest counter: if your app doesn't use Sorbet, most of that value
28
+ evaporates, and [graphlient](#graphlient) is the better answer.
29
+
30
+ This idea was tried once before. `yogurt` generated Sorbet types from GraphQL
31
+ documents, shipped two versions 70 minutes apart in November 2020, and stopped —
32
+ its own README concedes it lacked named fragments and that the author "probably
33
+ got a lot of the decisions wrong".<!-- https://raw.githubusercontent.com/theorygeek/yogurt/master/README.md ; versions 0.1.1 16:52 and 0.2.0 18:02 on 2020-11-26 — https://rubygems.org/api/v1/versions/yogurt.json ; last real commit 29ef902 2020-11-26 -->
34
+ So the thesis is unproven, not proven wrong.
35
+
36
+ ## The table
37
+
38
+ `graphql-ruby` isn't a column because it isn't a client — see
39
+ [graphql-ruby alone](#graphql-ruby-alone). "Hand-rolled" is `Net::HTTP` or
40
+ Faraday plus a query string and `response["data"]["..."]`, which is what most
41
+ vendor SDKs actually do.
42
+
43
+ | | **graph_weaver** | **graphql-client** | **graphlient** | **artemis** | **hand-rolled** |
44
+ |---|---|---|---|---|---|
45
+ | **Query lives in** | a `.graphql` file, one operation each<!-- README.md; DECISIONS.md "Directories organize queries" --> | a heredoc assigned to a Ruby constant (enforced)<!-- lib/graphql/client.rb:345-347 raises DynamicQueryError when definition.name is nil --> | a heredoc, a Ruby block DSL, or `parse`<!-- https://github.com/ashkan18/graphlient#usage ; lib/graphlient/query/serializer.rb --> | a `.graphql` file under `app/operations`<!-- https://github.com/yuki24/artemis#the-convention --> | a string in your code |
46
+ | **A result is** | a checked-in nested `T::Struct`<!-- README.md; lib/graph_weaver/result_struct.rb --> | an anonymous class, readers per selected field<!-- lib/graphql/client/schema/object_type.rb:10-22, 56-60 --> | the same (it returns graphql-client's `Response`)<!-- lib/graphlient/client.rb:41 --> | the same<!-- artemis lib/artemis/client.rb #execute delegates to client.query --> | a `Hash` |
47
+ | **A typo is caught** | at `srb tc`, before you run it | at runtime, on the request<!-- object_type.rb:243-266 --> | at runtime | at runtime | never |
48
+ | **Schema needed** | at codegen time: live class, introspection dump, SDL, or supergraph<!-- README.md; lib/graph_weaver/schema_loader.rb --> | at boot: dump recommended; SDL support merged but unreleased<!-- README.md:34-42; https://github.com/github-community-projects/graphql-client/pull/60 merged 2025-12-06, not in 0.26.0 --> | at runtime: introspects over HTTP lazily unless you pass `schema_path`<!-- lib/graphlient/schema.rb:16; lib/graphlient/client.rb:76 --> | a checked-in dump per service<!-- lib/artemis/railtie.rb schema_path vendor/graphql/schema/<service>.json --> | none |
49
+ | **Codegen** | yes — you check the Ruby in | none, all runtime metaprogramming<!-- lib/graphql/client/schema.rb:68-81 --> | none | none | none |
50
+ | **Typing** | Sorbet `# typed: strict`, per query<!-- README.md --> | none shipped; Tapioca PR stalled since 2024-11, and schema-wide not per-operation<!-- https://github.com/github-community-projects/graphql-client/pull/7 — open, mergeable_state blocked, last touched 2024-11-07 --> | none<!-- no .rbs/.rbi/sig in the gem --> | none | none |
51
+ | **Testing** | schema-driven fakes, pinning, failure simulation, cassettes<!-- docs/testing.md; lib/graph_weaver/rspec.rb; lib/graph_weaver/testing/ --> | nothing ships<!-- no stub/fake/fixture in lib/ --> | documented WebMock patterns only<!-- https://github.com/ashkan18/graphlient#testing-with-graphlient-and-rspec --> | `stub_graphql` + YAML fixtures, unvalidated against the schema<!-- lib/artemis/test_helper.rb; lib/artemis/adapters/test_adapter.rb returns fixtures verbatim --> | WebMock |
52
+ | **Federation** | plans and runs a supergraph in-process<!-- docs/federation.md; lib/graph_weaver/federation.rb --> | none; open crash against federated routers<!-- https://github.com/github-community-projects/graphql-client/issues/78 open since 2026-01-15 --> | none | none | none |
53
+ | **Errors** | typed envelope keeping partial data, hierarchy by failure site<!-- docs/errors.md; lib/graph_weaver/errors.rb --> | raw hashes; HTTP errors become a fake `errors` array<!-- lib/graphql/client/http.rb:80-85; https://github.com/github-community-projects/graphql-client/issues/67 --> | raises a real class hierarchy on every failure<!-- lib/graphlient/errors/ --> | thin hierarchy, mostly never raised<!-- lib/artemis/exceptions.rb — GraphQLError/GraphQLServerError defined, never raised --> | yours to write |
54
+ | **Transport** | `Net::HTTP` (pooled) or Faraday, plus `Retry`<!-- docs/transports.md; lib/graph_weaver/transport/ --> | stock adapter self-described as trivial<!-- lib/graphql/client/http.rb:17-19 "Production applications should consider implementing their own network adapter" --> | Faraday 2.x, full middleware access<!-- lib/graphlient/adapters/http/faraday_adapter.rb:36-48 --> | four adapters, no middleware layer<!-- lib/artemis/adapters.rb; https://github.com/yuki24/artemis/issues/57 open since 2019 --> | whatever you picked |
55
+ | **Rails** | generator, railtie, reload-on-edit, rake lifecycle<!-- lib/generators/graph_weaver/install_generator.rb; lib/graph_weaver/railtie.rb; lib/graph_weaver/tasks.rb --> | opt-in railtie, no generators; docs call the boot order "a mess"<!-- lib/graphql/client/railtie.rb:34-37 TODO; https://github.com/github-community-projects/graphql-client/blob/master/guides/rails-configuration.md --> | none | the whole pitch: generators, config, callbacks<!-- lib/artemis/railtie.rb; lib/generators/artemis/ --> | n/a |
56
+ | **Last release** | v0.7.0, 2026-09-12 | v0.26.0, 2025-05-29<!-- https://rubygems.org/api/v1/gems/graphql-client.json --> | v0.9.0, 2026-08-02<!-- https://rubygems.org/api/v1/gems/graphlient.json --> | v1.1.0, 2024-08-16<!-- https://rubygems.org/api/v1/gems/artemis.json --> | n/a |
57
+ | **Downloads** | 7.5k<!-- 7,478 — https://rubygems.org/api/v1/gems/graph_weaver.json --> | 94M<!-- 94,408,657 --> | 32M<!-- 32,287,626 --> | 430k<!-- 425,737 --> | n/a |
58
+
59
+ ## graphql-client
60
+
61
+ **Where it shines.** It is GitHub's, it has a decade of production use, and 94M
62
+ downloads means someone has hit your problem before you. The duck-typed
63
+ `execute:` slot is genuinely good design — point it at a graphql-ruby schema and
64
+ queries run in-process with no socket.<!-- https://github.com/github-community-projects/graphql-client/blob/master/guides/local-queries.md -->
65
+ It ships two RuboCop cops, including `GraphQL/Overfetch`, which nobody else
66
+ has.<!-- lib/rubocop/cop/graphql/ in the unpacked gem -->
67
+
68
+ **Where it gaps.** Custom scalars don't deserialize when the schema came from a
69
+ dump — the path its own README recommends — and the suggested workaround is
70
+ monkey-patching `GraphQL::Schema::BUILT_IN_TYPES`; the issue has been open since
71
+ February 2024.<!-- https://github.com/github-community-projects/graphql-client/issues/17 --> Network
72
+ errors are discarded: a 403 surfaces as `KeyError: key not found: "data"`.<!-- https://github.com/github-community-projects/graphql-client/issues/67 open since 2025-04-11 -->
73
+ Fragments enforce Relay-style data masking, so a field another fragment fetched
74
+ raises even though the value is right there in the response — and users file
75
+ issues asking for plain reuse.<!-- object_type.rb:161-168, 261-264; https://github.com/github-community-projects/graphql-client/issues/76 -->
76
+ Its CI matrix stops at Ruby 3.2 and Rails 7.1.<!-- https://github.com/github-community-projects/graphql-client/blob/master/.github/workflows/ci.yml -->
77
+ Nine issues and seven PRs are open, several waiting on a maintainer to approve a
78
+ CI run.<!-- gh api search/issues, repo:github-community-projects/graphql-client, 2026-09-12 -->
79
+
80
+ **Pick it over graph_weaver when** institutional safety outweighs static types,
81
+ or when you need data masking as a feature rather than a constraint.
82
+
83
+ ## graphlient
84
+
85
+ **Where it shines.** Quietly the healthiest Ruby client: 0.9.0 shipped
86
+ 2026-08-02, more recently than graphql-client itself, adding DSL fragments,
87
+ directives and scalar registration.<!-- https://github.com/ashkan18/graphlient/blob/master/CHANGELOG.md -->
88
+ It fixes the failure everyone hits with its substrate — it raises a real,
89
+ rescuable error hierarchy instead of handing you a half-populated response.<!-- lib/graphlient/errors/ --> Faraday
90
+ means your existing middleware just works.
91
+
92
+ **Where it gaps.** It is a wrapper, so it inherits graphql-client's result
93
+ model, its fragment isolation, and its untyped everything — its own README
94
+ offers `to_query_string` as "the escape hatch if you want to replace the
95
+ graphql-client dependency entirely".<!-- https://github.com/ashkan18/graphlient#readme, 0.9.0 -->
96
+ Read and write timeouts default to nil, i.e. none.<!-- README config table; lib/graphlient/adapters/http/adapter.rb:33-40 -->
97
+ No Rails integration, no field aliasing, and 18 open issues, the most-reacted
98
+ dating to 2017.<!-- https://github.com/ashkan18/graphlient/issues/10 ; gh api repos/ashkan18/graphlient -->
99
+
100
+ **Pick it over graph_weaver when** you want to call an API without thinking
101
+ about it. For that job graph_weaver is over-engineered, and this is the right
102
+ answer.
103
+
104
+ ## artemis
105
+
106
+ **Where it shines.** The best Rails story of the three: `rails g
107
+ artemis:install` writes the client, the config and the schema dump; `.graphql`
108
+ files map to methods by convention; `before_execute`/`after_execute` are real
109
+ hooks; and it has the only shipped test harness among the alternatives —
110
+ `stub_graphql(Artsy, :artist).to_return(:yayoi_kusama)` against YAML
111
+ fixtures.<!-- lib/artemis/test_helper.rb; https://github.com/yuki24/artemis#testing -->
112
+ It also batches, via `Client.multiplex`.<!-- lib/artemis/client.rb .multiplex/MultiplexQueue -->
113
+
114
+ **Where it gaps.** Fixtures are returned verbatim — nothing checks them against
115
+ the schema or the query's selection set, so a fixture can drift from reality and
116
+ stay green.<!-- lib/artemis/adapters/test_adapter.rb#execute -->
117
+ Its reloader and production preload are both gated on *not* using Zeitwerk,
118
+ which every Rails 7+ app does, so the README's preloading claim no longer
119
+ applies.<!-- lib/artemis/railtie.rb — graphql.client.set_reloader and graphql.client.preload both gated on not_on_zeitwerk -->
120
+ Last release August 2024; Rails 8 support exists only on `main`, and there have
121
+ been no commits since December 2025.<!-- gh api repos/yuki24/artemis/compare/v1.1.0...main ; last commit 8b3d76a 2025-12-04 -->
122
+
123
+ **Pick it over graph_weaver when** you want convention-over-configuration Rails
124
+ ergonomics and don't need types.
125
+
126
+ ## graphql-ruby alone
127
+
128
+ `graphql-ruby` is a **server** library and ships no HTTP client at all — its
129
+ only three runtime dependencies are `base64`, `fiber-storage` and `logger`, and
130
+ the only `Net::HTTP` call in the gem fetches a checksum for graphql-pro.<!-- gem spec graphql-2.6.10.gem dependencies; lib/graphql/rake_task/validate.rb:38-44 -->
131
+ The thing called "client" in its docs is
132
+ [JavaScript](https://graphql-ruby.org/javascript_client/overview).
133
+
134
+ What it does give a client author is the substrate everyone here builds on:
135
+ [`GraphQL.parse`](https://graphql-ruby.org/api-doc/2.6.10/GraphQL.html),
136
+ [`GraphQL::Schema.from_definition`](https://graphql-ruby.org/schema/sdl.html)
137
+ and `from_introspection`, and `GraphQL::StaticValidation::Validator` for
138
+ checking a document against a schema.<!-- lib/graphql.rb:49; lib/graphql/schema.rb:105,115; lib/graphql/static_validation/validator.rb:11-18 -->
139
+ GraphWeaver uses exactly these — it is a code generator on top of graphql-ruby,
140
+ not a reimplementation of it.
141
+
142
+ **Subscriptions are server-side only** across the whole ecosystem: graphql-ruby
143
+ delivers them over ActionCable and every documented consumer is
144
+ JavaScript,<!-- https://graphql-ruby.org/subscriptions/action_cable_implementation — "See client usage for: Apollo Client, Relay Modern, GraphiQL" -->
145
+ and no Ruby gem consumes GraphQL subscriptions over websockets.<!-- rubygems search graphql+websocket, graphql-ws, subscriptions-transport-ws all return 0 results, 2026-09-12 -->
146
+ Nobody in Ruby has this, GraphWeaver included.
147
+
148
+ ## Hand-rolled HTTP — the real incumbent
149
+
150
+ Most Ruby code talking to a GraphQL API isn't using a client library. It POSTs a
151
+ string and reads a hash, and that includes the vendors' own SDKs.
152
+
153
+ Shopify is the sharpest example. `shopify_api` v10 **removed** graphql-client,
154
+ saying so in its breaking-changes doc — "There is no need to dump the schema to
155
+ a local JSON file before using it anymore" — and the migration example replaces
156
+ `result.data.shop.name` with `response.body["data"]["shop"]["name"]`.<!-- https://github.com/Shopify/shopify-api-ruby/blob/main/BREAKING_CHANGES_FOR_V10.md:3,115,140-159 -->
157
+ The current gemspec declares `httparty`, `oj` and `sorbet-runtime` and no
158
+ `graphql` at all.<!-- https://github.com/Shopify/shopify-api-ruby/blob/main/shopify_api.gemspec:35-46 -->
159
+ The gem is `# typed: strict` throughout — and the GraphQL payload is typed
160
+ `T.any(T::Hash[String, T.untyped], String, OpenStruct)`.<!-- https://github.com/Shopify/shopify-api-ruby/blob/main/lib/shopify_api/clients/http_response.rb#L15-L16 -->
161
+ A Sorbet shop, shipping a Sorbet-typed SDK, with untyped GraphQL. Braintree does
162
+ the same thing without the Sorbet.<!-- https://github.com/braintree/braintree_ruby/blob/master/lib/braintree/graphql_client.rb#L13-L26 -->
163
+
164
+ **Where it shines.** Zero dependencies, zero build step, nothing to learn, and
165
+ it never gets in your way. For three queries against a stable API this is
166
+ genuinely the correct engineering call.
167
+
168
+ **Where it gaps.** Nothing validates the query, nothing knows the schema
169
+ changed, every response is `T.untyped`, and the error handling is a 40-line
170
+ layer you write once per project. The cost is invisible until the schema moves.
171
+
172
+ **Pick it over graph_weaver when** you have a handful of queries and no
173
+ appetite for a build step.
174
+
175
+ ## Where graph_weaver loses
176
+
177
+ Said plainly.
178
+
179
+ - **Sorbet is a hard dependency.** `sorbet-runtime` is a runtime
180
+ dependency,<!-- graph_weaver.gemspec: s.add_dependency "sorbet-runtime" --> and
181
+ the payoff — a typo caught before you run — needs `srb tc` in your build. No
182
+ RBS output. If Sorbet isn't in your stack, you're carrying the cost of
183
+ generated code for a fraction of the benefit.
184
+ - **Codegen is a build step.** You check generated Ruby in, and regenerate on
185
+ every gem upgrade, because any release can change what codegen emits. `rake
186
+ graph_weaver:verify` exists precisely because that step is easy to skip. The
187
+ alternatives have no build step at all.
188
+ - **No subscriptions.** A subscription document is refused outright:
189
+ `GraphWeaver::Error: unsupported operation: subscription`.<!-- lib/graph_weaver/internal/selection.rb:48 --> Nobody
190
+ in Ruby has this, but that doesn't make it present here.
191
+ - **No `@defer`, no file uploads, no persisted queries, no batching.** artemis
192
+ has multiplex batching;<!-- lib/artemis/client.rb .multiplex --> this gem has
193
+ none of the four.
194
+ - **One author, and it's new.** First commit July 2026, 4 stars, pre-1.0, and
195
+ about 30 breaking-change notes in the changelog so far.<!-- gh api repos/dpep/graph_weaver created_at 2026-07-07, stargazers_count 4; grep -c -i breaking CHANGELOG.md -->
196
+ Codegen is unforgiving and there is a lot of surface to get wrong.
197
+ `verify` mitigates drift; nothing mitigates the maintainer. "No static types"
198
+ is a cost many teams will rationally accept over that.
199
+
200
+ If you read that list and none of it stops you, the thing you get in exchange is
201
+ in [getting started](getting_started.md).
data/docs/cassettes.md CHANGED
@@ -31,6 +31,17 @@ to refresh. A *request* with no recording raises
31
31
  `GraphWeaver::Testing::MissingRecording`, naming the variables it was called
32
32
  with and the ones recorded for that same query — what usually differs.
33
33
 
34
+ **One entry per request.** The request key is the query, its variables and the
35
+ operation name together, and a cassette holds one entry per key: re-recording a
36
+ request *replaces* its entry, and a request the file hasn't seen appends one. So
37
+ however often `GRAPHWEAVER_RECORD=1` runs, no cassette ends up with two entries
38
+ for the same request.
39
+
40
+ Editing a query changes the key, so the re-record writes a new entry and the old
41
+ one stays behind — a recording of a request nothing sends any more.
42
+ `cassettes:check` counts those ("1 not sent by any query module", below); the
43
+ way to clear them is to delete the cassette and record it afresh.
44
+
34
45
  ## Has a recording gone stale?
35
46
 
36
47
  A cassette is the one artifact here recorded from *someone else's* server, and
@@ -50,7 +61,7 @@ belongs in the normal PR run beside `verify`, and exits non-zero on drift:
50
61
 
51
62
  ```
52
63
  spec/cassettes/dashboard.yml: 1 stale (3 checked, 1 not sent by any query module)
53
- DashboardQuery {"id" => "b1"}
64
+ DashboardQuery {"id":"b1"}
54
65
  failed to cast response into DashboardQuery::Result::Me::Reviews::Book: Parameter 'price_cents': Can't set …price_cents to 4200 (instance of Integer) - need a String
55
66
  ```
56
67
 
@@ -82,6 +93,11 @@ preserving everything that makes the recording faithful:
82
93
  | enums, booleans, `__typename` | numbers, dates |
83
94
  | id *relationships* (same original id → same fake id) | the id values themselves |
84
95
 
96
+ **Every plain string goes**, not the PII-shaped ones — nothing here can tell a
97
+ user's name from a product's, so a recorded `"pikachu"` replays as `"name-1"`
98
+ and an assertion pinned to it fails. Leave it off for a public, non-sensitive
99
+ API, where the real values *are* the point of the cassette.
100
+
85
101
  `data` is walked against the schema — which is why it needs one, to know which
86
102
  values are enums, dates, ids. `errors` and `extensions` have none behind them,
87
103
  so they're walked by shape instead: keys, nesting and structure survive, every