@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.
- package/LICENSE +28 -0
- package/README.md +289 -0
- package/package.json +56 -0
- package/src/contracts-validation.test.ts +136 -0
- package/src/events.test.ts +48 -0
- package/src/events.ts +85 -0
- package/src/index.ts +6 -0
- package/src/notifications.test.ts +80 -0
- package/src/notifications.ts +298 -0
- package/src/oauth-connections.test.ts +80 -0
- package/src/oauth-connections.ts +220 -0
- package/src/schemas/index.ts +138 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/validation.test.ts +96 -0
- package/src/utils/validation.ts +136 -0
- package/src/versioning.test.ts +112 -0
- package/src/versioning.ts +156 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright (c) 2024 Opendex, Inc.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary and confidential property of Opendex, Inc. ("Opendex").
|
|
7
|
+
|
|
8
|
+
The Software is protected by copyright laws and international copyright
|
|
9
|
+
treaties, as well as other intellectual property laws and treaties.
|
|
10
|
+
|
|
11
|
+
No part of this Software may be reproduced, distributed, transmitted,
|
|
12
|
+
displayed, published, or broadcast in any form or by any means, including
|
|
13
|
+
but not limited to electronic, mechanical, photocopying, recording, or
|
|
14
|
+
otherwise, without the prior written permission of Opendex.
|
|
15
|
+
|
|
16
|
+
Unauthorized copying, modification, distribution, or use of this Software,
|
|
17
|
+
via any medium, is strictly prohibited and may result in severe civil and
|
|
18
|
+
criminal penalties, and will be prosecuted to the maximum extent possible
|
|
19
|
+
under the law.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
24
|
+
OPENDEX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
25
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
26
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
+
|
|
28
|
+
For licensing inquiries, please contact: legal@opendex.com
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Atlas ID Contracts
|
|
2
|
+
|
|
3
|
+
Biblioteca de contratos TypeScript compartidos para el ecosistema de servicios de Atlas ID. Define los tipos, estructuras de datos y contratos de eventos utilizados para la comunicación entre servicios en arquitecturas distribuidas.
|
|
4
|
+
|
|
5
|
+
**Propiedad de Opendex, Inc.** - Este software es propietario y no es de código abierto.
|
|
6
|
+
|
|
7
|
+
## Función Principal
|
|
8
|
+
|
|
9
|
+
Este paquete proporciona contratos tipados y versionados que garantizan la consistencia y compatibilidad entre los diferentes servicios del sistema IAM. Actúa como la fuente única de verdad para:
|
|
10
|
+
|
|
11
|
+
- Definiciones de tipos TypeScript para eventos, notificaciones y conexiones OAuth
|
|
12
|
+
- Contratos de eventos con versionado semántico
|
|
13
|
+
- Esquemas de validación en runtime
|
|
14
|
+
- Utilidades de validación y type guards
|
|
15
|
+
|
|
16
|
+
## Arquitectura
|
|
17
|
+
|
|
18
|
+
El proyecto está organizado en módulos especializados:
|
|
19
|
+
|
|
20
|
+
### Eventos
|
|
21
|
+
|
|
22
|
+
Define la estructura base para eventos en sistemas distribuidos con soporte para:
|
|
23
|
+
- Versionado semántico de contratos
|
|
24
|
+
- Metadata de trazabilidad (traceId, spanId, correlationId, causationId)
|
|
25
|
+
- Soporte para multi-tenancy (tenantId, projectId)
|
|
26
|
+
- Identificación de origen (source)
|
|
27
|
+
|
|
28
|
+
### Notificaciones
|
|
29
|
+
|
|
30
|
+
Contratos para el sistema de notificaciones que soporta múltiples canales:
|
|
31
|
+
- Email con soporte para templates, variables, CC, BCC y reply-to
|
|
32
|
+
- SMS con templates y variables
|
|
33
|
+
- Webhooks con versionado de firmas y payloads personalizados
|
|
34
|
+
|
|
35
|
+
Incluye estados del ciclo de vida: requested, scheduled, dispatched, delivered, failed, cancelled.
|
|
36
|
+
|
|
37
|
+
### Conexiones OAuth
|
|
38
|
+
|
|
39
|
+
Contratos para gestión de conexiones OAuth con soporte para:
|
|
40
|
+
- Múltiples protocolos: OAuth2, OIDC, SAML
|
|
41
|
+
- Gestión de tokens (access, refresh, ID tokens)
|
|
42
|
+
- Estados de conexión: active, refreshing, revoked, expired
|
|
43
|
+
- Soporte para PKCE y webhooks de refresh
|
|
44
|
+
|
|
45
|
+
### Versionado
|
|
46
|
+
|
|
47
|
+
Utilidades para trabajar con versionado semántico:
|
|
48
|
+
- Validación de formato de versiones
|
|
49
|
+
- Parsing y comparación de versiones
|
|
50
|
+
- Funciones de comparación (mayor que, menor que, igual)
|
|
51
|
+
|
|
52
|
+
## Uso
|
|
53
|
+
|
|
54
|
+
### Instalación
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm add @atlas-id/contracts
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Importación de Tipos
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import type {
|
|
64
|
+
EventEnvelope,
|
|
65
|
+
NotificationRequest,
|
|
66
|
+
OAuthConnection,
|
|
67
|
+
SemanticVersion,
|
|
68
|
+
} from '@atlas-id/contracts';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Uso de Contratos de Eventos
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { notificationEvents } from '@atlas-id/contracts';
|
|
75
|
+
|
|
76
|
+
// El contrato incluye el tipo, versión, schema y un ejemplo
|
|
77
|
+
const event = {
|
|
78
|
+
...notificationEvents.requested.example,
|
|
79
|
+
id: 'evt_custom_123',
|
|
80
|
+
occurredAt: new Date().toISOString(),
|
|
81
|
+
payload: {
|
|
82
|
+
notification: {
|
|
83
|
+
// ... datos de la notificación
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Validación en Runtime
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import {
|
|
93
|
+
notificationRequestSchema,
|
|
94
|
+
createEventEnvelopeSchema,
|
|
95
|
+
} from '@atlas-id/contracts';
|
|
96
|
+
|
|
97
|
+
// Validar una solicitud de notificación
|
|
98
|
+
const result = notificationRequestSchema.safeParse(requestData);
|
|
99
|
+
if (result.success) {
|
|
100
|
+
// requestData es válido
|
|
101
|
+
} else {
|
|
102
|
+
// result.error contiene los errores de validación
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Type Guards
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import {
|
|
110
|
+
isEmailNotificationRequest,
|
|
111
|
+
isSmsNotificationRequest,
|
|
112
|
+
isWebhookNotificationRequest,
|
|
113
|
+
} from '@atlas-id/contracts';
|
|
114
|
+
|
|
115
|
+
function processNotification(request: NotificationRequest) {
|
|
116
|
+
if (isEmailNotificationRequest(request)) {
|
|
117
|
+
// TypeScript sabe que request es EmailNotificationRequest
|
|
118
|
+
console.log(request.to);
|
|
119
|
+
} else if (isSmsNotificationRequest(request)) {
|
|
120
|
+
// TypeScript sabe que request es SmsNotificationRequest
|
|
121
|
+
console.log(request.to);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Utilidades de Validación
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import {
|
|
130
|
+
isValidUuid,
|
|
131
|
+
isValidEmail,
|
|
132
|
+
isValidUrl,
|
|
133
|
+
isValidE164Phone,
|
|
134
|
+
isValidIso8601,
|
|
135
|
+
} from '@atlas-id/contracts';
|
|
136
|
+
|
|
137
|
+
if (isValidUuid(userId)) {
|
|
138
|
+
// userId es un UUID válido
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (isValidEmail(email)) {
|
|
142
|
+
// email tiene formato válido
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Versionado Semántico
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import {
|
|
150
|
+
isValidSemanticVersion,
|
|
151
|
+
parseSemanticVersion,
|
|
152
|
+
compareSemanticVersions,
|
|
153
|
+
isVersionGreaterThan,
|
|
154
|
+
} from '@atlas-id/contracts';
|
|
155
|
+
|
|
156
|
+
if (isValidSemanticVersion('1.2.3')) {
|
|
157
|
+
const parsed = parseSemanticVersion('1.2.3');
|
|
158
|
+
// { major: 1, minor: 2, patch: 3 }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isVersionGreaterThan('2.0.0', '1.9.9')) {
|
|
162
|
+
// La versión 2.0.0 es mayor que 1.9.9
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Estructura de Contratos
|
|
167
|
+
|
|
168
|
+
Cada contrato de evento sigue la estructura:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
{
|
|
172
|
+
type: string; // Tipo del evento (ej: 'notifications.requested')
|
|
173
|
+
version: SemanticVersion; // Versión semántica del contrato
|
|
174
|
+
schema: string; // Identificador del schema (tipo@version)
|
|
175
|
+
example: EventEnvelope; // Ejemplo completo del evento
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Los eventos incluyen metadata de trazabilidad:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
{
|
|
183
|
+
traceId?: string; // ID de traza para distributed tracing
|
|
184
|
+
spanId?: string; // ID de span dentro de la traza
|
|
185
|
+
correlationId?: string; // ID para correlacionar eventos relacionados
|
|
186
|
+
causationId?: string; // ID del evento que causó este evento
|
|
187
|
+
tenantId?: string; // ID del tenant (multi-tenancy)
|
|
188
|
+
projectId?: string; // ID del proyecto
|
|
189
|
+
source: string; // Servicio que originó el evento
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Validación
|
|
194
|
+
|
|
195
|
+
El paquete proporciona validación en dos niveles:
|
|
196
|
+
|
|
197
|
+
1. **Compilación**: TypeScript valida los tipos en tiempo de compilación
|
|
198
|
+
2. **Runtime**: Zod valida los datos en tiempo de ejecución
|
|
199
|
+
|
|
200
|
+
Los esquemas de Zod están disponibles para validar:
|
|
201
|
+
- Event envelopes
|
|
202
|
+
- Notification requests
|
|
203
|
+
- OAuth connections
|
|
204
|
+
- Token sets
|
|
205
|
+
- Versiones semánticas
|
|
206
|
+
|
|
207
|
+
## Constantes
|
|
208
|
+
|
|
209
|
+
Los tipos de eventos están centralizados en constantes para evitar errores de tipeo:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import {
|
|
213
|
+
NOTIFICATION_EVENT_TYPES,
|
|
214
|
+
OAUTH_CONNECTION_EVENT_TYPES,
|
|
215
|
+
} from '@atlas-id/contracts';
|
|
216
|
+
|
|
217
|
+
// En lugar de 'notifications.requested'
|
|
218
|
+
const eventType = NOTIFICATION_EVENT_TYPES.REQUESTED;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Desarrollo
|
|
222
|
+
|
|
223
|
+
### Scripts Disponibles
|
|
224
|
+
|
|
225
|
+
- `pnpm build`: Compila el proyecto TypeScript
|
|
226
|
+
- `pnpm typecheck`: Valida tipos sin compilar
|
|
227
|
+
- `pnpm test`: Ejecuta tests unitarios
|
|
228
|
+
- `pnpm test:watch`: Ejecuta tests en modo watch
|
|
229
|
+
- `pnpm test:coverage`: Genera reporte de cobertura
|
|
230
|
+
- `pnpm lint`: Ejecuta ESLint
|
|
231
|
+
- `pnpm lint:fix`: Ejecuta ESLint y corrige errores automáticamente
|
|
232
|
+
- `pnpm clean`: Limpia directorios de build y node_modules
|
|
233
|
+
|
|
234
|
+
### Estructura del Proyecto
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
src/
|
|
238
|
+
events.ts # Tipos y contratos base de eventos
|
|
239
|
+
notifications.ts # Contratos de notificaciones
|
|
240
|
+
oauth-connections.ts # Contratos de conexiones OAuth
|
|
241
|
+
versioning.ts # Utilidades de versionado semántico
|
|
242
|
+
utils/
|
|
243
|
+
validation.ts # Utilidades de validación
|
|
244
|
+
schemas/
|
|
245
|
+
index.ts # Esquemas Zod para validación runtime
|
|
246
|
+
*.test.ts # Tests unitarios
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Tests
|
|
250
|
+
|
|
251
|
+
Los tests validan:
|
|
252
|
+
- Funcionalidad de utilidades de versionado
|
|
253
|
+
- Type guards y discriminación de tipos
|
|
254
|
+
- Validación de formatos (UUID, email, URL, etc.)
|
|
255
|
+
- Estructura y validez de ejemplos en contratos
|
|
256
|
+
- Esquemas de validación Zod
|
|
257
|
+
|
|
258
|
+
### Contribución
|
|
259
|
+
|
|
260
|
+
Al agregar nuevos contratos o modificar existentes:
|
|
261
|
+
|
|
262
|
+
1. Actualizar los tipos TypeScript
|
|
263
|
+
2. Agregar documentación JSDoc
|
|
264
|
+
3. Crear o actualizar esquemas Zod
|
|
265
|
+
4. Agregar ejemplos en los contratos
|
|
266
|
+
5. Escribir tests unitarios
|
|
267
|
+
6. Actualizar el CHANGELOG.md
|
|
268
|
+
|
|
269
|
+
Los cambios que modifiquen la estructura de contratos existentes deben incrementar la versión semántica del contrato afectado.
|
|
270
|
+
|
|
271
|
+
## Compatibilidad
|
|
272
|
+
|
|
273
|
+
- Node.js: 18+
|
|
274
|
+
- TypeScript: 5.3+
|
|
275
|
+
- ESM y CommonJS soportados
|
|
276
|
+
|
|
277
|
+
## Propiedad y Licencia
|
|
278
|
+
|
|
279
|
+
Copyright (c) 2024 Opendex, Inc. Todos los derechos reservados.
|
|
280
|
+
|
|
281
|
+
Este software es propiedad exclusiva de Opendex, Inc. y no es de código abierto.
|
|
282
|
+
Está protegido por leyes de derechos de autor y otras leyes de propiedad intelectual.
|
|
283
|
+
|
|
284
|
+
El uso, reproducción, distribución o modificación de este software sin el
|
|
285
|
+
consentimiento escrito previo de Opendex, Inc. está estrictamente prohibido.
|
|
286
|
+
|
|
287
|
+
Para consultas sobre licencias, contactar: legal@opendex.com
|
|
288
|
+
|
|
289
|
+
Ver archivo LICENSE para más detalles.
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atlas-id/contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"author": "Opendex, Inc.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "rimraf dist && tsup-node",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest watch",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
14
|
+
"lint": "eslint --ext .ts .",
|
|
15
|
+
"lint:fix": "eslint --ext .ts . --fix",
|
|
16
|
+
"clean": "rimraf dist && rimraf node_modules",
|
|
17
|
+
"prebuild": "pnpm typecheck && pnpm lint && pnpm test"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"require": {
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"default": "./dist/esm/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./dist/*": {
|
|
34
|
+
"types": "./dist/*.d.ts",
|
|
35
|
+
"require": {
|
|
36
|
+
"default": "./dist/*.js"
|
|
37
|
+
},
|
|
38
|
+
"default": "./dist/esm/*.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^3.23.8"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
47
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
48
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
49
|
+
"eslint": "^8.57.1",
|
|
50
|
+
"husky": "^9.0.11",
|
|
51
|
+
"rimraf": "^6.1.2",
|
|
52
|
+
"tsup": "^8.5.1",
|
|
53
|
+
"typescript": "5.3.3",
|
|
54
|
+
"vitest": "^4.0.16"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { notificationEvents } from './notifications';
|
|
4
|
+
import { oauthConnectionEvents } from './oauth-connections';
|
|
5
|
+
import { isValidSemanticVersion } from './versioning';
|
|
6
|
+
import {
|
|
7
|
+
createEventEnvelopeSchema,
|
|
8
|
+
notificationRequestSchema,
|
|
9
|
+
oauthConnectionSchema,
|
|
10
|
+
tokenSetSchema,
|
|
11
|
+
} from './schemas';
|
|
12
|
+
|
|
13
|
+
describe('contracts validation', () => {
|
|
14
|
+
describe('notificationEvents examples', () => {
|
|
15
|
+
it('debe validar el ejemplo de requested event', () => {
|
|
16
|
+
const contract = notificationEvents.requested;
|
|
17
|
+
const example = contract.example;
|
|
18
|
+
|
|
19
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
20
|
+
expect(example.type).toBe('notifications.requested');
|
|
21
|
+
expect(contract.schema).toBe('notifications.requested@1.0.0');
|
|
22
|
+
expect(example.meta.source).toBeDefined();
|
|
23
|
+
|
|
24
|
+
// Type guard para acceder a las propiedades específicas
|
|
25
|
+
if ('notification' in example.payload) {
|
|
26
|
+
expect(example.payload.notification).toBeDefined();
|
|
27
|
+
expect(example.payload.notification.request.channel).toBe('email');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('debe validar el ejemplo de dispatched event', () => {
|
|
32
|
+
const example = notificationEvents.dispatched.example;
|
|
33
|
+
|
|
34
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
35
|
+
expect(example.type).toBe('notifications.dispatched');
|
|
36
|
+
|
|
37
|
+
if ('notificationId' in example.payload && 'channel' in example.payload && 'provider' in example.payload) {
|
|
38
|
+
expect(example.payload.notificationId).toBeDefined();
|
|
39
|
+
expect(example.payload.channel).toBeDefined();
|
|
40
|
+
expect(example.payload.provider).toBeDefined();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('debe validar el ejemplo de delivered event', () => {
|
|
45
|
+
const example = notificationEvents.delivered.example;
|
|
46
|
+
|
|
47
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
48
|
+
expect(example.type).toBe('notifications.delivered');
|
|
49
|
+
|
|
50
|
+
if ('notificationId' in example.payload && 'deliveredAt' in example.payload) {
|
|
51
|
+
expect(example.payload.notificationId).toBeDefined();
|
|
52
|
+
expect(example.payload.deliveredAt).toBeDefined();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('debe validar el ejemplo de failed event', () => {
|
|
57
|
+
const example = notificationEvents.failed.example;
|
|
58
|
+
|
|
59
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
60
|
+
expect(example.type).toBe('notifications.failed');
|
|
61
|
+
|
|
62
|
+
if ('notificationId' in example.payload && 'errorCode' in example.payload) {
|
|
63
|
+
expect(example.payload.notificationId).toBeDefined();
|
|
64
|
+
expect(example.payload.errorCode).toBeDefined();
|
|
65
|
+
expect(example.payload.retriable).toBeDefined();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('los ejemplos deben pasar validación con Zod', () => {
|
|
70
|
+
const requestedPayloadSchema = z.object({
|
|
71
|
+
notification: z.object({
|
|
72
|
+
id: z.string(),
|
|
73
|
+
request: notificationRequestSchema,
|
|
74
|
+
status: z.string(),
|
|
75
|
+
createdAt: z.string(),
|
|
76
|
+
updatedAt: z.string(),
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const requestedEnvelopeSchema = createEventEnvelopeSchema(requestedPayloadSchema);
|
|
81
|
+
|
|
82
|
+
const result = requestedEnvelopeSchema.safeParse(notificationEvents.requested.example);
|
|
83
|
+
expect(result.success).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('oauthConnectionEvents examples', () => {
|
|
88
|
+
it('debe validar el ejemplo de linked event', () => {
|
|
89
|
+
const example = oauthConnectionEvents.linked.example;
|
|
90
|
+
|
|
91
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
92
|
+
expect(example.type).toBe('oauth.connection.linked');
|
|
93
|
+
|
|
94
|
+
if ('connection' in example.payload && 'tokenSet' in example.payload) {
|
|
95
|
+
expect(example.payload.connection).toBeDefined();
|
|
96
|
+
expect(example.payload.tokenSet).toBeDefined();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('debe validar el ejemplo de tokenRefreshed event', () => {
|
|
101
|
+
const example = oauthConnectionEvents.tokenRefreshed.example;
|
|
102
|
+
|
|
103
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
104
|
+
expect(example.type).toBe('oauth.connection.token_refreshed');
|
|
105
|
+
|
|
106
|
+
if ('connectionId' in example.payload && 'tokenSet' in example.payload) {
|
|
107
|
+
expect(example.payload.connectionId).toBeDefined();
|
|
108
|
+
expect(example.payload.tokenSet).toBeDefined();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('debe validar el ejemplo de revoked event', () => {
|
|
113
|
+
const example = oauthConnectionEvents.revoked.example;
|
|
114
|
+
|
|
115
|
+
expect(isValidSemanticVersion(example.version)).toBe(true);
|
|
116
|
+
expect(example.type).toBe('oauth.connection.revoked');
|
|
117
|
+
|
|
118
|
+
if ('connectionId' in example.payload && 'reason' in example.payload) {
|
|
119
|
+
expect(example.payload.connectionId).toBeDefined();
|
|
120
|
+
expect(example.payload.reason).toBeDefined();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('los ejemplos deben pasar validación con Zod', () => {
|
|
125
|
+
const linkedPayloadSchema = z.object({
|
|
126
|
+
connection: oauthConnectionSchema,
|
|
127
|
+
tokenSet: tokenSetSchema,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const linkedEnvelopeSchema = createEventEnvelopeSchema(linkedPayloadSchema);
|
|
131
|
+
|
|
132
|
+
const result = linkedEnvelopeSchema.safeParse(oauthConnectionEvents.linked.example);
|
|
133
|
+
expect(result.success).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
NOTIFICATION_EVENT_TYPES,
|
|
4
|
+
OAUTH_CONNECTION_EVENT_TYPES,
|
|
5
|
+
type EventEnvelope,
|
|
6
|
+
type EventMeta,
|
|
7
|
+
} from './events';
|
|
8
|
+
|
|
9
|
+
describe('events', () => {
|
|
10
|
+
describe('constants', () => {
|
|
11
|
+
it('NOTIFICATION_EVENT_TYPES debe tener todos los tipos', () => {
|
|
12
|
+
expect(NOTIFICATION_EVENT_TYPES.REQUESTED).toBe('notifications.requested');
|
|
13
|
+
expect(NOTIFICATION_EVENT_TYPES.DISPATCHED).toBe('notifications.dispatched');
|
|
14
|
+
expect(NOTIFICATION_EVENT_TYPES.DELIVERED).toBe('notifications.delivered');
|
|
15
|
+
expect(NOTIFICATION_EVENT_TYPES.FAILED).toBe('notifications.failed');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('OAUTH_CONNECTION_EVENT_TYPES debe tener todos los tipos', () => {
|
|
19
|
+
expect(OAUTH_CONNECTION_EVENT_TYPES.LINKED).toBe('oauth.connection.linked');
|
|
20
|
+
expect(OAUTH_CONNECTION_EVENT_TYPES.TOKEN_REFRESHED).toBe('oauth.connection.token_refreshed');
|
|
21
|
+
expect(OAUTH_CONNECTION_EVENT_TYPES.REVOKED).toBe('oauth.connection.revoked');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('EventEnvelope', () => {
|
|
26
|
+
it('debe crear un envelope válido', () => {
|
|
27
|
+
const meta: EventMeta = {
|
|
28
|
+
source: 'test-service',
|
|
29
|
+
traceId: 'trace_123',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const envelope: EventEnvelope<{ data: string }> = {
|
|
33
|
+
id: 'evt_123',
|
|
34
|
+
type: 'test.event',
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
occurredAt: new Date().toISOString(),
|
|
37
|
+
payload: { data: 'test' },
|
|
38
|
+
meta,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
expect(envelope.id).toBe('evt_123');
|
|
42
|
+
expect(envelope.type).toBe('test.event');
|
|
43
|
+
expect(envelope.version).toBe('1.0.0');
|
|
44
|
+
expect(envelope.payload.data).toBe('test');
|
|
45
|
+
expect(envelope.meta.source).toBe('test-service');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SemanticVersion } from './versioning';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Constantes para tipos de eventos del sistema de notificaciones.
|
|
5
|
+
* Centraliza los nombres de eventos para evitar errores de tipeo.
|
|
6
|
+
*/
|
|
7
|
+
export const NOTIFICATION_EVENT_TYPES = {
|
|
8
|
+
REQUESTED: 'notifications.requested',
|
|
9
|
+
DISPATCHED: 'notifications.dispatched',
|
|
10
|
+
DELIVERED: 'notifications.delivered',
|
|
11
|
+
FAILED: 'notifications.failed',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Constantes para tipos de eventos de conexiones OAuth.
|
|
16
|
+
*/
|
|
17
|
+
export const OAUTH_CONNECTION_EVENT_TYPES = {
|
|
18
|
+
LINKED: 'oauth.connection.linked',
|
|
19
|
+
TOKEN_REFRESHED: 'oauth.connection.token_refreshed',
|
|
20
|
+
REVOKED: 'oauth.connection.revoked',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Metadata asociada a un evento para trazabilidad y correlación en sistemas distribuidos.
|
|
25
|
+
*
|
|
26
|
+
* @property traceId - ID de traza para distributed tracing (OpenTelemetry, etc.)
|
|
27
|
+
* @property spanId - ID de span dentro de la traza
|
|
28
|
+
* @property correlationId - ID para correlacionar eventos relacionados en un flujo de negocio
|
|
29
|
+
* @property causationId - ID del evento que causó este evento (event sourcing)
|
|
30
|
+
* @property tenantId - ID del tenant en arquitecturas multi-tenant
|
|
31
|
+
* @property projectId - ID del proyecto asociado
|
|
32
|
+
* @property source - Nombre del servicio o componente que originó el evento
|
|
33
|
+
*/
|
|
34
|
+
export type EventMeta = {
|
|
35
|
+
traceId?: string;
|
|
36
|
+
spanId?: string;
|
|
37
|
+
correlationId?: string;
|
|
38
|
+
causationId?: string;
|
|
39
|
+
tenantId?: string;
|
|
40
|
+
projectId?: string;
|
|
41
|
+
source: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Envelope que envuelve un evento con toda su metadata y información de versionado.
|
|
46
|
+
* Esta estructura permite la serialización, transmisión y procesamiento de eventos
|
|
47
|
+
* en sistemas distribuidos con garantías de trazabilidad.
|
|
48
|
+
*
|
|
49
|
+
* @template TPayload - Tipo del payload específico del evento
|
|
50
|
+
* @template TMeta - Tipo de metadata (por defecto EventMeta)
|
|
51
|
+
*
|
|
52
|
+
* @property id - Identificador único del evento (recomendado: UUID v4)
|
|
53
|
+
* @property type - Tipo del evento (ej: 'notifications.requested')
|
|
54
|
+
* @property version - Versión semántica del contrato del evento
|
|
55
|
+
* @property occurredAt - Timestamp ISO 8601 de cuándo ocurrió el evento
|
|
56
|
+
* @property payload - Datos específicos del evento
|
|
57
|
+
* @property meta - Metadata de trazabilidad y contexto
|
|
58
|
+
*/
|
|
59
|
+
export type EventEnvelope<TPayload, TMeta extends EventMeta = EventMeta> = {
|
|
60
|
+
id: string;
|
|
61
|
+
type: string;
|
|
62
|
+
version: SemanticVersion;
|
|
63
|
+
occurredAt: string;
|
|
64
|
+
payload: TPayload;
|
|
65
|
+
meta: TMeta;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Contrato que define la estructura y versión de un tipo de evento.
|
|
70
|
+
* Incluye un ejemplo para documentación y validación.
|
|
71
|
+
*
|
|
72
|
+
* @template TPayload - Tipo del payload del evento
|
|
73
|
+
* @template TMeta - Tipo de metadata del evento
|
|
74
|
+
*
|
|
75
|
+
* @property type - Tipo del evento
|
|
76
|
+
* @property version - Versión semántica del contrato
|
|
77
|
+
* @property schema - Identificador del esquema (formato: tipo@version)
|
|
78
|
+
* @property example - Ejemplo completo de un evento de este tipo
|
|
79
|
+
*/
|
|
80
|
+
export type EventContract<TPayload, TMeta extends EventMeta = EventMeta> = {
|
|
81
|
+
type: string;
|
|
82
|
+
version: SemanticVersion;
|
|
83
|
+
schema: string;
|
|
84
|
+
example: EventEnvelope<TPayload, TMeta>;
|
|
85
|
+
};
|