graph_weaver 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +72 -72
- data/docs/generated_modules.md +56 -10
- data/docs/getting_started.md +14 -2
- data/docs/upgrading.md +39 -1
- data/examples/github/generated/star_mutation.rb +1 -1
- data/examples/github/generated/stargazers_query.rb +1 -1
- data/examples/github/generated/starred_query.rb +1 -1
- data/lib/graph_weaver/client.rb +16 -4
- data/lib/graph_weaver/codegen.rb +42 -10
- data/lib/graph_weaver/graph.rb +12 -1
- data/lib/graph_weaver/internal.rb +33 -2
- data/lib/graph_weaver/railtie.rb +250 -11
- data/lib/graph_weaver/schema_loader.rb +58 -9
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +17 -8
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd9b2703206fc3145399e11794cd488a5e3c3734584ca7a56fa0e4d2afed9a77
|
|
4
|
+
data.tar.gz: 0e247a7b925ff0a89cb5952ac962b31545af417f865342a22b8019dcbcdbd135
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7310d664c88a34de5fbdf9768e6a697ca6565e5ccd4c407cfea31c772f0f44001ceb1145a7e1837d1a46bcc74c16902c55c88d2be014df9c423bb2355dc882a0
|
|
7
|
+
data.tar.gz: abc81fbb21a1f3f26d8a7c93f1f660347e9f68088a833b37b6a66dcf05f17e6bb0bec95fc9432c6e99192ac3682e7961936d9e08b3f518b4af9e013623ef3f6c
|
data/Gemfile.lock
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
graph_weaver (0.7.
|
|
4
|
+
graph_weaver (0.7.8)
|
|
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.
|
|
12
|
-
actionview (= 8.1.
|
|
13
|
-
activesupport (= 8.1.
|
|
11
|
+
actionpack (8.1.4)
|
|
12
|
+
actionview (= 8.1.4)
|
|
13
|
+
activesupport (= 8.1.4)
|
|
14
14
|
nokogiri (>= 1.8.5)
|
|
15
15
|
rack (>= 2.2.4)
|
|
16
16
|
rack-session (>= 1.0.1)
|
|
@@ -18,13 +18,13 @@ GEM
|
|
|
18
18
|
rails-dom-testing (~> 2.2)
|
|
19
19
|
rails-html-sanitizer (~> 1.6)
|
|
20
20
|
useragent (~> 0.16)
|
|
21
|
-
actionview (8.1.
|
|
22
|
-
activesupport (= 8.1.
|
|
21
|
+
actionview (8.1.4)
|
|
22
|
+
activesupport (= 8.1.4)
|
|
23
23
|
builder (~> 3.1)
|
|
24
24
|
erubi (~> 1.11)
|
|
25
25
|
rails-dom-testing (~> 2.2)
|
|
26
26
|
rails-html-sanitizer (~> 1.6)
|
|
27
|
-
activesupport (8.1.
|
|
27
|
+
activesupport (8.1.4)
|
|
28
28
|
base64
|
|
29
29
|
bigdecimal
|
|
30
30
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
@@ -45,9 +45,9 @@ GEM
|
|
|
45
45
|
ast (2.4.3)
|
|
46
46
|
base64 (0.3.0)
|
|
47
47
|
benchmark (0.5.0)
|
|
48
|
-
bigdecimal (4.1.
|
|
48
|
+
bigdecimal (4.1.3)
|
|
49
49
|
builder (3.3.0)
|
|
50
|
-
concurrent-ruby (1.3.
|
|
50
|
+
concurrent-ruby (1.3.8)
|
|
51
51
|
connection_pool (3.0.2)
|
|
52
52
|
crack (1.0.1)
|
|
53
53
|
bigdecimal
|
|
@@ -58,11 +58,11 @@ GEM
|
|
|
58
58
|
reline (>= 0.3.8)
|
|
59
59
|
diff-lcs (1.6.2)
|
|
60
60
|
drb (2.2.3)
|
|
61
|
-
erb (6.0.
|
|
61
|
+
erb (6.0.7)
|
|
62
62
|
erubi (1.13.1)
|
|
63
63
|
faker (3.8.0)
|
|
64
64
|
i18n (>= 1.8.11, < 2)
|
|
65
|
-
faraday (2.14.
|
|
65
|
+
faraday (2.14.4)
|
|
66
66
|
faraday-net_http (>= 2.0, < 3.5)
|
|
67
67
|
json
|
|
68
68
|
logger
|
|
@@ -81,20 +81,20 @@ GEM
|
|
|
81
81
|
google-protobuf (4.35.1-x86_64-linux-gnu)
|
|
82
82
|
bigdecimal
|
|
83
83
|
rake (~> 13.3)
|
|
84
|
-
graphql (2.6.
|
|
84
|
+
graphql (2.6.11)
|
|
85
85
|
base64
|
|
86
86
|
fiber-storage
|
|
87
87
|
logger
|
|
88
88
|
hashdiff (1.2.1)
|
|
89
89
|
i18n (1.15.2)
|
|
90
90
|
concurrent-ruby (~> 1.0)
|
|
91
|
-
io-console (0.
|
|
91
|
+
io-console (0.9.4)
|
|
92
92
|
irb (1.18.0)
|
|
93
93
|
pp (>= 0.6.0)
|
|
94
94
|
prism (>= 1.3.0)
|
|
95
95
|
rdoc (>= 4.0.0)
|
|
96
96
|
reline (>= 0.4.2)
|
|
97
|
-
json (
|
|
97
|
+
json (3.0.2)
|
|
98
98
|
logger (1.7.0)
|
|
99
99
|
loofah (2.25.2)
|
|
100
100
|
crass (~> 1.0.2)
|
|
@@ -124,7 +124,7 @@ GEM
|
|
|
124
124
|
racc (~> 1.4)
|
|
125
125
|
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
126
126
|
racc (~> 1.4)
|
|
127
|
-
parallel (2.
|
|
127
|
+
parallel (2.2.0)
|
|
128
128
|
parser (3.3.12.0)
|
|
129
129
|
ast (~> 2.4.1)
|
|
130
130
|
racc
|
|
@@ -149,9 +149,9 @@ GEM
|
|
|
149
149
|
rails-html-sanitizer (1.7.1)
|
|
150
150
|
loofah (~> 2.25, >= 2.25.2)
|
|
151
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.
|
|
153
|
-
actionpack (= 8.1.
|
|
154
|
-
activesupport (= 8.1.
|
|
152
|
+
railties (8.1.4)
|
|
153
|
+
actionpack (= 8.1.4)
|
|
154
|
+
activesupport (= 8.1.4)
|
|
155
155
|
irb (~> 1.13)
|
|
156
156
|
rackup (>= 1.0.0)
|
|
157
157
|
rake (>= 12.2)
|
|
@@ -159,23 +159,23 @@ GEM
|
|
|
159
159
|
tsort (>= 0.2)
|
|
160
160
|
zeitwerk (~> 2.6)
|
|
161
161
|
rake (13.4.2)
|
|
162
|
-
rbi (0.3
|
|
162
|
+
rbi (0.4.3)
|
|
163
163
|
prism (~> 1.0)
|
|
164
164
|
rbs (>= 4.0.1)
|
|
165
|
-
rbs (4.0
|
|
165
|
+
rbs (4.2.0)
|
|
166
166
|
logger
|
|
167
167
|
prism (>= 1.6.0)
|
|
168
168
|
tsort
|
|
169
|
-
rdoc (8.
|
|
169
|
+
rdoc (8.1.0)
|
|
170
170
|
erb
|
|
171
171
|
prism (>= 1.6.0)
|
|
172
172
|
rbs (>= 4.0.0)
|
|
173
173
|
tsort
|
|
174
174
|
redcarpet (3.6.1)
|
|
175
175
|
regexp_parser (2.12.0)
|
|
176
|
-
reline (0.
|
|
176
|
+
reline (0.7.0)
|
|
177
177
|
io-console (~> 0.5)
|
|
178
|
-
require-hooks (0.
|
|
178
|
+
require-hooks (0.5.1)
|
|
179
179
|
rexml (3.4.4)
|
|
180
180
|
rspec (3.13.2)
|
|
181
181
|
rspec-core (~> 3.13.0)
|
|
@@ -190,35 +190,35 @@ GEM
|
|
|
190
190
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
191
191
|
rspec-support (~> 3.13.0)
|
|
192
192
|
rspec-support (3.13.7)
|
|
193
|
-
rubydex (0.
|
|
194
|
-
rubydex (0.
|
|
195
|
-
rubydex (0.
|
|
196
|
-
rubydex (0.
|
|
193
|
+
rubydex (0.4.1-aarch64-linux)
|
|
194
|
+
rubydex (0.4.1-arm64-darwin)
|
|
195
|
+
rubydex (0.4.1-x86_64-darwin)
|
|
196
|
+
rubydex (0.4.1-x86_64-linux)
|
|
197
197
|
securerandom (0.4.1)
|
|
198
|
-
simplecov (1.3.
|
|
199
|
-
sorbet (0.6.
|
|
200
|
-
sorbet-static (= 0.6.
|
|
201
|
-
sorbet-runtime (0.6.
|
|
202
|
-
sorbet-static (0.6.
|
|
203
|
-
sorbet-static (0.6.
|
|
204
|
-
sorbet-static (0.6.
|
|
205
|
-
sorbet-static-and-runtime (0.6.
|
|
206
|
-
sorbet (= 0.6.
|
|
207
|
-
sorbet-runtime (= 0.6.
|
|
208
|
-
spoom (1.8.
|
|
198
|
+
simplecov (1.3.1)
|
|
199
|
+
sorbet (0.6.13508)
|
|
200
|
+
sorbet-static (= 0.6.13508)
|
|
201
|
+
sorbet-runtime (0.6.13508)
|
|
202
|
+
sorbet-static (0.6.13508-aarch64-linux)
|
|
203
|
+
sorbet-static (0.6.13508-universal-darwin)
|
|
204
|
+
sorbet-static (0.6.13508-x86_64-linux)
|
|
205
|
+
sorbet-static-and-runtime (0.6.13508)
|
|
206
|
+
sorbet (= 0.6.13508)
|
|
207
|
+
sorbet-runtime (= 0.6.13508)
|
|
208
|
+
spoom (1.8.7)
|
|
209
209
|
erubi (>= 1.10.0)
|
|
210
210
|
prism (>= 0.28.0)
|
|
211
|
-
rbi (>= 0.
|
|
211
|
+
rbi (>= 0.4.2)
|
|
212
212
|
rbs (>= 4.0.0.dev.5)
|
|
213
213
|
rexml (>= 3.2.6)
|
|
214
214
|
sorbet-static-and-runtime (>= 0.5.10187)
|
|
215
215
|
thor (>= 0.19.2)
|
|
216
|
-
tapioca (0.
|
|
216
|
+
tapioca (0.20.0)
|
|
217
217
|
benchmark
|
|
218
218
|
bundler (>= 2.2.25)
|
|
219
219
|
netrc (>= 0.11.0)
|
|
220
220
|
parallel (>= 1.21.0)
|
|
221
|
-
rbi (>= 0.
|
|
221
|
+
rbi (>= 0.4.1)
|
|
222
222
|
require-hooks (>= 0.2.2)
|
|
223
223
|
rubydex (>= 0.1.0.beta10)
|
|
224
224
|
sorbet-static-and-runtime (>= 0.6.12698)
|
|
@@ -272,40 +272,40 @@ DEPENDENCIES
|
|
|
272
272
|
yard
|
|
273
273
|
|
|
274
274
|
CHECKSUMS
|
|
275
|
-
actionpack (8.1.
|
|
276
|
-
actionview (8.1.
|
|
277
|
-
activesupport (8.1.
|
|
275
|
+
actionpack (8.1.4) sha256=245493cc51be195a7645e6fc184880fa7e5b32ff1a08e053ee31f58c76e30ea4
|
|
276
|
+
actionview (8.1.4) sha256=eff1efd07c4d969abad288322b423bcb67fdbab5e324c91ca37352b80f226076
|
|
277
|
+
activesupport (8.1.4) sha256=891dd3fe43c4fc8391934b237b1189bb1f661c932a84a840043db9d46a4e80df
|
|
278
278
|
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
|
279
279
|
apollo-federation (3.10.3) sha256=61e6d8e1fd695ee45bab5d84f86f586d99c296ad77a86c6f1e7b964ac17be3d7
|
|
280
280
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
281
281
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
282
282
|
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
|
|
283
|
-
bigdecimal (4.1.
|
|
283
|
+
bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66
|
|
284
284
|
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
|
285
|
-
concurrent-ruby (1.3.
|
|
285
|
+
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
|
|
286
286
|
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
287
287
|
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
|
|
288
288
|
crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
|
|
289
289
|
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
|
|
290
290
|
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
291
291
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
292
|
-
erb (6.0.
|
|
292
|
+
erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92
|
|
293
293
|
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
|
294
294
|
faker (3.8.0) sha256=c147b308df73a90f27a4fc84f18d4c22ef0ad9c2a64b2b61c86fd0ca71753efc
|
|
295
|
-
faraday (2.14.
|
|
295
|
+
faraday (2.14.4) sha256=9bb4408c44621b0dbaaaa39e5dbfc4dbe58cc6751b6eb94d16ae9e1f2f2fab6e
|
|
296
296
|
faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
|
|
297
297
|
fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1
|
|
298
298
|
google-protobuf (4.35.1-aarch64-linux-gnu) sha256=50ca44d0eeff3f8475e630a1accdd974256f3510694d574e2c9d6119ea8bc9e1
|
|
299
299
|
google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b
|
|
300
300
|
google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee
|
|
301
301
|
google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd
|
|
302
|
-
graph_weaver (0.7.
|
|
303
|
-
graphql (2.6.
|
|
302
|
+
graph_weaver (0.7.8)
|
|
303
|
+
graphql (2.6.11) sha256=9a9cde910e805c4c08fdf8d22bf129e935affc9ca8b4f9b2ec26b4b63666b15d
|
|
304
304
|
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
|
|
305
305
|
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
|
|
306
|
-
io-console (0.
|
|
306
|
+
io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1
|
|
307
307
|
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
|
|
308
|
-
json (
|
|
308
|
+
json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df
|
|
309
309
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
310
310
|
loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918
|
|
311
311
|
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
|
@@ -317,7 +317,7 @@ CHECKSUMS
|
|
|
317
317
|
nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527
|
|
318
318
|
nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551
|
|
319
319
|
nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
|
|
320
|
-
parallel (2.
|
|
320
|
+
parallel (2.2.0) sha256=e1059c5fd7b649558a0aec38a769f06a42942bdb40503d005a59c352fe011cd8
|
|
321
321
|
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
|
|
322
322
|
pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570
|
|
323
323
|
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
|
@@ -330,35 +330,35 @@ CHECKSUMS
|
|
|
330
330
|
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
331
331
|
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
|
|
332
332
|
rails-html-sanitizer (1.7.1) sha256=e797a7c9b01e567307e317c576b49ab4168017e63eea4dba9ce3cb587e2f22c2
|
|
333
|
-
railties (8.1.
|
|
333
|
+
railties (8.1.4) sha256=b5e7a8e8984e33114d68af62227971960aae1e6c02bd7c9d826ffad97fff583c
|
|
334
334
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
335
|
-
rbi (0.3
|
|
336
|
-
rbs (4.0
|
|
337
|
-
rdoc (8.
|
|
335
|
+
rbi (0.4.3) sha256=cc090ae62c3246412b992954f54077d6046c9b04f1537a4d24bfae8714503eac
|
|
336
|
+
rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674
|
|
337
|
+
rdoc (8.1.0) sha256=befc37880a5cfd36dcb176c748b5fd5ee39dd3536a75e81824e4972ce230f60a
|
|
338
338
|
redcarpet (3.6.1) sha256=d444910e6aa55480c6bcdc0cdb057626e8a32c054c29e793fa642ba2f155f445
|
|
339
339
|
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
|
340
|
-
reline (0.
|
|
341
|
-
require-hooks (0.
|
|
340
|
+
reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
|
|
341
|
+
require-hooks (0.5.1) sha256=49a6b14518b91c5b8006d0644ef257af0ca033df23625bd4e314a8c5282191d9
|
|
342
342
|
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
|
343
343
|
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
344
344
|
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
345
345
|
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
346
346
|
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
347
347
|
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
348
|
-
rubydex (0.
|
|
349
|
-
rubydex (0.
|
|
350
|
-
rubydex (0.
|
|
351
|
-
rubydex (0.
|
|
348
|
+
rubydex (0.4.1-aarch64-linux) sha256=abc1c9eb22479f60dd015fa81373163c5a71f2700b76bced1282631686bd6271
|
|
349
|
+
rubydex (0.4.1-arm64-darwin) sha256=d23720253876d88fb98954e5d3a1ef9696a269697acf50e3ab53cdfbb2b66b96
|
|
350
|
+
rubydex (0.4.1-x86_64-darwin) sha256=a9681db5bc9be6a62f226d96d6889b6b600d0b09e7576391702aafd29912145b
|
|
351
|
+
rubydex (0.4.1-x86_64-linux) sha256=4e2c8398c9fc585485b54edd28f5ecf76b5a02255e177b6f8402e9d2afffd51b
|
|
352
352
|
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
|
353
|
-
simplecov (1.3.
|
|
354
|
-
sorbet (0.6.
|
|
355
|
-
sorbet-runtime (0.6.
|
|
356
|
-
sorbet-static (0.6.
|
|
357
|
-
sorbet-static (0.6.
|
|
358
|
-
sorbet-static (0.6.
|
|
359
|
-
sorbet-static-and-runtime (0.6.
|
|
360
|
-
spoom (1.8.
|
|
361
|
-
tapioca (0.
|
|
353
|
+
simplecov (1.3.1) sha256=b326d38e819053499aa2ebe35d544d8bd780eba143a50f48a624b450ad87feec
|
|
354
|
+
sorbet (0.6.13508) sha256=de67c75424731d39ac529639ff6849c3fe33d91c94608765d912d35efa5941a2
|
|
355
|
+
sorbet-runtime (0.6.13508) sha256=011ee266adead459077ebc0058d36aeb75d6c90968db8524916050ce3c62e526
|
|
356
|
+
sorbet-static (0.6.13508-aarch64-linux) sha256=1c54603399d40d78d6e73c853c5e0b6dcfc34105a92f6630f4a5329d1e6ed3c0
|
|
357
|
+
sorbet-static (0.6.13508-universal-darwin) sha256=65280392d193ca85828b13d07bb8eb90f41ad57f8d9bc79c5edcd078aa7dc308
|
|
358
|
+
sorbet-static (0.6.13508-x86_64-linux) sha256=57fd620b1a747baad432ef5c2bc2cd6218664e00626fc4e7fb5ed5f633e3a286
|
|
359
|
+
sorbet-static-and-runtime (0.6.13508) sha256=874d9fe5d98eebddd322629ee3d27021a6da32594edfa170295c0b4bcd285d66
|
|
360
|
+
spoom (1.8.7) sha256=81960502cdf23aa991f015988aa5dd43325e23ae4318b8e00d541299cbdb70f6
|
|
361
|
+
tapioca (0.20.0) sha256=b375ed7bf51d2c86c09543eafedd0de85513634187c0f7a062615af8bb9347a0
|
|
362
362
|
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|
|
363
363
|
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
|
364
364
|
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
|
data/docs/generated_modules.md
CHANGED
|
@@ -142,7 +142,9 @@ file, and `verify` fails until you regenerate.
|
|
|
142
142
|
**A graph's `namespace:` nests what it generates**, and is the answer when two
|
|
143
143
|
schemas in one app each have a `person.graphql`: `namespace: "Billing"` makes
|
|
144
144
|
that one `Billing::PersonQuery` in the same `person_query.rb`, and its shared
|
|
145
|
-
types module `Billing::
|
|
145
|
+
types module `Billing::Types` (plain `Types` inside a namespace, where nothing
|
|
146
|
+
collides with graphql-ruby's own `Types::`; `GraphQLTypes` without one). Nothing
|
|
147
|
+
else about the rule changes. See
|
|
146
148
|
[getting started](getting_started.md#more-than-one-schema).
|
|
147
149
|
|
|
148
150
|
Parsing a raw query *string* has no file to name it after, so it uses the
|
|
@@ -833,10 +835,8 @@ has the other attributes it reads.
|
|
|
833
835
|
|
|
834
836
|
### Loading what it wrote
|
|
835
837
|
|
|
836
|
-
In Rails, loading is automatic — the
|
|
837
|
-
|
|
838
|
-
in a `to_prepare` block, and again after each development reload. Elsewhere it's
|
|
839
|
-
explicit, factory_bot-style:
|
|
838
|
+
In Rails, loading is automatic — [one rule](#the-rule-in-rails) decides whether
|
|
839
|
+
Zeitwerk does it or the Railtie does. Elsewhere it's explicit, factory_bot-style:
|
|
840
840
|
`GraphWeaver.load_generated!` requires every file under `generated_paths`.
|
|
841
841
|
|
|
842
842
|
**Outside Rails, four things have to agree**, and nothing wires them together for
|
|
@@ -865,11 +865,57 @@ list would name a dump nothing ever opens. A relative path resolves against
|
|
|
865
865
|
`GraphWeaver.root` — `Rails.root` in a Rails app, the working directory otherwise
|
|
866
866
|
— so where you started the process doesn't change which files it reads.
|
|
867
867
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
868
|
+
#### The rule in Rails
|
|
869
|
+
|
|
870
|
+
**A generated tree Zeitwerk would name correctly is left to Zeitwerk; any other
|
|
871
|
+
is hidden from it and required at boot.**
|
|
872
|
+
|
|
873
|
+
The conventional layout is the second one: `generated/person_query.rb` defines
|
|
874
|
+
`::PersonQuery` where Zeitwerk reads `Generated::PersonQuery` off the path. So
|
|
875
|
+
the Railtie hides that directory from the autoloader and requires every file at
|
|
876
|
+
the end of boot — after your initializers and after any `to_prepare`
|
|
877
|
+
registrations of your own, and again after each development reload.
|
|
878
|
+
|
|
879
|
+
A tree is named correctly when the graph's [`namespace`](getting_started.md#more-than-one-schema)
|
|
880
|
+
is the constant its output path spells:
|
|
881
|
+
|
|
882
|
+
```ruby
|
|
883
|
+
GraphWeaver.graph :reports do
|
|
884
|
+
output "app/graphql/queries/reports/generated"
|
|
885
|
+
namespace "Queries::Reports::Generated"
|
|
886
|
+
end
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
`report_query.rb` then defines `Queries::Reports::Generated::ReportQuery`, which
|
|
890
|
+
is exactly what Zeitwerk expects there — your own `inflect`/`acronym` rules
|
|
891
|
+
included, since graph_weaver asks the loader rather than guessing. Nothing is
|
|
892
|
+
hidden and nothing is required: the modules autoload on first reference and
|
|
893
|
+
reload with the rest of the app, and **no part of the parent namespace chain is
|
|
894
|
+
defined until a query is used**. Leave `types_module` unset for it — the shared
|
|
895
|
+
manifest is `types.rb`, which Zeitwerk reads as `<Namespace>::Types`, and `Types`
|
|
896
|
+
is what a namespaced graph already calls it.
|
|
897
|
+
|
|
898
|
+
The Railtie says which side each graph fell on, one line apiece at debug, naming
|
|
899
|
+
the constant that disagreed:
|
|
900
|
+
|
|
901
|
+
```
|
|
902
|
+
graph :reports's output app/graphql/queries/reports/generated is hidden from
|
|
903
|
+
Zeitwerk and required at boot — types.rb defines …::GraphQLTypes, Zeitwerk
|
|
904
|
+
expects …::Types
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**The settings have two lifetimes.** `schema`, `queries`, `output` and
|
|
908
|
+
`namespace` matter only when `generate`/`verify` runs. `extend_type`,
|
|
909
|
+
`register_scalar` and `register_enum` are needed wherever generated code *loads*
|
|
910
|
+
— which under autoloading is the first request that uses a query, long after
|
|
911
|
+
boot. So generation settings can live wherever generation happens, and
|
|
912
|
+
registrations belong in `config/initializers` or a `to_prepare` block, where
|
|
913
|
+
every boot runs them.
|
|
914
|
+
|
|
915
|
+
In development a query edit regenerates and reloads before the next request;
|
|
916
|
+
everywhere else generated code changes only on regeneration — restart, like a
|
|
917
|
+
schema migration, or call `GraphWeaver.reload_generated!` after regenerating in
|
|
918
|
+
another terminal.
|
|
873
919
|
|
|
874
920
|
## Dynamic mode
|
|
875
921
|
|
data/docs/getting_started.md
CHANGED
|
@@ -466,6 +466,15 @@ db/schema.graphql records no source url and the graph names no server behind it
|
|
|
466
466
|
still exits 1, because a gate that passes on having compared nothing is worse
|
|
467
467
|
than one that says so.
|
|
468
468
|
|
|
469
|
+
**A large dump costs nothing until something asks for it.** A client built from
|
|
470
|
+
a file stats the path at construction — a typo fails at boot, naming it — and
|
|
471
|
+
parses the file the first time anything wants the schema: codegen, a test mode,
|
|
472
|
+
`check_query`. Executing a generated module never does, so a production boot
|
|
473
|
+
never parses.
|
|
474
|
+
|
|
475
|
+
Keep one client per server rather than building one per call — a client parses
|
|
476
|
+
its dump once and keeps the schema.
|
|
477
|
+
|
|
469
478
|
## More than one schema
|
|
470
479
|
|
|
471
480
|
The five steps above describe one graph — a schema, its queries, its output. An
|
|
@@ -496,7 +505,7 @@ plus `register_scalar`, `register_enum` and `extend_type`).
|
|
|
496
505
|
| `output` | one directory — where this graph's generated Ruby is written |
|
|
497
506
|
| `client` | what this graph's modules execute against — a client, or the name of the constant holding one |
|
|
498
507
|
| `namespace` | a constant, or its name — what every constant this graph generates nests under |
|
|
499
|
-
| `types_module` | a constant name for the shared types module (default: `
|
|
508
|
+
| `types_module` | a constant name for the shared types module (default: `Types` under a `namespace`, `GraphQLTypes` without one) |
|
|
500
509
|
|
|
501
510
|
**Declaring graph two means declaring graph one.** A declared graph *replaces*
|
|
502
511
|
the implicit one the top-level settings describe, so the moment any graph is
|
|
@@ -527,7 +536,10 @@ a bare `schema` reads back; there is no `schema = "x"` form, since the block is
|
|
|
527
536
|
`Billing::PersonQuery` ([naming](generated_modules.md#naming)). Constants are
|
|
528
537
|
global, so two schemas that both have a `person.graphql` would otherwise fight
|
|
529
538
|
over one name; without a namespace the collision is refused at generation,
|
|
530
|
-
naming both files. **
|
|
539
|
+
naming both files. **A namespace that is the constant the output path spells is
|
|
540
|
+
also what makes the tree autoloadable** — graph_weaver then leaves it to Zeitwerk
|
|
541
|
+
instead of hiding it and requiring it at boot
|
|
542
|
+
([loading](generated_modules.md#the-rule-in-rails)). **The block's registrations reach that graph alone**, laid
|
|
531
543
|
over the top-level ones — so `Money` is checked against the schema it was
|
|
532
544
|
registered for, and against no other.
|
|
533
545
|
|
data/docs/upgrading.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Upgrading
|
|
2
2
|
|
|
3
3
|
[Regenerate](#regenerate-on-every-upgrade) whichever version you're on, then read
|
|
4
|
-
the one section that is yours: from [0.7.
|
|
4
|
+
the one section that is yours: from [0.7.7](#upgrading-from-077), from
|
|
5
|
+
[0.7.6](#upgrading-from-076), from
|
|
6
|
+
[0.7.5](#upgrading-from-075), from
|
|
5
7
|
[0.7.4](#upgrading-from-074), from [0.7.3](#upgrading-from-073), from
|
|
6
8
|
[0.7.1](#upgrading-from-071), from [0.7.0](#upgrading-from-070) or from
|
|
7
9
|
[0.6.1](#upgrading-from-061). Coming from 0.6.0 or older, the path is that
|
|
@@ -24,6 +26,42 @@ That's the reminder working, not a false alarm. Generation is deterministic, so
|
|
|
24
26
|
the diff is exactly what the new version emits differently and nothing else —
|
|
25
27
|
worth reading rather than rubber-stamping.
|
|
26
28
|
|
|
29
|
+
## Upgrading from 0.7.7
|
|
30
|
+
|
|
31
|
+
A patch release about load time. Nothing in it reaches an app that reads its
|
|
32
|
+
dump and generates; one edge moved, below. Read the left column and skip what
|
|
33
|
+
isn't yours; the [changelog](../CHANGELOG.md) says why each one moved.
|
|
34
|
+
|
|
35
|
+
| applies if you… | what changed |
|
|
36
|
+
|---|---|
|
|
37
|
+
| build a client from a schema dump — `GraphWeaver.new("schema.graphql")`, or a graph naming one | it is parsed on first use, not at construction, so a production boot never parses it. A missing or unreadable path still refuses at construction, naming it; a file that exists but isn't a schema refuses at the first `schema`, `parse` or `check_query`. `rake graph_weaver:verify` catches that one in CI |
|
|
38
|
+
| call `client.check_query` per query against a supergraph client | the routing table is built once per file version rather than per call — nothing to change, ~200 ms back per call on a 2000-type supergraph |
|
|
39
|
+
| generate anonymous operations whose comments carry non-ASCII text, or check out with CRLF | they generate under any parser graphql-ruby is configured with; nothing to change unless you had named an operation to dodge a "could not name the anonymous operation" refusal |
|
|
40
|
+
|
|
41
|
+
Then regenerate, and the gate:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
rake graph_weaver:generate
|
|
45
|
+
rake graph_weaver:verify
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Upgrading from 0.7.6
|
|
49
|
+
|
|
50
|
+
Read the left column and skip what isn't yours; the
|
|
51
|
+
[changelog](../CHANGELOG.md) says why each one moved.
|
|
52
|
+
|
|
53
|
+
| applies if you… | what changed |
|
|
54
|
+
|---|---|
|
|
55
|
+
| set `namespace:` on any graph — `rake graph_weaver:graphs` lists them | its shared types module is `<Namespace>::Types`, not `<Namespace>::GraphQLTypes`. **Regenerate**, then move app code naming `<Namespace>::GraphQLTypes::X` to `<Namespace>::Types::X` — `srb tc` finds every site. Keep the old name with `types_module "GraphQLTypes"` in that graph's block, at the cost of the autoloadable layout below |
|
|
56
|
+
| run Rails, and have a graph whose `namespace:` is the constant its output path spells | that tree is [left to Zeitwerk](generated_modules.md#the-rule-in-rails) — autoloaded on first reference, reloaded with the app, and no longer required at boot, so booting stops defining its parent namespace chain. Nothing to do; the Railtie says which side each graph fell on at debug. Every other tree is hidden and required exactly as before |
|
|
57
|
+
|
|
58
|
+
Then regenerate, and the gate:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
rake graph_weaver:generate
|
|
62
|
+
rake graph_weaver:verify
|
|
63
|
+
```
|
|
64
|
+
|
|
27
65
|
## Upgrading from 0.7.5
|
|
28
66
|
|
|
29
67
|
A patch release. The regenerate is what changes behaviour — every module gains
|
data/lib/graph_weaver/client.rb
CHANGED
|
@@ -75,8 +75,18 @@ class GraphWeaver::Client
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# a live schema class doubles as an in-process transport; a loaded
|
|
78
|
-
# dump has no resolvers, so it is type information only
|
|
79
|
-
|
|
78
|
+
# dump has no resolvers, so it is type information only — and it is
|
|
79
|
+
# read on first use, the rule a url client already follows. An app
|
|
80
|
+
# that builds one only to name its supergraph (what `graphql: :router`
|
|
81
|
+
# plans against) then never parses the file at all.
|
|
82
|
+
if source.is_a?(Module)
|
|
83
|
+
@schema = source
|
|
84
|
+
else
|
|
85
|
+
# a path is checked now — a stat, not a parse — so a wrong one
|
|
86
|
+
# refuses where it was written rather than at the first request
|
|
87
|
+
GraphWeaver::SchemaLoader.check_path!(source) unless GraphWeaver::SchemaLoader.content?(source)
|
|
88
|
+
@dump = source
|
|
89
|
+
end
|
|
80
90
|
# A supergraph's routing table lives in the file, not in the loaded
|
|
81
91
|
# schema, so the path is the only thing that can name one later. Told
|
|
82
92
|
# from SDL by its extension, as SchemaLoader tells it.
|
|
@@ -147,8 +157,9 @@ class GraphWeaver::Client
|
|
|
147
157
|
FAILURE_TTL = 1.0
|
|
148
158
|
private_constant :FAILURE_TTL
|
|
149
159
|
|
|
150
|
-
# The schema,
|
|
151
|
-
# per the client's cache:/ttl:)
|
|
160
|
+
# The schema, on first use: a dump is read and built, a url is
|
|
161
|
+
# introspected through the transport (cached per the client's cache:/ttl:).
|
|
162
|
+
# A schema class is the schema, and was there all along.
|
|
152
163
|
#
|
|
153
164
|
# Locked because a cold Puma process serves its first requests
|
|
154
165
|
# concurrently: a bare ||= there is one full introspection round trip per
|
|
@@ -156,6 +167,7 @@ class GraphWeaver::Client
|
|
|
156
167
|
def schema
|
|
157
168
|
@schema_lock.synchronize do
|
|
158
169
|
next @schema if @schema
|
|
170
|
+
next @schema = GraphWeaver::SchemaLoader.load(@dump) if @dump
|
|
159
171
|
raise @schema_error if @schema_error && Process.clock_gettime(Process::CLOCK_MONOTONIC) < @schema_error_until
|
|
160
172
|
|
|
161
173
|
begin
|
data/lib/graph_weaver/codegen.rb
CHANGED
|
@@ -458,19 +458,51 @@ class GraphWeaver::Codegen
|
|
|
458
458
|
declares!(named, name)
|
|
459
459
|
end
|
|
460
460
|
|
|
461
|
-
#
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
#
|
|
466
|
-
#
|
|
461
|
+
# What an operation definition opens with.
|
|
462
|
+
OPERATION_START = %i[QUERY MUTATION SUBSCRIPTION LCURLY].to_set.freeze
|
|
463
|
+
private_constant :OPERATION_START
|
|
464
|
+
|
|
465
|
+
# Where the operation starts in @query, in characters — lexed, not derived
|
|
466
|
+
# from a reported position, because no two parsers report the same one:
|
|
467
|
+
# graphql-ruby's own gives a BYTE position minus a CHARACTER line start
|
|
468
|
+
# (Language::Parser#column_at), its C parser gives a true column and counts
|
|
469
|
+
# \r\n as two lines. Language::Lexer is the same implementation whichever
|
|
470
|
+
# parser is default, and its #pos is an exact byte offset, so nothing here is
|
|
471
|
+
# left to interpret.
|
|
472
|
+
#
|
|
473
|
+
# The operation is the first one of those tokens outside every fragment
|
|
474
|
+
# definition, selection set and argument list — an argument's braces are
|
|
475
|
+
# not a body, and a fragment's body is not the operation's.
|
|
467
476
|
def operation_offset
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
477
|
+
lexer = GraphQL::Language::Lexer.new(@query)
|
|
478
|
+
depth = 0
|
|
479
|
+
parens = 0
|
|
480
|
+
fragment = T.let(false, T::Boolean)
|
|
481
|
+
|
|
482
|
+
while (kind = lexer.advance)
|
|
483
|
+
parens += 1 if kind == :LPAREN
|
|
484
|
+
parens -= 1 if kind == :RPAREN
|
|
485
|
+
next unless parens.zero?
|
|
486
|
+
|
|
487
|
+
if depth.zero? && !fragment && OPERATION_START.include?(kind)
|
|
488
|
+
return @query.byteslice(0, lexer.pos).length
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
case kind
|
|
492
|
+
when :LCURLY
|
|
493
|
+
depth += 1
|
|
494
|
+
fragment = false
|
|
495
|
+
when :RCURLY then depth -= 1
|
|
496
|
+
when :FRAGMENT then fragment = true if depth.zero?
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# The lexer and the parser disagree about what this document holds. Splice
|
|
501
|
+
# past the end, which declares! can only refuse.
|
|
502
|
+
@query.length
|
|
471
503
|
end
|
|
472
504
|
|
|
473
|
-
# The splice above
|
|
505
|
+
# The splice above lands on an offset this file worked out for itself, so it
|
|
474
506
|
# can land wrong and still emit a module that looks fine — and OPERATION_NAME
|
|
475
507
|
# would then be an operationName the document doesn't declare, which every
|
|
476
508
|
# server rejects. Nothing else re-reads the query, so this is the only place
|
data/lib/graph_weaver/graph.rb
CHANGED
|
@@ -93,8 +93,19 @@ module GraphWeaver
|
|
|
93
93
|
# constant; this is the one knob that settles both.
|
|
94
94
|
def namespace = @namespace
|
|
95
95
|
|
|
96
|
+
# The shared types module. Inside a namespace the default is plain `Types`,
|
|
97
|
+
# so `namespace:` alone leaves a tree Zeitwerk names correctly — types.rb
|
|
98
|
+
# holds it, and Zeitwerk reads that file as `<Namespace>::Types`. At the top
|
|
99
|
+
# level it stays GraphQLTypes, where a bare ::Types would collide with
|
|
100
|
+
# graphql-ruby's own Types:: convention.
|
|
101
|
+
NESTED_TYPES_MODULE = "Types"
|
|
102
|
+
private_constant :NESTED_TYPES_MODULE
|
|
103
|
+
|
|
96
104
|
def types_module
|
|
97
|
-
@types_module
|
|
105
|
+
return @types_module if @types_module
|
|
106
|
+
return GraphWeaver.types_module unless namespace
|
|
107
|
+
|
|
108
|
+
"#{namespace}::#{GraphWeaver.types_module(NESTED_TYPES_MODULE)}"
|
|
98
109
|
end
|
|
99
110
|
|
|
100
111
|
# The graphql-ruby schema, however it was named: a class, a Client, a path
|
|
@@ -192,6 +192,23 @@ module GraphWeaver
|
|
|
192
192
|
GraphWeaver.generated_paths | extra
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
+
# The generated directories this process has to require ITSELF:
|
|
196
|
+
# generated_dirs, less any tree the Railtie left to Zeitwerk. Those
|
|
197
|
+
# load on first reference and reload with the app, and requiring them
|
|
198
|
+
# is the one thing an autoloadable layout exists to avoid.
|
|
199
|
+
def required_dirs
|
|
200
|
+
generated_dirs.reject { |dir| left_to_zeitwerk?(dir) }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# by name, not spelled: the railtie is Rails-only and `typed: ignore`,
|
|
204
|
+
# so sorbet has no constant to resolve. Outside Rails there is no
|
|
205
|
+
# autoloader and the answer is always no.
|
|
206
|
+
def left_to_zeitwerk?(path)
|
|
207
|
+
return false unless GraphWeaver.const_defined?(:Railtie, false)
|
|
208
|
+
|
|
209
|
+
GraphWeaver.const_get(:Railtie, false).left_to_zeitwerk?(path)
|
|
210
|
+
end
|
|
211
|
+
|
|
195
212
|
# Every query document under these directories, sorted — the files
|
|
196
213
|
# generate!, verify_generated!, check_queries and load_queries! read.
|
|
197
214
|
def query_files(paths = GraphWeaver.queries_paths)
|
|
@@ -358,10 +375,24 @@ module GraphWeaver
|
|
|
358
375
|
path = path.to_path if path.respond_to?(:to_path)
|
|
359
376
|
return unless path&.end_with?(".graphql", ".gql")
|
|
360
377
|
|
|
361
|
-
|
|
362
|
-
|
|
378
|
+
# once per file version: check_query is called per query, and the
|
|
379
|
+
# table behind a 2000-type supergraph costs ~200 ms to read against
|
|
380
|
+
# a fraction of a millisecond to stat — mtime and size in the key
|
|
381
|
+
# are what let schema:refresh's rewrite be seen without a reload
|
|
382
|
+
stat = File.stat(path)
|
|
383
|
+
key = [File.expand_path(path), stat.mtime, stat.size]
|
|
384
|
+
TABLES.synchronize do
|
|
385
|
+
return TABLES_BY_FILE[key] if TABLES_BY_FILE.key?(key)
|
|
386
|
+
|
|
387
|
+
sdl = File.read(path)
|
|
388
|
+
TABLES_BY_FILE[key] = SchemaLoader.routing_table(sdl) if SchemaLoader.federation_sdl?(sdl)
|
|
389
|
+
end
|
|
363
390
|
end
|
|
364
391
|
|
|
392
|
+
TABLES = Mutex.new
|
|
393
|
+
TABLES_BY_FILE = {}
|
|
394
|
+
private_constant :TABLES, :TABLES_BY_FILE
|
|
395
|
+
|
|
365
396
|
private
|
|
366
397
|
|
|
367
398
|
# Which subgraphs a validation error is about, on a federated schema:
|
data/lib/graph_weaver/railtie.rb
CHANGED
|
@@ -85,6 +85,12 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
85
85
|
# calling ignore again — check_generated_ignored! refuses instead.
|
|
86
86
|
attr_accessor :ignored_dirs
|
|
87
87
|
|
|
88
|
+
# The outputs left to Zeitwerk, resolved — the trees whose every module is
|
|
89
|
+
# the constant Zeitwerk infers from its path, so hiding them would be
|
|
90
|
+
# hiding code that already works. Decided beside the sweep, because it is
|
|
91
|
+
# what the sweep must not hide.
|
|
92
|
+
attr_accessor :autoloaded_dirs
|
|
93
|
+
|
|
88
94
|
# Whether hiding an output right now would actually hide it: false until
|
|
89
95
|
# the initializer below has swept (it picks up anything declared so far),
|
|
90
96
|
# and false again from the moment Zeitwerk sets the main autoloader up.
|
|
@@ -108,14 +114,24 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
108
114
|
GraphWeaver::Railtie.hide_generated!
|
|
109
115
|
end
|
|
110
116
|
|
|
117
|
+
# ONE rule: a generated tree Zeitwerk would name correctly is left to
|
|
118
|
+
# Zeitwerk; any other is hidden from it and required at boot.
|
|
119
|
+
#
|
|
111
120
|
# generated/person_query.rb defines ::PersonQuery, but Zeitwerk infers
|
|
112
121
|
# Generated::PersonQuery from the path — and app/graphql/generated is inside
|
|
113
122
|
# an autoload root by default, so eager loading raised "uninitialized
|
|
114
123
|
# constant Generated::PersonQuery" in production while development (lazy) was
|
|
115
|
-
# fine. prepare_generated! requires
|
|
124
|
+
# fine. prepare_generated! requires those instead. A graph whose `namespace:`
|
|
125
|
+
# is the constant its output path spells has no such mismatch, and requiring
|
|
126
|
+
# it at boot would define its whole parent chain before a query is ever used.
|
|
116
127
|
def self.hide_generated!
|
|
117
128
|
self.ignored_dirs = []
|
|
129
|
+
self.autoloaded_dirs = []
|
|
118
130
|
self.hiding_outputs = true
|
|
131
|
+
# the aligned trees first: they are what the sweep must NOT hide, and
|
|
132
|
+
# generated_paths' own globs cover a graph's output (app/graphql/*/generated
|
|
133
|
+
# matches app/graphql/billing/generated)
|
|
134
|
+
GraphWeaver.graphs.each { |graph| consider_output!(graph) }
|
|
119
135
|
GraphWeaver::Internal::Util.generated_dirs.each { |path| ignore_output!(path) }
|
|
120
136
|
|
|
121
137
|
# Zeitwerk reads its ignore list once, at setup, and nothing in Rails
|
|
@@ -124,12 +140,189 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
124
140
|
# check_generated_ignored! has to be the one to say so.
|
|
125
141
|
main = Rails.autoloaders.main if Rails.autoloaders.respond_to?(:main)
|
|
126
142
|
if main.respond_to?(:on_setup)
|
|
127
|
-
main.on_setup
|
|
143
|
+
main.on_setup do
|
|
144
|
+
GraphWeaver::Railtie.hiding_outputs = false
|
|
145
|
+
GraphWeaver::Railtie.check_alignment!
|
|
146
|
+
end
|
|
128
147
|
else
|
|
129
148
|
self.hiding_outputs = false
|
|
130
149
|
end
|
|
131
150
|
end
|
|
132
151
|
|
|
152
|
+
# Which side of the rule one graph's output falls, and say so in a line at
|
|
153
|
+
# debug: a user who set `namespace:` and expected laziness learns here why
|
|
154
|
+
# they didn't get it. True when the tree is Zeitwerk's to load.
|
|
155
|
+
def self.consider_output!(graph)
|
|
156
|
+
dir = autoload_path(graph.output)
|
|
157
|
+
return true if autoloaded_dirs.include?(dir)
|
|
158
|
+
# already hidden by an earlier pass, and an ignore can't be taken back
|
|
159
|
+
return false if ignored_dirs.include?(dir)
|
|
160
|
+
|
|
161
|
+
reason = misalignment(graph, dir)
|
|
162
|
+
subject, = describe_output(dir)
|
|
163
|
+
if reason
|
|
164
|
+
GraphWeaver::Internal::Log.log(:debug) do
|
|
165
|
+
"#{subject} is hidden from Zeitwerk and required at boot — #{reason}"
|
|
166
|
+
end
|
|
167
|
+
# here rather than back in ignore_output!, so the decision is recorded
|
|
168
|
+
# the moment it is made and the sweep doesn't reach it a second time
|
|
169
|
+
hide!(dir)
|
|
170
|
+
return false
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
autoloaded_dirs << dir
|
|
174
|
+
# types.rb requires its own directory — one file per type, in the order the
|
|
175
|
+
# runtime needs — so Zeitwerk never autoloads those files one at a time.
|
|
176
|
+
# Only eager loading walks them, and it would want a constant name for each
|
|
177
|
+
# that the generator's own inflection doesn't promise: HTTPHeaderInput
|
|
178
|
+
# writes httpheader_input.rb, which Zeitwerk reads as HttpheaderInput.
|
|
179
|
+
# Loading types.rb is what loads them, and types.rb is eager loaded.
|
|
180
|
+
Rails.autoloaders.each { |loader| loader.do_not_eager_load(File.join(dir, "types")) }
|
|
181
|
+
GraphWeaver::Internal::Log.log(:debug) do
|
|
182
|
+
"#{subject} is left to Zeitwerk — every module it generates is the constant Zeitwerk reads " \
|
|
183
|
+
"off the path, so it autoloads on first reference and reloads with the app"
|
|
184
|
+
end
|
|
185
|
+
true
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Why Zeitwerk would NOT name what this graph generates the way its files do
|
|
189
|
+
# — nil when it would, which is the aligned case. The first disagreement is
|
|
190
|
+
# the whole answer: one namespace settles every file, so the fix for one is
|
|
191
|
+
# the fix for all of them.
|
|
192
|
+
def self.misalignment(graph, dir)
|
|
193
|
+
main = Rails.autoloaders.main if Rails.respond_to?(:autoloaders) && Rails.autoloaders.respond_to?(:main)
|
|
194
|
+
# 2.6.2 is where cpath_expected_at arrives, and with it the only answer
|
|
195
|
+
# that can confirm this one at setup — without it nothing is left to
|
|
196
|
+
# Zeitwerk, which is what every app got before.
|
|
197
|
+
unless main.respond_to?(:cpath_expected_at)
|
|
198
|
+
return "no autoloader here answers cpath_expected_at (Zeitwerk 2.6.2 and up does)"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if ignored_by_app?(dir)
|
|
202
|
+
return "it is already hidden from autoloading — a GraphWeaver.generated_paths pattern reaches " \
|
|
203
|
+
"it, or the app ignored it itself"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
namespace = expected_cpath(dir)
|
|
207
|
+
return "it is not under an autoload path, so nothing but this would load it" unless namespace
|
|
208
|
+
unless namespace == graph.namespace
|
|
209
|
+
return "Zeitwerk reads that directory as #{namespace}, and this graph's constants are " \
|
|
210
|
+
"#{graph.namespace || "top-level"} (namespace #{namespace.inspect} makes them agree)"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
types = expected_cpath(File.join(dir, "types.rb"))
|
|
214
|
+
return "types.rb defines #{graph.types_module}, Zeitwerk expects #{types}" unless types == graph.types_module
|
|
215
|
+
|
|
216
|
+
first_mismatch(graph, dir)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# The first query module whose name isn't the one Zeitwerk reads off its
|
|
220
|
+
# file. Only reached once the namespace agrees, so what is left is a file
|
|
221
|
+
# name the app's inflector camelizes differently from the generator.
|
|
222
|
+
def self.first_mismatch(graph, dir)
|
|
223
|
+
GraphWeaver::Internal::Util.query_files(graph.queries).each do |query|
|
|
224
|
+
name, filename = graph.generated_names(query, File.read(query))
|
|
225
|
+
expected = expected_cpath(File.join(dir, filename))
|
|
226
|
+
next if expected == name
|
|
227
|
+
|
|
228
|
+
return "#{filename} defines #{name}, Zeitwerk expects #{expected}"
|
|
229
|
+
end
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The constant Zeitwerk will expect at an absolute path, or nil when nothing
|
|
234
|
+
# will autoload it.
|
|
235
|
+
#
|
|
236
|
+
# Zeitwerk answers this itself, with cpath_expected_at — but only once Rails
|
|
237
|
+
# has pushed the main loader's roots, and Rails does that INSIDE
|
|
238
|
+
# :setup_main_autoloader, whose start is the deadline for `ignore`. So the
|
|
239
|
+
# roots here are the list Rails is about to push, and every constant name
|
|
240
|
+
# comes from the loader's own inflector, which an app's `inflect`/`acronym`
|
|
241
|
+
# is already on. check_alignment! re-asks Zeitwerk the moment it can answer.
|
|
242
|
+
def self.expected_cpath(path)
|
|
243
|
+
main = Rails.autoloaders.main
|
|
244
|
+
roots = autoload_roots
|
|
245
|
+
cnames = []
|
|
246
|
+
dir = path
|
|
247
|
+
if path.end_with?(".rb")
|
|
248
|
+
cnames << main.inflector.camelize(File.basename(path, ".rb"), path)
|
|
249
|
+
dir = File.dirname(path)
|
|
250
|
+
end
|
|
251
|
+
until roots.key?(dir)
|
|
252
|
+
parent = File.dirname(dir)
|
|
253
|
+
return if parent == dir || ignored_by_app?(dir)
|
|
254
|
+
|
|
255
|
+
cnames << main.inflector.camelize(File.basename(dir), dir)
|
|
256
|
+
dir = parent
|
|
257
|
+
end
|
|
258
|
+
return if ignored_by_app?(dir)
|
|
259
|
+
|
|
260
|
+
[roots[dir], *cnames.reverse].compact.join("::")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Every directory the main autoloader will have as a root, and the name of
|
|
264
|
+
# the namespace each sits under (nil for Object): the paths Rails is about to
|
|
265
|
+
# push, plus any the app pushed itself — which is the only way a root gets a
|
|
266
|
+
# namespace, and the one thing Rails leaves alone when it pushes the rest.
|
|
267
|
+
def self.autoload_roots
|
|
268
|
+
roots = {}
|
|
269
|
+
if defined?(ActiveSupport::Dependencies) && ActiveSupport::Dependencies.respond_to?(:autoload_paths)
|
|
270
|
+
once = Rails.autoloaders.respond_to?(:once) ? Rails.autoloaders.once.dirs : []
|
|
271
|
+
ActiveSupport::Dependencies.autoload_paths.each do |path|
|
|
272
|
+
path = path.to_s
|
|
273
|
+
# through autoload_path, so a root and an output under it are spelled
|
|
274
|
+
# the same way — a symlinked checkout (Capistrano's current/) otherwise
|
|
275
|
+
# has the root as Rails wrote it and the output as realpath saw it
|
|
276
|
+
roots[autoload_path(path)] = nil unless once.include?(path)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
Rails.autoloaders.main.dirs(namespaces: true).each do |dir, namespace|
|
|
280
|
+
roots[autoload_path(dir)] = (namespace == Object) ? nil : namespace.name
|
|
281
|
+
end
|
|
282
|
+
roots
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# An app that hid the directory itself has a tree Zeitwerk will not load
|
|
286
|
+
# however it is named — so it is one to require at boot, exactly as before.
|
|
287
|
+
def self.ignored_by_app?(dir)
|
|
288
|
+
asked = IGNORES.find { |name| Rails.autoloaders.main.respond_to?(name) }
|
|
289
|
+
asked && Rails.autoloaders.main.public_send(asked, dir)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# The prediction above, against the one answer that is authoritative. A
|
|
293
|
+
# `collapse` between the autoload root and the output, or a root pushed under
|
|
294
|
+
# its own namespace after this railtie looked, changes the name — and neither
|
|
295
|
+
# is readable before setup. Left unsaid, that surfaces as Zeitwerk's own
|
|
296
|
+
# error on the first eager load in production.
|
|
297
|
+
def self.check_alignment!
|
|
298
|
+
main = Rails.autoloaders.main
|
|
299
|
+
Array(autoloaded_dirs).each do |dir|
|
|
300
|
+
# the top level only: types/ is excluded from eager loading, so what
|
|
301
|
+
# Zeitwerk reads off those files never comes up
|
|
302
|
+
Dir[File.join(dir, "*.rb")].sort.each do |file|
|
|
303
|
+
expected = main.cpath_expected_at(file)
|
|
304
|
+
# nil is "I don't see that file" — which is what Zeitwerk answers for a
|
|
305
|
+
# path spelled through a symlink its own roots aren't, so it is not an
|
|
306
|
+
# answer to compare against
|
|
307
|
+
next if expected.nil? || expected == expected_cpath(file)
|
|
308
|
+
|
|
309
|
+
subject, short = describe_output(dir)
|
|
310
|
+
raise GraphWeaver::Error,
|
|
311
|
+
"#{subject} was left to Zeitwerk because the constants it generates are the ones its path " \
|
|
312
|
+
"spells — but Zeitwerk reads #{GraphWeaver::Internal::Util.relative(file)} as " \
|
|
313
|
+
"#{expected.inspect}, not #{expected_cpath(file).inspect}. A `collapse`, or an autoload root " \
|
|
314
|
+
"pushed under its own namespace, over #{short} changes that name: move the output out from " \
|
|
315
|
+
"under it, or set namespace: to what Zeitwerk reads."
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Whether this generated directory is one Zeitwerk is loading — what
|
|
321
|
+
# Internal::Util.required_dirs asks, so the boot-time require skips it.
|
|
322
|
+
def self.left_to_zeitwerk?(path)
|
|
323
|
+
Array(autoloaded_dirs).include?(autoload_path(path))
|
|
324
|
+
end
|
|
325
|
+
|
|
133
326
|
# Hide one output from Zeitwerk, the moment it is named — a graph declared in
|
|
134
327
|
# config/initializers says where it writes long after the sweep above ran.
|
|
135
328
|
# Waiting for it instead is what `after: :load_config_initializers` used to
|
|
@@ -141,9 +334,17 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
141
334
|
def self.ignore_output!(path)
|
|
142
335
|
return unless hiding_outputs
|
|
143
336
|
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
|
|
337
|
+
# a graph saying where it writes is also the moment the rule can be applied
|
|
338
|
+
# to it: config/initializers is later than the sweep above, so this is
|
|
339
|
+
# where an app's second graph is first heard of
|
|
340
|
+
graph = GraphWeaver.graphs.find { |candidate| autoload_path(candidate.output) == autoload_path(path) }
|
|
341
|
+
return if graph && consider_output!(graph)
|
|
342
|
+
|
|
343
|
+
hidden_for(path).each { |dir| hide!(dir) }
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Hide one resolved directory, once.
|
|
347
|
+
def self.hide!(dir)
|
|
147
348
|
return if ignored_dirs.include?(dir)
|
|
148
349
|
|
|
149
350
|
check_autoload_once!([dir])
|
|
@@ -151,6 +352,25 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
151
352
|
Rails.autoloaders.each { |loader| loader.ignore(dir) }
|
|
152
353
|
end
|
|
153
354
|
|
|
355
|
+
# What hiding one generated path means. Normally the pattern itself —
|
|
356
|
+
# generated_paths may be globs, and Zeitwerk expands its own at setup, so a
|
|
357
|
+
# directory a pattern will match is hidden however late it appears; that is
|
|
358
|
+
# what the window stays open until. The exception is a pattern that also
|
|
359
|
+
# covers a tree Zeitwerk is keeping (app/graphql/*/generated matches a
|
|
360
|
+
# namespaced graph's own output), since a glob can't say "all but this one":
|
|
361
|
+
# then it is expanded here and the aligned directories left out.
|
|
362
|
+
def self.hidden_for(path)
|
|
363
|
+
dir = autoload_path(path)
|
|
364
|
+
kept = Array(autoloaded_dirs)
|
|
365
|
+
return [dir] if kept.empty?
|
|
366
|
+
|
|
367
|
+
expanded = Dir[GraphWeaver::Internal::Util.resolve(path)]
|
|
368
|
+
.select { |found| File.directory?(found) }.map { |found| autoload_path(found) }
|
|
369
|
+
return [dir] if (expanded & kept).empty?
|
|
370
|
+
|
|
371
|
+
expanded - kept
|
|
372
|
+
end
|
|
373
|
+
|
|
154
374
|
# The `once` autoloader is set up in bootstrap, before any of this — and
|
|
155
375
|
# Zeitwerk reads its ignore list only at setup, so `loader.ignore` hides
|
|
156
376
|
# nothing from it however the output is spelled. The generic advice ("name it
|
|
@@ -211,7 +431,9 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
211
431
|
def self.describe_output(dir)
|
|
212
432
|
graph = GraphWeaver.graphs.find { autoload_path(_1.output) == dir }
|
|
213
433
|
short = GraphWeaver::Internal::Util.relative(GraphWeaver::Internal::Util.resolve(graph&.output || dir))
|
|
214
|
-
|
|
434
|
+
# the default graph has no name to say, and "graph :'s output" is worse
|
|
435
|
+
# than the path on its own
|
|
436
|
+
["#{graph&.name ? "graph #{graph.name.inspect}'s output" : "generated path"} #{short}", short]
|
|
215
437
|
end
|
|
216
438
|
|
|
217
439
|
# A graph declared from to_prepare — what the docs say to do when its block
|
|
@@ -219,16 +441,20 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
219
441
|
# Zeitwerk reads its ignore list only then. So an output that arrives that
|
|
220
442
|
# late can't be hidden: its files load as ordinary autoloads and raise on the
|
|
221
443
|
# constant they don't define, in a Zeitwerk error that blames a dropped
|
|
222
|
-
# extend_type. Refuse, and name what actually happened
|
|
444
|
+
# extend_type. Refuse, and name what actually happened — unless the tree is
|
|
445
|
+
# one Zeitwerk names correctly, which needed no hiding at any point and is as
|
|
446
|
+
# true declared late as declared early.
|
|
223
447
|
def self.check_generated_ignored!
|
|
224
448
|
# no autoloaders, no Zeitwerk, nothing to refuse
|
|
225
449
|
return unless Rails.respond_to?(:autoloaders)
|
|
226
450
|
|
|
227
|
-
late = GraphWeaver::Internal::Util.generated_dirs.map { autoload_path(_1) } -
|
|
451
|
+
late = GraphWeaver::Internal::Util.generated_dirs.map { autoload_path(_1) } -
|
|
452
|
+
Array(ignored_dirs) - Array(autoloaded_dirs)
|
|
228
453
|
return if late.empty?
|
|
229
454
|
|
|
230
455
|
late.each do |dir|
|
|
231
456
|
next unless Rails.autoloaders.any? { |loader| autoloaded?(loader, dir) }
|
|
457
|
+
next if aligned_late?(dir)
|
|
232
458
|
|
|
233
459
|
subject, short = describe_output(dir)
|
|
234
460
|
raise GraphWeaver::Error,
|
|
@@ -239,6 +465,17 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
239
465
|
end
|
|
240
466
|
end
|
|
241
467
|
|
|
468
|
+
# A graph declared after setup, whose tree Zeitwerk already names correctly:
|
|
469
|
+
# nothing had to be hidden, so nothing was missed. Recorded on the way past,
|
|
470
|
+
# so the boot-time require skips it too.
|
|
471
|
+
def self.aligned_late?(dir)
|
|
472
|
+
graph = GraphWeaver.graphs.find { autoload_path(_1.output) == dir }
|
|
473
|
+
return false unless graph && misalignment(graph, dir).nil?
|
|
474
|
+
|
|
475
|
+
self.autoloaded_dirs = Array(autoloaded_dirs) | [dir]
|
|
476
|
+
true
|
|
477
|
+
end
|
|
478
|
+
|
|
242
479
|
# The two auto-wires an app gets for free, and the only two it can turn off
|
|
243
480
|
# by assigning nil.
|
|
244
481
|
#
|
|
@@ -408,8 +645,10 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
408
645
|
# reloaded what it wrote.
|
|
409
646
|
return if watcher&.execute_if_updated
|
|
410
647
|
|
|
411
|
-
# entries may be globs, so Dir[] rather than Dir.exist
|
|
412
|
-
|
|
648
|
+
# entries may be globs, so Dir[] rather than Dir.exist?. required_dirs, not
|
|
649
|
+
# generated_dirs: a tree left to Zeitwerk loads on first reference, and not
|
|
650
|
+
# requiring its whole parent chain here is the point of leaving it there.
|
|
651
|
+
generated = GraphWeaver::Internal::Util.required_dirs.any? do |dir|
|
|
413
652
|
Dir[GraphWeaver::Internal::Util.resolve(dir)].any?
|
|
414
653
|
end
|
|
415
654
|
return unless generated
|
|
@@ -422,7 +661,7 @@ class GraphWeaver::Railtie < Rails::Railtie
|
|
|
422
661
|
# request 500s on "uninitialized constant Accounts::PersonQuery" until a
|
|
423
662
|
# .graphql edit happens to trigger the watcher. An un-namespaced module
|
|
424
663
|
# defines a top-level constant Zeitwerk never manages, so it survives.
|
|
425
|
-
if GraphWeaver.graphs.any?(
|
|
664
|
+
if GraphWeaver.graphs.any? { |graph| graph.namespace && !left_to_zeitwerk?(graph.output) }
|
|
426
665
|
GraphWeaver.reload_generated!
|
|
427
666
|
else
|
|
428
667
|
GraphWeaver.load_generated!
|
|
@@ -49,6 +49,14 @@ module GraphWeaver::SchemaLoader
|
|
|
49
49
|
source.match?(SDL_CONTENT)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Schema content rather than a path: introspection JSON, SDL, or anything
|
|
53
|
+
# spanning lines (which `load` refuses as content it doesn't recognize).
|
|
54
|
+
def self.content?(source)
|
|
55
|
+
source = source.to_path if source.respond_to?(:to_path)
|
|
56
|
+
source = source.to_s
|
|
57
|
+
source.lstrip.start_with?("{") || sdl_content?(source) || source.include?("\n")
|
|
58
|
+
end
|
|
59
|
+
|
|
52
60
|
def self.load_path(path)
|
|
53
61
|
case File.extname(path)
|
|
54
62
|
when ".json"
|
|
@@ -56,13 +64,33 @@ module GraphWeaver::SchemaLoader
|
|
|
56
64
|
when ".graphql", ".gql"
|
|
57
65
|
build_sdl(read_schema(path))
|
|
58
66
|
else
|
|
59
|
-
|
|
60
|
-
"unsupported schema format: #{truncate(path)} — expected a .json (introspection) or " \
|
|
61
|
-
".graphql/.gql (SDL) path, or the content itself"
|
|
67
|
+
unsupported_path!(path)
|
|
62
68
|
end
|
|
63
69
|
end
|
|
64
70
|
private_class_method :load_path
|
|
65
71
|
|
|
72
|
+
# Fail fast on a path `load` would refuse later — the extension and a
|
|
73
|
+
# stat, no read and no parse. A client defers building the schema to first
|
|
74
|
+
# use, so this is what still refuses at the line that named the file: a
|
|
75
|
+
# wrong path is known at boot, a malformed file at first use.
|
|
76
|
+
def self.check_path!(path)
|
|
77
|
+
path = path.to_path if path.respond_to?(:to_path)
|
|
78
|
+
unsupported_path!(path) unless dump_path?(path)
|
|
79
|
+
|
|
80
|
+
resolved = GraphWeaver::Internal::Util.resolve(path)
|
|
81
|
+
File.stat(resolved)
|
|
82
|
+
raise GraphWeaver::Error, "can't read the schema at #{path}: permission denied" unless File.readable?(resolved)
|
|
83
|
+
rescue SystemCallError => e
|
|
84
|
+
raise GraphWeaver::Error, "can't read the schema at #{path}: #{e.message}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.unsupported_path!(path)
|
|
88
|
+
raise GraphWeaver::Error, url_error(path) ||
|
|
89
|
+
"unsupported schema format: #{truncate(path)} — expected a .json (introspection) or " \
|
|
90
|
+
".graphql/.gql (SDL) path, or the content itself"
|
|
91
|
+
end
|
|
92
|
+
private_class_method :unsupported_path!
|
|
93
|
+
|
|
66
94
|
def self.read_schema(path)
|
|
67
95
|
File.read(GraphWeaver::Internal::Util.resolve(path))
|
|
68
96
|
rescue SystemCallError => e
|
|
@@ -123,10 +151,23 @@ module GraphWeaver::SchemaLoader
|
|
|
123
151
|
else sdl
|
|
124
152
|
end
|
|
125
153
|
|
|
126
|
-
build(kind) {
|
|
154
|
+
build(kind) { build_schema(prepared) }
|
|
127
155
|
end
|
|
128
156
|
private_class_method :build_sdl
|
|
129
157
|
|
|
158
|
+
# A supergraph arrives here as the AST strip_federation filtered, anything
|
|
159
|
+
# else as SDL text. `from_definition` is parse-then-build, so an AST takes
|
|
160
|
+
# the build half directly — printing it back and parsing it again is a
|
|
161
|
+
# round trip through text that can only cost. spec/schema_loader_spec.rb
|
|
162
|
+
# pins the entry point, so a graphql-ruby that moves it fails there rather
|
|
163
|
+
# than as a NoMethodError inside someone's load.
|
|
164
|
+
def self.build_schema(prepared)
|
|
165
|
+
return GraphQL::Schema.from_definition(prepared) if prepared.is_a?(String)
|
|
166
|
+
|
|
167
|
+
GraphQL::Schema::BuildFromDefinition.from_document(GraphQL::Schema, prepared, default_resolve: nil)
|
|
168
|
+
end
|
|
169
|
+
private_class_method :build_schema
|
|
170
|
+
|
|
130
171
|
def self.build_introspection(result)
|
|
131
172
|
build(:introspection) { GraphQL::Schema.from_introspection(result) }.tap do |schema|
|
|
132
173
|
restore_one_of!(schema, result)
|
|
@@ -519,9 +560,9 @@ module GraphWeaver::SchemaLoader
|
|
|
519
560
|
# join__*/link__* type and directive definitions, and every @join__*/@link
|
|
520
561
|
# application on the types that remain. What's left is the merged graph's
|
|
521
562
|
# ordinary type shapes — exactly what codegen reads. Parsing is lenient (it's
|
|
522
|
-
# schema *building* that rejects the join directives), so we parse
|
|
523
|
-
# AST, and
|
|
524
|
-
# and no join__* leaking into schema.types.
|
|
563
|
+
# schema *building* that rejects the join directives), so we parse and filter
|
|
564
|
+
# the AST, and hand that document to the builder — no graphql-ruby
|
|
565
|
+
# monkeypatch and no join__* leaking into schema.types.
|
|
525
566
|
def self.strip_federation(sdl)
|
|
526
567
|
doc = GraphQL.parse(sdl)
|
|
527
568
|
ns = link_namespaces(doc)
|
|
@@ -535,7 +576,7 @@ module GraphWeaver::SchemaLoader
|
|
|
535
576
|
"is the whole schema behind @inaccessible?"
|
|
536
577
|
end
|
|
537
578
|
|
|
538
|
-
GraphQL::Language::Nodes::Document.new(definitions: defs)
|
|
579
|
+
GraphQL::Language::Nodes::Document.new(definitions: defs)
|
|
539
580
|
end
|
|
540
581
|
private_class_method :strip_federation
|
|
541
582
|
|
|
@@ -1259,10 +1300,18 @@ module GraphWeaver::SchemaLoader
|
|
|
1259
1300
|
|
|
1260
1301
|
# A @key/@requires/@provides field set is a selection set. Flattened to
|
|
1261
1302
|
# dotted paths, so a nested one is recognizable as nested by its shape.
|
|
1303
|
+
# Memoized on the text: a supergraph repeats the same few spellings
|
|
1304
|
+
# thousands of times ("id" on every entity), and each was a parse.
|
|
1262
1305
|
def self.parse_field_set(text)
|
|
1263
|
-
|
|
1306
|
+
FIELD_SETS.synchronize do
|
|
1307
|
+
FIELD_SETS_BY_TEXT[text] ||= flatten(GraphQL.parse("{ #{text} }").definitions.first.selections, []).freeze
|
|
1308
|
+
end
|
|
1264
1309
|
end
|
|
1265
1310
|
|
|
1311
|
+
FIELD_SETS = Mutex.new
|
|
1312
|
+
FIELD_SETS_BY_TEXT = {}
|
|
1313
|
+
private_constant :FIELD_SETS, :FIELD_SETS_BY_TEXT
|
|
1314
|
+
|
|
1266
1315
|
def self.flatten(selections, prefix)
|
|
1267
1316
|
selections.flat_map do |node|
|
|
1268
1317
|
unless node.is_a?(GraphQL::Language::Nodes::Field)
|
data/lib/graph_weaver/version.rb
CHANGED
data/lib/graph_weaver.rb
CHANGED
|
@@ -219,9 +219,15 @@ module GraphWeaver
|
|
|
219
219
|
# from where you put the files: set it globally, or pass types_module: per
|
|
220
220
|
# generate!. A multi-schema layout names it in the same initializer that
|
|
221
221
|
# sets its paths.
|
|
222
|
+
#
|
|
223
|
+
# Unset, the default is whichever name is free where the constants land:
|
|
224
|
+
# GraphQLTypes at the top level, where a bare ::Types would collide with
|
|
225
|
+
# graphql-ruby's own Types:: convention, and plain Types inside a graph's
|
|
226
|
+
# namespace, where it can't — which is what leaves types.rb a file Zeitwerk
|
|
227
|
+
# names correctly (see Graph#types_module).
|
|
222
228
|
attr_writer :types_module
|
|
223
229
|
|
|
224
|
-
def types_module = @types_module ||
|
|
230
|
+
def types_module(default = "GraphQLTypes") = @types_module || default
|
|
225
231
|
|
|
226
232
|
# Declare a second schema — and a third, and the rest. Everything a graph
|
|
227
233
|
# knows is said inside its block, in call style:
|
|
@@ -703,12 +709,13 @@ module GraphWeaver
|
|
|
703
709
|
#
|
|
704
710
|
# GraphWeaver.load_generated!
|
|
705
711
|
#
|
|
706
|
-
# In Rails,
|
|
707
|
-
#
|
|
708
|
-
#
|
|
709
|
-
#
|
|
712
|
+
# In Rails this is automatic, and a tree Zeitwerk already names correctly
|
|
713
|
+
# is skipped: it autoloads on first reference (see the Railtie). Everything
|
|
714
|
+
# else is required — Zeitwerk would expect Generated::PersonQuery from
|
|
715
|
+
# generated/person_query.rb, and generated code only changes on
|
|
716
|
+
# regeneration anyway (restart, like a schema migration).
|
|
710
717
|
def load_generated!(path = nil)
|
|
711
|
-
paths = path ? [path] : Internal::Util.
|
|
718
|
+
paths = path ? [path] : Internal::Util.required_dirs
|
|
712
719
|
files = paths.flat_map { |dir| Dir[File.join(Internal::Util.resolve(dir), "**/*.rb")].sort }.uniq
|
|
713
720
|
files.each do |file|
|
|
714
721
|
require file
|
|
@@ -761,7 +768,9 @@ module GraphWeaver
|
|
|
761
768
|
# whose query was just deleted keeps its old constant until restart —
|
|
762
769
|
# nothing on disk says what it was called any more.
|
|
763
770
|
def reload_generated!
|
|
764
|
-
|
|
771
|
+
# a graph Zeitwerk owns unloads and reloads itself; undefining its
|
|
772
|
+
# constants here would only take them out from under it
|
|
773
|
+
names = graphs.reject { |graph| Internal::Util.left_to_zeitwerk?(graph.output) }.flat_map do |graph|
|
|
765
774
|
modules = Internal::Util.query_files(graph.queries).map do |path|
|
|
766
775
|
graph.generated_names(path, File.read(path)).first
|
|
767
776
|
end
|
|
@@ -769,7 +778,7 @@ module GraphWeaver
|
|
|
769
778
|
end
|
|
770
779
|
names.each { |name| undefine(name) }
|
|
771
780
|
|
|
772
|
-
Internal::Util.
|
|
781
|
+
Internal::Util.required_dirs.each do |dir|
|
|
773
782
|
Dir[File.join(Internal::Util.resolve(dir), "**/*.rb")].each do |file|
|
|
774
783
|
# require stores the realpath; the path load_generated! passed is
|
|
775
784
|
# the other one under a symlinked checkout
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graph_weaver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Pepper
|
|
@@ -364,7 +364,7 @@ licenses:
|
|
|
364
364
|
- MIT
|
|
365
365
|
metadata:
|
|
366
366
|
bug_tracker_uri: https://github.com/dpep/graph_weaver/issues
|
|
367
|
-
changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.
|
|
367
|
+
changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.8/CHANGELOG.md
|
|
368
368
|
documentation_uri: https://github.com/dpep/graph_weaver/tree/main/docs
|
|
369
369
|
rubygems_mfa_required: 'true'
|
|
370
370
|
source_code_uri: https://github.com/dpep/graph_weaver
|