graph_weaver 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0ae9c23b2566bfa26c786e07703e4fad357cbd09a29112d3e0c9dd5d3af916c
4
- data.tar.gz: fb5f4534174e5ba9cb7b9d323af26c2a13456c022f8682bcc3ccae5e512a1c96
3
+ metadata.gz: 2cb0372c9f8505bec490fbb13344cbd586da68cb4932cbce0f86de2467d0eaf4
4
+ data.tar.gz: 6bc17449eea717c8cb145c159a2395f61f484a2f585c016ee31521f979f1a6d6
5
5
  SHA512:
6
- metadata.gz: dc780e4f155ea89b9977822a7bb62ed9880d3b7e707fd5046229b1c91c843a9c1ec37c39fb5e2039e71c9095dfb7a0df006347a19ce4544f75f2b3fe13a35a5c
7
- data.tar.gz: 3dcc86b06e670408ebc18eaaaad7447d5825d1d580b9640ed8860133ba29908eb42553d52da4a2619eb23f072f4999f5aceb622271fafc8d5431fd57a3106ed3
6
+ metadata.gz: 63670eee3838644725166822028b4816164450c853e24d74dd6ce47212e4e18c2bda7309d5549f8830e709d547e73678425ae9984d960a5367b97f5102613f5c
7
+ data.tar.gz: d7b30350095090f9c69e7b5316137ac5324b60965a242ffe5c9be4a893812a48351a172bde26f36000797d830eb01c3e15ea0352eea8ecda9ac775f55b12ac82
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graph_weaver (0.7.1)
4
+ graph_weaver (0.7.2)
5
5
  graphql (>= 2.6.7)
6
6
  sorbet-runtime
7
7
 
@@ -299,7 +299,7 @@ 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.1)
302
+ graph_weaver (0.7.2)
303
303
  graphql (2.6.10) sha256=9b7c8633767f516ff9d48a8d6305b2a00a2101c82aa871b92e69086944f9f83e
304
304
  hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
305
305
  i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
data/docs/federation.md CHANGED
@@ -129,11 +129,13 @@ your resolvers run, which is the whole point.
129
129
 
130
130
  In rspec that's the [`graphql: :router`](testing.md#a-federated-graph--graphql-router)
131
131
  tag and there is nothing to pass — the tag builds it, once for the suite. It finds
