Skip to content

Commit

Permalink
fix(handler): Always include the data field in stream messages (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo authored Jul 31, 2023
1 parent f33ccc2 commit 4643c9a
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"babel-jest": "^29.5.0",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eventsource": "^2.0.2",
"express": "^4.18.2",
"fastify": "^4.18.0",
"glob": "^10.2.7",
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/__snapshots__/handler.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ data: {"data":{"getValue":"value"}}

exports[`distinct connections mode should stream query operations to connected event stream and then disconnect 3`] = `
"event: complete
data:
"
`;
Expand All @@ -34,6 +35,7 @@ data: {"data":{"getValue":"value"}}

exports[`distinct connections mode should stream query operations to connected event stream and then disconnect 6`] = `
"event: complete
data:
"
`;
Expand Down Expand Up @@ -81,6 +83,7 @@ data: {"data":{"greetings":"Zdravo"}}

exports[`distinct connections mode should stream subscription operations to connected event stream and then disconnect 7`] = `
"event: complete
data:
"
`;
Expand Down Expand Up @@ -128,6 +131,7 @@ data: {"data":{"greetings":"Zdravo"}}

exports[`distinct connections mode should stream subscription operations to connected event stream and then disconnect 14`] = `
"event: complete
data:
"
`;
Expand Down
39 changes: 39 additions & 0 deletions src/__tests__/client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { jest } from '@jest/globals';
import http from 'http';
import { createClient, StreamMessage, StreamEvent } from '../client';
import { createTFetch } from './utils/tfetch';
import { tsubscribe } from './utils/tsubscribe';
import { pong } from './fixtures/simple';
import { sleep } from './utils/testkit';
import { createHandler } from '../use/http';
import { schema } from './fixtures/simple';
import EventSource from 'eventsource';
import { startDisposableServer } from './utils/tserver';

function noop() {
// do nothing
Expand Down Expand Up @@ -896,3 +901,37 @@ describe('iterate', () => {
expect(req.signal.aborted).toBeTruthy();
});
});

it('should support distinct connections mode with EventSource', async () => {
const [serverUrl] = startDisposableServer(
http.createServer(createHandler({ schema })),
);

const url = new URL(serverUrl);
url.searchParams.set('query', 'subscription { greetings }');

const source = new EventSource(url.toString());

await expect(
new Promise((resolve, reject) => {
const msgs: unknown[] = [];
source.addEventListener('next', ({ data }) => msgs.push(data));
source.addEventListener('error', (e) => {
source.close();
reject(e);
});
source.addEventListener('complete', () => {
source.close();
resolve(msgs);
});
}),
).resolves.toMatchInlineSnapshot(`
[
"{"data":{"greetings":"Hi"}}",
"{"data":{"greetings":"Bonjour"}}",
"{"data":{"greetings":"Hola"}}",
"{"data":{"greetings":"Ciao"}}",
"{"data":{"greetings":"Zdravo"}}",
]
`);
});
2 changes: 2 additions & 0 deletions src/__tests__/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ describe('distinct connections mode', () => {
{
"done": false,
"value": "event: complete
data:
",
}
Expand Down Expand Up @@ -664,6 +665,7 @@ describe('distinct connections mode', () => {
{
"done": false,
"value": "event: complete
data:
",
}
Expand Down
36 changes: 36 additions & 0 deletions src/__tests__/utils/tserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import net from 'net';
import http from 'http';

const leftovers: Dispose[] = [];
afterAll(async () => {
while (leftovers.length > 0) {
await leftovers.pop()?.();
}
});

export type Dispose = () => Promise<void>;

export function startDisposableServer(
server: http.Server,
): [url: string, port: number, dispose: Dispose] {
const sockets = new Set<net.Socket>();
server.on('connection', (socket) => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket));
});

const dispose = async () => {
for (const socket of sockets) {
socket.destroy();
}
await new Promise<void>((resolve) => server.close(() => resolve()));
};
leftovers.push(dispose);

server.listen(0);

const { port } = server.address() as net.AddressInfo;
const url = `http://localhost:${port}`;

return [url, port, dispose];
}
7 changes: 5 additions & 2 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ export function validateStreamEvent(e: unknown): StreamEvent {
export function print<ForID extends boolean, E extends StreamEvent>(
msg: StreamMessage<ForID, E>,
): string {
let str = `event: ${msg.event}`;
if (msg.data) str += `\ndata: ${JSON.stringify(msg.data)}`;
let str = `event: ${msg.event}\ndata:`;
if (msg.data) {
str += ' ';
str += JSON.stringify(msg.data);
}
str += '\n\n';
return str;
}
Expand Down
28 changes: 28 additions & 0 deletions website/src/pages/recipes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@ Short and concise code snippets for starting with common use-cases.

## Client Usage

### With [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) (distinct connections mode)

The following example doesn't use the `graphql-sse` client and works only for the ["distinct connections mode" of the GraphQL over SSE spec](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md#distinct-connections-mode). This mode is the client default and should suffice for most of the use-cases.

```js
const url = new URL('http://localhost:4000/graphql/stream');
url.searchParams.append('query', 'subscription { greetings }');

const source = new EventSource(url);

source.addEventListener('next', ({ data }) => {
console.log(data);
// {"data":{"greetings":"Hi"}}
// {"data":{"greetings":"Bonjour"}}
// {"data":{"greetings":"Hola"}}
// {"data":{"greetings":"Ciao"}}
// {"data":{"greetings":"Zdravo"}}
});

source.addEventListener('error', (e) => {
console.error(e);
});

source.addEventListener('complete', () => {
source.close();
});
```

### With [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)

```ts
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6697,6 +6697,13 @@ __metadata:
languageName: node
linkType: hard

"eventsource@npm:^2.0.2":
version: 2.0.2
resolution: "eventsource@npm:2.0.2"
checksum: c0072d972753e10c705d9b2285b559184bf29d011bc208973dde9c8b6b8b7b6fdad4ef0846cecb249f7b1585e860fdf324cbd2ac854a76bc53649e797496e99a
languageName: node
linkType: hard

"execa@npm:^0.8.0":
version: 0.8.0
resolution: "execa@npm:0.8.0"
Expand Down Expand Up @@ -7548,6 +7555,7 @@ __metadata:
babel-jest: ^29.5.0
eslint: ^8.43.0
eslint-config-prettier: ^8.8.0
eventsource: ^2.0.2
express: ^4.18.2
fastify: ^4.18.0
glob: ^10.2.7
Expand Down

0 comments on commit 4643c9a

Please sign in to comment.