Module: GraphWeaver
- Extended by:
- T::Sig
- Defined in:
- lib/graph_weaver.rb,
lib/graph_weaver/hints.rb,
lib/graph_weaver/rspec.rb,
lib/graph_weaver/errors.rb,
lib/graph_weaver/inflect.rb,
lib/graph_weaver/logging.rb,
lib/graph_weaver/testing.rb,
lib/graph_weaver/version.rb,
lib/graph_weaver/response.rb,
lib/graph_weaver/selection.rb,
lib/graph_weaver/input_struct.rb,
lib/graph_weaver/transport/http.rb,
lib/graph_weaver/testing/failure.rb,
lib/graph_weaver/testing/cassette.rb,
lib/graph_weaver/codegen/enum_type.rb,
lib/graph_weaver/transport/faraday.rb
Overview
Opt-in test tooling: require "graph_weaver/testing" from your spec helper (never from production code). Configure once, initializer-style:
GraphWeaver::Testing.configure do |config|
config.schema = MySchema # for auto_fake / cassettes
config.seed = 42 # reproducible fakes
config.mode = :faker # or :literal; nil = auto
config.overrides = { "Person.name" => "Daniel" }
config.list_size = 2..4
config.null_chance = 0.1 # nullable fields go nil sometimes
config.cassette_dir = "spec/cassettes"
end
mode picks how values are fabricated: :faker — semantic, field-name matched (requires the faker gem) :literal — plain type-derived values ("name-1", seeded numbers) nil — auto: :faker when the gem is loaded, else :literal
rspec users: require "graph_weaver/rspec" instead — it hooks the suite (seed from rspec, optional auto-faked client per example).
Defined Under Namespace
Modules: ErrorFiltering, Hints, Inflect, InputStruct, SchemaLoader, Selection, Testing, TypeHelpers Classes: Client, Codegen, Error, GraphQLError, InputError, QueryError, Railtie, Response, Retry, ServerError, Transport, TransportError, TypeError, ValidationError
Constant Summary collapse
- VERSION =
"0.4.6"
Class Attribute Summary collapse
-
.auto_coerce ⇒ Object
Default input coercion for scalars that don't say coerce: themselves, resolved lazily at generation time (so set it any time before you generate — no reset_scalars! ordering dance):.
-
.client ⇒ Object
The app's default client — how generated modules find their server:.
-
.extend_t_sig ⇒ Object
writeonly
Whether generated modules/structs emit
extend T::Sig(sosigresolves standalone). -
.fragments_paths ⇒ Object
Reusable named fragments, defined once and available to every query — each query inlines only the ones it (transitively) spreads, so the sent query stays self-contained.
- .generated_paths ⇒ Object
- .inputs_module(output = generated_path) ⇒ Object
-
.logger ⇒ Object
Where GraphWeaver narrates what it's doing — anything stdlib-Logger-compatible (Logger, Rails.logger, semantic_logger...).
-
.queries_paths ⇒ Object
Entries may be glob patterns — the generated default also matches per-schema layouts (app/graphql/github/generated).
- .schema_path ⇒ Object
- .unions_module(output = generated_path) ⇒ Object
Class Method Summary collapse
-
.clear_scalars! ⇒ Object
Empty the scalar registry entirely, built-ins included (see reset_scalars! to restore the defaults).
-
.client! ⇒ Object
the default client, when one is required.
-
.derive_module(suffix, output) ⇒ Object
Name a shared module from the output path:
in a multi-schema layout, else GraphQL . -
.did_you_mean(dictionary, term) ⇒ Object
The closest entry in
dictionarytoterm— a "did you mean" suggestion, or nil (also nil when did_you_mean isn't loadable). -
.execute(source, query, **variables) ⇒ Object
One-shot dynamic execution — a throwaway client, no build step:.
-
.execute!(source, query, **variables) ⇒ Object
execute + data! — the typed result, or a raised QueryError.
-
.extend_t_sig? ⇒ Boolean
The resolved boolean codegen uses: the explicit setting, else emit unless T::Sig is globally injected into Module.
-
.extend_type(graphql_name, *mixins, requires: nil, **kw, &block) ⇒ Object
Include app-owned helper modules into every struct generated from a GraphQL type — derived values live as methods next to the honest wire data, on the struct at runtime (fakes/cassettes included):.
- .fragments_path ⇒ Object
-
.generate!(schema: nil, queries: queries_path, output: generated_path, client: nil, inputs_module: nil, unions_module: nil) ⇒ Object
Generate every .graphql query in a directory into checked-in Ruby files.
- .generated_path ⇒ Object
- .generated_path=(path) ⇒ Object
-
.generation_plan(queries:, schema:, client:, inputs_module: self.inputs_module, unions_module: self.unions_module, fragments: fragments_paths) ⇒ Object
(filename, source) per artifact.
-
.global_tsig? ⇒ Boolean
Whether the host app has globally injected T::Sig into every module (
class Module; include T::Sig) — extracted so it's stubbable in tests. -
.load_generated!(path = nil) ⇒ Object
Load the generated modules — one line in an initializer or spec helper (loading happens only when you call this; skip it and require files yourself if you'd rather):.
-
.locate_schema! ⇒ Object
the conventional schema dump, required.
-
.log(level, &block) ⇒ Object
Internal: level-gated and lazy — the block only runs when a logger is listening.
-
.log_timed(level, label) ⇒ Object
Internal: run the block, logging "
-
.new(source, **options, &middleware) ⇒ Object
A client for one GraphQL server — transport, schema, and scoped scalars in one object (see Client):.
-
.parse(schema:, query:, name: nil, client: nil, scalars: nil, enums: nil, types: nil, fragments: fragments_paths) ⇒ Object
Parse a query into a typed query module:.
- .queries_path ⇒ Object
- .queries_path=(path) ⇒ Object
-
.register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil) ⇒ Object
Map a GraphQL enum onto an app-owned T::Enum, so generated code speaks YOUR enum — casting wire values in, serializing members out:.
-
.register_enums(mappings) ⇒ Object
Bulk, inference-only form: register_enums("Species" => PetKind, ...).
-
.register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) ⇒ Object
Teach the generator how a GraphQL custom scalar deserializes into a rich Ruby object (and serializes back onto the wire when used as a variable):.
- .register_transport_error(*classes) ⇒ Object
-
.reset_scalars! ⇒ Object
Restore the built-in scalars, dropping every custom registration — the clean slate to reach for between tests or to undo overrides.
-
.resolve_transport(target) ⇒ Object
The transport behind a client-or-transport value: a Client resolves to its own transport, anything else already speaks execute.
-
.shared_artifacts(output) ⇒ Object
The wholly-generated shared-artifact files under output (inputs/*.rb and unions.rb) — safe to prune when regeneration no longer produces them.
- .transport_errors ⇒ Object
-
.verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil, inputs_module: nil, unions_module: nil) ⇒ Object
The freshness guard: raise unless every generated file matches what the current schema + queries + scalar registrations would produce.
Class Attribute Details
.auto_coerce ⇒ Object
Default input coercion for scalars that don't say coerce: themselves, resolved lazily at generation time (so set it any time before you generate — no reset_scalars! ordering dance):
GraphWeaver.auto_coerce = true
Convertible built-ins take their conversion (Int accepts 5/"5"), and any scalar with a full cast/serialize pair (Date, your Money) accepts its raw wire form. An explicit coerce: true/false/Symbol on a registration always wins.
275 276 277 |
# File 'lib/graph_weaver.rb', line 275 def auto_coerce @auto_coerce end |
.client ⇒ Object
The app's default client — how generated modules find their server:
GraphWeaver.client = GraphWeaver.new(url, auth: token)
Accepts a Client or anything satisfying the execute contract (a schema class, a fake — testing's auto_fake swaps one in per example). Generated modules resolve per call -> per module (MyQuery.client=) -> baked constant -> here.
42 43 44 |
# File 'lib/graph_weaver.rb', line 42 def client @client end |
.extend_t_sig=(value) ⇒ Object (writeonly)
Whether generated modules/structs emit extend T::Sig (so sig
resolves standalone). Default (nil) auto-detects: an app that globally
injects T::Sig (class Module; include T::Sig) makes the per-struct
extend redundant — rubocop's Sorbet/RedundantExtendTSig flags it — so
generation skips it. Force with true/false. Resolved at generation time.
GraphWeaver.extend_t_sig = false # never emit (rely on a global include)
284 285 286 |
# File 'lib/graph_weaver.rb', line 284 def extend_t_sig=(value) @extend_t_sig = value end |
.fragments_paths ⇒ Object
Reusable named fragments, defined once and available to every query — each query inlines only the ones it (transitively) spreads, so the sent query stays self-contained.
78 |
# File 'lib/graph_weaver.rb', line 78 def fragments_paths = @fragments_paths ||= ["app/graphql/fragments"] |
.generated_paths ⇒ Object
73 |
# File 'lib/graph_weaver.rb', line 73 def generated_paths = @generated_paths ||= ["app/graphql/generated", "app/graphql/*/generated"] |
.inputs_module(output = generated_path) ⇒ Object
102 103 104 |
# File 'lib/graph_weaver.rb', line 102 def inputs_module(output = generated_path) @inputs_module || derive_module("Inputs", output) end |
.logger ⇒ Object
Where GraphWeaver narrates what it's doing — anything stdlib-Logger-compatible (Logger, Rails.logger, semantic_logger...). Silent by default; Rails apps get Rails.logger wired by the railtie.
GraphWeaver.logger = Logger.new($stdout, level: Logger::INFO)
What logs where:
debug — full queries + variables on the wire, responses
(status/bytes/ms), connection lifecycle, parsed modules
info — schema introspection and cache decisions, generated files
written, query modules loaded
warn — every GraphWeaver error raised
Queries, variables, and responses appear at debug ONLY — they can carry PII. Auth headers never log.
21 22 23 |
# File 'lib/graph_weaver/logging.rb', line 21 def logger @logger end |
.queries_paths ⇒ Object
Entries may be glob patterns — the generated default also matches per-schema layouts (app/graphql/github/generated). Queries stay single-schema: load_queries! parses everything against one client.
72 |
# File 'lib/graph_weaver.rb', line 72 def queries_paths = @queries_paths ||= ["app/graphql/queries"] |
.schema_path ⇒ Object
92 |
# File 'lib/graph_weaver.rb', line 92 def schema_path = @schema_path || "app/graphql/schema.json" |
.unions_module(output = generated_path) ⇒ Object
106 107 108 |
# File 'lib/graph_weaver.rb', line 106 def unions_module(output = generated_path) @unions_module || derive_module("Unions", output) end |
Class Method Details
.clear_scalars! ⇒ Object
Empty the scalar registry entirely, built-ins included (see reset_scalars! to restore the defaults).
386 387 388 |
# File 'lib/graph_weaver.rb', line 386 def clear_scalars! Codegen.clear_scalars! end |
.client! ⇒ Object
the default client, when one is required
45 46 47 |
# File 'lib/graph_weaver.rb', line 45 def client! @client or raise Error, "no client configured — set GraphWeaver.client= or pass a client" end |
.derive_module(suffix, output) ⇒ Object
Name a shared module from the output path:
112 113 114 115 116 117 118 119 120 121 |
# File 'lib/graph_weaver.rb', line 112 def derive_module(suffix, output) segments = File.(output.to_s).split(File::SEPARATOR) segments.pop if segments.last == "generated" parent = segments.last.to_s if parent.match?(/\A[a-zA-Z]\w*\z/) && !%w[graphql app lib spec support test].include?(parent) "#{Inflect.camelize(parent)}#{suffix}" else "GraphQL#{suffix}" end end |
.did_you_mean(dictionary, term) ⇒ Object
The closest entry in dictionary to term — a "did you mean" suggestion,
or nil (also nil when did_you_mean isn't loadable). One home for the guard
used by codegen validation, alias resolution, and the runtime prop hints.
299 300 301 302 303 |
# File 'lib/graph_weaver.rb', line 299 def did_you_mean(dictionary, term) return unless defined?(DidYouMean::SpellChecker) DidYouMean::SpellChecker.new(dictionary: dictionary).correct(term).first end |
.execute(source, query, **variables) ⇒ Object
One-shot dynamic execution — a throwaway client, no build step:
GraphWeaver.execute(schema, "query($id: ID!) { ... }", id: "1") # => Response
GraphWeaver.execute!(url, "query { viewer { login } }") # => Result (or raise)
The first argument is a url or schema source, exactly as GraphWeaver.new; this is Client#execute on a client you don't keep. (A url source introspects the schema on every call — keep a client for more than one query.) Variables are plain kwargs, as on a generated module (nothing reserved). execute returns the Response envelope, execute! the typed result, raising QueryError on top-level errors.
422 423 424 425 |
# File 'lib/graph_weaver.rb', line 422 def execute(source, query, **variables) client = source.is_a?(Client) ? source : Client.new(source) client.execute(query, **variables) end |
.execute!(source, query, **variables) ⇒ Object
execute + data! — the typed result, or a raised QueryError. See execute.
428 429 430 |
# File 'lib/graph_weaver.rb', line 428 def execute!(source, query, **variables) execute(source, query, **variables).data! end |
.extend_t_sig? ⇒ Boolean
The resolved boolean codegen uses: the explicit setting, else emit unless T::Sig is globally injected into Module.
288 289 290 |
# File 'lib/graph_weaver.rb', line 288 def extend_t_sig? @extend_t_sig.nil? ? !global_tsig? : @extend_t_sig end |
.extend_type(graphql_name, *mixins, requires: nil, **kw, &block) ⇒ Object
Include app-owned helper modules into every struct generated from a GraphQL type — derived values live as methods next to the honest wire data, on the struct at runtime (fakes/cassettes included):
GraphWeaver.extend_type("Pet", PetHelpers)
A helper that reads a wire field is checked by srb tc in the module's own scope, not the struct's, so the field won't resolve — write it
typed: false or reach it via T.unsafe(self). Or build the mixin inline
with a block (module_eval'd into an auto-named module — invisible to srb):
GraphWeaver.extend_type("Pet") do
def display_name = "#{name} the pet"
end
Additive (repeated and client-scoped registrations stack). Global; client.extend_type scopes to one client.
373 374 375 |
# File 'lib/graph_weaver.rb', line 373 def extend_type(graphql_name, *mixins, requires: nil, **kw, &block) Codegen.extend_type(graphql_name, *mixins, requires:, **kw, &block) end |
.fragments_path ⇒ Object
82 |
# File 'lib/graph_weaver.rb', line 82 def fragments_path = fragments_paths.first |
.generate!(schema: nil, queries: queries_path, output: generated_path, client: nil, inputs_module: nil, unions_module: nil) ⇒ Object
Generate every .graphql query in a directory into checked-in Ruby files. Paths default to the conventions above; schema: defaults to the dump at schema_path (any supported extension):
GraphWeaver.generate! # queries_path -> generated_path
person.graphql => person_query.rb defining PersonQuery. Returns the written paths. Pair with a freshness spec (docs/generated_modules.md).
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/graph_weaver.rb', line 132 def generate!(schema: nil, queries: queries_path, output: generated_path, client: nil, inputs_module: nil, unions_module: nil) schema ||= locate_schema! inputs_module ||= self.inputs_module(output) unions_module ||= self.unions_module(output) plan = generation_plan(queries:, schema:, client:, inputs_module:, unions_module:) written = plan.map do |filename, source| target = File.join(output, filename) FileUtils.mkdir_p(File.dirname(target)) File.write(target, source) log(:info) { "generated #{target}" } target end # a type dropped from the schema (or a union no longer hoisted) must not # linger as a stale file — inputs/ and unions.rb are wholly generated (shared_artifacts(output) - written).each do |orphan| File.delete(orphan) log(:info) { "pruned #{orphan}" } end written end |
.generated_path ⇒ Object
81 |
# File 'lib/graph_weaver.rb', line 81 def generated_path = generated_paths.first |
.generated_path=(path) ⇒ Object
88 89 90 |
# File 'lib/graph_weaver.rb', line 88 def generated_path=(path) @generated_paths = path.nil? ? nil : [path] end |
.generation_plan(queries:, schema:, client:, inputs_module: self.inputs_module, unions_module: self.unions_module, fragments: fragments_paths) ⇒ Object
(filename, source) per artifact. Every variable type is emitted once into inputs.rb, and each named shared fragment spread as a whole-union field once into unions.rb, with query modules aliasing what they use — the difference between hundreds of duplicated bool_exp structs (or the same union re-typed per query) and one copy per schema. (Single-query parse inlines both — there's no cross-query set to share against.)
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/graph_weaver.rb', line 222 def generation_plan(queries:, schema:, client:, inputs_module: self.inputs_module, unions_module: self.unions_module, fragments: fragments_paths) used = { inputs: [], enums: [], mapped: [] } used_unions = [] shared = Codegen.load_fragments(fragments) plan = Dir[File.join(queries, "*.graphql")].sort.map do |path| base = File.basename(path, ".graphql") source = File.read(path) codegen = Codegen.new( schema:, query: Codegen.inline_fragments(source, shared), module_name: "#{Inflect.camelize(base)}Query", client:, inputs_namespace: inputs_module, unions_namespace: unions_module, hoistable_unions: Codegen.shared_fragment_spreads(source, shared), ) out = codegen.generate codegen.variable_type_names.each { |kind, names| used[kind] |= names } used_unions |= codegen.used_union_names ["#{base}_query.rb", out] end if inputs_module && used.values.any?(&:any?) inputs = Codegen.generate_inputs( schema:, module_name: inputs_module, input_types: used[:inputs], enum_types: used[:enums] + used[:mapped], ) plan = inputs.to_a + plan end if unions_module && used_unions.any? unions = Codegen.generate_unions( schema:, module_name: unions_module, fragments: shared, names: used_unions, ) plan = unions.to_a + plan end plan end |
.global_tsig? ⇒ Boolean
Whether the host app has globally injected T::Sig into every module
(class Module; include T::Sig) — extracted so it's stubbable in tests.
294 |
# File 'lib/graph_weaver.rb', line 294 def global_tsig? = Module.include?(T::Sig) |
.load_generated!(path = nil) ⇒ Object
Load the generated modules — one line in an initializer or spec helper (loading happens only when you call this; skip it and require files yourself if you'd rather):
GraphWeaver.load_generated!
In Rails, prefer this over autoloading: Zeitwerk would expect Generated::PersonQuery from generated/person_query.rb, and generated code only changes on regeneration anyway (restart, like a schema migration).
201 202 203 204 205 206 207 |
# File 'lib/graph_weaver.rb', line 201 def load_generated!(path = nil) paths = path ? [path] : generated_paths files = paths.flat_map { |dir| Dir[File.join(dir, "**/*.rb")].sort }.uniq files.each { |file| require File.(file) } log(:info) { "loaded #{files.size} generated module(s) from #{paths.join(", ")}" } files end |
.locate_schema! ⇒ Object
the conventional schema dump, required
210 211 212 213 |
# File 'lib/graph_weaver.rb', line 210 def locate_schema! SchemaLoader.locate or raise Error, "no schema dump at #{schema_path} (.json/.graphql/.gql) — pass schema:, or cache one: GraphWeaver.new(url, cache: true).schema" end |
.log(level, &block) ⇒ Object
Internal: level-gated and lazy — the block only runs when a logger is listening. Messages carry "graph_weaver" as progname.
25 26 27 |
# File 'lib/graph_weaver/logging.rb', line 25 def log(level, &block) logger&.public_send(level, "graph_weaver", &block) end |
.log_timed(level, label) ⇒ Object
Internal: run the block, logging "
31 32 33 34 35 36 37 38 39 |
# File 'lib/graph_weaver/logging.rb', line 31 def log_timed(level, label) return yield unless logger start = Process.clock_gettime(Process::CLOCK_MONOTONIC) result = yield ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round log(level) { "#{label} (#{ms}ms)" } result end |
.new(source, **options, &middleware) ⇒ Object
A client for one GraphQL server — transport, schema, and scoped scalars in one object (see Client):
github = GraphWeaver.new("https://api.github.com/graphql", auth: token, cache: true)
RepoQuery = github.parse("queries/repo.graphql")
The first argument is a url or any schema source (a live schema class, or a path/SDL/introspection dump).
30 31 32 |
# File 'lib/graph_weaver.rb', line 30 def new(source, **, &middleware) Client.new(source, **, &middleware) end |
.parse(schema:, query:, name: nil, client: nil, scalars: nil, enums: nil, types: nil, fragments: fragments_paths) ⇒ Object
Parse a query into a typed query module:
PersonQuery = GraphWeaver.parse(schema:, query: "queries/person.graphql")
query is a .graphql/.gql path (module name derived from the file name) or a raw query string (name derived from the operation name, falling back to "Query" for anonymous operations — collisions are impossible since each parse gets its own container). Pass name: to override, client: to bake the module's default client/transport.
399 400 401 402 403 404 405 406 407 408 |
# File 'lib/graph_weaver.rb', line 399 def parse(schema:, query:, name: nil, client: nil, scalars: nil, enums: nil, types: nil, fragments: fragments_paths) if query.end_with?(".graphql", ".gql") name ||= "#{Inflect.camelize(File.basename(query, ".*"))}Query" query = File.read(query) end query = Codegen.inline_fragments(query, Codegen.load_fragments(fragments)) Codegen.parse(schema:, query:, module_name: name, client:, scalars:, enums:, types:) end |
.queries_path ⇒ Object
80 |
# File 'lib/graph_weaver.rb', line 80 def queries_path = queries_paths.first |
.queries_path=(path) ⇒ Object
84 85 86 |
# File 'lib/graph_weaver.rb', line 84 def queries_path=(path) @queries_paths = path.nil? ? nil : [path] end |
.register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil) ⇒ Object
Map a GraphQL enum onto an app-owned T::Enum, so generated code speaks YOUR enum — casting wire values in, serializing members out:
GraphWeaver.register_enum("Species", PetKind)
The mapping is inferred by name ("CAT" <-> PetKind::Cat); map: pins renames, fallback: absorbs unknown wire values on cast (inputs stay strict), requires: names files the generated code should require. Generation fails naming any schema value that doesn't resolve — exhaustiveness checked ahead of runtime. Global; client.register_enum scopes to one client.
347 348 349 |
# File 'lib/graph_weaver.rb', line 347 def register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil) Codegen.register_enum(graphql_name, type, map:, fallback:, requires:) end |
.register_enums(mappings) ⇒ Object
Bulk, inference-only form: register_enums("Species" => PetKind, ...)
352 353 354 |
# File 'lib/graph_weaver.rb', line 352 def register_enums(mappings) Codegen.register_enums(mappings) end |
.register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) ⇒ Object
Teach the generator how a GraphQL custom scalar deserializes into a rich Ruby object (and serializes back onto the wire when used as a variable):
GraphWeaver.register_scalar("Money", Money, requires: "bigdecimal")
A field typed Money then generates const :price, T.nilable(Money)
and casts with Money.parse(...) in from_h. Pass a real class as
type: and cast:/serialize: are inferred from it — .parse/#to_s, or
.load/.dump — by probing the deserialize side (see ScalarType::CODECS).
Override with a Symbol method name (safest — no string to misspell), a
Proc(expr) => code string, or :itself to force pass-through. requires:
(a String or Array) names files the generated code needs — validated,
and actually required to confirm it resolves when type: is a real class.
coerce: true makes a variable of this scalar accept the value OR its
raw input (e.g. "12.00"), running the latter through the cast before
serializing — it raises on bad input, so some safety survives. Built-in
scalars are pre-registered the same way, so this also overrides them.
Pass a Type.field coordinate instead of a scalar name to override just
that one field — so the same scalar can deserialize as different Ruby
types across fields (a Date for User.birthday, a Time elsewhere):
GraphWeaver.register_scalar("User.birthday", Date)
A field-level override wins over the scalar-name registration. Same signature either way. Call before generating.
332 333 334 |
# File 'lib/graph_weaver.rb', line 332 def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) Codegen.register_scalar(graphql_name, type, cast:, serialize:, requires:, coerce:) end |
.register_transport_error(*classes) ⇒ Object
70 71 72 73 |
# File 'lib/graph_weaver/errors.rb', line 70 def register_transport_error(*classes) transport_errors.merge(classes) classes end |
.reset_scalars! ⇒ Object
Restore the built-in scalars, dropping every custom registration — the clean slate to reach for between tests or to undo overrides. (Coercible built-ins are auto_coerce's job, not a reset flavor.)
380 381 382 |
# File 'lib/graph_weaver.rb', line 380 def reset_scalars! Codegen.reset_scalars! end |
.resolve_transport(target) ⇒ Object
The transport behind a client-or-transport value: a Client resolves to its own transport, anything else already speaks execute. Generated modules call this on every execute, so any slot in the resolution chain can hold either kind.
53 54 55 |
# File 'lib/graph_weaver.rb', line 53 def resolve_transport(target) target.is_a?(Client) ? target.transport! : target end |
.shared_artifacts(output) ⇒ Object
The wholly-generated shared-artifact files under output (inputs/*.rb and unions.rb) — safe to prune when regeneration no longer produces them.
159 160 161 |
# File 'lib/graph_weaver.rb', line 159 def shared_artifacts(output) Dir[File.join(output, "inputs", "*.rb")] + Dir[File.join(output, "unions.rb")] end |
.transport_errors ⇒ Object
61 62 63 64 65 66 |
# File 'lib/graph_weaver/errors.rb', line 61 def transport_errors @transport_errors ||= T.let( Set[SocketError, SystemCallError, IOError], T.nilable(T::Set[T.class_of(Exception)]), ) end |
.verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil, inputs_module: nil, unions_module: nil) ⇒ Object
The freshness guard: raise unless every generated file matches what
the current schema + queries + scalar registrations would produce.
One line in a spec, or rake graph_weaver:verify in CI:
it "generated queries are current" do
GraphWeaver.verify_generated!
end
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/graph_weaver.rb', line 171 def verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil, inputs_module: nil, unions_module: nil) schema ||= locate_schema! inputs_module ||= self.inputs_module(output) unions_module ||= self.unions_module(output) plan = generation_plan(queries:, schema:, client:, inputs_module:, unions_module:) stale = plan.filter_map do |filename, source| target = File.join(output, filename) target unless File.exist?(target) && File.read(target) == source end # strays: a shared-artifact file the current schema + queries no longer produce stale += shared_artifacts(output) - plan.map { |f, _| File.join(output, f) } unless stale.empty? raise Error, "stale generated queries — regenerate (rake graph_weaver:generate): #{stale.join(", ")}" end true end |