Types Reference

Complete TypeScript type definitions for the Erebus Core SDK

Types Reference

Complete TypeScript type definitions and interfaces for the Erebus Core SDK. Use this reference for type-safe development and IDE autocompletion.

Core Client Types

ErebusOptions

Configuration options for the PubSub client constructor:

interface ErebusOptions {
  wsUrl: string;                    // WebSocket URL
  httpBaseUrl?: string;             // HTTP base URL for history API
  tokenProvider: (channel: string) => Promise<string>; // Token provider function
  heartbeatMs?: number;             // Heartbeat interval (default: 25000)
  log?: Logger;                     // Optional logging function
  debug?: boolean;                  // Enable debug logging
  connectionTimeoutMs?: number;     // Connection timeout
  subscriptionTimeoutMs?: number;   // Subscription timeout
}

type Logger = (level: "info" | "warn" | "error", msg: string, meta?: unknown) => void;

Usage Example:

const client = ErebusClient.createClient({
  client: ErebusClientState.PubSub,
  authBaseUrl: "https://your-auth.com",
  wsBaseUrl: "wss://gateway.erebus.sh",
  heartbeatMs: 30000,
  debug: process.env.NODE_ENV === 'development',
  log: (level, msg, meta) => console[level](msg, meta)
} as ErebusOptions);

MessageBody

Structure of received messages:

interface MessageBody {
  id: string;              // Unique message ID (ULID)
  topic: string;           // Topic name
  senderId: string;        // Sender's client ID
  seq: string;             // Sequence number (ULID)
  sentAt: Date;            // Server timestamp
  payload: string;         // Message content
  clientMsgId?: string;    // Optional client correlation ID
  clientPublishTs?: number; // Client publish timestamp
}

Usage Example:

await client.subscribe("chat", (msg: MessageBody) => {
  console.log(`[${msg.seq}] ${msg.senderId}: ${msg.payload}`);
  console.log(`Sent at: ${msg.sentAt.toISOString()}`);
});

Callback Types

AckResponse and AckCallback

Types for acknowledgement handling:

type AckCallback = (response: AckResponse) => void;

type AckResponse = AckSuccess | AckError;

type AckSuccess = {
  success: true;
  ack: AckPacketType;
  seq: string;
  serverMsgId: string;
  topic: string;
};

type AckError = {
  success: false;
  ack: AckPacketType;
  error: {
    code: string;
    message: string;
  };
  topic: string;
};

Usage Example:

const ackHandler: AckCallback = (response) => {
  if (response.success) {
    console.log("Message delivered with seq:", response.seq);
  } else {
    console.error("Delivery failed:", response.error.message);
  }
};

await client.publishWithAck("topic", "message", ackHandler);

SubscriptionCallback and Response

Types for subscription acknowledgements:

type SubscriptionCallback = (response: SubscriptionResponse) => void;

type SubscriptionResponse = SubscriptionSuccess | SubscriptionError;

type SubscriptionSuccess = {
  success: true;
  ack: AckPacketType;
  topic: string;
  status: "subscribed" | "unsubscribed";
  path: "subscribe" | "unsubscribe";
};

type SubscriptionError = {
  success: false;
  ack?: AckPacketType;
  error: {
    code: string;
    message: string;
  };
  topic: string;
  path: "subscribe" | "unsubscribe";
};

Usage Example:

const subscriptionHandler: SubscriptionCallback = (response) => {
  if (response.success) {
    console.log(`Successfully ${response.status} to ${response.topic}`);
  } else {
    console.error(`Subscription failed: ${response.error.message}`);
  }
};

client.subscribeWithCallback("topic", messageHandler, subscriptionHandler);

Subscription Types

SubscribeOptions

Options for subscription behavior:

interface SubscribeOptions {
  streamOldMessages?: boolean; // Deliver missed messages (default: false)
}

Usage Example:

