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.
- package/CHANGELOG.md +26 -0
- package/LICENSE +201 -0
- package/README.md +4 -1
- package/README_EN.md +230 -0
- package/README_PT.md +230 -0
- package/docs/ARQUITECTURA_ROUTES.md +186 -0
- package/docs/EXTENSION_MANUAL.md +955 -0
- package/docs/FIREWALL_MANUAL.md +416 -0
- package/docs/HOOK-2.0.md +512 -0
- package/docs/HOOKS_REFERENCE_IMPROVED.md +596 -0
- package/docs/JERK_FRAMEWORK_DIAGRAM.txt +492 -0
- package/docs/JERK_FRAMEWORK_DIAGRAM_MERMAID.mmd +124 -0
- package/docs/JERK_FRAMEWORK_DOCUMENTATION.md +553 -0
- package/docs/JERK_MODELOS_HOWTO.md +566 -0
- package/docs/MANUAL_API_SDK.md +536 -0
- package/docs/MARIADB_TOKENS_IMPLEMENTATION.md +110 -0
- package/docs/MIDDLEWARE_MANUAL.md +518 -0
- package/docs/OAUTH2_GOOGLE_MANUAL.md +405 -0
- package/docs/ROUTING_WITHOUT_JSON_GUIDE.md +454 -0
- package/docs/frontend-and-sessions.md +353 -0
- package/docs/guia_inicio_rapido_jerkjs.md +113 -0
- package/examples/examples.arj +0 -0
- package/index.js +12 -1
- package/jerk-qbuilder/CHANGELOG.md +71 -0
- package/jerk-qbuilder/HOWTO.md +325 -0
- package/jerk-qbuilder/README.md +52 -0
- package/lib/mvc/controllerBase.js +31 -14
- package/lib/query/MariaDBAdapter.js +78 -0
- package/lib/query/consoleAdapter.js +184 -0
- package/lib/query/queryBuilder.js +953 -0
- package/lib/query/queryBuilderHooks.js +455 -0
- package/lib/query/queryBuilderMiddleware.js +332 -0
- package/package.json +2 -2
- package/utils/find_file_path.sh +36 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware de QueryBuilder para el framework JERK
|
|
3
|
+
* Implementación del componente middleware/queryBuilderMiddleware.js
|
|
4
|
+
* Proporciona acceso al QueryBuilder a través del sistema de hooks y middlewares
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const QueryBuilder = require('./queryBuilder');
|
|
8
|
+
|
|
9
|
+
class QueryBuilderMiddleware {
|
|
10
|
+
/**
|
|
11
|
+
* Constructor del middleware
|
|
12
|
+
* @param {Object} options - Opciones de configuración
|
|
13
|
+
*/
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.queryBuilders = new Map(); // Almacenar instancias de QueryBuilder por contexto
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Middleware para inyectar el QueryBuilder en la solicitud
|
|
21
|
+
* @param {Object} req - Objeto de solicitud HTTP
|
|
22
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
23
|
+
* @param {Function} next - Función para continuar con el siguiente middleware
|
|
24
|
+
*/
|
|
25
|
+
injectQueryBuilder(req, res, next) {
|
|
26
|
+
// Si se ha almacenado un adaptador en la solicitud previamente, usarlo
|
|
27
|
+
if (req._dbAdapter) {
|
|
28
|
+
req.queryBuilder = (tableName = null) => {
|
|
29
|
+
const qb = new QueryBuilder(null, tableName);
|
|
30
|
+
qb.setAdapter(req._dbAdapter);
|
|
31
|
+
return qb;
|
|
32
|
+
};
|
|
33
|
+
} else {
|
|
34
|
+
// Si no hay adaptador en la solicitud, crear QueryBuilder sin adaptador
|
|
35
|
+
req.queryBuilder = (tableName = null) => new QueryBuilder(null, tableName);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Agregar también un acceso directo al QueryBuilder en el objeto de solicitud
|
|
39
|
+
req.QueryBuilder = QueryBuilder;
|
|
40
|
+
|
|
41
|
+
next();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Middleware para registrar el QueryBuilder en el sistema de hooks
|
|
46
|
+
* @param {Object} req - Objeto de solicitud HTTP
|
|
47
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
48
|
+
* @param {Function} next - Función para continuar con el siguiente middleware
|
|
49
|
+
*/
|
|
50
|
+
registerWithHooks(req, res, next) {
|
|
51
|
+
// Disparar hook para permitir que otros componentes se registren con el QueryBuilder
|
|
52
|
+
const { hooks } = require('../../index.js');
|
|
53
|
+
if (hooks) {
|
|
54
|
+
hooks.doAction('query_builder_registered', {
|
|
55
|
+
request: req,
|
|
56
|
+
response: res,
|
|
57
|
+
queryBuilder: req.queryBuilder
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
next();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Middleware para configurar el QueryBuilder con un adaptador específico
|
|
66
|
+
* @param {Object} adapter - Adaptador de base de datos
|
|
67
|
+
* @returns {Function} - Middleware para configurar el QueryBuilder
|
|
68
|
+
*/
|
|
69
|
+
configureWithAdapter(adapter) {
|
|
70
|
+
return (req, res, next) => {
|
|
71
|
+
// Guardar el adaptador en la solicitud para uso posterior
|
|
72
|
+
req._dbAdapter = adapter;
|
|
73
|
+
|
|
74
|
+
// Si queryBuilder ya existe, actualizarlo con el nuevo adaptador
|
|
75
|
+
if (req.queryBuilder) {
|
|
76
|
+
const originalQueryBuilder = req.queryBuilder;
|
|
77
|
+
req.queryBuilder = (tableName = null) => {
|
|
78
|
+
const qb = originalQueryBuilder(tableName);
|
|
79
|
+
qb.setAdapter(adapter);
|
|
80
|
+
return qb;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
next();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Middleware para inicializar el QueryBuilder con opciones específicas
|
|
90
|
+
* @param {Object} options - Opciones de inicialización
|
|
91
|
+
* @returns {Function} - Middleware para inicializar el QueryBuilder
|
|
92
|
+
*/
|
|
93
|
+
initializeWithOptions(options = {}) {
|
|
94
|
+
return (req, res, next) => {
|
|
95
|
+
// Guardar las opciones en la solicitud para uso posterior
|
|
96
|
+
req.queryBuilderOptions = { ...this.options, ...options };
|
|
97
|
+
|
|
98
|
+
next();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Middleware para registrar un QueryBuilder en el contexto de un modelo
|
|
104
|
+
* @param {Object} model - Instancia del modelo
|
|
105
|
+
* @returns {Function} - Middleware para registrar el QueryBuilder en el modelo
|
|
106
|
+
*/
|
|
107
|
+
registerInModel(model) {
|
|
108
|
+
return (req, res, next) => {
|
|
109
|
+
// Asegurarse de que el modelo tenga acceso al QueryBuilder
|
|
110
|
+
if (model && typeof model.setAdapter === 'function' && model.adapter) {
|
|
111
|
+
// Crear una instancia de QueryBuilder con el adaptador del modelo
|
|
112
|
+
model.queryBuilder = (tableName = null) => new QueryBuilder(model.adapter, tableName);
|
|
113
|
+
|
|
114
|
+
// Registrar el modelo con el QueryBuilder
|
|
115
|
+
if (typeof model.registerQueryBuilder === 'function') {
|
|
116
|
+
model.registerQueryBuilder(model.queryBuilder);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
next();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Middleware para aplicar transformaciones a las consultas antes de ejecutarlas
|
|
126
|
+
* @param {Function} transformer - Función para transformar la consulta
|
|
127
|
+
* @returns {Function} - Middleware para transformar consultas
|
|
128
|
+
*/
|
|
129
|
+
applyQueryTransformer(transformer) {
|
|
130
|
+
return (req, res, next) => {
|
|
131
|
+
if (typeof transformer !== 'function') {
|
|
132
|
+
next();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Guardar la función original de queryBuilder
|
|
137
|
+
const originalQueryBuilder = req.queryBuilder;
|
|
138
|
+
|
|
139
|
+
if (originalQueryBuilder) {
|
|
140
|
+
// Reemplazar la función queryBuilder con una versión que aplica el transformador
|
|
141
|
+
req.queryBuilder = (tableName = null) => {
|
|
142
|
+
const qb = originalQueryBuilder(tableName);
|
|
143
|
+
|
|
144
|
+
// Guardar la función original toSQL
|
|
145
|
+
const originalToSQL = qb.toSQL;
|
|
146
|
+
|
|
147
|
+
// Reemplazar toSQL para aplicar transformaciones
|
|
148
|
+
qb.toSQL = function() {
|
|
149
|
+
const result = originalToSQL.call(this);
|
|
150
|
+
|
|
151
|
+
// Aplicar el transformador si existe
|
|
152
|
+
if (transformer) {
|
|
153
|
+
const transformed = transformer(result, this);
|
|
154
|
+
return transformed || result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return qb;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
next();
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Middleware para habilitar el registro de consultas SQL
|
|
170
|
+
* @param {Object} logger - Instancia de logger
|
|
171
|
+
* @returns {Function} - Middleware para registrar consultas SQL
|
|
172
|
+
*/
|
|
173
|
+
enableQueryLogging(logger = console) {
|
|
174
|
+
return (req, res, next) => {
|
|
175
|
+
// Guardar la función original de queryBuilder
|
|
176
|
+
const originalQueryBuilder = req.queryBuilder;
|
|
177
|
+
|
|
178
|
+
if (originalQueryBuilder) {
|
|
179
|
+
// Reemplazar la función queryBuilder con una versión que registra consultas
|
|
180
|
+
req.queryBuilder = (tableName = null) => {
|
|
181
|
+
const qb = originalQueryBuilder(tableName);
|
|
182
|
+
|
|
183
|
+
// Guardar las funciones originales de ejecución
|
|
184
|
+
const originalGet = qb.get;
|
|
185
|
+
const originalFirst = qb.first;
|
|
186
|
+
const originalInsert = qb.insert;
|
|
187
|
+
const originalUpdate = qb.update;
|
|
188
|
+
const originalDelete = qb.delete;
|
|
189
|
+
|
|
190
|
+
// Reemplazar get para registrar consultas
|
|
191
|
+
qb.get = async function() {
|
|
192
|
+
const { sql, params } = this.toSQL();
|
|
193
|
+
logger.info(`[QUERY_BUILDER] Ejecutando consulta: ${sql}`, { params });
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const result = await originalGet.call(this);
|
|
197
|
+
logger.info(`[QUERY_BUILDER] Consulta completada, resultados: ${result.length}`);
|
|
198
|
+
return result;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logger.error(`[QUERY_BUILDER] Error en consulta: ${error.message}`, { sql, params });
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Reemplazar first para registrar consultas
|
|
206
|
+
qb.first = async function() {
|
|
207
|
+
const { sql, params } = this.toSQL();
|
|
208
|
+
logger.info(`[QUERY_BUILDER] Ejecutando consulta (first): ${sql}`, { params });
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const result = await originalFirst.call(this);
|
|
212
|
+
logger.info(`[QUERY_BUILDER] Consulta (first) completada`, { result: result !== null });
|
|
213
|
+
return result;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logger.error(`[QUERY_BUILDER] Error en consulta (first): ${error.message}`, { sql, params });
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Reemplazar insert para registrar consultas
|
|
221
|
+
qb.insert = async function(data) {
|
|
222
|
+
const columns = Object.keys(data);
|
|
223
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
224
|
+
const sql = `INSERT INTO \`${this.tableName}\` (\`${columns.join('`, `')}\`) VALUES (${placeholders})`;
|
|
225
|
+
|
|
226
|
+
logger.info(`[QUERY_BUILDER] Ejecutando inserción: ${sql}`, { data });
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const result = await originalInsert.call(this, data);
|
|
230
|
+
logger.info(`[QUERY_BUILDER] Inserción completada`, { result });
|
|
231
|
+
return result;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
logger.error(`[QUERY_BUILDER] Error en inserción: ${error.message}`, { sql, data });
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Reemplazar update para registrar consultas
|
|
239
|
+
qb.update = async function(data) {
|
|
240
|
+
const updates = Object.keys(data).map(key => `\`${key}\` = ?`).join(', ');
|
|
241
|
+
const sql = `UPDATE \`${this.tableName}\` SET ${updates}`;
|
|
242
|
+
|
|
243
|
+
logger.info(`[QUERY_BUILDER] Ejecutando actualización: ${sql}`, { data });
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const result = await originalUpdate.call(this, data);
|
|
247
|
+
logger.info(`[QUERY_BUILDER] Actualización completada`, { affectedRows: result });
|
|
248
|
+
return result;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
logger.error(`[QUERY_BUILDER] Error en actualización: ${error.message}`, { sql, data });
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Reemplazar delete para registrar consultas
|
|
256
|
+
qb.delete = async function() {
|
|
257
|
+
const { sql, params } = this.toSQL();
|
|
258
|
+
logger.info(`[QUERY_BUILDER] Ejecutando eliminación: ${sql}`, { params });
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const result = await originalDelete.call(this);
|
|
262
|
+
logger.info(`[QUERY_BUILDER] Eliminación completada`, { affectedRows: result });
|
|
263
|
+
return result;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
logger.error(`[QUERY_BUILDER] Error en eliminación: ${error.message}`, { sql, params });
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return qb;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
next();
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Middleware para aplicar reglas de seguridad a las consultas
|
|
280
|
+
* @param {Object} securityRules - Reglas de seguridad
|
|
281
|
+
* @returns {Function} - Middleware para aplicar reglas de seguridad
|
|
282
|
+
*/
|
|
283
|
+
applySecurityRules(securityRules = {}) {
|
|
284
|
+
return (req, res, next) => {
|
|
285
|
+
// Guardar la función original de queryBuilder
|
|
286
|
+
const originalQueryBuilder = req.queryBuilder;
|
|
287
|
+
|
|
288
|
+
if (originalQueryBuilder) {
|
|
289
|
+
// Reemplazar la función queryBuilder con una versión que aplica reglas de seguridad
|
|
290
|
+
req.queryBuilder = (tableName = null) => {
|
|
291
|
+
const qb = originalQueryBuilder(tableName);
|
|
292
|
+
|
|
293
|
+
// Guardar la función original toSQL
|
|
294
|
+
const originalToSQL = qb.toSQL;
|
|
295
|
+
|
|
296
|
+
// Reemplazar toSQL para aplicar reglas de seguridad
|
|
297
|
+
qb.toSQL = function() {
|
|
298
|
+
// Aplicar reglas de seguridad antes de generar la consulta
|
|
299
|
+
if (securityRules.disallowSelectAll && this.selectFields.length === 0) {
|
|
300
|
+
throw new Error('SELECT * no está permitido por razones de seguridad');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (securityRules.requiredWheres && this.whereConditions.length === 0 && this.tableName) {
|
|
304
|
+
// Verificar si la tabla requiere condiciones WHERE
|
|
305
|
+
if (Array.isArray(securityRules.requiredWheres) &&
|
|
306
|
+
securityRules.requiredWheres.includes(this.tableName)) {
|
|
307
|
+
throw new Error(`La tabla ${this.tableName} requiere condiciones WHERE por razones de seguridad`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Aplicar límites de seguridad
|
|
312
|
+
if (securityRules.maxLimit && this.limitValue > securityRules.maxLimit) {
|
|
313
|
+
this.limitValue = securityRules.maxLimit;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return originalToSQL.call(this);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return qb;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
next();
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Exportar el middleware y el QueryBuilder
|
|
329
|
+
module.exports = {
|
|
330
|
+
QueryBuilderMiddleware,
|
|
331
|
+
QueryBuilder
|
|
332
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jerkjs",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "JERK Framework v2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "JERK Framework v2.3 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, QueryBuilder, and complete MVC architecture with models",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script to find a file's location using 'which' and show its real path with 'ls -ln'
|
|
4
|
+
|
|
5
|
+
if [ $# -eq 0 ]; then
|
|
6
|
+
echo "Usage: $0 <filename>"
|
|
7
|
+
echo "Example: $0 python"
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
filename="$1"
|
|
12
|
+
|
|
13
|
+
# Find the file using which
|
|
14
|
+
echo "Finding location of '$filename' using 'which':"
|
|
15
|
+
which_result=$(which "$filename")
|
|
16
|
+
|
|
17
|
+
if [ $? -eq 0 ]; then
|
|
18
|
+
echo "Found: $which_result"
|
|
19
|
+
|
|
20
|
+
# Get the directory containing the file
|
|
21
|
+
file_dir=$(dirname "$which_result")
|
|
22
|
+
file_basename=$(basename "$which_result")
|
|
23
|
+
|
|
24
|
+
# Change to the directory and use ls -ln to show the real path
|
|
25
|
+
echo ""
|
|
26
|
+
echo "Using 'ls -ln' to show detailed info:"
|
|
27
|
+
(cd "$file_dir" && ls -ln "$file_basename")
|
|
28
|
+
|
|
29
|
+
# If the file is a symlink, show the real path using readlink
|
|
30
|
+
echo ""
|
|
31
|
+
echo "Real path (using readlink -f):"
|
|
32
|
+
readlink -f "$which_result"
|
|
33
|
+
else
|
|
34
|
+
echo "File '$filename' not found in PATH"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|