Skip to content

Add streaming support: GraphQL subscriptions over Server-Sent Events - #21

Merged
charles2ke merged 5 commits into
mainfrom
copilot/add-streaming-support
Sep 16, 2026
Merged

charles2ke merged 5 commits into
mainfrom
copilot/add-streaming-support

Conversation

Copilot AI commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

The service had no way to push data to clients — every operation was a one-shot request/response. This adds a Subscription root type and an SSE transport so clients can stream results over plain HTTP, without a WebSocket upgrade or an external broker.

Changes

  • src/streaming/pubsub.js — in-process pub/sub returning async iterators. Bounded per-subscriber queue (oldest events dropped), cleanup on return()/abort. Swappable for a Redis/NATS backend behind the same publish/subscribe contract.
  • src/schema.js — adds Subscription { userCreated, postCreated(authorId) } and a createSchema() factory so Apollo Server and the streaming endpoint execute against one schema instance.
  • src/resolvers.js — subscription resolvers (with optional author filtering); createUser/createPost publish their results. pubsub comes from context with a module-level default.
  • src/streaming/sseRouter.js — GET|POST /graphql/stream accepting the usual query/variables/operationName. Subscriptions emit one event: next per result then event: complete; queries and mutations emit a single pair, so one client transport covers all operation types. Includes comment-frame heartbeats and AbortController-based teardown on client disconnect.
  • src/index.js — mounts the router ahead of /graphql and threads pubsub into both contexts.
  • Tests in test/streaming.test.js; README documents the endpoint, frame format, and the single-instance caveat.
curl -N --get http://localhost:4000/graphql/stream \
  --data-urlencode 'query=subscription { userCreated { id name } }'

: connected

event: next
data: {"data":{"userCreated":{"id":"3","name":"Grace"}}}

Note: @graphql-tools/schema is promoted from a transitive dependency of @apollo/server to a direct one, since createSchema() now builds the executable schema explicitly.

Co-authored-by: charles2ke <6725706+charles2ke@users.noreply.github.com>
Comment thread README.md

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved security, backpressure, cleanup, error-handling, and test-coverage issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds GraphQL subscriptions over Server-Sent Events with shared schema execution and in-process pub/sub.

Changes:

  • Adds subscription schema, resolvers, and event publication.
  • Implements /graphql/stream with heartbeats and disconnect cleanup.
  • Adds tests, documentation, and dependency updates.
File summaries
File Description
test/streaming.test.js Streaming and pub/sub tests
src/streaming/sseRouter.js SSE request handling and GraphQL execution
src/streaming/pubsub.js Bounded in-process async pub/sub
src/server.js Apollo executable schema integration
src/schema.js Subscription schema and executable schema factory
src/resolvers.js Subscription resolvers and event publishing
src/index.js SSE route and shared context wiring
README.md SSE usage documentation
package.json Direct schema dependency
package-lock.json Dependency lockfile updates
Review details

Suppressed comments (2)

src/resolvers.js:60

  • The streaming tests publish directly through app.pubsub, while the existing GraphQL tests only assert mutation results, so a regression in this new mutation-to-pub/sub bridge would pass. Add an end-to-end assertion that executes createUser/createPost through the GraphQL mutation path and observes the matching SSE frame.
    createUser: (_parent, { name, email }, { store, pubsub = defaultPubSub }) => {
      const user = store.createUser({ name, email });
      pubsub.publish(TOPICS.USER_CREATED, { userCreated: user });
      return user;

src/streaming/pubsub.js:44

  • With maxQueueSize: 0 (or any negative value), this condition is always true, but shift() on an empty queue is a no-op and the following push() still stores every payload. The supposedly bounded queue can therefore grow without limit; reject non-positive sizes or explicitly drop payloads when zero buffering is intended.
      if (queue.length >= maxQueueSize) queue.shift();
      queue.push(payload);
  • Files reviewed: 9/10 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/streaming/sseRouter.js Outdated
Comment thread src/streaming/pubsub.js
Comment thread src/streaming/sseRouter.js Outdated
Comment thread src/streaming/sseRouter.js Outdated
Comment thread test/streaming.test.js
Copilot AI and others added 2 commits September 16, 2026 03:28
Co-authored-by: charles2ke <6725706+charles2ke@users.noreply.github.com>
Co-authored-by: charles2ke <6725706+charles2ke@users.noreply.github.com>
Copilot AI and others added 2 commits September 16, 2026 03:32
…ure, test mutation streaming

Co-authored-by: charles2ke <6725706+charles2ke@users.noreply.github.com>
…ket errors

Co-authored-by: charles2ke <6725706+charles2ke@users.noreply.github.com>
@charles2ke
charles2ke merged commit 56f9791 into main Sep 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants