graph_weaver 0.7.7 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7b665c1263db52f315217a6298ff638d963b926e44f94c16dc2972de4140320
4
- data.tar.gz: a9712a11f712adfa8846b180b1e22874f63d57fd00514d311b9cb0e60c9f6ebf
3
+ metadata.gz: bd9b2703206fc3145399e11794cd488a5e3c3734584ca7a56fa0e4d2afed9a77
4
+ data.tar.gz: 0e247a7b925ff0a89cb5952ac962b31545af417f865342a22b8019dcbcdbd135
5
5
  SHA512:
6
- metadata.gz: 693fa12dd8117988b35adcf7f88234b382bae9a8d82307a23ce7ffa3f2837c1304a529155dedab414691b13c3d982314cb70c8d5587660f0766988c7defc0f83
7
- data.tar.gz: 2933a687d3c85faa3d514e6daddd5fe9ea62ee6e48f146e847f492a63e91b6c43c6986964f52f1fa458f1f6a60b92038058357e21062e966c47c8fef0a0235c4
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.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.3.1)
12
- actionview (= 8.1.3.1)
13
- activesupport (= 8.1.3.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.3.1)
22
- activesupport (= 8.1.3.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.3.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.2)
48
+ bigdecimal (4.1.3)
49
49
  builder (3.3.0)
50
- concurrent-ruby (1.3.7)
50
+ concurrent-ruby (1.3.8)
51
51
  connection_pool (3.0.2)
52
52
  crack (1.0.1)
53
53
  bigdecimal
@@ -58,7 +58,7 @@ GEM
58
58
  reline (>= 0.3.8)
59
59
  diff-lcs (1.6.2)
60
60
  drb (2.2.3)
61
- erb (6.0.4)
61
+ erb (6.0.7)
62
62
  erubi (1.13.1)
63
63
  faker (3.8.0)
64
64
  i18n (>= 1.8.11, < 2)
@@ -81,14 +81,14 @@ GEM
81
81
  google-protobuf (4.35.1-x86_64-linux-gnu)
82
82
  bigdecimal
83
83
  rake (~> 13.3)
84
- graphql (2.6.10)
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.8.2)
91
+ io-console (0.9.4)
92
92
  irb (1.18.0)
93
93
  pp (>= 0.6.0)
94
94
  prism (>= 1.3.0)
@@ -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.3.1)
153
- actionpack (= 8.1.3.1)
154
- activesupport (= 8.1.3.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)
@@ -166,14 +166,14 @@ GEM
166
166
  logger
167
167
  prism (>= 1.6.0)
168
168
  tsort
169
- rdoc (8.0.0)
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.6.3)
176
+ reline (0.7.0)
177
177
  io-console (~> 0.5)
178
178
  require-hooks (0.5.1)
179
179
  rexml (3.4.4)
@@ -195,16 +195,16 @@ GEM
195
195
  rubydex (0.4.1-x86_64-darwin)
196
196
  rubydex (0.4.1-x86_64-linux)
197
197
  securerandom (0.4.1)
198
- simplecov (1.3.0)
199
- sorbet (0.6.13506)
200
- sorbet-static (= 0.6.13506)
201
- sorbet-runtime (0.6.13506)
202
- sorbet-static (0.6.13506-aarch64-linux)
203
- sorbet-static (0.6.13506-universal-darwin)
204
- sorbet-static (0.6.13506-x86_64-linux)
205
- sorbet-static-and-runtime (0.6.13506)
206
- sorbet (= 0.6.13506)
207
- sorbet-runtime (= 0.6.13506)
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
208
  spoom (1.8.7)
209
209
  erubi (>= 1.10.0)
210
210
  prism (>= 0.28.0)
@@ -272,24 +272,24 @@ DEPENDENCIES
272
272
  yard
273
273
 
274
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
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.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
283
+ bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66
284
284
  builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
285
- concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
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.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
292
+ erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92
293
293
  erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
294
294
  faker (3.8.0) sha256=c147b308df73a90f27a4fc84f18d4c22ef0ad9c2a64b2b61c86fd0ca71753efc
295
295
  faraday (2.14.4) sha256=9bb4408c44621b0dbaaaa39e5dbfc4dbe58cc6751b6eb94d16ae9e1f2f2fab6e
@@ -299,11 +299,11 @@ CHECKSUMS
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.7)
303
- graphql (2.6.10) sha256=9b7c8633767f516ff9d48a8d6305b2a00a2101c82aa871b92e69086944f9f83e
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.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
306
+ io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1
307
307
  irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
308
308
  json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df
309
309
  logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
@@ -330,14 +330,14 @@ 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.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487
333
+ railties (8.1.4) sha256=b5e7a8e8984e33114d68af62227971960aae1e6c02bd7c9d826ffad97fff583c
334
334
  rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
335
335
  rbi (0.4.3) sha256=cc090ae62c3246412b992954f54077d6046c9b04f1537a4d24bfae8714503eac
336
336
  rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674
337
- rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
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.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
340
+ reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
341
341
  require-hooks (0.5.1) sha256=49a6b14518b91c5b8006d0644ef257af0ca033df23625bd4e314a8c5282191d9
342
342
  rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
343
343
  rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
@@ -350,13 +350,13 @@ CHECKSUMS
350
350
  rubydex (0.4.1-x86_64-darwin) sha256=a9681db5bc9be6a62f226d96d6889b6b600d0b09e7576391702aafd29912145b
