Repository navigation
Releases
v0.26.3 — -R works again against current rq and keeps rq's hedges; remote schemas say their cache age, fall back to a stale copy with disclosure, and retry transient failures; corrected queries no longer take a minute.
Added
- A live schema whose refetch fails is answered from the expired copy
instead of failing — when the endpoint is down, times out, answers 5xx, or
answers with a GraphQLerrorsbody, and a cached copy exists (also under
--refresh). stderr says what failed and how old the copy is, even under
-q; JSONsourceadds"stale": trueanderror. Exit0: an answer
was given. A 401/403 never falls back, and with no copy the fetch fails as
before. - An answer from a cached copy of a live schema says how old it is:
from a copy of <url> cached 41m ago — --refresh to refetchon stderr
(silenced by-q), and asourceobject (url,cached: true,
age_secs) on every JSON row and on the-Jno_matchesrow. Rows from a
file or a fresh fetch are unchanged. - A miss from a remembered discovery says so:
no matches for "x" in schema.graphql (remembered from an earlier walk — --refresh re-walks), and
the-Jmiss row carriessource: {path, discovered: true, remembered}
whenever gqls picked the schema. - A live fetch retries a transient failure. A refused or dropped
connection, a 502/503/504, or a 429 (honouringRetry-Afterup to 5 s) is
tried up to three times in all, with a short backoff, inside a 40 s budget —
so a hung endpoint costs one timeout, not three. A 4xx refusal and a GraphQL
errorsbody are never retried.-vsays what was retried and why. - A fetch still running after 2 s says so (
still waiting on <url>); a
hung endpoint was silent for 30. -Rpasses rq's hedges through. A class declared in several places shows
also in <file:line>and carriesdeclarations/also_inin JSON. A hit
from a checkout rq is still indexing saysrq still indexing: N of M files
on stderr and carries rq'swarmingobject; a hit rq held back as
provisional (rq's exit 2) is kept, marked(provisional), with
"provisional": trueon every JSON row. Exit0— it's an answer.
Changed
--profiletimes the explain check and the miss diagnosis as their own
phases (explain check,miss diagnosis);outputis now just writing
the results, which it was always presumed to be.-R -jrenamesconfidencetorq_confidence. It was rq's confidence
in the name match, so a(guess)rq matched exactly read as
"confidence": 1.0. gqls's own verdict isloose. A script reading
confidencemust readrq_confidence— though against rq 0.52.0 or newer
-Rfailed outright, so no current setup was producing the old field.-Rexits1when rq is still indexing and found nothing, with rq's
progress and hint, instead of exiting0under ano code definition found
that read as definitive. A miss on a finished index still exits0.- A failing rq is reported in its own words — its JSON
errorand its
exit code — rather than the first stderr line besideexit Some(64).
Fixed
- A failed fetch says what happened once.
introspecting URL: URL: Network Error: Network Error: …readsintrospecting URL: couldn't connect (Connection refused), and an HTTP failure names its status and the
server's GraphQL error message (HTTP 401 Unauthorized: bad token). - A query correcting to a common field name no longer takes a minute.
Deciding whether a query names one record re-scanned every name in the
schema once per record it corrected to: on a 48k-record schema,emial
(→ 2,811emailfields) took about 60 s andñame77 s. It is asked once
per query now, and the check allocates nothing for ASCII names and runs in
parallel —emialtakes about 16 ms. Every result is unchanged. -Rworks again. It passed rq--no-record, which rq removed in 0.52.0,
so every resolve against a current rq failed withunexpected argument.
gqls now passes rq only--ndjson --limit 10(and--verboseunder-v),
and a test drives the installed rq when there is one.
v0.26.2
Changed
-
The
and N morehint no longer quotes the command it suggests. A
trailing dot is no shell metacharacter, and quoting it there taught the habit
everywhere else. -
A phrase in your own words finds more than it used to. Three rules widen
what counts as covering one of its words:Where a schema names something in its own vocabulary and describes it in the
user's, the description now bridges them —current userreaches
Query.viewer("The currently authenticated user"), which its name alone
never did.The words a question is built from (
who,what,does,has,this,
my) named nothing in any schema, so each one a record happened to echo
lifted its word count and buried the records matching the words you meant.
They're dropped before scoring now, like the other stopwords, so a question
asked as a sentence narrows about as well as the keywords inside it.A word matching nothing at all is retried as its singular, one tier
below a direct match:typesnever reachedpokemon_v2_type, because the
trailingshas nowhere to land in a subsequence and a prefixed name is far
outside the typo budget. The other direction already worked, sincetype
lands insidetypeson its own.A description word counts toward how much of a phrase a record covers, but
the bar to clear is still the best coverage by name — prose can reach it,
never raise it, so a record whose description happens to mention a word can't
displace the one named after it. Single-word queries are matched against
names only and are untouched by all three.Measured, each against the labelled sets in
script/eval/: the description
rule took MRR from 0.43 to 0.47 on 49 GitHub queries, rescuing five and
losing none; dropping question words took it from 0.06 to 0.13 on 30 queries
written as full questions by someone other than their author, leaving the
shorter-phrase sets unchanged; the singular retry took the held-out set from
0.22 to 0.27, and four of six sampled PokeAPI root fields from unreachable to
found. -
A generated schema's boilerplate ranks below what it wraps. Hasura and
PostGraphile surround every real table with a dozen derived types — filters,
aggregates, sort orders, insert shapes,_streamsubscriptions — each
carrying the table's name, so each matches everything the table matches. On
PokeAPI's schema they are 22,667 of 26,218 records, and they filled the whole
first page of every answer:list of egg groupshad 50 of them above the
table it named. Relay'sConnectionandEdgeare the same shape in the
other spelling —RepositoryRulesetEdgewas the top answer for "programming
languages used by a repo" — and now rank below the type they wrap too. Both
lists are suffix conventions their generators own, so a hand-written schema
is untouched, and a type actually calledEdgeis left alone.
Fixed
- A truncated wildcard said so twice. A query compiles its filters once to
search and once to explain, and the warning lived in the matcher. - A cache location gqls can't use is said aloud. A relative or empty
XDG_CACHE_HOMEis ignored (the XDG spec's rule, and what keeps
--clear-cacheout of your cwd) — but silently, so a run cached somewhere
other than where you pointed it. It now says which variable it ignored. - Having nowhere to cache no longer reports an empty cache.
--clear-cachewith neitherXDG_CACHE_HOMEnorHOMEset said
cleared 0 cached file(s), which reads as "the cache was already empty". - A qualifier naming no type leads with what it searched. The line read
no type named "Repo" — matching …, which looks like a failure above an
answer that is usually right (Repo.ownerfindsRepository.owner).
v0.26.1
Fixed
--clear-cachetook any*.tmp*file, including.tmpltemplates. It
now takes only gqls's own shape,<name>.tmp<pid>— the same data-loss class
as the bug above, narrowed but not closed.- A cache directory that is a symlink is cleared rather than skipped.
Writes follow it, so--clear-cachesaidcleared 0 cached file(s)over a
full cache — indistinguishable from an empty one. A symlink inside the
cache is still never followed. - A non-ASCII type name no longer aborts a search. The word check indexed a
char table with a byte offset, so an introspection dump carrying one panicked
(index out of bounds) instead of answering. - The plural of a schema word isn't corrected into a lookalike either.
starwas protected bystar_countbutstarsstill becamestart; a
plural the schema really has (starts→start) still corrects.-eand
-Rnow judge that against the whole schema, as explaining already did. - A path-shaped query is fine once the source is named.
gqls schema.graphql 'read/write access'was refused as a bad source, though a second positional
can't be one; the same query on stdin was answered. - A path that isn't a schema source is refused.
gqls user ./schema.grapqhlfolded the typo into the query and answered from whatever
schema discovery found, exit 0. A positional containing/, or naming an
existing file, that isn't a schema source now exits 1 saying what a source
looks like. --returnsgiven an enum value or a directive says what it is
(ADMIN is a value of enum Role, not a type), as it already did for a field;
the field message no longer says an input field is "fetched".- A qualifier naming no type says the search went unscoped.
gqls Repo.namefell back to matching every type's members — documented, but
silent, so the scope looked applied. --fuzzy=xis dropped with the same warning as--fuzzy, rather than
clap's tip to search for the flag's own text.- 0.26.0's schema-word rule refused names it should have drafted. It
stopped a correction whenever the typed word was a word of any name —
including the target's own, sobinary -estopped draftingisBinary, and
published,stargazerand about 45 others on GitHub's schema went the same
way. A word of the target's own name, or its plural, now counts as naming
it. And_now separates words, sostaragainststar_countor
STAR_COUNTis protected from becomingstartthe waystarCountwas. --clear-cachecould delete files gqls doesn't own. 0.26.0 cleared
every file under the cache dir, and an empty or relativeXDG_CACHE_HOMEor
HOME— common in CI and containers — resolved that dir against the cwd, so
a project directory namedgqlslost its files; a symlink inside the cache
was followed too. The cache location now needs an absolute path, as the XDG
spec says, and clearing deletes only the kinds of file gqls writes (and the
.vecsolder releases wrote), never following a symlink. If you ran
--clear-cacheon 0.26.0 with either variable empty or relative, check the
directory you ran it from.--returnsgiven a field says so.gqls --returns User.nameanswered
nothing returns User.name— a claim about the schema, when the flag takes a
type. It now exits 1 naming what the field returns and pointing at
gqls User.name -e, which drafts a query that fetches it.
v0.26.0
Removed
- Semantic search. gqls is fuzzy-only now: one build, no ONNX Runtime, no
model download, no background embedding. It wasn't earning its keep: on 50
intent queries against GitHub's schema, the default fuzzy+semantic combine
ranked the right record lower than fuzzy alone (MRR 0.37 vs 0.42), and
semantic on its own only tied fuzzy on the synonym-style queries it existed
for.--semantic,--modeland--warmare now usage errors (exit 2)
that say why — drop them from scripts.--fuzzystill runs, since every
query is fuzzy now, but warns that it does nothing. Thesemantic/
semantic-dynamiccargo features and theGQLS_NO_AUTOWARM,
GQLS_MODEL_DIRandGQLS_SEMANTIC_FLOORvariables are gone too, and JSON
rows no longer carrydegraded. Rungqls --clear-cacheonce to reclaim the
embedding vectors an earlier release cached — it now empties the whole cache
directory, files it no longer writes included. The embedding model under
~/.cache/huggingfaceis left alone, since other tools may share it.
Fixed
- A word the schema already uses is no longer "corrected" into a lookalike.
gqls starexplainedCheckAnnotationSpan.startas a misspelling and hid
addStarbehind "76 other matches";gqls star -edrafted against it.
Across every word in GitHub's schema, 83 were corrected this way —hostto
RateLimit.cost,theto the country codeTH— and a few more, like
bot, stopped explaining the record they named exactly because a bogus
correction tied with it. A correction now applies only where the part it
corrects isn't a word of some name;stratandUsr.emailstill correct. - A miss that a filter caused counts its matches in plain English. It said
43 match without it, and1 matchesfor one. --profileJSON rounds milliseconds to two decimals rather than printing
float noise like0.10579200000000001.-ewarns only about deprecated fields the draft selects. A field left as
a commented# name: Type { … }hole, or dropped from an implementor's
fragment because the interface already selected it, was named in the
deprecated:line with nothing in the draft to find. On the GitHub schema at
--depth 2, 125 of 300 root drafts (42%) named at least one such field; none
do now. Fields that really are selected keep both their inline# deprecated:
flag and their place in the line.- The
deprecated:line no longer says(flagged inline). The drafted field
itself is deprecated often enough to matter and never carries an inline note —
a note goes on a selection, and the target is the operation. It reads
(drafted anyway)now, which says the thing worth saying: nothing was
silently dropped.
v0.25.0
Added
--viapicks the route-edraws. The walk settles each type at its
nearest hop, so on a schema with a global-ID lookup every type is one hop from
Query.nodeandgqls <gh> Issue.title -edrafts
node(id:) { … on Issue { title } }— the route through a repository wasn't
ranked low, it was never a candidate.--vianames a prefix of the route in
the notation# pathsprints, and the walk takes only the field named at each
of those hops:-e --via Query.repositorygives
repository(owner:, name:) { issue(number:) { title } }, and
--via 'Query.repository > Repository.issues'goes through the connection.
Hand back any path the listing prints, at any length, including an input's
Mutation.addStar(input:). It repairs a draft for someone who noticed the
route was silly —# pathslisting four generic ID lookups is the only tell —
and does nothing for someone who pastes the first answer. Fixing that default
is a weighted walk rather than a sort key, and is not this.
Fixed
- A deprecated field is named once in the
-ewarning, not once per place
the draft selects it. A type selected under two fields was listed twice; the
inline flags were right, the summary line wasn't.
v0.24.0
Changed
-
-esays when a draft is too large to paste. A selection set fans out by
the branching factor of the schema, so--depthis cheap to type and
expensive to render — GitHub'sRepositoryis 6KB at depth 1 and 200KB at
depth 3. The size is invisible until it has scrolled past, so a draft over
32KB now says how big it is, and names--depthwhen that's what got you
there. Not a cap: a large schema can legitimately want a large draft. The
threshold is measured — across four schemas every draft anyone would paste
came in under 9KB, and the smallest unusable one was 88KB. -
A fuzzy score is now the fraction of a perfect match, where perfect means
the query is the name — one scale, meaning the same thing whatever the
query's length, the name's length, or which branch produced it. A clean word
inside a longer name used to score an order of magnitude below a prefix match
of the same quality, so the weak-tail cut dropped it outright:gqls <schema> disputnever reachedMutation.in_app_disputesat any limit. It ranks 85th of
116 now — still past a default page, but findable.--json/--ndjsonconsumers: scores are smaller and differently shaped.
A perfect match is 1000, plus up to 300 where aType.qualifier names the
right parent; the old top was 1060. Note that only the fuzzy ranker writes on
this scale —--semanticreports a cosine in 0..1 and the default combine of
the two reports a rank-fusion score around 0.03, as both always did. Sort on
scorewithin one query; don't compare it across two. -
Kind — root field, type, leaf field — no longer adds to the score; it
breaks ties between matches of equal quality instead. -
A name that repeats the query, or ends with it, outranks a shorter name that
merely starts with it. On a schema where every name shares a prefix —
anything Hasura-generated —gqls <schema> pokemonreturned four unrelated
tables ahead ofpokemon_v2_pokemon, which sat at rank 21 with the object type
at 899. Measured over 2309 queries on four schemas, 292 changed top hits are
better and 17 worse by an independent referee; the table-lookup case went from
17 unfindable to 7. -
--profiletimes the network apart from the parse when the source is a
URL. Both were inside oneloadspan, so a slow run against a live endpoint
gave no way to tell a slow endpoint from slow gqls — on one measurement the
split was 481ms of network against 3ms of parsing. -
The one-time embedding pass no longer guesses "may take a minute". It
reports an estimate measured from the run's own rate, and now prints to
non-terminal stderr every 15s — a piped or CI run could not tell slow from
hung, and on the largest schema tested this is minutes, not one. -
--json/--ndjsoncarrydegraded: truewhen semantic ranking fell back
to the hash embedder; absent otherwise. The warning was stderr-only, which
never reaches a caller parsing stdout. -
Cached introspection responses are bounded by count and size, least
recently used evicted, rather than by age alone. -
-e/-Rsay out loud when several records are spelled exactly like the
query.idnamesCharacter.id,Location.idandEpisode.id; the pick
used to look settled. It still drafts and still exits 0 — the runners-up go to
stderr, and-e -jgainsalso_named([]when there was no choice), because
a stderr line doesn't reach a JSON consumer. -
-esays when one level of selection reaches no leaf at all. A Relay
connection has nothing but object-valued fields, so the default draft runs and
fetches nothing; the note points at--depth. The draft itself is unchanged.
Said only where--depthcan actually help: a namespace container whose
fields every one takes required arguments draws the same empty-looking
selection, but no depth selects through them — draft the inner field by name. -
--depthis capped at 6. A selection set fans out geometrically, so a
mistyped depth was a stack overflow or a gigabyte of stdout rather than a slow
answer: GitHub'sRepositorydrafts 1.8MB at depth 4 and 877MB at 7, chime's
User90MB at 12. Past the cap nothing is a document you could paste. A
--depthabove it now drafts at 6 and says so on stderr; scripts passing a
larger number get a smaller draft instead of a crash.
Added
--resolvefollows a namespaced root. A field onCardMutationRootlooks
forMutations::Card::ActivateCard, andQueries::/Subscriptions::are
tried besideResolvers::for root fields. Across six field shapes on a real
server repo the tally went from 1 clean hit, 1 near miss, 3 misses and 1
confidently wrong answer, to 5 clean hits, 1 honest miss and none wrong.-vsays when introspection can't report applied directives. The protocol
exposes directive definitions but not their applications, sodirectivesis
always empty from an endpoint or a dump however many@auths the SDL applies —
a limit of the protocol, not of the schema. Said only when the schema defines
directives of its own, so it stays quiet on the five every server defines.
@deprecatedis the exception and is reconstructed.
Fixed
-ecould overflow the stack on a legal schema, at the default depth. The
payload/errors convention expands anerrorsfield at the last level too, and
did it unconditionally — so a schema where the errors chain returns to a type
already being selected (Payload.errors -> UserError.errors -> Payload, or
justPayload { errors: Payload }) recursed until the process aborted with
exit 134. The convention still expands; it just no longer re-opens a type
open above it, which is a cycle with no level left to spend.gqlsannounced "no matches" over rows it had just printed. The miss
message was decided by the fuzzy match count, but the default ranking also
prints rows that matched by meaning alone — so stderr said the schema had
nothing while stdout listed fields, and an agent reading one reached the
opposite conclusion of an agent reading the other. Rows now get a message
that says what they are.- A single
-Jquery that matched nothing printed nothing at all. Zero rows
on a row-per-line stream is zero bytes, so the caller could not tell a miss
from a query that never ran, and thedegradedflag marking weak ranking had
nowhere to ride. It now emits{"status":"no_matches"}— withdegradedwhen
it applies — the way a batch always has.-jis unchanged: an empty array was
already a complete answer. - An argument answers when nothing matched a name, not when nothing matched
at all. The first pass matches names and qualified paths, so a weak
subsequence of someone else's path counted as an answer and suppressed the
argument pass entirely.gqls <github> untilreturnedCheckRun.titleat
0.128 of a perfect match and hid the seven fields that takeuntil:at 1.0 —
one unrelated type in the schema was enough to do it. Path matches still stand
when nothing takes the query as an argument either, and an argument still
never outranks a name:firstreturns the fields named for it, not the 348
that take one. - One long field name no longer indents every other row of a fields block.
The search table's columns were capped last round and the block you get by
naming a type wasn't, so a single generated name or return type set the
width for every row under it — a 35-column name and a 38-column type gave
every line of one real type an 81-column indent against an 80-column
terminal. Both columns are now capped the way the table's are (24 and 28,
the ninetieth percentile on two production schemas), and a cell wider than
its column overflows its own line instead of moving everyone else's. Across
those schemas the share of field rows running past 80 columns falls from 14%
and 24% to 5%. A schema with no outlying names renders unchanged. - The introspection cache ignored the credentials that fetched a schema. It
keyed on the URL alone, so the first response to succeed was replayed for every
later run against that URL whatever headers were supplied — or not supplied.
A revoked or rotated token kept working for up to an hour, a CI check existing
to assert auth is enforced passed falsely, and two callers sharing a URL with
different credentials could be served each other's schema. The key covers the
request headers now — sorted, names lowercased, hashed, never written to the
path. Entries cached under the old rule are unreachable and refetch once;
nothing for you to do. - A parsed-record cache file that won't decode says so under
-vbefore
falling back to reparsing, the way the introspection cache already did. -eleaked a multi-line deprecation reason out of its comment. A#
comment ends at the newline, so only the reason's first line stayed commented
and the rest landed in the selection set, where a server reads it as field
names. On one real schema that broke 80 of 2083 drafts — all from a single
field, because it's selected under every actor chain. Every free-form string a
draft quotes is flattened to one line now, including an optional argument's
block-string default, which was independently broken.- A type's fields block showed no directives. The type carried its own and a
named field carried its own, but the list between them carried none — so
working out which subgraph owns each field of a federated type meant naming
every field, one command each. Directives sit in the description's cell now,
after any(deprecated: …)marker, because the two are disjoint in practice:
across 14,806 field records in four schemas, exactly one carries both. A schema
whose fields have no applied directives renders exactly as before — which is
every schema loaded by introspection, since the protoco...
v0.23.3
Fixed
- Naming
QueryorMutationlisted no fields. A root type's fields carry
their operation's kind rather thanField, so the fields block skipped them —
leaving the one type whose fields are most worth listing showing none at all.
Added
- An argument's name finds the field that takes it.
gqls followRenames
found nothing: arguments aren't records, so their names were unsearchable and
the README promised otherwise. The field that takes one is now matched by it —
which is what you'd call anyway, and naming that field says what the argument
is for. It ranks below every name and path match and so only surfaces when
nothing else matched, which is what keeps a Relay schema's 348first
arguments from burying a search that meant a field. -ereaches an input object that only another input holds. An input is
drafted through the field that takes it, and one nothing takes was refused —
though it may still be held by one that something takes
(AddressValidationInput { address: AddressInput! }), which is exactly where
a caller needs to see it go. The draft is the outer input's, with the one you
asked about expanded where it sits inside the variables block, and a stderr
line saying which input carries it. That takes one production schema's
draftable input objects from 198 of 230 to all 230.--jsoncarries
passed_inside.
v0.23.2
Added
-
-ereaches a target through as many hops as it takes. It only ever
drafted a field or type one hop from a root operation field, which on a schema
that namespaces its roots (Query.early_pay: EarlyPayQueryRoot, with the real
fields hanging off that) is a small fraction of what's there. Across the two
production schemas gqls is swept against, 340 of 1737 types and 643 of 1102
drafted before; 1734 and 1101 do now, and everything still refused is
genuinely unreachable rather than too deep. It walks the type graph out from
the roots and nests through the shortest chain it finds, on both edges — a
field or type by what returns it, an input object by what takes it, which
takes one schema's draftable input objects from 168 of 230 to 198. The walk is
cycle-safe, prefers the chain asking fewest arguments among equals, and stops
at six hops — a cap neither schema reaches, since every type either one can
reach at all lands inside it. Past the cap the refusal names the distance, so
"too deep" reads differently from "not there".viaand each# pathsentry now spell the whole chain,Query.early_pay > EarlyPayQueryRoot.status, in text and--jsonalike. A one-hop path is
unchanged; the one label that moves is a nested input consumer's, which used
to name only the field taking the input and now carries the way in as well.Still refused: an input object that no field takes and only another input
object holds — a third edge, and 32 of that schema's 230. -
Arguments carry what they're for. Both loaders dropped argument
descriptions — the introspection query had always asked for them — so the one
thing a signature can't tell you was the one thing gqls couldn't show:
owner: String!never says it wants a login. Naming a field now lists its
arguments with the schema's prose for each,-eprints an# arguments:
block for the ones along the chain, and--jsoncarries them on both. Only
documented arguments appear; a block of bare names would restate the
signature. Arguments are still not searchable by name — they aren't records —
and the record cache re-reads once on upgrade, as it does whenever its format
gains a field. -
A piped query explains what it names. The explanation tier — the
annotations, and thematch/values/fields/referenced_bykeys — was
switched off for stdin, so the same query answered differently depending on
whether it arrived as an argument or down a pipe, with nothing saying so. A
pipe is how an agent drives gqls, which is where the fuller answer is worth
most.--no-explainstill forces the list. -
A
Type.query with nothing to enumerate answers with the type. A union
has no fields, sogqls 'SearchHit.'reported no matches while the union it
named sat one line away with its members and what references it. An
enumeration that finds something still never explains — a trailing dot is one
character from the type's own name, andQuery.means the root fields. -
Naming an object or an interface lists its fields. Only an input object
did, on the reasoning that an object's fields show up in a selection set
somewhere else — they don't, and reaching them meant a second search
(User.) that ranks and truncates rather than listing. Each field comes with
its type, description and deprecation; one taking arguments is marked
posts(…). Past a couple of dozen the list is elided with the command that
spells out the rest (… and 121 more —gqls 'Repository.' -l 145lists them all);--jsoncarries every field and every argument signature. -
-edrafts an operation for a type.gqls Animal -eused to answer
"can't draft an operation for a union". Asking about a type is asking how to
fetch one, so it now drafts the root that reaches it — narrowed to it where
that root returns something broader, sogqls Cat -eis
pets { ... on Cat { … } }. An enum or a scalar still can't be selected, but
says so pointing at--returns, which is the question with an answer. -
--returnswidens rather than dead-ending. Nothing returns the
Commentableinterface, so the filter answered nothing — while every field
returning aPosthands you one, and-edrafts exactly that. When no field
returns the named type outright, the filter now matches what narrows to it,
and says so on stderr. A wildcard is left alone, and so is a type something
already returns.
Fixed
- A deprecated object-valued field drafted an operation that doesn't parse.
The# deprecatednote was appended to the field name, so on a field with a
selection set the{landed inside the comment and the document came out
unbalanced. It fired at the default depth, since the errors convention is
always expanded: 8 of 130 root operations in one production schema were
affected. -edrafted operations a conformant server rejects. Two members of an
abstract type giving the same field name a different type — GitHub's
Organization.email: StringbesideUser.email: String!— were selected
under one response name, which the spec forbids (§5.3.2); graphql-js rejects
it outright and graphql-ruby warns that it will. Each is aliased by its
member now (userEmail: email). Every root of two production schemas, at
three depths, validates strictly.-eorphaned a selection set it meant to drop. A field an interface had
already selected was dropped from its implementors' fragments one line at a
time, so a field with a selection set left its body and closing brace behind
and the operation didn't parse.-edropped an interface's implementors. One whose added fields are all
object-valued has nothing but# field: Type { … }markers, and the
interface path dropped every marker — so the fragment came out empty and the
implementor vanished, with nothing saying it existed. A union in the same
position kept its markers, so the two abstract paths disagreed.-ldecided whether an answer was a list or an explanation. Whether a
query names exactly one record was read off the top-lrows, so the display
limit changed the answer and, on a big schema, which record got explained —
user -l 1,-l 2and-l 3each answered differently, one of them
explaining a deprecated field as an exact match out of 84. The decision now
runs over every record the filters admit, and the count of what it set aside
is out of the whole match set.--profileprinted nothing with-eor-R. Both return before the
report block.- The semantic index was promised when nothing was building it. With
GQLS_NO_AUTOWARMset — or when no warm could be spawned — the "building the
semantic index in the background" line printed every run regardless, forever. - An annotation row stayed a row.
implemented bywas uncapped while
referenced byelided past six, so GitHub'sNodeprinted 128 implementor
names.-Dalso swappedvaluesandreferenced by, though it only chooses
between an enum's block and collapsed forms. @join__*matched nothing. A pattern only addressed paths when it held a
., so pasting back a directive the way gqls prints it silently missed.--returns '[Card!]!'matched nothing. Wrappers came off the schema side
of the comparison but not off the flag, so the type as the schema writes it
was the one spelling that failed.-l 0reported "no matches" for a query with five of them: a miss is
nothing matching, not an empty page.- The README and
--helpclaimed arguments were searchable. They aren't
records, sogqls followRenamesfinds nothing; you reach an argument through
the field that takes it. The claim is gone rather than the gap papered over. --returnswith no QUERY reportedno matches for "*". The wildcard is
gqls's own — nothing the caller typed — so the miss now names the filter:
nothing returns Zork.-ereaches a field through the fragment that narrows to it. A field was
only drafted when some root field returned its enclosing type outright, so
Pet.nicknamereported "isn't reachable in one hop" whenever the only path
ran through a union or another abstract type — a query people write daily,
and onegqls Query.pets -ealready drafted. A root whose runtime types
overlap the field's parent now counts, and the draft says how:
pets { __typename ... on Pet { nickname } }. A root returning a type that
implements the interface still selects the field directly, with no
redundant fragment.- A truncated result list says so without
-v. TheN matches; showing top Mcount was a verbose-only diagnostic, so a list cut off at-l(20 by
default) read as the complete answer — naming a type with more fields than
that quietly lost the rest. It's normal stderr status now, and it no longer
fires when explain mode collapsed the list, which reports its own hidden
matches. - A piped batch now answers each query as it arrives.
read_queriesdrained
stdin to EOF before searching anything, soproducer | gqls schema.graphql -J
stayed silent until the producer closed — and never printed at all for a
producer that doesn't. The output bytes are identical either way, which is why
this went unnoticed; the new test asserts the timing instead.
v0.23.1
Fixed
-
Input field defaults were dropped at load time.
role: Role = MEMBERloaded as plain
Role, which left a defaulted field indistinguishable from a mandatory one —
direction: OrderDirection! = ASCmay be omitted, and the!on its own says
the opposite.SchemaRecordcarries adefaultnow, both loaders populate
it, and it shows beside the type:direction OrderDirection! = ASCwhen you
name the input, and"<OrderDirection! = ASC>"in an-eskeleton.
--jsoncarries it on input-field records and insidefields.On upgrade the parsed-record cache re-reads once, since its on-disk format
gained a field. Nothing to do — a miss costs milliseconds.
v0.23.0
Added
- Naming an input object lists its fields. It was the one composite kind
whose members the explanation never showed — an enum gave its values, a union
its members, an input object a single line — sogqls CreateUserInputsaid
nothing about what goes in one. Each field is listed with its type, its
description and its deprecation, and--jsoncarries the same under a
fieldskey. -edrafts an input object through the field that takes it. An input is
never callable but always passable, sogqls PostFilter -enow drafts
Query.posts(filter: $filter)instead of refusing. Other fields taking one
are listed under# paths; an input field (CreateUserInput.email) drafts
through its enclosing input. The argument carrying it is supplied even where
the schema calls it optional — a draft forPostFilterthat leavesfilter
out answers nothing. Such a draft stays about the input: the reply gets the
barest selection a server accepts, and only the named input's own types are
expanded, soPostFilterno longer spells out the three types hanging off a
sibling argument it doesn't fill in.--depth 1asks for the payload back.
Changed
-
The
-evariables block is a fillable skeleton, and# input types:is
gone."<CreateUserInput!>"never said anything the($input: CreateUserInput!)signature above it didn't, which is exactly why a second
block had to spell the type out in SDL — so the shape got printed twice, in
two notations, neither of which you could paste into a variables pane. An
input-object argument is now expanded into its fields, in the schema's own
order, inside the block you paste. A list gets one element; a self-reference
(Filter { and: [Filter!] }) closes as a placeholder, since its shape is in
the object containing it; an input the schema has no fields for stays a
placeholder rather than becoming a{}that claims it takes nothing.What's left over is what JSON can't express, and it's named for what it is:
# enums:lists the values for each enum the variables reach. And because an
expanded key stops naming its type, the heading does —# variables — input: CreateUserInput!.--jsonconsumers:input_typesis replaced byenums(a list of
"Role = ADMIN | MEMBER"strings) andvariable_types(a list of
"input: CreateUserInput!").variablesnow nests instead of holding a flat
type placeholder per key. -
--depth 0now means the barest valid selection rather than being
silently clamped to 1. It's what an input-object draft takes by default, and
what--depth 0always looked like it should do. -
referenced bycounts arguments, not just return types. It scanned what
returns a type and nothing else, so it reported nothing at all for input
objects, which are only ever passed in. An argument reference reads
Mutation.createUser(input:), naming where the value goes. Applies to every
kind:Rolenow shows@auth(requires:)too. -
The library API is a fifth of its former size — 148 public items down to
30.lib.rsre-exported all twelve modules, so most of that surface was
public by default rather than by decision — which the header above already
said was never the intent.logging,paths,profile,render,
resolve,semanticandstyleare now crate-private, along with 25 items
inside the five modules that callers do use.model::SchemaRecord,
model::Kind,load::sdl,load::LoadOptions,search::search,
search::Hit,search::Filters,example::buildandcli::runstay public.
Nothing to do on upgrade: the gqls binary and its CLI surface are unchanged.
Removed
semantic::search, a one-shot convenience wrapper, has never been called —
the CLI builds its ownSession. It was dead from the initial commit and
invisible todead_codeonly becausesemanticwas a public module.
Fixed
- A drafted selection set is never all comments. A type whose fields are
every one object-valued got a selection set holding only# field: Type { … }
markers, which no server will parse. It now selects__typenamealongside
them, the same answer already used for a type the schema doesn't detail. paths::temp_diris gated on_semantic, matching its only caller. It was
compiled, and dead, in the fuzzy-only build.
Internal
unreachable_pubis on. Apubitem inside a private module is reachable
from nowhere, andpubis precisely what makesdead_codeskip an item — so
the two together were hiding the two entries above.