@afterlink/server 1.1.1 → 1.1.3
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 +758 -0
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
|
|
2
|
+
<div align="center">
|
|
3
|
+
|
|
4
|
+
<img width="1774" height="887" alt="AfterLink README.md File Logo" src="https://github.com/user-attachments/assets/85611f61-2080-4172-b01a-dcd5119f2fc8" />
|
|
5
|
+
|
|
6
|
+
# AfterLink
|
|
7
|
+
|
|
8
|
+
**A custom binary communication protocol for fast, reliable, real-time messaging.**
|
|
9
|
+
Persistent connections · Built-in Pub/Sub · Automatic Zod validation · 10-byte frame
|
|
10
|
+
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](./CODE_OF_CONDUCT.md)
|
|
13
|
+
[](https://nodejs.org/)
|
|
14
|
+
[](https://www.npmjs.com/package/afterlink)
|
|
15
|
+
[](https://www.npmjs.com/package/afterlink)
|
|
16
|
+
[](./CHANGELOG.md)
|
|
17
|
+
[](./CHANGELOG.md)
|
|
18
|
+
[](./CONTRIBUTING.md)
|
|
19
|
+
|
|
20
|
+
[**Docs**](https://afterlinkdocs.vercel.app) · [**npm**](https://www.npmjs.com/package/afterlink) · [**Examples**](./examples) · [**Benchmarks**](./BENCHMARKS.md) · [**Discussions**](https://github.com/AJAYMYTH/AfterLink/discussions) · [**Changelog**](./CHANGELOG.md)
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## What is AfterLink?
|
|
27
|
+
|
|
28
|
+
AfterLink is a **custom application-layer binary communication protocol** built for developers who are tired of HTTP boilerplate. It combines structured request/response, real-time pub/sub, automatic schema validation, and persistent connections — all over a compact **10-byte binary frame**.
|
|
29
|
+
|
|
30
|
+
It is faster, simpler, and more developer-friendly than HTTP for modern real-time applications. You write 5 lines of code. AfterLink handles the rest.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why AfterLink?
|
|
35
|
+
|
|
36
|
+
| Problem with HTTP | AfterLink Solution |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Verbose text headers add overhead | **10-byte binary header** — 90% smaller |
|
|
39
|
+
| Stateless — every request rebuilds context | **Persistent TCP connections** with session state |
|
|
40
|
+
| No built-in real-time — need WebSockets separately | **Pub/Sub built-in** — same protocol, same connection |
|
|
41
|
+
| No schema validation — manual checks everywhere | **Automatic Zod validation** — invalid payloads rejected before your code runs |
|
|
42
|
+
| One connection per request (HTTP/1.1) | **Multiplexing** — hundreds of concurrent requests over one connection |
|
|
43
|
+
| No binary support — need base64 workarounds | **Native binary** via MessagePack serialization |
|
|
44
|
+
| Complex setup for REST APIs | **5 lines** to spin up a full server with routes |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Feature Comparison
|
|
49
|
+
|
|
50
|
+
| Feature | AfterLink | HTTP/REST | WebSocket | gRPC | MQTT |
|
|
51
|
+
|---|---|---|---|---|---|
|
|
52
|
+
| **Setup complexity** | Very Easy | Easy | Medium | Hard | Medium |
|
|
53
|
+
| **Binary protocol** | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
54
|
+
| **TLS encryption** | ✅ Built-in | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
55
|
+
| **Payload compression** | ✅ zlib/Brotli | ✅ gzip | ❌ No | ✅ Yes | ❌ No |
|
|
56
|
+
| **Schema validation** | ✅ Built-in | ❌ No | ❌ No | Proto only | ❌ No |
|
|
57
|
+
| **Multiplexing** | ✅ Yes | ❌ No (HTTP/1.1) | ❌ No | ✅ Yes | ❌ No |
|
|
58
|
+
| **Pub/Sub** | ✅ Built-in | ❌ No | ❌ No | ❌ No | ✅ Yes |
|
|
59
|
+
| **Streaming** | ✅ First-class | SSE only | Manual | ✅ Yes | ❌ No |
|
|
60
|
+
| **Browser support** | ✅ Yes (TCP/WS) | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
|
|
61
|
+
| **Auto-reconnect** | ✅ Built-in | ❌ No | ❌ No | ❌ No | ✅ Yes |
|
|
62
|
+
| **Rate limiting** | ✅ Built-in | ❌ No | ❌ No | ❌ No | ❌ No |
|
|
63
|
+
| **Graceful shutdown** | ✅ Built-in | ✅ Yes | ❌ No | ✅ Yes | ❌ No |
|
|
64
|
+
| **Built-in auth** | ✅ Yes (JWT) | ❌ No | ❌ No | Optional | Optional |
|
|
65
|
+
| **CLI tooling** | ✅ Yes | curl | ❌ No | Limited | Limited |
|
|
66
|
+
| **Header overhead** | **10 bytes** | 200–800 bytes | 2–14 bytes | 5–50 bytes | 2–5 bytes |
|
|
67
|
+
| **Latency (LAN)** | **< 1ms** | 5–50ms | 1–10ms | 1–5ms | 5–20ms |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## npm Packages
|
|
72
|
+
|
|
73
|
+
| Package | Description | Link |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| **`afterlink`** | Meta-package (installs all 3) | [npm](https://www.npmjs.com/package/afterlink) |
|
|
76
|
+
| **`@afterlink/core`** | Frame codec and MessagePack serialization | [npm](https://www.npmjs.com/package/@afterlink/core) |
|
|
77
|
+
| **`@afterlink/server`** | Server SDK (TCP, routing, pub/sub, middleware) | [npm](https://www.npmjs.com/package/@afterlink/server) |
|
|
78
|
+
| **`@afterlink/client`** | Client SDK (auto-reconnect, subscriptions) | [npm](https://www.npmjs.com/package/@afterlink/client) |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Architecture
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
86
|
+
│ AfterLink Ecosystem │
|
|
87
|
+
│ │
|
|
88
|
+
│ ┌──────────┐ ┌──────────┐ ┌────────────────────────────────┐ │
|
|
89
|
+
│ │ Browser │ │ Mobile │ │ IoT / Microservices │ │
|
|
90
|
+
│ │ (JS SDK) │ │ (Dart) │ │ (Node.js / Python) │ │
|
|
91
|
+
│ └────┬─────┘ └────┬─────┘ └────────────┬───────────────────┘ │
|
|
92
|
+
│ │ WebSocket │ TCP │ TCP │
|
|
93
|
+
│ └───────────────┼────────────────────────┘ │
|
|
94
|
+
│ │ │
|
|
95
|
+
│ ┌────────▼──────────┐ │
|
|
96
|
+
│ │ AfterLink Server │ │
|
|
97
|
+
│ │ │ ┌──────────────────────────┐ │
|
|
98
|
+
│ │ ┌──────────────┐ │ │ Your Backend │ │
|
|
99
|
+
│ │ │ Frame Router │──┼───▶│ (Supabase / Firebase / │ │
|
|
100
|
+
│ │ └──────┬───────┘ │ │ MongoDB / AWS / pg) │ │
|
|
101
|
+
│ │ │ │ └──────────────────────────┘ │
|
|
102
|
+
│ │ ┌──────▼───────┐ │ │
|
|
103
|
+
│ │ │ Middleware │ │ ┌──────────────────────────┐ │
|
|
104
|
+
│ │ └──────┬───────┘ │ │ Pub/Sub Broker │ │
|
|
105
|
+
│ │ │ │ │ (In-process) │ │
|
|
106
|
+
│ │ ┌──────▼───────┐ │ └──────────────────────────┘ │
|
|
107
|
+
│ │ │ Route Handler│ │ │
|
|
108
|
+
│ │ └──────────────┘ │ │
|
|
109
|
+
│ └────────────────────┘ │
|
|
110
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Quick Start
|
|
116
|
+
|
|
117
|
+
### Installation
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Install all packages (recommended)
|
|
121
|
+
npm install afterlink
|
|
122
|
+
|
|
123
|
+
# Or install individual packages
|
|
124
|
+
npm install @afterlink/core @afterlink/server @afterlink/client
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**From source:**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/AJAYMYTH/AfterLink.git
|
|
131
|
+
cd AfterLink
|
|
132
|
+
npm install -g pnpm
|
|
133
|
+
pnpm install
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Hello World (5 minutes)
|
|
137
|
+
|
|
138
|
+
**Server** — `server.js`
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const { Server } = require('@afterlink/server');
|
|
142
|
+
|
|
143
|
+
const server = new Server({ port: 4000 });
|
|
144
|
+
|
|
145
|
+
server.on('ping', async (req, res) => {
|
|
146
|
+
res.send({ message: 'pong', timestamp: Date.now() });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
server.listen();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Client** — `client.js`
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
const { Client } = require('@afterlink/client');
|
|
156
|
+
|
|
157
|
+
async function main() {
|
|
158
|
+
const client = new Client('afterlink://localhost:4000');
|
|
159
|
+
await client.connect();
|
|
160
|
+
|
|
161
|
+
const result = await client.request('ping', {});
|
|
162
|
+
console.log(result); // { message: 'pong', timestamp: ... }
|
|
163
|
+
|
|
164
|
+
await client.disconnect();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Run it:**
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
node server.js # Terminal 1
|
|
174
|
+
node client.js # Terminal 2
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### TLS/SSL Encryption
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
// Server with TLS
|
|
181
|
+
const { Server, generateDevCerts } = require('@afterlink/server');
|
|
182
|
+
const { key, cert } = await generateDevCerts({ commonName: 'my-server' });
|
|
183
|
+
|
|
184
|
+
const server = new Server({
|
|
185
|
+
port: 4443,
|
|
186
|
+
tls: { enabled: true, key, cert, rejectUnauthorized: false },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Client connects via afterlinks:// scheme
|
|
190
|
+
const client = new Client('afterlinks://localhost:4443');
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Payload Compression
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
// Server enables compression
|
|
197
|
+
const server = new Server({
|
|
198
|
+
port: 4000,
|
|
199
|
+
compression: { enabled: true, algorithm: 'zlib', threshold: 1024 },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Client opts in
|
|
203
|
+
const client = new Client('tcp://localhost:4000', {
|
|
204
|
+
compression: { enabled: true, algorithm: 'zlib' },
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Rate Limiting
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
const server = new Server({
|
|
212
|
+
port: 4000,
|
|
213
|
+
rateLimit: {
|
|
214
|
+
enabled: true,
|
|
215
|
+
requestsPerSecond: 100,
|
|
216
|
+
burstSize: 200,
|
|
217
|
+
closeAfterViolations: 10,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Graceful Shutdown
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
const server = new Server({
|
|
226
|
+
port: 4000,
|
|
227
|
+
shutdown: { drainTimeout: 5000, reason: 'planned_restart' },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
server.handleProcessSignals(); // Auto-handles SIGTERM/SIGINT
|
|
231
|
+
await server.listen();
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Features in Action
|
|
237
|
+
|
|
238
|
+
### 1. Schema Validation
|
|
239
|
+
|
|
240
|
+
Invalid payloads are rejected **before** your handler runs:
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const { z } = require('zod');
|
|
244
|
+
|
|
245
|
+
server.on('createUser',
|
|
246
|
+
async (req, res) => {
|
|
247
|
+
const user = await db.create(req.body);
|
|
248
|
+
res.send({ user });
|
|
249
|
+
},
|
|
250
|
+
z.object({
|
|
251
|
+
name: z.string().min(2),
|
|
252
|
+
email: z.string().email(),
|
|
253
|
+
role: z.enum(['user', 'admin']).optional(),
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Client receives automatic error on invalid input:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{ "code": "VALIDATION_ERROR", "message": "String must contain at least 2 character(s)" }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 2. Middleware Chain
|
|
265
|
+
|
|
266
|
+
Express-style middleware for auth, logging, and rate limiting:
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
server.use(async (req, next) => {
|
|
270
|
+
if (!req.session?.userId) throw new Error('Not authenticated');
|
|
271
|
+
await next();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
server.use(async (req, next) => {
|
|
275
|
+
const start = Date.now();
|
|
276
|
+
await next();
|
|
277
|
+
console.log(`[${req.route}] ${Date.now() - start}ms`);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Built-in rate limiting (configured via Server options)
|
|
281
|
+
const server = new Server({
|
|
282
|
+
rateLimit: { enabled: true, requestsPerSecond: 100, burstSize: 200 },
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 3. Real-time Pub/Sub
|
|
287
|
+
|
|
288
|
+
Broadcast to all subscribers over the same connection:
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
// Server: publish on event
|
|
292
|
+
server.on('sendMessage', async (req, res) => {
|
|
293
|
+
const msg = await db.save(req.body);
|
|
294
|
+
server.publish('chat.newMessage', msg);
|
|
295
|
+
res.send({ ok: true });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Client: subscribe to topic
|
|
299
|
+
await client.subscribe('chat.newMessage', (msg) => {
|
|
300
|
+
console.log(`[${msg.from}] ${msg.text}`);
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 4. Auto-Reconnect
|
|
305
|
+
|
|
306
|
+
```js
|
|
307
|
+
const client = new Client('afterlink://api.example.com', {
|
|
308
|
+
autoReconnect: true,
|
|
309
|
+
maxReconnectAttempts: 10,
|
|
310
|
+
reconnectDelay: 1000,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
client.on('reconnecting', ({ attempt, delay }) => {
|
|
314
|
+
console.log(`Reconnecting (attempt ${attempt}) in ${delay}ms`);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
client.on('reconnected', () => console.log('Connection restored'));
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Protocol Specification
|
|
323
|
+
|
|
324
|
+
### Binary Frame Format
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
0 1 2 3
|
|
328
|
+
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|
|
329
|
+
├───────────────┼───────────────┼───────────────────────────────────┤
|
|
330
|
+
│ Frame Type │ Flags │ Message ID (4 bytes) │
|
|
331
|
+
├───────────────┴───────────────┴───────────────────────────────────┤
|
|
332
|
+
│ Payload Length (4 bytes) │
|
|
333
|
+
├───────────────────────────────────────────────────────────────────┤
|
|
334
|
+
│ Payload (MessagePack encoded) │
|
|
335
|
+
└───────────────────────────────────────────────────────────────────┘
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
| Field | Size | Purpose |
|
|
339
|
+
|---|---|---|
|
|
340
|
+
| Frame Type | 1 byte | Identifies the frame (REQUEST, RESPONSE, etc.) |
|
|
341
|
+
| Flags | 1 byte | Compression, encryption, priority bits |
|
|
342
|
+
| Message ID | 4 bytes | Correlates requests with responses (multiplexing) |
|
|
343
|
+
| Payload Length | 4 bytes | Size of the payload in bytes |
|
|
344
|
+
| Payload | Variable | MessagePack-encoded data |
|
|
345
|
+
|
|
346
|
+
**Total header: 10 bytes** | **Max payload: 16 MB**
|
|
347
|
+
|
|
348
|
+
### Frame Types
|
|
349
|
+
|
|
350
|
+
| Code | Type | Direction | Description |
|
|
351
|
+
|---|---|---|---|
|
|
352
|
+
| `0x01` | REQUEST | C → S | Client request to a named route |
|
|
353
|
+
| `0x02` | RESPONSE | S → C | Server response to a request |
|
|
354
|
+
| `0x03` | STREAM_START | S → C | Begin a streaming sequence |
|
|
355
|
+
| `0x04` | STREAM_DATA | S → C | A chunk of streamed data |
|
|
356
|
+
| `0x05` | STREAM_END | S → C | End of stream |
|
|
357
|
+
| `0x06` | ERROR | Both | Error response |
|
|
358
|
+
| `0x07` | PING | Both | Keep-alive ping |
|
|
359
|
+
| `0x08` | PONG | Both | Keep-alive pong |
|
|
360
|
+
| `0x09` | BROADCAST | S → C | Push to all clients |
|
|
361
|
+
| `0x0A` | SUBSCRIBE | C → S | Subscribe to a topic |
|
|
362
|
+
| `0x0B` | UNSUBSCRIBE | C → S | Unsubscribe from a topic |
|
|
363
|
+
| `0x0C` | PUBLISH | Both | Publish message to a topic |
|
|
364
|
+
| `0x0D` | CLOSE | Both | Graceful connection close |
|
|
365
|
+
| `0x0E` | CLOSE_ACK | Both | Acknowledge close |
|
|
366
|
+
| `0x0F` | HELLO | C → S | Initial handshake |
|
|
367
|
+
| `0x10` | HELLO_ACK | S → C | Handshake acknowledgment |
|
|
368
|
+
| `0x11` | SERVER_CLOSING | S → C | Server shutdown notification |
|
|
369
|
+
|
|
370
|
+
### Flags Byte
|
|
371
|
+
|
|
372
|
+
| Bit | Mask | Name | Description |
|
|
373
|
+
|---|---|---|---|
|
|
374
|
+
| 0 | `0x01` | COMPRESSED | Payload is compressed (zlib/Brotli) |
|
|
375
|
+
| 1 | `0x02` | ENCRYPTED | Reserved for future end-to-end encryption |
|
|
376
|
+
| 2 | `0x04` | PRIORITY | Reserved for future priority routing |
|
|
377
|
+
| 3 | `0x08` | FRAGMENTED | Reserved for future frame fragmentation |
|
|
378
|
+
| 4–7 | — | Reserved | Must be 0 |
|
|
379
|
+
|
|
380
|
+
### Connection Lifecycle
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
Client Server
|
|
384
|
+
│ │
|
|
385
|
+
│──── TCP Connect ──────────────────▶│
|
|
386
|
+
│──── HELLO Frame ──────────────────▶│
|
|
387
|
+
│ { version: "AL/1.1", │
|
|
388
|
+
│ auth: <JWT token>, │
|
|
389
|
+
│ capabilities: [...], │
|
|
390
|
+
│ compression: "zlib" } │
|
|
391
|
+
│ │
|
|
392
|
+
│◀─── HELLO_ACK Frame ──────────────│
|
|
393
|
+
│ { session_id: "...", │
|
|
394
|
+
│ server_version: "AL/1.1", │
|
|
395
|
+
│ compression: "zlib", │
|
|
396
|
+
│ rateLimit: { rps, burst } } │
|
|
397
|
+
│ │
|
|
398
|
+
│◀──▶ REQUEST / RESPONSE frames │
|
|
399
|
+
│◀──▶ PUBLISH / SUBSCRIBE frames │
|
|
400
|
+
│◀──▶ PING / PONG keep-alive │
|
|
401
|
+
│ │
|
|
402
|
+
│──── CLOSE Frame ─────────────────▶│
|
|
403
|
+
│◀─── CLOSE_ACK ────────────────────│
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Backend-Agnostic
|
|
409
|
+
|
|
410
|
+
AfterLink is a **communication layer**, not a database. It works with **any** backend:
|
|
411
|
+
|
|
412
|
+
| Your Backend | SDK | AfterLink Role |
|
|
413
|
+
|---|---|---|
|
|
414
|
+
| **Supabase** | `@supabase/supabase-js` | Real-time layer + auth gateway |
|
|
415
|
+
| **Firebase** | `firebase-admin` | Multi-client sync layer |
|
|
416
|
+
| **AWS** | `@aws-sdk/*` | Persistent connection manager |
|
|
417
|
+
| **MongoDB** | `mongodb` | Real-time change broadcasting |
|
|
418
|
+
| **PostgreSQL** | `pg` | Connection pooling + routing |
|
|
419
|
+
| **Custom REST** | `node-fetch` | Protocol upgrade layer |
|
|
420
|
+
|
|
421
|
+
Switch backends without changing any client code.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Demos
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
cd examples/demo-runner
|
|
429
|
+
node index.js
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
| Demo | What it Shows |
|
|
433
|
+
|---|---|
|
|
434
|
+
| `demo-runner` | Interactive showcase — 7 demos in one |
|
|
435
|
+
| `demo-chat` | Real-time pub/sub chat app |
|
|
436
|
+
| `demo-dashboard` | Live stock price feed |
|
|
437
|
+
| `demo-microservice` | CRUD with Zod schema validation |
|
|
438
|
+
| `hello-world` | Simple ping/pong starter |
|
|
439
|
+
| `tls-example` | TLS server + client with dev certs |
|
|
440
|
+
| `compression-example` | zlib/Brotli compression demo |
|
|
441
|
+
| `rate-limit-shutdown-example` | Rate limiting + graceful shutdown |
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## API Reference
|
|
446
|
+
|
|
447
|
+
### Server
|
|
448
|
+
|
|
449
|
+
```js
|
|
450
|
+
const server = new Server({
|
|
451
|
+
port: 4000,
|
|
452
|
+
host: '0.0.0.0',
|
|
453
|
+
maxConnections: 10000,
|
|
454
|
+
auth: { type: 'jwt', secret: process.env.JWT_SECRET },
|
|
455
|
+
tls: { enabled: true, key, cert, rejectUnauthorized: false },
|
|
456
|
+
compression: { enabled: true, algorithm: 'zlib', threshold: 1024, level: 6 },
|
|
457
|
+
rateLimit: { enabled: true, requestsPerSecond: 100, burstSize: 200 },
|
|
458
|
+
shutdown: { drainTimeout: 5000, reason: 'planned_restart' },
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
server.on('routeName', async (req, res) => { ... }, schema);
|
|
462
|
+
server.use(async (req, next) => { ... });
|
|
463
|
+
server.publish('topic', data);
|
|
464
|
+
server.on('closing', ({ activeConnections, activeRequests }) => { ... });
|
|
465
|
+
server.on('drained', ({ timedOut }) => { ... });
|
|
466
|
+
server.on('closed', () => { ... });
|
|
467
|
+
server.handleProcessSignals(); // Auto-handles SIGTERM/SIGINT
|
|
468
|
+
await server.listen();
|
|
469
|
+
await server.close(); // Graceful shutdown (async)
|
|
470
|
+
await server.close({ force: true }); // Force close (skip drain)
|
|
471
|
+
server.getConnectionCount();
|
|
472
|
+
server.getRouteCount();
|
|
473
|
+
server.isTLS();
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Client
|
|
477
|
+
|
|
478
|
+
```js
|
|
479
|
+
const client = new Client('afterlinks://localhost:4000', {
|
|
480
|
+
timeout: 30000,
|
|
481
|
+
autoReconnect: true,
|
|
482
|
+
maxReconnectAttempts: 5,
|
|
483
|
+
reconnectDelay: 1000,
|
|
484
|
+
pingInterval: 30000,
|
|
485
|
+
compression: { enabled: true, algorithm: 'zlib', level: 6, threshold: 1024 },
|
|
486
|
+
tls: { ca: fs.readFileSync('./ca.cert'), rejectUnauthorized: true },
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
await client.connect();
|
|
490
|
+
const result = await client.request('route', { body });
|
|
491
|
+
await client.subscribe('topic', handler);
|
|
492
|
+
client.publish('topic', data);
|
|
493
|
+
client.on('server-closing', ({ drainTimeout, reason }) => { ... });
|
|
494
|
+
client.on('disconnected', ({ graceful }) => { ... });
|
|
495
|
+
await client.disconnect();
|
|
496
|
+
client.isConnected();
|
|
497
|
+
client.isTLS();
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Project Structure
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
AfterLink/
|
|
506
|
+
├── packages/
|
|
507
|
+
│ ├── core/ # Protocol core (Frame, Serializer, Compression)
|
|
508
|
+
│ │ └── src/codec/ # Compression codec (zlib/Brotli)
|
|
509
|
+
│ ├── server/ # Server SDK (TCP, Router, Pub/Sub)
|
|
510
|
+
│ │ ├── src/middleware/ # Rate limiting middleware
|
|
511
|
+
│ │ ├── src/shutdown/ # Graceful shutdown handler
|
|
512
|
+
│ │ └── src/tls/ # Dev certificate generator
|
|
513
|
+
│ └── client/ # Client SDK (TCP, Reconnect, TLS)
|
|
514
|
+
├── examples/
|
|
515
|
+
│ ├── demo-runner/ # Interactive showcase (7 demos)
|
|
516
|
+
│ ├── demo-chat/ # Real-time chat app
|
|
517
|
+
│ ├── demo-dashboard/ # Stock price dashboard
|
|
518
|
+
│ ├── demo-microservice/ # CRUD with validation
|
|
519
|
+
│ ├── hello-world/ # Simple ping/pong
|
|
520
|
+
│ ├── tls-example/ # TLS server + client demo
|
|
521
|
+
│ ├── compression-example/# Compression demo
|
|
522
|
+
│ └── rate-limit-shutdown-example/
|
|
523
|
+
├── benchmarks/ # Performance benchmarks (vs WebSocket, Socket.IO)
|
|
524
|
+
├── docs/ # Protocol and API documentation
|
|
525
|
+
├── scripts/ # Release scripts (changelog check)
|
|
526
|
+
├── CHANGELOG.md # Formal versioned changelog
|
|
527
|
+
├── install.sh # Linux/macOS installer
|
|
528
|
+
├── install.ps1 # Windows installer
|
|
529
|
+
├── DEPLOYMENT.md # Full deployment guide
|
|
530
|
+
├── SECURITY.md # Security policy
|
|
531
|
+
├── BENCHMARKS.md # Benchmark results and methodology
|
|
532
|
+
└── README.md
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Testing
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
# Run all tests
|
|
541
|
+
pnpm test
|
|
542
|
+
|
|
543
|
+
# Individual package tests
|
|
544
|
+
pnpm test:core # 25 tests (Frame, Serializer, Compression)
|
|
545
|
+
pnpm test:server # 8 tests (Rate Limit, Shutdown)
|
|
546
|
+
pnpm test:client # 6 tests (Client API)
|
|
547
|
+
|
|
548
|
+
# Integration tests
|
|
549
|
+
node test-demos.js
|
|
550
|
+
|
|
551
|
+
# Demo showcase
|
|
552
|
+
cd examples/demo-runner && node index.js
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Benchmarks
|
|
558
|
+
|
|
559
|
+
**Environment**: Node.js v24.14.1 · Windows 11 (x64) · 10,000 iterations · localhost
|
|
560
|
+
|
|
561
|
+
### Latency (Request/Response)
|
|
562
|
+
|
|
563
|
+
| Library | Avg (ms) | P50 (ms) | P99 (ms) | Throughput |
|
|
564
|
+
|---------|----------|----------|----------|------------|
|
|
565
|
+
| **AfterLink** | **0.033** | **0.028** | **0.122** | **30,167 msg/s** |
|
|
566
|
+
| WebSocket (ws) | 0.058 | 0.037 | 0.199 | 17,054 msg/s |
|
|
567
|
+
| Socket.IO | 0.096 | 0.061 | 0.362 | 10,387 msg/s |
|
|
568
|
+
|
|
569
|
+
**AfterLink is 76.9% faster than WebSocket and 190.4% faster than Socket.IO.**
|
|
570
|
+
|
|
571
|
+
### Throughput by Payload Size
|
|
572
|
+
|
|
573
|
+
| Bytes | AfterLink | WebSocket | Socket.IO |
|
|
574
|
+
|-------|-----------|-----------|-----------|
|
|
575
|
+
| 64 | 15,474 | 16,501 | 9,592 |
|
|
576
|
+
| 256 | 10,766 | 13,441 | 8,815 |
|
|
577
|
+
| 1,024 | 10,914 | 13,720 | 8,331 |
|
|
578
|
+
| 4,096 | 7,450 | 12,959 | 8,736 |
|
|
579
|
+
| 16,384 | 4,161 | 5,754 | 3,931 |
|
|
580
|
+
|
|
581
|
+
See [BENCHMARKS.md](./BENCHMARKS.md) for full methodology and memory usage data.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## Performance Targets
|
|
586
|
+
|
|
587
|
+
| Metric | Target |
|
|
588
|
+
|---|---|
|
|
589
|
+
| Requests/second (single core) | > 100,000 |
|
|
590
|
+
| Round-trip latency (LAN) | < 1ms p50 |
|
|
591
|
+
| Memory per idle connection | < 50 KB |
|
|
592
|
+
| Frame decode time | < 10 µs |
|
|
593
|
+
| Header overhead | 10 bytes |
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Security
|
|
598
|
+
|
|
599
|
+
AfterLink is secure by default. See [SECURITY.md](./SECURITY.md) for full details.
|
|
600
|
+
|
|
601
|
+
- **Supply Chain Safe:** Minimal dependencies, locked versions, no postinstall scripts
|
|
602
|
+
- **Protocol Hardened:** Strict frame validation, buffer limits, and MessagePack deserialization
|
|
603
|
+
- **TLS Encryption:** End-to-end encrypted connections with `afterlinks://` scheme
|
|
604
|
+
- **Dev Certificates:** `generateDevCerts()` for self-signed certs without external tools
|
|
605
|
+
- **Rate Limiting:** Per-connection token-bucket rate limiter prevents abuse
|
|
606
|
+
- **JWT Auth:** Built-in JWT validation in HELLO handshake
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
pnpm audit --prod
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Production Deployment
|
|
615
|
+
|
|
616
|
+
See [DEPLOYMENT.md](./DEPLOYMENT.md) for full guides.
|
|
617
|
+
|
|
618
|
+
| Platform | Setup Time | Difficulty |
|
|
619
|
+
|---|---|---|
|
|
620
|
+
| **PM2** (single server) | 2 min | Easy |
|
|
621
|
+
| **Docker** | 5 min | Easy |
|
|
622
|
+
| **Railway** | 2 min | Easy |
|
|
623
|
+
| **Render** | 3 min | Easy |
|
|
624
|
+
| **Fly.io** | 3 min | Easy |
|
|
625
|
+
| **AWS EC2** | 5 min | Medium |
|
|
626
|
+
| **Kubernetes** | 15 min | Advanced |
|
|
627
|
+
| **VPS + Nginx** | 10 min | Medium |
|
|
628
|
+
|
|
629
|
+
### Quick Start with PM2
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
npm install -g pm2
|
|
633
|
+
pm2 start server.js --name afterlink
|
|
634
|
+
pm2 save && pm2 startup
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Quick Start with Docker
|
|
638
|
+
|
|
639
|
+
```dockerfile
|
|
640
|
+
FROM node:20-alpine
|
|
641
|
+
WORKDIR /app
|
|
642
|
+
COPY . .
|
|
643
|
+
RUN npm install -g pnpm && pnpm install --prod
|
|
644
|
+
EXPOSE 4000
|
|
645
|
+
CMD ["node", "server.js"]
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Environment Variables
|
|
649
|
+
|
|
650
|
+
```env
|
|
651
|
+
AFTERLINK_PORT=4000
|
|
652
|
+
AFTERLINK_HOST=0.0.0.0
|
|
653
|
+
AFTERLINK_MAX_CONNECTIONS=10000
|
|
654
|
+
AFTERLINK_JWT_SECRET=your-secret
|
|
655
|
+
NODE_ENV=production
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## Roadmap
|
|
661
|
+
|
|
662
|
+
> The following upgrades are planned across three phases. See the [Upgrade Schedule](#upgrade-schedule) below for timeline details.
|
|
663
|
+
|
|
664
|
+
### ~~Phase 1~~ — v1.1 · Protocol & Stability ✅ **COMPLETE**
|
|
665
|
+
|
|
666
|
+
| Feature | Description | Status |
|
|
667
|
+
|---|---|---|
|
|
668
|
+
| **TLS/SSL encryption** | End-to-end encrypted connections via `afterlinks://` | ✅ Done |
|
|
669
|
+
| **Payload compression** | zlib/Brotli compression on the Flags byte | ✅ Done |
|
|
670
|
+
| **Rate limiting middleware** | Per-connection token-bucket rate limiter | ✅ Done |
|
|
671
|
+
| **Graceful shutdown** | Drain active requests, `SERVER_CLOSING` frame | ✅ Done |
|
|
672
|
+
| **CHANGELOG.md** | Formal versioned changelog | ✅ Done |
|
|
673
|
+
|
|
674
|
+
### Phase 2 — v1.2 · Developer Experience (Weeks 5–8)
|
|
675
|
+
|
|
676
|
+
| Feature | Description |
|
|
677
|
+
|---|---|
|
|
678
|
+
| **`afterlink` CLI tool** | `afterlink ping`, `afterlink call <route>`, `afterlink monitor` in terminal |
|
|
679
|
+
| **`@afterlink/browser`** | Native WebSocket transport wrapper so browsers can connect directly |
|
|
680
|
+
| **TypeScript types** | Full `.d.ts` type definitions for server, client, and core packages |
|
|
681
|
+
| **Health check endpoint** | Built-in `/__health` route returning server stats and uptime |
|
|
682
|
+
| **Better error codes** | Structured error taxonomy: `AUTH_FAILED`, `ROUTE_NOT_FOUND`, `TIMEOUT`, etc. |
|
|
683
|
+
|
|
684
|
+
### Phase 3 — v2.0 · Scale & Ecosystem (Weeks 9–16)
|
|
685
|
+
|
|
686
|
+
| Feature | Description |
|
|
687
|
+
|---|---|
|
|
688
|
+
| **`@afterlink/cluster`** | Multi-process clustering with shared pub/sub via Redis adapter |
|
|
689
|
+
| **`@afterlink/python`** | Python client/server SDK (`pip install afterlink`) |
|
|
690
|
+
| **`@afterlink/dart`** | Dart/Flutter client for mobile apps |
|
|
691
|
+
| **Protocol v2 frame** | Extended header with routing key + priority field |
|
|
692
|
+
| **Metrics & observability** | Prometheus-compatible `/metrics` endpoint + OpenTelemetry tracing |
|
|
693
|
+
| **Playground UI** | Browser-based interactive demo at `afterlinkdocs.vercel.app/playground` |
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Upgrade Schedule
|
|
698
|
+
|
|
699
|
+
```
|
|
700
|
+
May 2026 ──────────────────────────────────────────────── Aug 2026
|
|
701
|
+
|
|
702
|
+
Week 1–2 [████] ✅ TLS encryption + compression flag
|
|
703
|
+
Week 2–3 [████] ✅ Rate limiting middleware
|
|
704
|
+
Week 3–4 [████] ✅ Graceful shutdown + CHANGELOG
|
|
705
|
+
Week 5–6 [░░░░] CLI tool (afterlink ping/call/monitor)
|
|
706
|
+
Week 6–7 [░░░░] @afterlink/browser WebSocket transport
|
|
707
|
+
Week 7–8 [░░░░] TypeScript definitions + health check
|
|
708
|
+
Week 9–10 [░░░░] Redis-backed cluster pub/sub
|
|
709
|
+
Week 11–12 [░░░░] Python SDK
|
|
710
|
+
Week 13–14 [░░░░] Dart/Flutter SDK
|
|
711
|
+
Week 15 [░░░░] Protocol v2 frame design + migration guide
|
|
712
|
+
Week 16 [░░░░] Prometheus metrics + playground UI launch
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
| Milestone | Target Date | Version | Status |
|
|
716
|
+
|---|---|---|---|
|
|
717
|
+
| TLS + compression + rate limiting + shutdown | May 2026 | v1.1.0 | ✅ Released |
|
|
718
|
+
| CLI tool + browser SDK + TypeScript | July 2026 | v1.2.0 | 🔄 Planned |
|
|
719
|
+
| Cluster + Python + Dart SDKs | August 2026 | v2.0.0 | 🔄 Planned |
|
|
720
|
+
| Metrics, Protocol v2, Playground | August 2026 | v2.0.0 | 🔄 Planned |
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## Contributing
|
|
725
|
+
|
|
726
|
+
Contributions, issues, and feature requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) to get started.
|
|
727
|
+
|
|
728
|
+
Please note that this project is released with a [Contributor Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you agree to uphold this code.
|
|
729
|
+
|
|
730
|
+
1. Fork the repository
|
|
731
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
732
|
+
3. Commit your changes (`git commit -m 'feat: add my feature'`)
|
|
733
|
+
4. Push to the branch (`git push origin feature/my-feature`)
|
|
734
|
+
5. Open a Pull Request
|
|
735
|
+
|
|
736
|
+
**Have a question?** Start a [GitHub Discussion](https://github.com/AJAYMYTH/AfterLink/discussions) — we welcome ideas, Q&A, and project showcases.
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## Author
|
|
741
|
+
|
|
742
|
+
**Ajju** (Javali Ajayakumar)
|
|
743
|
+
Diploma in AI & ML · GTTC Magadi, Karnataka
|
|
744
|
+
[GitHub](https://github.com/AJAYMYTH) · [npm](https://www.npmjs.com/~ajaymyth)
|
|
745
|
+
|
|
746
|
+
---
|
|
747
|
+
|
|
748
|
+
## License
|
|
749
|
+
|
|
750
|
+
[MIT](./LICENSE) — Free for personal and commercial use.
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
<div align="center">
|
|
755
|
+
|
|
756
|
+
**Built with precision. Designed for speed.**
|
|
757
|
+
|
|
758
|
+
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afterlink/server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "AfterLink Protocol Server SDK - TCP server with routing, middleware, and pub/sub",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"middleware"
|
|
21
21
|
],
|
|
22
22
|
"files": [
|
|
23
|
-
"src/**/*.js"
|
|
23
|
+
"src/**/*.js",
|
|
24
|
+
"README.md"
|
|
24
25
|
],
|
|
25
26
|
"publishConfig": {
|
|
26
27
|
"access": "public"
|