Repository navigation
fix(deps): update module github.com/iodesystems/gwag/v2 to v2.9.0 - #17
Merged
Merged
Conversation
Contributor
Author
ℹ️ Artifact update noticeFile name: go.modIn order to perform the update(s) described in the table above, Renovate ran the
Details:
|
renovate
Bot
force-pushed
the
renovate/github.com-iodesystems-gwag-v2-2.x
branch
from
October 30, 2026 20:35
e7b1ff7 to
10a3c66
Compare
renovate
Bot
force-pushed
the
renovate/github.com-iodesystems-gwag-v2-2.x
branch
from
October 31, 2026 21:46
10a3c66 to
6d16d24
Compare
renovate
Bot
force-pushed
the
renovate/github.com-iodesystems-gwag-v2-2.x
branch
from
November 1, 2026 01:36
6d16d24 to
1d93061
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
v2.1.0→v2.9.0Release Notes
iodesystems/gwag (github.com/iodesystems/gwag/v2)
v2.9.0Compare Source
Relay deploys and sub status:
Relay.Shutdowncloses sockets cleanly, and the generated runtime reports each subscription's real state and retries failures.Added
Relay.Shutdown(ctx)sends close 1001 ("server restarting") to every socket, closes them, and answers new ones with 503, so a deploy is not a 1006 for every client.http.Server.Shutdowndoes not touch hijacked connections: call it fromsrv.RegisterOnShutdown.useSubreturns{status, error}, andsubscribeRawtakes anonStatecallback:pending,subscribed(the relay acked), orfailed(the mint threw or the relay refused the cap).setMintRetrytunes the retry.Fixed
errframe was ignored. Both now retry with jittered backoff, 2 s to 60 s, while the sub is mounted and the socket is open.v2.8.0Compare Source
Relay liveness and cheap compression: a
pingop, a heartbeat and live state in the generated runtime, andWithCompression, which compresses a fan-out once rather than once per socket.Added
relay.WithCompression()negotiates permessage-deflate (no context takeover) on the multiplex relay's sockets. Off by default. A data frame is compressed once per event and sub id and shared among sockets, so a fan-out does not compress per socket.pingop. A client frame{"op":"ping","id":…}gets{"op":"pong","id":…}, so a browser (which cannot send WebSocket ping control frames) can keep a socket alive through a proxy idle timeout and detect a dead one. Before this, the relay answeredpingwith{"op":"err","err":"unknown op"}.useLiveSocket,subscribeLive,liveSnapshotandsetHeartbeat. React 18 or later is required foruseLiveSocket.<channel>#<n>, so sockets on a channel can share compressed frames. No relay change is needed; a mismatch of runtime and relay versions works.v2.7.0Compare Source
OpenAPI 3.2 ingest, a 3.0 / 3.1 export option with down-conversion, base64 validation, and one pub/sub transport under gat and gwag.
Migration
format: byteoutsidemultipart/form-datais now a GraphQLString, notUpload, andcontentEncoding: base64ingests the same way. Clients pass base64 text. It used to reach the upstream as a multipart body or query value holding a Go struct.format: byteinmultipart/form-datais stillUpload.bytesfields are base64 both ways (proto3 JSON). GraphQL args used to take the raw string, and output was base64 on gwag's default path but the raw string on gat and gwag's map path. Clients that send raw text to abytesarg must base64-encode it; gat clients readingbytesoutput must decode it. Input accepts standard or URL-safe base64, padded or not. Upload-markedbytesfields are unchanged.ps/v1appears in the schema, admin service lists and MCP tools withoutWithCluster. Tests that count services or slots see one more.WithChannelAuthpatterns with a>before the last token, or an empty token, makeNewpanic. Writea.>(rest of the channel) ora.*.b(one token).Added
format: byteorcontentEncoding: base64are checked before dispatch.WithValidation("strict"|"warn"|"off")(gwag),Validation(...)(gat);strictis the default, so bad base64 fails the call without reaching the upstream.WithOpenAPIVersion("3.0"|"3.1")(gwag),OpenAPIVersion("3.0"|"3.1")(gat) or?openapi=3.0|3.1write every spec in that version.3.1writes"openapi": "3.1.0"with JSON Schema 2020-12 forms:nullablebecomes a"null"type entry, boolean exclusive bounds become numbers, a schemaexamplebecomesexamples; a 3.2 source is converted down.3.0converts 3.1 and 3.2 sources (huma writes 3.1.0) down to"openapi": "3.0.3", for client generators that read only 3.0; what 3.0 cannot express is kept inx-gwag-oas31,x-gwag-oas32,x-webhooks,x-additional-operationsandx-item-schema. Without the option or parameter the output is unchanged: synthesized specs 3.0.0, OpenAPI sources as declared. Newir.RenderOpenAPIWith,ir.RenderOpenAPIOptions{Version},ir.ParseOpenAPIVersion,ir.OpenAPI30Version. See docs/gat.md "OpenAPI version" for every converted construct and which are lossy.x-additional-operations(QUERY and custom methods) andx-item-schema(streamed responses), so a 3.0 spec written by gwag registers with the same operations as its source, on GraphQL, gRPC and REST ingress.queryandadditionalOperations. A QUERY operation becomes a GraphQL Query field with abodyarg; a custom method becomes a Mutation. gwag, gat and REST ingress send the declared method and body upstream. HEAD / OPTIONS / TRACE / CONNECT operations are still skipped, now with a warning at registration (newir.UnsupportedOpenAPIOperations). Cross-kind OpenAPI 3.0 export puts these methods under thex-additional-operationsPath Item extension.itemSchemaontext/event-stream,application/jsonl,application/x-ndjsonorapplication/json-seqingests as a subscription (one upstream request per subscriber), in gwag and in gat BYO-IR registrations. gwag also relays it onIngressHandlerin the declared media type and serves it as a server-streaming gRPC method; streams count againstMaxStreams/MaxStreamsTotal. An operation that also offersapplication/jsonstays unary. Newir.OpenAPIStreamOf,ir.OpenAPIStream,ir.NewOpenAPIStreamReader(experimental). See docs/gat.md "Streamed responses from an upstream spec".gat.Bus(experimental): one pub/sub transport interface under gat'sPubSuband gwag'sps.pub/ps.sub. Implementationsgat.NewLocalBus,gat.NewPeerMeshBus(whatEnablePeerMeshinstalls), andGateway.UseBusto plug one in.gat.MatchChannelis the shared NATS-style matcher;gw/gat/bustestis a conformance suite for new buses.gw/gat/gatnats(experimental):gatnats.New(nc)is agat.Busover a NATS connection, in its own package sogatdoes not import nats.go.WithClusterinstalls thepsslot and servesps.pub/ps.subin-process.ps.pub/ps.subdeliver on this node only and switch back to NATS on reconnect. New gaugego_api_gateway_pubsub_bus_degraded;HealthHandlerbody gainspubsub(nats/local/local_degraded) anddegraded: truewhile degraded (status code unchanged). See docs/pubsub.md "Transport".Changed
$ref(allOf: [$ref]or a one-branchoneOfwithnullable: true, or the 3.1oneOf: [$ref, {type: null}]) now becomes a nullable field of the referenced type. It used to fall back to a scalar, so the GraphQL type of such fields changes.MaxStreams) now carriesextensions.codeon the GraphQLerrorframe (was message only).ps/v1slot: the schema includesps.pub(Query) andps_sub/ps_stable_sub(Subscription), andps/v1appears in admin service listings, service stats, and the MCP schema tools, as it already did in cluster mode.WithChannelBindingwithout a cluster no longer panics inNew.>that is not the last token (e.g. inWithChannelAuth/WithChannelBinding) now match nothing, as in NATS and gat. gwag used to treat such a>as "match the rest".Newnow panics on such aWithChannelAuthpattern (or one with an empty token): the rule would match nothing, and its channels would fall back to the default HMAC tier, which can be weaker than the tier the rule declared.Fixed
xs: Int), so a list could not be sent. They are now[T]args, and map fields take the JSON scalar.optional, proto3optional, oneof members) read as zero values on gwag's default GraphQL path, and a proto2 enum without a 0 value read as0. They now readnull, as on the map path and in protojson.map<K,V>fields in gRPC args and responses (gwag and gat) panicked in the codec. They now convert as a JSON object: string keys, values in proto3 JSON form (enum names, base64 bytes, 64-bit integers as strings).uint64/fixed64args above 2^63-1 were refused, and negative values wrapped. They now parse over the full unsigned range, and a negative value is an error.uint32args refuse negative values too.ClusterOptions.ClientListenwith port 0 now picks a free port. natsd read 0 as its default 4222.ps.pubas aMutationreturningBoolean!andps.subnested underSubscription.ps. The real fields areQuery.ps.pub(...)returningGwag_Ps_V1_PubResponseandSubscription.ps_sub(...), withtsas a string andpayloadTypein camelCase.v2.6.0Compare Source
gwag's GraphQL and gRPC ingress now run on gat's: one request path, so gwag gains batching, Connect and gRPC-Web.
Migration
variablesthat is not an object.MaxOperationsrefuses the whole request. This applies to a document or to a batch counted as a whole. Before, the first n operations ran and the rest failed. RaiseMaxOperationsif a client sends more.*connect.Erroror a gRPC status keep that code where they got Internal.PERMISSION_DENIEDinextensions.code(none before).Added
WithMaxOperations(n)also caps the batch: at mostndocuments, and at mostnoperations summed over them (over the cap, the whole batch is refused with 400 and nothing runs). (The GraphQL handler now runs on gat's request path.)PreRequest,WSUpgrade,GraphiQL,ParseContentType,AcceptStringVariables,UsePlanCache,EchoCompletelet a host wrap gat's GraphQL handler; see docs/gat.md "Ingress hooks". Newgat.Request,gat.RequestHook,gat.RequestParser.ConnectHandler()(experimental) serves the routesGRPCUnknownHandlerserves over Connect, gRPC-Web and gRPC on anhttp.Handler. Both now run on gat's gRPC ingress; see docs/operations.md "gRPC, Connect and gRPC-Web".SetGRPCRoutesserves host-supplied methods (gat.GRPCRoute) over Connect, gRPC and gRPC-Web (GRPCHandler) and on a grpc-go server (GRPCStreamHandler); the table swaps atomically.PreCallhooks every gRPC call;gat.ErrorCodeis the one error-to-code mapping both use. See docs/gat.md "gRPC routes you supply".WithConnectErrorCodes()(experimental). GraphQL dispatch errors carry gat's extensions shape,{status, code: "not_found"}(lower-case gRPC code, as the gRPC and REST ingresses classify the error), instead of{code: "NOT_FOUND"}. Default stays upper case until v3. Newir.RuntimeOptions.WrapError.gat.HTTPStatus(code)(experimental). A gRPC / Connect code as an HTTP status, by the Connect protocol's table.Changed
variablesthat are not an object (a JSON string holding one is still accepted; invalidvariableson GET included) now answer 400 instead of 200 with a GraphQL error or silently dropped variables.WithUploadLimitanswers 413 (was 400). On POST,query/variablesin the URL no longer override the body. A WebSocket upgrade without thegraphql-transport-wssubprotocol is accepted and closed with 1008 (was served as an HTTP request).MaxOperationsrefuses the whole document. A document selecting more operations than the cap now fails validation and nothing runs (was: the firstnran and the rest got field errors). In a batch the cap counts the operations of all its documents together: a batch over it is refused whole with 400.gat.ErrorCode): 409 AlreadyExists (was InvalidArgument), 412 FailedPrecondition, 413 ResourceExhausted, 501 Unimplemented, 502/503/504 Unavailable (were Internal). GraphQLextensions.codeis unchanged.IngressHandleranswers a failed dispatch with the upstream's own HTTP status (was: 4xx other than 400/401/403/404/429 became 400, every 5xx became 500). An error with no HTTP status usesgat.HTTPStatus(gat.ErrorCode(err)): a gRPC upstream's status picks the REST status (was 500 for all); a cancelled or timed-out call is 499 / 504 (was 500).*connect.Erroror carries a gRPC status keeps its code (was Internal unless it had an HTTP status);context.Canceled/DeadlineExceededanswer Canceled / DeadlineExceeded (was Internal). Wrapped errors are unwrapped.ps.pub/ps.subrefused by channel auth (HMAC or delegate) now fails with PermissionDenied: GraphQLextensions.codePERMISSION_DENIED(was none), gRPC PermissionDenied, REST 403. Messages are unchanged.extensions.code(BAD_REQUEST), as gwag's does.Fixed
upstream timeout aftermessage, and no count toward replica ejection.v2.5.0Compare Source
Migration
WithMaxOperations(n)/WithMaxRequestBytes(n), or pass 0 to turn a limit off. gat had these defaults already.NOT_READYinstead of 200 with an error.Added
WithMetricPrefix(prefix). Opt in togwag_*Prometheus metric names; default staysgo_api_gateway_*until v3.WithMaxOperations(n),WithMaxRequestBytes(n),WithMaxDepth(n),WithoutIntrospection()on the GraphQL endpoint; see docs/operations.md "Request limits". Shared with gat (gw/internal/gqlguard).WithTrustedDocuments(docs, enforce)andParseTrustedDocuments: persisted-document ids, Apollo APQ and GET by id, as in gat.Timeout(d)(per service),WithUpstreamTimeout(d)(gateway default) andServiceBinding.timeout_ms(control plane) bound unary dispatch togRPC, OpenAPI and downstream-GraphQL upstreams. Off by default.
reached an upstream (connect refused, no connection). Delivered
requests, 5xx and timeouts are never retried.
WithReplicaEjection(failures, cooldown): areplica with 5 consecutive failures (default) gets no traffic for
10 s (default). Fails open when every replica is ejected.
gat.IntegerScalar(name, desc)for a Go type'shuma.SchemaProvider, orx-gwag-scalar: <Name>on an OpenAPI integer schema, renders that field as its own GraphQL scalar: a JSON number exact within ±(2^53-1), input as number or decimal string. REST stays integer/int64, proto int64. Newir.TypeRef.ScalarName,ir.ScalarExtension.VersionedOnly(true)(experimental). Render each GraphQL operation once, atns.v1.op, without the unversionedns.opcopy. Default off. Newir.RuntimeOptions.VersionedOnly.SetSchema(experimental). Serve a host-assembled schema and replace it at run time; in-flight requests keep the schema they started with. A gat handler with no schema yet answers 503NOT_READY(was 200 with a "Must provide schema" error).Security
WithMaxOperations(0)/WithMaxRequestBytes(0).?query=executed the query server-side, outside the plan cache, so it skipped the request limits and the no-mutations-over-GET rule: a link could fire a mutation with the visitor's cookies. The page now only prefills the query.Fixed
uint64/fixed64field no longer panics on the map conversion path (gRPC ingress, subscriptions, pub/sub, runtime transforms); it renders as a decimal string.v2.4.0Compare Source
Added
@exportwithin one document. A field reading a variable thatanother field of the same query exports runs after it: gat splits the
document into stages at plan time and merges their data into one
response in query field order. One request, one result type.
@export. A POST body that is an arrayof operations runs them in order;
@export(as: "x")on a field passesits values (deduplicated; a list under a list) to the operations after
it as
$x. The answer to "fetch these, then the things they reference"in one round trip, without links or nested resolvers.
ir.RuntimeOptions.Directivesdeclares extra directives on therendered schema;
ir.PrintSchemaSDLprints them.gat.Attrsis a bag scoped to theinbound request (GraphQL HTTP request, gRPC call, graphql-ws
connection) that every fused operation shares;
gat.LockIfAbsentAndFillcomputes a value once while concurrentcallers wait, so a session lookup runs once per request, not once per
operation.
gat.AttrsMiddlewarescopes REST requests.ParallelQueries(n)runs up tonqueryoperations of one GraphQL request concurrently; mutations stay
serial. Off by default.
MaxOperations(n)caps operations per GraphQLrequest (aliases included; excess ones fail with 400, not dispatched).
MaxRequestBytes(n)caps the GraphQL body.TrustedDocuments(docs, enforce)servesrequests naming a document by
documentId, the APQsha256Hashextension, or
?documentId=over GET;enforcerefuses query text.ParseTrustedDocumentsreads graphql-codegen's persisted-documentsmanifest.
Cache-Control,ETag,Last-ModifiedandVaryfrom each operation's huma outputmerge into the GraphQL response: identical values pass through,
otherwise the most restrictive combination; ETags combine into one
weak ETag. GET with a matching
If-None-Matchgets 304.MaxDepthandDisableIntrospection. Validation rules run bythe plan cache, so each distinct query is checked once.
DisableIntrospectionalso stops serving{prefix}/schema/*.HookDispatch. A per-operation hook that receives therequest context and returns the one the handler sees, plus a done
callback with the error — for trace spans. No tracing dependency.
Security
GET /graphql?query=mutation…ran the mutation upstream, cookies forwarded by
ForwardHeadersincluded. It now gets 405 before execution, as GraphQL over HTTP
requires.
GET {prefix}/graphql?query=mutation…ran the mutation, so any page could trigger one with a visitor's
cookies (a link or an
<img src>;SameSite=Laxstill sends cookieson top-level GETs). A GET whose operation is a mutation now gets 405
before anything runs; GraphQL over HTTP forbids them there.
Fixed
type. Ingest dropped it:
?ids=a&ids=bbecame aStringargumentand a JSON-array response a single object (fields read as null), in
gat and gwag, over GraphQL and gRPC. Outbound OpenAPI calls now send a
list argument as repeated keys instead of
ids=[a b], and gat's gRPCingress answers list operations under the response message's
value.variablesandextensions. They wereignored, so a GET query with variables failed.
ObserveDispatchnow fires for gRPC calls. gRPC ingressinvoked handlers directly and was never observed; observation now
wraps the handler invocation both surfaces share.
Changed
array responses (the fix above):
ids: String→ids: [String!],listThings: Thing→listThings: [Thing!]. Regenerate clients.Gateway,New,ServiceRegistration,Handler,Register,RegisterHuma,RegisterSSE,RegisterGRPC,HandleMux, and the/graphql,/schema/*and gRPC routes. Therest of
gw/gatstays experimental (docs/stability.md).(
MaxOperations); excess aliases fail with 400 instead ofdispatching.
MaxOperations(0)removes the cap.MaxRequestBytesdefault), matching huma's REST limit for the same operation. Set
MaxRequestBytes(0)for no limit.PlanCacheOptions.Rules,Plan.IsMutation.adapter's own request on humago/humachi/humamux/humaflow, and binary
proto requests decode straight into the input struct (gatbench:
GraphQL Get 88 → 74 allocs/op, Connect proto Get 69 → 59).
v2.3.1Compare Source
Security
and body schema validation (
maxLength,pattern,minimum,enum,formats, ...) and input
Resolvemethods ran over REST only; GraphQLand gRPC-ingress calls (unary,
RegisterSSE,Subscribe) reached thehandler with input REST rejects. A failure is now a 422, or the status
a Resolver error carries, as GraphQL
extensions.statusor the mappedgRPC code.
v2.3.0Compare Source
Added
RegisterSSE, the paired drop-in for huma'ssse.Register.One handler serves REST (Server-Sent Events), a GraphQL subscription
over
graphql-transport-ws, and a gRPC server-streaming method onRegisterGRPC, behind the op's huma middlewares. Several event typesrender as a GraphQL union and a proto message with one
oneof.Deprecated
Subscribe. UseRegisterSSE, which defines the stream inhuma and also serves it over REST.
Fixed
oneOf) produced a descriptor set that failed to build(
"….AB" not found), so gat'sRegisterGRPCfailed for it. Aunion now renders as a message holding one
oneof value.Subscribestreams over gRPC. v2.2.0 listed them asserver-streaming methods in
/schema/protobutRegisterGRPCdidnot mount them; they are now served.
v2.2.0Compare Source
Added
Subscriberegisters GraphQL subscriptions.gat.Subscribe[I, O]takes a huma operation and a source
func(ctx, *I) (<-chan *O, error);the field is served over
graphql-transport-wson{prefix}/graphql(WebSocket upgrade on
GET), with the op's huma middlewares applied.gat.ConnectionInitPayload(ctx)exposes theconnection_initpayload.v2.1.3Compare Source
Security
registered with
gat.Registercalled its handler directly over/graphqland gRPC ingress, skipping bothapi.UseMiddlewareandhuma.Operation.Middlewares. An op guarded only by a huma middleware(auth, role check) was open on those surfaces. The chain now runs in
huma's order; a middleware that writes an error instead of calling
nextbecomes a GraphQL error (withextensions.status) or themapped Connect code. Middlewares see the transport request's headers,
Method()is the op's method,Param()returns"", andBodyReader()is empty.v2.1.2Compare Source
Fixed
"now parse. A one-line descriptionlike
e.g. "match 0.97"rendered as"""…"match 0.97"""", whichcloses the block string early; graphql-js and codegen rejected the
whole schema. Such descriptions now use the multi-line form. An
embedded
"""is now escaped as\"""(was\"\"\", which putthe backslashes into the parsed description).
RetractStableand clusterRegistersurvive a KV replicachange. The gateway raises the KV buckets' replica count as peers
join; a put sent during that change could get no ack and fail after
5s. For a retract, the local retract stayed applied but was not
persisted; for a registration, the call failed. Both puts now retry
with a 2s per-attempt timeout inside a 10s budget. Heartbeat TTL
refreshes retry inside their existing 5s budget.
Changed
v2.15.1, nats.go v1.54.0, grpc v1.84.0, otel v1.47.0, mcp-go v1.2.1,
prometheus client_golang v1.25.0, protoreflect v1.19.0 (v2 module
v2.0.0 stable), golang.org/x/net v0.61.0. No source changes.
errors now read as the error text joined with "; ".
v2.1.1Compare Source
Fixed
graphql-go v2.0.0 → v2.0.1.
runSubscriptionstops reading once itscontext is cancelled; graphql-go's producer then blocked forever on
its next result send, and its result channel never closed.
Closeno longerhangs. If the initial self-registration Put failed (for example,
timing out while JetStream settles at boot), the half-started tracker
stayed installed. The boot retry loop saw it and stopped, so the
gateway ran without peer refresh, the reconciler or KV watches, and
Closeblocked forever waiting on loops that never started.Closealso no longer hangs when it runs while that start is still in flight,
as it can against the background start
ControlPlanelaunches.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.