Class: GraphWeaver::Client
- Inherits:
-
Object
- Object
- GraphWeaver::Client
- Defined in:
- lib/graph_weaver/client.rb
Overview
One object tying the whole flow together — transport, schema, and generation:
github = GraphWeaver.new("https://api.github.com/graphql", auth: token, cache: true)
github.register_scalar("DateTime", Time, serialize: :iso8601, requires: "time")
RepoQuery = github.parse("queries/repo.graphql") # implicit schema + transport
github.execute!("query { viewer { login } }") # one-shot
The first argument is a url (a transport is built; the schema comes from introspection on first use, cached per cache:/ttl:) or a schema source — a live schema class (which also executes in-process), or a path/SDL/introspection dump via SchemaLoader. Pass transport: to bring your own transport for a schema source.
Clients are independent: each has its own transport, schema, and scalar registrations, so one app can talk to several GraphQL servers — even ones that disagree about what a "DateTime" is.
Constant Summary collapse
- URL =
%r{\Ahttps?://}i
Instance Attribute Summary collapse
-
#transport ⇒ Object
readonly
The transport queries run through: a url-built transport, an explicit transport:, or the live schema class executing in-process.
Instance Method Summary collapse
-
#execute(query, **variables) ⇒ Object
One-shot dynamic execution — parse + execute, returning the typed Response envelope (execute! returns the result or raises).
- #execute!(query, **variables) ⇒ Object
-
#initialize(source, auth: nil, headers: {}, retries: false, transport: nil, cache: nil, ttl: nil, &middleware) ⇒ Client
constructor
A new instance of Client.
-
#load_queries!(dir = nil, namespace: Object) ⇒ Object
Parse every .graphql query in a directory into typed modules, named like generation would name them — the no-build-step analog of generate! + load_generated!:.
-
#parse(query, name: nil) ⇒ Object
Parse a query (a .graphql path or raw string) into a typed module bound to this client's schema, scalars, enums, helpers, and transport (including a live schema class executing in-process — the module came from this client, so it runs against it; pass a client per call to override, e.g. with a fake).
-
#register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil) ⇒ Object
Client-scoped enum mapping: this client's generated code speaks your T::Enum for the named GraphQL enum (see Codegen::EnumType — inference by name, map: for renames, fallback: to absorb unknown wire values).
-
#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
Client-scoped scalar registration: consulted before the global registry when this client generates code, so two clients can map the same scalar name onto different Ruby types.
-
#register_type(graphql_name, *mixins, requires: nil, &block) ⇒ Object
Client-scoped type helpers: include app-owned modules into every struct this client generates from the named GraphQL type — pass modules, or a block to build one inline.
-
#schema ⇒ Object
The schema, introspecting through the transport on first use (cached per the client's cache:/ttl:) unless one was given up front.
-
#transport! ⇒ Object
transport, when this client must be able to execute.
Constructor Details
#initialize(source, auth: nil, headers: {}, retries: false, transport: nil, cache: nil, ttl: nil, &middleware) ⇒ Client
Returns a new instance of Client.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/graph_weaver/client.rb', line 32 def initialize(source, auth: nil, headers: {}, retries: false, transport: nil, cache: nil, ttl: nil, &middleware) if source.is_a?(String) && source.match?(URL) raise ArgumentError, "pass a url or transport:, not both" if transport @transport = wrap_retries(build_transport(source, auth:, headers:, &middleware), retries) else if auth || middleware || retries != false raise ArgumentError, "auth:/retries:/middleware apply to a url — got a schema source" end if cache || ttl # a schema source never introspects, so a cache would silently no-op raise ArgumentError, "cache:/ttl: apply to url introspection — got a schema source" end # a live schema class doubles as an in-process transport; a loaded # dump has no resolvers, so it is type information only @schema = source.is_a?(Module) ? source : GraphWeaver::SchemaLoader.load(source) @transport = transport || (source if source.is_a?(Module)) end @cache = cache @ttl = ttl @scalars = {} @enums = {} @types = {} end |
Instance Attribute Details
#transport ⇒ Object (readonly)
The transport queries run through: a url-built transport, an explicit transport:, or the live schema class executing in-process. Clients are self-contained — the app default never leaks in; nil for schema-dump clients (type information only).
63 64 65 |
# File 'lib/graph_weaver/client.rb', line 63 def transport @transport end |
Instance Method Details
#execute(query, **variables) ⇒ Object
One-shot dynamic execution — parse + execute, returning the typed Response envelope (execute! returns the result or raises). Variables are plain kwargs, exactly as on a generated module; graphql-cased string keys work too.
144 145 146 147 148 |
# File 'lib/graph_weaver/client.rb', line 144 def execute(query, **variables) mod = parse(query) kwargs = variables.to_h { |key, value| [GraphWeaver::Inflect.underscore(key.to_s).to_sym, value] } mod.execute(transport!, **kwargs) end |
#execute!(query, **variables) ⇒ Object
150 151 152 |
# File 'lib/graph_weaver/client.rb', line 150 def execute!(query, **variables) execute(query, **variables).data! end |
#load_queries!(dir = nil, namespace: Object) ⇒ Object
Parse every .graphql query in a directory into typed modules, named like generation would name them — the no-build-step analog of generate! + load_generated!:
github.load_queries! # queries/person.graphql => ::PersonQuery
github.load_queries!(namespace: Github) # => Github::PersonQuery
Reloadable (constants are replaced), so it suits consoles and dev. Returns the modules.
130 131 132 133 134 135 136 137 138 |
# File 'lib/graph_weaver/client.rb', line 130 def load_queries!(dir = nil, namespace: Object) dirs = dir ? [dir] : GraphWeaver.queries_paths dirs.flat_map { |d| Dir[File.join(d, "*.graphql")].sort }.map do |path| name = "#{GraphWeaver::Inflect.camelize(File.basename(path, ".graphql"))}Query" namespace.send(:remove_const, name) if namespace.const_defined?(name, false) GraphWeaver.log(:info) { "loaded #{name} from #{path}" } namespace.const_set(name, parse(path)) end end |
#parse(query, name: nil) ⇒ Object
Parse a query (a .graphql path or raw string) into a typed module bound to this client's schema, scalars, enums, helpers, and transport (including a live schema class executing in-process — the module came from this client, so it runs against it; pass a client per call to override, e.g. with a fake).
116 117 118 119 |
# File 'lib/graph_weaver/client.rb', line 116 def parse(query, name: nil) GraphWeaver.parse(schema:, query:, name:, client: transport, scalars: @scalars, enums: @enums, types: @types) end |
#register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil) ⇒ Object
Client-scoped enum mapping: this client's generated code speaks your T::Enum for the named GraphQL enum (see Codegen::EnumType — inference by name, map: for renames, fallback: to absorb unknown wire values).
90 91 92 93 94 |
# File 'lib/graph_weaver/client.rb', line 90 def register_enum(graphql_name, type, map: nil, fallback: nil, requires: nil) validate_registration!("enum", graphql_name.to_s) @enums[graphql_name.to_s] = GraphWeaver::Codegen::EnumType.new(graphql_name, type, map:, fallback:, requires:) end |
#register_enums(mappings) ⇒ Object
Bulk, inference-only form: register_enums("Species" => PetKind, ...)
97 98 99 |
# File 'lib/graph_weaver/client.rb', line 97 def register_enums(mappings) mappings.each { |graphql_name, type| register_enum(graphql_name, type) } end |
#register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) ⇒ Object
Client-scoped scalar registration: consulted before the global registry when this client generates code, so two clients can map the same scalar name onto different Ruby types. Same signature as GraphWeaver.register_scalar.
81 82 83 84 85 |
# File 'lib/graph_weaver/client.rb', line 81 def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) validate_registration!("scalar", graphql_name.to_s) @scalars[graphql_name.to_s] = GraphWeaver::Codegen::ScalarType.new(graphql_name, type, cast:, serialize:, requires:, coerce:) end |
#register_type(graphql_name, *mixins, requires: nil, &block) ⇒ Object
Client-scoped type helpers: include app-owned modules into every struct this client generates from the named GraphQL type — pass modules, or a block to build one inline. Additive with global registrations (see GraphWeaver.register_type).
105 106 107 108 109 |
# File 'lib/graph_weaver/client.rb', line 105 def register_type(graphql_name, *mixins, requires: nil, &block) validate_registration!("type", graphql_name.to_s) entry = @types[graphql_name.to_s] ||= { mixins: [], requires: [] } GraphWeaver::Codegen.add_type_helpers(entry, graphql_name, mixins, requires, block) end |
#schema ⇒ Object
The schema, introspecting through the transport on first use (cached per the client's cache:/ttl:) unless one was given up front.
73 74 75 |
# File 'lib/graph_weaver/client.rb', line 73 def schema @schema ||= GraphWeaver::SchemaLoader.introspect(transport!, cache: @cache, ttl: @ttl) end |
#transport! ⇒ Object
transport, when this client must be able to execute
66 67 68 69 |
# File 'lib/graph_weaver/client.rb', line 66 def transport! transport or raise GraphWeaver::Error, "this client has no transport (built from a schema dump) — pass a url or transport:" end |