stelar-time-real 1.0.2 → 1.1.0

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 CHANGED
@@ -13,6 +13,8 @@ Tu propio sistema de tiempo real personalizado. Una librería ligera y sin depen
13
13
  - 🎯 **Personalizable** - Vos controlás todo, nada de código ajeno
14
14
  - 🔌 **Compatible** - Funciona con Express, Fastify, HTTP nativo, etc.
15
15
  - 💓 **Heartbeat incluido** - Detecta desconexiones automáticamente
16
+ - 🌐 **Namespaces** - Múltiples canales independientes (`/chat`, `/game`, etc.)
17
+ - ⚡ **ACK ultra rápido** - Request-response con Promesas, sin overhead
16
18
 
17
19
  ## Instalación
18
20
 
@@ -22,11 +24,17 @@ npm install stelar-time-real
22
24
 
23
25
  ## Inicio Rápido
24
26
 
27
+ ### Un solo import para todo
28
+
29
+ ```javascript
30
+ import StelarServer, { StelarClient } from 'stelar-time-real';
31
+ ```
32
+
25
33
  ### Servidor
26
34
 
27
35
  ```javascript
28
36
  import express from 'express';
29
- import StelarServer from 'stelar-time-real';
37
+ import { StelarServer } from 'stelar-time-real';
30
38
 
31
39
  const app = express();
32
40
  const server = app.listen(3000);
@@ -48,7 +56,7 @@ stelar.start();
48
56
  ### Cliente
49
57
 
50
58
  ```javascript
51
- import StelarClient from 'stelar-time-real/client';
59
+ import { StelarClient } from 'stelar-time-real';
52
60
 
53
61
  const client = new StelarClient('localhost:3000');
54
62
 
@@ -184,15 +192,78 @@ stelar.on('mensaje', (ctx) => {
184
192
  ctx.socket // WebSocket del cliente
185
193
  ctx.req // Request HTTP original
186
194
  ctx.data // Datos recibidos
187
-
195
+
188
196
  // Métodos disponibles:
189
197
  ctx.emit('evento', data) // Enviar solo a este cliente
198
+ ctx.send('respuesta', data) // Responder a un ACK
190
199
  ctx.broadcast('evento', data) // Enviar a todos
191
200
  ctx.to('sala', 'evento', data) // Enviar a una sala
192
201
  ctx.toId('id', 'evento', data) // Enviar a un cliente específico
193
202
  ctx.getClients('sala') // Ver clientes en sala
194
203
  ctx.joinRoom('sala') // Unir a sala
195
204
  ctx.leaveRoom() // Salir de sala
205
+ ctx.ack('miAck', data) // Responder a un ACK personalizado
206
+ });
207
+ ```
208
+
209
+ #### Namespaces
210
+
211
+ Crear canales independientes:
212
+
213
+ ```javascript
214
+ import { StelarServer } from 'stelar-time-real';
215
+
216
+ // Namespace principal
217
+ const main = new StelarServer({ server, namespace: '/' });
218
+
219
+ // Namespace de chat
220
+ const chat = StelarServer.of('/chat', { server });
221
+ chat.on('message', (ctx) => {
222
+ ctx.broadcast('message', ctx.data);
223
+ });
224
+
225
+ // Namespace de game
226
+ const game = StelarServer.of('/game', { server });
227
+ game.on('move', (ctx) => {
228
+ ctx.to(ctx.data.room, 'move', ctx.data);
229
+ });
230
+ ```
231
+
232
+ #### ACK (Request-Response)
233
+
234
+ Sistema ultra eficiente con Promesas:
235
+
236
+ **Servidor:**
237
+
238
+ ```javascript
239
+ // Registrar un ACK handler
240
+ stelar.onAck('getUser', (ctx) => {
241
+ return { id: ctx.data.id, name: 'John' };
242
+ });
243
+
244
+ // O con lógica más compleja
245
+ stelar.onAck('saveData', (ctx) => {
246
+ const result = saveToDatabase(ctx.data);
247
+ return { success: true, id: result.id };
248
+ });
249
+ ```
250
+
251
+ **Cliente:**
252
+
253
+ ```javascript
254
+ // Usando request() - retorna Promise
255
+ const user = await client.request('getUser', { id: 1 }, 'userData');
256
+ console.log(user); // { id: 1, name: 'John' }
257
+
258
+ // O emitiendo con callback
259
+ client.emit('getUser', { id: 1 }, { ack: 'userData' });
260
+ client.on('userData', (data) => {
261
+ console.log(data);
262
+ });
263
+
264
+ // ACK desde el servidor al cliente
265
+ client.onAck('serverPush', (data) => {
266
+ console.log('El servidor envió:', data);
196
267
  });
197
268
  ```
198
269
 
@@ -249,11 +320,32 @@ client.onAll(({ event, data }) => {
249
320
  });
250
321
  ```
251
322
 
