Files
2026-05-27 23:29:53 +01:00

2.2 KiB

subscriptions.ts

(no submodules)

Purpose

Bridges the WsBroker contract from @suaveplan/api-ws to GraphQL Subscription resolvers via AsyncIterable<Payload>. The Yoga engine wires graphql-ws over the host's already-mounted WebSocket transport; this module is the per-subscription helper that converts a broker topic

  • tenant pair into an iterator the engine can consume.

Tenant filtering is enforced at the bridge: only BrokerEnvelope items whose tenant === tenantArg reach the GraphQL iterator. Closing the iterator (return() or the explicit close() method) calls subscription.unsubscribe() on the broker so the WS slot is released within one tick.

Features

  • Returns AsyncIterable<Payload> with an explicit close() accessor.
  • Queues payloads when no consumer is awaiting; resolves immediately when a consumer is parked.
  • Calls GraphqlYogaError("SUBSCRIPTION_TRANSPORT_UNAVAILABLE") when the host hands a null broker, satisfying the design doc §10.4 contract.

Usage: Basic

import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory";
import { subscribeBrokerTopic } from "@suaveplan/api-graphql-yoga";

const broker = createInMemoryWsBroker();
const iterable = subscribeBrokerTopic(
    broker,
    "billing.invoice.created",
    "tenant-a" as never
);
for await (const event of iterable) {
    console.log(event);
    break;
}
await iterable.close();

Usage: Advanced

import { subscribeBrokerTopic } from "@suaveplan/api-graphql-yoga";

const subscription = {
    invoiceCreated(_parent: unknown, _args: unknown, ctx: { broker: never; tenantId: never }) {
        return subscribeBrokerTopic(ctx.broker, "billing.invoice.created", ctx.tenantId);
    },
};

API Reference

  • subscribeBrokerTopic(broker, topic, tenant) — returns AsyncIterable<Payload> & { close(): Promise<void> }.

Implementation Notes

The internal queue + waiters arrays form a simple bounded mailbox. Backpressure (queue cap, drop oldest, etc.) is intentionally not modelled here — the in-memory broker enforces its own bounds, and the Redis broker will inherit the same back-end contract.