@unispechq/unispec-schema 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +170 -162
  2. package/examples/README.md +128 -0
  3. package/examples/invalid/config/additional-properties.json +26 -0
  4. package/examples/invalid/config/missing-service-name.json +22 -0
  5. package/examples/invalid/config/missing-version.json +6 -0
  6. package/examples/invalid/graphql-additional-properties.json +22 -0
  7. package/examples/invalid/graphql-missing-arg-type.json +26 -0
  8. package/examples/invalid/graphql-missing-name.json +19 -0
  9. package/examples/invalid/graphql-missing-schema.json +19 -0
  10. package/examples/invalid/mixed-invalid-protocol.json +26 -0
  11. package/examples/invalid/mixed-missing-graphql-schema.json +33 -0
  12. package/examples/invalid/mixed-multiple-errors.json +41 -0
  13. package/examples/invalid/rest-additional-properties.json +25 -0
  14. package/examples/invalid/rest-invalid-identifiers.json +29 -0
  15. package/examples/invalid/rest-invalid-method.json +23 -0
  16. package/examples/invalid/rest-missing-required.json +21 -0
  17. package/examples/invalid/websocket-additional-properties.json +27 -0
  18. package/examples/invalid/websocket-invalid-direction.json +27 -0
  19. package/examples/invalid/websocket-missing-channel-name.json +25 -0
  20. package/examples/invalid/websocket-missing-message-name.json +25 -0
  21. package/examples/valid/config/complete.json +61 -0
  22. package/examples/valid/config/minimal.json +8 -0
  23. package/examples/valid/graphql-complete.json +348 -0
  24. package/examples/valid/graphql-simple.json +34 -0
  25. package/examples/valid/mixed-complete.json +799 -0
  26. package/examples/valid/mixed-simple.json +56 -0
  27. package/examples/valid/rest-complete.json +539 -0
  28. package/examples/valid/rest-simple.json +279 -0
  29. package/examples/valid/websocket-complete.json +471 -0
  30. package/examples/valid/websocket-simple.json +116 -0
  31. package/index.cjs +7 -7
  32. package/index.d.ts +9 -9
  33. package/index.mjs +9 -9
  34. package/package.json +15 -6
  35. package/schema/index.json +19 -19
  36. package/schema/types/common.schema.json +195 -195
  37. package/schema/types/graphql.schema.json +172 -172
  38. package/schema/types/rest.schema.json +221 -226
  39. package/schema/types/schemas.schema.json +84 -84
  40. package/schema/types/service.schema.json +158 -158
  41. package/schema/types/websocket.schema.json +185 -190
  42. package/schema/unispec-config.schema.json +509 -509
  43. package/schema/unispec-tests.schema.json +368 -378
  44. package/schema/unispec.schema.json +18 -23
