diff --git a/.changeset/three-rabbits-exercise.md b/.changeset/three-rabbits-exercise.md new file mode 100644 index 0000000000..eff679e878 --- /dev/null +++ b/.changeset/three-rabbits-exercise.md @@ -0,0 +1,5 @@ +--- +'@graphql-yoga/redis-event-target': minor +--- + +Added `serializeMessage` and `deserializeMessage` options to `createRedisEventTarget` diff --git a/packages/event-target/redis-event-target/__tests__/serializer.spec.ts b/packages/event-target/redis-event-target/__tests__/serializer.spec.ts new file mode 100644 index 0000000000..be744a6236 --- /dev/null +++ b/packages/event-target/redis-event-target/__tests__/serializer.spec.ts @@ -0,0 +1,68 @@ +import Redis from 'ioredis-mock'; +import { CustomEvent } from '@whatwg-node/events'; +import { createRedisEventTarget } from '../src'; + +describe('createRedisEventTarget: serializer arg', () => { + it('uses native JSON by default', done => { + const eventTarget = createRedisEventTarget({ + publishClient: new Redis({}), + subscribeClient: new Redis({}), + }); + + eventTarget.addEventListener('a', (event: CustomEvent) => { + // Serialized by JSON + expect(event.detail).toEqual({ + someNumber: 1, + someBoolean: true, + someText: 'hi', + }); + done(); + }); + + const event = new CustomEvent('a', { + detail: { + someNumber: 1, + someBoolean: true, + someText: 'hi', + }, + }); + eventTarget.dispatchEvent(event); + }); + + it('can use a custom serializer', done => { + const eventTarget = createRedisEventTarget({ + publishClient: new Redis({}), + subscribeClient: new Redis({}), + serializer: { + stringify: message => `__CUSTOM__${JSON.stringify(message)}`, + parse: (message: string) => { + const result = JSON.parse(message.replace(/^__CUSTOM__/, '')); + for (const key in result) { + if (typeof result[key] === 'number') { + result[key]++; + } + } + return result; + }, + }, + }); + + eventTarget.addEventListener('b', (event: CustomEvent) => { + expect(event.detail).toEqual({ + someNumber: 2, + someBoolean: true, + someText: 'hi', + }); + done(); + }); + + const event = new CustomEvent('b', { + detail: { + someNumber: 1, + someBoolean: true, + someText: 'hi', + }, + }); + eventTarget.dispatchEvent(event); + }); +}); diff --git a/packages/event-target/redis-event-target/src/index.ts b/packages/event-target/redis-event-target/src/index.ts index 40047612c6..7680bb5f5d 100644 --- a/packages/event-target/redis-event-target/src/index.ts +++ b/packages/event-target/redis-event-target/src/index.ts @@ -5,6 +5,10 @@ import { CustomEvent } from '@whatwg-node/events'; export type CreateRedisEventTargetArgs = { publishClient: Redis | Cluster; subscribeClient: Redis | Cluster; + serializer?: { + stringify: (message: unknown) => string; + parse: (message: string) => unknown; + }; }; export function createRedisEventTarget( @@ -12,6 +16,8 @@ export function createRedisEventTarget( ): TypedEventTarget { const { publishClient, subscribeClient } = args; + const serializer = args.serializer ?? JSON; + const callbacksForTopic = new Map void>>(); function onMessage(channel: string, message: string) { @@ -21,7 +27,7 @@ export function createRedisEventTarget( } const event = new CustomEvent(channel, { - detail: message === '' ? null : JSON.parse(message), + detail: message === '' ? null : serializer.parse(message), }) as TEvent; for (const callback of callbacks) { callback(event); @@ -65,7 +71,7 @@ export function createRedisEventTarget( dispatchEvent(event: TEvent) { publishClient.publish( event.type, - event.detail === undefined ? '' : JSON.stringify(event.detail), + event.detail === undefined ? '' : serializer.stringify(event.detail), ); return true; }, diff --git a/website/src/pages/docs/features/subscriptions.mdx b/website/src/pages/docs/features/subscriptions.mdx index 4eb027f6e6..38f54e3cf7 100644 --- a/website/src/pages/docs/features/subscriptions.mdx +++ b/website/src/pages/docs/features/subscriptions.mdx @@ -472,6 +472,40 @@ suitable solution for serverless or edge function environments. data structures over the wire you can use tools such as [`superjson`](https://github.com/blitz-js/superjson). +### Custom serializer + +By default, messages will be serialized to Redis using the native `JSON` available in your +JavaScript environment. However, you can customize this by providing an alternative with the +`serializer` option that expects a `stringify` and a `parse` methods similar to the native `JSON`. +For example, you can install the [superjson](https://github.com/blitz-js/superjson) package and use +it in the Redis event target instead: + +```ts +import SuperJSON from 'superjson' + +const publishClient = new Redis() +const subscribeClient = new Redis() + +const eventTarget = createRedisEventTarget({ + publishClient, + subscribeClient, + serializer: SuperJSON +}) +``` + +You can also provide your own logic if you want: + +```ts +const eventTarget = createRedisEventTarget({ + publishClient, + subscribeClient, + serializer: { + stringify: data => 'some serialized data', + parse: message => ({ some: 'deserialized data' }) + } +}) +``` + ## Advanced ### Filter and Map Values