await client.subscribe(
  "chat",
  (msg) => console.log(msg),
  { streamOldMessages: true } // Catch up on missed messages
);

Message Handler Types

// Basic message handler
type MessageHandler = (message: MessageBody) => void;

// Handler with metadata (used internally)
type Handler = (
  payload: MessageBody,
  meta: { topic: string; seq: string; ts: number }
) => void;

Presence Types

Presence and PresenceHandler

Presence event structure:

interface Presence {
  clientId: string;                 // Client that changed status
  topic: string;                    // Topic name
  status: "online" | "offline";     // Presence status
  timestamp: number;                // Event timestamp
  subscribers?: string[];           // Optional: list of current subscribers
}

type PresenceHandler = (presence: Presence) => void;

Usage Example:

const presenceHandler: PresenceHandler = (presence) => {
  console.log(`${presence.clientId} is ${presence.status} on ${presence.topic}`);
  updateUserList(presence.clientId, presence.status);
};

await client.onPresence("chat", presenceHandler);

History Types

HistoryResponse and Options

interface HistoryResponse {
  items: MessageBody[];      // Array of messages
  nextCursor: string | null; // Cursor for next page (null = no more)
}

interface HistoryOptions {
  cursor?: string;                           // ULID cursor for pagination
  limit?: number;                           // Messages per page (1-1000, default: 50)
  direction?: "forward" | "backward";       // Sort order (default: "backward")
}

Usage Example:

const history: HistoryResponse = await client.getHistory("chat", {
  limit: 100,
  direction: "backward"
} as HistoryOptions);

console.log(`Fetched ${history.items.length} messages`);
if (history.nextCursor) {
  console.log("More messages available");
}

History Iterator Types

type HistoryIterator = () => Promise<{
  items: MessageBody[];
  hasMore: boolean;
} | null>;

Usage Example:

const iterator: HistoryIterator = client.createHistoryIterator("chat", {
  limit: 50
});

const batch = await iterator();
if (batch && batch.hasMore) {
  console.log("More batches available");
}

State and Connection Types

Connection States

type ConnectionState = "idle" | "connecting" | "open" | "error" | "closed";
type SubscriptionStatus = "subscribed" | "unsubscribed" | "pending";

Usage Example:

function checkConnectionState(client: ErebusPubSubClient) {
  if (client.isConnected) {
    console.log("Client is connected and ready");
  }
  
  // Monitor state changes
  setInterval(() => {
    console.log("Connected:", client.isConnected);
    console.log("Readable:", client.isReadable);  
    console.log("Writable:", client.isWritable);
  }, 5000);
}

Pending Operation Types

type PendingPublish = {
  requestId: string;
  clientMsgId: string;
  topic: string;
  callback: AckCallback;
  timestamp: number;
  timeoutId?: NodeJS.Timeout;
};

type PendingSubscription = {
  requestId: string;
  clientMsgId?: string;
  topic: string;
  path: "subscribe" | "unsubscribe";
  callback: SubscriptionCallback;
  timestamp: number;
  timeoutId?: NodeJS.Timeout;
};

Enums

ErebusClientState

enum ErebusClientState {
  PubSub = 0  // Select PubSub client
}

Usage Example:

const client = ErebusClient.createClient({
  client: ErebusClientState.PubSub, // Type-safe client selection
  authBaseUrl: "https://auth.example.com"
});

Access

enum Access {
  ReadWrite = "rw",  // Full read/write access
  Read = "r",        // Read-only access
  Write = "w"        // Write-only access
}

Usage Example:

import { Access } from "@erebus-sh/sdk/service";

session.allow("chat:*", Access.ReadWrite);  // Full access to chat topics
session.allow("announcements", Access.Read); // Read-only access to announcements

Generic Types

Schema Types (for Typed Facade)

type SchemaMap = Record<string, z.ZodType>;

type Topic<S extends SchemaMap> = Extract<keyof S, string>;