252
- **`.emit(event, data)`**
253
- Enviar eventos al servidor.
323
+ **`.onAck(name, handler)`**
324
+ Escuchar respuestas de ACK del servidor.
325
+
326
+ ```javascript
327
+ client.onAck('userData', (data) => {
328
+ console.log('Datos recibidos:', data);
329
+ });
330
+ ```
331
+
332
+ **`.emit(event, data, opts)`**
333
+ Enviar eventos al servidor. Soporta `opts.ack` para ACKs.
254
334
 
255
335
  ```javascript
256
336
  client.emit('chat', { mensaje: 'Hola!' });
337
+ client.emit('getUser', { id: 1 }, { ack: 'userData' });
338
+ ```
339
+
340
+ **`.request(event, data, ackName)`**
341
+ Enviar y esperar respuesta como Promise.
342
+
343
+ ```javascript
344
+ const result = await client.request('getUser', { id: 1 }, 'userData');
345
+ console.log(result); // { id: 1, name: 'John' }
346
+
347
+ // Con timeout opcional
348
+ const client = new StelarClient(3000, { ackTimeout: 10000 });
257
349
  ```
258
350
 
259
351
  **`.joinRoom(room)`**
@@ -320,7 +412,7 @@ client.on('error', (err) => {}); // Cuando hay error
320
412
  **server.js**
321
413
  ```javascript
322
414
  import express from 'express';
323
- import StelarServer from 'stelar-time-real';
415
+ import { StelarServer } from 'stelar-time-real';
324
416
 
325
417
  const app = express();
326
418
  const server = app.listen(3000);
@@ -342,7 +434,7 @@ console.log('Chat en http://localhost:3000');
342
434
  **cliente.html**
343
435
  ```html
344
436
  <script type="module">
345
- import StelarClient from 'stelar-time-real/client.js';
437
+ import { StelarClient } from 'stelar-time-real';
346
438
 
347
439
  const client = new StelarClient(3000);
348
440
 