132
- the supergraph where you have already said it is: `Testing.config.router =
133
- { supergraph: … }` if you named one there, else the schema a
132
+ the supergraph where you have already said it is: the schema a
134
133
  [graph](getting_started.md#more-than-one-schema) declares when that schema is
135
- composed, else the committed dump when *that* is. Two graphs may name one
136
- supergraph; two naming different ones is refused rather than picked between.
134
+ composed, else `Testing.config.router = { supergraph: }` if you named one
135
+ there, else the committed dump when *that* is, else the dump your own client was
136
+ built from (`GraphWeaver.new("supergraph.graphql")` keeps its path). Two graphs
137
+ may name one supergraph; two naming different ones is refused rather than picked
138
+ between.
137
139
  Outside rspec, build it yourself:
138
140
 
139
141
  ```ruby
@@ -406,14 +408,12 @@ client distinct from the one the app uses to reach the gateway from outside:
406
408
 
407
409
  ```ruby
408
410
  # config/initializers/graph_weaver.rb — beside GraphWeaver.graph :reviews,
409
- # whose client is "Reviews::Schema"
410
- PLATFORM = GraphWeaver.new(ENV.fetch("PLATFORM_GRAPHQL_URL"))
411
-
411
+ # whose client is Reviews::Schema
412
412
  GraphWeaver.graph :platform do
413
413
  schema "app/graphql/supergraph.graphql"
414
414
  queries "app/graphql/platform/queries"
415
415
  output "app/graphql/platform/generated"
416
- client "PLATFORM"
416
+ client GraphWeaver.new(ENV.fetch("PLATFORM_GRAPHQL_URL"))
417
417
  namespace "Platform"
418
418
  end
419
419
  ```
@@ -38,7 +38,7 @@ module PersonQuery
38
38
  const :person, T.nilable(Person)
39
39
  end
40
40
 
41
- extend GraphWeaver::QueryModule # client / client= (see below)
41
+ extend GraphWeaver::QueryModule # client (see below)
42
42
  def self.execute(id:, client: nil) # -> GraphWeaver::Response[Result]
43
43
  def self.execute!(id:, client: nil) # -> Result, or raises QueryError
44
44
 
@@ -543,21 +543,20 @@ bodies. Every form above, and every error it raises, is a named example in
543
543
 
544
544
  A client is anything satisfying the [execute contract](transports.md) — a
545
545
  `GraphWeaver::Client`, a transport, a `Retry`, a live schema class, a fake.
546
- Resolution is per call (`client:`) per module baked constant
547
- `GraphWeaver.client`; the canonical list is in
548
- [transports](transports.md#client-resolution). Generate *without* a baked
549
- constant when you want modules to follow the app default (`GraphWeaver.client =`
550
- in an initializer). A baked one is no reason a module escapes
551
- [testing's `graphql:` tag](testing.md), which is exactly the instruction to
552
- replace the client generation chose; what the *example* says still wins.
553
-
554
- `client`/`client=` live in the gem (`GraphWeaver::QueryModule`, extended by every
555
- generated module). A baked constant is emitted as a private `DEFAULT_CLIENT`,
556
- resolved on first use so a module can load before the initializer that builds its
557
- client. A module generated from a
558
- [declared graph](getting_started.md#more-than-one-schema) also carries a private
559
- `GRAPH` naming it — so with two graphs, `graphql: :fake` fabricates each module's
560
- own schema instead of having to be told which one you meant, and it is the
546
+ A module knows which graph it belongs to, and the graph knows how to reach it:
547
+ resolution is per call (`client:`) a test mode's stand-in → the client its
548
+ [graph](getting_started.md#more-than-one-schema) names `GraphWeaver.client`;
549
+ the canonical list is in [transports](transports.md#client-resolution). There is
550
+ no setter — `MyQuery.client` reads back what the module would execute through,
551
+ and a [parsed](#dynamic-mode) module, which has no graph, runs
552
+ against whatever parsed it. A graph naming its own client is no reason a module
553
+ escapes [testing's `graphql:` tag](testing.md), which is exactly the instruction
554
+ to replace it; what the *example* says still wins.
555
+
556
+ `client` lives in the gem (`GraphWeaver::QueryModule`, extended by every
557
+ generated module). A generated file says nothing about transport — only a private
558
+ `GRAPH` naming its graph, which is also how `graphql: :fake` fabricates each
559
+ module's own schema with two graphs in play, and the
561
560
  `:graph` on every [instrumentation event](logging.md#the-payload) the module's
562
561
  `execute` produces.
563
562
 
@@ -690,8 +689,8 @@ you — a script that generates its own modules sets all four:
690
689
  2. `generated_paths` — where it writes, and where `load_generated!` reads. Point
691
690
  them at the same directory or generation is invisible.
692
691
  3. the call above, before the first `execute` — nothing else requires the files.
693
- 4. `GraphWeaver.client =` — a module generated without a baked
694
- [`client:`](#clients) has none of its own.
692
+ 4. `GraphWeaver.client =` — a module belonging to no declared graph has no
693
+ other [client](#clients) to reach for.
695
694
 
696
695
  Miss (3) and the script gets a `NameError` for its own module; miss (4) and it
697
696
  gets `PersonQuery: client must respond to #execute(query, variables:), got
@@ -723,9 +722,16 @@ form: parse and execute in one call, no module kept. In development
723
722
  `client.load_queries!` parses every query file into modules with the same names
724
723
  generation would use.
725
724
 
725
+ A parsed module **runs against whatever parsed it** — `client.parse(query)` and
726
+ `load_queries!` bind the client they came from, and `GraphWeaver.parse(client:)`
727
+ says it outright. That is a property of parsing, not a slot you can set later:
728
+ it generates no file, so it has no graph to read a client off, and a per-call
729
+ `client:` still wins over it.
730
+
726
731
  In an app with [more than one graph](getting_started.md#more-than-one-schema), a
727
732
  parsed module belongs to one of them — that is what a `graphql:` tag runs it
728
- against, the same thing generation bakes into a file. It is read off the schema
733
+ against and whose `client` it reaches for, the same thing generation writes into
734
+ a file. It is read off the schema
729
735
  you parsed against when a graph runs that class in-process; say it outright
730
736
  otherwise:
731
737
 
@@ -52,8 +52,8 @@ would drop the source url it records; delete it and re-run to re-introspect.
52
52
  What it wrote:
53
53
 
54
54
  - **`config/initializers/graph_weaver.rb`.** `GraphWeaver.client =` is the
55
- load-bearing line: generated modules without a baked transport resolve to it
56
- at execute time (the full
55
+ load-bearing line: generated modules belonging to no declared graph resolve to
56
+ it at execute time (the full
57
57
  [resolution order](transports.md#client-resolution)). Custom
58
58
  scalars/enums/type helpers register here too — the rake tasks bake them into
59
59
  generated source, so they have to run first ([scalars](scalars.md)):
@@ -428,7 +428,7 @@ plus `register_scalar`, `register_enum` and `extend_type`).
428
428
  | `schema` | a graphql-ruby schema class, a [`Client`](transports.md), a path to a dump, SDL, or a lambda returning one |
429
429
  | `queries` | a directory, or a list of them — the `.graphql` files this graph generates from |
430
430
  | `output` | one directory — where this graph's generated Ruby is written |
431
- | `client` | a constant, or its name what this graph's modules execute against |
431
+ | `client` | what this graph's modules execute against — a client, or the name of the constant holding one |
432
432
  | `namespace` | a constant, or its name — what every constant this graph generates nests under |
433
433
  | `types_module` | a constant name for the shared types module (default: `GraphQLTypes`, under `namespace`) |
434
434
 
@@ -447,14 +447,14 @@ GraphWeaver.graph :app do
447
447
  end
448
448
  ```
449
449
 
450
- **`client` names a constant, not a url** — its value is spelled into every module
451
- this graph generates and resolved the first time one of them executes, so it has
452
- to be something generated source can write down. Build the client wherever you
453
- like (`GITHUB = GraphWeaver.new(url, auth: …)`) and put the constant holding it
454
- here. A graph with no `client` generates modules that fall back to
455
- `GraphWeaver.client`, the app default. `schema "x"` sets and a bare `schema`
456
- reads back; there is no `schema = "x"` form, since the block is `instance_eval`'d
457
- and that would be a local variable that silently does nothing.
450
+ **`client` is where this graph's endpoint lives** — its modules say which graph
451
+ they belong to and nothing about transport, so they read it when they execute.
452
+ A graph with no `client` falls back to `GraphWeaver.client`, the app default.
453
+ Name the object (`client GraphWeaver.new(url, auth: …)`) or, when the constant
454
+ holding it is defined later than the graph block, its name (`client "GITHUB"`),
455
+ which is resolved on first use. `schema "x"` sets and a bare `schema` reads
456
+ back; there is no `schema = "x"` form, since the block is `instance_eval`'d and
457
+ that would be a local variable that silently does nothing.
458
458
 
459
459
  **`namespace` nests everything that graph generates** — `person.graphql` becomes
460
460
  `Billing::PersonQuery` ([naming](generated_modules.md#naming)). Constants are
data/docs/testing.md CHANGED
@@ -36,8 +36,8 @@ it "sends the caller tag", graphql: :wire do … end
36
36
  There is nothing else to set up: each mode works out what to run against per
37
37
  graph, and [refuses rather than guessing](#nothing-to-configure). The tag
38
38
  installs a stand-in per graph, and every generated module of that graph runs
39
- against it — including one generated *with* a baked `client:`, since that
40
- constant is exactly what the tag means to replace. `rspec --tag graphql:router`
39
+ against it — including one whose graph names a `client` of its own, since that
40
+ is exactly what the tag means to replace. `rspec --tag graphql:router`
41
41
  runs one mode's examples.
42
42
 
43
43
  **Every example has exactly one mode.** An untagged one takes
@@ -50,7 +50,8 @@ whatever the example did to it, so building your own client is a plain
50
50
  assignment: `before { GraphWeaver.client = GraphWeaver::Testing::Failure.throttled }`.
51
51
  Do both at once and **the tag wins**: the assignment reads back while the modules
52
52
  keep using the mode. Two things do step out of a tag — a per-call `client:`, and
53
- `DashboardQuery.client =` on the module itself
53
+ a module [parsed](generated_modules.md#dynamic-mode) from a client of its own,
54
+ which runs against that client
54
55
  ([client resolution](transports.md#client-resolution) has the full order).
55
56
 
56
57
  **The half `:fake` can't reach** is refusal. It fabricates a shape-correct
@@ -340,10 +341,10 @@ suite. The tag adds one stub per endpoint and takes each back after the example
340
341
  it never disables net connections on your behalf, and never resets stubs it
341
342
  didn't make.
342
343
 
343
- **Every endpoint an example can reach is served**, one per graph: the client each
344
- [declared graph](getting_started.md#more-than-one-schema) bakes into its modules
345
- with `client:`, or `GraphWeaver.client` for a graph that bakes none — so a
346
- billing module posts to billing's url and is answered by billing's schema. What
344
+ **Every endpoint an example can reach is served**, one per graph: the `client`
345
+ each [declared graph](getting_started.md#more-than-one-schema) names, or
346
+ `GraphWeaver.client` for a graph naming none — so a billing module posts to
347
+ billing's url and is answered by billing's schema. What
347
348
  sits behind each is **what that graph is**, in descending faithfulness: that
348
349
  graph's [router](#a-federated-graph--graphql-router) when it is in a composed
349
350
  supergraph, its [live schema class](#real-resolvers--graphql-in_process) when it
@@ -352,8 +353,8 @@ that is a pure *client* of someone else's API gets a schema-correct server
352
353
  without writing one. A graph with no schema at all is refused, **by `:wire`'s own
353
354
  name** — the one fallback the other tags have and this one can't use is your
354
355
  client's own schema, since reading it means introspecting the endpoint `:wire`
355
- has just stubbed. Commit a dump, or set `config.schema`. A graph whose baked
356
- client posts nowhere is refused by name too.
356
+ has just stubbed. Commit a dump, or set `config.schema`. A graph whose `client`
357
+ posts nowhere is refused by name too.
357
358
 
358
359
  **It says which, on the logger** — the choice is the one thing this tag makes for
359
360
  you, and it is invisible from inside the example. One line per endpoint, at
@@ -542,11 +543,13 @@ rather than guessing**:
542
543
  [maps subgraphs](federation.md#which-schema-serves-which-subgraph).
543
544
  - **`:router`** plans against the composed supergraph **that graph** names, else
544
545
  `config.router = { supergraph: … }`, else the committed dump when *that*
545
- carries `@join__*` markers which for a federated app is usually no config at
546
- all. A graph that is in no supergraph is refused **by name**, rather than
547
- planned against another graph's. A client can't stand in for one: a client's
548
- schema is the API schema the router serves, with the `@join__*` routing table
549
- stripped out. Subgraphs are derived either way.
546
+ carries `@join__*` markers, else the dump your own client was built from
547
+ (`GraphWeaver.new("supergraph.graphql")`) which for a federated app is
548
+ usually no config at all. A graph that is in no supergraph is refused **by
549
+ name**, rather than planned against another graph's. A client's *schema* can't
550
+ stand in for one it is the API schema the router serves, with the `@join__*`
551
+ routing table stripped out — but the file it was read from carries the table.
552
+ Subgraphs are derived either way.
550
553
 
551
554
  So configure only to override a derivation, or to tune fabricated values — in the
552
555
  same file as the require, since support files load in sorted order and one naming
data/docs/transports.md CHANGED
@@ -37,8 +37,8 @@ applied (exposed as `client.transport`), the schema introspected lazily, and
37
37
 
38
38
  They combine, so the whole thing is still one call —
39
39
  `GraphWeaver.new(url, transport: :faraday, retries: 2) { |conn| conn.response :logger }`.
40
- To wire generated modules that don't bake a client, make it the app's default:
41
- `GraphWeaver.client = github`.
40
+ To wire generated modules belonging to no declared graph, make it the app's
41
+ default: `GraphWeaver.client = github`.
42
42
 
43
43
  ## What fills the client slot
44
44
 
@@ -202,22 +202,29 @@ APM payload gets ([errors](errors.md)).
202
202
 
203
203
  ## Client resolution
204
204
 
205
+ A module knows which graph it belongs to, and the graph knows how to reach it.
205
206
  The canonical order — how a generated module finds its client (each slot takes a
206
207
  `Client` or any bare transport/fake):
207
208
 
208
209
  1. per call: `execute(client: some_client, ...)` — a kwarg like the variables, and
209
210
  a name no GraphQL variable is allowed to take
210
- 2. per module: `MyQuery.client = something`
211
- 3. a test mode's stand-in: under `graphql: :fake` / `:in_process` / `:router`,
211
+ 2. a test mode's stand-in: under `graphql: :fake` / `:in_process` / `:router`,
212
212
  built from the graph this module was generated from
213
- 4. baked constant: `Codegen.generate(..., client: "MyApi::CLIENT")` — the
214
- constant's *name*, not the object, because generated source spells it
215
- 5. the app default: `GraphWeaver.client=`
213
+ 3. the client its [graph](getting_started.md#more-than-one-schema) names
214
+ 4. the app default: `GraphWeaver.client=`
216
215
 
217
- The mode replaces what codegen baked in, not what your example said — 1 and 2
218
- still win. Nothing set anywhere raises, naming the two you'd usually reach for:
216
+ The mode replaces what the graph says, not what your example said — 1 still
217
+ wins. Nothing set anywhere raises, naming the two you'd usually reach for:
219
218
  `no client configured — set GraphWeaver.client= or pass a client`.
220
219
 
220
+ **A [parsed](generated_modules.md#dynamic-mode) module isn't in that list**: it generates no
221
+ file, so it has no graph, and it runs against whatever parsed it —
222
+ `client.parse(query)`, `load_queries!`, or `GraphWeaver.parse(client:)`. That
223
+ binding sits directly under the per-call `client:`, above even a test mode's
224
+ stand-in, and is the only one there is: `MyQuery.client` reads back what a
225
+ module would execute through, but there is no setter for it. A module's client
226
+ comes from its graph, its parser, or the call.
227
+
221
228
  ## Retries
222
229
 
223
230
  A url client retries when you give it a count; the rest of the options sit beside
data/docs/upgrading.md CHANGED
@@ -1,10 +1,11 @@
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.0](#upgrading-from-070) or from
5
- [0.6.1](#upgrading-from-061). Coming from 0.6.0 or older, the path is that
6
- version's own upgrade notes — read them at the tag they shipped under
7
- (`git show v0.7.1:docs/upgrading.md`), then this page from 0.6.1 down.
4
+ the one section that is yours: from [0.7.1](#upgrading-from-071), from
5
+ [0.7.0](#upgrading-from-070) or from [0.6.1](#upgrading-from-061). Coming from
6
+ 0.6.0 or older, the path is that version's own upgrade notes — read them at the
7
+ tag they shipped under (`git show v0.7.1:docs/upgrading.md`), then this page from
8
+ 0.6.1 down.
8
9
 
9
10
  ## Regenerate on every upgrade
10
11
 
@@ -22,6 +23,28 @@ That's the reminder working, not a false alarm. Generation is deterministic, so
22
23
  the diff is exactly what the new version emits differently and nothing else —
23
24
  worth reading rather than rubber-stamping.
24
25
 
26
+ ## Upgrading from 0.7.1
27
+
28
+ A patch release, and one change with a shape: a generated module no longer
29
+ carries a client of its own — the graph it belongs to resolves one. Read the
30
+ left column and skip what isn't yours; the [changelog](../CHANGELOG.md) says why
31
+ each one moved.
32
+
33
+ | applies if you… | what changed |
34
+ |---|---|
35
+ | call `generate!`, `verify_generated!`, `Codegen.new` or `Codegen.generate` yourself — `grep -rn "client:" config lib Rakefile` | the `client:` kwarg is gone, and the call raises `unknown keyword: :client`. Say it once on the graph (`client` in a `GraphWeaver.graph` block), or as the app default (`GraphWeaver.client =`) for modules in no declared graph. `Graph#client` answers that object now, not the name of the constant holding it |
36
+ | assign a generated module's client — `grep -rn "\.client *=" app config lib` (a hit on `GraphWeaver.client =` is the app default, and still fine) | the writer is private, and `MyQuery.client = …` raises `NoMethodError`. A module's client comes from its graph, from `client:` on the call, or — for a module you parsed — from `GraphWeaver.parse(client:)`, which `client.parse` and `load_queries!` already pass |
37
+
38
+ Then regenerate, and the gate:
39
+
40
+ ```sh
41
+ # generated files no longer carry a DEFAULT_CLIENT
42
+ rake graph_weaver:generate
43
+
44
+ # red while any checked-in file is still what 0.7.1 wrote
45
+ rake graph_weaver:verify
46
+ ```
47
+
25
48
  ## Upgrading from 0.7.0
26
49
 
27
50
  A patch release of fixes, and a typical app ticks none of these. Read the left
@@ -167,7 +190,7 @@ only one here that shows up in production rather than in your code.
167
190
  - **A `graphql:` tag reaches a module generated with `client:`.** The baked client
168
191
  used to sit above the slot a tag swaps, so a bound module ran against its real
169
192
  endpoint under `graphql: :fake`. **If a spec relied on that**, pass `client:` on
170
- the call, set `MyQuery.client =`, or tag the example `graphql: :live`.
193
+ the call, or tag the example `graphql: :live`.
171
194
  - **`config.context`, `config.schema` and `config.router` are suite setup.**
172
195
  Setting any of the three once an example is running refuses, naming the
173
196
  per-example helper (`graphql_context`, `graphql_fake(schema:)`,
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.0 — do not edit.
4
+ # Generated by GraphWeaver 0.7.2 — do not edit.
5
5
 
6
6
  module StarMutation
7
7
  extend T::Sig
@@ -79,11 +79,11 @@ module StarMutation
79
79
  end
80
80
  end
81
81
 
82
- # client / client= — see GraphWeaver::QueryModule
82
+ # client — see GraphWeaver::QueryModule
83
83
  extend GraphWeaver::QueryModule
84
84
 
85
- # the graph this module was generated from — what a test mode builds
86
- # its stand-in client from
85
+ # the graph this module was generated from — whose client it runs
86
+ # against, and what a test mode builds its stand-in from
87
87
  GRAPH = T.let(:github, Symbol)
88
88
  private_constant :GRAPH
89
89
 
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.0 — do not edit.
4
+ # Generated by GraphWeaver 0.7.2 — do not edit.
5
5
 
6
6
  require "time"
7
7
 
@@ -183,11 +183,11 @@ module StargazersQuery
183
183
  end
184
184
  end
185
185
 
186
- # client / client= — see GraphWeaver::QueryModule
186
+ # client — see GraphWeaver::QueryModule
187
187
  extend GraphWeaver::QueryModule
188
188
 
189
- # the graph this module was generated from — what a test mode builds
190
- # its stand-in client from
189
+ # the graph this module was generated from — whose client it runs
190
+ # against, and what a test mode builds its stand-in from
191
191
  GRAPH = T.let(:github, Symbol)
192
192
  private_constant :GRAPH
193
193
 
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- # Generated by GraphWeaver 0.7.0 — do not edit.
4
+ # Generated by GraphWeaver 0.7.2 — do not edit.
5
5
 
6
6
  module StarredQuery
7
7
  extend T::Sig
@@ -103,11 +103,11 @@ module StarredQuery
103
103
  end
104
104
  end
105
105
 
106
- # client / client= — see GraphWeaver::QueryModule
106
+ # client — see GraphWeaver::QueryModule
107
107
  extend GraphWeaver::QueryModule
108
108
 
109
- # the graph this module was generated from — what a test mode builds
110
- # its stand-in client from
109
+ # the graph this module was generated from — whose client it runs
110
+ # against, and what a test mode builds its stand-in from
111
111
  GRAPH = T.let(:github, Symbol)
112
112
  private_constant :GRAPH
113
113
 
@@ -77,6 +77,10 @@ class GraphWeaver::Client
77
77
  # a live schema class doubles as an in-process transport; a loaded
78
78
  # dump has no resolvers, so it is type information only
79
79
  @schema = source.is_a?(Module) ? source : GraphWeaver::SchemaLoader.load(source)
80
+ # A supergraph's routing table lives in the file, not in the loaded
81
+ # schema, so the path is the only thing that can name one later. Told
82
+ # from SDL by its extension, as SchemaLoader tells it.
83
+ @schema_source = source if !source.is_a?(Module) && GraphWeaver::SchemaLoader.dump_path?(source)
80
84
 
81
85
  if context && !(source.is_a?(Module) && transport.nil?)
82
86
  # nothing would ever read it — a dump has no resolvers, and an
@@ -122,6 +126,10 @@ class GraphWeaver::Client
122
126
  # schema-dump clients (type information only).
123
127
  attr_reader :transport
124
128
 
129
+ # The dump this client's schema was read from, or nil for a url, a schema
130
+ # class, or inline SDL. What a graph named by this client is named by.
131
+ attr_reader :schema_source
132
+
125
133
  # transport, when this client must be able to execute
126
134
  private def transport!
127
135
  transport or raise GraphWeaver::Error,
@@ -456,22 +456,16 @@ class GraphWeaver::Codegen
456
456
  end
457
457
 
458
458
  def emit_execute(out, variables)
459
- # client/client= carry no per-query types, so they live in the gem
460
- out << " # client / client= — see GraphWeaver::QueryModule"
459
+ # client carries no per-query types, so it lives in the gem
460
+ out << " # client — see GraphWeaver::QueryModule"
461
461
  out << " extend GraphWeaver::QueryModule"
462
462
  if @graph_name
463
463
  out << ""
464
- out << " # the graph this module was generated from — what a test mode builds"
465
- out << " # its stand-in client from"
464
+ out << " # the graph this module was generated from — whose client it runs"
465
+ out << " # against, and what a test mode builds its stand-in from"
466
466
  out << " GRAPH = T.let(#{@graph_name.inspect}, Symbol)"
467
- out << " private_constant :GRAPH"
468
- end
469
- if @client_const
470
- out << ""
471
- out << " # the baked default client, resolved on first use"
472
- out << " DEFAULT_CLIENT = T.let(-> { #{@client_const} }, T.proc.returns(T.untyped))"
473
467
  # QueryModule reads it with const_get, which privacy doesn't block
474
- out << " private_constant :DEFAULT_CLIENT"
468
+ out << " private_constant :GRAPH"
475
469
  end
476
470
  out << ""
477
471
 
@@ -59,15 +59,10 @@ class GraphWeaver::Codegen
59
59
 
60
60
  attr_reader :name
61
61
 
62
- # A client is anything responding to `execute(query, variables:)`
63
- # whose result `to_h`s into {"data" => ..., "errors" => ...} a
64
- # GraphWeaver::Client, a transport, a schema class, a fake.
65
- #
66
- # client: (a constant, or its name as a string) becomes the generated
67
- # module's baked default; when omitted, generated code falls back to
68
- # the app default (GraphWeaver.client=). graph_name: is the graph the
69
- # module belongs to, baked in so a test mode can build its stand-in from
70
- # the right schema. name: is the module the file
62
+ # graph_name: is the graph the module belongs to, baked in because it is
63
+ # the one thing a module can't be told at call time: it decides which
64
+ # client the module runs against (GraphWeaver::QueryModule) and which
65
+ # schema a test mode fabricates from. name: is the module the file
71
66
  # defines, defaulting to the operation's own name; default_name: is
72
67
  # parse's container-scoped fallback (file generation stays strict — a
73
68
  # checked-in file deserves a deliberate name). types_namespace: is the shared-types workflow (see
@@ -78,7 +73,7 @@ class GraphWeaver::Codegen
78
73
  # whole-union field spread as one of them resolves to a canonical type in the
79
74
  # shared module (see used_union_names). path: is the file the query was read
80
75
  # from, named alongside line and column in validation errors.
81
- def initialize(schema:, query:, name: nil, client: nil, default_name: nil,
76
+ def initialize(schema:, query:, name: nil, default_name: nil,
82
77
  types_namespace: nil, hoistable_unions: nil, path: nil, module_name: nil,
83
78
  graph_name: nil, registry: GraphWeaver::Codegen.registry)
84
79
  renamed!(module_name)
@@ -96,28 +91,10 @@ class GraphWeaver::Codegen
96
91
  @used_unions = []
97
92
  # scalars this generation had no registration for (see report_untyped_scalars)
98
93
  @untyped_scalars = []
99
- @client_const = CLIENT_CONST.call(client)
100
- # the graph this module belongs to, baked in beside the client: a test
101
- # mode builds its stand-in from the module's own schema, and only the
102
- # module can say whose that is (GraphWeaver::Internal::TestClients).
103
- # A Symbol, as GraphWeaver.graph makes it — the name is the identity.
94
+ # the graph this module belongs to: its client and, under a test mode,
95
+ # its stand-in are both read off it, and only the module can say whose
96
+ # it is. A Symbol, as GraphWeaver.graph makes it the name is the identity.
104
97
  @graph_name = graph_name&.to_sym
105
-
106
- if client && @client_const.nil?
107
- # a live object can't be spelled in generated source — parse can
108
- # set one via the module's writer, but file generation cannot
109
- raise ArgumentError, "client: must be a named constant or String (got #{client.inspect}) — " \
110
- "put the object in a constant and name it, client: \"MyApi::CLIENT\"; pass live objects to parse"
111
- end
112
- # The String is written into the module verbatim, so anything that isn't a
113
- # constant path emits source that doesn't parse. A url is the way to get
114
- # here — it is where the endpoint is spelled everywhere else — so the fix
115
- # names the value that was passed.
116
- if @client_const && !@client_const.match?(CONSTANT_NAME)
117
- raise ArgumentError, "client: #{@client_const.inspect} isn't a constant — generated source " \
118
- "spells this name, so it has to be one: CLIENT = GraphWeaver.new(#{@client_const.inspect}), " \
119
- "then client \"CLIENT\""
120
- end
121
98
  end
122
99
 
123
100
  # 0.5 spelled it module_name:, in two of the three doors. One knob, one
@@ -129,33 +106,19 @@ class GraphWeaver::Codegen
129
106
  end
130
107
  private :renamed!
131
108
 
132
- # The constant name a client can be referenced by in generated
133
- # source — nil when it can't be (live objects, anonymous modules).
134
- # A lambda rather than a method: both `parse` and `initialize` need it,
135
- # from the class and from an instance.
136
- CLIENT_CONST = lambda do |client|
137
- case client
138
- when String then client
139
- when Module then client.name
140
- end
141
- end
142
- private_constant :CLIENT_CONST
143
-
144
109
  # one-step shorthand
145
- def self.generate(schema:, query:, name: nil, client: nil, path: nil, module_name: nil)
146
- new(schema:, query:, name:, client:, path:, module_name:).generate
110
+ def self.generate(schema:, query:, name: nil, path: nil, module_name: nil)
111
+ new(schema:, query:, name:, path:, module_name:).generate
147
112
  end
148
113
 
149
114
  # Development convenience: generate + eval in one step, no build
150
115
  # artifact or checked-in file. Same runtime semantics as the generated
151
116
  # file, but invisible to srb tc — use the build step for static typing.
152
- # Evaluates into an anonymous container, so no global constants leak;
153
- # client: additionally accepts a live object (set via .client=).
117
+ # Evaluates into an anonymous container, so no global constants leak.
118
+ # client: is the client the parsed module runs against — it has no graph to
119
+ # read one off — and a per-call `client:` still wins over it.
154
120
  def self.parse(schema:, query:, name: nil, client: nil, path: nil, module_name: nil, graph_name: nil)
155
- client_const = CLIENT_CONST.call(client)
156
-
157
- codegen = new(schema:, query:, name:, client: client_const, path:, module_name:, graph_name:,
158
- default_name: "Query")
121
+ codegen = new(schema:, query:, name:, path:, module_name:, graph_name:, default_name: "Query")
159
122
  source = codegen.generate
160
123
 
161
124
  container = Module.new
@@ -167,9 +130,10 @@ class GraphWeaver::Codegen
167
130
  container.module_eval(source, "(graph_weaver)", 1)
168
131
  mod = container.const_get(codegen.name)
169
132
  GraphWeaver::Internal::Log.log(:debug) { "parsed #{codegen.name} (dynamic module, #{source.bytesize} bytes)" }
170
- # live objects (or anonymous modules) can't be referenced from
171
- # generated source set them via the module's writer instead
172
- mod.client = client if client && client_const.nil?
133
+ # a parsed module generates no file, so it has no graph to read a client
134
+ # offclient: binds one, whatever kind of object it is. The writer is
135
+ # private: parsing is the only thing that may bind one.
136
+ mod.send(:client=, client) if client
173
137
  mod
174
138
  end
175
139
 
@@ -65,7 +65,24 @@ module GraphWeaver
65
65
 
66
66
  def queries = @queries || GraphWeaver.queries_paths
67
67
  def output = @output || GraphWeaver.generated_paths.first
68
- def client = @client
68
+
69
+ # The client this graph's modules call: the object `client` named, or the
70
+ # constant its name spells. nil when the graph names none — its modules
71
+ # go to GraphWeaver.client, like every other module.
72
+ #
73
+ # Resolved here, at call time, rather than spelled into generated source:
74
+ # renaming the constant is then an initializer edit and not a regeneration
75
+ # of every module, and a graph can name a live object.
76
+ def client
77
+ return @client unless @client.is_a?(String)
78
+
79
+ Object.const_get(@client)
80
+ rescue NameError
81
+ raise GraphWeaver::Error, "the client#{described} names #{@client.inspect} and nothing " \
82
+ "defines that constant, so its modules have no server to reach. Define it where the graph " \
83
+ "block can see it (config/initializers), or name the object itself: client " \
84
+ "GraphWeaver.new(\"https://api.example.com/graphql\")"
85
+ end
69
86
 
70
87
  # Every constant this graph generates lives under `namespace:` — the query
71
88
  # modules and the shared types module alike. Two schemas that each have a
@@ -95,7 +112,7 @@ module GraphWeaver
95
112
 
96
113
  # The dump this graph's schema was named by, when it was named by a file —
97
114
  # what a validation error's subgraph branding is read off. nil for a live
98
- # class, a Client, or inline SDL.
115
+ # class, inline SDL, or a Client built from any of those.
99
116
  def dump_path
100
117
  path = named_dump_path
101
118
  path if path && File.exist?(path)
@@ -109,6 +126,9 @@ module GraphWeaver
109
126
  return GraphWeaver::SchemaLoader.locate_path unless @schema
110
127
 
111
128
  source = named_source
129
+ # a client built from a dump names that dump: the file is where a
130
+ # supergraph's routing table is, and the loaded schema is not
131
+ source = source.schema_source if source.respond_to?(:schema_source) && source.schema_source
112
132
  path = source.respond_to?(:to_path) ? source.to_path : source
113
133
  path if path.is_a?(String) && GraphWeaver::SchemaLoader.dump_path?(path)
114
134
  end
@@ -133,26 +153,16 @@ module GraphWeaver
133
153
  url || live_schema
134
154
  end
135
155
 
136
- # The url this graph's modules post to, or nil. `client:` holds a constant
137
- # or its name codegen spells it into source so a name is resolved here
138
- # the way the generated DEFAULT_CLIENT lambda resolves it; a graph baking
139
- # none posts to the app default, which is where its modules go too.
156
+ # The url this graph's modules post to, or nil the graph's own client,
157
+ # else the app default, which is where its modules go too. What
158
+ # `schema:refresh` bootstraps a missing dump from, and what `rake
159
+ # graph_weaver:graphs` reports; a client with no url (a schema class
160
+ # running in-process) has none to report.
140
161
  def client_url
141
- client = @client.is_a?(String) ? resolve_client! : @client
142
- client ||= GraphWeaver.client
143
- target = (client.transport if client.respond_to?(:transport)) || client
162
+ target = client || GraphWeaver.client
163
+ target = (target.transport if target.respond_to?(:transport)) || target
144
164
  target.url if target.respond_to?(:url)
145
165
  end
146
- private :client_url
147
-
148
- def resolve_client!
149
- Object.const_get(@client)
150
- rescue NameError
151
- raise GraphWeaver::Error, "graph #{name.inspect} bakes client #{@client.inspect} into its " \
152
- "modules and nothing defines that constant, so there is no endpoint to introspect " \
153
- "#{named_dump_path} from"
154
- end
155
- private :resolve_client!
156
166
 
157
167
  # The composed supergraph this graph plans against, or nil — the dump it
158
168
  # names (for the default graph, the conventional one) when that dump
@@ -222,13 +232,12 @@ module GraphWeaver
222
232
  # The same three calls an app already writes at the top level, scoped here
223
233
  # to this graph alone.
224
234
  REGISTRATIONS = %i[register_scalar register_enum extend_type].freeze
225
- # These three end up spelled in generated source, so each takes the
226
- # constant or its name and stores the name.
227
- CONSTANT_SETTINGS = %i[client namespace types_module].freeze
228
- # …and these two are spelled as a `module` DEFINITION rather than a
229
- # reference, which is why a root anchor is refused on them below.
235
+ # These two are spelled in generated source, as a `module` DEFINITION,
236
+ # so each takes the constant or its name and stores the name — and a
237
+ # root anchor is refused on them below. `client` isn't spelled anywhere:
238
+ # the graph resolves it at call time, so it takes the object.
230
239
  MODULE_SETTINGS = %i[namespace types_module].freeze
231
- private_constant :CONSTANT_SETTINGS, :MODULE_SETTINGS
240
+ private_constant :MODULE_SETTINGS
232
241
 
233
242
  attr_reader :settings, :registrations
234
243
 
@@ -283,17 +292,18 @@ module GraphWeaver
283
292
  def respond_to_missing?(name, _private = false) = false
284
293
 
285
294
  # A Module where a constant's name goes says the same thing, and is what
286
- # `client Billing::CLIENT` reads like. Anything else passes through:
287
- # a schema is a path, SDL, a class, a Client, or a callable.
295
+ # `namespace Billing` reads like. Anything else passes through: a schema
296
+ # is a path, SDL, a class, a Client, or a callable, and a client is
297
+ # whatever object answers #execute.
288
298
  # On the singleton so the define_method setters above can reach it — srb
289
299
  # reads a define_method block's self as the class.
290
300
  def self.constant_name(setting, value)
291
- return value unless CONSTANT_SETTINGS.include?(setting)
301
+ return value unless MODULE_SETTINGS.include?(setting)
292
302
 
293
303
  # Generated modules are defined at the top level, where a root anchor
294
304
  # says nothing — and `module ::A::B` is not a name const_get can spell,
295
305
  # so it used to surface as a verdict on the .graphql file's name.
296
- if MODULE_SETTINGS.include?(setting) && value.is_a?(String) && value.start_with?("::")
306
+ if value.is_a?(String) && value.start_with?("::")
297
307
  raise ArgumentError, "#{setting} #{value.inspect}: drop the leading `::` — #{setting} " \
298
308
  "names a module generated source defines, and it defines it at the top level either way"
299
309
  end
@@ -8,13 +8,12 @@ module GraphWeaver
8
8
  # a `graphql_*` helper writes to.
9
9
  #
10
10
  # A tag used to work by swapping GraphWeaver.client, which is the LAST
11
- # place a module looks: one generated with `client:` reads its baked
12
- # DEFAULT_CLIENT first and never got there, so the tag quietly didn't
13
- # apply. The mode installs itself here instead, and QueryModule asks
14
- # before it reads that constant — so a tag reaches every module the
15
- # example runs, bound or not.
11
+ # place a module looks: one whose graph names a client of its own reads
12
+ # that first and never got there, so the tag quietly didn't apply. The
13
+ # mode installs itself here instead, and QueryModule asks before it reads
14
+ # the graph — so a tag reaches every module the example runs.
16
15
  #
17
- # Keyed by the graph a module was generated from (its baked GRAPH), since
16
+ # Keyed by the graph a module was generated from (its GRAPH), since
18
17
  # the honest answer varies: :fake for a billing module has to fabricate
19
18
  # billing's shapes, not the other schema's. A helper names its graphs the
20
19
  # same way and lands in the same table, so what an example says applies to
@@ -254,11 +253,8 @@ module GraphWeaver
254
253
  # generated before its graph was declared, or by an older release —
255
254
  # and guessing would fake one schema's shapes at another's module.
256
255
  def graph_for!(mod)
257
- graphs = GraphWeaver.graphs
258
- return graphs.first if graphs.one?
259
-
260
256
  name = mod.const_defined?(:GRAPH, false) ? mod.const_get(:GRAPH) : nil
261
- found = graphs.find { |graph| graph.name == name }
257
+ found = Util.graph_named(name)
262
258
  return found if found
263
259
 
264
260
  # Two doors produce a module, so the fix has two spellings: a file
@@ -268,7 +264,7 @@ module GraphWeaver
268
264
  raise GraphWeaver::Error, "#{mod} doesn't say which of this app's graphs " \
269
265
  "(#{declared_names}) it was generated from, so #{@mode.inspect} has nothing to run " \
270
266
  "it against — regenerate it (rake graph_weaver:generate), or, if it came from " \
271
- "GraphWeaver.parse, say which there (graph: #{graphs.first.name.inspect})."
267
+ "GraphWeaver.parse, say which there (graph: #{GraphWeaver.graphs.first.name.inspect})."
272
268
  end
273
269
  end
274
270
  end
@@ -141,6 +141,21 @@ module GraphWeaver
141
141
  schema && GraphWeaver.graphs.find { |candidate| candidate.live_schema.equal?(schema) }
142
142
  end
143
143
 
144
+ # The declared graph `name` names, or nil — how a generated module
145
+ # finds the graph whose client it runs against, and whose schema a
146
+ # test mode fabricates from.
147
+ #
148
+ # One graph in an app is the answer whatever a module calls it: a
149
+ # module generated before its graph was named, or by an older release,
150
+ # still belongs to the only graph there is. With several, guessing
151
+ # would send one schema's query to another's endpoint.
152
+ def graph_named(name)
153
+ graphs = GraphWeaver.graphs
154
+ return graphs.first if graphs.one?
155
+
156
+ graphs.find { |graph| graph.name == name }
157
+ end
158
+
144
159
  # Where generated modules are READ from: the configured patterns, plus
145
160
  # any graph writing somewhere they don't already cover. generated_paths'
146
161
  # default glob (app/graphql/*/generated) covers the conventional layout,
@@ -12,21 +12,21 @@ module GraphWeaver
12
12
  # Runtime for generated query modules: the client plumbing, which is the
13
13
  # one part of a generated module that carries no per-query type
14
14
  # information — every module's copy was identical. `extend
15
- # GraphWeaver::QueryModule` supplies `client`/`client=`; execute and
16
- # from_response stay generated, since their sigs are the query's types and
17
- # those are the point.
15
+ # GraphWeaver::QueryModule` supplies `client`; execute and from_response
16
+ # stay generated, since their sigs are the query's types and those are the
17
+ # point.
18
18
  #
19
- # Resolution order, per the docs: per call → per module (`MyQuery.client =`)
20
- # → a test mode's stand-in (Internal::TestClients) → the module's baked
21
- # DEFAULT_CLIENT → `GraphWeaver.client`.
19
+ # Resolution order, per the docs: per call → a test mode's stand-in
20
+ # (Internal::TestClients) → the client the module's graph names →
21
+ # `GraphWeaver.client`. A module has no fifth slot you can set: a parsed
22
+ # module runs against whatever parsed it (GraphWeaver.parse(client:)),
23
+ # which is a property of parsing rather than a per-module override.
22
24
  module QueryModule
23
25
  extend T::Sig
24
26
 
25
- sig { params(client: T.untyped).void }
26
- attr_writer :client
27
-
28
- # the default client (a GraphWeaver::Client or any transport) for
29
- # execute: per-module override, else the baked default, else the app one
27
+ # What this module would execute through, right now — the client a parse
28
+ # bound it to, else the order above. A diagnostic, and what `execute`
29
+ # reads when the call names none.
30
30
  sig { returns(T.untyped) }
31
31
  def client
32
32
  @client || default_client
@@ -34,6 +34,13 @@ module GraphWeaver
34
34
 
35
35
  private
36
36
 
37
+ # Bound by GraphWeaver.parse, which is the only caller: a parsed module
38
+ # generates no file, so it has no graph to read a client off. Private
39
+ # because a generated module's client comes from its graph — one way to
40
+ # say a thing.
41
+ sig { params(client: T.untyped).void }
42
+ attr_writer :client
43
+
37
44
  # The one call a generated `execute` makes: resolve the client, run this
38
45
  # module's own operation, hand the raw response back for from_response to
39
46
  # wrap. Here rather than emitted, so what has to BRACKET a request — the
@@ -55,9 +62,7 @@ module GraphWeaver
55
62
  mod = T.unsafe(self)
56
63
  # the graph codegen baked in, never one inferred from the client — a
57
64
  # wrong label on a request is worse than no label
58
- graph = mod.const_defined?(:GRAPH, false) ? mod.const_get(:GRAPH) : nil
59
-
60
- GraphWeaver::Internal::Log.with_graph(graph) do
65
+ GraphWeaver::Internal::Log.with_graph(graph_name) do
61
66
  client_for(client).execute(mod.const_get(:QUERY), variables:,
62
67
  operation_name: mod.const_get(:OPERATION_NAME))
63
68
  end
@@ -79,20 +84,28 @@ module GraphWeaver
79
84
  "#{self}: client must respond to #execute(query, variables:), got #{target.class}"
80
85
  end
81
86
 
82
- # Codegen's `client:` constant, emitted as a DEFAULT_CLIENT lambda so the
83
- # constant it names is resolved on first use rather than at load — a
84
- # generated file may load before the initializer that builds the client.
87
+ # A module knows which graph it belongs to, and the graph knows how to
88
+ # reach it: the client that graph names, else the app default. Read at
89
+ # call time, so renaming the constant a graph names is an initializer
90
+ # edit rather than a regeneration of every module.
85
91
  #
86
- # A test mode stands in for it: what codegen baked in is exactly what a
87
- # `graphql:` tag means to replace, so a bound module is covered by the
88
- # tag like every other one.
92
+ # A test mode stands in ahead of it: the graph's client is exactly what a
93
+ # `graphql:` tag means to replace, so a tagged example reaches a module
94
+ # whose graph names a client like every other one.
89
95
  sig { returns(T.untyped) }
90
96
  def default_client
91
- mod = T.unsafe(self)
92
- stand_in = GraphWeaver::Internal::TestClients.for(mod)
97
+ stand_in = GraphWeaver::Internal::TestClients.for(T.unsafe(self))
93
98
  return stand_in if stand_in
94
99
 
95
- mod.const_defined?(:DEFAULT_CLIENT, false) ? mod.const_get(:DEFAULT_CLIENT).call : GraphWeaver.client!
100
+ GraphWeaver::Internal::Util.graph_named(graph_name)&.client || GraphWeaver.client!
101
+ end
102
+
103
+ # The graph codegen baked in, by name — nil for a module generated before
104
+ # graphs existed, or by a GraphWeaver.parse that named none.
105
+ sig { returns(T.untyped) }
106
+ def graph_name
107
+ mod = T.unsafe(self)
108
+ mod.const_defined?(:GRAPH, false) ? mod.const_get(:GRAPH) : nil
96
109
  end
97
110
  end
98
111
  end
@@ -255,13 +255,12 @@ module GraphWeaver
255
255
  def self.unserve!(stub) = WebMock::StubRegistry.instance.request_stubs.delete(stub)
256
256
 
257
257
  # Every endpoint an example's modules can post to, each with the graph
258
- # whose resolvers belong behind it: the client each graph bakes into its
259
- # modules, or GraphWeaver.client for a graph baking none. One graph per
260
- # endpoint — an app whose graphs all bake clients needs no app default
261
- # at all.
258
+ # whose resolvers belong behind it: the client each graph names, or
259
+ # GraphWeaver.client for a graph naming none. One graph per endpoint —
260
+ # an app whose graphs all name clients needs no app default at all.
262
261
  def self.wire_targets
263
262
  targets = GraphWeaver.graphs.filter_map do |graph|
264
- client = baked_client(graph) || GraphWeaver.client
263
+ client = graph.client || GraphWeaver.client
265
264
  [endpoint!(client, graph), graph] if client
266
265
  end
267
266
  refuse_shared_endpoint!(targets)
@@ -283,24 +282,10 @@ module GraphWeaver
283
282
  raise GraphWeaver::Error, "#{TAG}: :wire serves one schema at each endpoint, and graphs " \
284
283
  "#{names} post to the same one (#{url}) — whichever were served there would answer the " \
285
284
  "others' queries, as fields its schema doesn't define. Give each graph a client of its " \
286
- "own (client: in the graph block), or tag the example #{TAG}: :in_process or " \
285
+ "own (`client` in the graph block), or tag the example #{TAG}: :in_process or " \
287
286
  "#{TAG}: :router, which run above the wire."
288
287
  end
289
288
 
290
- # The client a graph's generated modules call. `client:` holds a
291
- # constant or its name — codegen writes it into source — so a name is
292
- # resolved here the way the generated DEFAULT_CLIENT lambda resolves it.
293
- def self.baked_client(graph)
294
- named = graph.client
295
- return named unless named.is_a?(String)
296
-
297
- Object.const_get(named)
298
- rescue NameError
299
- raise GraphWeaver::Error, "#{TAG}: graph #{graph.name.inspect} bakes client: " \
300
- "#{named.inspect} into its modules and nothing defines that constant, so :wire can't " \
301
- "find the endpoint they post to."
302
- end
303
-
304
289
  # The endpoint a client posts to: a transport, a Retry around one, or a
305
290
  # Client that built one. `graph` says whose client it is, when it isn't
306
291
  # the app's own.
@@ -316,12 +301,16 @@ module GraphWeaver
316
301
  "#{TAG}: :in_process or #{TAG}: :router — they run above the wire."
317
302
  end
318
303
 
319
- # which client posts to nothing — the app's, or one graph's
304
+ # which client posts to nothing — the app's, or one graph's. A schema
305
+ # class in a client slot is named by ITS name: `client.class` is the
306
+ # word "Class", which names nothing anyone wrote.
320
307
  def self.whose_client(client, graph)
321
308
  return "GraphWeaver.client isn't set" unless client
322
- return "GraphWeaver.client is #{client.class}, which posts to none" unless graph&.name
323
309
 
324
- "graph #{graph.name.inspect} bakes client: #{client.class}, which posts to none"
310
+ named = client.is_a?(Module) ? client : client.class
311
+ return "GraphWeaver.client is #{named}, which posts to none" unless graph&.name
312
+
313
+ "graph #{graph.name.inspect} names client #{named}, which posts to none"
325
314
  end
326
315
 
327
316
  def self.webmock!
@@ -353,7 +342,7 @@ module GraphWeaver
353
342
  !WebMock::HttpLibAdapters::NetHttpAdapter::OriginalNetHTTP.equal?(Net::HTTP)
354
343
  end
355
344
 
356
- private_class_method :wire_targets, :refuse_shared_endpoint!, :baked_client, :whose_client,
345
+ private_class_method :wire_targets, :refuse_shared_endpoint!, :whose_client,
357
346
  :webmock!, :webmock_enabled?, :disclose!, :served, :unnamed_schemas, :loaded_schemas
358
347
 
359
348
  # Included into every example group, so graphql_context is there
@@ -273,9 +273,17 @@ namespace :graph_weaver do
273
273
  puts "#{name} #{Array(graph.queries).join(", ")} -> #{GraphWeaver::Internal::Util.relative(graph.output)}"
274
274
  puts " namespace: #{graph.namespace}" if graph.namespace
275
275
  # which server a graph's modules call — the one thing this task couldn't
276
- # say. A graph that bakes none falls back to GraphWeaver.client, which is
276
+ # say. A graph that names none falls back to GraphWeaver.client, which is
277
277
  # an app-wide setting and not this task's subject.
278
- puts " client: #{graph.client}" if graph.client
278
+ #
279
+ # Reported rather than raised: this is the task you run to find out why a
280
+ # graph is wrong, so a client whose constant is missing is the answer,
281
+ # not a reason to stop listing the others.
282
+ begin
283
+ puts " client: #{graph.client_url || graph.client}" if graph.client
284
+ rescue GraphWeaver::Error => e
285
+ puts " client: #{e.message}"
286
+ end
279
287
  # a registration is scoped to one graph, and nothing else says which
280
288
  GraphWeaver::Internal::Tasks.registrations(graph).each { |line| puts line }
281
289
  end
@@ -221,9 +221,10 @@ module GraphWeaver
221
221
  # warn line as it is constructed, and a predicate that raised to say
222
222
  # "no" put a refusal that never happened in the log of every :wire
223
223
  # example. The one that graph names, else config.router[:supergraph],
224
- # else the conventional dump when that's what it is. A client can't
225
- # supply one its schema is the API schema a router serves, with the
226
- # @join__* routing table stripped out.
224
+ # else the conventional dump when that's what it is, else the dump the
225
+ # app's own client was built from. A client's *schema* can't supply one
226
+ # — it is the API schema a router serves, with the @join__* routing
227
+ # table stripped out — but the file behind it carries the table.
227
228
  private def supergraph_for(graph)
228
229
  # named_schema?, so a graph that declared no schema of its own falls
229
230
  # through to config.router rather than past it to the conventional dump
@@ -232,7 +233,14 @@ module GraphWeaver
232
233
  return @router[:supergraph] if @router&.key?(:supergraph)
233
234
 
234
235
  path = GraphWeaver::SchemaLoader.locate_path
235
- path if path && GraphWeaver::Internal::Util.composed?(path)
236
+ return path if path && GraphWeaver::Internal::Util.composed?(path)
237
+
238
+ # Last, because codegen for the default graph reads the conventional
239
+ # dump, and the router must plan against what the modules were typed
240
+ # against.
241
+ client = GraphWeaver.client
242
+ source = client.schema_source if client.respond_to?(:schema_source)
243
+ source if source && GraphWeaver::Internal::Util.composed?(source)
236
244
  end
237
245
 
238
246
  # what to do about it, which differs by who asked: a graph in no
@@ -1,3 +1,3 @@
1
1
  module GraphWeaver
2
- VERSION = "0.7.1"
2
+ VERSION = "0.7.2"
3
3
  end
data/lib/graph_weaver.rb CHANGED
@@ -65,8 +65,8 @@ module GraphWeaver
65
65
  #
66
66
  # Anything satisfying the execute contract — a Client, a schema class,
67
67
  # a transport, a fake (testing's graphql: tag swaps one in per
68
- # example). Generated modules resolve per call -> per module
69
- # (MyQuery.client=) -> baked constant -> here.
68
+ # example). Generated modules resolve per call -> a test mode's stand-in
69
+ # -> the client their graph names -> here.
70
70
  attr_accessor :client
71
71
 
72
72
  # the default client, when one is required
@@ -389,7 +389,7 @@ module GraphWeaver
389
389
  # (see #changed_files). Generated files the plan no longer produces are deleted
390
390
  # (see #orphaned), so renaming or dropping a .graphql leaves nothing
391
391
  # behind. Pair with a freshness spec (docs/generated_modules.md).
392
- def generate!(schema: nil, queries: nil, output: nil, client: nil, types_module: nil)
392
+ def generate!(schema: nil, queries: nil, output: nil, types_module: nil)
393
393
  @changed_files = []
394
394
  @unmatched_registrations = []
395
395
  @untyped_scalars_by_graph = {}
@@ -399,7 +399,7 @@ module GraphWeaver
399
399
  # tree exactly as it was — the railtie's watch mode regenerates on a
400
400
  # request and promises a failed save changes nothing, and that promise
401
401
  # was true within a graph and false across them.
402
- planned = graphs_for(schema:, queries:, output:, client:, types_module:).map do |graph|
402
+ planned = graphs_for(schema:, queries:, output:, types_module:).map do |graph|
403
403
  if Internal::Util.query_files(graph.queries).empty?
404
404
  # a brand-new app legitimately has none; a mistyped queries_paths looks
405
405
  # exactly the same, and prints nothing either way
@@ -488,11 +488,11 @@ module GraphWeaver
488
488
  # it "generated queries are current" do
489
489
  # GraphWeaver.verify_generated!
490
490
  # end
491
- def verify_generated!(schema: nil, queries: nil, output: nil, client: nil, types_module: nil)
491
+ def verify_generated!(schema: nil, queries: nil, output: nil, types_module: nil)
492
492
  @unmatched_registrations = []
493
493
  @untyped_scalars_by_graph = {}
494
494
  seen = new_seen
495
- graphs = graphs_for(schema:, queries:, output:, client:, types_module:)
495
+ graphs = graphs_for(schema:, queries:, output:, types_module:)
496
496
 
497
497
  # The dump is checked in too, and everything below reads it — so a
498
498
  # stale one is answered before staleness downstream of it, because
@@ -860,7 +860,6 @@ module GraphWeaver
860
860
  schema:,
861
861
  query: Codegen.inline_fragments(source, shared, path),
862
862
  name:,
863
- client: graph.client,
864
863
  graph_name: graph.name,
865
864
  types_namespace: graph.types_module,
866
865
  hoistable_unions: Codegen.shared_fragment_spreads(source, shared, path),
@@ -1082,8 +1081,10 @@ module GraphWeaver
1082
1081
  # name derived from the file name and the operation — see #module_name) or
1083
1082
  # a raw query string (name derived from the operation name, falling back to
1084
1083
  # "Query" for anonymous operations — collisions are impossible since each
1085
- # parse gets its own container). Pass name: to override, client: to bake
1086
- # the module's default client/transport.
1084
+ # parse gets its own container). Pass name: to override, client: to say
1085
+ # what the module runs against — a parsed module generates no file, so it
1086
+ # has no graph to read one off, and this is the only time one is bound
1087
+ # (a per-call `client:` still wins).
1087
1088
  #
1088
1089
  # graph: names the graph this module belongs to, which is what a test mode
1089
1090
  # runs it against in an app with more than one — the same thing generation
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.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
@@ -363,7 +363,7 @@ licenses:
363
363
  - MIT
364
364
  metadata:
365
365
  bug_tracker_uri: https://github.com/dpep/graph_weaver/issues
366
- changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.1/CHANGELOG.md
366
+ changelog_uri: https://github.com/dpep/graph_weaver/blob/v0.7.2/CHANGELOG.md
367
367
  documentation_uri: https://github.com/dpep/graph_weaver/tree/main/docs
368
368
  rubygems_mfa_required: 'true'
369
369
  source_code_uri: https://github.com/dpep/graph_weaver