type Payload<S extends SchemaMap, K extends Topic<S>> = z.infer<S[K]>;

type TypedMessage<P> = Omit<MessageBody, "payload"> & { payload: P };

type MessageFor<S extends SchemaMap, K extends Topic<S>> = TypedMessage<
  Payload<S, K>
>;

Usage Example:

import { z } from "zod";

const schemas = {
  chat: z.object({
    text: z.string(),
    username: z.string(),
  }),
  notification: z.object({
    title: z.string(),
    urgent: z.boolean(),
  }),
} as const;

type ChatSchema = typeof schemas;
type ChatTopic = Topic<ChatSchema>; // "chat" | "notification"
type ChatMessage = MessageFor<ChatSchema, "chat">; // Typed message with chat payload

Utility Types

Client Interface

interface IPubSubClient<TTopic, TPayload, TMessage> {
  connect(timeout?: number): Promise<void>;
  joinChannel(channel: string): void;
  close(): void;
  
  publish<K extends TTopic>(topic: K, payload: TPayload): Promise<string>;
  publishWithAck<K extends TTopic>(
    topic: K,
    payload: TPayload,
    onAck: AckCallback,
    timeoutMs?: number,
  ): Promise<string>;
  
  subscribe<K extends TTopic>(
    topic: K,
    handler: (message: TMessage) => void,
    options?: SubscribeOptions,
  ): Promise<void>;
  
  unsubscribe<K extends TTopic>(topic: K): void;
  
  onPresence<K extends TTopic>(
    topic: K,
    handler: PresenceHandler,
  ): Promise<void>;
  
  get isConnected(): boolean;
  get isReadable(): boolean;
  get isWritable(): boolean;
}

Error Types

interface ErebusError extends Error {
  code?: string;
  context?: Record<string, unknown>;
}

type ErrorCode = 
  | "CONNECTION_ERROR"
  | "TIMEOUT"
  | "INVALID_TOPIC"
  | "NOT_CONNECTED"
  | "MALFORMED_ACK"
  | "SUBSCRIPTION_ERROR";

Type Guards

Utility functions for type checking:

// Check if response is successful
function isAckSuccess(response: AckResponse): response is AckSuccess {
  return response.success;
}

function isSubscriptionSuccess(
  response: SubscriptionResponse
): response is SubscriptionSuccess {
  return response.success;
}

// Check connection state
function isConnected(client: ErebusPubSubClient): boolean {
  return client.isConnected && client.isReadable && client.isWritable;
}

Usage Example:

client.publishWithAck("topic", "message", (ack) => {
  if (isAckSuccess(ack)) {
    // TypeScript knows ack has seq, serverMsgId properties
    console.log("Success! Seq:", ack.seq);
  } else {
    // TypeScript knows ack has error property
    console.error("Failed:", ack.error.message);
  }
});

Advanced Type Patterns

Typed Message Handlers

// Create typed message handler
function createTypedHandler<T>(
  validator: (data: unknown) => data is T,
  handler: (payload: T, msg: MessageBody) => void
): (msg: MessageBody) => void {
  return (msg) => {
    try {
      const parsed = JSON.parse(msg.payload);
      if (validator(parsed)) {
        handler(parsed, msg);
      } else {
        console.warn("Invalid message format:", parsed);
      }
    } catch (error) {
      console.error("Failed to parse message:", error);
    }
  };
}

// Usage
interface ChatMessage {
  text: string;
  username: string;
}

const isChatMessage = (data: unknown): data is ChatMessage => {
  return typeof data === 'object' && 
         data !== null &&
         'text' in data && 
         'username' in data;
};

const chatHandler = createTypedHandler(isChatMessage, (payload, msg) => {
  console.log(`${payload.username}: ${payload.text}`);
  console.log(`Sent at: ${msg.sentAt}`);
});

await client.subscribe("chat", chatHandler);

Next Steps