From 78aa671f1faaf53cf99e3e4e91beb454f85f8b8f Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:30:17 +0000 Subject: [PATCH 1/4] Documentation edits made through Mintlify web editor --- docs/router/custom-modules.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/router/custom-modules.mdx b/docs/router/custom-modules.mdx index ef735d70..4706418d 100644 --- a/docs/router/custom-modules.mdx +++ b/docs/router/custom-modules.mdx @@ -17,6 +17,7 @@ The Cosmo Router can be easily extended by providing custom modules. Modules are - `core.EnginePostOriginHandler` Implement a custom handler executed after the request to the subgraph but before the response is passed to the GraphQL engine. This handler is called for every subgraph response. **Use cases:** Logging, Caching. - `core.Provisioner` Implements a Module lifecycle hook that is executed when the module is instantiated. Use it to prepare your module and validate the configuration. - `core.Cleaner` Implements a Module lifecycle hook that is executed after the server is shutdown. Use it to close connections gracefully or for any other cleanup. +- `core.SubscriptionOnStartHandler` Implements a custom handler that is executed before a subscription is started. It allows you to verify if the client is allowed to start the subscription and also send initial data to the subscription. Use cases: send an initial message to the client, custom subscription authorization logics `*OriginHandler` handlers are called concurrently when your GraphQL operation @@ -28,11 +29,10 @@ The Cosmo Router can be easily extended by providing custom modules. Modules are - `RouterOnRequestHander` is only available since Router - [0.188.0](https://github.com/wundergraph/cosmo/releases/tag/router%400.188.0) - + `RouterOnRequestHander` is only available since Router [0.188.0](https://github.com/wundergraph/cosmo/releases/tag/router%400.188.0) -## Example + `SubscriptionOnStartHandler` is only available since Router 0.X.X + The example below shows how to implement a custom middleware that has access to the GraphQL operation information. From fc93e13d9972420e18a3156c2ee5ad56e347cee0 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 08:06:08 +0000 Subject: [PATCH 2/4] Documentation edits made through Mintlify web editor --- docs/router/custom-modules.mdx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/router/custom-modules.mdx b/docs/router/custom-modules.mdx index 4706418d..6f76b402 100644 --- a/docs/router/custom-modules.mdx +++ b/docs/router/custom-modules.mdx @@ -17,7 +17,7 @@ The Cosmo Router can be easily extended by providing custom modules. Modules are - `core.EnginePostOriginHandler` Implement a custom handler executed after the request to the subgraph but before the response is passed to the GraphQL engine. This handler is called for every subgraph response. **Use cases:** Logging, Caching. - `core.Provisioner` Implements a Module lifecycle hook that is executed when the module is instantiated. Use it to prepare your module and validate the configuration. - `core.Cleaner` Implements a Module lifecycle hook that is executed after the server is shutdown. Use it to close connections gracefully or for any other cleanup. -- `core.SubscriptionOnStartHandler` Implements a custom handler that is executed before a subscription is started. It allows you to verify if the client is allowed to start the subscription and also send initial data to the subscription. Use cases: send an initial message to the client, custom subscription authorization logics +- `core.SubscriptionOnStartHandler` Implements a custom handler that is executed before a subscription is started. It allows you to verify if the client is allowed to start the subscription and also send initial data to the subscription. **Use cases:** send an initial message to the client, custom subscription authorization logics `*OriginHandler` handlers are called concurrently when your GraphQL operation @@ -261,6 +261,27 @@ func (m *SetScopesModule) RouterOnRequest(ctx core.RequestContext, next http.Han } ``` +## Send an initial message in the subscription + +When a subscription is started you could need to send an initial message to the client, even if nothing is coming from the subgraph or edfs provider. In these cases you can implement a `SubscriptionOnStartHandler` . + +Lets say that you have a GraphQL subgraph that defines the following subscription: + +```tsx +type Subscription { + +} +``` + +```go +func (m *InitialMsgModule) Middleware(core.RequestContext, next http.Handler) { + // Set the authentication scopes + ctx.SetAuthenticationScopes(customScopes) + + next.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) +} +``` + ## Return GraphQL conform errors Please always use `core.WriteResponseError` to return an error. It ensures that the request is properly tracked for tracing and metrics. From 7078293bd19646b20e1fe4d5aa1758d6f16442b9 Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Mon, 25 Aug 2025 11:20:02 +0200 Subject: [PATCH 3/4] docs: enhance custom modules documentation for subscriptions * Updated the `SubscriptionOnStartHandler` section to clarify its usage and provide examples for handling initial messages and permission checks. * Corrected the formatting of the GraphQL subscription example. * Added explanations for improved user experience when dealing with delayed subscription messages. --- docs/router/custom-modules.mdx | 86 +++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/docs/router/custom-modules.mdx b/docs/router/custom-modules.mdx index 6f76b402..126de3d0 100644 --- a/docs/router/custom-modules.mdx +++ b/docs/router/custom-modules.mdx @@ -31,7 +31,7 @@ The Cosmo Router can be easily extended by providing custom modules. Modules are `RouterOnRequestHander` is only available since Router [0.188.0](https://github.com/wundergraph/cosmo/releases/tag/router%400.188.0) - `SubscriptionOnStartHandler` is only available since Router 0.X.X + `SubscriptionOnStartHandler` is only available since Router [0.X.X](https://github.com/wundergraph/cosmo/releases/tag/router%400.X.X) The example below shows how to implement a custom middleware that has access to the GraphQL operation information. @@ -267,18 +267,86 @@ When a subscription is started you could need to send an initial message to the Lets say that you have a GraphQL subgraph that defines the following subscription: -```tsx +```graphql type Subscription { - + matchScore(matchId: Int!): Score! @edfs__kafkaSubscribe(topics: ["scores"], providerId: "my-kafka") } ``` +When a client subscribes to the `matchScore` subscription, it will receive the score as soon as a message is published on the `scores` topic. +But if the first message is coming late, the subscribed client could remain without data for a long time. +To improve the user experience you could send an initial message to the client when the subscription is started, calling an external service +to get the initial score. + ```go -func (m *InitialMsgModule) Middleware(core.RequestContext, next http.Handler) { - // Set the authentication scopes - ctx.SetAuthenticationScopes(customScopes) +func (m *InitialMsgModule) SubscriptionOnStart(ctx core.SubscriptionOnStartHookContext) { + // Check if the subscription is the one you want to handle + if ctx.SubscriptionEventConfiguration().RootFieldName() != "matchScore" { + return + } - next.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) + // Get the matchId from the subscription + matchId := ctx.Operation().Variables().GetInt("matchId") + + // Call the external service to get the initial score + scores, err := m.getInitialScore(matchId) + if err != nil { + return + } + + // Send the initial score to the client + // The message should follow the same schema of other messages sent in the topic + ctx.WriteEvent(&kafka.Event{ + Data: []byte(fmt.Sprintf(`{"teamAName": %s, "teamBName": %s, "teamAScore": %d, "teamBScore": %d, "_typename": "Score"}`, scores.TeamAName, scores.TeamBName, scores.TeamAScore, scores.TeamBScore)), + }) +} +``` + + +## Stop subscription if the client has not the right claim + +You might like to have additional permissions check to decide if a client is allowed to subscribe to a subscription. +In these cases you can implement a `SubscriptionOnStartHandler` to check the permissions and stop the subscription if the +client has not the right permissions. + +Lets say that you have a GraphQL subgraph that defines the following subscription: + +```graphql +type Subscription { + matchScore(matchId: Int!): Score! +} +``` + +When a client subscribes to the `matchScore` subscription, and if the client has not the right claim, the subscription +will not be started and the client will receive an error. + +```go +func (m *StopSubscriptionModule) SubscriptionOnStart(ctx core.SubscriptionOnStartHookContext) { + rootFieldName := ctx.SubscriptionEventConfiguration().RootFieldName() + if rootFieldName != "matchScore" { + return nil + } + auths := ctx.Authentication() + if auths == nil { + return core.NewStreamHookError( + errors.New("client does not have authentication set"), + "client does not have authentication set", + http.StatusUnauthorized, + http.StatusText(http.StatusUnauthorized), + ) + } + // Get the claims from the authentication + claims := auths.Claims() + if val, ok := claims["can_subscribe"]; !ok || val != "true" { + return core.NewStreamHookError( + errors.New("client does not have the right claim"), + "client does not have the right claim", + http.StatusUnauthorized, + http.StatusText(http.StatusUnauthorized), + ) + } + + return nil } ``` @@ -303,6 +371,10 @@ The current module handler allow to intercept and modify request / response subg Incoming client request │ └─▶ core.RouterOnRequestHandler (Early return, Custom Authentication Logic) + │ + └─▶ "If the request starts a subscription" + │ + └─▶ core.SubscriptionOnStartHandler (Early return, Custom Authentication Logic) │ └─▶ core.RouterMiddlewareHandler (Early return, Validation) │ From 03109d43374393f6bf43da59093b576d8afbc92f Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Mon, 25 Aug 2025 11:49:18 +0200 Subject: [PATCH 4/4] docs: fix request handler lifecycle --- docs/router/custom-modules.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/router/custom-modules.mdx b/docs/router/custom-modules.mdx index 126de3d0..b6b075c2 100644 --- a/docs/router/custom-modules.mdx +++ b/docs/router/custom-modules.mdx @@ -371,13 +371,13 @@ The current module handler allow to intercept and modify request / response subg Incoming client request │ └─▶ core.RouterOnRequestHandler (Early return, Custom Authentication Logic) - │ - └─▶ "If the request starts a subscription" - │ - └─▶ core.SubscriptionOnStartHandler (Early return, Custom Authentication Logic) │ └─▶ core.RouterMiddlewareHandler (Early return, Validation) │ + └─▶ "If the request starts a subscription" + │ + └─▶ core.SubscriptionOnStartHandler (Early return, Custom Authentication Logic) + │ └─▶ core.EnginePreOriginHandler (Header mods, Custom Response, Caching) │ └─▶ "Request to the subgraph"