andres-db 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.
- package/README.md +202 -0
- package/index.js +1151 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
# 🗄️ EasyDB
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
**EasyDB** es una interfaz **simple, moderna y poderosa** para trabajar con **SQLite** en Node.js usando `better-sqlite3`.
|
|
10
|
+
|
|
11
|
+
Diseñada para ofrecer **respuestas JSON limpias**, **creación automática de tablas**, **operaciones CRUD intuitivas** y utilidades avanzadas sin escribir SQL complejo.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Características
|
|
16
|
+
|
|
17
|
+
- 📦 SQLite rápido con `better-sqlite3`
|
|
18
|
+
- ⚡ CRUD completo
|
|
19
|
+
- 🧠 Creación automática de tablas
|
|
20
|
+
- 🧩 Columnas dinámicas
|
|
21
|
+
- 🔎 Búsquedas avanzadas
|
|
22
|
+
- 🔁 Transacciones seguras
|
|
23
|
+
- 📤 Exportar / importar JSON
|
|
24
|
+
- 💾 Backups automáticos
|
|
25
|
+
- 📊 Estadísticas del sistema
|
|
26
|
+
- 🧾 Respuestas JSON estándar
|
|
27
|
+
- 🛠️ Consultas SQL personalizadas
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📦 Instalación
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install andres-db
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requiere **Node.js v16+**
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 🚀 Uso rápido
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const db = require('andres-db');
|
|
45
|
+
|
|
46
|
+
db.create('users', {
|
|
47
|
+
name: 'Juan',
|
|
48
|
+
email: 'juan@email.com',
|
|
49
|
+
age: 25
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const users = db.read('users');
|
|
53
|
+
console.log(users.data);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## ⚙️ Configuración
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const db = require('andres-db').createInstance({
|
|
62
|
+
filename: './db/app.db',
|
|
63
|
+
verbose: true,
|
|
64
|
+
jsonResponse: true
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 🧱 Crear registros
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
db.create('products', {
|
|
74
|
+
name: 'Laptop',
|
|
75
|
+
price: 1200,
|
|
76
|
+
stock: 10
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Múltiples registros
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
db.create('users', [
|
|
84
|
+
{ name: 'Ana', age: 22 },
|
|
85
|
+
{ name: 'Luis', age: 30 }
|
|
86
|
+
]);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 📖 Leer datos
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
db.read('users', { age: { $gte: 18 } });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
db.readOne('users', { id: 1 });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ✏️ Actualizar
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
db.update('users', { id: 1 }, { age: 26 });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## ❌ Eliminar
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
db.delete('users', { id: 1 });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 🔎 Buscar texto
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
db.search('users', ['name', 'email'], 'juan');
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 🧩 Agregar columnas
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
db.addColumn('users', 'phone', 'TEXT');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
db.addColumns('users', [
|
|
135
|
+
{ name: 'city', type: 'TEXT' },
|
|
136
|
+
{ name: 'active', type: 'INTEGER', defaultValue: 1 }
|
|
137
|
+
]);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 🔁 Transacciones
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
db.transaction((db) => {
|
|
146
|
+
db.create('accounts', { name: 'Cuenta A' });
|
|
147
|
+
db.create('accounts', { name: 'Cuenta B' });
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 🧪 SQL personalizado
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
db.query('SELECT * FROM users WHERE age > ?', [20]);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 📤 Exportar / 📥 Importar
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
db.exportToJSON();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
db.importFromJSON({ users: [{ name: 'Carlos', age: 28 }] });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 💾 Backup
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
db.backup('./backup/db_backup.db');
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 📊 Estadísticas
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
db.getStats();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🧑💻 Autor
|
|
190
|
+
|
|
191
|
+
**ANDRES CARVAJAL**
|
|
192
|
+
|
|
193
|
+
🌐 https://andres-carvajal.vercel.app
|
|
194
|
+
📸 Instagram: [@05_carvajal](https://instagram.com/05_carvajal)
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 📄 Licencia
|
|
199
|
+
|
|
200
|
+

|
|
201
|
+
|
|
202
|
+
Este proyecto está bajo la licencia **MIT**.
|
package/index.js
ADDED
|
@@ -0,0 +1,1151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* db.js - Base de Datos SQL Simplificada
|
|
3
|
+
* @version 2.2.0
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @description Interfaz JavaScript simple para bases de datos SQLite con respuestas JSON
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Database = require('better-sqlite3');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// ===========================================
|
|
13
|
+
// VARIABLE GLOBAL PARA RUTA DE LA BASE DE DATOS
|
|
14
|
+
// ===========================================
|
|
15
|
+
const DEFAULT_DB_PATH = './db/database.db'; // <-- VARIABLE GLOBAL
|
|
16
|
+
|
|
17
|
+
// Función para asegurar que el directorio existe
|
|
18
|
+
function ensureDirectoryExists(filePath) {
|
|
19
|
+
const dir = path.dirname(filePath);
|
|
20
|
+
if (!fs.existsSync(dir)) {
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
console.log(`Directorio creado: ${path.resolve(dir)}`);
|
|
23
|
+
}
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class EasyDB {
|
|
28
|
+
/**
|
|
29
|
+
* Crea una nueva instancia de EasyDB
|
|
30
|
+
* @param {string|Object} config - Configuración de la base de datos
|
|
31
|
+
* @param {string} [config.filename=DEFAULT_DB_PATH] - Nombre del archivo
|
|
32
|
+
* @param {boolean} [config.memory=false] - Usar base de datos en memoria
|
|
33
|
+
* @param {boolean} [config.readonly=false] - Modo solo lectura
|
|
34
|
+
* @param {boolean} [config.verbose=false] - Mostrar logs SQL
|
|
35
|
+
* @param {boolean} [config.jsonResponse=true] - Formatear respuestas como JSON limpio
|
|
36
|
+
* @param {boolean} [config.autoCreateDir=true] - Crear directorio automáticamente si no existe
|
|
37
|
+
*/
|
|
38
|
+
constructor(config = DEFAULT_DB_PATH) {
|
|
39
|
+
this.config = {
|
|
40
|
+
filename: DEFAULT_DB_PATH,
|
|
41
|
+
memory: false,
|
|
42
|
+
readonly: false,
|
|
43
|
+
verbose: false,
|
|
44
|
+
jsonResponse: true,
|
|
45
|
+
autoCreateDir: true,
|
|
46
|
+
...(typeof config === 'string' ? { filename: config } : config)
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Crear directorio automáticamente si es necesario
|
|
50
|
+
if (!this.config.memory && this.config.autoCreateDir) {
|
|
51
|
+
ensureDirectoryExists(this.config.filename);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Opciones de conexión
|
|
55
|
+
const dbOptions = {
|
|
56
|
+
verbose: this.config.verbose ? console.log : null,
|
|
57
|
+
readonly: this.config.readonly
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Establecer conexión
|
|
61
|
+
if (this.config.memory) {
|
|
62
|
+
this.db = new Database(':memory:', dbOptions);
|
|
63
|
+
if (this.config.verbose) console.log('EasyDB: Conectado a base de datos en memoria');
|
|
64
|
+
} else {
|
|
65
|
+
try {
|
|
66
|
+
this.db = new Database(this.config.filename, dbOptions);
|
|
67
|
+
if (this.config.verbose) {
|
|
68
|
+
console.log(`EasyDB: Conectado a ${this.config.filename}`);
|
|
69
|
+
console.log(`Ruta completa: ${path.resolve(this.config.filename)}`);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Error conectando a ${this.config.filename}:`, error.message);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Optimizaciones
|
|
78
|
+
this.db.pragma('foreign_keys = ON');
|
|
79
|
+
this.db.pragma('journal_mode = WAL');
|
|
80
|
+
this.db.pragma('synchronous = NORMAL');
|
|
81
|
+
|
|
82
|
+
// Inicializar sistema de metadatos
|
|
83
|
+
this._initMetadata();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Inicializa la tabla de metadatos
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
_initMetadata() {
|
|
91
|
+
const sql = `
|
|
92
|
+
CREATE TABLE IF NOT EXISTS _easydb_meta (
|
|
93
|
+
key TEXT PRIMARY KEY,
|
|
94
|
+
value TEXT,
|
|
95
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
96
|
+
)
|
|
97
|
+
`;
|
|
98
|
+
this.db.exec(sql);
|
|
99
|
+
this._setMeta('version', '2.2.0');
|
|
100
|
+
this._setMeta('created_at', new Date().toISOString());
|
|
101
|
+
this._setMeta('db_path', this.config.filename);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Establece un metadato
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
_setMeta(key, value) {
|
|
109
|
+
const sql = `INSERT OR REPLACE INTO _easydb_meta (key, value) VALUES (?, ?)`;
|
|
110
|
+
this.db.prepare(sql).run(key, value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Obtiene un metadato
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_getMeta(key) {
|
|
118
|
+
const sql = 'SELECT value FROM _easydb_meta WHERE key = ?';
|
|
119
|
+
const result = this.db.prepare(sql).get(key);
|
|
120
|
+
return result ? result.value : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Verifica si una tabla existe
|
|
125
|
+
* @param {string} tableName - Nombre de la tabla
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
128
|
+
tableExists(tableName) {
|
|
129
|
+
const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name = ?`;
|
|
130
|
+
return !!this.db.prepare(sql).get(tableName);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Crea una tabla automáticamente basada en datos de muestra
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
_autoCreateTable(tableName, sampleData) {
|
|
138
|
+
const columns = ['id INTEGER PRIMARY KEY AUTOINCREMENT'];
|
|
139
|
+
|
|
140
|
+
// Mapear tipos JavaScript a tipos SQL
|
|
141
|
+
for (const [key, value] of Object.entries(sampleData)) {
|
|
142
|
+
let columnType = 'TEXT';
|
|
143
|
+
|
|
144
|
+
if (value === null || value === undefined) {
|
|
145
|
+
columnType = 'TEXT';
|
|
146
|
+
} else if (typeof value === 'number') {
|
|
147
|
+
columnType = Number.isInteger(value) ? 'INTEGER' : 'REAL';
|
|
148
|
+
} else if (typeof value === 'boolean') {
|
|
149
|
+
columnType = 'INTEGER';
|
|
150
|
+
} else if (value instanceof Date) {
|
|
151
|
+
columnType = 'TEXT';
|
|
152
|
+
} else if (typeof value === 'object') {
|
|
153
|
+
columnType = 'TEXT'; // JSON
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
columns.push(`${key} ${columnType}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
columns.push('created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP');
|
|
160
|
+
columns.push('updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP');
|
|
161
|
+
|
|
162
|
+
// Crear tabla
|
|
163
|
+
const sql = `CREATE TABLE ${tableName} (${columns.join(', ')})`;
|
|
164
|
+
this.db.exec(sql);
|
|
165
|
+
|
|
166
|
+
// Crear trigger para updated_at
|
|
167
|
+
const triggerSql = `
|
|
168
|
+
CREATE TRIGGER IF NOT EXISTS ${tableName}_updated
|
|
169
|
+
AFTER UPDATE ON ${tableName}
|
|
170
|
+
BEGIN
|
|
171
|
+
UPDATE ${tableName}
|
|
172
|
+
SET updated_at = CURRENT_TIMESTAMP
|
|
173
|
+
WHERE id = NEW.id;
|
|
174
|
+
END
|
|
175
|
+
`;
|
|
176
|
+
this.db.exec(triggerSql);
|
|
177
|
+
|
|
178
|
+
if (this.config.verbose) {
|
|
179
|
+
console.log(`EasyDB: Tabla '${tableName}' creada automáticamente`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Agrega una nueva columna a una tabla existente
|
|
185
|
+
* @param {string} tableName - Nombre de la tabla
|
|
186
|
+
* @param {string} columnName - Nombre de la nueva columna
|
|
187
|
+
* @param {string} columnType - Tipo de dato (INTEGER, TEXT, REAL, BLOB, etc.)
|
|
188
|
+
* @param {*} [defaultValue] - Valor por defecto (opcional)
|
|
189
|
+
* @returns {Object} - Respuesta JSON con resultado de la operación
|
|
190
|
+
*/
|
|
191
|
+
addColumn(tableName, columnName, columnType, defaultValue = null) {
|
|
192
|
+
try {
|
|
193
|
+
if (!this.tableExists(tableName)) {
|
|
194
|
+
throw new Error(`La tabla '${tableName}' no existe`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Verificar si la columna ya existe
|
|
198
|
+
const tableInfo = this.tableInfo(tableName);
|
|
199
|
+
if (tableInfo.success) {
|
|
200
|
+
const columnExists = tableInfo.columns.some(col => col.name === columnName);
|
|
201
|
+
if (columnExists) {
|
|
202
|
+
throw new Error(`La columna '${columnName}' ya existe en la tabla '${tableName}'`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Construir la sentencia ALTER TABLE
|
|
207
|
+
let sql = `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnType}`;
|
|
208
|
+
|
|
209
|
+
if (defaultValue !== null) {
|
|
210
|
+
const defaultValueStr = typeof defaultValue === 'string'
|
|
211
|
+
? `'${defaultValue.replace(/'/g, "''")}'`
|
|
212
|
+
: defaultValue;
|
|
213
|
+
sql += ` DEFAULT ${defaultValueStr}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`);
|
|
217
|
+
|
|
218
|
+
this.db.exec(sql);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
operation: 'addColumn',
|
|
223
|
+
table: tableName,
|
|
224
|
+
column: columnName,
|
|
225
|
+
type: columnType,
|
|
226
|
+
defaultValue: defaultValue,
|
|
227
|
+
message: `Columna '${columnName}' agregada exitosamente a la tabla '${tableName}'`,
|
|
228
|
+
timestamp: new Date().toISOString()
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
} catch (error) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
operation: 'addColumn',
|
|
235
|
+
error: error.message,
|
|
236
|
+
timestamp: new Date().toISOString()
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Agrega múltiples columnas a una tabla existente
|
|
243
|
+
* @param {string} tableName - Nombre de la tabla
|
|
244
|
+
* @param {Array} columns - Array de objetos {name, type, defaultValue}
|
|
245
|
+
* @returns {Object} - Respuesta JSON con resultado de la operación
|
|
246
|
+
*/
|
|
247
|
+
addColumns(tableName, columns) {
|
|
248
|
+
try {
|
|
249
|
+
const results = [];
|
|
250
|
+
|
|
251
|
+
for (const column of columns) {
|
|
252
|
+
const result = this.addColumn(
|
|
253
|
+
tableName,
|
|
254
|
+
column.name,
|
|
255
|
+
column.type,
|
|
256
|
+
column.defaultValue
|
|
257
|
+
);
|
|
258
|
+
results.push(result);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const successful = results.filter(r => r.success).length;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
success: successful === columns.length,
|
|
265
|
+
operation: 'addColumns',
|
|
266
|
+
table: tableName,
|
|
267
|
+
results: results,
|
|
268
|
+
added: successful,
|
|
269
|
+
failed: columns.length - successful,
|
|
270
|
+
message: `Agregadas ${successful} de ${columns.length} columnas a la tabla '${tableName}'`,
|
|
271
|
+
timestamp: new Date().toISOString()
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return {
|
|
276
|
+
success: false,
|
|
277
|
+
operation: 'addColumns',
|
|
278
|
+
error: error.message,
|
|
279
|
+
timestamp: new Date().toISOString()
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Construye cláusula WHERE a partir de condiciones
|
|
286
|
+
* @private
|
|
287
|
+
*/
|
|
288
|
+
_buildWhereClause(conditions) {
|
|
289
|
+
if (!conditions || Object.keys(conditions).length === 0) {
|
|
290
|
+
return { clause: '', params: [] };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const clauses = [];
|
|
294
|
+
const params = [];
|
|
295
|
+
|
|
296
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
297
|
+
if (typeof value === 'object' && value !== null) {
|
|
298
|
+
// Operadores especiales
|
|
299
|
+
if (value.$eq !== undefined) {
|
|
300
|
+
clauses.push(`${key} = ?`);
|
|
301
|
+
params.push(value.$eq);
|
|
302
|
+
} else if (value.$ne !== undefined) {
|
|
303
|
+
clauses.push(`${key} != ?`);
|
|
304
|
+
params.push(value.$ne);
|
|
305
|
+
} else if (value.$gt !== undefined) {
|
|
306
|
+
clauses.push(`${key} > ?`);
|
|
307
|
+
params.push(value.$gt);
|
|
308
|
+
} else if (value.$gte !== undefined) {
|
|
309
|
+
clauses.push(`${key} >= ?`);
|
|
310
|
+
params.push(value.$gte);
|
|
311
|
+
} else if (value.$lt !== undefined) {
|
|
312
|
+
clauses.push(`${key} < ?`);
|
|
313
|
+
params.push(value.$lt);
|
|
314
|
+
} else if (value.$lte !== undefined) {
|
|
315
|
+
clauses.push(`${key} <= ?`);
|
|
316
|
+
params.push(value.$lte);
|
|
317
|
+
} else if (value.$in !== undefined) {
|
|
318
|
+
const placeholders = value.$in.map(() => '?').join(', ');
|
|
319
|
+
clauses.push(`${key} IN (${placeholders})`);
|
|
320
|
+
params.push(...value.$in);
|
|
321
|
+
} else if (value.$like !== undefined) {
|
|
322
|
+
clauses.push(`${key} LIKE ?`);
|
|
323
|
+
params.push(value.$like);
|
|
324
|
+
} else if (value.$between !== undefined && Array.isArray(value.$between)) {
|
|
325
|
+
clauses.push(`${key} BETWEEN ? AND ?`);
|
|
326
|
+
params.push(value.$between[0], value.$between[1]);
|
|
327
|
+
} else if (value.$not !== undefined) {
|
|
328
|
+
clauses.push(`${key} != ?`);
|
|
329
|
+
params.push(value.$not);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
clauses.push(`${key} = ?`);
|
|
333
|
+
params.push(value);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
clause: clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '',
|
|
339
|
+
params
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Formatea respuesta como JSON limpio
|
|
345
|
+
* @private
|
|
346
|
+
*/
|
|
347
|
+
_formatResponse(data, operation, table = null) {
|
|
348
|
+
if (!this.config.jsonResponse) return data;
|
|
349
|
+
|
|
350
|
+
const response = {
|
|
351
|
+
success: true,
|
|
352
|
+
operation: operation,
|
|
353
|
+
timestamp: new Date().toISOString(),
|
|
354
|
+
data: data
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
if (table) response.table = table;
|
|
358
|
+
|
|
359
|
+
// Para operaciones de creación
|
|
360
|
+
if (operation === 'create' && data && data.id) {
|
|
361
|
+
response.message = `Registro creado con ID: ${data.id}`;
|
|
362
|
+
response.recordId = data.id;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Para operaciones de actualización/eliminación
|
|
366
|
+
if (operation === 'update' && data.changes !== undefined) {
|
|
367
|
+
response.message = `Registros actualizados: ${data.changes}`;
|
|
368
|
+
response.changes = data.changes;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (operation === 'delete' && data.deleted !== undefined) {
|
|
372
|
+
response.message = `Registros eliminados: ${data.deleted}`;
|
|
373
|
+
response.deleted = data.deleted;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Para operaciones de lectura
|
|
377
|
+
if (operation === 'read' || operation === 'search') {
|
|
378
|
+
response.count = Array.isArray(data) ? data.length : 1;
|
|
379
|
+
response.message = `Registros encontrados: ${response.count}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return response;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Crea uno o múltiples registros
|
|
387
|
+
* @param {string} table - Nombre de la tabla
|
|
388
|
+
* @param {Object|Array} data - Datos a insertar
|
|
389
|
+
* @returns {Object} - Respuesta JSON con registro(s) creado(s)
|
|
390
|
+
*/
|
|
391
|
+
create(table, data) {
|
|
392
|
+
try {
|
|
393
|
+
// Crear tabla si no existe
|
|
394
|
+
if (!this.tableExists(table)) {
|
|
395
|
+
const sampleData = Array.isArray(data) ? data[0] : data;
|
|
396
|
+
this._autoCreateTable(table, sampleData);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const isArray = Array.isArray(data);
|
|
400
|
+
const records = isArray ? data : [data];
|
|
401
|
+
const results = [];
|
|
402
|
+
|
|
403
|
+
for (const record of records) {
|
|
404
|
+
const filtered = {};
|
|
405
|
+
for (const [key, value] of Object.entries(record)) {
|
|
406
|
+
if (value !== undefined) filtered[key] = value;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const keys = Object.keys(filtered);
|
|
410
|
+
const values = Object.values(filtered);
|
|
411
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
412
|
+
|
|
413
|
+
const sql = `INSERT INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`;
|
|
414
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, values);
|
|
415
|
+
|
|
416
|
+
const stmt = this.db.prepare(sql);
|
|
417
|
+
const result = stmt.run(...values);
|
|
418
|
+
|
|
419
|
+
const newRecord = {
|
|
420
|
+
id: result.lastInsertRowid,
|
|
421
|
+
...filtered,
|
|
422
|
+
created_at: new Date().toISOString()
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
results.push(newRecord);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const responseData = isArray ? results : results[0];
|
|
429
|
+
return this._formatResponse(responseData, 'create', table);
|
|
430
|
+
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
operation: 'create',
|
|
435
|
+
error: error.message,
|
|
436
|
+
timestamp: new Date().toISOString()
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Lee registros de una tabla
|
|
443
|
+
* @param {string} table - Nombre de la tabla
|
|
444
|
+
* @param {Object} [conditions={}] - Condiciones de filtro
|
|
445
|
+
* @param {Object} [options={}] - Opciones de consulta
|
|
446
|
+
* @param {string} [options.orderBy] - Campo para ordenar
|
|
447
|
+
* @param {string} [options.order='ASC'] - Dirección del orden
|
|
448
|
+
* @param {number} [options.limit] - Límite de resultados
|
|
449
|
+
* @param {number} [options.offset] - Desplazamiento
|
|
450
|
+
* @returns {Object} - Respuesta JSON con registros encontrados
|
|
451
|
+
*/
|
|
452
|
+
read(table, conditions = {}, options = {}) {
|
|
453
|
+
try {
|
|
454
|
+
if (!this.tableExists(table)) {
|
|
455
|
+
return this._formatResponse([], 'read', table);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const { clause, params } = this._buildWhereClause(conditions);
|
|
459
|
+
let sql = `SELECT * FROM ${table} ${clause}`;
|
|
460
|
+
|
|
461
|
+
if (options.orderBy) {
|
|
462
|
+
sql += ` ORDER BY ${options.orderBy} ${options.order || 'ASC'}`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (options.limit) {
|
|
466
|
+
sql += ` LIMIT ${options.limit}`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (options.offset) {
|
|
470
|
+
sql += ` OFFSET ${options.offset}`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, params);
|
|
474
|
+
|
|
475
|
+
const stmt = this.db.prepare(sql);
|
|
476
|
+
const results = stmt.all(...params);
|
|
477
|
+
|
|
478
|
+
return this._formatResponse(results, 'read', table);
|
|
479
|
+
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return {
|
|
482
|
+
success: false,
|
|
483
|
+
operation: 'read',
|
|
484
|
+
error: error.message,
|
|
485
|
+
timestamp: new Date().toISOString()
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Lee un solo registro
|
|
492
|
+
* @param {string} table - Nombre de la tabla
|
|
493
|
+
* @param {Object} [conditions={}] - Condiciones de filtro
|
|
494
|
+
* @returns {Object} - Respuesta JSON con registro encontrado o null
|
|
495
|
+
*/
|
|
496
|
+
readOne(table, conditions = {}) {
|
|
497
|
+
try {
|
|
498
|
+
const result = this.read(table, conditions, { limit: 1 });
|
|
499
|
+
if (result.success && Array.isArray(result.data)) {
|
|
500
|
+
result.data = result.data[0] || null;
|
|
501
|
+
result.count = result.data ? 1 : 0;
|
|
502
|
+
result.message = result.data ? 'Registro encontrado' : 'No se encontró el registro';
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
return {
|
|
507
|
+
success: false,
|
|
508
|
+
operation: 'readOne',
|
|
509
|
+
error: error.message,
|
|
510
|
+
timestamp: new Date().toISOString()
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Actualiza registros
|
|
517
|
+
* @param {string} table - Nombre de la tabla
|
|
518
|
+
* @param {Object} conditions - Condiciones para seleccionar registros
|
|
519
|
+
* @param {Object} data - Datos a actualizar
|
|
520
|
+
* @returns {Object} - Respuesta JSON con resultado de la operación
|
|
521
|
+
*/
|
|
522
|
+
update(table, conditions, data) {
|
|
523
|
+
try {
|
|
524
|
+
if (!this.tableExists(table)) {
|
|
525
|
+
throw new Error(`La tabla '${table}' no existe`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const updateData = {};
|
|
529
|
+
for (const [key, value] of Object.entries(data)) {
|
|
530
|
+
if (value !== undefined) updateData[key] = value;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (Object.keys(updateData).length === 0) {
|
|
534
|
+
throw new Error('No hay datos para actualizar');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const setClause = Object.keys(updateData)
|
|
538
|
+
.map(key => `${key} = ?`)
|
|
539
|
+
.join(', ');
|
|
540
|
+
|
|
541
|
+
const { clause: whereClause, params: whereParams } = this._buildWhereClause(conditions);
|
|
542
|
+
|
|
543
|
+
if (!whereClause) {
|
|
544
|
+
throw new Error('Se requieren condiciones para actualizar');
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const params = [
|
|
548
|
+
...Object.values(updateData),
|
|
549
|
+
...whereParams
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
const sql = `UPDATE ${table} SET ${setClause} ${whereClause}`;
|
|
553
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, params);
|
|
554
|
+
|
|
555
|
+
const stmt = this.db.prepare(sql);
|
|
556
|
+
const result = stmt.run(...params);
|
|
557
|
+
|
|
558
|
+
const responseData = {
|
|
559
|
+
changes: result.changes,
|
|
560
|
+
message: `Actualizados ${result.changes} registro(s)`
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
return this._formatResponse(responseData, 'update', table);
|
|
564
|
+
|
|
565
|
+
} catch (error) {
|
|
566
|
+
return {
|
|
567
|
+
success: false,
|
|
568
|
+
operation: 'update',
|
|
569
|
+
error: error.message,
|
|
570
|
+
timestamp: new Date().toISOString()
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Elimina registros
|
|
577
|
+
* @param {string} table - Nombre de la tabla
|
|
578
|
+
* @param {Object} conditions - Condiciones para seleccionar registros
|
|
579
|
+
* @returns {Object} - Respuesta JSON con resultado de la operación
|
|
580
|
+
*/
|
|
581
|
+
delete(table, conditions) {
|
|
582
|
+
try {
|
|
583
|
+
if (!this.tableExists(table)) {
|
|
584
|
+
throw new Error(`La tabla '${table}' no existe`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const { clause, params } = this._buildWhereClause(conditions);
|
|
588
|
+
|
|
589
|
+
if (!clause) {
|
|
590
|
+
throw new Error('Se requieren condiciones para eliminar');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const sql = `DELETE FROM ${table} ${clause}`;
|
|
594
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, params);
|
|
595
|
+
|
|
596
|
+
const stmt = this.db.prepare(sql);
|
|
597
|
+
const result = stmt.run(...params);
|
|
598
|
+
|
|
599
|
+
const responseData = {
|
|
600
|
+
deleted: result.changes,
|
|
601
|
+
message: `Eliminados ${result.changes} registro(s)`
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
return this._formatResponse(responseData, 'delete', table);
|
|
605
|
+
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return {
|
|
608
|
+
success: false,
|
|
609
|
+
operation: 'delete',
|
|
610
|
+
error: error.message,
|
|
611
|
+
timestamp: new Date().toISOString()
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Busca registros por texto
|
|
618
|
+
* @param {string} table - Nombre de la tabla
|
|
619
|
+
* @param {string|Array} fields - Campo(s) donde buscar
|
|
620
|
+
* @param {string} searchTerm - Término de búsqueda
|
|
621
|
+
* @returns {Object} - Respuesta JSON con registros encontrados
|
|
622
|
+
*/
|
|
623
|
+
search(table, fields, searchTerm) {
|
|
624
|
+
try {
|
|
625
|
+
if (!this.tableExists(table)) {
|
|
626
|
+
return this._formatResponse([], 'search', table);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const fieldList = Array.isArray(fields) ? fields : [fields];
|
|
630
|
+
const conditions = fieldList.map(field => `${field} LIKE ?`).join(' OR ');
|
|
631
|
+
const params = fieldList.map(() => `%${searchTerm}%`);
|
|
632
|
+
|
|
633
|
+
const sql = `SELECT * FROM ${table} WHERE ${conditions}`;
|
|
634
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, params);
|
|
635
|
+
|
|
636
|
+
const stmt = this.db.prepare(sql);
|
|
637
|
+
const results = stmt.all(...params);
|
|
638
|
+
|
|
639
|
+
return this._formatResponse(results, 'search', table);
|
|
640
|
+
|
|
641
|
+
} catch (error) {
|
|
642
|
+
return {
|
|
643
|
+
success: false,
|
|
644
|
+
operation: 'search',
|
|
645
|
+
error: error.message,
|
|
646
|
+
timestamp: new Date().toISOString()
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Cuenta registros
|
|
653
|
+
* @param {string} table - Nombre de la tabla
|
|
654
|
+
* @param {Object} [conditions={}] - Condiciones de filtro
|
|
655
|
+
* @returns {Object} - Respuesta JSON con cantidad de registros
|
|
656
|
+
*/
|
|
657
|
+
count(table, conditions = {}) {
|
|
658
|
+
try {
|
|
659
|
+
if (!this.tableExists(table)) {
|
|
660
|
+
return {
|
|
661
|
+
success: true,
|
|
662
|
+
operation: 'count',
|
|
663
|
+
count: 0,
|
|
664
|
+
message: 'La tabla no existe',
|
|
665
|
+
timestamp: new Date().toISOString()
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const { clause, params } = this._buildWhereClause(conditions);
|
|
670
|
+
const sql = `SELECT COUNT(*) as total FROM ${table} ${clause}`;
|
|
671
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, params);
|
|
672
|
+
|
|
673
|
+
const stmt = this.db.prepare(sql);
|
|
674
|
+
const result = stmt.get(...params);
|
|
675
|
+
const count = result ? result.total : 0;
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
success: true,
|
|
679
|
+
operation: 'count',
|
|
680
|
+
count: count,
|
|
681
|
+
message: `Total de registros: ${count}`,
|
|
682
|
+
timestamp: new Date().toISOString()
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
} catch (error) {
|
|
686
|
+
return {
|
|
687
|
+
success: false,
|
|
688
|
+
operation: 'count',
|
|
689
|
+
error: error.message,
|
|
690
|
+
timestamp: new Date().toISOString()
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Ejecuta una consulta SQL personalizada
|
|
697
|
+
* @param {string} sql - Consulta SQL
|
|
698
|
+
* @param {Array} [params=[]] - Parámetros para la consulta
|
|
699
|
+
* @returns {Object} - Respuesta JSON con resultado de la consulta
|
|
700
|
+
*/
|
|
701
|
+
query(sql, params = []) {
|
|
702
|
+
try {
|
|
703
|
+
const stmt = this.db.prepare(sql);
|
|
704
|
+
const isSelect = sql.trim().toUpperCase().startsWith('SELECT');
|
|
705
|
+
|
|
706
|
+
if (this.config.verbose) console.log(`SQL: ${sql}`, params);
|
|
707
|
+
|
|
708
|
+
if (isSelect) {
|
|
709
|
+
const results = stmt.all(...params);
|
|
710
|
+
return {
|
|
711
|
+
success: true,
|
|
712
|
+
operation: 'query',
|
|
713
|
+
type: 'SELECT',
|
|
714
|
+
data: results,
|
|
715
|
+
count: results.length,
|
|
716
|
+
message: `Consulta ejecutada: ${results.length} resultados`,
|
|
717
|
+
timestamp: new Date().toISOString()
|
|
718
|
+
};
|
|
719
|
+
} else {
|
|
720
|
+
const result = stmt.run(...params);
|
|
721
|
+
return {
|
|
722
|
+
success: true,
|
|
723
|
+
operation: 'query',
|
|
724
|
+
type: 'EXECUTE',
|
|
725
|
+
changes: result.changes,
|
|
726
|
+
lastInsertRowid: result.lastInsertRowid,
|
|
727
|
+
message: `Consulta ejecutada: ${result.changes} cambios`,
|
|
728
|
+
timestamp: new Date().toISOString()
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
} catch (error) {
|
|
733
|
+
return {
|
|
734
|
+
success: false,
|
|
735
|
+
operation: 'query',
|
|
736
|
+
error: error.message,
|
|
737
|
+
timestamp: new Date().toISOString()
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Ejecuta operaciones en una transacción
|
|
744
|
+
* @param {Function} operations - Función con operaciones a ejecutar
|
|
745
|
+
* @returns {Object} - Respuesta JSON con resultado de la transacción
|
|
746
|
+
*/
|
|
747
|
+
transaction(operations) {
|
|
748
|
+
try {
|
|
749
|
+
this.db.exec('BEGIN TRANSACTION');
|
|
750
|
+
let result;
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
result = operations(this);
|
|
754
|
+
this.db.exec('COMMIT');
|
|
755
|
+
return {
|
|
756
|
+
success: true,
|
|
757
|
+
operation: 'transaction',
|
|
758
|
+
data: result,
|
|
759
|
+
message: 'Transacción completada exitosamente',
|
|
760
|
+
timestamp: new Date().toISOString()
|
|
761
|
+
};
|
|
762
|
+
} catch (error) {
|
|
763
|
+
this.db.exec('ROLLBACK');
|
|
764
|
+
throw error;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
} catch (error) {
|
|
768
|
+
return {
|
|
769
|
+
success: false,
|
|
770
|
+
operation: 'transaction',
|
|
771
|
+
error: error.message,
|
|
772
|
+
timestamp: new Date().toISOString()
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Lista todas las tablas
|
|
779
|
+
* @returns {Object} - Respuesta JSON con nombres de las tablas
|
|
780
|
+
*/
|
|
781
|
+
listTables() {
|
|
782
|
+
try {
|
|
783
|
+
const sql = `
|
|
784
|
+
SELECT name FROM sqlite_master
|
|
785
|
+
WHERE type='table'
|
|
786
|
+
AND name NOT LIKE 'sqlite_%'
|
|
787
|
+
AND name NOT LIKE '_easydb_%'
|
|
788
|
+
ORDER BY name
|
|
789
|
+
`;
|
|
790
|
+
const stmt = this.db.prepare(sql);
|
|
791
|
+
const tables = stmt.all().map(table => table.name);
|
|
792
|
+
|
|
793
|
+
return {
|
|
794
|
+
success: true,
|
|
795
|
+
operation: 'listTables',
|
|
796
|
+
tables: tables,
|
|
797
|
+
count: tables.length,
|
|
798
|
+
message: `Tablas encontradas: ${tables.length}`,
|
|
799
|
+
timestamp: new Date().toISOString()
|
|
800
|
+
};
|
|
801
|
+
} catch (error) {
|
|
802
|
+
return {
|
|
803
|
+
success: false,
|
|
804
|
+
operation: 'listTables',
|
|
805
|
+
error: error.message,
|
|
806
|
+
timestamp: new Date().toISOString()
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Obtiene información de una tabla
|
|
813
|
+
* @param {string} table - Nombre de la tabla
|
|
814
|
+
* @returns {Object} - Respuesta JSON con información de columnas
|
|
815
|
+
*/
|
|
816
|
+
tableInfo(table) {
|
|
817
|
+
try {
|
|
818
|
+
if (!this.tableExists(table)) {
|
|
819
|
+
throw new Error(`La tabla '${table}' no existe`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const sql = `PRAGMA table_info(${table})`;
|
|
823
|
+
const stmt = this.db.prepare(sql);
|
|
824
|
+
const columns = stmt.all();
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
success: true,
|
|
828
|
+
operation: 'tableInfo',
|
|
829
|
+
table: table,
|
|
830
|
+
columns: columns,
|
|
831
|
+
count: columns.length,
|
|
832
|
+
message: `Columnas en ${table}: ${columns.length}`,
|
|
833
|
+
timestamp: new Date().toISOString()
|
|
834
|
+
};
|
|
835
|
+
} catch (error) {
|
|
836
|
+
return {
|
|
837
|
+
success: false,
|
|
838
|
+
operation: 'tableInfo',
|
|
839
|
+
error: error.message,
|
|
840
|
+
timestamp: new Date().toISOString()
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Exporta toda la base de datos a JSON
|
|
847
|
+
* @returns {Object} - Respuesta JSON con datos exportados
|
|
848
|
+
*/
|
|
849
|
+
exportToJSON() {
|
|
850
|
+
try {
|
|
851
|
+
const tablesResult = this.listTables();
|
|
852
|
+
if (!tablesResult.success) throw new Error(tablesResult.error);
|
|
853
|
+
|
|
854
|
+
const tables = tablesResult.tables;
|
|
855
|
+
const exportData = {};
|
|
856
|
+
|
|
857
|
+
for (const table of tables) {
|
|
858
|
+
const result = this.read(table);
|
|
859
|
+
if (result.success) {
|
|
860
|
+
exportData[table] = result.data;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return {
|
|
865
|
+
success: true,
|
|
866
|
+
operation: 'exportToJSON',
|
|
867
|
+
metadata: {
|
|
868
|
+
exported_at: new Date().toISOString(),
|
|
869
|
+
version: this._getMeta('version'),
|
|
870
|
+
tables: tables.length
|
|
871
|
+
},
|
|
872
|
+
data: exportData,
|
|
873
|
+
message: `Base de datos exportada: ${tables.length} tablas`,
|
|
874
|
+
timestamp: new Date().toISOString()
|
|
875
|
+
};
|
|
876
|
+
} catch (error) {
|
|
877
|
+
return {
|
|
878
|
+
success: false,
|
|
879
|
+
operation: 'exportToJSON',
|
|
880
|
+
error: error.message,
|
|
881
|
+
timestamp: new Date().toISOString()
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Importa datos desde JSON
|
|
888
|
+
* @param {Object} data - Datos a importar
|
|
889
|
+
* @returns {Object} - Respuesta JSON con resultado de la importación
|
|
890
|
+
*/
|
|
891
|
+
importFromJSON(data) {
|
|
892
|
+
try {
|
|
893
|
+
let imported = 0;
|
|
894
|
+
let created = 0;
|
|
895
|
+
|
|
896
|
+
for (const [table, records] of Object.entries(data)) {
|
|
897
|
+
if (!this.tableExists(table) && records.length > 0) {
|
|
898
|
+
this._autoCreateTable(table, records[0]);
|
|
899
|
+
created++;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
for (const record of records) {
|
|
903
|
+
const result = this.create(table, record);
|
|
904
|
+
if (result.success) imported++;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
success: true,
|
|
910
|
+
operation: 'importFromJSON',
|
|
911
|
+
imported: imported,
|
|
912
|
+
tables_created: created,
|
|
913
|
+
message: `Importados ${imported} registros, creadas ${created} tablas`,
|
|
914
|
+
timestamp: new Date().toISOString()
|
|
915
|
+
};
|
|
916
|
+
} catch (error) {
|
|
917
|
+
return {
|
|
918
|
+
success: false,
|
|
919
|
+
operation: 'importFromJSON',
|
|
920
|
+
error: error.message,
|
|
921
|
+
timestamp: new Date().toISOString()
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Crea un backup de la base de datos
|
|
928
|
+
* @param {string} [backupPath] - Ruta para el backup
|
|
929
|
+
* @returns {Object} - Respuesta JSON con resultado del backup
|
|
930
|
+
*/
|
|
931
|
+
backup(backupPath) {
|
|
932
|
+
try {
|
|
933
|
+
const defaultPath = `backup_${Date.now()}.db`;
|
|
934
|
+
const targetPath = backupPath || defaultPath;
|
|
935
|
+
|
|
936
|
+
// Asegurar que el directorio del backup existe
|
|
937
|
+
ensureDirectoryExists(targetPath);
|
|
938
|
+
|
|
939
|
+
if (this.config.memory) {
|
|
940
|
+
const jsonData = this.exportToJSON();
|
|
941
|
+
fs.writeFileSync(
|
|
942
|
+
targetPath.replace('.db', '.json'),
|
|
943
|
+
JSON.stringify(jsonData, null, 2)
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
return {
|
|
947
|
+
success: true,
|
|
948
|
+
operation: 'backup',
|
|
949
|
+
path: path.resolve(targetPath.replace('.db', '.json')),
|
|
950
|
+
format: 'json',
|
|
951
|
+
message: 'Backup en memoria exportado a JSON',
|
|
952
|
+
timestamp: new Date().toISOString()
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
fs.copyFileSync(this.config.filename, targetPath);
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
success: true,
|
|
960
|
+
operation: 'backup',
|
|
961
|
+
path: path.resolve(targetPath),
|
|
962
|
+
format: 'sqlite',
|
|
963
|
+
size: fs.statSync(targetPath).size,
|
|
964
|
+
message: 'Backup de base de datos creado',
|
|
965
|
+
timestamp: new Date().toISOString()
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
} catch (error) {
|
|
969
|
+
return {
|
|
970
|
+
success: false,
|
|
971
|
+
operation: 'backup',
|
|
972
|
+
error: error.message,
|
|
973
|
+
timestamp: new Date().toISOString()
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Cierra la conexión a la base de datos
|
|
980
|
+
* @returns {Object} - Respuesta JSON de confirmación
|
|
981
|
+
*/
|
|
982
|
+
close() {
|
|
983
|
+
try {
|
|
984
|
+
this.db.close();
|
|
985
|
+
return {
|
|
986
|
+
success: true,
|
|
987
|
+
operation: 'close',
|
|
988
|
+
message: 'Conexión cerrada exitosamente',
|
|
989
|
+
timestamp: new Date().toISOString()
|
|
990
|
+
};
|
|
991
|
+
} catch (error) {
|
|
992
|
+
return {
|
|
993
|
+
success: false,
|
|
994
|
+
operation: 'close',
|
|
995
|
+
error: error.message,
|
|
996
|
+
timestamp: new Date().toISOString()
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Obtiene estadísticas de la base de datos
|
|
1003
|
+
* @returns {Object} - Respuesta JSON con estadísticas
|
|
1004
|
+
*/
|
|
1005
|
+
getStats() {
|
|
1006
|
+
try {
|
|
1007
|
+
const tablesResult = this.listTables();
|
|
1008
|
+
if (!tablesResult.success) throw new Error(tablesResult.error);
|
|
1009
|
+
|
|
1010
|
+
const tables = tablesResult.tables;
|
|
1011
|
+
const stats = {
|
|
1012
|
+
tables: tables.length,
|
|
1013
|
+
total_records: 0,
|
|
1014
|
+
table_stats: {}
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
for (const table of tables) {
|
|
1018
|
+
const countResult = this.count(table);
|
|
1019
|
+
if (countResult.success) {
|
|
1020
|
+
stats.total_records += countResult.count;
|
|
1021
|
+
const infoResult = this.tableInfo(table);
|
|
1022
|
+
stats.table_stats[table] = {
|
|
1023
|
+
records: countResult.count,
|
|
1024
|
+
columns: infoResult.success ? infoResult.count : 0
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
return {
|
|
1030
|
+
success: true,
|
|
1031
|
+
operation: 'getStats',
|
|
1032
|
+
stats: stats,
|
|
1033
|
+
metadata: {
|
|
1034
|
+
filename: path.resolve(this.config.filename),
|
|
1035
|
+
memory: this.config.memory,
|
|
1036
|
+
version: this._getMeta('version'),
|
|
1037
|
+
created: this._getMeta('created_at')
|
|
1038
|
+
},
|
|
1039
|
+
message: `Estadísticas: ${stats.tables} tablas, ${stats.total_records} registros`,
|
|
1040
|
+
timestamp: new Date().toISOString()
|
|
1041
|
+
};
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
return {
|
|
1044
|
+
success: false,
|
|
1045
|
+
operation: 'getStats',
|
|
1046
|
+
error: error.message,
|
|
1047
|
+
timestamp: new Date().toISOString()
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Obtiene información del archivo de la base de datos
|
|
1054
|
+
* @returns {Object} - Información del archivo
|
|
1055
|
+
*/
|
|
1056
|
+
getFileInfo() {
|
|
1057
|
+
try {
|
|
1058
|
+
if (this.config.memory) {
|
|
1059
|
+
return {
|
|
1060
|
+
success: true,
|
|
1061
|
+
is_memory: true,
|
|
1062
|
+
message: 'Base de datos en memoria',
|
|
1063
|
+
timestamp: new Date().toISOString()
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const filePath = this.config.filename;
|
|
1068
|
+
const exists = fs.existsSync(filePath);
|
|
1069
|
+
const info = exists ? fs.statSync(filePath) : null;
|
|
1070
|
+
|
|
1071
|
+
return {
|
|
1072
|
+
success: true,
|
|
1073
|
+
path: path.resolve(filePath),
|
|
1074
|
+
exists: exists,
|
|
1075
|
+
size: exists ? info.size : 0,
|
|
1076
|
+
created: exists ? info.birthtime : null,
|
|
1077
|
+
modified: exists ? info.mtime : null,
|
|
1078
|
+
is_memory: false,
|
|
1079
|
+
timestamp: new Date().toISOString()
|
|
1080
|
+
};
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
return {
|
|
1083
|
+
success: false,
|
|
1084
|
+
error: error.message,
|
|
1085
|
+
timestamp: new Date().toISOString()
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// ======================
|
|
1092
|
+
// INSTANCIA GLOBAL
|
|
1093
|
+
// ======================
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Crea una instancia de EasyDB
|
|
1097
|
+
* @param {string|Object} config - Configuración
|
|
1098
|
+
* @returns {EasyDB} - Instancia de EasyDB
|
|
1099
|
+
*/
|
|
1100
|
+
function createInstance(config = DEFAULT_DB_PATH) {
|
|
1101
|
+
return new EasyDB(config);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Instancia global predeterminada
|
|
1105
|
+
const defaultInstance = createInstance();
|
|
1106
|
+
|
|
1107
|
+
// API global para acceso rápido con respuestas JSON
|
|
1108
|
+
const db = {
|
|
1109
|
+
// Operaciones básicas
|
|
1110
|
+
create: (table, data) => defaultInstance.create(table, data),
|
|
1111
|
+
read: (table, conditions, options) => defaultInstance.read(table, conditions, options),
|
|
1112
|
+
readOne: (table, conditions) => defaultInstance.readOne(table, conditions),
|
|
1113
|
+
update: (table, conditions, data) => defaultInstance.update(table, conditions, data),
|
|
1114
|
+
delete: (table, conditions) => defaultInstance.delete(table, conditions),
|
|
1115
|
+
|
|
1116
|
+
// Operaciones avanzadas
|
|
1117
|
+
search: (table, fields, searchTerm) => defaultInstance.search(table, fields, searchTerm),
|
|
1118
|
+
count: (table, conditions) => defaultInstance.count(table, conditions),
|
|
1119
|
+
query: (sql, params) => defaultInstance.query(sql, params),
|
|
1120
|
+
transaction: (operations) => defaultInstance.transaction(operations),
|
|
1121
|
+
|
|
1122
|
+
// Nueva funcionalidad para agregar columnas
|
|
1123
|
+
addColumn: (tableName, columnName, columnType, defaultValue) =>
|
|
1124
|
+
defaultInstance.addColumn(tableName, columnName, columnType, defaultValue),
|
|
1125
|
+
addColumns: (tableName, columns) => defaultInstance.addColumns(tableName, columns),
|
|
1126
|
+
|
|
1127
|
+
// Metadatos y utilidades
|
|
1128
|
+
listTables: () => defaultInstance.listTables(),
|
|
1129
|
+
tableInfo: (table) => defaultInstance.tableInfo(table),
|
|
1130
|
+
exportToJSON: () => defaultInstance.exportToJSON(),
|
|
1131
|
+
importFromJSON: (data) => defaultInstance.importFromJSON(data),
|
|
1132
|
+
backup: (backupPath) => defaultInstance.backup(backupPath),
|
|
1133
|
+
close: () => defaultInstance.close(),
|
|
1134
|
+
getStats: () => defaultInstance.getStats(),
|
|
1135
|
+
getFileInfo: () => defaultInstance.getFileInfo(),
|
|
1136
|
+
|
|
1137
|
+
// Información del sistema
|
|
1138
|
+
getVersion: () => defaultInstance._getMeta('version'),
|
|
1139
|
+
|
|
1140
|
+
// Crear nueva instancia
|
|
1141
|
+
createInstance: (config) => createInstance(config),
|
|
1142
|
+
|
|
1143
|
+
// Variables globales (exportadas para uso externo)
|
|
1144
|
+
DB_PATH: DEFAULT_DB_PATH,
|
|
1145
|
+
ensureDirectoryExists: ensureDirectoryExists,
|
|
1146
|
+
|
|
1147
|
+
// Acceso directo a la instancia (opcional)
|
|
1148
|
+
_instance: defaultInstance
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
module.exports = db;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "andres-db",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Interfaz JavaScript simplificada para bases de datos SQLite con respuestas JSON estructuradas",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=14.0.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"sqlite",
|
|
14
|
+
"database",
|
|
15
|
+
"json",
|
|
16
|
+
"crud",
|
|
17
|
+
"nodejs",
|
|
18
|
+
"andres-db",
|
|
19
|
+
"simpledb",
|
|
20
|
+
"db"
|
|
21
|
+
],
|
|
22
|
+
"author": "ANDRES CARVAJAL OROZCO <https://andres-carvajal.vercel.app/>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"better-sqlite3": "^8.5.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.js",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|