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 +4 -4
- data/Gemfile.lock +2 -2
- data/docs/federation.md +8 -8
- data/docs/generated_modules.md +25 -19
- data/docs/getting_started.md +11 -11
- data/docs/testing.md +17 -14
- data/docs/transports.md +16 -9
- data/docs/upgrading.md +28 -5
- data/examples/github/generated/star_mutation.rb +4 -4
- data/examples/github/generated/stargazers_query.rb +4 -4
- data/examples/github/generated/starred_query.rb +4 -4
- data/lib/graph_weaver/client.rb +8 -0
- data/lib/graph_weaver/codegen/emit.rb +5 -11
- data/lib/graph_weaver/codegen.rb +18 -54
- data/lib/graph_weaver/graph.rb +39 -29
- data/lib/graph_weaver/internal/test_clients.rb +7 -11
- data/lib/graph_weaver/internal.rb +15 -0
- data/lib/graph_weaver/query_module.rb +36 -23
- data/lib/graph_weaver/rspec.rb +13 -24
- data/lib/graph_weaver/tasks.rb +10 -2
- data/lib/graph_weaver/testing.rb +12 -4
- data/lib/graph_weaver/version.rb +1 -1
- data/lib/graph_weaver.rb +10 -9
- 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: 2cb0372c9f8505bec490fbb13344cbd586da68cb4932cbce0f86de2467d0eaf4
|
|
4
|
+
data.tar.gz: 6bc17449eea717c8cb145c159a2395f61f484a2f585c016ee31521f979f1a6d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
136
|
-
|
|
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
|
|
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 "
|
|
416
|
+
client GraphWeaver.new(ENV.fetch("PLATFORM_GRAPHQL_URL"))
|
|
417
417
|
namespace "Platform"
|
|
418
418
|
end
|
|
419
419
|
```
|
data/docs/generated_modules.md
CHANGED
|
@@ -38,7 +38,7 @@ module PersonQuery
|
|
|
38
38
|
const :person, T.nilable(Person)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
extend GraphWeaver::QueryModule # client
|
|
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
|
-
|
|
547
|
-
`
|
|
548
|
-
[
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
|
694
|
-
[
|
|
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
|
|
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
|
|
data/docs/getting_started.md
CHANGED
|
@@ -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
|
|
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
|
|
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`
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
344
|
-
[declared graph](getting_started.md#more-than-one-schema)
|
|
345
|
-
|
|
346
|
-
billing
|
|
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
|
|
356
|
-
|
|
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
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
214
|
-
|
|
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
|
|
218
|
-
|
|
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.
|
|
5
|
-
[0.
|
|
6
|
-
version's own upgrade notes — read them at the
|
|
7
|
-
(`git show v0.7.1:docs/upgrading.md`), then this page from
|
|
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,
|
|
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.
|
|
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
|
|
82
|
+
# client — see GraphWeaver::QueryModule
|
|
83
83
|
extend GraphWeaver::QueryModule
|
|
84
84
|
|
|
85
|
-
# the graph this module was generated from —
|
|
86
|
-
# its stand-in
|
|
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.
|
|
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
|
|
186
|
+
# client — see GraphWeaver::QueryModule
|
|
187
187
|
extend GraphWeaver::QueryModule
|
|
188
188
|
|
|
189
|
-
# the graph this module was generated from —
|
|
190
|
-
# its stand-in
|
|
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.
|
|
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
|
|
106
|
+
# client — see GraphWeaver::QueryModule
|
|
107
107
|
extend GraphWeaver::QueryModule
|
|
108
108
|
|
|
109
|
-
# the graph this module was generated from —
|
|
110
|
-
# its stand-in
|
|
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
|
|
data/lib/graph_weaver/client.rb
CHANGED
|
@@ -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
|
|
460
|
-
out << " # client
|
|
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 —
|
|
465
|
-
out << " # its stand-in
|
|
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 :
|
|
468
|
+
out << " private_constant :GRAPH"
|
|
475
469
|
end
|
|
476
470
|
out << ""
|
|
477
471
|
|
data/lib/graph_weaver/codegen.rb
CHANGED
|
@@ -59,15 +59,10 @@ class GraphWeaver::Codegen
|
|
|
59
59
|
|
|
60
60
|
attr_reader :name
|
|
61
61
|
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
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,
|
|
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
|
-
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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,
|
|
146
|
-
new(schema:, query:, name:,
|
|
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:
|
|
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
|
-
|
|
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
|
-
#
|
|
171
|
-
#
|
|
172
|
-
|
|
133
|
+
# a parsed module generates no file, so it has no graph to read a client
|
|
134
|
+
# off — client: 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
|
|
data/lib/graph_weaver/graph.rb
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
226
|
-
# constant or its name and stores the name
|
|
227
|
-
|
|
228
|
-
#
|
|
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 :
|
|
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
|
-
# `
|
|
287
|
-
#
|
|
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
|
|
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
|
|
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
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
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
|
|
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 =
|
|
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
|
|
16
|
-
#
|
|
17
|
-
#
|
|
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 →
|
|
20
|
-
#
|
|
21
|
-
#
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
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
|
|
87
|
-
# `graphql:` tag means to replace, so a
|
|
88
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/graph_weaver/rspec.rb
CHANGED
|
@@ -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
|
|
259
|
-
#
|
|
260
|
-
#
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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!, :
|
|
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
|
data/lib/graph_weaver/tasks.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
data/lib/graph_weaver/testing.rb
CHANGED
|
@@ -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
|
|
225
|
-
#
|
|
226
|
-
# @join__* routing
|
|
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
|
data/lib/graph_weaver/version.rb
CHANGED
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 ->
|
|
69
|
-
#
|
|
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,
|
|
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:,
|
|
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,
|
|
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:,
|
|
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
|
|
1086
|
-
# the module
|
|
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.
|
|
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.
|
|
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
|