@dbcube/cli 5.1.4 → 5.1.5
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/bun.lock +20 -20
- package/package.json +3 -3
- package/src/commands/dev.js +83 -0
- package/src/commands/doctor.js +99 -0
- package/src/commands/generate.js +160 -0
- package/src/commands/help.js +24 -8
- package/src/commands/init.js +82 -0
- package/src/commands/migrate/lib.js +125 -0
- package/src/commands/migrate/rollback.js +72 -0
- package/src/commands/migrate/status.js +55 -0
- package/src/commands/run/pull.js +192 -0
- package/src/commands/run/table/alter.js +55 -3
- package/src/commands/validate.js +70 -0
- package/src/index.js +12 -0
- package/test-new-features.js +129 -0
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
});
|