TypeScript
PubSubJS is built with TypeScript and provides comprehensive type inference.
Type Inference
Section titled “Type Inference”Event Types
Section titled “Event Types”Event definitions automatically infer types:
import { defineEvent, type EventNames, type EventPayload } from "@pubsubjs/core";import { z } from "zod";
const events = defineEvent([ { name: "user.created", schema: z.object({ userId: z.string(), email: z.string().email(), }), }, { name: "order.placed", schema: z.object({ orderId: z.string(), total: z.number(), }), },]);
// Extract event typestype Events = typeof events;
// Get all event names as uniontype AllEventNames = EventNames<Events>;// => "user.created" | "order.placed"
// Get payload type for specific eventtype UserCreatedPayload = EventPayload<Events, "user.created">;// => { userId: string; email: string }
type OrderPlacedPayload = EventPayload<Events, "order.placed">;// => { orderId: string; total: number }Publisher Type Safety
Section titled “Publisher Type Safety”const publisher = new Publisher({ events, transport });
// Type-checked event namesawait publisher.publish("user.created", payload); // OKawait publisher.publish("typo", payload); // Error!
// Type-checked payloadsawait publisher.publish("user.created", { userId: "123", email: "test@example.com",}); // OK
await publisher.publish("user.created", { userId: 123, // Error: number not string email: "test@example.com",});
await publisher.publish("user.created", { userId: "123", // Error: missing email});Subscriber Type Safety
Section titled “Subscriber Type Safety”const subscriber = new Subscriber({ events, transport });
// Handler payload is typedsubscriber.on("user.created", (payload) => { payload.userId; // string payload.email; // string payload.invalid; // Error: Property does not exist});
// Context is typedsubscriber.on("user.created", (payload, { ctx }) => { ctx.messageId; // string ctx.timestamp; // Date});Generic Constraints
Section titled “Generic Constraints”Custom Context Types
Section titled “Custom Context Types”interface MyContext { messageId: string; timestamp: Date; userId: string; roles: string[];}
const subscriber = new Subscriber<typeof events, MyContext>({ events, transport, contextFactory: (metadata) => ({ messageId: metadata.messageId, timestamp: new Date(), userId: metadata.userId as string, roles: (metadata.roles as string[]) || [], }),});
subscriber.on("user.created", (payload, { ctx }) => { ctx.userId; // string ctx.roles; // string[]});Typed Middleware
Section titled “Typed Middleware”import type { SubscribeMiddleware } from "@pubsubjs/core";
// Middleware with typed events and contextconst myMiddleware: SubscribeMiddleware<typeof events, MyContext> = async ( eventName, // "user.created" | "order.placed" payload, // unknown (validated by the time it reaches handler) context, // MyContext next) => { console.log(`User ${context.userId} processing ${eventName}`); await next();};Utility Types
Section titled “Utility Types”Creating Type-Safe Event Maps
Section titled “Creating Type-Safe Event Maps”import type { HandlerMap } from "@pubsubjs/core";
// Type-safe handler mapconst handlers: HandlerMap<typeof events> = { "user.created": (payload) => { // payload is typed as { userId: string; email: string } }, "order.placed": (payload) => { // payload is typed as { orderId: string; total: number } },};
subscriber.onMany(handlers);Publisher Interface
Section titled “Publisher Interface”import type { PublisherInterface, EventRegistry } from "@pubsubjs/core";
// Use in dependency injectionclass NotificationService { constructor(private publisher: PublisherInterface<typeof events>) {}
async notifyUserCreated(userId: string, email: string) { await this.publisher.publish("user.created", { userId, email }); }}Schema Types
Section titled “Schema Types”Extracting Schema Types
Section titled “Extracting Schema Types”import { z } from "zod";import type { InferOutput } from "@pubsubjs/core";
const userSchema = z.object({ userId: z.string(), email: z.string().email(), profile: z.object({ name: z.string(), age: z.number().optional(), }),});
// Extract type from schematype User = InferOutput<typeof userSchema>;// => { userId: string; email: string; profile: { name: string; age?: number } }Standard Schema Support
Section titled “Standard Schema Support”PubSubJS works with any Standard Schema compatible library:
// Zodimport { z } from "zod";const zodSchema = z.object({ name: z.string() });
// Valibotimport * as v from "valibot";const valibotSchema = v.object({ name: v.string() });
// Both work with defineEventconst events = defineEvent([ { name: "event1", schema: zodSchema }, { name: "event2", schema: valibotSchema },]);Advanced Patterns
Section titled “Advanced Patterns”Discriminated Unions
Section titled “Discriminated Unions”const events = defineEvent([ { name: "notification", schema: z.discriminatedUnion("type", [ z.object({ type: z.literal("email"), to: z.string().email(), subject: z.string(), }), z.object({ type: z.literal("sms"), phone: z.string(), message: z.string(), }), z.object({ type: z.literal("push"), deviceId: z.string(), title: z.string(), }), ]), },]);
subscriber.on("notification", (payload) => { // TypeScript narrows the type based on discriminator if (payload.type === "email") { payload.to; // string (email) payload.subject; // string } else if (payload.type === "sms") { payload.phone; // string payload.message; // string } else { payload.deviceId; // string payload.title; // string }});Branded Types
Section titled “Branded Types”import { z } from "zod";
// Create branded types for type safetyconst UserId = z.string().brand("UserId");const OrderId = z.string().brand("OrderId");
type UserId = z.infer<typeof UserId>;type OrderId = z.infer<typeof OrderId>;
const events = defineEvent([ { name: "order.placed", schema: z.object({ orderId: OrderId, userId: UserId, total: z.number(), }), },]);
// Type-safe IDsconst userId: UserId = "user-123" as UserId;const orderId: OrderId = "order-456" as OrderId;
// Can't mix up IDsawait publisher.publish("order.placed", { orderId: userId, // Error! UserId is not OrderId userId: orderId, // Error! OrderId is not UserId total: 99.99,});Module Augmentation
Section titled “Module Augmentation”Extend PubSubJS types:
declare module "@pubsubjs/core" { interface TransportMetadata { userId?: string; traceId?: string; source?: string; }}Next Steps
Section titled “Next Steps”- Testing - Test typed code
- Events & Schemas - Schema validation