@dbcube/cli 5.1.5 → 5.2.1

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 ADDED
@@ -0,0 +1,59 @@
1
+ # @dbcube/cli
2
+
3
+ Command-line interface for [DBCube](https://www.npmjs.com/package/dbcube): schema management with `.cube` files, tracked migrations with rollback, seeders, triggers, TypeScript type generation and database introspection.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @dbcube/cli
9
+ npx dbcube init
10
+ ```
11
+
12
+ ## Commands
13
+
14
+ | Command | Description |
15
+ |---|---|
16
+ | `npx dbcube init` | Scaffold a new project (config + `dbcube/` + example schema) |
17
+ | `npx dbcube generate` | Generate TypeScript types from `.table.cube` files |
18
+ | `npx dbcube validate` | Validate every `.cube` file without executing (exit 1 on errors — CI-friendly) |
19
+ | `npx dbcube doctor` | Diagnose config, binaries and database connectivity |
20
+ | `npx dbcube dev` | Watch mode: saving a `.cube` file applies it automatically |
21
+ | `npx dbcube run database:create` | Interactive database setup (config + physical creation) |
22
+ | `npx dbcube run table:fresh` | Drop and recreate all tables (asks for written confirmation; `--force` for CI) |
23
+ | `npx dbcube run table:refresh` | Apply schema changes **without** dropping data |
24
+ | `npx dbcube run table:alter` | Apply pending `.alter.cube` migrations (`--dry-run`, `--all`) |
25
+ | `npx dbcube migrate:status` | Show applied / pending / modified migrations |
26
+ | `npx dbcube migrate:rollback` | Revert the last migration batch |
27
+ | `npx dbcube run pull [db]` | Generate `.table.cube` files FROM an existing database (MySQL, PostgreSQL, SQLite) |
28
+ | `npx dbcube run seeder:add` | Run `.seeder.cube` data seeders |
29
+ | `npx dbcube run trigger:fresh` | Recreate triggers from `.trigger.cube` files |
30
+ | `npx dbcube update` | Check and update the Rust engine binaries |
31
+ | `npx dbcube run download <engine> [ver]` | Download a specific engine binary |
32
+ | `npx dbcube -v` | Versions of CLI, packages, engines, Node and platform |
33
+
34
+ Run `npx dbcube help` for the full reference.
35
+
36
+ ## Configuration
37
+
38
+ `dbcube.config.js` (created by `init`):
39
+
40
+ ```js
41
+ module.exports = function (config) {
42
+ config.set({
43
+ databases: {
44
+ myapp: {
45
+ type: "sqlite", // mysql | postgres | sqlite | mongodb
46
+ config: { DATABASE: "myapp" }
47
+ }
48
+ }
49
+ });
50
+ };
51
+ ```
52
+
53
+ ## Documentation
54
+
55
+ https://dbcube.org
56
+
57
+ ## License
58
+
59
+ MIT
package/package.json CHANGED
@@ -1,22 +1,38 @@
1
1
  {
2
2
  "name": "@dbcube/cli",
3
- "version": "5.1.5",
4
- "main": "index.js",
3
+ "version": "5.2.1",
4
+ "main": "src/index.js",
5
5
  "scripts": {
6
6
  "dbcube": "node src/index.js"
7
7
  },
8
8
  "bin": {
9
9
  "dbcube": "./src/index.js"
10
10
  },
11
- "keywords": [],
12
- "author": "",
13
- "license": "ISC",
14
- "description": "",
11
+ "files": [
12
+ "src"
13
+ ],
14
+ "keywords": [
15
+ "dbcube",
16
+ "cli",
17
+ "database",
18
+ "migrations",
19
+ "mysql",
20
+ "postgresql",
21
+ "sqlite",
22
+ "orm"
23
+ ],
24
+ "author": "Albert Araya",
25
+ "license": "MIT",
26
+ "description": "Command-line interface for DBCube: schema management (.cube files), migrations with rollback, seeders, triggers, TypeScript type generation and database introspection.",
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
15
30
  "dependencies": {
16
- "@dbcube/schema-builder": "^5.1.7",
31
+ "@dbcube/schema-builder": "^5.2.1",
17
32
  "@inquirer/prompts": "^8.5.2",
18
33
  "alwait": "^1.0.0",
19
34
  "chalk": "4.1.2",
35
+ "dbcube": "^5.2.1",
20
36
  "dotenv": "^17.4.2",
21
37
  "fs-extra": "^11.3.5",
22
38
  "glob": "^13.0.6",
@@ -3,7 +3,7 @@ const alwait = require('alwait');
3
3
  const ConfigFileUtils = require('../../../../utils/ConfigFileUtils');
4
4
  const { select, input } = require('@inquirer/prompts');
5
5
  const ora = require('ora');
6
- const { execSync } = require('child_process');
6
+ const { execFileSync } = require('child_process');
7
7
  const path = require('path');
8
8
 
9
9
  /**
@@ -18,10 +18,9 @@ async function createNewDatabase() {
18
18
 
19
19
  // Fase 1: Ejecutar addDatabaseConfig.js
20
20
  console.log('🔧 Iniciando configuración de base de datos...');
21
- const configCommand = `node "${path.join(__dirname, 'addDatabaseConfig.js')}"`;
22
21
 
23
22
  try {
24
- execSync(configCommand, {
23
+ execFileSync(process.execPath, [path.join(__dirname, 'addDatabaseConfig.js')], {
25
24
  stdio: 'inherit',
26
25
  cwd: process.cwd()
27
26
  });
@@ -37,20 +36,20 @@ async function createNewDatabase() {
37
36
 
38
37
  try {
39
38
  databaseName = fs.readFileSync(tempFile, 'utf8');
40
- // Limpiar el archivo temporal
41
- fs.unlinkSync(tempFile);
42
39
  } catch (error) {
43
40
  console.error('❌ Error: No se pudo obtener el nombre de la base de datos configurada');
44
41
  process.exit(1);
42
+ } finally {
43
+ // Limpiar el archivo temporal pase lo que pase
44
+ try { fs.unlinkSync(tempFile); } catch { /* ya no existe */ }
45
45
  }
46
46
 
47
47
  console.log('\n🏗️ Iniciando creación física de la base de datos...');
48
48
 
49
- // Fase 2: Ejecutar createDatabase.js
50
- const createCommand = `node "${path.join(__dirname, 'createDatabase.js')}" --name="${databaseName}"`;
51
-
49
+ // Fase 2: Ejecutar createDatabase.js. execFileSync con array de
50
+ // argumentos: el nombre no pasa por el shell (sin inyección de comandos)
52
51
  try {
53
- execSync(createCommand, {
52
+ execFileSync(process.execPath, [path.join(__dirname, 'createDatabase.js'), `--name=${databaseName}`], {
54
53
  stdio: 'inherit',
55
54
  cwd: process.cwd()
56
55
  });
@@ -75,10 +74,9 @@ async function createNewDatabase() {
75
74
  async function useExistingDatabase(databaseName) {
76
75
  try {
77
76
  // Ejecutar createDatabase.js con el nombre de la base de datos existente
78
- const createCommand = `node "${path.join(__dirname, 'createDatabase.js')}" --name="${databaseName}"`;
79
-
77
+ // (execFileSync: el nombre no pasa por el shell, sin inyección de comandos)
80
78
  try {
81
- execSync(createCommand, {
79
+ execFileSync(process.execPath, [path.join(__dirname, 'createDatabase.js'), `--name=${databaseName}`], {
82
80
  stdio: 'inherit',
83
81
  cwd: process.cwd()
84
82
  });
@@ -101,13 +101,70 @@ async function introspect(engine, dbName, motor) {
101
101
  if (!tables[fk.tn]) continue;
102
102
  tables[fk.tn].fks[fk.cn] = { table: fk.rt, column: fk.rc };
103
103
  }
104
+ } else if (motor === 'mongodb') {
105
+ // Introspección por muestreo: MongoDB no tiene esquema fijo, así que
106
+ // se infieren los tipos de los primeros 50 documentos por colección.
107
+ const listRes = await raw(engine, JSON.stringify({ listCollections: 1, nameOnly: true }));
108
+ const batch = listRes?.[0]?.cursor?.firstBatch ?? [];
109
+ const collections = batch
110
+ .map(c => c.name)
111
+ .filter(n => n && !n.startsWith('system.') && !n.startsWith('dbcube_'));
112
+
113
+ for (const coll of collections) {
114
+ const findRes = await raw(engine, JSON.stringify({ find: coll, limit: 50 }));
115
+ const docs = findRes?.[0]?.cursor?.firstBatch ?? [];
116
+
117
+ // campo -> { types: Set, seen: n, nullSeen: bool }
118
+ const fields = new Map();
119
+ for (const doc of docs) {
120
+ for (const [key, value] of Object.entries(doc)) {
121
+ if (key === '_id') continue; // gestionado por MongoDB
122
+ let f = fields.get(key);
123
+ if (!f) { f = { types: new Set(), seen: 0, nullSeen: false }; fields.set(key, f); }
124
+ f.seen++;
125
+ if (value === null) { f.nullSeen = true; continue; }
126
+ f.types.add(inferMongoCubeType(value));
127
+ }
128
+ }
129
+
130
+ tables[coll] = {
131
+ columns: [...fields.entries()].map(([name, f]) => {
132
+ const types = [...f.types];
133
+ const dataType = types.length === 1 ? types[0] : 'json'; // tipos mixtos → json
134
+ return {
135
+ name,
136
+ dataType,
137
+ length: dataType === 'varchar' ? '255' : null,
138
+ nullable: f.nullSeen || f.seen < docs.length,
139
+ defaultValue: null,
140
+ isPk: false,
141
+ autoinc: false,
142
+ enumValues: null,
143
+ };
144
+ }),
145
+ fks: {},
146
+ };
147
+ }
104
148
  } else {
105
- throw new Error(`pull is not supported yet for motor '${motor}' (mysql, postgresql, sqlite available)`);
149
+ throw new Error(`pull is not supported yet for motor '${motor}' (mysql, postgresql, sqlite, mongodb available)`);
106
150
  }
107
151
 
108
152
  return tables;
109
153
  }
110
154
 
155
+ function inferMongoCubeType(value) {
156
+ if (typeof value === 'boolean') return 'boolean';
157
+ if (typeof value === 'number') return Number.isInteger(value) ? 'int' : 'double';
158
+ if (typeof value === 'string') {
159
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) return 'datetime';
160
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return 'date';
161
+ return 'varchar';
162
+ }
163
+ // BSON dates llegan como {$date: ...} al serializar a JSON
164
+ if (value && typeof value === 'object' && '$date' in value) return 'datetime';
165
+ return 'json'; // objetos anidados y arrays
166
+ }
167
+
111
168
  function renderCube(dbName, tableName, table) {
112
169
  let out = `@database("${dbName}");\n\n`;
113
170
  out += `@meta({\n name: "${tableName}";\n description: "Imported from existing database by dbcube pull";\n});\n\n`;
@@ -16,14 +16,15 @@ async function main() {
16
16
  if (error.message.includes("reading 'init'")) {
17
17
  console.error('❌ Configuracion de base de datos no encontrada\n');
18
18
  console.error('Ejecute el comando para crear una nueva base de datos:');
19
- console.error(`\tdbcube run create:database`);
19
+ console.error(`\tnpx dbcube run database:create`);
20
20
  console.error('\nO verifique que la base de datos este configurada en el archivo dbcube.config.js\n');
21
21
  process.exit(1);
22
22
  } else if (error.message.includes("reading 'getDatabase'")) {
23
23
  console.error('- Se sugiere cambiar el linea o crear la base de datos a la que se hace referencia.');
24
24
  } else {
25
- console.error('Error aqui:', error);
26
- console.error('Error aqui:', error.message);
25
+ console.error(`❌ ${error.message}`);
26
+ if (process.env.DEBUG) console.error(error);
27
+ process.exit(1);
27
28
  }
28
29
  }
29
30
  }
@@ -1,35 +1,81 @@
1
- const { Schema } = require('@dbcube/schema-builder');
2
- const ConfigFileUtils = require('./../../../utils/ConfigFileUtils');
3
-
4
- async function main() {
5
- try {
6
- const args = process.argv.slice(2);
7
- const runSeeders = args.includes('--seeder');
8
-
9
- const configuredDatabases = await ConfigFileUtils.getConfiguredDatabases();
10
-
11
- for (const config of configuredDatabases) {
12
- const schema = new Schema(config.name);
13
- await schema.freshTables();
14
- if (runSeeders) {
15
- await schema.executeSeeders();
16
- }
17
- }
18
- } catch (error) {
19
- if (error.message.includes("reading 'init'")) {
20
- console.error('❌ Configuracion de base de datos no encontrada\n');
21
- console.error('Ejecute el comando para crear una nueva base de datos:');
22
- console.error(`\tdbcube run create:database`);
23
- console.error('\nO verifique que la base de datos este configurada en el archivo dbcube.config.js\n');
24
- process.exit(1);
25
- } else if (error.message.includes("reading 'getDatabase'")) {
26
- console.error('- Se sugiere cambiar el linea o crear la base de datos a la que se hace referencia.');
27
- } else {
28
- console.error('Error aqui:', error);
29
- console.error('Error aqui:', error.message);
30
- }
31
- }
32
- }
33
-
34
- // Ejecutar el ejemplo
35
- main().catch(console.error);
1
+ const chalk = require('chalk');
2
+ const { Schema } = require('@dbcube/schema-builder');
3
+ const ConfigFileUtils = require('./../../../utils/ConfigFileUtils');
4
+
5
+ /**
6
+ * dbcube run table:fresh [--seeder] [--force]
7
+ * DESTRUCTIVO: dropea y recrea todas las tablas desde los .table.cube.
8
+ * Pide confirmación ESCRITA (el nombre de la base de datos) salvo con --force.
9
+ */
10
+ async function confirmDestruction(databases) {
11
+ const { input } = require('@inquirer/prompts');
12
+
13
+ console.log('');
14
+ console.log(` ${chalk.bgRed.white.bold(' ⚠ DESTRUCTIVE OPERATION ')}`);
15
+ console.log('');
16
+ console.log(` ${chalk.red('table:fresh will DROP and recreate ALL tables.')}`);
17
+ console.log(` ${chalk.red.bold('ALL DATA WILL BE PERMANENTLY LOST')} in:`);
18
+ for (const db of databases) {
19
+ console.log(` ${chalk.yellow('')} ${chalk.bold(db.name)} ${chalk.gray(`(${db.type})`)}`);
20
+ }
21
+ console.log('');
22
+ console.log(` ${chalk.gray('To update schemas WITHOUT losing data use:')} ${chalk.white('npx dbcube run table:refresh')}`);
23
+ console.log('');
24
+
25
+ const expected = databases.length === 1 ? databases[0].name : 'all';
26
+ const answer = await input({
27
+ message: `Type ${chalk.bold(expected)} to confirm (anything else aborts):`,
28
+ });
29
+
30
+ return answer.trim() === expected;
31
+ }
32
+
33
+ async function main() {
34
+ try {
35
+ const args = process.argv.slice(2);
36
+ const runSeeders = args.includes('--seeder');
37
+ const force = args.includes('--force') || args.includes('-y') || args.includes('--yes');
38
+
39
+ const configuredDatabases = await ConfigFileUtils.getConfiguredDatabases();
40
+
41
+ if (!force) {
42
+ if (!process.stdin.isTTY) {
43
+ console.error(`\n${chalk.red('❌ table:fresh is destructive and needs confirmation.')}`);
44
+ console.error(`${chalk.gray('Non-interactive environment detected — pass')} ${chalk.white('--force')} ${chalk.gray('to proceed (e.g. in CI).')}\n`);
45
+ process.exit(1);
46
+ }
47
+ const confirmed = await confirmDestruction(configuredDatabases);
48
+ if (!confirmed) {
49
+ console.log(`\n${chalk.yellow('Aborted.')} ${chalk.gray('No changes were made.')}\n`);
50
+ process.exit(0);
51
+ }
52
+ }
53
+
54
+ for (const config of configuredDatabases) {
55
+ const schema = new Schema(config.name);
56
+ await schema.freshTables();
57
+ if (runSeeders) {
58
+ await schema.executeSeeders();
59
+ }
60
+ }
61
+ } catch (error) {
62
+ if (error.name === 'ExitPromptError') {
63
+ console.log(`\n${chalk.yellow('Aborted.')} ${chalk.gray('No changes were made.')}\n`);
64
+ process.exit(0);
65
+ }
66
+ if (error.message.includes("reading 'init'")) {
67
+ console.error('❌ Configuracion de base de datos no encontrada\n');
68
+ console.error('Ejecute el comando para crear una nueva base de datos:');
69
+ console.error(`\tdbcube run database:create`);
70
+ console.error('\nO verifique que la base de datos este configurada en el archivo dbcube.config.js\n');
71
+ process.exit(1);
72
+ } else if (error.message.includes("reading 'getDatabase'")) {
73
+ console.error('- Se sugiere cambiar el linea o crear la base de datos a la que se hace referencia.');
74
+ } else {
75
+ console.error('Error:', error.message);
76
+ process.exit(1);
77
+ }
78
+ }
79
+ }
80
+
81
+ main().catch(console.error);
@@ -2,7 +2,6 @@ const { Schema } = require('@dbcube/schema-builder');
2
2
  const ConfigFileUtils = require('./../../../utils/ConfigFileUtils');
3
3
 
4
4
  async function main() {
5
- console.clear();
6
5
  try {
7
6
  const configuredDatabases = await ConfigFileUtils.getConfiguredDatabases();
8
7
  // Recorrer cada archivo y mostrar su contenido
@@ -14,14 +13,15 @@ async function main() {
14
13
  if (error.message.includes("reading 'init'")) {
15
14
  console.error('❌ Configuracion de base de datos no encontrada\n');
16
15
  console.error('Ejecute el comando para crear una nueva base de datos:');
17
- console.error(`\tdbcube run create:database`);
16
+ console.error(`\tnpx dbcube run database:create`);
18
17
  console.error('\nO verifique que la base de datos este configurada en el archivo dbcube.config.js\n');
19
18
  process.exit(1);
20
19
  } else if (error.message.includes("reading 'getDatabase'")) {
21
20
  console.error('- Se sugiere cambiar el linea o crear la base de datos a la que se hace referencia.');
22
21
  } else {
23
- console.error('Error aqui:', error);
24
- console.error('Error aqui:', error.message);
22
+ console.error(`❌ ${error.message}`);
23
+ if (process.env.DEBUG) console.error(error);
24
+ process.exit(1);
25
25
  }
26
26
  }
27
27
  }
@@ -14,14 +14,15 @@ async function main() {
14
14
  if (error.message.includes("reading 'init'")) {
15
15
  console.error('❌ Configuracion de base de datos no encontrada\n');
16
16
  console.error('Ejecute el comando para crear una nueva base de datos:');
17
- console.error(`\tdbcube run create:database`);
17
+ console.error(`\tnpx dbcube run database:create`);
18
18
  console.error('\nO verifique que la base de datos este configurada en el archivo dbcube.config.js\n');
19
19
  process.exit(1);
20
20
  } else if (error.message.includes("reading 'getDatabase'")) {
21
21
  console.error('- Se sugiere cambiar el linea o crear la base de datos a la que se hace referencia.');
22
22
  } else {
23
- console.error('Error aqui:', error);
24
- console.error('Error aqui:', error.message);
23
+ console.error(`❌ ${error.message}`);
24
+ if (process.env.DEBUG) console.error(error);
25
+ process.exit(1);
25
26
  }
26
27
  }
27
28
  console.log('\n');
@@ -1,29 +1,66 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const chalk = require('chalk');
4
-
5
- /**
6
- * Muestra la versión actual del CLI de Dbcube
7
- */
8
- async function showVersion() {
9
- try {
10
- // Leer el package.json del CLI
11
- const packageJsonPath = path.join(__dirname, '../../package.json');
12
- const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
13
-
14
- console.log(`\n${chalk.green('🚀 Dbcube CLI')}`);
15
- console.log(`${chalk.cyan('Versión:')} ${chalk.bold(packageData.version)}`);
16
- console.log(`${chalk.cyan('Nombre:')} ${packageData.name}`);
17
- console.log(`${chalk.gray('Licencia:')} ${packageData.license}\n`);
18
-
19
- } catch (error) {
20
- console.error('❌ Error al obtener la versión:', error.message);
21
- process.exit(1);
22
- }
23
- }
24
-
25
- // Ejecutar función
26
- showVersion().catch(error => {
27
- console.error('Error fatal:', error);
28
- process.exit(1);
29
- });
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * dbcube version CLI, packages, engine binaries, and environment info.
7
+ */
8
+ function readVersion(pkgPath) {
9
+ try {
10
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ function findEngineVersions() {
17
+ const binDir = path.join(process.cwd(), '.dbcube', 'bin');
18
+ const engines = {};
19
+ try {
20
+ for (const f of fs.readdirSync(binDir)) {
21
+ const m = f.match(/^(query-engine|schema-engine|sqlite-engine)-v([\d.]+)-/);
22
+ if (m) engines[m[1]] = `v${m[2]}`;
23
+ }
24
+ } catch { /* no binaries downloaded yet */ }
25
+ return engines;
26
+ }
27
+
28
+ async function showVersion() {
29
+ try {
30
+ const cliVersion = readVersion(path.join(__dirname, '../../package.json'));
31
+ const row = (label, value, dim = false) =>
32
+ console.log(` ${chalk.gray(label.padEnd(16))} ${dim ? chalk.gray(value) : chalk.white(value)}`);
33
+
34
+ console.log('');
35
+ console.log(` ${chalk.green.bold('▣ DBCube CLI')} ${chalk.bold('v' + cliVersion)}`);
36
+ console.log(` ${chalk.gray('─'.repeat(40))}`);
37
+
38
+ // Local package versions (when resolvable from the project)
39
+ const localPkgs = ['dbcube', '@dbcube/query-builder', '@dbcube/schema-builder', '@dbcube/core'];
40
+ for (const pkg of localPkgs) {
41
+ try {
42
+ const pkgJson = require.resolve(`${pkg}/package.json`, { paths: [process.cwd()] });
43
+ const v = readVersion(pkgJson);
44
+ if (v) row(pkg, 'v' + v);
45
+ } catch { /* not installed in this project */ }
46
+ }
47
+
48
+ const engines = findEngineVersions();
49
+ for (const [name, v] of Object.entries(engines)) {
50
+ row(name, `${v} ${chalk.gray('(rust)')}`);
51
+ }
52
+
53
+ console.log(` ${chalk.gray('─'.repeat(40))}`);
54
+ row('node', process.version, true);
55
+ row('platform', `${process.platform}-${process.arch}`, true);
56
+ console.log('');
57
+ } catch (error) {
58
+ console.error('❌ Error al obtener la versión:', error.message);
59
+ process.exit(1);
60
+ }
61
+ }
62
+
63
+ showVersion().catch(error => {
64
+ console.error('Error fatal:', error);
65
+ process.exit(1);
66
+ });
package/src/index.js CHANGED
@@ -1,104 +1,162 @@
1
- #!/usr/bin/env node
2
- const path = require('path');
3
-
4
- // Obtener los argumentos pasados al comando
5
- const args = process.argv.slice(2);
6
-
7
- // Procesar los argumentos
8
- let mainCommand = '';
9
- const commandArgs = [];
10
-
11
- if (args.length > 0) {
12
- // Si el primer argumento es "run", combinamos con el segundo para formar el comando
13
- if (args[0] === 'run' && args.length > 1) {
14
- mainCommand = 'run:' + args[1];
15
- // Añadir el resto de argumentos
16
- for (let i = 2; i < args.length; i++) {
17
- commandArgs.push(args[i]);
18
- }
19
- } else if (args[0].includes('create:')) {
20
- // Si es un comando create: lo usamos directamente
21
- mainCommand = args[0];
22
- // Añadir el resto de argumentos
23
- for (let i = 1; i < args.length; i++) {
24
- commandArgs.push(args[i]);
25
- }
26
- } else {
27
- // Para otros comandos, usamos el primer argumento como comando
28
- mainCommand = args[0];
29
- // Añadir el resto de argumentos
30
- for (let i = 1; i < args.length; i++) {
31
- commandArgs.push(args[i]);
32
- }
33
- }
34
- }
35
-
36
- // Mapa de comandos para mapear nombres de comandos a rutas de archivo
37
- const commandMap = {
38
- 'run:table:fresh': '../src/commands/run/table/fresh.js',
39
- 'run:table:refresh': '../src/commands/run/table/refresh.js',
40
- 'run:table:alter': '../src/commands/run/table/alter.js',
41
-
42
- 'run:trigger:fresh': '../src/commands/run/trigger/fresh.js',
43
-
44
- 'run:seeder:add': '../src/commands/run/seeder/add.js',
45
-
46
- 'run:database:create': '../src/commands/run/database/create/index.js',
47
- 'run:database:create:config': '../src/commands/run/database/create/addDatabaseConfig.js',
48
- 'run:database:create:physical': '../src/commands/run/database/create/createDatabase.js',
49
-
50
- 'run:pull': '../src/commands/run/pull.js',
51
-
52
- 'run:download': '../src/commands/run/download.js',
53
- 'run:update': '../src/commands/run/update.js',
54
-
55
- 'init': '../src/commands/init.js',
56
- 'generate': '../src/commands/generate.js',
57
- 'validate': '../src/commands/validate.js',
58
- 'doctor': '../src/commands/doctor.js',
59
- 'dev': '../src/commands/dev.js',
60
-
61
- 'migrate:status': '../src/commands/migrate/status.js',
62
- 'migrate:rollback': '../src/commands/migrate/rollback.js',
63
-
64
- 'update': '../src/commands/update.js',
65
-
66
- '--version': '../src/commands/version.js',
67
- '-v': '../src/commands/version.js',
68
-
69
- 'help': '../src/commands/help.js',
70
- '--help': '../src/commands/help.js',
71
- '-h': '../src/commands/help.js',
72
- };
73
-
74
- // Función para ejecutar comandos basados en los argumentos
75
- async function executeCommand(command, commandArgs) {
76
- // Verificar si el comando existe en el mapa
77
- if (commandMap[command]) {
78
- const examplePath = path.join(__dirname, commandMap[command]);
79
-
80
- // Guardamos los argumentos originales
81
- const originalArgv = process.argv;
82
-
83
- try {
84
- // Reemplazamos los argumentos con los que necesitamos pasar al script
85
- process.argv = [process.argv[0], process.argv[1], ...commandArgs];
86
- await require(examplePath);
87
- } finally {
88
- // Restauramos los argumentos originales
89
- process.argv = originalArgv;
90
- }
91
- } else {
92
- // Comando desconocido - mostrar help
93
- const chalk = require('chalk');
94
- console.log(`\n${chalk.red('❌ Unknown command:')} ${command} ${commandArgs.join(' ')}\n`);
95
- console.log(`${chalk.cyan('Run')} ${chalk.yellow('npx dbcube help')} ${chalk.cyan('to see all available commands.')}\n`);
96
- process.exit(1);
97
- }
98
- }
99
-
100
- // Ejecutar el comando correspondiente
101
- executeCommand(mainCommand, commandArgs).catch(err => {
102
- console.error('Error inesperado:', err);
103
- process.exit(1);
104
- });
1
+ #!/usr/bin/env node
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ // Obtener los argumentos pasados al comando
6
+ const args = process.argv.slice(2);
7
+
8
+ // Procesar los argumentos
9
+ let mainCommand = '';
10
+ const commandArgs = [];
11
+
12
+ if (args.length > 0) {
13
+ // Si el primer argumento es "run", combinamos con el segundo para formar el comando
14
+ if (args[0] === 'run' && args.length > 1) {
15
+ mainCommand = 'run:' + args[1];
16
+ for (let i = 2; i < args.length; i++) {
17
+ commandArgs.push(args[i]);
18
+ }
19
+ } else if (args[0].includes('create:')) {
20
+ mainCommand = args[0];
21
+ for (let i = 1; i < args.length; i++) {
22
+ commandArgs.push(args[i]);
23
+ }
24
+ } else {
25
+ mainCommand = args[0];
26
+ for (let i = 1; i < args.length; i++) {
27
+ commandArgs.push(args[i]);
28
+ }
29
+ }
30
+ } else {
31
+ // Sin argumentos: mostrar ayuda en lugar de fallar
32
+ mainCommand = '--help';
33
+ }
34
+
35
+ // Mapa de comandos para mapear nombres de comandos a rutas de archivo
36
+ const commandMap = {
37
+ 'run:table:fresh': '../src/commands/run/table/fresh.js',
38
+ 'run:table:refresh': '../src/commands/run/table/refresh.js',
39
+ 'run:table:alter': '../src/commands/run/table/alter.js',
40
+
41
+ 'run:trigger:fresh': '../src/commands/run/trigger/fresh.js',
42
+
43
+ 'run:seeder:add': '../src/commands/run/seeder/add.js',
44
+
45
+ 'run:database:create': '../src/commands/run/database/create/index.js',
46
+ 'run:database:create:config': '../src/commands/run/database/create/addDatabaseConfig.js',
47
+ 'run:database:create:physical': '../src/commands/run/database/create/createDatabase.js',
48
+ // Alias sin "run" (el help histórico lo documenta así)
49
+ 'database:create': '../src/commands/run/database/create/index.js',
50
+
51
+ 'run:pull': '../src/commands/run/pull.js',
52
+
53
+ 'run:download': '../src/commands/run/download.js',
54
+ 'run:update': '../src/commands/run/update.js',
55
+
56
+ 'init': '../src/commands/init.js',
57
+ 'generate': '../src/commands/generate.js',
58
+ 'validate': '../src/commands/validate.js',
59
+ 'doctor': '../src/commands/doctor.js',
60
+ 'dev': '../src/commands/dev.js',
61
+
62
+ 'migrate:status': '../src/commands/migrate/status.js',
63
+ 'migrate:rollback': '../src/commands/migrate/rollback.js',
64
+
65
+ 'update': '../src/commands/update.js',
66
+
67
+ 'version': '../src/commands/version.js',
68
+ '--version': '../src/commands/version.js',
69
+ '-v': '../src/commands/version.js',
70
+
71
+ 'help': '../src/commands/help.js',
72
+ '--help': '../src/commands/help.js',
73
+ '-h': '../src/commands/help.js',
74
+ };
75
+
76
+ // Distancia de Levenshtein para sugerencias "did you mean"
77
+ function levenshtein(a, b) {
78
+ const m = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
79
+ for (let j = 0; j <= b.length; j++) m[0][j] = j;
80
+ for (let i = 1; i <= a.length; i++) {
81
+ for (let j = 1; j <= b.length; j++) {
82
+ m[i][j] = Math.min(
83
+ m[i - 1][j] + 1,
84
+ m[i][j - 1] + 1,
85
+ m[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
86
+ );
87
+ }
88
+ }
89
+ return m[a.length][b.length];
90
+ }
91
+
92
+ function suggestCommand(input) {
93
+ // Candidatos: forma completa ("run table:fresh") y forma corta ("table:fresh"),
94
+ // ambos mostrando siempre la forma completa al usuario.
95
+ const candidates = [];
96
+ for (const c of Object.keys(commandMap)) {
97
+ if (c.startsWith('-')) continue;
98
+ if (c.startsWith('run:')) {
99
+ const display = 'run ' + c.slice(4);
100
+ candidates.push({ match: display, display });
101
+ candidates.push({ match: c.slice(4), display });
102
+ } else {
103
+ candidates.push({ match: c, display: c });
104
+ }
105
+ }
106
+
107
+ const scored = candidates
108
+ .map(({ match, display }) => ({ display, d: levenshtein(input.toLowerCase(), match.toLowerCase()) }))
109
+ .sort((x, y) => x.d - y.d);
110
+
111
+ if (scored.length === 0 || scored[0].d > Math.max(3, Math.floor(input.length / 2))) return [];
112
+
113
+ const seen = new Set();
114
+ return scored
115
+ .filter(s => s.d <= scored[0].d + 1)
116
+ .filter(s => !seen.has(s.display) && seen.add(s.display))
117
+ .slice(0, 3)
118
+ .map(s => s.display);
119
+ }
120
+
121
+ // Función para ejecutar comandos basados en los argumentos
122
+ async function executeCommand(command, commandArgs) {
123
+ if (commandMap[command]) {
124
+ const examplePath = path.join(__dirname, commandMap[command]);
125
+ const originalArgv = process.argv;
126
+
127
+ try {
128
+ process.argv = [process.argv[0], process.argv[1], ...commandArgs];
129
+ await require(examplePath);
130
+ } finally {
131
+ process.argv = originalArgv;
132
+ }
133
+ } else {
134
+ // Comando desconocido: error claro + sugerencias
135
+ const typed = [args[0], args[1]].filter(Boolean).join(' ');
136
+ console.log('');
137
+ console.log(` ${chalk.bgRed.white.bold(' ERROR ')} ${chalk.red(`Unknown command:`)} ${chalk.bold(typed)}`);
138
+
139
+ const suggestions = suggestCommand(typed);
140
+ if (suggestions.length > 0) {
141
+ console.log('');
142
+ console.log(` ${chalk.cyan('Did you mean:')}`);
143
+ for (const s of suggestions) {
144
+ console.log(` ${chalk.green('→')} ${chalk.white('npx dbcube ' + s)}`);
145
+ }
146
+ }
147
+
148
+ console.log('');
149
+ console.log(` ${chalk.gray('Run')} ${chalk.yellow('npx dbcube help')} ${chalk.gray('to see all available commands.')}`);
150
+ console.log('');
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ // Ejecutar el comando correspondiente
156
+ executeCommand(mainCommand, commandArgs).catch(err => {
157
+ console.log('');
158
+ console.log(` ${chalk.bgRed.white.bold(' FATAL ')} ${chalk.red(err.message)}`);
159
+ console.log(` ${chalk.gray('Run')} ${chalk.yellow('npx dbcube doctor')} ${chalk.gray('to diagnose your setup.')}`);
160
+ console.log('');
161
+ process.exit(1);
162
+ });
@@ -78,8 +78,9 @@ class ConfigFileUtils {
78
78
  }
79
79
  return arrayDatabases;
80
80
  } catch (error) {
81
- console.error('Error al cargar las bases de datos desde ' + configPath, error);
82
- return ['test1']; // Valor por defecto
81
+ // Fallar claro: devolver una DB inventada ocultaría el problema
82
+ // y haría que los comandos operen contra una base inexistente
83
+ throw new Error(`No se pudo cargar la configuración desde ${configPath}: ${error.message}`);
83
84
  }
84
85
  }
85
86
 
package/.npmignore DELETED
@@ -1,58 +0,0 @@
1
- # Directories
2
- examples
3
-
4
- # Ignorar dependencias y configuraciones de desarrollo
5
- node_modules/
6
- npm-debug.log*
7
- yarn-debug.log*
8
- yarn-error.log*
9
-
10
- # Ignorar carpetas y archivos irrelevantes
11
- .vscode/
12
- .lh
13
- .idea/
14
- .DS_Store
15
- Thumbs.db
16
- *.log
17
-
18
- # Ignorar configuraciones del proyecto
19
- .env
20
- .env.*.local
21
- package-lock.json
22
-
23
- # Ignorar archivos del sistema
24
- *.swp
25
- *.swo
26
- *.tmp
27
- *.temp
28
-
29
- # Ignorar carpetas de trabajo
30
- temp/
31
- logs/
32
- debug/
33
-
34
- # Ignorar archivos de compilación
35
- src/
36
- tsconfig.json
37
- tsconfig.tsbuildinfo
38
-
39
- # Ignorar pruebas y configuraciones
40
- tests/
41
- __tests__/
42
- __mocks__/
43
- coverage/
44
- jest.config.js
45
-
46
- # Ignorar documentación o ejemplos no necesarios
47
- docs/
48
- examples/
49
-
50
- # Asegurarse de incluir solo lo esencial
51
- !.npmignore
52
- tsconfig.json
53
- tsup.config.ts
54
-
55
- .dbcube
56
- dbcube
57
- .env
58
- *.db
package/bun.lock DELETED
@@ -1,192 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "name": "@dbcube/cli",
6
- "dependencies": {
7
- "@dbcube/schema-builder": "^5.1.7",
8
- "@inquirer/prompts": "^8.5.2",
9
- "alwait": "^1.0.0",
10
- "chalk": "4.1.2",
11
- "dotenv": "^17.4.2",
12
- "fs-extra": "^11.3.5",
13
- "glob": "^13.0.6",
14
- "ora": "5.4.1",
15
- "unzipper": "^0.12.3",
16
- },
17
- },
18
- },
19
- "overrides": {
20
- "chalk": "4.1.2",
21
- "ora": "5.4.1",
22
- },
23
- "packages": {
24
- "@dbcube/core": ["@dbcube/core@5.1.15", "", { "dependencies": { "chalk": "4.1.2", "deasync": "^0.1.31", "follow-redirects": "^1.16.0", "ora": "5.4.1", "unzipper": "^0.12.3" }, "bin": { "dbcube-core": "dist/bin.cjs" } }, "sha512-/sA4Mya1pqb77Ki8LVm8mPnQVZb6OFsfxjR58rP8OeAOp/uWDWrBfL5GFdi8+a2+oZYJvz5d1DA/nEBB3jYjSA=="],
25
-
26
- "@dbcube/schema-builder": ["@dbcube/schema-builder@5.1.7", "", { "dependencies": { "@dbcube/core": "^5.1.15", "chalk": "^5.6.2", "ora": "^9.4.0" } }, "sha512-oTea6R1EgKxwRjI7N1IkFnMZZms3pOMzPVATUaCZzHiUSol+IgHZ5rYQJvGIGXAs1s4lGndE1OokISHoc6o89g=="],
27
-
28
- "@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="],
29
-
30
- "@inquirer/checkbox": ["@inquirer/checkbox@5.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/core": "^11.2.1", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw=="],
31
-
32
- "@inquirer/confirm": ["@inquirer/confirm@6.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ=="],
33
-
34
- "@inquirer/core": ["@inquirer/core@11.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA=="],
35
-
36
- "@inquirer/editor": ["@inquirer/editor@5.2.2", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/external-editor": "^3.0.3", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg=="],
37
-
38
- "@inquirer/expand": ["@inquirer/expand@5.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g=="],
39
-
40
- "@inquirer/external-editor": ["@inquirer/external-editor@3.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA=="],
41
-
42
- "@inquirer/figures": ["@inquirer/figures@2.0.7", "", {}, "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw=="],
43
-
44
- "@inquirer/input": ["@inquirer/input@5.1.2", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg=="],
45
-
46
- "@inquirer/number": ["@inquirer/number@4.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA=="],
47
-
48
- "@inquirer/password": ["@inquirer/password@5.1.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg=="],
49
-
50
- "@inquirer/prompts": ["@inquirer/prompts@8.5.2", "", { "dependencies": { "@inquirer/checkbox": "^5.2.1", "@inquirer/confirm": "^6.1.1", "@inquirer/editor": "^5.2.2", "@inquirer/expand": "^5.1.1", "@inquirer/input": "^5.1.2", "@inquirer/number": "^4.1.1", "@inquirer/password": "^5.1.1", "@inquirer/rawlist": "^5.3.1", "@inquirer/search": "^4.2.1", "@inquirer/select": "^5.2.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g=="],
51
-
52
- "@inquirer/rawlist": ["@inquirer/rawlist@5.3.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og=="],
53
-
54
- "@inquirer/search": ["@inquirer/search@4.2.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g=="],
55
-
56
- "@inquirer/select": ["@inquirer/select@5.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/core": "^11.2.1", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw=="],
57
-
58
- "@inquirer/type": ["@inquirer/type@4.0.7", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g=="],
59
-
60
- "alwait": ["alwait@1.0.0", "", {}, "sha512-o+jEoGVTJyvYDT5mOWkj2i4IsOC7SNCWGc/+o6rAfGx1x3jCBIeFsCwgW/3ZiNJ2dE3Hr1lzxC2a++6dLRLGqQ=="],
61
-
62
- "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
63
-
64
- "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
65
-
66
- "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
67
-
68
- "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
69
-
70
- "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
71
-
72
- "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
73
-
74
- "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
75
-
76
- "brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="],
77
-
78
- "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
79
-
80
- "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
81
-
82
- "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
83
-
84
- "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
85
-
86
- "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
87
-
88
- "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
89
-
90
- "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
91
-
92
- "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
93
-
94
- "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
95
-
96
- "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
97
-
98
- "deasync": ["deasync@0.1.31", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^1.7.1" } }, "sha512-/6/cXqkw4LPqBVK6H0Y3L4zT7yI3pxykxPXErQ2tDCw0LJyThWL5VpBCpUOWX0vPq2OnF1pzcXvlNnvCiOQJuA=="],
99
-
100
- "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
101
-
102
- "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
103
-
104
- "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="],
105
-
106
- "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
107
-
108
- "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
109
-
110
- "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],
111
-
112
- "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
113
-
114
- "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
115
-
116
- "fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="],
117
-
118
- "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
119
-
120
- "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
121
-
122
- "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
123
-
124
- "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
125
-
126
- "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
127
-
128
- "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
129
-
130
- "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
131
-
132
- "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
133
-
134
- "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
135
-
136
- "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
137
-
138
- "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
139
-
140
- "lru-cache": ["lru-cache@11.2.1", "", {}, "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ=="],
141
-
142
- "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
143
-
144
- "minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="],
145
-
146
- "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
147
-
148
- "mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
149
-
150
- "node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
151
-
152
- "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
153
-
154
- "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
155
-
156
- "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
157
-
158
- "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
159
-
160
- "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
161
-
162
- "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
163
-
164
- "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
165
-
166
- "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
167
-
168
- "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
169
-
170
- "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
171
-
172
- "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
173
-
174
- "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
175
-
176
- "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
177
-
178
- "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
179
-
180
- "unzipper": ["unzipper@0.12.3", "", { "dependencies": { "bluebird": "~3.7.2", "duplexer2": "~0.1.4", "fs-extra": "^11.2.0", "graceful-fs": "^4.2.2", "node-int64": "^0.4.0" } }, "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA=="],
181
-
182
- "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
183
-
184
- "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
185
-
186
- "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
187
-
188
- "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
189
-
190
- "unzipper/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="],
191
- }
192
- }
package/dbcube.config.js DELETED
@@ -1,15 +0,0 @@
1
- require('dotenv').config({ quiet: true });
2
-
3
- module.exports = function (config) {
4
- config.set({
5
- databases: {
6
- test: {
7
- type: "sqlite",
8
- config:{
9
- DATABASE: process.env.DBCUBE_TEST_DATABASE
10
- }
11
- },
12
- }
13
- });
14
- };
15
-
@@ -1,3 +0,0 @@
1
- onlyBuiltDependencies:
2
- - '@dbcube/core'
3
- - better-sqlite3
@@ -1,129 +0,0 @@
1
- /**
2
- * End-to-end test of the new query-builder features against the local
3
- * SQLite test database (daemon mode + transactions + new methods).
4
- * Run: node test-new-features.js
5
- */
6
- const { Database } = require('@dbcube/query-builder');
7
-
8
- const results = [];
9
- function check(name, condition, detail = '') {
10
- results.push({ name, ok: !!condition, detail });
11
- console.log(`${condition ? '✅' : '❌'} ${name}${detail ? ` — ${detail}` : ''}`);
12
- }
13
-
14
- async function main() {
15
- const db = new Database('test');
16
- const t0 = Date.now();
17
-
18
- // 0. Clean slate for the test table via raw()
19
- await db.raw('DROP TABLE IF EXISTS qa_items');
20
- await db.raw('CREATE TABLE qa_items (uuid TEXT, id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, qty INTEGER DEFAULT 0, price REAL)');
21
- check('raw(): DDL ejecutado', true);
22
-
23
- // 1. insert
24
- const inserted = await db.table('qa_items').insert([
25
- { name: 'alpha', qty: 10, price: 5.5 },
26
- { name: 'beta', qty: 3, price: 2.0 },
27
- { name: 'gamma', qty: 0, price: 9.9 },
28
- ]);
29
- check('insert(): 3 filas', Array.isArray(inserted));
30
-
31
- // 2. raw with params
32
- const rawRows = await db.raw('SELECT * FROM qa_items WHERE qty > ?', [1]);
33
- check('raw() con parámetros', Array.isArray(rawRows) && rawRows.length === 2, `${rawRows.length} filas`);
34
-
35
- // 3. exists / whereNotIn / offset
36
- const hasAlpha = await db.table('qa_items').where('name', '=', 'alpha').exists();
37
- const noDelta = await db.table('qa_items').where('name', '=', 'delta').exists();
38
- check('exists()', hasAlpha === true && noDelta === false);
39
-
40
- const notIn = await db.table('qa_items').whereNotIn('name', ['alpha']).get();
41
- check('whereNotIn()', notIn.length === 2, `${notIn.length} filas`);
42
-
43
- const offsetRows = await db.table('qa_items').orderBy('id', 'ASC').limit(10).offset(1).get();
44
- check('offset()', offsetRows.length === 2 && offsetRows[0].name === 'beta');
45
-
46
- // 4. paginate
47
- const page = await db.table('qa_items').orderBy('id', 'ASC').paginate(1, 2);
48
- check('paginate()', page.items.length === 2 && page.total === 3 && page.totalPages === 2 && page.hasNext === true,
49
- JSON.stringify({ total: page.total, pages: page.totalPages }));
50
-
51
- // 5. chunk
52
- let chunkTotal = 0, chunkCalls = 0;
53
- await db.table('qa_items').orderBy('id', 'ASC').chunk(2, rows => { chunkTotal += rows.length; chunkCalls++; });
54
- check('chunk()', chunkTotal === 3 && chunkCalls === 2, `${chunkCalls} lotes, ${chunkTotal} filas`);
55
-
56
- // 6. increment / decrement (atómico)
57
- await db.table('qa_items').where('name', '=', 'beta').increment('qty', 5);
58
- await db.table('qa_items').where('name', '=', 'alpha').decrement('qty', 4);
59
- const beta = await db.table('qa_items').find('beta', 'name');
60
- const alpha = await db.table('qa_items').find('alpha', 'name');
61
- check('increment()/decrement()', beta.qty === 8 && alpha.qty === 6, `beta=${beta.qty} alpha=${alpha.qty}`);
62
-
63
- // 7. upsert (SQLite necesita UNIQUE en la clave de conflicto)
64
- await db.raw('CREATE UNIQUE INDEX IF NOT EXISTS qa_items_name ON qa_items(name)');
65
- await db.table('qa_items').upsert([{ name: 'alpha', qty: 100, price: 1.0 }, { name: 'delta', qty: 7, price: 3.3 }], ['name']);
66
- const alphaUp = await db.table('qa_items').find('alpha', 'name');
67
- const delta = await db.table('qa_items').find('delta', 'name');
68
- check('upsert()', alphaUp.qty === 100 && delta !== null, `alpha.qty=${alphaUp.qty}, delta insertado=${delta !== null}`);
69
-
70
- // 8. having + selectRaw + groupBy
71
- const grouped = await db.table('qa_items')
72
- .selectRaw(['CASE WHEN qty > 5 THEN \'high\' ELSE \'low\' END AS bucket', 'COUNT(*) AS n'])
73
- .groupBy('bucket')
74
- .having('n', '>', 1)
75
- .get();
76
- check('selectRaw()+groupBy()+having()', Array.isArray(grouped) && grouped.length >= 1, JSON.stringify(grouped));
77
-
78
- // 9. transaction commit
79
- await db.transaction(async (trx) => {
80
- await trx.table('qa_items').where('name', '=', 'beta').update({ price: 111 });
81
- await trx.table('qa_items').insert([{ name: 'epsilon', qty: 1, price: 0.5 }]);
82
- });
83
- const betaTx = await db.table('qa_items').find('beta', 'name');
84
- const eps = await db.table('qa_items').find('epsilon', 'name');
85
- check('transaction() commit', betaTx.price === 111 && eps !== null);
86
-
87
- // 10. transaction rollback
88
- let rolledBack = false;
89
- try {
90
- await db.transaction(async (trx) => {
91
- await trx.table('qa_items').where('name', '=', 'beta').update({ price: 999 });
92
- throw new Error('forced failure');
93
- });
94
- } catch (e) {
95
- rolledBack = e.message === 'forced failure';
96
- }
97
- const betaRb = await db.table('qa_items').find('beta', 'name');
98
- check('transaction() rollback', rolledBack && betaRb.price === 111, `price=${betaRb.price} (esperado 111)`);
99
-
100
- // 11. truncate
101
- await db.raw('CREATE TABLE IF NOT EXISTS qa_tmp (uuid TEXT, id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT)');
102
- await db.table('qa_tmp').insert([{ v: 'x' }, { v: 'y' }]);
103
- await db.table('qa_tmp').truncate();
104
- const tmpCount = await db.table('qa_tmp').count();
105
- check('truncate()', Number(tmpCount) === 0);
106
-
107
- // 12. Velocidad: 30 queries seguidas (daemon vs spawn)
108
- const n = 30;
109
- const t1 = Date.now();
110
- for (let i = 0; i < n; i++) {
111
- await db.table('qa_items').where('qty', '>', 0).limit(1).get();
112
- }
113
- const elapsed = Date.now() - t1;
114
- const avg = (elapsed / n).toFixed(1);
115
- check('velocidad: 30 queries seguidas', true, `${elapsed}ms total → ${avg}ms/query promedio`);
116
-
117
- // Cleanup
118
- await db.raw('DROP TABLE IF EXISTS qa_items');
119
- await db.raw('DROP TABLE IF EXISTS qa_tmp');
120
-
121
- const failed = results.filter(r => !r.ok);
122
- console.log(`\n${failed.length === 0 ? '🎉 TODOS LOS TESTS PASARON' : `❌ ${failed.length} fallos`} (${results.length} checks, ${Date.now() - t0}ms)`);
123
- process.exit(failed.length === 0 ? 0 : 1);
124
- }
125
-
126
- main().catch(e => {
127
- console.error('💥 Test crashed:', e.message);
128
- process.exit(1);
129
- });