@syncar/server 1.0.0-alpha.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -90
- package/dist/index.d.ts +66 -66
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +55 -55
package/README.md
CHANGED
|
@@ -1,139 +1,206 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @syncar/server
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Real-time WebSocket server with pub/sub channels and composable middleware.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@syncar/server)
|
|
6
|
+

|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Why Syncar?
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **Automatic Chunking** - Handles high-volume broadcasts without blocking the event loop.
|
|
10
|
+
- **Simple API** — Create servers in 3 lines of code
|
|
11
|
+
- **Type-safe** — Full TypeScript support
|
|
12
|
+
- **Flexible Channels** — Broadcast (all clients) or Multicast (subscribers only)
|
|
13
|
+
- **Middleware** — Hono-style composable middleware for auth, logging, rate limiting
|
|
14
|
+
- **Production Ready** — 97%+ test coverage, automatic reconnection, chunked broadcasts
|
|
16
15
|
|
|
17
16
|
## Installation
|
|
18
17
|
|
|
19
18
|
```bash
|
|
20
|
-
npm install @
|
|
19
|
+
npm install @syncar/server
|
|
21
20
|
```
|
|
22
21
|
|
|
23
22
|
## Quick Start
|
|
24
23
|
|
|
25
24
|
```typescript
|
|
26
|
-
import {
|
|
25
|
+
import { createSyncarServer } from '@syncar/server'
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
server.use(async (c, next) => {
|
|
32
|
-
console.log(`[${c.req.action}] client:${c.req.client?.id}`)
|
|
33
|
-
await next()
|
|
34
|
-
})
|
|
27
|
+
// Create and start server
|
|
28
|
+
const server = createSyncarServer({ port: 3000 })
|
|
29
|
+
await server.start()
|
|
35
30
|
|
|
36
|
-
//
|
|
37
|
-
const chat = server.createMulticast<string>('chat')
|
|
31
|
+
// Create a chat channel (multicast - subscribers only)
|
|
32
|
+
const chat = server.createMulticast<{ text: string; user: string }>('chat')
|
|
38
33
|
|
|
39
|
-
//
|
|
34
|
+
// Handle incoming messages
|
|
40
35
|
chat.onMessage((data, client) => {
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
console.log(`${client.id}: ${data.text}`)
|
|
37
|
+
chat.publish(data, { exclude: [client.id] }) // Broadcast to others
|
|
43
38
|
})
|
|
39
|
+
```
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
## Core Concepts
|
|
42
|
+
|
|
43
|
+
### Channels
|
|
44
|
+
|
|
45
|
+
| Type | Description | Use Case |
|
|
46
|
+
| ----------- | ------------------------------------ | ----------------------------------- |
|
|
47
|
+
| `Broadcast` | Sends to **all** connected clients | System announcements, notifications |
|
|
48
|
+
| `Multicast` | Sends to **subscribed** clients only | Chat rooms, topic-based feeds |
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Broadcast - everyone receives
|
|
52
|
+
const broadcast = server.createBroadcast<string>()
|
|
53
|
+
broadcast.publish('Server maintenance in 5 minutes')
|
|
54
|
+
|
|
55
|
+
// Multicast - only subscribers receive
|
|
56
|
+
const chat = server.createMulticast<Message>('chat')
|
|
57
|
+
chat.publish({ text: 'Hello', user: 'Alice' })
|
|
47
58
|
```
|
|
48
59
|
|
|
49
|
-
|
|
60
|
+
### Client Connection
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// WebSocket connect
|
|
64
|
+
const ws = new WebSocket('ws://localhost:3000')
|
|
65
|
+
|
|
66
|
+
// Subscribe to multicast channel
|
|
67
|
+
ws.send(JSON.stringify({ action: 'subscribe', channel: 'chat' }))
|
|
68
|
+
|
|
69
|
+
// Send message to channel
|
|
70
|
+
ws.send(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
action: 'message',
|
|
73
|
+
channel: 'chat',
|
|
74
|
+
data: { text: 'Hello' },
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
```
|
|
50
78
|
|
|
51
|
-
|
|
79
|
+
## Middleware
|
|
52
80
|
|
|
53
|
-
|
|
81
|
+
Add middleware globally or per-channel:
|
|
54
82
|
|
|
55
83
|
```typescript
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
import { createAuthMiddleware, createRateLimitMiddleware } from '@syncar/server'
|
|
85
|
+
|
|
86
|
+
const server = createSyncarServer({
|
|
87
|
+
port: 3000,
|
|
88
|
+
middleware: [
|
|
89
|
+
createAuthMiddleware({
|
|
90
|
+
verifyToken: async (token) => verifyJwt(token),
|
|
91
|
+
getToken: (ctx) => ctx.message?.data?.token,
|
|
92
|
+
}),
|
|
93
|
+
createRateLimitMiddleware({ maxRequests: 100, windowMs: 60000 }),
|
|
94
|
+
],
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Channel-specific middleware
|
|
98
|
+
const admin = server.createMulticast('admin')
|
|
99
|
+
admin.use(async (c, next) => {
|
|
100
|
+
if (c.get('user')?.role !== 'admin') return c.reject('Unauthorized')
|
|
101
|
+
await next()
|
|
74
102
|
})
|
|
75
103
|
```
|
|
76
104
|
|
|
77
|
-
|
|
105
|
+
Built-in middleware: `createAuthMiddleware`, `createLoggingMiddleware`, `createRateLimitMiddleware`, `createChannelWhitelistMiddleware`
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
## Server Options
|
|
80
108
|
|
|
81
109
|
```typescript
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
createSyncarServer({
|
|
111
|
+
port: 3000, // Server port
|
|
112
|
+
host: '0.0.0.0', // Bind address
|
|
113
|
+
path: '/syncar', // WebSocket endpoint
|
|
114
|
+
enablePing: true, // Auto ping/pong
|
|
115
|
+
pingInterval: 30000, // Ping interval (ms)
|
|
116
|
+
broadcastChunkSize: 500, // Chunk size for large broadcasts
|
|
117
|
+
logger: console, // Custom logger
|
|
118
|
+
middleware: [], // Global middleware
|
|
88
119
|
})
|
|
89
120
|
```
|
|
90
121
|
|
|
91
|
-
##
|
|
92
|
-
|
|
93
|
-
### Broadcast Channel
|
|
94
|
-
Sends messages to **all** connected clients. No subscription needed.
|
|
122
|
+
## Server Lifecycle
|
|
95
123
|
|
|
96
124
|
```typescript
|
|
97
|
-
|
|
98
|
-
|
|
125
|
+
// Start server
|
|
126
|
+
await server.start()
|
|
127
|
+
|
|
128
|
+
// Get statistics
|
|
129
|
+
const stats = server.getStats()
|
|
130
|
+
// { clientCount, channelCount, subscriptionCount, startedAt }
|
|
131
|
+
|
|
132
|
+
// Stop server (closes all connections)
|
|
133
|
+
await server.stop()
|
|
99
134
|
```
|
|
100
135
|
|
|
101
|
-
|
|
102
|
-
Topic-based messaging for **subscribed** clients.
|
|
136
|
+
## With Existing HTTP Server
|
|
103
137
|
|
|
104
138
|
```typescript
|
|
105
|
-
|
|
139
|
+
import { createServer } from 'http'
|
|
140
|
+
import { Syncar } from '@syncar/server'
|
|
106
141
|
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
const httpServer = createServer()
|
|
143
|
+
const server = new Syncar({ server: httpServer })
|
|
109
144
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// data is typed
|
|
113
|
-
})
|
|
145
|
+
await server.start()
|
|
146
|
+
httpServer.listen(3000)
|
|
114
147
|
```
|
|
115
148
|
|
|
116
149
|
## API Reference
|
|
117
150
|
|
|
118
|
-
###
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
### Syncar Class
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Lifecycle
|
|
155
|
+
start(): Promise<void>
|
|
156
|
+
stop(): Promise<void>
|
|
157
|
+
|
|
158
|
+
// Channels
|
|
159
|
+
createBroadcast<T>(): BroadcastChannel<T>
|
|
160
|
+
createMulticast<T>(name: string): MulticastChannel<T>
|
|
161
|
+
hasChannel(name: string): boolean
|
|
162
|
+
getChannels(): string[]
|
|
163
|
+
|
|
164
|
+
// Middleware & Auth
|
|
165
|
+
use(middleware: Middleware): void
|
|
166
|
+
authenticate(hook: (request: IncomingMessage) => Promise<string>): void
|
|
167
|
+
|
|
168
|
+
// Utilities
|
|
169
|
+
getStats(): ServerStats
|
|
170
|
+
getConfig(): Readonly<ServerConfig>
|
|
171
|
+
getRegistry(): ClientRegistry
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Channel Methods (Broadcast/Multicast)
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Publishing
|
|
178
|
+
publish(data: T, options?: { exclude?: string[], to?: string[] }): Promise<void>
|
|
179
|
+
|
|
180
|
+
// Handlers
|
|
181
|
+
onMessage(handler: (data: T, client: Client) => void): void
|
|
182
|
+
|
|
183
|
+
// Middleware
|
|
184
|
+
use(middleware: Middleware): void
|
|
185
|
+
|
|
186
|
+
// Subscription (Multicast only)
|
|
187
|
+
subscribe(clientId: string): void
|
|
188
|
+
unsubscribe(clientId: string): void
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Error Types
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import {
|
|
195
|
+
ConfigError, // Invalid configuration
|
|
196
|
+
TransportError, // WebSocket connection issues
|
|
197
|
+
ChannelError, // Channel operation errors
|
|
198
|
+
ClientError, // Client-specific errors
|
|
199
|
+
ValidationError, // Invalid input data
|
|
200
|
+
StateError, // Invalid server state
|
|
201
|
+
MiddlewareRejectionError, // Middleware blocked action
|
|
202
|
+
} from '@syncar/server'
|
|
203
|
+
```
|
|
137
204
|
|
|
138
205
|
## License
|
|
139
206
|
|