jerkjs 2.1.6 → 2.2.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +202 -5
  3. package/index.js +29 -4
  4. package/lib/core/server.js +328 -27
  5. package/lib/loader/routeLoader.js +148 -117
  6. package/lib/middleware/compressor.js +87 -18
  7. package/lib/mvc/GenericAdapter.js +136 -0
  8. package/lib/mvc/MariaDBAdapter.js +315 -0
  9. package/lib/mvc/MemoryAdapter.js +269 -0
  10. package/lib/mvc/ModelControllerExample.js +285 -0
  11. package/lib/mvc/controllerBase.js +60 -0
  12. package/lib/mvc/modelBase.js +383 -0
  13. package/lib/mvc/modelManager.js +284 -0
  14. package/lib/mvc/userModel.js +265 -0
  15. package/lib/mvc/viewEngine.js +32 -1
  16. package/lib/utils/mimeType.js +62 -0
  17. package/package.json +5 -3
  18. package/JERK_FRAMEWORK_DIAGRAM.txt +0 -492
  19. package/JERK_FRAMEWORK_DIAGRAM_MERMAID.mmd +0 -124
  20. package/JERK_FRAMEWORK_DOCUMENTATION.md +0 -527
  21. package/LICENSE +0 -201
  22. package/README_EN.md +0 -230
  23. package/README_PT.md +0 -230
  24. package/docs/ARQUITECTURA_ROUTES.md +0 -140
  25. package/docs/EXTENSION_MANUAL.md +0 -955
  26. package/docs/FIREWALL_MANUAL.md +0 -416
  27. package/docs/HOOK-2.0.md +0 -512
  28. package/docs/HOOKS_REFERENCE_IMPROVED.md +0 -596
  29. package/docs/MANUAL_API_SDK.md +0 -536
  30. package/docs/MARIADB_TOKENS_IMPLEMENTATION.md +0 -110
  31. package/docs/MIDDLEWARE_MANUAL.md +0 -518
  32. package/docs/OAUTH2_GOOGLE_MANUAL.md +0 -405
  33. package/docs/ROUTING_WITHOUT_JSON_GUIDE.md +0 -454
  34. package/docs/frontend-and-sessions.md +0 -353
  35. package/docs/guia_inicio_rapido_jerkjs.md +0 -113
  36. package/examples/examples.arj +0 -0
  37. package/standard/CompressionTestController.js +0 -56
  38. package/standard/HealthController.js +0 -16
  39. package/standard/HomeController.js +0 -12
  40. package/standard/ProductController.js +0 -18
  41. package/standard/README.md +0 -47
  42. package/standard/UserController.js +0 -23
  43. package/standard/package.json +0 -22
  44. package/standard/routes.json +0 -65
  45. package/standard/server.js +0 -140
  46. package/standardA/controllers/AuthController.js +0 -82
  47. package/standardA/controllers/HomeController.js +0 -19
  48. package/standardA/controllers/UserController.js +0 -41
  49. package/standardA/server.js +0 -311
  50. package/standardA/views/auth/dashboard.html +0 -51
  51. package/standardA/views/auth/login.html +0 -47
  52. package/standardA/views/index.html +0 -32
  53. package/standardA/views/users/detail.html +0 -28
  54. package/standardA/views/users/list.html +0 -36
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Adaptador de MariaDB para modelos en el framework JERK
3
+ * Implementación del componente MVC MariaDBAdapter.js
4
+ * Proporciona acceso a bases de datos MariaDB/MySQL
5
+ */
6
+
7
+ const GenericAdapter = require('../../lib/mvc/GenericAdapter');
8
+ const mariadb = require('mariadb');
9
+
10
+ class MariaDBAdapter extends GenericAdapter {
11
+ /**
12
+ * Constructor del adaptador MariaDB
13
+ * @param {Object} options - Opciones de configuración
14
+ */
15
+ constructor(options = {}) {
16
+ super({ ...options, type: 'mariadb' });
17
+
18
+ // Configuración de la conexión a la base de datos
19
+ this.dbConfig = {
20
+ host: options.host || 'localhost',
21
+ user: options.user || 'root',
22
+ password: options.password || '',
23
+ database: options.database || 'otrack_db',
24
+ waitForConnections: options.waitForConnections !== false,
25
+ connectionLimit: options.connectionLimit || 10,
26
+ queueLimit: options.queueLimit || 0,
27
+ ...options
28
+ };
29
+
30
+ // Crear pool de conexiones
31
+ this.pool = mariadb.createPool(this.dbConfig);
32
+ }
33
+
34
+ /**
35
+ * Método para crear un registro
36
+ * @param {string} tableName - Nombre de la tabla
37
+ * @param {Object} data - Datos a crear
38
+ * @returns {Promise<Object>} - Promesa con el resultado
39
+ */
40
+ async create(tableName, data) {
41
+ const columns = Object.keys(data).join(', ');
42
+ const placeholders = Object.keys(data).map(() => '?').join(', ');
43
+ const query = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`;
44
+ const params = Object.values(data);
45
+
46
+ const connection = await this.pool.getConnection();
47
+ try {
48
+ const result = await connection.query(query, params);
49
+
50
+ // Obtener el registro recién insertado
51
+ const insertedRecord = await connection.query(
52
+ `SELECT * FROM ${tableName} WHERE id = ?`,
53
+ [result.insertId]
54
+ );
55
+
56
+ return insertedRecord[0] || { id: result.insertId, ...data };
57
+ } finally {
58
+ connection.release();
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Método para encontrar registros
64
+ * @param {string} tableName - Nombre de la tabla
65
+ * @param {Object} conditions - Condiciones de búsqueda
66
+ * @param {Object} options - Opciones adicionales
67
+ * @returns {Promise<Array>} - Promesa con los resultados
68
+ */
69
+ async find(tableName, conditions, options = {}) {
70
+ let query = `SELECT * FROM ${tableName}`;
71
+ const params = [];
72
+
73
+ // Añadir condiciones WHERE
74
+ if (conditions && Object.keys(conditions).length > 0) {
75
+ const conditionsList = [];
76
+ for (const [key, value] of Object.entries(conditions)) {
77
+ if (value !== undefined && value !== null) {
78
+ conditionsList.push(`${key} = ?`);
79
+ params.push(value);
80
+ }
81
+ }
82
+ if (conditionsList.length > 0) {
83
+ query += ' WHERE ' + conditionsList.join(' AND ');
84
+ }
85
+ }
86
+
87
+ // Añadir ordenamiento
88
+ if (options.orderBy) {
89
+ query += ` ORDER BY ${options.orderBy}`;
90
+ }
91
+
92
+ // Añadir paginación
93
+ if (options.limit) {
94
+ query += ` LIMIT `;
95
+ if (options.offset) {
96
+ query += `${options.offset}, `;
97
+ }
98
+ query += options.limit;
99
+ }
100
+
101
+ const connection = await this.pool.getConnection();
102
+ try {
103
+ return await connection.query(query, params);
104
+ } finally {
105
+ connection.release();
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Método para encontrar un solo registro
111
+ * @param {string} tableName - Nombre de la tabla
112
+ * @param {Object} conditions - Condiciones de búsqueda
113
+ * @param {Object} options - Opciones adicionales
114
+ * @returns {Promise<Object|null>} - Promesa con el resultado o null
115
+ */
116
+ async findOne(tableName, conditions, options = {}) {
117
+ const results = await this.find(tableName, conditions, { ...options, limit: 1 });
118
+ return results.length > 0 ? results[0] : null;
119
+ }
120
+
121
+ /**
122
+ * Método para actualizar registros
123
+ * @param {string} tableName - Nombre de la tabla
124
+ * @param {Object} conditions - Condiciones para seleccionar registros
125
+ * @param {Object} data - Datos a actualizar
126
+ * @returns {Promise<number>} - Promesa con el número de registros afectados
127
+ */
128
+ async update(tableName, conditions, data) {
129
+ const dataEntries = Object.entries(data);
130
+ if (dataEntries.length === 0) {
131
+ return 0; // No hay nada que actualizar
132
+ }
133
+
134
+ // Construir la parte SET de la consulta
135
+ const setClause = dataEntries.map(([key]) => `${key} = ?`).join(', ');
136
+ const params = dataEntries.map(([, value]) => value);
137
+
138
+ // Construir la parte WHERE de la consulta
139
+ const whereClause = [];
140
+ for (const [key, value] of Object.entries(conditions)) {
141
+ if (value !== undefined && value !== null) {
142
+ whereClause.push(`${key} = ?`);
143
+ params.push(value);
144
+ }
145
+ }
146
+
147
+ let query = `UPDATE ${tableName} SET ${setClause}`;
148
+ if (whereClause.length > 0) {
149
+ query += ` WHERE ${whereClause.join(' AND ')}`;
150
+ }
151
+
152
+ const connection = await this.pool.getConnection();
153
+ try {
154
+ const result = await connection.query(query, params);
155
+ return result.affectedRows;
156
+ } finally {
157
+ connection.release();
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Método para eliminar registros
163
+ * @param {string} tableName - Nombre de la tabla
164
+ * @param {Object} conditions - Condiciones para seleccionar registros
165
+ * @returns {Promise<number>} - Promesa con el número de registros eliminados
166
+ */
167
+ async delete(tableName, conditions) {
168
+ let query = `DELETE FROM ${tableName}`;
169
+ const params = [];
170
+
171
+ if (conditions && Object.keys(conditions).length > 0) {
172
+ const conditionsList = [];
173
+ for (const [key, value] of Object.entries(conditions)) {
174
+ if (value !== undefined && value !== null) {
175
+ conditionsList.push(`${key} = ?`);
176
+ params.push(value);
177
+ }
178
+ }
179
+ if (conditionsList.length > 0) {
180
+ query += ' WHERE ' + conditionsList.join(' AND ');
181
+ }
182
+ }
183
+
184
+ const connection = await this.pool.getConnection();
185
+ try {
186
+ const result = await connection.query(query, params);
187
+ return result.affectedRows;
188
+ } finally {
189
+ connection.release();
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Método para contar registros
195
+ * @param {string} tableName - Nombre de la tabla
196
+ * @param {Object} conditions - Condiciones de conteo
197
+ * @returns {Promise<number>} - Promesa con el número de registros
198
+ */
199
+ async count(tableName, conditions) {
200
+ let query = `SELECT COUNT(*) as total FROM ${tableName}`;
201
+ const params = [];
202
+
203
+ if (conditions && Object.keys(conditions).length > 0) {
204
+ const conditionsList = [];
205
+ for (const [key, value] of Object.entries(conditions)) {
206
+ if (value !== undefined && value !== null) {
207
+ conditionsList.push(`${key} = ?`);
208
+ params.push(value);
209
+ }
210
+ }
211
+ if (conditionsList.length > 0) {
212
+ query += ' WHERE ' + conditionsList.join(' AND ');
213
+ }
214
+ }
215
+
216
+ const connection = await this.pool.getConnection();
217
+ try {
218
+ const result = await connection.query(query, params);
219
+ return result[0] ? result[0].total : 0;
220
+ } finally {
221
+ connection.release();
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Método para ejecutar consultas personalizadas
227
+ * @param {string} query - Consulta personalizada
228
+ * @param {Array} params - Parámetros de la consulta
229
+ * @returns {Promise<any>} - Promesa con el resultado
230
+ */
231
+ async query(query, params = []) {
232
+ const connection = await this.pool.getConnection();
233
+ try {
234
+ return await connection.query(query, params);
235
+ } finally {
236
+ connection.release();
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Método para inicializar el adaptador
242
+ * @returns {Promise<void>}
243
+ */
244
+ async initialize() {
245
+ try {
246
+ // Probar la conexión
247
+ const connection = await this.pool.getConnection();
248
+ connection.release();
249
+ this.logger.info('Conexión a MariaDB establecida correctamente');
250
+ } catch (error) {
251
+ this.logger.error('Error al inicializar la conexión a MariaDB:', error.message);
252
+ throw error;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Método para cerrar la conexión
258
+ * @returns {Promise<void>}
259
+ */
260
+ async close() {
261
+ try {
262
+ await this.pool.end();
263
+ this.logger.info('Conexión a MariaDB cerrada correctamente');
264
+ } catch (error) {
265
+ this.logger.error('Error al cerrar la conexión a MariaDB:', error.message);
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Método para verificar si el adaptador está conectado
271
+ * @returns {Promise<boolean>} - Verdadero si está conectado
272
+ */
273
+ async isConnected() {
274
+ try {
275
+ const connection = await this.pool.getConnection();
276
+ connection.release();
277
+ return true;
278
+ } catch (error) {
279
+ return false;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Método para migrar una tabla
285
+ * @param {string} tableName - Nombre de la tabla a migrar
286
+ * @returns {Promise<void>}
287
+ */
288
+ async migrate(tableName) {
289
+ // En una implementación completa, aquí se ejecutarían scripts de migración
290
+ // Por ahora, simplemente verificamos si la tabla existe
291
+ try {
292
+ await this.query(`SELECT 1 FROM ${tableName} LIMIT 1`);
293
+ this.logger.info(`Tabla ${tableName} ya existe`);
294
+ } catch (error) {
295
+ this.logger.warn(`Tabla ${tableName} no existe o no se puede acceder:`, error.message);
296
+ // Aquí se podría crear la tabla si no existe
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Serializa el adaptador a JSON
302
+ * @returns {Object} - Representación JSON del adaptador
303
+ */
304
+ toJSON() {
305
+ const baseJson = super.toJSON();
306
+ return {
307
+ ...baseJson,
308
+ host: this.dbConfig.host,
309
+ database: this.dbConfig.database,
310
+ connected: this.isConnected()
311
+ };
312
+ }
313
+ }
314
+
315
+ module.exports = MariaDBAdapter;
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Adaptador de almacenamiento en memoria para modelos en el framework JERK
3
+ * Implementación del componente MVC MemoryAdapter.js
4
+ * Proporciona almacenamiento en memoria para modelos
5
+ */
6
+
7
+ const GenericAdapter = require('./GenericAdapter');
8
+
9
+ class MemoryAdapter extends GenericAdapter {
10
+ /**
11
+ * Constructor del adaptador en memoria
12
+ * @param {Object} options - Opciones de configuración
13
+ */
14
+ constructor(options = {}) {
15
+ super({ ...options, type: 'memory' });
16
+
17
+ // Almacenamiento en memoria
18
+ this.storage = new Map();
19
+ this.autoIncrementIds = new Map();
20
+ }
21
+
22
+ /**
23
+ * Método para crear un registro
24
+ * @param {string} tableName - Nombre de la tabla
25
+ * @param {Object} data - Datos a crear
26
+ * @returns {Promise<Object>} - Promesa con el resultado
27
+ */
28
+ async create(tableName, data) {
29
+ // Inicializar la tabla si no existe
30
+ if (!this.storage.has(tableName)) {
31
+ this.storage.set(tableName, []);
32
+ this.autoIncrementIds.set(tableName, 1);
33
+ }
34
+
35
+ const table = this.storage.get(tableName);
36
+ const newId = this.autoIncrementIds.get(tableName);
37
+
38
+ // Agregar ID al registro
39
+ const record = { id: newId, ...data, createdAt: new Date(), updatedAt: new Date() };
40
+
41
+ // Actualizar ID autoincrementable
42
+ this.autoIncrementIds.set(tableName, newId + 1);
43
+
44
+ // Agregar registro a la tabla
45
+ table.push(record);
46
+
47
+ return { ...record };
48
+ }
49
+
50
+ /**
51
+ * Método para encontrar registros
52
+ * @param {string} tableName - Nombre de la tabla
53
+ * @param {Object} conditions - Condiciones de búsqueda
54
+ * @param {Object} options - Opciones adicionales
55
+ * @returns {Promise<Array>} - Promesa con los resultados
56
+ */
57
+ async find(tableName, conditions, options = {}) {
58
+ if (!this.storage.has(tableName)) {
59
+ return [];
60
+ }
61
+
62
+ let records = [...this.storage.get(tableName)];
63
+
64
+ // Filtrar por condiciones
65
+ records = this.filterRecords(records, conditions);
66
+
67
+ // Aplicar opciones
68
+ if (options.orderBy) {
69
+ records = this.sortRecords(records, options.orderBy);
70
+ }
71
+
72
+ if (options.limit) {
73
+ const offset = options.offset || 0;
74
+ records = records.slice(offset, offset + options.limit);
75
+ }
76
+
77
+ return records;
78
+ }
79
+
80
+ /**
81
+ * Método para encontrar un solo registro
82
+ * @param {string} tableName - Nombre de la tabla
83
+ * @param {Object} conditions - Condiciones de búsqueda
84
+ * @param {Object} options - Opciones adicionales
85
+ * @returns {Promise<Object|null>} - Promesa con el resultado o null
86
+ */
87
+ async findOne(tableName, conditions, options = {}) {
88
+ const records = await this.find(tableName, conditions, { ...options, limit: 1 });
89
+ return records.length > 0 ? records[0] : null;
90
+ }
91
+
92
+ /**
93
+ * Método para actualizar registros
94
+ * @param {string} tableName - Nombre de la tabla
95
+ * @param {Object} conditions - Condiciones para seleccionar registros
96
+ * @param {Object} data - Datos a actualizar
97
+ * @returns {Promise<number>} - Promesa con el número de registros afectados
98
+ */
99
+ async update(tableName, conditions, data) {
100
+ if (!this.storage.has(tableName)) {
101
+ return 0;
102
+ }
103
+
104
+ const table = this.storage.get(tableName);
105
+ let updatedCount = 0;
106
+
107
+ // Actualizar la marca de tiempo
108
+ const now = new Date();
109
+ data.updatedAt = now;
110
+
111
+ for (let i = 0; i < table.length; i++) {
112
+ if (this.matchesConditions(table[i], conditions)) {
113
+ // Actualizar solo las propiedades proporcionadas
114
+ table[i] = { ...table[i], ...data };
115
+ updatedCount++;
116
+ }
117
+ }
118
+
119
+ return updatedCount;
120
+ }
121
+
122
+ /**
123
+ * Método para eliminar registros
124
+ * @param {string} tableName - Nombre de la tabla
125
+ * @param {Object} conditions - Condiciones para seleccionar registros
126
+ * @returns {Promise<number>} - Promesa con el número de registros eliminados
127
+ */
128
+ async delete(tableName, conditions) {
129
+ if (!this.storage.has(tableName)) {
130
+ return 0;
131
+ }
132
+
133
+ const table = this.storage.get(tableName);
134
+ const initialLength = table.length;
135
+
136
+ // Filtrar los registros que no coinciden con las condiciones
137
+ const filteredTable = table.filter(record => !this.matchesConditions(record, conditions));
138
+
139
+ // Actualizar la tabla
140
+ this.storage.set(tableName, filteredTable);
141
+
142
+ return initialLength - filteredTable.length;
143
+ }
144
+
145
+ /**
146
+ * Método para contar registros
147
+ * @param {string} tableName - Nombre de la tabla
148
+ * @param {Object} conditions - Condiciones de conteo
149
+ * @returns {Promise<number>} - Promesa con el número de registros
150
+ */
151
+ async count(tableName, conditions) {
152
+ if (!this.storage.has(tableName)) {
153
+ return 0;
154
+ }
155
+
156
+ const records = this.storage.get(tableName);
157
+ const filteredRecords = this.filterRecords(records, conditions);
158
+ return filteredRecords.length;
159
+ }
160
+
161
+ /**
162
+ * Método para ejecutar consultas personalizadas
163
+ * @param {string} query - Consulta personalizada (en este caso, una función)
164
+ * @param {Array} params - Parámetros de la consulta
165
+ * @returns {Promise<any>} - Promesa con el resultado
166
+ */
167
+ async query(query, params = []) {
168
+ // En el adaptador de memoria, tratamos la 'consulta' como una función
169
+ if (typeof query === 'function') {
170
+ return query(this.storage, ...params);
171
+ }
172
+
173
+ throw new Error('En el adaptador de memoria, la consulta debe ser una función');
174
+ }
175
+
176
+ /**
177
+ * Método para filtrar registros según condiciones
178
+ * @param {Array} records - Array de registros
179
+ * @param {Object} conditions - Condiciones de filtrado
180
+ * @returns {Array} - Registros filtrados
181
+ */
182
+ filterRecords(records, conditions) {
183
+ if (!conditions || Object.keys(conditions).length === 0) {
184
+ return records;
185
+ }
186
+
187
+ return records.filter(record => this.matchesConditions(record, conditions));
188
+ }
189
+
190
+ /**
191
+ * Método para verificar si un registro coincide con las condiciones
192
+ * @param {Object} record - Registro a verificar
193
+ * @param {Object} conditions - Condiciones a verificar
194
+ * @returns {boolean} - Verdadero si coincide
195
+ */
196
+ matchesConditions(record, conditions) {
197
+ for (const [key, value] of Object.entries(conditions)) {
198
+ if (record[key] !== value) {
199
+ return false;
200
+ }
201
+ }
202
+ return true;
203
+ }
204
+
205
+ /**
206
+ * Método para ordenar registros
207
+ * @param {Array} records - Array de registros
208
+ * @param {string} orderBy - Criterio de ordenamiento (ej: 'field ASC' o 'field DESC')
209
+ * @returns {Array} - Registros ordenados
210
+ */
211
+ sortRecords(records, orderBy) {
212
+ if (!orderBy) return records;
213
+
214
+ const [field, direction] = orderBy.split(' ');
215
+ const isAscending = (direction || 'ASC').toUpperCase() === 'ASC';
216
+
217
+ return records.sort((a, b) => {
218
+ const valueA = a[field];
219
+ const valueB = b[field];
220
+
221
+ if (valueA < valueB) {
222
+ return isAscending ? -1 : 1;
223
+ } else if (valueA > valueB) {
224
+ return isAscending ? 1 : -1;
225
+ } else {
226
+ return 0;
227
+ }
228
+ });
229
+ }
230
+
231
+ /**
232
+ * Método para limpiar el almacenamiento
233
+ */
234
+ clear() {
235
+ this.storage.clear();
236
+ this.autoIncrementIds.clear();
237
+ }
238
+
239
+ /**
240
+ * Método para migrar una tabla
241
+ * @param {string} tableName - Nombre de la tabla a migrar
242
+ * @returns {Promise<void>}
243
+ */
244
+ async migrate(tableName) {
245
+ // En el adaptador de memoria, la migración simplemente inicializa la tabla
246
+ if (!this.storage.has(tableName)) {
247
+ this.storage.set(tableName, []);
248
+ this.autoIncrementIds.set(tableName, 1);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Serializa el adaptador a JSON
254
+ * @returns {Object} - Representación JSON del adaptador
255
+ */
256
+ toJSON() {
257
+ const baseJson = super.toJSON();
258
+ return {
259
+ ...baseJson,
260
+ tables: Array.from(this.storage.keys()),
261
+ recordCounts: Array.from(this.storage.entries()).reduce((acc, [tableName, records]) => {
262
+ acc[tableName] = records.length;
263
+ return acc;
264
+ }, {})
265
+ };
266
+ }
267
+ }
268
+
269
+ module.exports = MemoryAdapter;