plug-n-play-ws
Version:
A plug-and-play WebSocket layer on top of Socket.IO with full TypeScript support, zero manual wiring, and production-ready features
584 lines (454 loc) โข 14.7 kB
Markdown
# plug-n-play-ws
[](https://badge.fury.io/js/plug-n-play-ws)
[](http://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
A plug-and-play WebSocket layer on top of Socket.IO with full TypeScript support, zero manual wiring, and production-ready features.
## ๐ Features
- **๐ Plug-and-Play**: Single call to instantiate server and client
- **๐ Type-Safe**: End-to-end TypeScript support with Zod validation
- **๐ก Real-Time Search**: Built-in n-gram/edge-gram search with streaming results
- **๐พ Scalable Storage**: Multiple adapter patterns (Memory, Redis, Upstash)
- **โ๏ธ React Integration**: Custom hooks for seamless React integration
- **๐ Next.js Ready**: Built-in API route wrappers for Next.js 15
- **๐ Auto-Reconnection**: Intelligent reconnection with backoff strategies
- **๐ Session Management**: Multi-tab support with automatic cleanup
- **๐๏ธ Production Ready**: Graceful shutdown, structured logging, cluster support
## ๐ฆ Installation
```bash
npm install plug-n-play-ws
# or
yarn add plug-n-play-ws
# or
pnpm add plug-n-play-ws
```
### Peer Dependencies
```bash
npm install socket.io socket.io-client react
```
## ๐โโ๏ธ Quick Start
### Server Setup
```typescript
import { PlugNPlayServer } from 'plug-n-play-ws/server';
// Create server with automatic type inference
const server = new PlugNPlayServer({
port: 3001,
cors: { origin: true },
});
// Handle custom events
server.on('chat-message', (data) => {
console.log('New message:', data.message);
server.broadcast('chat-message', data);
});
// Start server
await server.listen();
console.log('๐ WebSocket server running on port 3001');
```
### Client Setup
```typescript
import { PlugNPlayClient } from 'plug-n-play-ws/client';
// Create type-safe client
const client = new PlugNPlayClient({
url: 'http://localhost:3001',
autoConnect: true,
});
// Listen for messages
client.on('chat-message', (data) => {
console.log('Received:', data.message);
});
// Send messages
client.send('chat-message', {
user: 'Alice',
message: 'Hello World!',
timestamp: Date.now(),
});
```
### React Integration
```tsx
import { usePlugNPlayWs } from 'plug-n-play-ws/react';
function ChatComponent() {
const ws = usePlugNPlayWs({
url: 'http://localhost:3001',
onConnect: (data) => console.log('Connected:', data.sessionId),
onDisconnect: (data) => console.log('Disconnected:', data.reason),
});
const sendMessage = () => {
ws.send('chat-message', {
user: 'Alice',
message: 'Hello from React!',
timestamp: Date.now(),
});
};
return (
<div>
<div>Status: {ws.status}</div>
<div>Connected: {ws.isConnected ? 'Yes' : 'No'}</div>
<button onClick={sendMessage} disabled={!ws.isConnected}>
Send Message
</button>
</div>
);
}
```
## ๐ API Reference
### Server API
#### PlugNPlayServer
```typescript
interface ServerConfig {
port?: number;
cors?: CORSConfig;
heartbeatInterval?: number;
heartbeatTimeout?: number;
logger?: Logger;
adapter?: IAdapter;
redis?: RedisConfig;
gracefulShutdownTimeout?: number;
}
class PlugNPlayServer<T extends Record<string, unknown> = EventMap> {
constructor(config?: ServerConfig);
// Server lifecycle
async listen(port?: number): Promise<void>;
async close(): Promise<void>;
// Messaging
async sendToSession<K extends keyof T>(sessionId: string, event: K, data: T[K]): Promise<boolean>;
broadcast<K extends keyof T>(event: K, data: T[K]): void;
broadcastExcept<K extends keyof T>(senderSessionId: string, event: K, data: T[K]): void;
// Session management
async getActiveSessions(): Promise<SessionMetadata[]>;
async getSession(sessionId: string): Promise<SessionMetadata | null>;
async disconnectSession(sessionId: string, reason?: string): Promise<boolean>;
// Search functionality
async indexContent(id: string, content: string, metadata?: Record<string, unknown>): Promise<void>;
async removeContent(id: string): Promise<void>;
async search(query: SearchQuery, targetSessionId?: string): Promise<SearchResponse>;
// Event handling
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
emit<K extends keyof T>(event: K, data: T[K]): boolean;
once<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
removeAllListeners<K extends keyof T>(event?: K): this;
// Statistics
getStats(): ServerStats;
}
```
#### Search API
```typescript
interface SearchQuery {
query: string;
limit?: number;
offset?: number;
filters?: Record<string, unknown>;
streaming?: boolean;
}
interface SearchResponse<T = unknown> {
query: string;
results: SearchResult<T>[];
total: number;
took: number;
hasMore?: boolean;
}
interface SearchResult<T = unknown> {
id: string;
score: number;
data: T;
highlights?: string[];
}
```
### Client API
#### PlugNPlayClient
```typescript
interface ClientConfig {
url: string;
autoConnect?: boolean;
reconnection?: boolean;
reconnectionAttempts?: number;
reconnectionDelay?: number;
reconnectionDelayMax?: number;
timeout?: number;
forceNew?: boolean;
logger?: Logger;
auth?: Record<string, unknown>;
}
class PlugNPlayClient<T extends Record<string, unknown> = EventMap> {
constructor(config: ClientConfig);
// Connection management
async connect(): Promise<void>;
disconnect(): void;
// Messaging
send<K extends keyof T>(event: K, data: T[K]): boolean;
async search(query: SearchQuery): Promise<SearchResponse | null>;
// Status
getStatus(): ConnectionStatus;
getSession(): { id?: string; metadata?: SessionMetadata };
isConnected(): boolean;
getStats(): ClientStats;
// Event handling
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
emit<K extends keyof T>(event: K, data: T[K]): boolean;
once<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
removeAllListeners<K extends keyof T>(event?: K): this;
}
```
### React Hooks
#### usePlugNPlayWs
```typescript
interface UsePlugNPlayWsOptions<T> extends Omit<ClientConfig, 'autoConnect'> {
autoConnect?: boolean;
onConnect?: (data: { sessionId: string; metadata: SessionMetadata }) => void;
onDisconnect?: (data: { sessionId: string; reason: string }) => void;
onError?: (error: Error) => void;
onStatusChange?: (status: ConnectionStatus) => void;
}
function usePlugNPlayWs<T extends Record<string, unknown> = EventMap>(
options: UsePlugNPlayWsOptions<T>
): UsePlugNPlayWsReturn<T>;
```
#### usePlugNPlaySearch
```typescript
function usePlugNPlaySearch<T extends Record<string, unknown> = EventMap>(
client: UsePlugNPlayWsReturn<T>
): {
search: (query: SearchQuery) => Promise<void>;
clearResults: () => void;
isSearching: boolean;
results: SearchResponse | null;
streamingResults: unknown[];
error: string | null;
};
```
## ๐๏ธ Storage Adapters
### Memory Adapter (Development)
```typescript
import { MemoryAdapter } from 'plug-n-play-ws/adapters';
const adapter = new MemoryAdapter();
const server = new PlugNPlayServer({ adapter });
```
### Redis Adapter (Production)
```typescript
import { RedisAdapter } from 'plug-n-play-ws/adapters';
const adapter = new RedisAdapter({
host: 'localhost',
port: 6379,
password: 'your-password',
keyPrefix: 'myapp:',
});
const server = new PlugNPlayServer({ adapter });
```
### Upstash Redis Adapter (Serverless)
```typescript
import { UpstashRedisAdapter } from 'plug-n-play-ws/adapters';
const adapter = new UpstashRedisAdapter({
url: 'https://your-redis.upstash.io',
token: 'your-token',
keyPrefix: 'myapp:',
});
const server = new PlugNPlayServer({ adapter });
```
## ๐ Next.js Integration
### API Route Setup
```typescript
// app/api/ws/route.ts
import { PlugNPlayServer } from 'plug-n-play-ws/server';
import { createNextJSHandler } from 'plug-n-play-ws/nextjs';
const server = new PlugNPlayServer({
port: 3001,
cors: { origin: true },
});
// Start WebSocket server
server.listen().catch(console.error);
// Create API route handler
const handler = createNextJSHandler(server, {
corsOrigin: true,
enableHealthCheck: true,
});
export { handler as GET, handler as POST, handler as OPTIONS };
```
### Server Startup (instrumentation.ts)
```typescript
// instrumentation.ts
import { startWebSocketServer } from 'plug-n-play-ws/nextjs';
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await startWebSocketServer({
port: 3001,
cors: { origin: true },
});
}
}
```
## ๐ Search Features
### Document Indexing
```typescript
// Index documents with metadata
await server.indexContent('doc1', 'TypeScript is amazing', {
category: 'programming',
tags: ['typescript', 'javascript'],
});
await server.indexContent('doc2', 'WebSockets enable real-time communication', {
category: 'networking',
tags: ['websockets', 'realtime'],
});
```
### Search with Streaming
```typescript
// Regular search
const results = await server.search({
query: 'TypeScript programming',
limit: 10,
offset: 0,
});
// Streaming search
client.on('search-stream', ({ chunk, isLast }) => {
console.log('Streaming result:', chunk);
if (isLast) console.log('Search complete');
});
await server.search({
query: 'TypeScript programming',
streaming: true,
}, sessionId);
```
### Search in React
```typescript
import { usePlugNPlayWs, usePlugNPlaySearch } from 'plug-n-play-ws/react';
function SearchComponent() {
const ws = usePlugNPlayWs({ url: 'http://localhost:3001' });
const search = usePlugNPlaySearch(ws);
const handleSearch = async () => {
await search.search({
query: 'TypeScript',
limit: 10,
streaming: false,
});
};
return (
<div>
<button onClick={handleSearch} disabled={search.isSearching}>
{search.isSearching ? 'Searching...' : 'Search'}
</button>
{search.results && (
<div>
<h3>Results ({search.results.total})</h3>
{search.results.results.map(result => (
<div key={result.id}>
<strong>{result.id}</strong> (score: {result.score})
{result.highlights?.map(highlight => (
<div dangerouslySetInnerHTML={{ __html: highlight }} />
))}
</div>
))}
</div>
)}
</div>
);
}
```
## ๐๏ธ Production Deployment
### Environment Variables
```bash
# WebSocket Configuration
WS_PORT=3001
WS_CORS_ORIGIN=https://yourdomain.com
# Redis Configuration (if using Redis adapter)
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=your-password
# Upstash Redis (if using Upstash adapter)
UPSTASH_REDIS_URL=https://your-redis.upstash.io
UPSTASH_REDIS_TOKEN=your-token
```
### Docker Deployment
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3001
CMD ["npm", "start"]
```
### Graceful Shutdown
```typescript
const server = new PlugNPlayServer({
gracefulShutdownTimeout: 10000, // 10 seconds
});
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...');
await server.close();
process.exit(0);
});
```
## ๐ Examples
The `examples/` directory contains comprehensive examples demonstrating various features:
### 01-basic-echo.ts
Basic server-client communication with modern features:
- Connection limiting and heartbeat configuration
- User count tracking and broadcasts
- Enhanced error handling and reconnection
- Type-safe event handling
```bash
npm run example:echo
```
### 02-redis-search.ts
Redis storage with search functionality:
- Document indexing with metadata
- Full-text search with relevance scoring
- Real-time result broadcasting
- Production Redis configuration
```bash
npm run example:redis
```
### 03-nextjs-react.tsx
Complete Next.js integration example:
- API route setup with WebSocket server
- React component with search functionality
- Type-safe hooks integration
- Responsive UI with real-time updates
### 04-streaming-search.ts
Advanced streaming search capabilities:
- Real-time search result streaming
- Optimized search configuration
- Large dataset handling
- Progressive result display
```bash
npm run example:streaming
```
### 05-upstash-serverless.ts
Serverless-ready Upstash Redis integration:
- Environment-based adapter selection
- Automatic fallback to regular Redis
- Optimized for edge computing
- Rate limiting and error handling
```bash
npm run example:upstash
```
#### Running Examples
1. **Basic Echo**: `cd examples && npx tsx 01-basic-echo.ts`
2. **Redis Search**: Ensure Redis is running, then `npx tsx 02-redis-search.ts`
3. **Streaming**: `npx tsx 04-streaming-search.ts`
4. **Upstash**: Set environment variables, then `npx tsx 05-upstash-serverless.ts`
Each example includes detailed logging and demonstrates best practices for production usage.
## ๐งช Testing
```bash
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run specific test file
npm test -- server.test.ts
```
The package includes comprehensive tests for:
- Server and client functionality
- All storage adapters
- Search functionality
- Type validation
- React hooks
## ๐ License
MIT ยฉ [Your Name](https://github.com/yourusername)
## ๐ค Contributing
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
## ๐ Support
- ๐ [Documentation](https://github.com/yourusername/plug-n-play-ws#readme)
- ๐ [Issue Tracker](https://github.com/yourusername/plug-n-play-ws/issues)
- ๐ฌ [Discussions](https://github.com/yourusername/plug-n-play-ws/discussions)
---
Made with โค๏ธ for the TypeScript community