servcraft 0.1.0 → 0.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.
Files changed (217) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/LICENSE +21 -0
  9. package/README.md +1102 -1
  10. package/dist/cli/index.cjs +2026 -2168
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +2026 -2168
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/index.cjs +595 -616
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +114 -52
  17. package/dist/index.d.ts +114 -52
  18. package/dist/index.js +595 -616
  19. package/dist/index.js.map +1 -1
  20. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  21. package/docs/DATABASE_MULTI_ORM.md +399 -0
  22. package/docs/PHASE1_BREAKDOWN.md +346 -0
  23. package/docs/PROGRESS.md +550 -0
  24. package/docs/modules/ANALYTICS.md +226 -0
  25. package/docs/modules/API-VERSIONING.md +252 -0
  26. package/docs/modules/AUDIT.md +192 -0
  27. package/docs/modules/AUTH.md +431 -0
  28. package/docs/modules/CACHE.md +346 -0
  29. package/docs/modules/EMAIL.md +254 -0
  30. package/docs/modules/FEATURE-FLAG.md +291 -0
  31. package/docs/modules/I18N.md +294 -0
  32. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  33. package/docs/modules/MFA.md +266 -0
  34. package/docs/modules/NOTIFICATION.md +311 -0
  35. package/docs/modules/OAUTH.md +237 -0
  36. package/docs/modules/PAYMENT.md +804 -0
  37. package/docs/modules/QUEUE.md +540 -0
  38. package/docs/modules/RATE-LIMIT.md +339 -0
  39. package/docs/modules/SEARCH.md +288 -0
  40. package/docs/modules/SECURITY.md +327 -0
  41. package/docs/modules/SESSION.md +382 -0
  42. package/docs/modules/SWAGGER.md +305 -0
  43. package/docs/modules/UPLOAD.md +296 -0
  44. package/docs/modules/USER.md +505 -0
  45. package/docs/modules/VALIDATION.md +294 -0
  46. package/docs/modules/WEBHOOK.md +270 -0
  47. package/docs/modules/WEBSOCKET.md +691 -0
  48. package/package.json +53 -38
  49. package/prisma/schema.prisma +395 -1
  50. package/src/cli/commands/add-module.ts +520 -87
  51. package/src/cli/commands/db.ts +3 -4
  52. package/src/cli/commands/docs.ts +256 -6
  53. package/src/cli/commands/generate.ts +12 -19
  54. package/src/cli/commands/init.ts +384 -214
  55. package/src/cli/index.ts +0 -4
  56. package/src/cli/templates/repository.ts +6 -1
  57. package/src/cli/templates/routes.ts +6 -21
  58. package/src/cli/utils/docs-generator.ts +6 -7
  59. package/src/cli/utils/env-manager.ts +717 -0
  60. package/src/cli/utils/field-parser.ts +16 -7
  61. package/src/cli/utils/interactive-prompt.ts +223 -0
  62. package/src/cli/utils/template-manager.ts +346 -0
  63. package/src/config/database.config.ts +183 -0
  64. package/src/config/env.ts +0 -10
  65. package/src/config/index.ts +0 -14
  66. package/src/core/server.ts +1 -1
  67. package/src/database/adapters/mongoose.adapter.ts +132 -0
  68. package/src/database/adapters/prisma.adapter.ts +118 -0
  69. package/src/database/connection.ts +190 -0
  70. package/src/database/interfaces/database.interface.ts +85 -0
  71. package/src/database/interfaces/index.ts +7 -0
  72. package/src/database/interfaces/repository.interface.ts +129 -0
  73. package/src/database/models/mongoose/index.ts +7 -0
  74. package/src/database/models/mongoose/payment.schema.ts +347 -0
  75. package/src/database/models/mongoose/user.schema.ts +154 -0
  76. package/src/database/prisma.ts +1 -4
  77. package/src/database/redis.ts +101 -0
  78. package/src/database/repositories/mongoose/index.ts +7 -0
  79. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  80. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  81. package/src/database/seed.ts +6 -1
  82. package/src/index.ts +9 -20
  83. package/src/middleware/security.ts +2 -6
  84. package/src/modules/analytics/analytics.routes.ts +80 -0
  85. package/src/modules/analytics/analytics.service.ts +364 -0
  86. package/src/modules/analytics/index.ts +18 -0
  87. package/src/modules/analytics/types.ts +180 -0
  88. package/src/modules/api-versioning/index.ts +15 -0
  89. package/src/modules/api-versioning/types.ts +86 -0
  90. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  91. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  92. package/src/modules/api-versioning/versioning.service.ts +189 -0
  93. package/src/modules/audit/audit.repository.ts +206 -0
  94. package/src/modules/audit/audit.service.ts +27 -59
  95. package/src/modules/auth/auth.controller.ts +2 -2
  96. package/src/modules/auth/auth.middleware.ts +3 -9
  97. package/src/modules/auth/auth.routes.ts +10 -107
  98. package/src/modules/auth/auth.service.ts +126 -23
  99. package/src/modules/auth/index.ts +3 -4
  100. package/src/modules/cache/cache.service.ts +367 -0
  101. package/src/modules/cache/index.ts +10 -0
  102. package/src/modules/cache/types.ts +44 -0
  103. package/src/modules/email/email.service.ts +3 -10
  104. package/src/modules/email/templates.ts +2 -8
  105. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  106. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  107. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  108. package/src/modules/feature-flag/index.ts +20 -0
  109. package/src/modules/feature-flag/types.ts +192 -0
  110. package/src/modules/i18n/i18n.middleware.ts +186 -0
  111. package/src/modules/i18n/i18n.routes.ts +191 -0
  112. package/src/modules/i18n/i18n.service.ts +456 -0
  113. package/src/modules/i18n/index.ts +18 -0
  114. package/src/modules/i18n/types.ts +118 -0
  115. package/src/modules/media-processing/index.ts +17 -0
  116. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  117. package/src/modules/media-processing/media-processing.service.ts +245 -0
  118. package/src/modules/media-processing/types.ts +156 -0
  119. package/src/modules/mfa/index.ts +20 -0
  120. package/src/modules/mfa/mfa.repository.ts +206 -0
  121. package/src/modules/mfa/mfa.routes.ts +595 -0
  122. package/src/modules/mfa/mfa.service.ts +572 -0
  123. package/src/modules/mfa/totp.ts +150 -0
  124. package/src/modules/mfa/types.ts +57 -0
  125. package/src/modules/notification/index.ts +20 -0
  126. package/src/modules/notification/notification.repository.ts +356 -0
  127. package/src/modules/notification/notification.service.ts +483 -0
  128. package/src/modules/notification/types.ts +119 -0
  129. package/src/modules/oauth/index.ts +20 -0
  130. package/src/modules/oauth/oauth.repository.ts +219 -0
  131. package/src/modules/oauth/oauth.routes.ts +446 -0
  132. package/src/modules/oauth/oauth.service.ts +293 -0
  133. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  134. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  135. package/src/modules/oauth/providers/github.provider.ts +248 -0
  136. package/src/modules/oauth/providers/google.provider.ts +189 -0
  137. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  138. package/src/modules/oauth/types.ts +94 -0
  139. package/src/modules/payment/index.ts +19 -0
  140. package/src/modules/payment/payment.repository.ts +733 -0
  141. package/src/modules/payment/payment.routes.ts +390 -0
  142. package/src/modules/payment/payment.service.ts +354 -0
  143. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  144. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  145. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  146. package/src/modules/payment/types.ts +140 -0
  147. package/src/modules/queue/cron.ts +438 -0
  148. package/src/modules/queue/index.ts +87 -0
  149. package/src/modules/queue/queue.routes.ts +600 -0
  150. package/src/modules/queue/queue.service.ts +842 -0
  151. package/src/modules/queue/types.ts +222 -0
  152. package/src/modules/queue/workers.ts +366 -0
  153. package/src/modules/rate-limit/index.ts +59 -0
  154. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  155. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  156. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  157. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  158. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  159. package/src/modules/rate-limit/types.ts +153 -0
  160. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  161. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  162. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  163. package/src/modules/search/index.ts +21 -0
  164. package/src/modules/search/search.service.ts +234 -0
  165. package/src/modules/search/types.ts +214 -0
  166. package/src/modules/security/index.ts +40 -0
  167. package/src/modules/security/sanitize.ts +223 -0
  168. package/src/modules/security/security-audit.service.ts +388 -0
  169. package/src/modules/security/security.middleware.ts +398 -0
  170. package/src/modules/session/index.ts +3 -0
  171. package/src/modules/session/session.repository.ts +159 -0
  172. package/src/modules/session/session.service.ts +340 -0
  173. package/src/modules/session/types.ts +38 -0
  174. package/src/modules/swagger/index.ts +7 -1
  175. package/src/modules/swagger/schema-builder.ts +16 -4
  176. package/src/modules/swagger/swagger.service.ts +9 -10
  177. package/src/modules/swagger/types.ts +0 -2
  178. package/src/modules/upload/index.ts +14 -0
  179. package/src/modules/upload/types.ts +83 -0
  180. package/src/modules/upload/upload.repository.ts +199 -0
  181. package/src/modules/upload/upload.routes.ts +311 -0
  182. package/src/modules/upload/upload.service.ts +448 -0
  183. package/src/modules/user/index.ts +3 -3
  184. package/src/modules/user/user.controller.ts +15 -9
  185. package/src/modules/user/user.repository.ts +237 -113
  186. package/src/modules/user/user.routes.ts +39 -164
  187. package/src/modules/user/user.service.ts +4 -3
  188. package/src/modules/validation/validator.ts +12 -17
  189. package/src/modules/webhook/index.ts +91 -0
  190. package/src/modules/webhook/retry.ts +196 -0
  191. package/src/modules/webhook/signature.ts +135 -0
  192. package/src/modules/webhook/types.ts +181 -0
  193. package/src/modules/webhook/webhook.repository.ts +358 -0
  194. package/src/modules/webhook/webhook.routes.ts +442 -0
  195. package/src/modules/webhook/webhook.service.ts +457 -0
  196. package/src/modules/websocket/features.ts +504 -0
  197. package/src/modules/websocket/index.ts +106 -0
  198. package/src/modules/websocket/middlewares.ts +298 -0
  199. package/src/modules/websocket/types.ts +181 -0
  200. package/src/modules/websocket/websocket.service.ts +692 -0
  201. package/src/utils/errors.ts +7 -0
  202. package/src/utils/pagination.ts +4 -1
  203. package/tests/helpers/db-check.ts +79 -0
  204. package/tests/integration/auth-redis.test.ts +94 -0
  205. package/tests/integration/cache-redis.test.ts +387 -0
  206. package/tests/integration/mongoose-repositories.test.ts +410 -0
  207. package/tests/integration/payment-prisma.test.ts +637 -0
  208. package/tests/integration/queue-bullmq.test.ts +417 -0
  209. package/tests/integration/user-prisma.test.ts +441 -0
  210. package/tests/integration/websocket-socketio.test.ts +552 -0
  211. package/tests/setup.ts +11 -9
  212. package/vitest.config.ts +3 -8
  213. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  216. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  217. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -0,0 +1,504 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { logger } from '../../core/logger.js';
