@onurege3467/zerohelper 10.2.5 → 10.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +432 -54
  2. package/dist/bin/commands/cache.d.ts +2 -0
  3. package/dist/bin/commands/cache.js +92 -0
  4. package/dist/bin/commands/db-backup.d.ts +3 -0
  5. package/dist/bin/commands/db-backup.js +118 -0
  6. package/dist/bin/commands/db.d.ts +2 -0
  7. package/dist/bin/commands/db.js +334 -0
  8. package/dist/bin/commands/import-export.d.ts +3 -0
  9. package/dist/bin/commands/import-export.js +123 -0
  10. package/dist/bin/commands/init.d.ts +2 -0
  11. package/dist/bin/commands/init.js +85 -0
  12. package/dist/bin/commands/migrate.d.ts +3 -0
  13. package/dist/bin/commands/migrate.js +167 -0
  14. package/dist/bin/commands/repl.d.ts +2 -0
  15. package/dist/bin/commands/repl.js +96 -0
  16. package/dist/bin/commands/seed.d.ts +2 -0
  17. package/dist/bin/commands/seed.js +76 -0
  18. package/dist/bin/commands/zpack.d.ts +2 -0
  19. package/dist/bin/commands/zpack.js +36 -0
  20. package/dist/bin/index.d.ts +2 -0
  21. package/dist/bin/index.js +28 -0
  22. package/dist/bin/types.d.ts +22 -0
  23. package/dist/bin/types.js +2 -0
  24. package/dist/bin/utils/config.d.ts +3 -0
  25. package/dist/bin/utils/config.js +78 -0
  26. package/dist/bin/utils/prompts.d.ts +3 -0
  27. package/dist/bin/utils/prompts.js +115 -0
  28. package/dist/bin/zero.js +789 -81
  29. package/dist/migrations/1767521950635_test_migration.d.ts +3 -0
  30. package/dist/migrations/1767521950635_test_migration.js +11 -0
  31. package/dist/migrations/1767522158826_create_users_table.d.ts +2 -0
  32. package/dist/migrations/1767522158826_create_users_table.js +11 -0
  33. package/dist/package.json +79 -0
  34. package/dist/zero.config.d.ts +10 -0
  35. package/dist/zero.config.js +13 -0
  36. package/package.json +2 -2
package/dist/bin/zero.js CHANGED
@@ -11,87 +11,421 @@ const ora_1 = __importDefault(require("ora"));
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const index_1 = require("../index");
14
+ const package_json_1 = require("../package.json");
14
15
  const program = new commander_1.Command();
15
16
  program
16
17
  .name('zero')
17
- .description(chalk_1.default.cyan('ZeroHelper - The Elite Node.js Development Framework'))
18
- .version('9.1.0');
19
- // --- 1. INTERACTIVE INIT ---
18
+ .description(chalk_1.default.cyan('ZeroHelper CLI - Elite Database Management Tool'))
19
+ .version(package_json_1.version);
20
+ function loadConfig(configPath) {
21
+ const absolutePath = path_1.default.resolve(process.cwd(), configPath);
22
+ if (!fs_1.default.existsSync(absolutePath)) {
23
+ throw new Error(`Configuration file not found: ${configPath}`);
24
+ }
25
+ try {
26
+ const modulePath = require.resolve(absolutePath);
27
+ delete require.cache[modulePath];
28
+ const config = require(absolutePath);
29
+ if (!config.zeroConfig) {
30
+ throw new Error('Configuration file must export "zeroConfig"');
31
+ }
32
+ return config.zeroConfig;
33
+ }
34
+ catch (error) {
35
+ if (error.code === 'MODULE_NOT_FOUND') {
36
+ throw new Error(`Cannot load config. Make sure TypeScript is compiled: ${error.message}`);
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ async function getDatabase(configPath) {
42
+ const config = loadConfig(configPath);
43
+ const db = index_1.database.createDatabase(config);
44
+ return db;
45
+ }
20
46
  program.command('init')
21
- .description('Set up ZeroHelper in your project (Interactive)')
47
+ .description('Initialize ZeroHelper in your project (Interactive)')
22
48
  .action(async () => {
23
- console.log(chalk_1.default.bold.blue('\nšŸš€ Welcome to ZeroHelper v9.1.0\n'));
49
+ console.log(chalk_1.default.bold.blue(`\nšŸš€ Welcome to ZeroHelper v${package_json_1.version} Setup\n`));
24
50
  const answers = await inquirer_1.default.prompt([
25
51
  {
26
52
  type: 'list',
27
53
  name: 'adapter',
28
- message: 'Which database adapter would you like to use?',
29
- choices: ['zpack', 'json', 'sqlite', 'mysql', 'postgres', 'mongodb', 'redis']
30
- },
31
- {
32
- type: 'input',
33
- name: 'path',
34
- message: 'Enter data storage path (e.g., ./data/zero.db):',
35
- default: (ans) => ans.adapter === 'zpack' ? './zero.zpack' : './zero.json'
54
+ message: 'Select database adapter:',
55
+ choices: [
56
+ { name: 'šŸ“„ JSON (Simple file-based)', value: 'json' },
57
+ { name: 'šŸ“¦ ZPack (High-performance binary)', value: 'zpack' },
58
+ { name: 'šŸ’¾ SQLite (Embedded SQL)', value: 'sqlite' },
59
+ { name: '🐬 MySQL (Network SQL)', value: 'mysql' },
60
+ { name: '🐘 PostgreSQL (Network SQL)', value: 'postgres' },
61
+ { name: 'šŸƒ MongoDB (Document NoSQL)', value: 'mongodb' },
62
+ { name: '⚔ Redis (In-memory cache)', value: 'redis' },
63
+ { name: 'šŸ“Š TOON (Native TOON DB)', value: 'toon' }
64
+ ]
36
65
  },
37
66
  {
38
67
  type: 'confirm',
39
- name: 'cache',
40
- message: 'Enable intelligent caching layer?',
68
+ name: 'enableCache',
69
+ message: 'Enable caching layer?',
41
70
  default: true
42
71
  }
43
72
  ]);
44
- const spinner = (0, ora_1.default)('Generating configuration...').start();
45
- const configTemplate = `
46
- /**
73
+ const prompts = [];
74
+ if (['json', 'zpack', 'sqlite', 'toon'].includes(answers.adapter)) {
75
+ prompts.push({
76
+ type: 'input',
77
+ name: 'filePath',
78
+ message: 'Enter database file path:',
79
+ default: (ans) => {
80
+ const ext = ans.adapter === 'json' ? '.json' :
81
+ ans.adapter === 'zpack' ? '.zpack' :
82
+ ans.adapter === 'toon' ? '.toon' : '.db';
83
+ return `./data/storage${ext}`;
84
+ },
85
+ validate: (input) => input.length > 0 || 'Path is required'
86
+ });
87
+ }
88
+ else {
89
+ prompts.push({
90
+ type: 'input',
91
+ name: 'host',
92
+ message: 'Database host:',
93
+ default: 'localhost',
94
+ validate: (input) => input.length > 0 || 'Host is required'
95
+ }, {
96
+ type: 'number',
97
+ name: 'port',
98
+ message: 'Database port:',
99
+ default: (ans) => {
100
+ const ports = { mysql: 3306, postgres: 5432, mongodb: 27017, redis: 6379 };
101
+ return ports[ans.adapter] || 3306;
102
+ }
103
+ }, {
104
+ type: 'input',
105
+ name: 'username',
106
+ message: 'Username:',
107
+ default: 'root',
108
+ when: (ans) => ans.adapter !== 'mongodb'
109
+ }, {
110
+ type: 'password',
111
+ name: 'password',
112
+ message: 'Password (leave empty if none):'
113
+ }, {
114
+ type: 'input',
115
+ name: 'database',
116
+ message: 'Database name:',
117
+ default: 'zero_db',
118
+ validate: (input) => input.length > 0 || 'Database name is required'
119
+ });
120
+ }
121
+ if (answers.enableCache) {
122
+ prompts.push({
123
+ type: 'list',
124
+ name: 'cacheType',
125
+ message: 'Cache type:',
126
+ choices: ['memory', 'redis'],
127
+ default: 'memory'
128
+ });
129
+ prompts.push({
130
+ type: 'number',
131
+ name: 'cacheTtl',
132
+ message: 'Cache TTL (seconds):',
133
+ default: 300,
134
+ when: (ans) => ans.cacheType === 'memory'
135
+ });
136
+ }
137
+ const extraAnswers = await inquirer_1.default.prompt(prompts);
138
+ const finalAnswers = { ...answers, ...extraAnswers };
139
+ const spinner = (0, ora_1.default)('Creating configuration...').start();
140
+ try {
141
+ const configObject = buildConfig(finalAnswers);
142
+ const configTemplate = formatConfigTemplate(finalAnswers, configObject);
143
+ fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'zero.config.ts'), configTemplate);
144
+ spinner.succeed(chalk_1.default.green('āœ… zero.config.ts created successfully!'));
145
+ console.log(chalk_1.default.gray('\nšŸ“ Usage example:'));
146
+ console.log(chalk_1.default.yellow(`import { database } from '@onurege3467/zerohelper';`));
147
+ console.log(chalk_1.default.yellow(`import { zeroConfig } from './zero.config';`));
148
+ console.log(chalk_1.default.yellow(`const db = database.createDatabase(zeroConfig);`));
149
+ }
150
+ catch (error) {
151
+ spinner.fail(chalk_1.default.red('Configuration creation failed'));
152
+ console.error(chalk_1.default.red(error.message));
153
+ process.exit(1);
154
+ }
155
+ });
156
+ function buildConfig(answers) {
157
+ if (['json', 'zpack', 'sqlite', 'toon'].includes(answers.adapter)) {
158
+ return JSON.stringify({
159
+ adapter: answers.adapter,
160
+ config: {
161
+ path: answers.filePath,
162
+ ...(answers.enableCache && {
163
+ cache: {
164
+ type: answers.cacheType || 'memory',
165
+ ...(answers.cacheType === 'memory' && answers.cacheTtl && { ttl: answers.cacheTtl * 1000 })
166
+ }
167
+ })
168
+ }
169
+ }, null, 2);
170
+ }
171
+ const config = {
172
+ adapter: answers.adapter,
173
+ config: {
174
+ host: answers.host,
175
+ port: answers.port,
176
+ ...(answers.username && { username: answers.username }),
177
+ ...(answers.password && { password: answers.password }),
178
+ database: answers.database,
179
+ ...(answers.adapter === 'mongodb' && { url: `mongodb://${answers.host}:${answers.port}/${answers.database}` })
180
+ }
181
+ };
182
+ if (answers.enableCache && answers.cacheType) {
183
+ config.config.cache = {
184
+ type: answers.cacheType
185
+ };
186
+ if (answers.cacheType === 'redis') {
187
+ config.config.cache.host = answers.host;
188
+ config.config.cache.port = 6379;
189
+ }
190
+ else if (answers.cacheTtl) {
191
+ config.config.cache.ttl = answers.cacheTtl * 1000;
192
+ }
193
+ }
194
+ return JSON.stringify(config, null, 2);
195
+ }
196
+ function formatConfigTemplate(answers, configObject) {
197
+ return `/**
47
198
  * ZeroHelper Configuration
48
199
  * Generated on ${new Date().toLocaleDateString()}
49
200
  */
50
- export const zeroConfig = {
51
- adapter: '${answers.adapter}',
52
- config: {
53
- ${answers.adapter === 'sqlite' || answers.adapter === 'zpack' || answers.adapter === 'json'
54
- ? `filePath: '${answers.path}',\n filename: '${answers.path}',`
55
- : `host: 'localhost',\n user: 'root',\n database: 'zero_db',`}
56
- ${answers.cache ? `cache: { type: 'memory', ttl: 60000 },` : ''}
57
- }
201
+ export const zeroConfig = ${configObject};
58
202
  `;
59
- fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'zero.config.ts'), configTemplate);
60
- spinner.succeed(chalk_1.default.green('zero.config.ts created successfully!'));
61
- console.log(chalk_1.default.gray('\nYou can now initialize your database with:'));
62
- console.log(chalk_1.default.yellow('import { database } from "@onurege3467/zerohelper";'));
63
- console.log(chalk_1.default.yellow('import { zeroConfig } from "./zero.config";'));
64
- console.log(chalk_1.default.yellow('const db = database.createDatabase(zeroConfig);'));
65
- });
66
- // --- 2. DATABASE INSPECTION ---
203
+ }
204
+ program.command('db:test')
205
+ .description('Test database connection and show basic stats')
206
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
207
+ .action(async (options) => {
208
+ const spinner = (0, ora_1.default)('Testing database connection...').start();
209
+ try {
210
+ const db = await getDatabase(options.config);
211
+ spinner.succeed(chalk_1.default.green('āœ… Database connection successful'));
212
+ console.log(chalk_1.default.bold('\nšŸ“Š Database Stats:'));
213
+ console.log(chalk_1.default.cyan(' Adapter:'), chalk_1.default.white(db.constructor.name));
214
+ console.log(chalk_1.default.cyan(' Status:'), chalk_1.default.green('Connected'));
215
+ try {
216
+ const metrics = db.getMetrics();
217
+ console.log(chalk_1.default.cyan(' Operations:'), chalk_1.default.white(metrics.database.count));
218
+ console.log(chalk_1.default.cyan(' Avg Latency:'), chalk_1.default.white(`${metrics.database.averageDuration.toFixed(2)}ms`));
219
+ }
220
+ catch (err) {
221
+ console.log(chalk_1.default.yellow(' Note: Metrics not available for this adapter'));
222
+ }
223
+ await db.close();
224
+ }
225
+ catch (error) {
226
+ spinner.fail(chalk_1.default.red('āŒ Connection failed'));
227
+ console.error(chalk_1.default.red(error.message));
228
+ process.exit(1);
229
+ }
230
+ });
67
231
  program.command('db:stats')
68
- .description('Show database performance and health metrics')
232
+ .description('Show detailed database performance metrics')
69
233
  .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
70
234
  .action(async (options) => {
71
235
  try {
72
- const configPath = path_1.default.resolve(process.cwd(), options.config);
73
- if (!fs_1.default.existsSync(configPath)) {
74
- console.error(chalk_1.default.red(`Error: Configuration file not found at ${options.config}`));
236
+ const db = await getDatabase(options.config);
237
+ const metrics = db.getMetrics();
238
+ console.log(chalk_1.default.bold('\nšŸ“Š Database Performance Dashboard'));
239
+ console.log(chalk_1.default.gray('─'.repeat(50)));
240
+ console.log(chalk_1.default.bold('\nšŸ”¹ Database Operations:'));
241
+ if (metrics.database) {
242
+ const count = metrics.database.count ?? 0;
243
+ const totalDuration = metrics.database.totalDuration ?? 0;
244
+ const avgLatency = metrics.database.averageDuration ?? 0;
245
+ const minDuration = metrics.database.minDuration ?? 0;
246
+ const maxDuration = metrics.database.maxDuration ?? 0;
247
+ const formatNum = (n) => typeof n === 'number' ? n.toFixed(2) : n;
248
+ console.log(` Total Operations: ${chalk_1.default.cyan(count)}`);
249
+ console.log(` Total Duration: ${chalk_1.default.cyan(`${formatNum(totalDuration)} ms`)}`);
250
+ console.log(` Avg Latency: ${chalk_1.default.green(`${formatNum(avgLatency)} ms`)}`);
251
+ console.log(` Min Duration: ${chalk_1.default.yellow(`${formatNum(minDuration)} ms`)}`);
252
+ console.log(` Max Duration: ${chalk_1.default.yellow(`${formatNum(maxDuration)} ms`)}`);
253
+ }
254
+ else {
255
+ console.log(chalk_1.default.gray(' No metrics available for this adapter'));
256
+ }
257
+ if (metrics.cache) {
258
+ console.log(chalk_1.default.bold('\nšŸ”¹ Cache Performance:'));
259
+ console.log(` Total Requests: ${chalk_1.default.cyan((metrics.cache.hits || 0) + (metrics.cache.misses || 0))}`);
260
+ console.log(` Cache Hits: ${chalk_1.default.green(metrics.cache.hits || 0)}`);
261
+ console.log(` Cache Misses: ${chalk_1.default.red(metrics.cache.misses || 0)}`);
262
+ const hits = metrics.cache.hits || 0;
263
+ const misses = metrics.cache.misses || 0;
264
+ const total = hits + misses;
265
+ if (total > 0) {
266
+ const ratio = (hits / total) * 100;
267
+ console.log(` Hit Ratio: ${ratio.toFixed(1)}% ${ratio > 80 ? 'āœ…' : ratio > 50 ? 'āš ļø' : 'āŒ'}`);
268
+ }
269
+ }
270
+ console.log(chalk_1.default.gray('\n' + '─'.repeat(50)));
271
+ await db.close();
272
+ }
273
+ catch (error) {
274
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
275
+ process.exit(1);
276
+ }
277
+ });
278
+ program.command('db:seed')
279
+ .description('Seed table with mock data')
280
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
281
+ .option('-t, --table <name>', 'Table name')
282
+ .option('-n, --count <number>', 'Number of records', '10')
283
+ .action(async (options) => {
284
+ if (!options.table) {
285
+ console.error(chalk_1.default.red('Error: --table option is required'));
286
+ process.exit(1);
287
+ }
288
+ const spinner = (0, ora_1.default)(`Seeding ${options.table}...`).start();
289
+ try {
290
+ const db = await getDatabase(options.config);
291
+ const seeder = new index_1.database.DataSeeder(db);
292
+ const schema = {};
293
+ const fieldTypes = ['string', 'number', 'email', 'boolean', 'date'];
294
+ for (let i = 0; i < 3; i++) {
295
+ const fieldType = fieldTypes[Math.floor(Math.random() * fieldTypes.length)];
296
+ schema[`field_${i + 1}`] = { type: fieldType };
297
+ }
298
+ const count = await seeder.seed(options.table, parseInt(options.count), schema);
299
+ spinner.succeed(chalk_1.default.green(`āœ… Seeded ${count} records into ${options.table}`));
300
+ await db.close();
301
+ }
302
+ catch (error) {
303
+ spinner.fail(chalk_1.default.red('āŒ Seeding failed'));
304
+ console.error(chalk_1.default.red(error.message));
305
+ process.exit(1);
306
+ }
307
+ });
308
+ program.command('migrate')
309
+ .description('Run pending migrations')
310
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
311
+ .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations')
312
+ .action(async (options) => {
313
+ const spinner = (0, ora_1.default)('Loading migrations...').start();
314
+ try {
315
+ const db = await getDatabase(options.config);
316
+ const migration = new index_1.database.MigrationManager(db, {
317
+ migrationsDir: options.migrationsDir
318
+ });
319
+ const pending = await migration.getPendingMigrations();
320
+ if (pending.length === 0) {
321
+ spinner.succeed(chalk_1.default.green('āœ… No pending migrations'));
322
+ await db.close();
75
323
  return;
76
324
  }
77
- console.log(chalk_1.default.blue('šŸ“Š Fetching Real-time Database Metrics...'));
78
- // In a real CLI, we would import the config and connect to the DB
79
- // Here we show a beautiful mock dashboard
80
- console.log('\n' + chalk_1.default.bold.underline('SYSTEM DASHBOARD'));
81
- console.log(`${chalk_1.default.cyan('Uptime:')} 99.9%`);
82
- console.log(`${chalk_1.default.cyan('Status:')} ${chalk_1.default.bgGreen.black(' HEALTHY ')}`);
83
- console.log(`${chalk_1.default.cyan('Latency:')} 12ms (avg)`);
84
- console.log(`${chalk_1.default.cyan('Cache Hit Rate:')} 87%`);
85
- console.log('\n' + chalk_1.default.yellow('Recent Operations:'));
86
- console.log(`- ${chalk_1.default.green('INSERT')} users [4ms]`);
87
- console.log(`- ${chalk_1.default.green('SELECT')} products [2ms]`);
88
- console.log(`- ${chalk_1.default.green('UPDATE')} settings [15ms]`);
325
+ spinner.text = `Running ${pending.length} migration(s)...`;
326
+ for (const m of pending) {
327
+ await migration.runMigration(m, 'up');
328
+ spinner.text = `āœ… ${m.name}`;
329
+ }
330
+ spinner.succeed(chalk_1.default.green(`āœ… ${pending.length} migration(s) executed successfully`));
331
+ await db.close();
332
+ }
333
+ catch (error) {
334
+ spinner.fail(chalk_1.default.red('āŒ Migration failed'));
335
+ console.error(chalk_1.default.red(error.message));
336
+ process.exit(1);
337
+ }
338
+ });
339
+ program.command('migration:rollback')
340
+ .description('Rollback the last migration(s)')
341
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
342
+ .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations')
343
+ .option('-s, --steps <number>', 'Number of migrations to rollback', '1')
344
+ .action(async (options) => {
345
+ const steps = parseInt(options.steps);
346
+ const { confirm } = await inquirer_1.default.prompt([
347
+ {
348
+ type: 'confirm',
349
+ name: 'confirm',
350
+ message: chalk_1.default.yellow(`āš ļø Are you sure you want to rollback ${steps} migration(s)?`),
351
+ default: false
352
+ }
353
+ ]);
354
+ if (!confirm) {
355
+ console.log(chalk_1.default.yellow('Rollback cancelled'));
356
+ return;
357
+ }
358
+ const spinner = (0, ora_1.default)(`Rolling back ${steps} migration(s)...`).start();
359
+ try {
360
+ const db = await getDatabase(options.config);
361
+ const migration = new index_1.database.MigrationManager(db, {
362
+ migrationsDir: options.migrationsDir
363
+ });
364
+ await migration.rollback(steps);
365
+ spinner.succeed(chalk_1.default.green(`āœ… Rolled back ${steps} migration(s)`));
366
+ await db.close();
367
+ }
368
+ catch (error) {
369
+ spinner.fail(chalk_1.default.red('āŒ Rollback failed'));
370
+ console.error(chalk_1.default.red(error.message));
371
+ process.exit(1);
372
+ }
373
+ });
374
+ program.command('migration:status')
375
+ .description('Show migration status')
376
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
377
+ .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations')
378
+ .action(async (options) => {
379
+ try {
380
+ const db = await getDatabase(options.config);
381
+ const migration = new index_1.database.MigrationManager(db, {
382
+ migrationsDir: options.migrationsDir
383
+ });
384
+ const all = migration.getMigrationFiles();
385
+ const executed = await migration.getExecutedMigrations();
386
+ console.log(chalk_1.default.bold('\nšŸ“‹ Migration Status'));
387
+ console.log(chalk_1.default.gray('─'.repeat(50)));
388
+ if (all.length === 0) {
389
+ console.log(chalk_1.default.yellow('No migrations found'));
390
+ }
391
+ else {
392
+ all.forEach(m => {
393
+ const isExecuted = executed.includes(m.name);
394
+ const status = isExecuted ? chalk_1.default.green('āœ… Done') : chalk_1.default.yellow('ā³ Pending');
395
+ console.log(` ${status} ${chalk_1.default.white(m.name)}`);
396
+ });
397
+ console.log(chalk_1.default.gray('\n' + '─'.repeat(50)));
398
+ console.log(` Total: ${all.length} | Executed: ${chalk_1.default.green(executed.length)} | Pending: ${chalk_1.default.yellow(all.length - executed.length)}`);
399
+ }
400
+ await db.close();
89
401
  }
90
- catch (e) {
91
- console.error(chalk_1.default.red(`CLI Error: ${e.message}`));
402
+ catch (error) {
403
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
404
+ process.exit(1);
92
405
  }
93
406
  });
94
- // --- 3. ZPACK VACUUM ---
407
+ program.command('make:migration')
408
+ .description('Generate a new migration template')
409
+ .argument('<name>', 'Name of the migration')
410
+ .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations')
411
+ .action((name, options) => {
412
+ const timestamp = Date.now();
413
+ const fileName = `${timestamp}_${name}.ts`;
414
+ const migrationsDir = path_1.default.join(process.cwd(), options.migrationsDir);
415
+ if (!fs_1.default.existsSync(migrationsDir)) {
416
+ fs_1.default.mkdirSync(migrationsDir, { recursive: true });
417
+ }
418
+ const template = `export const up = async (db: any) => {
419
+ // Write your migration logic here
420
+ };
421
+
422
+ export const down = async (db: any) => {
423
+ // Write your rollback logic here
424
+ };
425
+ `;
426
+ fs_1.default.writeFileSync(path_1.default.join(migrationsDir, fileName), template);
427
+ console.log(chalk_1.default.green(`\nāœ… Migration created: ./${options.migrationsDir}/${fileName}`));
428
+ });
95
429
  program.command('zpack:vacuum')
96
430
  .description('Compact a ZPack binary file to save disk space')
97
431
  .argument('<file>', 'ZPack file path')
@@ -106,36 +440,410 @@ program.command('zpack:vacuum')
106
440
  await db.vacuum();
107
441
  await db.close();
108
442
  const endSize = fs_1.default.statSync(file).size;
109
- spinner.succeed(chalk_1.default.green(`Vacuum complete for ${file}`));
110
- console.log(`${chalk_1.default.gray('Original Size:')} ${startSize} bytes`);
111
- console.log(`${chalk_1.default.gray('New Size:')} ${endSize} bytes`);
112
- console.log(`${chalk_1.default.bold.blue('Efficiency:')} ${Math.round((1 - endSize / (startSize || 1)) * 100)}% reduction`);
443
+ const reduction = startSize > 0 ? ((1 - endSize / startSize) * 100) : 0;
444
+ spinner.succeed(chalk_1.default.green(`āœ… Vacuum complete for ${file}`));
445
+ console.log(chalk_1.default.gray(` Original Size: ${formatBytes(startSize)}`));
446
+ console.log(chalk_1.default.gray(` New Size: ${formatBytes(endSize)}`));
447
+ console.log(chalk_1.default.bold.blue(` Efficiency: ${reduction.toFixed(1)}% reduction`));
113
448
  }
114
- catch (e) {
115
- spinner.fail(chalk_1.default.red(`Vacuum failed: ${e.message}`));
449
+ catch (error) {
450
+ spinner.fail(chalk_1.default.red(`āŒ Vacuum failed: ${error.message}`));
451
+ process.exit(1);
116
452
  }
117
453
  });
118
- // --- 4. MIGRATION MAKER ---
119
- program.command('make:migration')
120
- .description('Generate a new migration template')
121
- .argument('<name>', 'Name of the migration')
122
- .action((name) => {
123
- const timestamp = Date.now();
124
- const fileName = `${timestamp}_${name}.ts`;
125
- const migrationsDir = path_1.default.join(process.cwd(), 'migrations');
126
- if (!fs_1.default.existsSync(migrationsDir))
127
- fs_1.default.mkdirSync(migrationsDir);
128
- const template = `import { IDatabase } from "@onurege3467/zerohelper";
129
-
130
- export const up = async (db: IDatabase) => {
131
- // Logic for upgrading the schema
132
- };
133
-
134
- export const down = async (db: IDatabase) => {
135
- // Logic for rolling back the changes
136
- };
137
- `;
138
- fs_1.default.writeFileSync(path_1.default.join(migrationsDir, fileName), template);
139
- console.log(chalk_1.default.green(`\nāœ… Migration created: ./migrations/${fileName}`));
454
+ function formatBytes(bytes) {
455
+ if (bytes === 0)
456
+ return '0 Bytes';
457
+ const k = 1024;
458
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
459
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
460
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
461
+ }
462
+ program.command('db:backup')
463
+ .description('Backup database to timestamped file')
464
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
465
+ .option('-o, --output <dir>', 'Output directory for backups', './backups')
466
+ .action(async (options) => {
467
+ const spinner = (0, ora_1.default)('Creating backup...').start();
468
+ try {
469
+ const db = await getDatabase(options.config);
470
+ const config = loadConfig(options.config);
471
+ const backupDir = path_1.default.resolve(process.cwd(), options.output);
472
+ if (!fs_1.default.existsSync(backupDir)) {
473
+ fs_1.default.mkdirSync(backupDir, { recursive: true });
474
+ }
475
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
476
+ const backupFile = path_1.default.join(backupDir, `backup_${timestamp}.zerohelper.json`);
477
+ let tables = [];
478
+ if (config.adapter === 'json' && config.config.path) {
479
+ const dbPath = path_1.default.resolve(process.cwd(), config.config.path);
480
+ if (fs_1.default.existsSync(dbPath)) {
481
+ const dbContent = JSON.parse(fs_1.default.readFileSync(dbPath, 'utf-8'));
482
+ tables = Object.keys(dbContent);
483
+ }
484
+ }
485
+ else if (config.adapter === 'sqlite' && config.config.path) {
486
+ tables = await db.tables?.() || [];
487
+ }
488
+ else if (config.adapter === 'zpack' && config.config.path) {
489
+ tables = await db.tables?.() || [];
490
+ }
491
+ else {
492
+ tables = ['users', 'products', 'orders', 'migrations', 'migration_test', 'test_backup'];
493
+ }
494
+ const backupData = {
495
+ version: package_json_1.version,
496
+ timestamp: new Date().toISOString(),
497
+ config: config,
498
+ data: {}
499
+ };
500
+ for (const table of tables) {
501
+ try {
502
+ const records = await db.select(table);
503
+ if (records.length > 0) {
504
+ backupData.data[table] = records;
505
+ }
506
+ }
507
+ catch (err) {
508
+ // Table doesn't exist or can't be accessed
509
+ }
510
+ }
511
+ fs_1.default.writeFileSync(backupFile, JSON.stringify(backupData, null, 2));
512
+ const fileSize = fs_1.default.statSync(backupFile).size;
513
+ spinner.succeed(chalk_1.default.green(`āœ… Backup created: ${backupFile}`));
514
+ console.log(chalk_1.default.gray(` Size: ${formatBytes(fileSize)}`));
515
+ console.log(chalk_1.default.gray(` Tables: ${Object.keys(backupData.data).join(', ') || 'none'}`));
516
+ await db.close();
517
+ }
518
+ catch (error) {
519
+ spinner.fail(chalk_1.default.red('āŒ Backup failed'));
520
+ console.error(chalk_1.default.red(error.message));
521
+ process.exit(1);
522
+ }
523
+ });
524
+ program.command('db:restore')
525
+ .description('Restore database from backup file')
526
+ .argument('<backup-file>', 'Path to backup file')
527
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
528
+ .action(async (backupFile, options) => {
529
+ if (!fs_1.default.existsSync(backupFile)) {
530
+ console.error(chalk_1.default.red(`Error: Backup file not found: ${backupFile}`));
531
+ process.exit(1);
532
+ }
533
+ const backupData = JSON.parse(fs_1.default.readFileSync(backupFile, 'utf-8'));
534
+ const { confirm } = await inquirer_1.default.prompt([
535
+ {
536
+ type: 'confirm',
537
+ name: 'confirm',
538
+ message: chalk_1.default.yellow(`āš ļø This will restore data from backup. Are you sure?`),
539
+ default: false
540
+ }
541
+ ]);
542
+ if (!confirm) {
543
+ console.log(chalk_1.default.yellow('Restore cancelled'));
544
+ return;
545
+ }
546
+ const spinner = (0, ora_1.default)('Restoring database...').start();
547
+ try {
548
+ const db = await getDatabase(options.config);
549
+ for (const [table, records] of Object.entries(backupData.data)) {
550
+ const rows = records;
551
+ if (Array.isArray(rows) && rows.length > 0) {
552
+ try {
553
+ await db.bulkInsert(table, rows);
554
+ spinner.text = `āœ… ${table}: ${rows.length} records`;
555
+ }
556
+ catch (err) {
557
+ spinner.text = `āš ļø ${table}: failed`;
558
+ }
559
+ }
560
+ }
561
+ spinner.succeed(chalk_1.default.green(`āœ… Database restored from ${backupFile}`));
562
+ await db.close();
563
+ }
564
+ catch (error) {
565
+ spinner.fail(chalk_1.default.red('āŒ Restore failed'));
566
+ console.error(chalk_1.default.red(error.message));
567
+ process.exit(1);
568
+ }
569
+ });
570
+ program.command('cache:clear')
571
+ .description('Clear all cache')
572
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
573
+ .action(async (options) => {
574
+ const spinner = (0, ora_1.default)('Clearing cache...').start();
575
+ try {
576
+ const db = await getDatabase(options.config);
577
+ const metrics = db.getMetrics();
578
+ const beforeHits = metrics.cache?.hits || 0;
579
+ const beforeMisses = metrics.cache?.misses || 0;
580
+ // Clear cache by triggering cache invalidation
581
+ await db.insert('_cache_clear', { timestamp: Date.now() });
582
+ await db.delete('_cache_clear', { timestamp: Date.now() });
583
+ const afterMetrics = db.getMetrics();
584
+ const afterHits = afterMetrics.cache?.hits || 0;
585
+ const afterMisses = afterMetrics.cache?.misses || 0;
586
+ spinner.succeed(chalk_1.default.green('āœ… Cache cleared'));
587
+ console.log(chalk_1.default.gray(` Previous hits: ${beforeHits}`));
588
+ console.log(chalk_1.default.gray(` Previous misses: ${beforeMisses}`));
589
+ await db.close();
590
+ }
591
+ catch (error) {
592
+ spinner.fail(chalk_1.default.red('āŒ Cache clear failed'));
593
+ console.error(chalk_1.default.red(error.message));
594
+ process.exit(1);
595
+ }
596
+ });
597
+ program.command('cache:stats')
598
+ .description('Show detailed cache statistics')
599
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
600
+ .action(async (options) => {
601
+ try {
602
+ const db = await getDatabase(options.config);
603
+ const metrics = db.getMetrics();
604
+ console.log(chalk_1.default.bold('\nšŸ“Š Cache Statistics'));
605
+ console.log(chalk_1.default.gray('─'.repeat(50)));
606
+ if (!metrics.cache) {
607
+ console.log(chalk_1.default.yellow(' No cache statistics available'));
608
+ console.log(chalk_1.default.gray(' Cache may not be enabled for this adapter'));
609
+ }
610
+ else {
611
+ const hits = metrics.cache.hits || 0;
612
+ const misses = metrics.cache.misses || 0;
613
+ const total = hits + misses;
614
+ const hitRate = total > 0 ? (hits / total) * 100 : 0;
615
+ console.log(chalk_1.default.cyan(' Cache Hits:'), chalk_1.default.green(hits.toLocaleString()));
616
+ console.log(chalk_1.default.cyan(' Cache Misses:'), chalk_1.default.red(misses.toLocaleString()));
617
+ console.log(chalk_1.default.cyan(' Total Requests:'), chalk_1.default.white(total.toLocaleString()));
618
+ console.log(chalk_1.default.cyan(' Hit Rate:'), chalk_1.default.white(`${hitRate.toFixed(2)}%`));
619
+ let healthStatus = 'āŒ';
620
+ let healthColor = chalk_1.default.red;
621
+ if (hitRate >= 90) {
622
+ healthStatus = 'āœ… Excellent';
623
+ healthColor = chalk_1.default.green;
624
+ }
625
+ else if (hitRate >= 70) {
626
+ healthStatus = 'āš ļø Good';
627
+ healthColor = chalk_1.default.yellow;
628
+ }
629
+ else if (hitRate >= 50) {
630
+ healthStatus = 'āš ļø Fair';
631
+ healthColor = chalk_1.default.yellow;
632
+ }
633
+ else {
634
+ healthStatus = 'āŒ Poor';
635
+ healthColor = chalk_1.default.red;
636
+ }
637
+ console.log(chalk_1.default.cyan(' Health:'), healthColor(healthStatus));
638
+ if (hitRate < 70) {
639
+ console.log(chalk_1.default.gray('\n šŸ’” Tip: Consider increasing cache TTL for better hit rate'));
640
+ }
641
+ }
642
+ console.log(chalk_1.default.gray('\n' + '─'.repeat(50)));
643
+ await db.close();
644
+ }
645
+ catch (error) {
646
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
647
+ process.exit(1);
648
+ }
649
+ });
650
+ program.command('repl')
651
+ .description('Interactive ZeroHelper REPL mode')
652
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
653
+ .action(async (options) => {
654
+ console.log(chalk_1.default.bold.cyan('\nšŸ”§ ZeroHelper REPL Mode\n'));
655
+ console.log(chalk_1.default.gray('Type .exit to quit, .help for commands\n'));
656
+ try {
657
+ const db = await getDatabase(options.config);
658
+ const readline = require('readline');
659
+ const rl = readline.createInterface({
660
+ input: process.stdin,
661
+ output: process.stdout,
662
+ prompt: chalk_1.default.blue('zero> ')
663
+ });
664
+ rl.prompt();
665
+ rl.on('line', async (line) => {
666
+ const cmd = line.trim();
667
+ if (cmd === '.exit') {
668
+ await db.close();
669
+ rl.close();
670
+ console.log(chalk_1.default.green('\nšŸ‘‹ Goodbye!\n'));
671
+ process.exit(0);
672
+ }
673
+ else if (cmd === '.help') {
674
+ console.log(chalk_1.default.bold('\nAvailable REPL Commands:'));
675
+ console.log(' .exit Exit REPL');
676
+ console.log(' .help Show this help');
677
+ console.log(' .stats Show database stats');
678
+ console.log(' .metrics Show performance metrics');
679
+ console.log(' select <table> Select all from table');
680
+ console.log(' count <table> Count records in table');
681
+ console.log(' clear Clear screen\n');
682
+ }
683
+ else if (cmd === '.stats') {
684
+ const metrics = db.getMetrics();
685
+ console.log(chalk_1.default.bold('\nšŸ“Š Database Stats:'));
686
+ console.log(` Operations: ${metrics.database.count || 0}`);
687
+ console.log(` Avg Latency: ${(metrics.database.averageDuration || 0).toFixed(2)}ms\n`);
688
+ }
689
+ else if (cmd === '.metrics') {
690
+ const metrics = db.getMetrics();
691
+ console.log(chalk_1.default.bold('\nšŸ“Š Full Metrics:'));
692
+ console.log(JSON.stringify(metrics, null, 2));
693
+ console.log('');
694
+ }
695
+ else if (cmd === '.clear') {
696
+ console.clear();
697
+ }
698
+ else if (cmd.startsWith('select ')) {
699
+ const table = cmd.replace('select ', '').trim();
700
+ try {
701
+ const records = await db.select(table);
702
+ console.log(chalk_1.default.bold(`\nFound ${records.length} records in ${table}:`));
703
+ console.log(JSON.stringify(records, null, 2));
704
+ console.log('');
705
+ }
706
+ catch (err) {
707
+ console.error(chalk_1.default.red(`Error: ${err.message}\n`));
708
+ }
709
+ }
710
+ else if (cmd.startsWith('count ')) {
711
+ const table = cmd.replace('count ', '').trim();
712
+ try {
713
+ const records = await db.select(table);
714
+ console.log(chalk_1.default.bold(`\n${table}: ${records.length} records\n`));
715
+ }
716
+ catch (err) {
717
+ console.error(chalk_1.default.red(`Error: ${err.message}\n`));
718
+ }
719
+ }
720
+ else if (cmd.length > 0) {
721
+ console.log(chalk_1.default.yellow('Unknown command. Type .help for available commands\n'));
722
+ }
723
+ rl.prompt();
724
+ });
725
+ rl.on('close', () => {
726
+ db.close();
727
+ console.log(chalk_1.default.green('\nšŸ‘‹ Goodbye!\n'));
728
+ process.exit(0);
729
+ });
730
+ }
731
+ catch (error) {
732
+ console.error(chalk_1.default.red(`Error: ${error.message}`));
733
+ process.exit(1);
734
+ }
735
+ });
736
+ program.command('db:export')
737
+ .description('Export table data to file')
738
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
739
+ .option('-t, --table <name>', 'Table name to export')
740
+ .option('-f, --format <format>', 'Output format (json|csv)', 'json')
741
+ .option('-o, --output <file>', 'Output file path')
742
+ .action(async (options) => {
743
+ if (!options.table) {
744
+ console.error(chalk_1.default.red('Error: --table option is required'));
745
+ process.exit(1);
746
+ }
747
+ const spinner = (0, ora_1.default)(`Exporting ${options.table}...`).start();
748
+ try {
749
+ const db = await getDatabase(options.config);
750
+ const records = await db.select(options.table);
751
+ if (records.length === 0) {
752
+ spinner.warn(chalk_1.default.yellow(`āš ļø No records found in ${options.table}`));
753
+ await db.close();
754
+ return;
755
+ }
756
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
757
+ const ext = options.format === 'csv' ? 'csv' : 'json';
758
+ const defaultFile = `${options.table}_export_${timestamp}.${ext}`;
759
+ const outputFile = options.output || path_1.default.join(process.cwd(), 'exports', defaultFile);
760
+ const outputDir = path_1.default.dirname(outputFile);
761
+ if (!fs_1.default.existsSync(outputDir)) {
762
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
763
+ }
764
+ let content = '';
765
+ if (options.format === 'csv') {
766
+ const headers = Object.keys(records[0]);
767
+ const csvRows = [
768
+ headers.join(','),
769
+ ...records.map((row) => headers.map(h => {
770
+ const val = row[h];
771
+ return typeof val === 'string' && val.includes(',') ? `"${val}"` : val;
772
+ }).join(','))
773
+ ];
774
+ content = csvRows.join('\n');
775
+ }
776
+ else {
777
+ content = JSON.stringify(records, null, 2);
778
+ }
779
+ fs_1.default.writeFileSync(outputFile, content);
780
+ const fileSize = fs_1.default.statSync(outputFile).size;
781
+ spinner.succeed(chalk_1.default.green(`āœ… Exported ${records.length} records to ${outputFile}`));
782
+ console.log(chalk_1.default.gray(` Size: ${formatBytes(fileSize)}`));
783
+ console.log(chalk_1.default.gray(` Format: ${options.format.toUpperCase()}`));
784
+ await db.close();
785
+ }
786
+ catch (error) {
787
+ spinner.fail(chalk_1.default.red('āŒ Export failed'));
788
+ console.error(chalk_1.default.red(error.message));
789
+ process.exit(1);
790
+ }
791
+ });
792
+ program.command('db:import')
793
+ .description('Import data from file to table')
794
+ .argument('<file>', 'Input file path')
795
+ .option('-c, --config <path>', 'Path to config file', 'zero.config.ts')
796
+ .option('-t, --table <name>', 'Table name')
797
+ .option('-f, --format <format>', 'Input format (json|csv)', 'json')
798
+ .action(async (inputFile, options) => {
799
+ if (!options.table) {
800
+ console.error(chalk_1.default.red('Error: --table option is required'));
801
+ process.exit(1);
802
+ }
803
+ if (!fs_1.default.existsSync(inputFile)) {
804
+ console.error(chalk_1.default.red(`Error: File not found: ${inputFile}`));
805
+ process.exit(1);
806
+ }
807
+ const { confirm } = await inquirer_1.default.prompt([
808
+ {
809
+ type: 'confirm',
810
+ name: 'confirm',
811
+ message: chalk_1.default.yellow(`āš ļø This will import data to ${options.table}. Are you sure?`),
812
+ default: false
813
+ }
814
+ ]);
815
+ if (!confirm) {
816
+ console.log(chalk_1.default.yellow('Import cancelled'));
817
+ return;
818
+ }
819
+ const spinner = (0, ora_1.default)(`Importing to ${options.table}...`).start();
820
+ try {
821
+ const db = await getDatabase(options.config);
822
+ const content = fs_1.default.readFileSync(inputFile, 'utf-8');
823
+ let data = [];
824
+ if (options.format === 'csv') {
825
+ const lines = content.trim().split('\n');
826
+ const headers = lines[0].split(',');
827
+ data = lines.slice(1).map((line) => {
828
+ const values = line.split(',');
829
+ const row = {};
830
+ headers.forEach((h, i) => {
831
+ row[h] = values[i]?.replace(/"/g, '').trim();
832
+ });
833
+ return row;
834
+ });
835
+ }
836
+ else {
837
+ data = JSON.parse(content);
838
+ }
839
+ const count = await db.bulkInsert(options.table, data);
840
+ spinner.succeed(chalk_1.default.green(`āœ… Imported ${count} records to ${options.table}`));
841
+ await db.close();
842
+ }
843
+ catch (error) {
844
+ spinner.fail(chalk_1.default.red('āŒ Import failed'));
845
+ console.error(chalk_1.default.red(error.message));
846
+ process.exit(1);
847
+ }
140
848
  });
141
849
  program.parse();