351
351
  rubydex (0.4.1-x86_64-linux) sha256=4e2c8398c9fc585485b54edd28f5ecf76b5a02255e177b6f8402e9d2afffd51b
352
352
  securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
353
- simplecov (1.3.0) sha256=d9886307863c1ead47657dcbed869bfffd82318808b09014d8f0ff77dcfe7492
354
- sorbet (0.6.13506) sha256=b6fd9f9ab2563c709b9308437cb40754e190667fddfe841723a7dda430978cd6
355
- sorbet-runtime (0.6.13506) sha256=94a7ff795cf2a67818a954883c0301de0b7222a9f732b24ab30ea5253e7568f6
356
- sorbet-static (0.6.13506-aarch64-linux) sha256=04405070206de265a87e6544454092cd4c7b275415dccf76a4ab41649819ae9f
357
- sorbet-static (0.6.13506-universal-darwin) sha256=744918a125841c84d0804410bfda068a6dc8182285ba50ddb1e8ce5955b24700
358
- sorbet-static (0.6.13506-x86_64-linux) sha256=b57d36d1db374e10109bbd5af3bec0edf6f94f3e86dbffe69199763909ad2c91
359
- sorbet-static-and-runtime (0.6.13506) sha256=12c6abbc1d988267452c97e4cff75c563a480e3e8b7a1a633e2f714856e5c313
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
360
  spoom (1.8.7) sha256=81960502cdf23aa991f015988aa5dd43325e23ae4318b8e00d541299cbdb70f6
361
361
  tapioca (0.20.0) sha256=b375ed7bf51d2c86c09543eafedd0de85513634187c0f7a062615af8bb9347a0
362
362
  thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
@@ -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
data/docs/upgrading.md CHANGED
@@ -1,7 +1,8 @@
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.6](#upgrading-from-076), from
4
+ the one section that is yours: from [0.7.7](#upgrading-from-077), from
5
+ [0.7.6](#upgrading-from-076), from
5
6
  [0.7.5](#upgrading-from-075), from
6
7
  [0.7.4](#upgrading-from-074), from [0.7.3](#upgrading-from-073), from
7
8
  [0.7.1](#upgrading-from-071), from [0.7.0](#upgrading-from-070) or from
@@ -25,6 +26,25 @@ That's the reminder working, not a false alarm. Generation is deterministic, so
25
26
  the diff is exactly what the new version emits differently and nothing else —
26
27
  worth reading rather than rubber-stamping.
27
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
+
28
48
  ## Upgrading from 0.7.6
29
49
 
30
50
  Read the left column and skip what isn't yours; the
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.7 — do not edit.
4
+ # Generated by GraphWeaver 0.7.8 — do not edit.
5
5
 
6
6
  module StarMutation
7
7
  extend T::Sig
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.7 — do not edit.
4
+ # Generated by GraphWeaver 0.7.8 — do not edit.
5
5
 
6
6
  require "time"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.7 — do not edit.
4
+ # Generated by GraphWeaver 0.7.8 — do not edit.
5
5
 
6
6
  module StarredQuery
7
7
  extend T::Sig
@@ -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
- @schema = source.is_a?(Module) ? source : GraphWeaver::SchemaLoader.load(source)
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, introspecting through the transport on first use (cached
151
- # per the client's cache:/ttl:) unless one was given up front.
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
@@ -458,19 +458,51 @@ class GraphWeaver::Codegen
458
458
  declares!(named, name)
459
459
  end
460
460
 
461
- # Where the operation starts in @query, in characters. graphql-ruby subtracts
462
- # a CHARACTER line start from the lexer's BYTE position
463
- # (Language::Parser#column_at over Lexer's @scanner.pos), so line start plus
464
- # col is a byte offset — and col falls back to a whole-document offset for a
465
- # token with no newline after it, which .strip guarantees for the last line,
466
- # hence the copy that ends in one.
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
- operation = sole_operation("#{@query}\n")
469
- bytes = @query.lines.first(operation.line - 1).sum(&:length) + operation.col - 1
470
- @query.byteslice(0, bytes).length
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 is arithmetic over a position graphql-ruby reports, so it
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
@@ -375,10 +375,24 @@ module GraphWeaver
375
375
  path = path.to_path if path.respond_to?(:to_path)
376
376
  return unless path&.end_with?(".graphql", ".gql")
377
377
 
378
- sdl = File.read(path)
379
- SchemaLoader.routing_table(sdl) if SchemaLoader.federation_sdl?(sdl)
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
380
390
  end
381
391
 
392
+ TABLES = Mutex.new
393
+ TABLES_BY_FILE = {}
394
+ private_constant :TABLES, :TABLES_BY_FILE
395
+
382
396
  private
383
397
 
384
398
  # Which subgraphs a validation error is about, on a federated schema:
@@ -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
- raise GraphWeaver::Error, url_error(path) ||
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) { GraphQL::Schema.from_definition(prepared) }
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, filter the
523
- # AST, and reprint clean SDL for from_definition — no graphql-ruby monkeypatch
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).to_query_string
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
- flatten(GraphQL.parse("{ #{text} }").definitions.first.selections, [])
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)
@@ -1,3 +1,3 @@
1
1
  module GraphWeaver
2
- VERSION = "0.7.7"
2
+ VERSION = "0.7.8"
3
3
  end
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.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.7/CHANGELOG.md
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