@@ -0,0 +1,799 @@
1
+ {
2
+ "unispecVersion": "0.1.0",
3
+ "service": {
4
+ "name": "social-media-platform",
5
+ "description": "Complete social media platform with REST, GraphQL, and WebSocket protocols",
6
+ "version": "2.0.0",
7
+ "baseUrl": "https://api.social.example.com",
8
+ "contact": {
9
+ "name": "Platform Support",
10
+ "email": "support@social.example.com",
11
+ "url": "https://social.example.com/support"
12
+ },
13
+ "license": {
14
+ "name": "MIT",
15
+ "url": "https://opensource.org/licenses/MIT"
16
+ },
17
+ "tags": ["social", "media", "platform", "multi-protocol"],
18
+ "servers": [
19
+ {
20
+ "url": "https://api.social.example.com",
21
+ "description": "Production API server"
22
+ },
23
+ {
24
+ "url": "https://staging-api.social.example.com",
25
+ "description": "Staging API server"
26
+ }
27
+ ],
28
+ "securitySchemes": {
29
+ "bearerAuth": {
30
+ "type": "http",
31
+ "scheme": "bearer",
32
+ "bearerFormat": "JWT",
33
+ "description": "JWT authentication for all protocols"
34
+ },
35
+ "apiKeyAuth": {
36
+ "type": "apiKey",
37
+ "name": "X-API-Key",
38
+ "in": "header",
39
+ "description": "API key for server-to-server communication"
40
+ }
41
+ },
42
+ "rateLimit": {
43
+ "requestsPerMinute": 1000,
44
+ "requestsPerHour": 50000,
45
+ "perUser": true,
46
+ "perApiKey": true
47
+ },
48
+ "compliance": ["GDPR", "SOC2"],
49
+ "dataClassification": "confidential",
50
+ "protocols": {
51
+ "rest": {
52
+ "headers": [
53
+ {
54
+ "name": "Content-Type",
55
+ "description": "Request content type",
56
+ "required": true
57
+ },
58
+ {
59
+ "name": "Accept",
60
+ "description": "Response content type",
61
+ "required": true
62
+ }
63
+ ],
64
+ "routes": [
65
+ {
66
+ "name": "uploadMedia",
67
+ "summary": "Upload media file",
68
+ "tags": ["media"],
69
+ "description": "Upload images, videos, and other media files",
70
+ "path": "/media/upload",
71
+ "method": "POST",
72
+ "requestBody": {
73
+ "description": "Media file and metadata",
74
+ "required": true,
75
+ "content": {
76
+ "multipart/form-data": {
77
+ "schemaRef": "MediaUploadRequest"
78
+ }
79
+ }
80
+ },
81
+ "responses": {
82
+ "201": {
83
+ "description": "Media uploaded successfully",
84
+ "content": {
85
+ "application/json": {
86
+ "schemaRef": "MediaResponse"
87
+ }
88
+ }
89
+ },
90
+ "400": {
91
+ "description": "Invalid file format or size",
92
+ "content": {
93
+ "application/json": {
94
+ "schemaRef": "ErrorResponse"
95
+ }
96
+ }
97
+ },
98
+ "413": {
99
+ "description": "File too large",
100
+ "content": {
101
+ "application/json": {
102
+ "schemaRef": "ErrorResponse"
103
+ }
104
+ }
105
+ }
106
+ },
107
+ "security": [
108
+ {
109
+ "bearerAuth": []
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ "name": "getMedia",
115
+ "summary": "Get media by ID",
116
+ "tags": ["media"],
117
+ "description": "Retrieve media file and metadata",
118
+ "path": "/media/{id}",
119
+ "method": "GET",
120
+ "pathParams": [
121
+ {
122
+ "name": "id",
123
+ "description": "Media ID",
124
+ "required": true,
125
+ "schemaRef": "MediaId"
126
+ }
127
+ ],
128
+ "responses": {
129
+ "200": {
130
+ "description": "Media file and metadata",
131
+ "content": {
132
+ "application/json": {
133
+ "schemaRef": "MediaResponse"
134
+ }
135
+ }
136
+ },
137
+ "404": {
138
+ "description": "Media not found",
139
+ "content": {
140
+ "application/json": {
141
+ "schemaRef": "ErrorResponse"
142
+ }
143
+ }
144
+ }
145
+ },
146
+ "security": [
147
+ {
148
+ "bearerAuth": []
149
+ },
150
+ {
151
+ "apiKeyAuth": []
152
+ }
153
+ ]
154
+ },
155
+ {
156
+ "name": "deleteMedia",
157
+ "summary": "Delete media",
158
+ "tags": ["media"],
159
+ "description": "Delete media file",
160
+ "path": "/media/{id}",
161
+ "method": "DELETE",
162
+ "pathParams": [
163
+ {
164
+ "name": "id",
165
+ "description": "Media ID",
166
+ "required": true,
167
+ "schemaRef": "MediaId"
168
+ }
169
+ ],
170
+ "responses": {
171
+ "204": {
172
+ "description": "Media deleted successfully"
173
+ },
174
+ "404": {
175
+ "description": "Media not found",
176
+ "content": {
177
+ "application/json": {
178
+ "schemaRef": "ErrorResponse"
179
+ }
180
+ }
181
+ }
182
+ },
183
+ "security": [
184
+ {
185
+ "bearerAuth": []
186
+ }
187
+ ]
188
+ }
189
+ ]
190
+ },
191
+ "graphql": {
192
+ "url": "/graphql",
193
+ "schema": "type User {\n id: ID!\n username: String!\n email: String!\n profile: UserProfile!\n posts: [Post!]!\n followers: [User!]!\n following: [User!]!\n createdAt: String!\n updatedAt: String!\n}\n\ntype UserProfile {\n displayName: String!\n bio: String\n avatar: String\n location: String\n website: String\n birthDate: String\n}\n\ntype Post {\n id: ID!\n author: User!\n content: String!\n media: [Media!]!\n likes: [User!]!\n comments: [Comment!]!\n tags: [String!]!\n visibility: PostVisibility!\n createdAt: String!\n updatedAt: String!\n}\n\ntype Media {\n id: ID!\n url: String!\n type: MediaType!\n width: Int\n height: Int\n size: Int!\n alt: String\n}\n\ntype Comment {\n id: ID!\n author: User!\n post: Post!\n content: String!\n likes: [User!]!\n createdAt: String!\n updatedAt: String!\n}\n\nenum PostVisibility {\n PUBLIC\n FRIENDS\n PRIVATE\n}\n\nenum MediaType {\n IMAGE\n VIDEO\n AUDIO\n DOCUMENT\n}\n\ntype AuthPayload {\n token: String!\n user: User!\n}\n\ntype Query {\n users(page: Int = 1, limit: Int = 20, search: String): UserConnection!\n user(id: ID!, username: String): User\n me: User\n posts(page: Int = 1, limit: Int = 20, authorId: ID, tags: [String!]): PostConnection!\n post(id: ID!): Post\n feed(page: Int = 1, limit: Int = 20): PostConnection!\n trending: [Post!]!\n}\n\ntype Mutation {\n createUser(input: CreateUserInput!): User!\n updateUser(input: UpdateUserInput!): User!\n createPost(input: CreatePostInput!): Post!\n updatePost(id: ID!, input: UpdatePostInput!): Post!\n deletePost(id: ID!): Boolean!\n likePost(id: ID!): Post!\n unlikePost(id: ID!): Post!\n createComment(postId: ID!, content: String!): Comment!\n deleteComment(id: ID!): Boolean!\n followUser(userId: ID!): User!\n unfollowUser(userId: ID!): User!\n login(email: String!, password: String!): AuthPayload!\n logout: Boolean!\n}\n\ntype Subscription {\n postCreated(authorId: ID): Post!\n postUpdated(postId: ID): Post!\n postLiked(postId: ID): Post!\n commentAdded(postId: ID): Comment!\n userFollowed(userId: ID): User!\n notificationReceived: Notification!\n}\n\ntype UserConnection {\n users: [User!]!\n pageInfo: PageInfo!\n totalCount: Int!\n}\n\ntype PostConnection {\n posts: [Post!]!\n pageInfo: PageInfo!\n totalCount: Int!\n}\n\ntype PageInfo {\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n startCursor: String\n endCursor: String\n}\n\ntype Notification {\n id: ID!\n type: NotificationType!\n user: User!\n message: String!\n data: JSON\n read: Boolean!\n createdAt: String!\n}\n\nenum NotificationType {\n LIKE\n COMMENT\n FOLLOW\n MENTION\n POST\n}\n\nscalar JSON\n\ninput CreateUserInput {\n username: String!\n email: String!\n password: String!\n profile: UserProfileInput!\n}\n\ninput UpdateUserInput {\n profile: UserProfileInput\n}\n\ninput UserProfileInput {\n displayName: String\n bio: String\n avatar: String\n location: String\n website: String\n birthDate: String\n}\n\ninput CreatePostInput {\n content: String!\n mediaIds: [ID!]\n tags: [String!]\n visibility: PostVisibility = PUBLIC\n}\n\ninput UpdatePostInput {\n content: String\n tags: [String!]\n visibility: PostVisibility\n}\n",
194
+ "headers": [
195
+ {
196
+ "name": "Authorization",
197
+ "description": "Bearer token for authentication",
198
+ "required": true
199
+ }
200
+ ],
201
+ "queries": [
202
+ {
203
+ "name": "users",
204
+ "description": "Get paginated list of users",
205
+ "args": [
206
+ {
207
+ "name": "page",
208
+ "type": "Int",
209
+ "description": "Page number",
210
+ "defaultValue": 1
211
+ },
212
+ {
213
+ "name": "limit",
214
+ "type": "Int",
215
+ "description": "Items per page",
216
+ "defaultValue": 20
217
+ },
218
+ {
219
+ "name": "search",
220
+ "type": "String",
221
+ "description": "Search term"
222
+ }
223
+ ],
224
+ "returnType": "UserConnection!",
225
+ "security": [
226
+ {
227
+ "bearerAuth": []
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "name": "user",
233
+ "description": "Get user by ID or username",
234
+ "args": [
235
+ {
236
+ "name": "id",
237
+ "type": "ID",
238
+ "description": "User ID"
239
+ },
240
+ {
241
+ "name": "username",
242
+ "type": "String",
243
+ "description": "Username"
244
+ }
245
+ ],
246
+ "returnType": "User",
247
+ "security": [
248
+ {
249
+ "bearerAuth": []
250
+ }
251
+ ]
252
+ },
253
+ {
254
+ "name": "posts",
255
+ "description": "Get paginated list of posts",
256
+ "args": [
257
+ {
258
+ "name": "page",
259
+ "type": "Int",
260
+ "description": "Page number",
261
+ "defaultValue": 1
262
+ },
263
+ {
264
+ "name": "limit",
265
+ "type": "Int",
266
+ "description": "Items per page",
267
+ "defaultValue": 20
268
+ },
269
+ {
270
+ "name": "authorId",
271
+ "type": "ID",
272
+ "description": "Filter by author"
273
+ },
274
+ {
275
+ "name": "tags",
276
+ "type": ["String!"],
277
+ "description": "Filter by tags"
278
+ }
279
+ ],
280
+ "returnType": "PostConnection!",
281
+ "security": [
282
+ {
283
+ "bearerAuth": []
284
+ }
285
+ ]
286
+ },
287
+ {
288
+ "name": "feed",
289
+ "description": "Get personalized feed",
290
+ "args": [
291
+ {
292
+ "name": "page",
293
+ "type": "Int",
294
+ "description": "Page number",
295
+ "defaultValue": 1
296
+ },
297
+ {
298
+ "name": "limit",
299
+ "type": "Int",
300
+ "description": "Items per page",
301
+ "defaultValue": 20
302
+ }
303
+ ],
304
+ "returnType": "PostConnection!",
305
+ "security": [
306
+ {
307
+ "bearerAuth": []
308
+ }
309
+ ]
310
+ }
311
+ ],
312
+ "mutations": [
313
+ {
314
+ "name": "createPost",
315
+ "description": "Create a new post",
316
+ "args": [
317
+ {
318
+ "name": "input",
319
+ "type": "CreatePostInput!",
320
+ "description": "Post data"
321
+ }
322
+ ],
323
+ "returnType": "Post!",
324
+ "security": [
325
+ {
326
+ "bearerAuth": []
327
+ }
328
+ ]
329
+ },
330
+ {
331
+ "name": "likePost",
332
+ "description": "Like a post",
333
+ "args": [
334
+ {
335
+ "name": "id",
336
+ "type": "ID!",
337
+ "description": "Post ID"
338
+ }
339
+ ],
340
+ "returnType": "Post!",
341
+ "security": [
342
+ {
343
+ "bearerAuth": []
344
+ }
345
+ ]
346
+ },
347
+ {
348
+ "name": "followUser",
349
+ "description": "Follow a user",
350
+ "args": [
351
+ {
352
+ "name": "userId",
353
+ "type": "ID!",
354
+ "description": "User ID to follow"
355
+ }
356
+ ],
357
+ "returnType": "User!",
358
+ "security": [
359
+ {
360
+ "bearerAuth": []
361
+ }
362
+ ]
363
+ }
364
+ ],
365
+ "subscriptions": [
366
+ {
367
+ "name": "postCreated",
368
+ "description": "Subscribe to new posts",
369
+ "args": [
370
+ {
371
+ "name": "authorId",
372
+ "type": "ID",
373
+ "description": "Filter by author"
374
+ }
375
+ ],
376
+ "returnType": "Post!",
377
+ "security": [
378
+ {
379
+ "bearerAuth": []
380
+ }
381
+ ]
382
+ },
383
+ {
384
+ "name": "notificationReceived",
385
+ "description": "Subscribe to notifications",
386
+ "returnType": "Notification!",
387
+ "security": [
388
+ {
389
+ "bearerAuth": []
390
+ }
391
+ ]
392
+ }
393
+ ]
394
+ },
395
+ "websocket": {
396
+ "url": "/ws",
397
+ "headers": [
398
+ {
399
+ "name": "Authorization",
400
+ "description": "JWT token for WebSocket authentication",
401
+ "required": true
402
+ }
403
+ ],
404
+ "channels": [
405
+ {
406
+ "name": "notifications",
407
+ "description": "Real-time notifications",
408
+ "namespace": "social",
409
+ "direction": "subscribe",
410
+ "tags": ["notifications", "real-time"],
411
+ "reconnectStrategy": {
412
+ "maxAttempts": 10,
413
+ "delay": 2000,
414
+ "backoffMultiplier": 1.5
415
+ },
416
+ "security": [
417
+ {
418
+ "bearerAuth": []
419
+ }
420
+ ],
421
+ "messages": [
422
+ {
423
+ "name": "newNotification",
424
+ "description": "New notification received",
425
+ "direction": "subscribe",
426
+ "schemaRef": "WebSocketNotification"
427
+ },
428
+ {
429
+ "name": "notificationRead",
430
+ "description": "Mark notification as read",
431
+ "direction": "publish",
432
+ "schemaRef": "NotificationReadEvent"
433
+ }
434
+ ]
435
+ },
436
+ {
437
+ "name": "liveFeed",
438
+ "description": "Real-time feed updates",
439
+ "namespace": "social",
440
+ "direction": "subscribe",
441
+ "tags": ["feed", "posts", "real-time"],
442
+ "reconnectStrategy": {
443
+ "maxAttempts": 5,
444
+ "delay": 1000,
445
+ "backoffMultiplier": 2
446
+ },
447
+ "security": [
448
+ {
449
+ "bearerAuth": []
450
+ }
451
+ ],
452
+ "messages": [
453
+ {
454
+ "name": "newPost",
455
+ "description": "New post in feed",
456
+ "direction": "subscribe",
457
+ "schemaRef": "FeedPostEvent"
458
+ },
459
+ {
460
+ "name": "postUpdate",
461
+ "description": "Post updated in feed",
462
+ "direction": "subscribe",
463
+ "schemaRef": "FeedPostEvent"
464
+ },
465
+ {
466
+ "name": "postLike",
467
+ "description": "Post like notification",
468
+ "direction": "subscribe",
469
+ "schemaRef": "PostLikeEvent"
470
+ }
471
+ ]
472
+ },
473
+ {
474
+ "name": "chat",
475
+ "description": "Real-time messaging",
476
+ "namespace": "messaging",
477
+ "direction": "both",
478
+ "tags": ["chat", "messaging", "real-time"],
479
+ "connectionParams": {
480
+ "userId": {
481
+ "description": "Current user ID"
482
+ }
483
+ },
484
+ "reconnectStrategy": {
485
+ "maxAttempts": 20,
486
+ "delay": 1000,
487
+ "backoffMultiplier": 1.2
488
+ },
489
+ "security": [
490
+ {
491
+ "bearerAuth": []
492
+ }
493
+ ],
494
+ "messages": [
495
+ {
496
+ "name": "chatMessage",
497
+ "description": "Chat message",
498
+ "direction": "both",
499
+ "schemaRef": "ChatMessage"
500
+ },
501
+ {
502
+ "name": "typingIndicator",
503
+ "description": "User typing indicator",
504
+ "direction": "publish",
505
+ "schemaRef": "TypingIndicator"
506
+ },
507
+ {
508
+ "name": "userOnline",
509
+ "description": "User came online",
510
+ "direction": "subscribe",
511
+ "schemaRef": "UserPresenceEvent"
512
+ },
513
+ {
514
+ "name": "userOffline",
515
+ "description": "User went offline",
516
+ "direction": "subscribe",
517
+ "schemaRef": "UserPresenceEvent"
518
+ }
519
+ ]
520
+ }
521
+ ]
522
+ }
523
+ },
524
+ "schemas": {
525
+ "MediaId": {
526
+ "type": "string",
527
+ "pattern": "^[0-9a-fA-F]{24}$",
528
+ "description": "MongoDB ObjectId for media"
529
+ },
530
+ "MediaUploadRequest": {
531
+ "type": "object",
532
+ "required": ["file"],
533
+ "properties": {
534
+ "file": {
535
+ "type": "string",
536
+ "format": "binary",
537
+ "description": "Media file to upload"
538
+ },
539
+ "alt": {
540
+ "type": "string",
541
+ "maxLength": 200,
542
+ "description": "Alternative text for accessibility"
543
+ },
544
+ "tags": {
545
+ "type": "array",
546
+ "items": {
547
+ "type": "string"
548
+ },
549
+ "description": "Media tags"
550
+ }
551
+ }
552
+ },
553
+ "MediaResponse": {
554
+ "type": "object",
555
+ "properties": {
556
+ "id": {
557
+ "$ref": "#/service/schemas/MediaId"
558
+ },
559
+ "url": {
560
+ "type": "string",
561
+ "format": "uri",
562
+ "description": "Media file URL"
563
+ },
564
+ "type": {
565
+ "type": "string",
566
+ "enum": ["image", "video", "audio", "document"],
567
+ "description": "Media type"
568
+ },
569
+ "width": {
570
+ "type": "integer",
571
+ "description": "Media width (for images/videos)"
572
+ },
573
+ "height": {
574
+ "type": "integer",
575
+ "description": "Media height (for images/videos)"
576
+ },
577
+ "size": {
578
+ "type": "integer",
579
+ "description": "File size in bytes"
580
+ },
581
+ "alt": {
582
+ "type": "string",
583
+ "description": "Alternative text"
584
+ },
585
+ "tags": {
586
+ "type": "array",
587
+ "items": {
588
+ "type": "string"
589
+ },
590
+ "description": "Media tags"
591
+ },
592
+ "createdAt": {
593
+ "type": "string",
594
+ "format": "date-time",
595
+ "description": "Upload timestamp"
596
+ }
597
+ }
598
+ },
599
+ "ErrorResponse": {
600
+ "type": "object",
601
+ "properties": {
602
+ "error": {
603
+ "type": "string",
604
+ "description": "Error code"
605
+ },
606
+ "message": {
607
+ "type": "string",
608
+ "description": "Human-readable error message"
609
+ },
610
+ "details": {
611
+ "type": "object",
612
+ "description": "Additional error details"
613
+ }
614
+ }
615
+ },
616
+ "WebSocketNotification": {
617
+ "type": "object",
618
+ "required": ["id", "type", "user", "message", "timestamp"],
619
+ "properties": {
620
+ "id": {
621
+ "type": "string"
622
+ },
623
+ "type": {
624
+ "type": "string",
625
+ "enum": ["like", "comment", "follow", "mention", "post"]
626
+ },
627
+ "user": {
628
+ "type": "object",
629
+ "properties": {
630
+ "id": {
631
+ "type": "string"
632
+ },
633
+ "username": {
634
+ "type": "string"
635
+ },
636
+ "avatar": {
637
+ "type": "string"
638
+ }
639
+ }
640
+ },
641
+ "message": {
642
+ "type": "string"
643
+ },
644
+ "data": {
645
+ "type": "object",
646
+ "description": "Additional notification data"
647
+ },
648
+ "timestamp": {
649
+ "type": "string",
650
+ "format": "date-time"
651
+ }
652
+ }
653
+ },
654
+ "NotificationReadEvent": {
655
+ "type": "object",
656
+ "required": ["notificationId", "timestamp"],
657
+ "properties": {
658
+ "notificationId": {
659
+ "type": "string"
660
+ },
661
+ "timestamp": {
662
+ "type": "string",
663
+ "format": "date-time"
664
+ }
665
+ }
666
+ },
667
+ "FeedPostEvent": {
668
+ "type": "object",
669
+ "required": ["action", "post", "timestamp"],
670
+ "properties": {
671
+ "action": {
672
+ "type": "string",
673
+ "enum": ["created", "updated", "deleted"]
674
+ },
675
+ "post": {
676
+ "type": "object",
677
+ "properties": {
678
+ "id": {
679
+ "type": "string"
680
+ },
681
+ "author": {
682
+ "type": "object",
683
+ "properties": {
684
+ "id": {
685
+ "type": "string"
686
+ },
687
+ "username": {
688
+ "type": "string"
689
+ }
690
+ }
691
+ },
692
+ "content": {
693
+ "type": "string"
694
+ },
695
+ "createdAt": {
696
+ "type": "string",
697
+ "format": "date-time"
698
+ }
699
+ }
700
+ },
701
+ "timestamp": {
702
+ "type": "string",
703
+ "format": "date-time"
704
+ }
705
+ }
706
+ },
707
+ "PostLikeEvent": {
708
+ "type": "object",
709
+ "required": ["postId", "user", "action", "timestamp"],
710
+ "properties": {
711
+ "postId": {
712
+ "type": "string"
713
+ },
714
+ "user": {
715
+ "type": "object",
716
+ "properties": {
717
+ "id": {
718
+ "type": "string"
719
+ },
720
+ "username": {
721
+ "type": "string"
722
+ }
723
+ }
724
+ },
725
+ "action": {
726
+ "type": "string",
727
+ "enum": ["liked", "unliked"]
728
+ },
729
+ "timestamp": {
730
+ "type": "string",
731
+ "format": "date-time"
732
+ }
733
+ }
734
+ },
735
+ "ChatMessage": {
736
+ "type": "object",
737
+ "required": ["id", "fromUserId", "toUserId", "content", "timestamp"],
738
+ "properties": {
739
+ "id": {
740
+ "type": "string"
741
+ },
742
+ "fromUserId": {
743
+ "type": "string"
744
+ },
745
+ "toUserId": {
746
+ "type": "string"
747
+ },
748
+ "content": {
749
+ "type": "string",
750
+ "minLength": 1,
751
+ "maxLength": 2000
752
+ },
753
+ "messageType": {
754
+ "type": "string",
755
+ "enum": ["text", "image", "file"],
756
+ "default": "text"
757
+ },
758
+ "timestamp": {
759
+ "type": "string",
760
+ "format": "date-time"
761
+ }
762
+ }
763
+ },
764
+ "TypingIndicator": {
765
+ "type": "object",
766
+ "required": ["userId", "isTyping", "timestamp"],
767
+ "properties": {
768
+ "userId": {
769
+ "type": "string"
770
+ },
771
+ "isTyping": {
772
+ "type": "boolean"
773
+ },
774
+ "timestamp": {
775
+ "type": "string",
776
+ "format": "date-time"
777
+ }
778
+ }
779
+ },
780
+ "UserPresenceEvent": {
781
+ "type": "object",
782
+ "required": ["userId", "status", "timestamp"],
783
+ "properties": {
784
+ "userId": {
785
+ "type": "string"
786
+ },
787
+ "status": {
788
+ "type": "string",
789
+ "enum": ["online", "offline"]
790
+ },
791
+ "timestamp": {
792
+ "type": "string",
793
+ "format": "date-time"
794
+ }
795
+ }
796
+ }
797
+ }
798
+ }
799
+ }