Architecture
How EdgeStream's components assemble — transport layer, pipeline engine, hook system, subscription manager, and the facade that coordinates them all.
Architectural Layers
EdgeStream is organized into five distinct layers. Each layer has a single responsibility and communicates with adjacent layers through TypeScript interfaces — making every layer independently testable and replaceable.
| Layer | Components | Responsibility |
|---|---|---|
| Facade | EdgeStream, IEdgeStream | Orchestrates all servers, exposes the top-level API |
| Server | Server, IServer | Single connection context — transport + pipelines + subscriptions |
| Pipeline | IncomingPipeline, OutgoingPipeline | Ordered hook execution with abort/pause support |
| Hooks | IHook, built-in hooks | Composable message transformers and interceptors |
| Transport | SignalRTransport, SseTransport, RawWebSocketTransport | Low-level network I/O — raw bytes in and out |
The EdgeStream Facade
The EdgeStream class is the entry point. It owns a Map<string, IServer> of registered servers and delegates all operations to the appropriate server instance.
// EdgeStream facade — top-level orchestrator
export class EdgeStream implements IEdgeStream {
readonly servers: Map<string, IServer> = new Map();
status: EdgeStreamStatus = 'idle';
// Starts all registered servers concurrently
async start(): Promise<void> {
const startPromises = Array.from(this.servers.values()).map(s => s.start());
await Promise.all(startPromises);
this.status = 'running';
this.emit('stream:started', { timestamp: new Date() });
}
// Register a new server with transport config
registerServer(registration: IServerRegistration): void {
const transport = createTransport(registration.transportConfig);
const server = new Server(registration, transport);
this.servers.set(registration.id, server);
}
}
Server: The Central Broker
Each IServer instance is an isolated message domain. It holds:
- One transport — the network connection (
ITransport) - One incoming pipeline — processes messages arriving from the transport
- One outgoing pipeline — processes messages being sent to the transport
- One subscription manager — routes processed messages to matching subscriber callbacks
Server types are typed as 'bas' (BizFirstGO Application Server) or 'chat', allowing different pipeline configurations per domain.
Pipeline Engine
The pipeline is the ordered execution engine. IncomingPipeline handles messages from the transport; OutgoingPipeline handles messages being sent. Both share the same hook execution model:
Create Pipeline Context
A new IPipelineContext is created for each message containing the envelope, metadata map, and abort/pause controls.
Sort Hooks by Priority
Hooks are sorted by their priority number (lower runs first). HookActivityLogger uses priority 5 to run before all others.
Execute Each Hook
Each hook receives the context and returns a HookResult. If continue: false, the pipeline aborts and no further hooks run.
Publish to Subscribers
After all hooks pass, the SubscriptionManager.publish() delivers the envelope to all matching subscribers.
Hook System
Hooks are the extension point of EdgeStream. Every hook implements IHook:
export interface IHook<TContext extends IPipelineContext = IPipelineContext> {
readonly name: string;
readonly priority: number; // lower number = runs first
execute(context: TContext): Promise<HookResult>;
}
export interface HookResult {
continue: boolean; // false = abort pipeline (message dropped)
paused?: boolean; // true = pipeline paused awaiting user input
error?: string; // optional error message for logging
}
The pipeline context allows hooks to transform the message body, set metadata, add attributes, or abort/pause processing:
export interface IPipelineContext<TBody = unknown> {
readonly envelope: IEnvelope<TBody>;
readonly serverId: string;
body: TBody; // hooks mutate this
metadata: Record<string, unknown>; // shared between hooks
abort(reason?: string): void;
readonly isAborted: boolean;
pause(request: IUserInputRequest): IPipelinePause;
readonly isPaused: boolean;
}
Transport Abstraction
All transports implement ITransport — a uniform interface for connect/disconnect/send/receive regardless of the underlying protocol:
| Transport | Type Key | Duplex | Auto-Reconnect | Use Case |
|---|---|---|---|---|
SignalRTransport | 'signalr' | Yes | Yes (built-in) | Browser apps, recommended default |
RawWebSocketTransport | 'websocket' | Yes | Manual | Performance-critical, non-browser |
SseTransport | 'sse' | Server-only | Yes (EventSource) | Read-only event streams, firewall fallback |
HttpPollingTransport | 'http-polling' | Emulated | Yes | Restricted environments, no WS support |
Event System
The EdgeStream facade emits typed lifecycle events that applications can subscribe to:
const stream = createEdgeStream();
stream.on('stream:started', (event) => console.log('Started', event.timestamp));
stream.on('server:connected', (event) => console.log('Server connected', event.serverId));
stream.on('server:error', (event) => console.error('Server error', event.data?.error));
stream.on('pipeline:paused', (event) => console.log('Waiting for user input'));
stream.on('message:received', (event) => updateMessageCount());
Available events: stream:started, stream:stopped, stream:error, server:connected, server:disconnected, server:error, pipeline:paused, pipeline:resumed, message:received, message:sent