3
+ import type {
4
+ ChatMessage,
5
+ PresenceStatus,
6
+ Notification,
7
+ TypingIndicator,
8
+ LiveEvent,
9
+ } from './types.js';
10
+ import type { WebSocketService } from './websocket.service.js';
11
+
12
+ // Storage
13
+ const presenceStatus = new Map<string, PresenceStatus>();
14
+ const notifications = new Map<string, Notification[]>();
15
+ const typingIndicators = new Map<string, Map<string, TypingIndicator>>();
16
+
17
+ /**
18
+ * Chat Feature
19
+ * Real-time chat functionality
20
+ */
21
+ export class ChatFeature {
22
+ constructor(private ws: WebSocketService) {}
23
+
24
+ /**
25
+ * Send chat message
26
+ */
27
+ async sendMessage(
28
+ roomId: string,
29
+ userId: string,
30
+ content: string,
31
+ options?: {
32
+ replyTo?: string;
33
+ mentions?: string[];
34
+ attachments?: ChatMessage['attachments'];
35
+ }
36
+ ): Promise<ChatMessage> {
37
+ const message = (await this.ws.sendMessage(
38
+ roomId,
39
+ userId,
40
+ content,
41
+ 'text',
42
+ options
43
+ )) as ChatMessage;
44
+
45
+ message.replyTo = options?.replyTo;
46
+ message.mentions = options?.mentions;
47
+ message.attachments = options?.attachments;
48
+
49
+ // Broadcast to room
50
+ await this.ws.broadcastToRoom(roomId, 'chat:message', message);
51
+
52
+ // Notify mentioned users
53
+ if (options?.mentions) {
54
+ await this.notifyMentions(options.mentions, message);
55
+ }
56
+
57
+ logger.debug({ messageId: message.id, roomId, userId }, 'Chat message sent');
58
+
59
+ return message;
60
+ }
61
+
62
+ /**
63
+ * Edit message
64
+ */
65
+ async editMessage(
66
+ messageId: string,
67
+ roomId: string,
68
+ userId: string,
69
+ newContent: string
70
+ ): Promise<void> {
71
+ const messages = this.ws.getRoomMessages(roomId, 1000);
72
+ const message = messages.find((m) => m.id === messageId);
73
+
74
+ if (!message) {
75
+ throw new Error('Message not found');
76
+ }
77
+
78
+ if (message.userId !== userId) {
79
+ throw new Error('Unauthorized');
80
+ }
81
+
82
+ message.content = newContent;
83
+ message.edited = true;
84
+ message.editedAt = new Date();
85
+
86
+ await this.ws.broadcastToRoom(roomId, 'chat:message:edited', {
87
+ messageId,
88
+ content: newContent,
89
+ editedAt: message.editedAt,
90
+ });
91
+
92
+ logger.debug({ messageId, roomId, userId }, 'Message edited');
93
+ }
94
+
95
+ /**
96
+ * Delete message
97
+ */
98
+ async deleteMessage(messageId: string, roomId: string, userId: string): Promise<void> {
99
+ const messages = this.ws.getRoomMessages(roomId, 1000);
100
+ const messageIndex = messages.findIndex((m) => m.id === messageId);
101
+
102
+ if (messageIndex === -1) {
103
+ throw new Error('Message not found');
104
+ }
105
+
106
+ const message = messages[messageIndex];
107
+
108
+ if (!message || message.userId !== userId) {
109
+ throw new Error('Unauthorized');
110
+ }
111
+
112
+ messages.splice(messageIndex, 1);
113
+
114
+ await this.ws.broadcastToRoom(roomId, 'chat:message:deleted', {
115
+ messageId,
116
+ roomId,
117
+ });
118
+
119
+ logger.debug({ messageId, roomId, userId }, 'Message deleted');
120
+ }
121
+
122
+ /**
123
+ * Start typing indicator
124
+ */
125
+ async startTyping(roomId: string, userId: string, username?: string): Promise<void> {
126
+ if (!typingIndicators.has(roomId)) {
127
+ typingIndicators.set(roomId, new Map());
128
+ }
129
+
130
+ const roomTyping = typingIndicators.get(roomId)!;
131
+ roomTyping.set(userId, {
132
+ userId,
133
+ username,
134
+ roomId,
135
+ timestamp: new Date(),
136
+ });
137
+
138
+ await this.ws.broadcastToRoom(
139
+ roomId,
140
+ 'chat:typing:start',
141
+ { userId, username },
142
+ { except: this.ws.getUserSockets(userId) }
143
+ );
144
+
145
+ logger.debug({ roomId, userId }, 'User started typing');
146
+
147
+ // Auto-stop after 5 seconds
148
+ setTimeout(() => {
149
+ this.stopTyping(roomId, userId);
150
+ }, 5000);
151
+ }
152
+
153
+ /**
154
+ * Stop typing indicator
155
+ */
156
+ async stopTyping(roomId: string, userId: string): Promise<void> {
157
+ const roomTyping = typingIndicators.get(roomId);
158
+
159
+ if (roomTyping) {
160
+ roomTyping.delete(userId);
161
+
162
+ await this.ws.broadcastToRoom(
163
+ roomId,
164
+ 'chat:typing:stop',
165
+ { userId },
166
+ { except: this.ws.getUserSockets(userId) }
167
+ );
168
+
169
+ logger.debug({ roomId, userId }, 'User stopped typing');
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Get typing users in room
175
+ */
176
+ getTypingUsers(roomId: string): TypingIndicator[] {
177
+ const roomTyping = typingIndicators.get(roomId);
178
+
179
+ if (!roomTyping) {
180
+ return [];
181
+ }
182
+
183
+ return Array.from(roomTyping.values());
184
+ }
185
+
186
+ /**
187
+ * Notify mentioned users
188
+ */
189
+ private async notifyMentions(userIds: string[], message: ChatMessage): Promise<void> {
190
+ for (const userId of userIds) {
191
+ if (this.ws.isUserOnline(userId)) {
192
+ await this.ws.broadcastToUsers([userId], 'chat:mention', {
193
+ messageId: message.id,
194
+ roomId: message.roomId,
195
+ from: message.username,
196
+ content: message.content,
197
+ });
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Presence Feature
205
+ * User online/offline status tracking
206
+ */
207
+ export class PresenceFeature {
208
+ constructor(private ws: WebSocketService) {
209
+ // Listen to connection events
210
+ ws.on('connection', (user) => {
211
+ this.updateStatus(user.id, 'online');
212
+ });
213
+
214
+ ws.on('disconnect', (user) => {
215
+ this.updateStatus(user.id, 'offline');
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Update user status
221
+ */
222
+ async updateStatus(
223
+ userId: string,
224
+ status: PresenceStatus['status'],
225
+ metadata?: Record<string, unknown>
226
+ ): Promise<void> {
227
+ const presence: PresenceStatus = {
228
+ userId,
229
+ status,
230
+ lastSeen: new Date(),
231
+ metadata,
232
+ };
233
+
234
+ presenceStatus.set(userId, presence);
235
+
236
+ // Broadcast status change
237
+ await this.ws.broadcastToAll('presence:status', presence);
238
+
239
+ logger.debug({ userId, status }, 'Presence status updated');
240
+ }
241
+
242
+ /**
243
+ * Get user status
244
+ */
245
+ getStatus(userId: string): PresenceStatus | null {
246
+ return presenceStatus.get(userId) || null;
247
+ }
248
+
249
+ /**
250
+ * Get multiple user statuses
251
+ */
252
+ getStatuses(userIds: string[]): PresenceStatus[] {
253
+ return userIds
254
+ .map((userId) => presenceStatus.get(userId))
255
+ .filter((status): status is PresenceStatus => status !== undefined);
256
+ }
257
+
258
+ /**
259
+ * Get all online users
260
+ */
261
+ getOnlineUsers(): PresenceStatus[] {
262
+ return Array.from(presenceStatus.values()).filter((status) => status.status === 'online');
263
+ }
264
+
265
+ /**
266
+ * Subscribe to user status changes
267
+ */
268
+ async subscribeToUsers(socketId: string, userIds: string[]): Promise<void> {
269
+ // Send current statuses
270
+ const statuses = this.getStatuses(userIds);
271
+
272
+ await this.ws.emitToSocket(socketId, 'presence:statuses', statuses);
273
+
274
+ logger.debug({ socketId, userCount: userIds.length }, 'Subscribed to user statuses');
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Notification Feature
280
+ * Real-time notifications
281
+ */
282
+ export class NotificationFeature {
283
+ constructor(private ws: WebSocketService) {}
284
+
285
+ /**
286
+ * Send notification to user
287
+ */
288
+ async send(
289
+ userId: string,
290
+ type: string,
291
+ title: string,
292
+ message: string,
293
+ data?: Record<string, unknown>
294
+ ): Promise<Notification> {
295
+ const notification: Notification = {
296
+ id: randomUUID(),
297
+ userId,
298
+ type,
299
+ title,
300
+ message,
301
+ data,
302
+ read: false,
303
+ createdAt: new Date(),
304
+ };
305
+
306
+ // Store notification
307
+ if (!notifications.has(userId)) {
308
+ notifications.set(userId, []);
309
+ }
310
+
311
+ notifications.get(userId)!.push(notification);
312
+
313
+ // Send to user if online
314
+ if (this.ws.isUserOnline(userId)) {
315
+ await this.ws.broadcastToUsers([userId], 'notification:new', notification);
316
+ }
317
+
318
+ logger.debug({ notificationId: notification.id, userId, type }, 'Notification sent');
319
+
320
+ return notification;
321
+ }
322
+
323
+ /**
324
+ * Send bulk notifications
325
+ */
326
+ async sendBulk(
327
+ userIds: string[],
328
+ type: string,
329
+ title: string,
330
+ message: string,
331
+ data?: Record<string, unknown>
332
+ ): Promise<void> {
333
+ const promises = userIds.map((userId) => this.send(userId, type, title, message, data));
334
+
335
+ await Promise.all(promises);
336
+
337
+ logger.info({ userCount: userIds.length, type }, 'Bulk notifications sent');
338
+ }
339
+
340
+ /**
341
+ * Mark notification as read
342
+ */
343
+ async markAsRead(userId: string, notificationId: string): Promise<void> {
344
+ const userNotifications = notifications.get(userId);
345
+
346
+ if (!userNotifications) {
347
+ return;
348
+ }
349
+
350
+ const notification = userNotifications.find((n) => n.id === notificationId);
351
+
352
+ if (notification) {
353
+ notification.read = true;
354
+
355
+ await this.ws.broadcastToUsers([userId], 'notification:read', {
356
+ notificationId,
357
+ });
358
+
359
+ logger.debug({ notificationId, userId }, 'Notification marked as read');
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Mark all as read
365
+ */
366
+ async markAllAsRead(userId: string): Promise<void> {
367
+ const userNotifications = notifications.get(userId);
368
+
369
+ if (!userNotifications) {
370
+ return;
371
+ }
372
+
373
+ userNotifications.forEach((notification) => {
374
+ notification.read = true;
375
+ });
376
+
377
+ await this.ws.broadcastToUsers([userId], 'notification:all_read', {});
378
+
379
+ logger.debug({ userId, count: userNotifications.length }, 'All notifications marked as read');
380
+ }
381
+
382
+ /**
383
+ * Get user notifications
384
+ */
385
+ getNotifications(userId: string, unreadOnly = false): Notification[] {
386
+ const userNotifications = notifications.get(userId) || [];
387
+
388
+ if (unreadOnly) {
389
+ return userNotifications.filter((n) => !n.read);
390
+ }
391
+
392
+ return userNotifications;
393
+ }
394
+
395
+ /**
396
+ * Get unread count
397
+ */
398
+ getUnreadCount(userId: string): number {
399
+ const userNotifications = notifications.get(userId) || [];
400
+
401
+ return userNotifications.filter((n) => !n.read).length;
402
+ }
403
+
404
+ /**
405
+ * Delete notification
406
+ */
407
+ async deleteNotification(userId: string, notificationId: string): Promise<void> {
408
+ const userNotifications = notifications.get(userId);
409
+
410
+ if (!userNotifications) {
411
+ return;
412
+ }
413
+
414
+ const index = userNotifications.findIndex((n) => n.id === notificationId);
415
+
416
+ if (index !== -1) {
417
+ userNotifications.splice(index, 1);
418
+
419
+ await this.ws.broadcastToUsers([userId], 'notification:deleted', {
420
+ notificationId,
421
+ });
422
+
423
+ logger.debug({ notificationId, userId }, 'Notification deleted');
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Clear all notifications
429
+ */
430
+ async clearAll(userId: string): Promise<void> {
431
+ notifications.set(userId, []);
432
+
433
+ await this.ws.broadcastToUsers([userId], 'notification:cleared', {});
434
+
435
+ logger.debug({ userId }, 'All notifications cleared');
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Live Events Feature
441
+ * Broadcast live events (analytics, updates, etc.)
442
+ */
443
+ export class LiveEventsFeature {
444
+ constructor(private ws: WebSocketService) {}
445
+
446
+ /**
447
+ * Broadcast live event
448
+ */
449
+ async broadcast(
450
+ type: string,
451
+ data: Record<string, unknown>,
452
+ source?: string,
453
+ targetUsers?: string[]
454
+ ): Promise<void> {
455
+ const event: LiveEvent = {
456
+ id: randomUUID(),
457
+ type,
458
+ data,
459
+ timestamp: new Date(),
460
+ source,
461
+ };
462
+
463
+ if (targetUsers && targetUsers.length > 0) {
464
+ await this.ws.broadcastToUsers(targetUsers, 'live:event', event);
465
+ } else {
466
+ await this.ws.broadcastToAll('live:event', event);
467
+ }
468
+
469
+ logger.debug(
470
+ { eventId: event.id, type, targetCount: targetUsers?.length },
471
+ 'Live event broadcasted'
472
+ );
473
+ }
474
+
475
+ /**
476
+ * Broadcast analytics event
477
+ */
478
+ async broadcastAnalytics(
479
+ metric: string,
480
+ value: number,
481
+ metadata?: Record<string, unknown>
482
+ ): Promise<void> {
483
+ await this.broadcast('analytics', {
484
+ metric,
485
+ value,
486
+ ...metadata,
487
+ });
488
+ }
489
+
490
+ /**
491
+ * Broadcast system update
492
+ */
493
+ async broadcastSystemUpdate(
494
+ message: string,
495
+ severity: 'info' | 'warning' | 'error',
496
+ metadata?: Record<string, unknown>
497
+ ): Promise<void> {
498
+ await this.broadcast('system_update', {
499
+ message,
500
+ severity,
501
+ ...metadata,
502
+ });
503
+ }
504
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * WebSocket/Real-time Module
3
+ *
4
+ * Provides real-time communication with Socket.io:
5
+ * - Real-time chat with typing indicators
6
+ * - User presence tracking (online/offline)
7
+ * - Live notifications
8
+ * - Room management
9
+ * - Live events broadcasting
10
+ * - Authentication & authorization middlewares
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import {
15
+ * WebSocketService,
16
+ * ChatFeature,
17
+ * PresenceFeature,
18
+ * NotificationFeature,
19
+ * authMiddleware
20
+ * } from './modules/websocket';
21
+ *
22
+ * // Create WebSocket service
23
+ * const wsService = new WebSocketService({
24
+ * cors: { origin: 'http://localhost:3000' },
25
+ * redis: { host: 'localhost', port: 6379 }
26
+ * });
27
+ *
28
+ * // Initialize with HTTP server
29
+ * wsService.initialize(httpServer);
30
+ *
31
+ * // Create features
32
+ * const chat = new ChatFeature(wsService);
33
+ * const presence = new PresenceFeature(wsService);
34
+ * const notifications = new NotificationFeature(wsService);
35
+ *
36
+ * // Send chat message
37
+ * await chat.sendMessage('room-123', 'user-456', 'Hello!', {
38
+ * mentions: ['user-789']
39
+ * });
40
+ *
41
+ * // Send notification
42
+ * await notifications.send(
43
+ * 'user-123',
44
+ * 'message',
45
+ * 'New Message',
46
+ * 'You have a new message from John'
47
+ * );
48
+ *
49
+ * // Broadcast live event
50
+ * await wsService.broadcastToAll('analytics:update', {
51
+ * metric: 'active_users',
52
+ * value: 1250
53
+ * });
54
+ * ```
55
+ *
56
+ * ## Client-side Integration
57
+ *
58
+ * ```typescript
59
+ * import { io } from 'socket.io-client';
60
+ *
61
+ * const socket = io('http://localhost:3000', {
62
+ * auth: { token: 'your-jwt-token' },
63
+ * query: { username: 'john', namespace: 'chat' }
64
+ * });
65
+ *
66
+ * // Listen for events
67
+ * socket.on('chat:message', (message) => {
68
+ * console.log('New message:', message);
69
+ * });
70
+ *
71
+ * socket.on('presence:status', (status) => {
72
+ * console.log('User status changed:', status);
73
+ * });
74
+ *
75
+ * socket.on('notification:new', (notification) => {
76
+ * console.log('New notification:', notification);
77
+ * });
78
+ * ```
79
+ */
80
+
81
+ // Types
82
+ export * from './types.js';
83
+
84
+ // Service
85
+ export { WebSocketService } from './websocket.service.js';
86
+
87
+ // Features
88
+ export {
89
+ ChatFeature,
90
+ PresenceFeature,
91
+ NotificationFeature,
92
+ LiveEventsFeature,
93
+ } from './features.js';
94
+
95
+ // Middlewares
96
+ export {
97
+ authMiddleware,
98
+ rateLimitMiddleware,
99
+ corsMiddleware,
100
+ loggingMiddleware,
101
+ validationMiddleware,
102
+ roleMiddleware,
103
+ namespaceMiddleware,
104
+ throttleMiddleware,
105
+ errorMiddleware,
106
+ } from './middlewares.js';