@@ -393,6 +485,8 @@ stelar.use((ctx, next) => {
393
485
  ### Con Reconexión Automática
394
486
 
395
487
  ```javascript
488
+ import { StelarClient } from 'stelar-time-real';
489
+
396
490
  const client = new StelarClient('localhost:3000', {
397
491
  reconnection: true,
398
492
  reconnectionAttempts: 5,
@@ -420,6 +514,18 @@ client.connect();
420
514
 
421
515
  ## Changelog
422
516
 
517
+ ### v1.1.0
518
+ - Namespaces: `StelarServer.of('/chat')` para múltiples canales
519
+ - ACK personalizado: `ctx.ack('nombre', data)` y `client.request(event, data, 'ackName')`
520
+ - Sistema de request-response con Promesas, ultra eficiente
521
+
522
+ ### v1.0.3
523
+ - Exportación mejorada: un solo import para servidor y cliente
524
+ - `import { StelarServer, StelarClient } from 'stelar-time-real'`
525
+
526
+ ### v1.0.2
527
+ - Exportación mejorada: un solo import para servidor y cliente
528
+
423
529
  ### v1.0.0
424
530
  - Lanzamiento inicial
425
531
  - WebSockets con ws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stelar-time-real",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Tu propio sistema de tiempo real personalizado - WebSocket ligero sin dependencias",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/client.js CHANGED
@@ -12,7 +12,8 @@ class StelarClient {
12
12
  reconnection: options.reconnection !== false,
13
13
  reconnectionAttempts: options.reconnectionAttempts || 5,
14
14
  reconnectionDelay: options.reconnectionDelay || 1000,
15
- heartbeatInterval: options.heartbeatInterval || 30000
15
+ heartbeatInterval: options.heartbeatInterval || 30000,
16
+ ackTimeout: options.ackTimeout || 5000
16
17
  };
17
18
 
18
19
  this.ws = null;
@@ -23,6 +24,65 @@ class StelarClient {
23
24
  this._reconnectAttempts = 0;
24
25
  this._hbTimer = null;
25
26
  this._isManualClose = false;
27
+ this._acks = {};
28
+ }
29
+
30
+ setUrl(url) {
31
+ this.url = url;
32
+ return this;
33
+ }
34
+
35
+ on(event, handler) {
36
+ this.events[event] = handler;
37
+ return this;
38
+ }
39
+
40
+ onAll(handler) {
41
+ this._wildcardHandler = handler;
42
+ return this;
43
+ }
44
+
45
+ onAck(name, handler) {
46
+ this._acks[name] = handler;
47
+ return this;
48
+ }
49
+
50
+ emit(event, data, opts = {}) {
51
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
52
+ const payload = { event, data };
53
+ if (opts.ack) {
54
+ payload._ackName = opts.ack;
55
+ }
56
+ this.ws.send(JSON.stringify(payload));
57
+ }
58
+ return this;
59
+ }
60
+
61
+ request(event, data, ackName) {
62
+ return new Promise((resolve, reject) => {
63
+ const timeout = setTimeout(() => {
64
+ reject(new Error(`ACK '${ackName}' timeout`));
65
+ }, this.options.ackTimeout);
66
+
67
+ const handler = (responseData) => {
68
+ clearTimeout(timeout);
69
+ this.off(ackName, handler);
70
+ resolve(responseData);
71
+ };
72
+
73
+ this.on(ackName, handler);
74
+ this.emit(event, data, { ack: ackName });
75
+ });
76
+ }
77
+
78
+ joinRoom(room) {
79
+ this.emit('join-room', room);
80
+ return this;
81
+ }
82
+
83
+ leaveRoom() {
84
+ this.emit('leave-room', {});
85
+ return this;
26
86
  }
27
87
 
28
88
  setUrl(url) {
@@ -86,10 +146,15 @@ class StelarClient {
86
146
  this.ws.onmessage = (e) => {
87
147
  try {
88
148
  const msg = JSON.parse(e.data);
89
- const { event, data } = msg;
149
+ const { event, data, _isAck } = msg;
90
150
 
91
151
  if (event === 'ping') return;
92
152
 
153
+ if (_isAck && this._acks[event]) {
154
+ this._acks[event](data);
155
+ return;
156
+ }
157
+
93
158
  if (this.events[event]) this.events[event](data);
94
159
 
95
160
  if (this._wildcardHandler) {
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ class StelarServer {
5
5
  constructor(options = {}) {
6
6
  this.port = options.port || 3000;
7
7
  this.server = options.server || null;
8
+ this.namespace = options.namespace || '/';
8
9
  this.wss = null;
9
10
  this.clients = new Map();
10
11
  this.events = {};
@@ -13,6 +14,15 @@ class StelarServer {
13
14
  this._hbTimer = null;
14
15
  this._wildcardHandler = null;
15
16
  this._connectionHandler = null;
17
+ this._acks = {};
18
+ this._parent = null;
19
+ this._children = new Map();
20
+ }
21
+
22
+ static of(path, options = {}) {
23
+ const ns = new StelarServer({ ...options, namespace: path });
24
+ ns._parent = this;
25
+ return ns;
16
26
  }
17
27
 
18
28
  use(middleware) {
@@ -35,6 +45,11 @@ class StelarServer {
35
45
  return this;
36
46
  }
37
47
 
48
+ onAck(name, handler) {
49
+ this._acks[name] = handler;
50
+ return this;
51
+ }
52
+
38
53
  broadcast(event, data) {
39
54
  this.clients.forEach((info, client) => {
40
55
  client.send(JSON.stringify({ event, data }));
@@ -98,6 +113,14 @@ class StelarServer {
98
113
  }
99
114
 
100
115
  _handleConnection(client, req) {
116
+ const urlPath = new URL(req.url, `http://localhost`).pathname;
117
+ const nsPath = this.namespace === '/' ? '/' : this.namespace;
118
+
119
+ if (nsPath !== '/' && urlPath !== nsPath) {
120
+ client.close();
121
+ return;
122
+ }
123
+
101
124
  const clientId = Math.random().toString(36).substring(7);
102
125
  const clientInfo = { id: clientId, room: null, lastPing: Date.now() };
103
126
  this.clients.set(client, clientInfo);
@@ -107,6 +130,7 @@ class StelarServer {
107
130
  socket: client,
108
131
  req,
109
132
  emit: (evt, d) => client.send(JSON.stringify({ event: evt, data: d })),
133
+ send: (respId, d) => client.send(JSON.stringify({ event: respId, data: d, _isAck: true })),
110
134
  broadcast: (evt, d) => this.broadcast(evt, d),
111
135
  to: (room, evt, d) => this.to(room, evt, d),
112
136
  toId: (id, evt, d) => this.toId(id, evt, d),
@@ -117,6 +141,15 @@ class StelarServer {
117
141
  },
118
142
  leaveRoom: () => {
119
143
  clientInfo.room = null;
144
+ },
145
+ ack: (ackName, data) => {
146
+ const ackHandler = this._acks[ackName];
147
+ if (ackHandler) {
148
+ const result = ackHandler({ ...ctx, data });
149
+ if (result !== undefined) {
150
+ client.send(JSON.stringify({ event: ackName, data: result, _isAck: true }));
151
+ }
152
+ }
120
153
  }
121
154
  };
122
155
 
@@ -146,6 +179,15 @@ class StelarServer {
146
179
  client.send(JSON.stringify({ event: 'left-room', data }));
147
180
  }
148
181
 
182
+ if (msg._ackName && this._acks[msg._ackName]) {
183
+ const ackHandler = this._acks[msg._ackName];
184
+ const result = ackHandler({ ...ctx, data });
185
+ if (result !== undefined) {
186
+ client.send(JSON.stringify({ event: msg._ackName, data: result, _isAck: true }));
187
+ }
188
+ return;
189
+ }
190
+
149
191
  const eventCtx = { ...ctx, data };
150
192
 
151
193
  if (this.events[event]) {