graph_weaver 0.7.1 → 0.7.3
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 +19 -19
- data/docs/federation.md +8 -8
- data/docs/generated_modules.md +28 -21
- data/docs/getting_started.md +14 -13
- 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/railtie.rb +202 -147
- 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 +26 -9
- metadata +2 -2
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
|