Implement the publish-subscribe pattern in TypeScript

Recently I was working on a refactor of a personal project and I needed to implement a publish-subscribe pattern. I had never done it before, so I decided to do some research and write a simple implementation in TypeScript.

What is the publish-subscribe pattern?

The pub-sub pattern is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

Example

// subscribe.ts

export type ISubscribe<T> = {
  getState: () => T;
  setState: (value: Partial<T>) => void;
  subscribe: (callback: () => void) => () => void;
};

export const subscription = <T>(initialValue: T): ISubscribe<T> => {
  const listeners = new Set<() => void>();
  let value = initialValue;

  const getState = () => value;

  const setState = (newValue: Partial<T>) => {
    value = { ...value, ...newValue };
    listeners.forEach((listener) => listener());
  };

  const subscribe = (callback: () => void) => {
    listeners.add(callback);
    // Return a function to unsubscribe.
    return () => listeners.delete(callback);
  };

  return {
    getState,
    setState,
    subscribe,
  };
};

Testing the implementation

The test is using Vitest:

// subscribe.spec.ts

import { vi } from "vitest";
import { subscription } from ".";

describe("subscribe.ts", () => {
  const baseStore = {
    name: "foo",
    surname: "bar",
  };

  it("should test the subscription implementation", () => {
    const state = subscription(baseStore);
    const sub = vi.fn();

    expect(state.getState()).toEqual(baseStore);

    state.subscribe(sub);

    state.setState({ name: "baz" });

    expect(sub).toHaveBeenCalledTimes(1);
    expect(state.getState()).toEqual({ ...baseStore, name: "baz" });
  });
});