jerkjs 2.2.0 → 2.3.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,955 @@
1
+ # Manual de Extensión del Framework API SDK
2
+
3
+ ## Tabla de Contenidos
4
+
5
+ 1. [Introducción a la Extensión del Framework](#introducción-a-la-extensión-del-framework)
6
+ 2. [Arquitectura de Extensibilidad](#arquitectura-de-extensibilidad)
7
+ 3. [Patrones de Extensión Comunes](#patrones-de-extensión-comunes)
8
+ 4. [Ejemplo Práctico: Extensión para SQLite](#ejemplo-práctico-extensión-para-sqlite)
9
+ 5. [Guía de Implementación](#guía-de-implementación)
10
+ 6. [Pruebas y Validación](#pruebas-y-validación)
11
+ 7. [Integración con el Framework](#integración-con-el-framework)
12
+ 8. [Documentación y Distribución](#documentación-y-distribución)
13
+
14
+ ## Introducción a la Extensión del Framework
15
+
16
+ El API SDK Framework está diseñado con una arquitectura modular y extensible que permite a los desarrolladores crear extensiones personalizadas para satisfacer necesidades específicas. La extensibilidad es un principio fundamental del framework que permite:
17
+
18
+ - **Personalización**: Adaptar el framework a necesidades específicas
19
+ - **Integración**: Conectar con sistemas y tecnologías externas
20
+ - **Reutilización**: Crear componentes reutilizables
21
+ - **Mantenibilidad**: Aislar funcionalidades en módulos independientes
22
+
23
+ ## Arquitectura de Extensibilidad
24
+
25
+ El framework proporciona varias interfaces y patrones para la extensión:
26
+
27
+ ### 1. Patrón de Adaptador
28
+ El framework utiliza el patrón de adaptador para permitir diferentes implementaciones de servicios comunes como el almacenamiento de tokens.
29
+
30
+ ### 2. Middleware
31
+ Los componentes pueden extenderse mediante middleware que se inserta en el pipeline de procesamiento.
32
+
33
+ ### 3. Estrategias de Autenticación
34
+ El sistema de autenticación permite registrar nuevas estrategias personalizadas.
35
+
36
+ ### 4. Sistemas de Carga
37
+ Los loaders permiten extender la funcionalidad de carga de rutas y controladores.
38
+
39
+ ## Patrones de Extensión Comunes
40
+
41
+ ### 1. Adaptador de Almacenamiento
42
+ Patrón utilizado para diferentes sistemas de almacenamiento (JSON, MariaDB, etc.)
43
+
44
+ ### 2. Middleware Personalizado
45
+ Extensión de funcionalidad a través de middleware
46
+
47
+ ### 3. Estrategias de Autenticación
48
+ Nuevas formas de autenticar usuarios
49
+
50
+ ### 4. Sistemas de Logging Personalizados
51
+ Adaptadores para diferentes sistemas de logging
52
+
53
+ ## Ejemplo Práctico: Extensión para SQLite
54
+
55
+ Vamos a crear una extensión completa para almacenar tokens en SQLite, siguiendo los mismos patrones que el adaptador de MariaDB.
56
+
57
+ ### 1. Crear el Adaptador de SQLite
58
+
59
+ Primero, crearemos el archivo para el adaptador de SQLite:
60
+
61
+ ```javascript
62
+ // lib/utils/sqliteTokenAdapter.js
63
+ const sqlite3 = require('sqlite3').verbose();
64
+ const path = require('path');
65
+
66
+ class SQLiteTokenAdapter {
67
+ /**
68
+ * Constructor del adaptador
69
+ * @param {Object} config - Configuración de conexión
70
+ * @param {string} config.dbPath - Ruta a la base de datos SQLite
71
+ * @param {string} config.tableName - Nombre de la tabla de tokens
72
+ */
73
+ constructor(config) {
74
+ this.dbPath = config.dbPath || './tokens.sqlite';
75
+ this.tableName = config.tableName || 'tokens';
76
+ this.db = null;
77
+ }
78
+
79
+ /**
80
+ * Inicializa la conexión y la tabla de tokens
81
+ */
82
+ async initialize() {
83
+ return new Promise((resolve, reject) => {
84
+ this.db = new sqlite3.Database(this.dbPath, (err) => {
85
+ if (err) {
86
+ console.error('Error conectando a SQLite:', err.message);
87
+ reject(err);
88
+ return;
89
+ }
90
+
91
+ console.log(`✅ Conexión a SQLite establecida: ${this.dbPath}`);
92
+
93
+ // Crear tabla de tokens si no existe
94
+ this.initializeTable()
95
+ .then(() => {
96
+ console.log(`✅ Tabla ${this.tableName} inicializada correctamente`);
97
+ resolve();
98
+ })
99
+ .catch(reject);
100
+ });
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Inicializa la tabla de tokens en la base de datos
106
+ */
107
+ async initializeTable() {
108
+ const createTableQuery = `
109
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
110
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
111
+ token TEXT UNIQUE NOT NULL,
112
+ user_id INTEGER NOT NULL,
113
+ token_type TEXT DEFAULT 'access',
114
+ expires_at DATETIME NOT NULL,
115
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
116
+ revoked BOOLEAN DEFAULT 0,
117
+ INDEX_idx_user_id (user_id),
118
+ INDEX_idx_token (token),
119
+ INDEX_idx_expires_at (expires_at)
120
+ )
121
+ `;
122
+
123
+ return new Promise((resolve, reject) => {
124
+ this.db.run(createTableQuery, (err) => {
125
+ if (err) {
126
+ console.error('Error inicializando tabla de tokens:', err.message);
127
+ reject(err);
128
+ } else {
129
+ resolve();
130
+ }
131
+ });
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Guarda un token en la base de datos
137
+ * @param {string} token - Token a guardar
138
+ * @param {Object} userData - Datos del usuario
139
+ * @param {string} tokenType - Tipo de token ('access' o 'refresh')
140
+ * @param {Date} expiresAt - Fecha de expiración
141
+ */
142
+ async saveToken(token, userData, tokenType = 'access', expiresAt) {
143
+ const insertQuery = `
144
+ INSERT INTO ${this.tableName} (token, user_id, token_type, expires_at)
145
+ VALUES (?, ?, ?, ?)
146
+ `;
147
+
148
+ return new Promise((resolve, reject) => {
149
+ this.db.run(insertQuery, [
150
+ token,
151
+ userData.userId || userData.id,
152
+ tokenType,
153
+ expiresAt.toISOString()
154
+ ], function(err) {
155
+ if (err) {
156
+ console.error('Error guardando token:', err.message);
157
+ reject(err);
158
+ } else {
159
+ console.log(`✅ Token ${tokenType} guardado para usuario ${userData.userId || userData.id}`);
160
+ resolve();
161
+ }
162
+ });
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Verifica si un token existe y es válido
168
+ * @param {string} token - Token a verificar
169
+ * @returns {Object|null} - Datos del token o null si no es válido
170
+ */
171
+ async validateToken(token) {
172
+ const selectQuery = `
173
+ SELECT * FROM ${this.tableName}
174
+ WHERE token = ? AND revoked = 0 AND expires_at > datetime('now')
175
+ `;
176
+
177
+ return new Promise((resolve, reject) => {
178
+ this.db.get(selectQuery, [token], (err, row) => {
179
+ if (err) {
180
+ console.error('Error validando token:', err.message);
181
+ reject(err);
182
+ } else {
183
+ resolve(row || null);
184
+ }
185
+ });
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Revoca un token
191
+ * @param {string} token - Token a revocar
192
+ * @returns {boolean} - True si se revocó exitosamente
193
+ */
194
+ async revokeToken(token) {
195
+ const updateQuery = `
196
+ UPDATE ${this.tableName}
197
+ SET revoked = 1
198
+ WHERE token = ?
199
+ `;
200
+
201
+ return new Promise((resolve, reject) => {
202
+ this.db.run(updateQuery, [token], function(err) {
203
+ if (err) {
204
+ console.error('Error revocando token:', err.message);
205
+ reject(err);
206
+ } else {
207
+ const revoked = this.changes > 0;
208
+ if (revoked) {
209
+ console.log(`✅ Token revocado: ${token.substring(0, 20)}...`);
210
+ } else {
211
+ console.log(`⚠️ Token no encontrado o ya revocado: ${token.substring(0, 20)}...`);
212
+ }
213
+ resolve(revoked);
214
+ }
215
+ });
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Obtiene tokens de un usuario
221
+ * @param {number} userId - ID del usuario
222
+ * @returns {Array} - Array de tokens del usuario
223
+ */
224
+ async getUserTokens(userId) {
225
+ const selectQuery = `
226
+ SELECT * FROM ${this.tableName}
227
+ WHERE user_id = ? AND revoked = 0 AND expires_at > datetime('now')
228
+ `;
229
+
230
+ return new Promise((resolve, reject) => {
231
+ this.db.all(selectQuery, [userId], (err, rows) => {
232
+ if (err) {
233
+ console.error('Error obteniendo tokens de usuario:', err.message);
234
+ reject(err);
235
+ } else {
236
+ resolve(rows || []);
237
+ }
238
+ });
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Limpia tokens expirados
244
+ */
245
+ async cleanupExpiredTokens() {
246
+ const deleteQuery = `
247
+ DELETE FROM ${this.tableName}
248
+ WHERE expires_at <= datetime('now')
249
+ `;
250
+
251
+ return new Promise((resolve, reject) => {
252
+ this.db.run(deleteQuery, function(err) {
253
+ if (err) {
254
+ console.error('Error limpiando tokens expirados:', err.message);
255
+ reject(err);
256
+ } else {
257
+ console.log(`✅ Eliminados ${this.changes} tokens expirados`);
258
+ resolve(this.changes);
259
+ }
260
+ });
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Cierra la conexión a la base de datos
266
+ */
267
+ async close() {
268
+ if (this.db) {
269
+ return new Promise((resolve, reject) => {
270
+ this.db.close((err) => {
271
+ if (err) {
272
+ console.error('Error cerrando conexión a SQLite:', err.message);
273
+ reject(err);
274
+ } else {
275
+ console.log('🔒 Conexión a SQLite cerrada');
276
+ resolve();
277
+ }
278
+ });
279
+ });
280
+ }
281
+ }
282
+ }
283
+
284
+ module.exports = SQLiteTokenAdapter;
285
+ ```
286
+
287
+ ### 2. Instalar la dependencia de SQLite
288
+
289
+ Para usar SQLite, necesitamos instalar la dependencia:
290
+
291
+ ```bash
292
+ npm install sqlite3
293
+ ```
294
+
295
+ ### 3. Crear un ejemplo de uso
296
+
297
+ Ahora crearemos un ejemplo que demuestra cómo usar el adaptador de SQLite:
298
+
299
+ ```javascript
300
+ // examples/v2/sqlite_tokens_example.js
301
+ const { APISDK, Authenticator, Logger } = require('../../index');
302
+ const jwt = require('jsonwebtoken');
303
+ const SQLiteTokenAdapter = require('../../lib/utils/sqliteTokenAdapter');
304
+
305
+ // Crear instancia del logger
306
+ const logger = new Logger({ level: 'info', timestamp: true });
307
+
308
+ logger.info('🔐 Iniciando ejemplo con tokens en SQLite');
309
+
310
+ // Configuración de conexión a SQLite
311
+ const dbConfig = {
312
+ dbPath: './tokens.sqlite', // Ruta a la base de datos SQLite
313
+ tableName: 'tokens'
314
+ };
315
+
316
+ // Crear instancia del adaptador de tokens
317
+ const tokenAdapter = new SQLiteTokenAdapter(dbConfig);
318
+
319
+ // Inicializar la conexión y tabla
320
+ tokenAdapter.initialize()
321
+ .then(async () => {
322
+ logger.info('✅ Conexión a SQLite establecida');
323
+
324
+ // Crear instancia del servidor
325
+ const server = new APISDK({
326
+ port: 8083,
327
+ host: 'localhost'
328
+ });
329
+
330
+ // Crear instancia del autenticador
331
+ const authenticator = new Authenticator({ logger: logger });
332
+
333
+ // Secretos para tokens
334
+ const jwtSecret = 'sqlite-jwt-secret-key';
335
+ const refreshSecret = 'sqlite-refresh-secret-key';
336
+
337
+ // Estrategia de autenticación con tokens en SQLite
338
+ authenticator.use('sqliteJwt', async (req, options = {}) => {
339
+ const authHeader = req.headers.authorization;
340
+ const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
341
+
342
+ if (!token) {
343
+ return false;
344
+ }
345
+
346
+ try {
347
+ // Primero verificar si el token es válido en la base de datos
348
+ const tokenRecord = await tokenAdapter.validateToken(token);
349
+
350
+ if (!tokenRecord) {
351
+ return false;
352
+ }
353
+
354
+ // Luego verificar si el token JWT es válido
355
+ const decoded = jwt.verify(token, jwtSecret);
356
+
357
+ // Agregar el payload decodificado a la solicitud
358
+ req.user = decoded;
359
+ return true;
360
+ } catch (error) {
361
+ if (error.name === 'TokenExpiredError') {
362
+ // Token expirado, verificar si hay refresh token
363
+ const refreshToken = req.headers['x-refresh-token'];
364
+ if (refreshToken) {
365
+ const refreshRecord = await tokenAdapter.validateToken(refreshToken);
366
+ if (refreshRecord && refreshRecord.token_type === 'refresh') {
367
+ try {
368
+ // Decodificar el refresh token para obtener los datos del usuario
369
+ const refreshDecoded = jwt.verify(refreshToken, refreshSecret);
370
+
371
+ // Generar nuevo token de acceso
372
+ const newAccessToken = jwt.sign(
373
+ {
374
+ userId: refreshDecoded.userId || refreshDecoded.id,
375
+ username: refreshDecoded.username,
376
+ role: refreshDecoded.role,
377
+ tokenType: 'access'
378
+ },
379
+ jwtSecret,
380
+ { expiresIn: '15m' }
381
+ );
382
+
383
+ // Guardar nuevo token de acceso en la base de datos
384
+ const accessExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutos
385
+ await tokenAdapter.saveToken(
386
+ newAccessToken,
387
+ refreshDecoded,
388
+ 'access',
389
+ accessExpiresAt
390
+ );
391
+
392
+ // Agregar nuevo token a la respuesta para que el cliente lo actualice
393
+ if (req.res) {
394
+ req.res.setHeader('X-New-Access-Token', newAccessToken);
395
+ }
396
+
397
+ // Agregar el payload decodificado a la solicitud
398
+ req.user = refreshDecoded;
399
+ return true;
400
+ } catch (verifyError) {
401
+ return false;
402
+ }
403
+ } else {
404
+ return false;
405
+ }
406
+ } else {
407
+ return false;
408
+ }
409
+ } else {
410
+ return false;
411
+ }
412
+ }
413
+ });
414
+
415
+ // Middleware para adjuntar la respuesta al request para headers
416
+ server.use((req, res, next) => {
417
+ req.res = res;
418
+ next();
419
+ });
420
+
421
+ // Simulación de base de datos de usuarios
422
+ const users = [
423
+ { id: 1, username: 'sqlite_admin', password: 'password', role: 'admin' },
424
+ { id: 2, username: 'sqlite_user', password: 'password', role: 'user' }
425
+ ];
426
+
427
+ // Ruta de login para generar tokens en SQLite
428
+ server.addRoute('POST', '/api/login', async (req, res) => {
429
+ try {
430
+ const { username, password } = req.body;
431
+
432
+ // Validar credenciales
433
+ const user = users.find(u => u.username === username && u.password === password);
434
+
435
+ if (!user) {
436
+ res.writeHead(401, { 'Content-Type': 'application/json' });
437
+ res.end(JSON.stringify({
438
+ success: false,
439
+ message: 'Credenciales inválidas'
440
+ }));
441
+ return;
442
+ }
443
+
444
+ // Generar tokens JWT
445
+ const accessToken = jwt.sign(
446
+ {
447
+ userId: user.id,
448
+ username: user.username,
449
+ role: user.role,
450
+ tokenType: 'access'
451
+ },
452
+ jwtSecret,
453
+ { expiresIn: '15m' }
454
+ );
455
+
456
+ const refreshToken = jwt.sign(
457
+ {
458
+ userId: user.id,
459
+ username: user.username,
460
+ role: user.role,
461
+ tokenType: 'refresh'
462
+ },
463
+ refreshSecret,
464
+ { expiresIn: '7d' }
465
+ );
466
+
467
+ // Guardar tokens en la base de datos SQLite
468
+ const accessExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutos
469
+ const refreshExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 días
470
+
471
+ await tokenAdapter.saveToken(accessToken, user, 'access', accessExpiresAt);
472
+ await tokenAdapter.saveToken(refreshToken, user, 'refresh', refreshExpiresAt);
473
+
474
+ res.writeHead(200, { 'Content-Type': 'application/json' });
475
+ res.end(JSON.stringify({
476
+ success: true,
477
+ message: 'Login exitoso',
478
+ tokens: {
479
+ accessToken,
480
+ refreshToken
481
+ },
482
+ user: {
483
+ id: user.id,
484
+ username: user.username,
485
+ role: user.role
486
+ }
487
+ }));
488
+ } catch (error) {
489
+ logger.error('Error en login:', error.message);
490
+ res.writeHead(500, { 'Content-Type': 'application/json' });
491
+ res.end(JSON.stringify({
492
+ success: false,
493
+ message: 'Error en el proceso de login'
494
+ }));
495
+ }
496
+ });
497
+
498
+ // Ruta para renovar tokens
499
+ server.addRoute('POST', '/api/refresh', async (req, res) => {
500
+ try {
501
+ const refreshToken = req.headers['x-refresh-token'];
502
+
503
+ if (!refreshToken) {
504
+ res.writeHead(400, { 'Content-Type': 'application/json' });
505
+ res.end(JSON.stringify({
506
+ success: false,
507
+ message: 'Refresh token requerido'
508
+ }));
509
+ return;
510
+ }
511
+
512
+ // Verificar si el refresh token es válido en la base de datos
513
+ const tokenRecord = await tokenAdapter.validateToken(refreshToken);
514
+
515
+ if (!tokenRecord || tokenRecord.token_type !== 'refresh') {
516
+ res.writeHead(401, { 'Content-Type': 'application/json' });
517
+ res.end(JSON.stringify({
518
+ success: false,
519
+ message: 'Refresh token inválido o expirado'
520
+ }));
521
+ return;
522
+ }
523
+
524
+ try {
525
+ // Decodificar el refresh token para obtener los datos del usuario
526
+ const decoded = jwt.verify(refreshToken, refreshSecret);
527
+
528
+ // Generar nuevo token de acceso
529
+ const newAccessToken = jwt.sign(
530
+ {
531
+ userId: decoded.userId || decoded.id,
532
+ username: decoded.username,
533
+ role: decoded.role,
534
+ tokenType: 'access'
535
+ },
536
+ jwtSecret,
537
+ { expiresIn: '15m' }
538
+ );
539
+
540
+ // Guardar nuevo token de acceso en la base de datos
541
+ const accessExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutos
542
+ await tokenAdapter.saveToken(newAccessToken, decoded, 'access', accessExpiresAt);
543
+
544
+ res.writeHead(200, { 'Content-Type': 'application/json' });
545
+ res.end(JSON.stringify({
546
+ success: true,
547
+ message: 'Tokens renovados exitosamente',
548
+ tokens: {
549
+ accessToken: newAccessToken,
550
+ refreshToken: refreshToken // Devolver el mismo refresh token
551
+ }
552
+ }));
553
+ } catch (verifyError) {
554
+ res.writeHead(401, { 'Content-Type': 'application/json' });
555
+ res.end(JSON.stringify({
556
+ success: false,
557
+ message: 'Refresh token inválido'
558
+ }));
559
+ }
560
+ } catch (error) {
561
+ logger.error('Error al renovar token:', error.message);
562
+ res.writeHead(500, { 'Content-Type': 'application/json' });
563
+ res.end(JSON.stringify({
564
+ success: false,
565
+ message: 'Error al renovar token'
566
+ }));
567
+ }
568
+ });
569
+
570
+ // Ruta protegida con tokens de SQLite
571
+ server.addRoute('GET', '/api/data', authenticator.authenticate('sqliteJwt'), (req, res) => {
572
+ res.writeHead(200, { 'Content-Type': 'application/json' });
573
+ res.end(JSON.stringify({
574
+ success: true,
575
+ message: 'Datos protegidos accesados con token de SQLite',
576
+ user: req.user,
577
+ data: {
578
+ id: 1,
579
+ title: 'Datos de SQLite',
580
+ content: 'Este contenido está protegido por tokens almacenados en SQLite',
581
+ timestamp: new Date().toISOString()
582
+ }
583
+ }));
584
+ });
585
+
586
+ // Ruta de perfil protegida
587
+ server.addRoute('GET', '/api/profile', authenticator.authenticate('sqliteJwt'), (req, res) => {
588
+ res.writeHead(200, { 'Content-Type': 'application/json' });
589
+ res.end(JSON.stringify({
590
+ success: true,
591
+ message: 'Perfil obtenido con token de SQLite',
592
+ user: req.user
593
+ }));
594
+ });
595
+
596
+ // Middleware de logging
597
+ server.use((req, res, next) => {
598
+ logger.info(`${req.method} ${req.url} - IP: ${req.connection.remoteAddress}`);
599
+ next();
600
+ });
601
+
602
+ logger.info('✅ Rutas configuradas con tokens en SQLite');
603
+
604
+ // Iniciar el servidor
605
+ const httpServer = server.start();
606
+
607
+ logger.info('✅ Servidor iniciado en http://localhost:8083');
608
+ logger.info('📋 Endpoints disponibles:');
609
+ logger.info(' POST /api/login - Login para tokens en SQLite');
610
+ logger.info(' POST /api/refresh - Renovar tokens');
611
+ logger.info(' GET /api/data - Datos (JWT req)');
612
+ logger.info(' GET /api/profile - Perfil (JWT req)');
613
+
614
+ logger.info('\n🔧 Comandos de prueba con curl:');
615
+ logger.info(' # Login para obtener tokens en SQLite:');
616
+ logger.info(' curl -X POST http://localhost:8083/api/login \\');
617
+ logger.info(' -H "Content-Type: application/json" \\');
618
+ logger.info(' -d \'{"username":"sqlite_admin", "password":"password"}\'');
619
+ logger.info('');
620
+ logger.info(' # Acceder a datos protegidos (reemplaza con tu JWT):');
621
+ logger.info(' curl -H "Authorization: Bearer TU_JWT_TOKEN_AQUI" http://localhost:8083/api/data');
622
+ logger.info('');
623
+ logger.info(' # Renovar tokens (reemplaza con tu refresh token):');
624
+ logger.info(' curl -X POST http://localhost:8083/api/refresh \\');
625
+ logger.info(' -H "X-Refresh-Token: TU_REFRESH_TOKEN_AQUI"');
626
+
627
+ // Manejo de cierre
628
+ const gracefulShutdown = async () => {
629
+ logger.info('🛑 Cerrando servidor...');
630
+ httpServer.close(() => {
631
+ logger.info('🔌 Servidor detenido');
632
+
633
+ // Cerrar conexión a SQLite
634
+ tokenAdapter.close()
635
+ .then(() => {
636
+ logger.info('🔒 Conexión a SQLite cerrada');
637
+ process.exit(0);
638
+ })
639
+ .catch(err => {
640
+ console.error('Error cerrando conexión a SQLite:', err.message);
641
+ process.exit(1);
642
+ });
643
+ });
644
+ };
645
+
646
+ process.on('SIGTERM', gracefulShutdown);
647
+ process.on('SIGINT', gracefulShutdown);
648
+
649
+ })
650
+ .catch(error => {
651
+ logger.error('❌ Error inicializando adaptador de tokens:', error.message);
652
+ process.exit(1);
653
+ });
654
+ ```
655
+
656
+ ### 4. Actualizar el package.json
657
+
658
+ Agregamos la dependencia de SQLite al package.json:
659
+
660
+ ```json
661
+ {
662
+ "name": "apisdk",
663
+ "version": "1.0.0",
664
+ "description": "Framework para agilizar la creación de APIs",
665
+ "main": "index.js",
666
+ "scripts": {
667
+ "start": "node examples/basic/server.js",
668
+ "test": "echo \"Error: no test specified\" && exit 1",
669
+ "build": "mkdir -p dist && cp -r lib index.js package.json README.md examples dist/",
670
+ "example:v2:security": "node examples/v2/advanced_security_example.js",
671
+ "example:v2:nested": "node examples/v2/nested_routes_example.js",
672
+ "example:v2:full": "node examples/v2/full_features_example.js",
673
+ "example:v2:auth": "node examples/v2/advanced_auth_example.js",
674
+ "example:v2:routes": "node examples/v2/routes_json_example.js",
675
+ "example:v2:sqlite": "node examples/v2/sqlite_tokens_example.js"
676
+ },
677
+ "keywords": [
678
+ "api",
679
+ "framework",
680
+ "server",
681
+ "routing"
682
+ ],
683
+ "author": "",
684
+ "license": "MIT",
685
+ "dependencies": {
686
+ "jsonwebtoken": "^9.0.0",
687
+ "sqlite3": "^5.1.6"
688
+ }
689
+ }
690
+ ```
691
+
692
+ ### 5. Actualizar el index.js para exportar el nuevo adaptador
693
+
694
+ ```javascript
695
+ // Actualizar index.js para incluir el adaptador de SQLite
696
+ const APISDK = require('./lib/core/server');
697
+ const Router = require('./lib/core/router');
698
+ const HandlerManager = require('./lib/core/handler');
699
+ const Authenticator = require('./lib/middleware/authenticator');
700
+ const Validator = require('./lib/middleware/validator');
701
+ const RouteLoader = require('./lib/loader/routeLoader');
702
+ const ControllerLoader = require('./lib/loader/controllerLoader');
703
+ const ConfigParser = require('./lib/utils/configParser');
704
+ const { Logger } = require('./lib/utils/logger');
705
+
706
+ // Componentes adicionales de la versión 2.0
707
+ const Cors = require('./lib/middleware/cors');
708
+ const RateLimiter = require('./lib/middleware/rateLimiter');
709
+ const Compressor = require('./lib/middleware/compressor');
710
+ const TokenManager = require('./lib/utils/tokenManager');
711
+ const MariaDBTokenAdapter = require('./lib/utils/mariadbTokenAdapter');
712
+ const SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter'); // Nuevo adaptador
713
+
714
+ module.exports = {
715
+ // Componentes fundamentales (v1.0)
716
+ APISDK,
717
+ Router,
718
+ HandlerManager,
719
+ Authenticator,
720
+ Validator,
721
+ RouteLoader,
722
+ ControllerLoader,
723
+ ConfigParser,
724
+ Logger,
725
+
726
+ // Componentes de seguridad y rendimiento (v2.0)
727
+ Cors,
728
+ RateLimiter,
729
+ Compressor,
730
+
731
+ // Componentes de utilidad (v2.0)
732
+ TokenManager,
733
+ MariaDBTokenAdapter,
734
+ SQLiteTokenAdapter // Exportar el nuevo adaptador
735
+ };
736
+
737
+ // También exportar clases individuales por conveniencia
738
+ module.exports.APISDK = APIServer;
739
+ ```
740
+
741
+ ## Guía de Implementación
742
+
743
+ ### Paso 1: Analizar los Requisitos
744
+
745
+ Antes de crear cualquier extensión, es importante:
746
+
747
+ 1. **Definir el propósito**: ¿Qué problema resolverá la extensión?
748
+ 2. **Identificar los puntos de integración**: ¿Dónde se integrará con el framework?
749
+ 3. **Definir la interfaz**: ¿Qué métodos/propiedades debe implementar?
750
+ 4. **Considerar la compatibilidad**: ¿Cómo se integrará con versiones existentes?
751
+
752
+ ### Paso 2: Implementar el Patrón de Adaptador
753
+
754
+ El patrón de adaptador es clave para la extensibilidad:
755
+
756
+ ```javascript
757
+ // Plantilla para nuevos adaptadores
758
+ class NuevoAdaptador {
759
+ constructor(config) {
760
+ // Inicializar con configuración
761
+ }
762
+
763
+ async initialize() {
764
+ // Lógica de inicialización
765
+ }
766
+
767
+ // Métodos comunes que deben implementarse
768
+ async save(data) { /* ... */ }
769
+ async get(id) { /* ... */ }
770
+ async validate(data) { /* ... */ }
771
+ async close() { /* ... */ }
772
+ }
773
+ ```
774
+
775
+ ### Paso 3: Asegurar la Consistencia de la API
776
+
777
+ Mantener una API consistente con otros componentes del framework:
778
+
779
+ - Usar los mismos patrones de nomenclatura
780
+ - Mantener la misma estructura de callbacks/promesas
781
+ - Seguir las mismas convenciones de manejo de errores
782
+ - Proporcionar mensajes de log consistentes
783
+
784
+ ### Paso 4: Implementar la Lógica de Negocio
785
+
786
+ Implementar la funcionalidad específica del nuevo sistema:
787
+
788
+ - Conexión y desconexión
789
+ - Operaciones CRUD básicas
790
+ - Manejo de errores específicos del sistema
791
+ - Optimizaciones de rendimiento
792
+
793
+ ### Paso 5: Probar la Integración
794
+
795
+ Verificar que la extensión funcione correctamente con el framework:
796
+
797
+ - Pruebas unitarias de los métodos del adaptador
798
+ - Pruebas de integración con el sistema de autenticación
799
+ - Pruebas de rendimiento y seguridad
800
+ - Pruebas de compatibilidad con diferentes versiones
801
+
802
+ ## Pruebas y Validación
803
+
804
+ ### Pruebas Unitarias
805
+
806
+ ```javascript
807
+ // test_sqlite_adapter.js
808
+ const assert = require('assert');
809
+ const SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter');
810
+
811
+ describe('SQLiteTokenAdapter', () => {
812
+ let adapter;
813
+
814
+ beforeEach(async () => {
815
+ adapter = new SQLiteTokenAdapter({ dbPath: ':memory:' }); // Usar memoria para pruebas
816
+ await adapter.initialize();
817
+ });
818
+
819
+ afterEach(async () => {
820
+ await adapter.close();
821
+ });
822
+
823
+ it('debería guardar y validar tokens correctamente', async () => {
824
+ const token = 'test-token';
825
+ const userData = { userId: 1, username: 'testuser' };
826
+ const expiresAt = new Date(Date.now() + 3600000); // 1 hora
827
+
828
+ await adapter.saveToken(token, userData, 'access', expiresAt);
829
+ const result = await adapter.validateToken(token);
830
+
831
+ assert.ok(result);
832
+ assert.equal(result.user_id, 1);
833
+ assert.equal(result.token_type, 'access');
834
+ });
835
+
836
+ it('debería revocar tokens correctamente', async () => {
837
+ const token = 'test-token-revoke';
838
+ const userData = { userId: 1, username: 'testuser' };
839
+ const expiresAt = new Date(Date.now() + 3600000);
840
+
841
+ await adapter.saveToken(token, userData, 'access', expiresAt);
842
+ const revoked = await adapter.revokeToken(token);
843
+
844
+ assert.ok(revoked);
845
+
846
+ const validated = await adapter.validateToken(token);
847
+ assert.ok(!validated);
848
+ });
849
+ });
850
+ ```
851
+
852
+ ### Pruebas de Integración
853
+
854
+ ```javascript
855
+ // test_integration.js
856
+ const { APISDK, Authenticator } = require('./index');
857
+ const SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter');
858
+
859
+ async function testIntegration() {
860
+ const adapter = new SQLiteTokenAdapter({ dbPath: './integration_test.sqlite' });
861
+ await adapter.initialize();
862
+
863
+ const server = new APISDK({ port: 9999 });
864
+ const authenticator = new Authenticator();
865
+
866
+ // Registrar estrategia con SQLite
867
+ authenticator.use('sqliteAuth', async (req) => {
868
+ // Implementación de autenticación con SQLite
869
+ });
870
+
871
+ // Agregar rutas y probar la funcionalidad
872
+ // ...
873
+ }
874
+ ```
875
+
876
+ ## Integración con el Framework
877
+
878
+ ### 1. Registro de Componentes
879
+
880
+ Asegurar que la extensión esté disponible a través del punto de entrada:
881
+
882
+ ```javascript
883
+ // index.js - Ya implementado arriba
884
+ module.exports.SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter');
885
+ ```
886
+
887
+ ### 2. Documentación de la API
888
+
889
+ Proporcionar documentación clara sobre cómo usar la extensión:
890
+
891
+ ```javascript
892
+ /**
893
+ * Uso del adaptador de SQLite
894
+ */
895
+ const { APISDK, Authenticator, SQLiteTokenAdapter } = require('apisdk');
896
+
897
+ const adapter = new SQLiteTokenAdapter({
898
+ dbPath: './tokens.sqlite',
899
+ tableName: 'tokens'
900
+ });
901
+
902
+ await adapter.initialize();
903
+
904
+ // Usar en autenticación
905
+ authenticator.use('sqliteJwt', (req) => {
906
+ // Lógica de autenticación con SQLite
907
+ });
908
+ ```
909
+
910
+ ### 3. Ejemplos de Uso
911
+
912
+ Proporcionar ejemplos completos que demuestren el uso de la extensión.
913
+
914
+ ## Documentación y Distribución
915
+
916
+ ### 1. Documentación del API
917
+
918
+ Crear documentación detallada de todos los métodos y opciones disponibles.
919
+
920
+ ### 2. Guía de Instalación
921
+
922
+ Explicar cómo instalar y configurar la extensión.
923
+
924
+ ### 3. Ejemplos Prácticos
925
+
926
+ Mostrar casos de uso reales y escenarios comunes.
927
+
928
+ ### 4. Pruebas y Validación
929
+
930
+ Incluir pruebas que demuestren la funcionalidad y rendimiento.
931
+
932
+ ## Consideraciones de Seguridad
933
+
934
+ Al crear extensiones, especialmente para almacenamiento de datos sensibles como tokens:
935
+
936
+ 1. **Validación de Entrada**: Validar todos los datos antes de almacenarlos
937
+ 2. **Escapado de Consultas**: Usar consultas preparadas para prevenir inyección SQL
938
+ 3. **Cifrado**: Considerar cifrar datos sensibles si es necesario
939
+ 4. **Auditoría**: Registrar operaciones importantes para fines de auditoría
940
+ 5. **Límites**: Implementar límites para prevenir abusos
941
+
942
+ ## Buenas Prácticas
943
+
944
+ 1. **Seguir Convenciones**: Mantener consistencia con el resto del framework
945
+ 2. **Manejo de Errores**: Proporcionar mensajes de error claros y útiles
946
+ 3. **Rendimiento**: Optimizar operaciones para minimizar impacto en el rendimiento
947
+ 4. **Documentación**: Documentar claramente la API y casos de uso
948
+ 5. **Pruebas**: Incluir pruebas unitarias e integración
949
+ 6. **Compatibilidad**: Mantener compatibilidad hacia atrás cuando sea posible
950
+
951
+ ## Conclusión
952
+
953
+ La extensibilidad es una característica poderosa del API SDK Framework que permite adaptarlo a necesidades específicas. Al seguir los patrones y prácticas descritos en este manual, puedes crear extensiones robustas, seguras y fáciles de mantener que se integran perfectamente con el framework existente.
954
+
955
+ El ejemplo de SQLite demuestra cómo crear una extensión completa que sigue todos los principios de diseño del framework, manteniendo la consistencia de la API y proporcionando funcionalidad adicional de manera segura y eficiente.