@eleven-am/pondsocket-nest 0.0.130 → 0.0.131
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 +255 -228
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# PondSocket NestJS Integration
|
|
2
2
|
|
|
3
|
-
This package provides a NestJS integration layer for PondSocket, making it easy to use PondSocket's real-time WebSocket
|
|
3
|
+
This package provides a NestJS integration layer for PondSocket, making it easy to use PondSocket's real-time WebSocket
|
|
4
|
+
functionality within NestJS applications.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
@@ -10,7 +11,9 @@ npm install @eleven-am/pondsocket-nest
|
|
|
10
11
|
|
|
11
12
|
## Overview
|
|
12
13
|
|
|
13
|
-
This package integrates PondSocket's powerful WebSocket capabilities with NestJS's architecture and dependency injection
|
|
14
|
+
This package integrates PondSocket's powerful WebSocket capabilities with NestJS's architecture and dependency injection
|
|
15
|
+
system. It provides decorators and services that make it natural to use WebSocket functionality in a NestJS application
|
|
16
|
+
while maintaining all of PondSocket's features.
|
|
14
17
|
|
|
15
18
|
## Key Features
|
|
16
19
|
|
|
@@ -27,17 +30,17 @@ This package integrates PondSocket's powerful WebSocket capabilities with NestJS
|
|
|
27
30
|
### Module Setup
|
|
28
31
|
|
|
29
32
|
```typescript
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
@Module({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
import {Module} from '@nestjs/common';
|
|
34
|
+
import {PondSocketModule} from '@eleven-am/pondsocket-nest';
|
|
35
|
+
|
|
36
|
+
@Module ({
|
|
37
|
+
imports: [
|
|
38
|
+
PondSocketModule.forRoot ({
|
|
39
|
+
guards: [AuthGuard], // Optional: Global guards
|
|
40
|
+
pipes: [ValidationPipe], // Optional: Global pipes
|
|
41
|
+
isGlobal: true, // Optional: Make the module global
|
|
42
|
+
})
|
|
43
|
+
]
|
|
41
44
|
})
|
|
42
45
|
export class AppModule {}
|
|
43
46
|
```
|
|
@@ -45,75 +48,75 @@ export class AppModule {}
|
|
|
45
48
|
### Creating WebSocket Endpoints
|
|
46
49
|
|
|
47
50
|
```typescript
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
51
|
+
import {Controller} from '@nestjs/common';
|
|
52
|
+
import {PondSocketEndpoint, PondSocketConnection, Context} from '@eleven-am/pondsocket-nest';
|
|
50
53
|
|
|
51
|
-
@
|
|
54
|
+
@Endpoint('/api/socket')
|
|
52
55
|
export class SocketController {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
@OnConnection()
|
|
57
|
+
async handleConnection (ctx: Context) {
|
|
58
|
+
const token = ctx.request.query.token;
|
|
59
|
+
|
|
60
|
+
if (isValidToken (token)) {
|
|
61
|
+
const role = getRoleFromToken (token);
|
|
62
|
+
ctx.accept ({role});
|
|
63
|
+
} else {
|
|
64
|
+
ctx.reject ('Invalid token', 401);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
64
67
|
}
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
### Creating Channels
|
|
68
71
|
|
|
69
72
|
```typescript
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
73
|
+
import {Controller} from '@nestjs/common';
|
|
74
|
+
import {PondSocketChannel, PondSocketJoin, Context} from '@eleven-am/pondsocket-nest';
|
|
72
75
|
|
|
73
|
-
@
|
|
76
|
+
@Channel('/channel/:id')
|
|
74
77
|
export class ChannelController {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
@OnJoin()
|
|
79
|
+
async handleJoin (ctx: Context) {
|
|
80
|
+
const {role} = ctx.user.assigns;
|
|
81
|
+
const {username} = ctx.joinParams;
|
|
82
|
+
const {id} = ctx.event.params;
|
|
83
|
+
|
|
84
|
+
if (role === 'admin') {
|
|
85
|
+
ctx.accept({username})
|
|
86
|
+
.trackPresence({
|
|
87
|
+
username,
|
|
88
|
+
role,
|
|
89
|
+
status: 'online',
|
|
90
|
+
onlineSince: Date.now (),
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
ctx.decline('Insufficient permissions', 403);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
```
|
|
95
98
|
|
|
96
99
|
### Handling Channel Events
|
|
97
100
|
|
|
98
101
|
```typescript
|
|
99
|
-
import {
|
|
100
|
-
import {
|
|
102
|
+
import {Controller} from '@nestjs/common';
|
|
103
|
+
import {PondSocketEvent, Context} from '@eleven-am/pondsocket-nest';
|
|
101
104
|
|
|
102
|
-
@
|
|
105
|
+
@Channel('/channel/:id')
|
|
103
106
|
export class ChannelController {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
@OnEvent('message')
|
|
108
|
+
async handleMessage (ctx: Context) {
|
|
109
|
+
const {text} = ctx.event.payload;
|
|
110
|
+
|
|
111
|
+
// Broadcast to all users in the channel
|
|
112
|
+
ctx.broadcast('message', {text});
|
|
113
|
+
|
|
114
|
+
// Broadcast to specific users
|
|
115
|
+
ctx.broadcastTo(['user1', 'user2'], 'message', {text});
|
|
116
|
+
|
|
117
|
+
// Broadcast to all except sender
|
|
118
|
+
ctx.broadcastFrom('message', {text});
|
|
119
|
+
}
|
|
117
120
|
}
|
|
118
121
|
```
|
|
119
122
|
|
|
@@ -122,48 +125,56 @@ export class ChannelController {
|
|
|
122
125
|
### Presence Management
|
|
123
126
|
|
|
124
127
|
```typescript
|
|
125
|
-
|
|
128
|
+
|
|
129
|
+
@Channel ('/channel/:id')
|
|
126
130
|
export class ChannelController {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
@OnEvent ('presence')
|
|
132
|
+
async handlePresence (ctx: Context) {
|
|
133
|
+
ctx.trackPresence ({
|
|
134
|
+
username: ctx.user.assigns.username,
|
|
135
|
+
status: 'online',
|
|
136
|
+
lastSeen: Date.now ()
|
|
137
|
+
});
|
|
138
|
+
}
|
|
135
139
|
}
|
|
136
140
|
```
|
|
137
141
|
|
|
138
142
|
### User Assigns
|
|
139
143
|
|
|
140
144
|
```typescript
|
|
141
|
-
|
|
145
|
+
|
|
146
|
+
@Channel ('/channel/:id')
|
|
142
147
|
export class ChannelController {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
@OnEvent ('update-profile')
|
|
149
|
+
async handleProfileUpdate (ctx: Context) {
|
|
150
|
+
ctx.assign ({
|
|
151
|
+
...ctx.user.assigns,
|
|
152
|
+
profile: ctx.event.payload
|
|
153
|
+
});
|
|
154
|
+
}
|
|
150
155
|
}
|
|
151
156
|
```
|
|
152
157
|
|
|
153
158
|
### Error Handling
|
|
154
159
|
|
|
155
160
|
```typescript
|
|
156
|
-
@
|
|
161
|
+
import {PondChannel} from "@eleven-am/pondsocket/types";
|
|
162
|
+
import {ChannelInstance} from "@eleven-am/pondsocke-nest";
|
|
163
|
+
|
|
164
|
+
@Channel ('/channel/:id')
|
|
157
165
|
export class ChannelController {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
@ChannelInstance()
|
|
167
|
+
instancd: PondChannel // Instance of the channel
|
|
168
|
+
|
|
169
|
+
@OnEvent ('message')
|
|
170
|
+
async handleMessage (ctx: Context) {
|
|
171
|
+
try {
|
|
172
|
+
// Your logic here
|
|
173
|
+
ctx.accept();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
ctx.decline (error.message, 400);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
167
178
|
}
|
|
168
179
|
```
|
|
169
180
|
|
|
@@ -172,36 +183,38 @@ export class ChannelController {
|
|
|
172
183
|
The package maintains PondSocket's distributed deployment capabilities:
|
|
173
184
|
|
|
174
185
|
```typescript
|
|
175
|
-
import {
|
|
176
|
-
import {
|
|
177
|
-
import {
|
|
186
|
+
import {Module} from '@nestjs/common';
|
|
187
|
+
import {PondSocketModule} from '@eleven-am/pondsocket-nest';
|
|
188
|
+
import {RedisBackend} from '@eleven-am/pondsocket';
|
|
178
189
|
|
|
179
190
|
@Module({
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
imports: [
|
|
192
|
+
PondSocketModule.forRoot({
|
|
193
|
+
backend: new RedisBackend({
|
|
194
|
+
host: 'localhost',
|
|
195
|
+
port: 6379
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
]
|
|
188
199
|
})
|
|
189
|
-
export class AppModule {
|
|
200
|
+
export class AppModule {
|
|
201
|
+
}
|
|
190
202
|
```
|
|
191
203
|
|
|
192
204
|
### Distributed Mode Features
|
|
193
205
|
|
|
194
|
-
The distributed mode enables you to scale your WebSocket application across multiple server instances while maintaining
|
|
206
|
+
The distributed mode enables you to scale your WebSocket application across multiple server instances while maintaining
|
|
207
|
+
state synchronization. Here are the key features:
|
|
195
208
|
|
|
196
209
|
1. **State Synchronization**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
- Channel presence is synchronized across all instances
|
|
211
|
+
- User assigns are shared between instances
|
|
212
|
+
- Channel events are broadcasted to all instances
|
|
200
213
|
|
|
201
214
|
2. **Load Balancing**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
- Multiple server instances can handle WebSocket connections
|
|
216
|
+
- Connections are distributed across available instances
|
|
217
|
+
- Automatic failover if an instance goes down
|
|
205
218
|
|
|
206
219
|
3. **Backend Options**
|
|
207
220
|
```typescript
|
|
@@ -245,9 +258,9 @@ The distributed mode enables you to scale your WebSocket application across mult
|
|
|
245
258
|
|
|
246
259
|
5. **Error Handling**
|
|
247
260
|
```typescript
|
|
248
|
-
@
|
|
261
|
+
@Channel('/channel/:id')
|
|
249
262
|
export class ChannelController {
|
|
250
|
-
@
|
|
263
|
+
@OnEvent('message')
|
|
251
264
|
async handleMessage(ctx: Context) {
|
|
252
265
|
try {
|
|
253
266
|
// Your logic here
|
|
@@ -285,20 +298,20 @@ The distributed mode enables you to scale your WebSocket application across mult
|
|
|
285
298
|
```
|
|
286
299
|
|
|
287
300
|
7. **Best Practices**
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
301
|
+
- Use Redis backend in production environments
|
|
302
|
+
- Implement proper error handling for distributed operations
|
|
303
|
+
- Monitor backend connection health
|
|
304
|
+
- Use appropriate Redis configuration for your scale
|
|
305
|
+
- Consider using Redis Cluster for high availability
|
|
306
|
+
- Implement proper logging for distributed operations
|
|
294
307
|
|
|
295
308
|
8. **Scaling Considerations**
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
309
|
+
- Monitor Redis memory usage
|
|
310
|
+
- Implement proper cleanup of stale data
|
|
311
|
+
- Consider using Redis Cluster for larger deployments
|
|
312
|
+
- Implement proper error handling and retry mechanisms
|
|
313
|
+
- Monitor network latency between instances
|
|
314
|
+
- Implement proper logging and monitoring
|
|
302
315
|
|
|
303
316
|
## Configuration Options
|
|
304
317
|
|
|
@@ -306,14 +319,14 @@ The `PondSocketModule.forRoot()` method accepts the following options:
|
|
|
306
319
|
|
|
307
320
|
```typescript
|
|
308
321
|
interface PondSocketOptions {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
322
|
+
guards?: any[]; // Global guards
|
|
323
|
+
pipes?: any[]; // Global pipes
|
|
324
|
+
providers?: any[]; // Additional providers
|
|
325
|
+
imports?: any[]; // Additional imports
|
|
326
|
+
exports?: any[]; // Additional exports
|
|
327
|
+
isGlobal?: boolean; // Make the module global
|
|
328
|
+
isExclusiveSocketServer?: boolean; // Use exclusive socket server
|
|
329
|
+
backend?: IDistributedBackend; // Distributed backend
|
|
317
330
|
}
|
|
318
331
|
```
|
|
319
332
|
|
|
@@ -325,22 +338,22 @@ The client-side usage remains the same as the core PondSocket package:
|
|
|
325
338
|
import PondClient from "@eleven-am/pondsocket-client";
|
|
326
339
|
|
|
327
340
|
const socket = new PondClient('ws://your-server/api/socket', {
|
|
328
|
-
|
|
341
|
+
token: 'your-auth-token'
|
|
329
342
|
});
|
|
330
343
|
|
|
331
|
-
socket.connect();
|
|
344
|
+
socket.connect ();
|
|
332
345
|
|
|
333
346
|
const channel = socket.createChannel('/channel/123', {
|
|
334
|
-
|
|
347
|
+
username: 'user123'
|
|
335
348
|
});
|
|
336
349
|
|
|
337
|
-
channel.join();
|
|
350
|
+
channel.join ();
|
|
338
351
|
|
|
339
352
|
channel.onMessage((event, message) => {
|
|
340
|
-
|
|
353
|
+
console.log (`Received message: ${message.text}`);
|
|
341
354
|
});
|
|
342
355
|
|
|
343
|
-
channel.broadcast('message', {
|
|
356
|
+
channel.broadcast('message', {text: 'Hello, PondSocket!'});
|
|
344
357
|
```
|
|
345
358
|
|
|
346
359
|
## Contributing
|
|
@@ -353,27 +366,31 @@ This project is licensed under the GPL-3.0 License - see the LICENSE file for de
|
|
|
353
366
|
|
|
354
367
|
## Return Type Functionality
|
|
355
368
|
|
|
356
|
-
The NestJS integration provides a powerful return type system that allows you to declaratively specify actions to be
|
|
369
|
+
The NestJS integration provides a powerful return type system that allows you to declaratively specify actions to be
|
|
370
|
+
taken when handling WebSocket events. Instead of using the context object directly, you can return an object with
|
|
371
|
+
specific properties to trigger various actions.
|
|
357
372
|
|
|
358
373
|
### Return Type Interface
|
|
359
374
|
|
|
360
375
|
```typescript
|
|
361
|
-
type NestFuncType<Event extends string, Payload extends PondMessage, Presence extends PondPresence, Assigns extends PondAssigns = PondAssigns> =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
376
|
+
type NestFuncType<Event extends string, Payload extends PondMessage, Presence extends PondPresence, Assigns extends PondAssigns = PondAssigns> =
|
|
377
|
+
{
|
|
378
|
+
// Send an event to the user
|
|
379
|
+
event?: Event;
|
|
380
|
+
|
|
381
|
+
// Broadcast to all users in the channel
|
|
382
|
+
broadcast?: Event;
|
|
383
|
+
|
|
384
|
+
// Broadcast to all users except the sender
|
|
385
|
+
broadcastFrom?: Event;
|
|
386
|
+
|
|
387
|
+
// Update user assigns
|
|
388
|
+
assigns?: Partial<Assigns>;
|
|
389
|
+
|
|
390
|
+
// Update user presence
|
|
391
|
+
presence?: Presence;
|
|
392
|
+
}
|
|
393
|
+
& Payload;
|
|
377
394
|
```
|
|
378
395
|
|
|
379
396
|
### Usage Examples
|
|
@@ -381,103 +398,110 @@ type NestFuncType<Event extends string, Payload extends PondMessage, Presence ex
|
|
|
381
398
|
#### Channel Join with Multiple Actions
|
|
382
399
|
|
|
383
400
|
```typescript
|
|
401
|
+
|
|
384
402
|
@PondSocketChannel('/chat/:roomId')
|
|
385
403
|
export class ChatController {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
404
|
+
@PondSocketJoin()
|
|
405
|
+
async handleJoin (ctx: Context) {
|
|
406
|
+
const {username} = ctx.joinParams;
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
// Send welcome message to the joining user
|
|
410
|
+
event: 'welcome',
|
|
411
|
+
message: 'Welcome to the chat!',
|
|
412
|
+
|
|
413
|
+
// Broadcast join notification to all users
|
|
414
|
+
broadcast: 'user_joined',
|
|
415
|
+
username,
|
|
416
|
+
timestamp: Date.now(),
|
|
417
|
+
|
|
418
|
+
// Update user's assigns
|
|
419
|
+
assigns: {
|
|
420
|
+
username,
|
|
421
|
+
joinedAt: Date.now(),
|
|
422
|
+
role: 'member'
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
// Update user's presence
|
|
426
|
+
presence: {
|
|
427
|
+
username,
|
|
428
|
+
status: 'online',
|
|
429
|
+
lastSeen: Date.now()
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
415
433
|
}
|
|
416
434
|
```
|
|
417
435
|
|
|
418
436
|
#### Message Handling
|
|
419
437
|
|
|
420
438
|
```typescript
|
|
439
|
+
|
|
421
440
|
@PondSocketChannel('/chat/:roomId')
|
|
422
441
|
export class ChatController {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
442
|
+
@PondSocketEvent('message')
|
|
443
|
+
async handleMessage(ctx: Context) {
|
|
444
|
+
const {text} = ctx.event.payload;
|
|
445
|
+
const {username} = ctx.user.assigns;
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
// Broadcast the message to all users
|
|
449
|
+
broadcast: 'message',
|
|
450
|
+
text,
|
|
451
|
+
username,
|
|
452
|
+
timestamp: Date.now(),
|
|
453
|
+
|
|
454
|
+
// Update user's last message timestamp
|
|
455
|
+
assigns: {
|
|
456
|
+
lastMessageAt: Date.now()
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
441
460
|
}
|
|
442
461
|
```
|
|
443
462
|
|
|
444
463
|
#### Presence Updates
|
|
445
464
|
|
|
446
465
|
```typescript
|
|
447
|
-
|
|
466
|
+
|
|
467
|
+
@PondSocketChannel ('/chat/:roomId')
|
|
448
468
|
export class ChatController {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
469
|
+
@PondSocketEvent('status')
|
|
470
|
+
async handleStatus(ctx: Context) {
|
|
471
|
+
const {status} = ctx.event.payload;
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
// Update user's presence
|
|
475
|
+
presence: {
|
|
476
|
+
status,
|
|
477
|
+
lastSeen: Date.now()
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
// Notify others about the status change
|
|
481
|
+
broadcastFrom: 'status_change',
|
|
482
|
+
username: ctx.user.assigns.username,
|
|
483
|
+
status,
|
|
484
|
+
timestamp: Date.now()
|
|
485
|
+
};
|
|
486
|
+
}
|
|
467
487
|
}
|
|
468
488
|
```
|
|
469
489
|
|
|
470
490
|
### Benefits
|
|
471
491
|
|
|
472
|
-
1. **Declarative Code**: Actions are clearly specified in the return object, making the code more readable and
|
|
492
|
+
1. **Declarative Code**: Actions are clearly specified in the return object, making the code more readable and
|
|
493
|
+
maintainable.
|
|
473
494
|
|
|
474
495
|
2. **Type Safety**: The return type is fully typed, providing excellent TypeScript support and IDE autocompletion.
|
|
475
496
|
|
|
476
|
-
3. **Reduced Boilerplate**: No need to call multiple context methods; all actions are specified in a single return
|
|
497
|
+
3. **Reduced Boilerplate**: No need to call multiple context methods; all actions are specified in a single return
|
|
498
|
+
statement.
|
|
477
499
|
|
|
478
|
-
4. **Flexible Combinations**: You can combine multiple actions in a single return statement, making it easy to handle
|
|
500
|
+
4. **Flexible Combinations**: You can combine multiple actions in a single return statement, making it easy to handle
|
|
501
|
+
complex scenarios.
|
|
479
502
|
|
|
480
|
-
5. **Automatic Handling**: The framework automatically processes the returned object and executes the specified actions
|
|
503
|
+
5. **Automatic Handling**: The framework automatically processes the returned object and executes the specified actions
|
|
504
|
+
in the correct order.
|
|
481
505
|
|
|
482
506
|
### Best Practices
|
|
483
507
|
|
|
@@ -495,8 +519,11 @@ export class ChatController {
|
|
|
495
519
|
}
|
|
496
520
|
```
|
|
497
521
|
|
|
498
|
-
2. **Keep It Simple**: While you can combine multiple actions, try to keep the return object focused on a single
|
|
522
|
+
2. **Keep It Simple**: While you can combine multiple actions, try to keep the return object focused on a single
|
|
523
|
+
responsibility when possible.
|
|
499
524
|
|
|
500
|
-
3. **Use TypeScript**: Take advantage of TypeScript's type system to ensure your return objects are correctly
|
|
525
|
+
3. **Use TypeScript**: Take advantage of TypeScript's type system to ensure your return objects are correctly
|
|
526
|
+
structured.
|
|
501
527
|
|
|
502
|
-
4. **Handle Errors**: Remember that you can still use `ctx.decline()` for error cases where returning an object isn't
|
|
528
|
+
4. **Handle Errors**: Remember that you can still use `ctx.decline()` for error cases where returning an object isn't
|
|
529
|
+
appropriate.
|