@atlas-id/contracts 0.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.
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ isEmailNotificationRequest,
4
+ isSmsNotificationRequest,
5
+ isWebhookNotificationRequest,
6
+ notificationEvents,
7
+ type EmailNotificationRequest,
8
+ type SmsNotificationRequest,
9
+ type WebhookNotificationRequest,
10
+ } from './notifications';
11
+
12
+ describe('notifications', () => {
13
+ describe('type guards', () => {
14
+ const emailRequest: EmailNotificationRequest = {
15
+ channel: 'email',
16
+ projectId: 'proj_123',
17
+ to: 'user@example.com',
18
+ templateId: 'tmpl_welcome',
19
+ };
20
+
21
+ const smsRequest: SmsNotificationRequest = {
22
+ channel: 'sms',
23
+ projectId: 'proj_123',
24
+ to: '+1234567890',
25
+ templateId: 'tmpl_sms',
26
+ };
27
+
28
+ const webhookRequest: WebhookNotificationRequest = {
29
+ channel: 'webhook',
30
+ projectId: 'proj_123',
31
+ url: 'https://example.com/webhook',
32
+ signatureVersion: '1.0.0',
33
+ body: { data: 'test' },
34
+ };
35
+
36
+ it('isEmailNotificationRequest debe identificar requests de email', () => {
37
+ expect(isEmailNotificationRequest(emailRequest)).toBe(true);
38
+ expect(isEmailNotificationRequest(smsRequest)).toBe(false);
39
+ expect(isEmailNotificationRequest(webhookRequest)).toBe(false);
40
+ });
41
+
42
+ it('isSmsNotificationRequest debe identificar requests de SMS', () => {
43
+ expect(isSmsNotificationRequest(smsRequest)).toBe(true);
44
+ expect(isSmsNotificationRequest(emailRequest)).toBe(false);
45
+ expect(isSmsNotificationRequest(webhookRequest)).toBe(false);
46
+ });
47
+
48
+ it('isWebhookNotificationRequest debe identificar requests de webhook', () => {
49
+ expect(isWebhookNotificationRequest(webhookRequest)).toBe(true);
50
+ expect(isWebhookNotificationRequest(emailRequest)).toBe(false);
51
+ expect(isWebhookNotificationRequest(smsRequest)).toBe(false);
52
+ });
53
+ });
54
+
55
+ describe('notificationEvents contracts', () => {
56
+ it('debe tener todos los eventos definidos', () => {
57
+ expect(notificationEvents.requested).toBeDefined();
58
+ expect(notificationEvents.dispatched).toBeDefined();
59
+ expect(notificationEvents.delivered).toBeDefined();
60
+ expect(notificationEvents.failed).toBeDefined();
61
+ });
62
+
63
+ it('los ejemplos deben tener la estructura correcta', () => {
64
+ const requestedExample = notificationEvents.requested.example;
65
+ expect(requestedExample.id).toBeDefined();
66
+ expect(requestedExample.type).toBe('notifications.requested');
67
+ expect(requestedExample.version).toBe('1.0.0');
68
+ expect(requestedExample.payload).toBeDefined();
69
+ expect(requestedExample.meta).toBeDefined();
70
+ expect(requestedExample.meta.source).toBeDefined();
71
+ });
72
+
73
+ it('los schemas deben seguir el formato tipo@version', () => {
74
+ expect(notificationEvents.requested.schema).toBe('notifications.requested@1.0.0');
75
+ expect(notificationEvents.dispatched.schema).toBe('notifications.dispatched@1.0.0');
76
+ expect(notificationEvents.delivered.schema).toBe('notifications.delivered@1.0.0');
77
+ expect(notificationEvents.failed.schema).toBe('notifications.failed@1.0.0');
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,298 @@
1
+ import { EventContract, EventEnvelope, NOTIFICATION_EVENT_TYPES } from './events';
2
+ import { SemanticVersion } from './versioning';
3
+
4
+ /**
5
+ * Canales disponibles para el envío de notificaciones.
6
+ */
7
+ export type NotificationChannel = 'email' | 'sms' | 'webhook';
8
+
9
+ /**
10
+ * Base común para todas las solicitudes de notificación.
11
+ * Contiene campos compartidos entre todos los tipos de canales.
12
+ *
13
+ * @property channel - Canal de notificación a utilizar
14
+ * @property projectId - ID del proyecto que solicita la notificación
15
+ * @property tenantId - ID del tenant (opcional, para multi-tenancy)
16
+ * @property locale - Código de idioma para localización (ej: 'es-ES', 'en-US')
17
+ * @property deduplicationKey - Clave opcional para prevenir duplicados
18
+ * @property expiresAt - Timestamp ISO 8601 de expiración de la notificación
19
+ * @property metadata - Metadatos adicionales como pares clave-valor
20
+ */
21
+ export type BaseNotificationRequest = {
22
+ channel: NotificationChannel;
23
+ projectId: string;
24
+ tenantId?: string;
25
+ locale?: string;
26
+ deduplicationKey?: string;
27
+ expiresAt?: string;
28
+ metadata?: Record<string, string>;
29
+ };
30
+
31
+ /**
32
+ * Solicitud de notificación por correo electrónico.
33
+ *
34
+ * @property channel - Debe ser 'email'
35
+ * @property to - Dirección de correo electrónico del destinatario
36
+ * @property templateId - ID del template de email a utilizar
37
+ * @property variables - Variables para reemplazar en el template
38
+ * @property cc - Lista opcional de direcciones en copia
39
+ * @property bcc - Lista opcional de direcciones en copia oculta
40
+ * @property replyTo - Dirección de respuesta opcional
41
+ */
42
+ export type EmailNotificationRequest = BaseNotificationRequest & {
43
+ channel: 'email';
44
+ to: string;
45
+ templateId: string;
46
+ variables?: Record<string, string | number | boolean | null>;
47
+ cc?: string[];
48
+ bcc?: string[];
49
+ replyTo?: string;
50
+ };
51
+
52
+ /**
53
+ * Solicitud de notificación por SMS.
54
+ *
55
+ * @property channel - Debe ser 'sms'
56
+ * @property to - Número de teléfono del destinatario (formato E.164 recomendado)
57
+ * @property templateId - ID del template de SMS a utilizar
58
+ * @property variables - Variables para reemplazar en el template
59
+ */
60
+ export type SmsNotificationRequest = BaseNotificationRequest & {
61
+ channel: 'sms';
62
+ to: string;
63
+ templateId: string;
64
+ variables?: Record<string, string | number | boolean | null>;
65
+ };
66
+
67
+ /**
68
+ * Solicitud de notificación por webhook.
69
+ *
70
+ * @property channel - Debe ser 'webhook'
71
+ * @property url - URL del endpoint que recibirá el webhook
72
+ * @property signatureVersion - Versión del algoritmo de firma para verificación
73
+ * @property body - Cuerpo del webhook (estructura libre)
74
+ */
75
+ export type WebhookNotificationRequest = BaseNotificationRequest & {
76
+ channel: 'webhook';
77
+ url: string;
78
+ signatureVersion: SemanticVersion;
79
+ body: unknown;
80
+ };
81
+
82
+ /**
83
+ * Unión de todos los tipos de solicitudes de notificación.
84
+ * Utiliza discriminated union basado en el campo 'channel'.
85
+ */
86
+ export type NotificationRequest =
87
+ | EmailNotificationRequest
88
+ | SmsNotificationRequest
89
+ | WebhookNotificationRequest;
90
+
91
+ /**
92
+ * Estados posibles de una notificación en su ciclo de vida.
93
+ *
94
+ * - requested: Solicitud creada, pendiente de procesamiento
95
+ * - scheduled: Programada para envío futuro
96
+ * - dispatched: Enviada al proveedor externo
97
+ * - delivered: Confirmada como entregada por el proveedor
98
+ * - failed: Falló el envío o entrega
99
+ * - cancelled: Cancelada antes de ser enviada
100
+ */
101
+ export type NotificationStatus =
102
+ | 'requested'
103
+ | 'scheduled'
104
+ | 'dispatched'
105
+ | 'delivered'
106
+ | 'failed'
107
+ | 'cancelled';
108
+
109
+ /**
110
+ * Envelope que contiene una notificación con su estado y metadata.
111
+ *
112
+ * @property id - Identificador único de la notificación
113
+ * @property request - Solicitud original de la notificación
114
+ * @property status - Estado actual de la notificación
115
+ * @property createdAt - Timestamp ISO 8601 de creación
116
+ * @property updatedAt - Timestamp ISO 8601 de última actualización
117
+ * @property lastError - Información del último error si el estado es 'failed'
118
+ * @property providerResponseId - ID de respuesta del proveedor externo
119
+ */
120
+ export type NotificationEnvelope = {
121
+ id: string;
122
+ request: NotificationRequest;
123
+ status: NotificationStatus;
124
+ createdAt: string;
125
+ updatedAt: string;
126
+ lastError?: {
127
+ code: string;
128
+ message: string;
129
+ retriable: boolean;
130
+ };
131
+ providerResponseId?: string;
132
+ };
133
+
134
+ /**
135
+ * Type guard para verificar si una solicitud es de tipo email.
136
+ *
137
+ * @param request - Solicitud a verificar
138
+ * @returns true si es EmailNotificationRequest
139
+ */
140
+ export function isEmailNotificationRequest(
141
+ request: NotificationRequest
142
+ ): request is EmailNotificationRequest {
143
+ return request.channel === 'email';
144
+ }
145
+
146
+ /**
147
+ * Type guard para verificar si una solicitud es de tipo SMS.
148
+ *
149
+ * @param request - Solicitud a verificar
150
+ * @returns true si es SmsNotificationRequest
151
+ */
152
+ export function isSmsNotificationRequest(
153
+ request: NotificationRequest
154
+ ): request is SmsNotificationRequest {
155
+ return request.channel === 'sms';
156
+ }
157
+
158
+ /**
159
+ * Type guard para verificar si una solicitud es de tipo webhook.
160
+ *
161
+ * @param request - Solicitud a verificar
162
+ * @returns true si es WebhookNotificationRequest
163
+ */
164
+ export function isWebhookNotificationRequest(
165
+ request: NotificationRequest
166
+ ): request is WebhookNotificationRequest {
167
+ return request.channel === 'webhook';
168
+ }
169
+
170
+ type RequestedPayload = {
171
+ notification: NotificationEnvelope;
172
+ };
173
+
174
+ type DispatchedPayload = {
175
+ notificationId: string;
176
+ channel: NotificationChannel;
177
+ provider: string;
178
+ providerMessageId?: string;
179
+ };
180
+
181
+ type DeliveredPayload = {
182
+ notificationId: string;
183
+ deliveredAt: string;
184
+ };
185
+
186
+ type FailedPayload = {
187
+ notificationId: string;
188
+ failedAt: string;
189
+ errorCode: string;
190
+ message: string;
191
+ retriable: boolean;
192
+ };
193
+
194
+ /**
195
+ * Contratos de eventos para el sistema de notificaciones.
196
+ * Define los eventos que se emiten durante el ciclo de vida de una notificación.
197
+ */
198
+ export const notificationEvents: Record<
199
+ 'requested' | 'dispatched' | 'delivered' | 'failed',
200
+ EventContract<RequestedPayload | DispatchedPayload | DeliveredPayload | FailedPayload>
201
+ > = {
202
+ requested: {
203
+ type: NOTIFICATION_EVENT_TYPES.REQUESTED,
204
+ version: '1.0.0',
205
+ schema: 'notifications.requested@1.0.0',
206
+ example: {
207
+ id: 'evt_123',
208
+ type: NOTIFICATION_EVENT_TYPES.REQUESTED,
209
+ version: '1.0.0',
210
+ occurredAt: new Date().toISOString(),
211
+ payload: {
212
+ notification: {
213
+ id: 'ntf_123',
214
+ request: {
215
+ channel: 'email',
216
+ projectId: 'proj_123',
217
+ to: 'user@example.com',
218
+ templateId: 'tmpl_welcome',
219
+ },
220
+ status: 'requested',
221
+ createdAt: new Date().toISOString(),
222
+ updatedAt: new Date().toISOString(),
223
+ },
224
+ },
225
+ meta: {
226
+ source: 'notifications-service',
227
+ },
228
+ },
229
+ },
230
+ dispatched: {
231
+ type: NOTIFICATION_EVENT_TYPES.DISPATCHED,
232
+ version: '1.0.0',
233
+ schema: 'notifications.dispatched@1.0.0',
234
+ example: {
235
+ id: 'evt_124',
236
+ type: NOTIFICATION_EVENT_TYPES.DISPATCHED,
237
+ version: '1.0.0',
238
+ occurredAt: new Date().toISOString(),
239
+ payload: {
240
+ notificationId: 'ntf_123',
241
+ channel: 'email',
242
+ provider: 'postmark',
243
+ providerMessageId: 'msg_abc',
244
+ },
245
+ meta: {
246
+ source: 'notifications-service',
247
+ },
248
+ },
249
+ },
250
+ delivered: {
251
+ type: NOTIFICATION_EVENT_TYPES.DELIVERED,
252
+ version: '1.0.0',
253
+ schema: 'notifications.delivered@1.0.0',
254
+ example: {
255
+ id: 'evt_125',
256
+ type: NOTIFICATION_EVENT_TYPES.DELIVERED,
257
+ version: '1.0.0',
258
+ occurredAt: new Date().toISOString(),
259
+ payload: {
260
+ notificationId: 'ntf_123',
261
+ deliveredAt: new Date().toISOString(),
262
+ },
263
+ meta: {
264
+ source: 'notifications-service',
265
+ },
266
+ },
267
+ },
268
+ failed: {
269
+ type: NOTIFICATION_EVENT_TYPES.FAILED,
270
+ version: '1.0.0',
271
+ schema: 'notifications.failed@1.0.0',
272
+ example: {
273
+ id: 'evt_126',
274
+ type: NOTIFICATION_EVENT_TYPES.FAILED,
275
+ version: '1.0.0',
276
+ occurredAt: new Date().toISOString(),
277
+ payload: {
278
+ notificationId: 'ntf_123',
279
+ failedAt: new Date().toISOString(),
280
+ errorCode: 'bounce',
281
+ message: 'Mailbox unreachable',
282
+ retriable: false,
283
+ },
284
+ meta: {
285
+ source: 'notifications-service',
286
+ },
287
+ },
288
+ },
289
+ };
290
+
291
+ /**
292
+ * Unión de todos los tipos de eventos de notificaciones.
293
+ */
294
+ export type NotificationEvent =
295
+ | EventEnvelope<RequestedPayload>
296
+ | EventEnvelope<DispatchedPayload>
297
+ | EventEnvelope<DeliveredPayload>
298
+ | EventEnvelope<FailedPayload>;
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ isActiveConnection,
4
+ isRevokedConnection,
5
+ isExpiredConnection,
6
+ oauthConnectionEvents,
7
+ type OAuthConnection,
8
+ } from './oauth-connections';
9
+
10
+ describe('oauth-connections', () => {
11
+ describe('type guards', () => {
12
+ const activeConnection: OAuthConnection = {
13
+ id: 'conn_1',
14
+ providerId: 'google',
15
+ projectId: 'proj_123',
16
+ userId: 'user_123',
17
+ scope: ['calendar'],
18
+ status: 'active',
19
+ createdAt: new Date().toISOString(),
20
+ updatedAt: new Date().toISOString(),
21
+ };
22
+
23
+ const revokedConnection: OAuthConnection = {
24
+ ...activeConnection,
25
+ id: 'conn_2',
26
+ status: 'revoked',
27
+ };
28
+
29
+ const expiredConnection: OAuthConnection = {
30
+ ...activeConnection,
31
+ id: 'conn_3',
32
+ status: 'expired',
33
+ expiresAt: new Date(Date.now() - 1000).toISOString(),
34
+ };
35
+
36
+ it('isActiveConnection debe identificar conexiones activas', () => {
37
+ expect(isActiveConnection(activeConnection)).toBe(true);
38
+ expect(isActiveConnection(revokedConnection)).toBe(false);
39
+ });
40
+
41
+ it('isRevokedConnection debe identificar conexiones revocadas', () => {
42
+ expect(isRevokedConnection(revokedConnection)).toBe(true);
43
+ expect(isRevokedConnection(activeConnection)).toBe(false);
44
+ });
45
+
46
+ it('isExpiredConnection debe identificar conexiones expiradas', () => {
47
+ expect(isExpiredConnection(expiredConnection)).toBe(true);
48
+ expect(isExpiredConnection(activeConnection)).toBe(false);
49
+
50
+ const connectionWithPastExpiry: OAuthConnection = {
51
+ ...activeConnection,
52
+ expiresAt: new Date(Date.now() - 1000).toISOString(),
53
+ };
54
+ expect(isExpiredConnection(connectionWithPastExpiry)).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe('oauthConnectionEvents contracts', () => {
59
+ it('debe tener todos los eventos definidos', () => {
60
+ expect(oauthConnectionEvents.linked).toBeDefined();
61
+ expect(oauthConnectionEvents.tokenRefreshed).toBeDefined();
62
+ expect(oauthConnectionEvents.revoked).toBeDefined();
63
+ });
64
+
65
+ it('los ejemplos deben tener la estructura correcta', () => {
66
+ const linkedExample = oauthConnectionEvents.linked.example;
67
+ expect(linkedExample.id).toBeDefined();
68
+ expect(linkedExample.type).toBe('oauth.connection.linked');
69
+ expect(linkedExample.version).toBe('1.0.0');
70
+ expect(linkedExample.payload).toBeDefined();
71
+ expect(linkedExample.meta).toBeDefined();
72
+ });
73
+
74
+ it('los schemas deben seguir el formato tipo@version', () => {
75
+ expect(oauthConnectionEvents.linked.schema).toBe('oauth.connection.linked@1.0.0');
76
+ expect(oauthConnectionEvents.tokenRefreshed.schema).toBe('oauth.connection.token_refreshed@1.0.0');
77
+ expect(oauthConnectionEvents.revoked.schema).toBe('oauth.connection.revoked@1.0.0');
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,220 @@
1
+ import { EventContract, EventEnvelope, OAUTH_CONNECTION_EVENT_TYPES } from './events';
2
+
3
+ /**
4
+ * Categorías de proveedores OAuth soportados.
5
+ *
6
+ * - oauth2: OAuth 2.0 estándar
7
+ * - oidc: OpenID Connect (extensión de OAuth 2.0)
8
+ * - saml: Security Assertion Markup Language
9
+ */
10
+ export type OAuthProviderCategory = 'oauth2' | 'oidc' | 'saml';
11
+
12
+ /**
13
+ * Definición de un proveedor OAuth configurado en el sistema.
14
+ *
15
+ * @property id - Identificador único del proveedor (ej: 'google', 'github')
16
+ * @property name - Nombre legible del proveedor
17
+ * @property category - Categoría del protocolo OAuth
18
+ * @property authorizationUrl - URL del endpoint de autorización
19
+ * @property tokenUrl - URL del endpoint de intercambio de tokens
20
+ * @property scopes - Lista de scopes soportados por el proveedor
21
+ * @property supportsPkce - Indica si el proveedor soporta PKCE (Proof Key for Code Exchange)
22
+ * @property requiresWebhookForRefresh - Indica si requiere webhook para refrescar tokens
23
+ */
24
+ export type OAuthProvider = {
25
+ id: string;
26
+ name: string;
27
+ category: OAuthProviderCategory;
28
+ authorizationUrl: string;
29
+ tokenUrl: string;
30
+ scopes: string[];
31
+ supportsPkce: boolean;
32
+ requiresWebhookForRefresh?: boolean;
33
+ };
34
+
35
+ /**
36
+ * Conexión OAuth establecida entre un usuario y un proveedor.
37
+ * Representa la autorización activa de un usuario para acceder a recursos del proveedor.
38
+ *
39
+ * @property id - Identificador único de la conexión
40
+ * @property providerId - ID del proveedor OAuth
41
+ * @property projectId - ID del proyecto asociado
42
+ * @property tenantId - ID del tenant (opcional, para multi-tenancy)
43
+ * @property userId - ID del usuario que autorizó la conexión
44
+ * @property scope - Lista de scopes autorizados en esta conexión
45
+ * @property expiresAt - Timestamp ISO 8601 de expiración de la conexión
46
+ * @property createdAt - Timestamp ISO 8601 de creación
47
+ * @property updatedAt - Timestamp ISO 8601 de última actualización
48
+ * @property status - Estado actual de la conexión
49
+ */
50
+ export type OAuthConnection = {
51
+ id: string;
52
+ providerId: string;
53
+ projectId: string;
54
+ tenantId?: string;
55
+ userId: string;
56
+ scope: string[];
57
+ expiresAt?: string;
58
+ createdAt: string;
59
+ updatedAt: string;
60
+ status: 'active' | 'refreshing' | 'revoked' | 'expired';
61
+ };
62
+
63
+ /**
64
+ * Conjunto de tokens OAuth obtenidos de un proveedor.
65
+ *
66
+ * @property accessToken - Token de acceso para autenticación
67
+ * @property refreshToken - Token para refrescar el access token (opcional)
68
+ * @property expiresIn - Tiempo de expiración en segundos (opcional)
69
+ * @property tokenType - Tipo de token (generalmente 'Bearer')
70
+ * @property issuedAt - Timestamp ISO 8601 de emisión
71
+ * @property expiresAt - Timestamp ISO 8601 de expiración calculado
72
+ * @property idToken - ID Token para OIDC (opcional)
73
+ */
74
+ export type TokenSet = {
75
+ accessToken: string;
76
+ refreshToken?: string;
77
+ expiresIn?: number;
78
+ tokenType?: string;
79
+ issuedAt: string;
80
+ expiresAt?: string;
81
+ idToken?: string;
82
+ };
83
+
84
+ /**
85
+ * Type guard para verificar si una conexión está activa.
86
+ *
87
+ * @param connection - Conexión a verificar
88
+ * @returns true si el estado es 'active'
89
+ */
90
+ export function isActiveConnection(connection: OAuthConnection): boolean {
91
+ return connection.status === 'active';
92
+ }
93
+
94
+ /**
95
+ * Type guard para verificar si una conexión está revocada.
96
+ *
97
+ * @param connection - Conexión a verificar
98
+ * @returns true si el estado es 'revoked'
99
+ */
100
+ export function isRevokedConnection(connection: OAuthConnection): boolean {
101
+ return connection.status === 'revoked';
102
+ }
103
+
104
+ /**
105
+ * Type guard para verificar si una conexión está expirada.
106
+ *
107
+ * @param connection - Conexión a verificar
108
+ * @returns true si el estado es 'expired' o si expiresAt está en el pasado
109
+ */
110
+ export function isExpiredConnection(connection: OAuthConnection): boolean {
111
+ if (connection.status === 'expired') return true;
112
+ if (!connection.expiresAt) return false;
113
+ return new Date(connection.expiresAt) < new Date();
114
+ }
115
+
116
+ type ConnectionLinkedPayload = {
117
+ connection: OAuthConnection;
118
+ tokenSet: TokenSet;
119
+ };
120
+
121
+ type TokenRefreshedPayload = {
122
+ connectionId: string;
123
+ tokenSet: TokenSet;
124
+ };
125
+
126
+ type ConnectionRevokedPayload = {
127
+ connectionId: string;
128
+ reason: 'user' | 'provider' | 'security';
129
+ };
130
+
131
+ /**
132
+ * Contratos de eventos para el sistema de conexiones OAuth.
133
+ * Define los eventos que se emiten durante el ciclo de vida de una conexión OAuth.
134
+ */
135
+ export const oauthConnectionEvents: Record<
136
+ 'linked' | 'tokenRefreshed' | 'revoked',
137
+ EventContract<ConnectionLinkedPayload | TokenRefreshedPayload | ConnectionRevokedPayload>
138
+ > = {
139
+ linked: {
140
+ type: OAUTH_CONNECTION_EVENT_TYPES.LINKED,
141
+ version: '1.0.0',
142
+ schema: 'oauth.connection.linked@1.0.0',
143
+ example: {
144
+ id: 'evt_conn_1',
145
+ type: OAUTH_CONNECTION_EVENT_TYPES.LINKED,
146
+ version: '1.0.0',
147
+ occurredAt: new Date().toISOString(),
148
+ payload: {
149
+ connection: {
150
+ id: 'conn_123',
151
+ providerId: 'google',
152
+ projectId: 'proj_123',
153
+ userId: 'user_123',
154
+ scope: ['calendar', 'email'],
155
+ status: 'active',
156
+ createdAt: new Date().toISOString(),
157
+ updatedAt: new Date().toISOString(),
158
+ },
159
+ tokenSet: {
160
+ accessToken: 'access-token',
161
+ refreshToken: 'refresh-token',
162
+ issuedAt: new Date().toISOString(),
163
+ expiresIn: 3600,
164
+ },
165
+ },
166
+ meta: {
167
+ source: 'oauth-connections-service',
168
+ },
169
+ },
170
+ },
171
+ tokenRefreshed: {
172
+ type: OAUTH_CONNECTION_EVENT_TYPES.TOKEN_REFRESHED,
173
+ version: '1.0.0',
174
+ schema: 'oauth.connection.token_refreshed@1.0.0',
175
+ example: {
176
+ id: 'evt_conn_2',
177
+ type: OAUTH_CONNECTION_EVENT_TYPES.TOKEN_REFRESHED,
178
+ version: '1.0.0',
179
+ occurredAt: new Date().toISOString(),
180
+ payload: {
181
+ connectionId: 'conn_123',
182
+ tokenSet: {
183
+ accessToken: 'access-token-2',
184
+ refreshToken: 'refresh-token',
185
+ issuedAt: new Date().toISOString(),
186
+ expiresIn: 3600,
187
+ },
188
+ },
189
+ meta: {
190
+ source: 'oauth-connections-service',
191
+ },
192
+ },
193
+ },
194
+ revoked: {
195
+ type: OAUTH_CONNECTION_EVENT_TYPES.REVOKED,
196
+ version: '1.0.0',
197
+ schema: 'oauth.connection.revoked@1.0.0',
198
+ example: {
199
+ id: 'evt_conn_3',
200
+ type: OAUTH_CONNECTION_EVENT_TYPES.REVOKED,
201
+ version: '1.0.0',
202
+ occurredAt: new Date().toISOString(),
203
+ payload: {
204
+ connectionId: 'conn_123',
205
+ reason: 'security',
206
+ },
207
+ meta: {
208
+ source: 'oauth-connections-service',
209
+ },
210
+ },
211
+ },
212
+ };
213
+
214
+ /**
215
+ * Unión de todos los tipos de eventos de conexiones OAuth.
216
+ */
217
+ export type OAuthConnectionEvent =
218
+ | EventEnvelope<ConnectionLinkedPayload>
219
+ | EventEnvelope<TokenRefreshedPayload>
220
+ | EventEnvelope<ConnectionRevokedPayload>;