Return false instead of throwing when event stream does not exist (#20165)

## Summary

- When an event stream expires (TTL), `addQueryToEventStream` and
`removeQueryFromEventStream` now return `false` instead of throwing
`EVENT_STREAM_DOES_NOT_EXIST` as an `InternalServerError`
- Frontend checks the mutation return value and triggers the
destroy/recreate cycle, same recovery behavior without the error path
- Removes `EVENT_STREAM_DOES_NOT_EXIST` from exception code, exception
filter, and frontend graceful error check since it's no longer thrown

## Test plan

- [x] Verify that when an event stream TTL expires, the frontend
silently recreates the stream without error noise in logs/Sentry
- [x] Verify that `NOT_AUTHORIZED` errors still throw correctly on both
mutations
- [ ] Verify that the subscription `onEventSubscription` still works
end-to-end with stream creation, query registration, and heartbeat TTL
refresh

Made with [Cursor](https://cursor.com)
This commit is contained in:
Thomas Trompette
2026-05-04 17:06:18 +02:00
committed by GitHub
parent 4852ac401a
commit 95a34d8517
5 changed files with 23 additions and 16 deletions
@@ -14,7 +14,6 @@ export class EventStreamExceptionFilter implements GqlExceptionFilter {
catch(exception: EventStreamException) {
switch (exception.code) {
case EventStreamExceptionCode.EVENT_STREAM_ALREADY_EXISTS:
case EventStreamExceptionCode.EVENT_STREAM_DOES_NOT_EXIST:
case EventStreamExceptionCode.NOT_AUTHORIZED:
throw new InternalServerError(exception.message, {
subCode: exception.code,
@@ -7,7 +7,6 @@ import { CustomException } from 'src/utils/custom-exception';
export enum EventStreamExceptionCode {
EVENT_STREAM_ALREADY_EXISTS = 'EVENT_STREAM_ALREADY_EXISTS',
NOT_AUTHORIZED = 'NOT_AUTHORIZED',
EVENT_STREAM_DOES_NOT_EXIST = 'EVENT_STREAM_DOES_NOT_EXIST',
}
const getEventStreamExceptionUserFriendlyMessage = (
@@ -15,7 +14,6 @@ const getEventStreamExceptionUserFriendlyMessage = (
) => {
switch (code) {
case EventStreamExceptionCode.EVENT_STREAM_ALREADY_EXISTS:
case EventStreamExceptionCode.EVENT_STREAM_DOES_NOT_EXIST:
return msg`Failed to receive real time updates.`;
case EventStreamExceptionCode.NOT_AUTHORIZED:
return msg`You are not authorized to perform this action.`;
@@ -151,11 +151,9 @@ export class EventStreamResolver {
);
if (!isDefined(streamData)) {
throw new EventStreamException(
'Event stream does not exist',
EventStreamExceptionCode.EVENT_STREAM_DOES_NOT_EXIST,
);
return false;
}
const isAuthorized = await this.eventStreamService.isAuthorized({
streamData,
authContext: {
@@ -170,6 +168,7 @@ export class EventStreamResolver {
EventStreamExceptionCode.NOT_AUTHORIZED,
);
}
await this.eventStreamService.addQuery({
workspaceId: workspace.id,
eventStreamChannelId,
@@ -197,10 +196,7 @@ export class EventStreamResolver {
);
if (!isDefined(streamData)) {
throw new EventStreamException(
'Event stream does not exist',
EventStreamExceptionCode.EVENT_STREAM_DOES_NOT_EXIST,
);
return false;
}
const isAuthorized = await this.eventStreamService.isAuthorized({