outlet-orm 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/init.js ADDED
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script d'initialisation pour le package ORM
5
+ * Ce script aide à configurer rapidement un projet avec l'ORM
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const readline = require('readline');
11
+
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout
15
+ });
16
+
17
+ const question = (query) => new Promise((resolve) => rl.question(query, resolve));
18
+
19
+ async function init() {
20
+ console.log('\n🚀 Bienvenue dans l\'assistant de configuration Outlet ORM!\n');
21
+
22
+ try {
23
+ // Database driver
24
+ console.log('Quel driver de base de données souhaitez-vous utiliser?');
25
+ console.log('1. MySQL');
26
+ console.log('2. PostgreSQL');
27
+ console.log('3. SQLite');
28
+ const driverChoice = await question('\nVotre choix (1-3): ');
29
+
30
+ const drivers = {
31
+ '1': { name: 'mysql', package: 'mysql2', defaultPort: 3306 },
32
+ '2': { name: 'postgres', package: 'pg', defaultPort: 5432 },
33
+ '3': { name: 'sqlite', package: 'sqlite3', defaultPort: null }
34
+ };
35
+
36
+ const selectedDriver = drivers[driverChoice];
37
+ if (!selectedDriver) {
38
+ console.error('❌ Choix invalide!');
39
+ process.exit(1);
40
+ }
41
+
42
+ // Database configuration
43
+ let config = {
44
+ driver: selectedDriver.name
45
+ };
46
+
47
+ if (selectedDriver.name !== 'sqlite') {
48
+ config.host = await question('Host (localhost): ') || 'localhost';
49
+ config.port = await question(`Port (${selectedDriver.defaultPort}): `) || selectedDriver.defaultPort;
50
+ config.database = await question('Nom de la base de données: ');
51
+ config.user = await question('Utilisateur: ');
52
+ config.password = await question('Mot de passe: ');
53
+ } else {
54
+ config.database = await question('Chemin du fichier SQLite (./database.sqlite): ') || './database.sqlite';
55
+ }
56
+
57
+ // Ask to generate a .env file
58
+ const generateEnv = (await question('\nSouhaitez-vous générer un fichier .env avec ces paramètres ? (oui/non) [oui]: ')).trim().toLowerCase();
59
+ const wantEnv = generateEnv === '' || generateEnv === 'oui' || generateEnv === 'o' || generateEnv === 'yes' || generateEnv === 'y';
60
+
61
+ if (wantEnv) {
62
+ const envLines = [];
63
+ envLines.push(`DB_DRIVER=${config.driver}`);
64
+ if (config.driver !== 'sqlite') {
65
+ envLines.push(`DB_HOST=${config.host || 'localhost'}`);
66
+ envLines.push(`DB_PORT=${config.port || selectedDriver.defaultPort || ''}`);
67
+ envLines.push(`DB_USER=${config.user || ''}`);
68
+ envLines.push(`DB_PASSWORD=${config.password || ''}`);
69
+ envLines.push(`DB_DATABASE=${config.database || ''}`);
70
+ } else {
71
+ envLines.push(`DB_FILE=${config.database}`);
72
+ }
73
+
74
+ const envPath = path.join(process.cwd(), '.env');
75
+ if (fs.existsSync(envPath)) {
76
+ console.log('ℹ️ .env existe déjà, génération ignorée.');
77
+ } else {
78
+ fs.writeFileSync(envPath, envLines.join('\n') + '\n');
79
+ console.log(`✅ Fichier .env créé: ${envPath}`);
80
+ }
81
+ }
82
+
83
+ // Generate config file
84
+ const configContent = `const { DatabaseConnection } = require('outlet-orm');
85
+
86
+ // Configuration de la base de données
87
+ const db = new DatabaseConnection(${JSON.stringify(config, null, 2)});
88
+
89
+ module.exports = db;
90
+ `;
91
+
92
+ const configPath = path.join(process.cwd(), 'database.js');
93
+ fs.writeFileSync(configPath, configContent);
94
+ console.log(`\n✅ Fichier de configuration créé: ${configPath}`);
95
+
96
+ // Generate example model
97
+ const modelContent = `const { Model } = require('outlet-orm');
98
+ const db = require('./database');
99
+
100
+ class User extends Model {
101
+ static table = 'users';
102
+ static fillable = ['name', 'email', 'password'];
103
+ static hidden = ['password'];
104
+ static casts = {
105
+ id: 'int',
106
+ email_verified: 'boolean'
107
+ };
108
+ static connection = db;
109
+
110
+ // Définissez vos relations ici
111
+ // posts() {
112
+ // return this.hasMany(Post, 'user_id');
113
+ // }
114
+ }
115
+
116
+ module.exports = User;
117
+ `;
118
+
119
+ const modelPath = path.join(process.cwd(), 'User.js');
120
+ fs.writeFileSync(modelPath, modelContent);
121
+ console.log(`✅ Modèle exemple créé: ${modelPath}`);
122
+
123
+ // Generate usage example
124
+ const usageContent = `const User = require('./User');
125
+
126
+ async function main() {
127
+ try {
128
+ // Exemple: Créer un utilisateur
129
+ const user = await User.create({
130
+ name: 'John Doe',
131
+ email: 'john@example.com',
132
+ password: 'secret123'
133
+ });
134
+ console.log('Utilisateur créé:', user.toJSON());
135
+
136
+ // Exemple: Rechercher des utilisateurs
137
+ const users = await User.all();
138
+ console.log('Tous les utilisateurs:', users.length);
139
+
140
+ // Exemple: Requête avec conditions
141
+ const activeUsers = await User
142
+ .where('status', 'active')
143
+ .orderBy('name')
144
+ .get();
145
+ console.log('Utilisateurs actifs:', activeUsers.length);
146
+
147
+ } catch (error) {
148
+ console.error('Erreur:', error.message);
149
+ }
150
+ }
151
+
152
+ main();
153
+ `;
154
+
155
+ const usagePath = path.join(process.cwd(), 'example.js');
156
+ fs.writeFileSync(usagePath, usageContent);
157
+ console.log(`✅ Exemple d'utilisation créé: ${usagePath}`);
158
+
159
+ // Optionally skip package init/install in non-interactive or test context
160
+ const skipInstall = process.env.OUTLET_INIT_NO_INSTALL === '1';
161
+ if (!skipInstall) {
162
+ // Check if package needs to be installed
163
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
164
+ if (!fs.existsSync(packageJsonPath)) {
165
+ console.log('\n⚠️ Aucun package.json trouvé. Initialisation...');
166
+ require('child_process').execSync('npm init -y', { stdio: 'inherit' });
167
+ }
168
+
169
+ console.log(`\n📦 Installation du driver ${selectedDriver.package}...`);
170
+ require('child_process').execSync(`npm install ${selectedDriver.package}`, { stdio: 'inherit' });
171
+ } else {
172
+ console.log('\n⏭️ Installation du driver ignorée (OUTLET_INIT_NO_INSTALL=1).');
173
+ }
174
+
175
+ console.log('\n✨ Configuration terminée!\n');
176
+ console.log('Prochaines étapes:');
177
+ console.log('1. Créez votre schéma de base de données');
178
+ console.log('2. Modifiez User.js selon vos besoins');
179
+ console.log('3. Exécutez example.js: node example.js');
180
+ console.log('\n📚 Documentation: https://github.com/yourusername/outlet-orm');
181
+
182
+ } catch (error) {
183
+ console.error('\n❌ Erreur:', error.message);
184
+ process.exit(1);
185
+ } finally {
186
+ rl.close();
187
+ }
188
+ }
189
+
190
+ init();
package/bin/migrate.js ADDED
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * outlet-migrate CLI
5
+ * Migration management tool for outlet-orm
6
+ */
7
+
8
+ const readline = require('readline');
9
+ const fs = require('fs').promises;
10
+ const path = require('path');
11
+
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout
15
+ });
16
+
17
+ function question(query) {
18
+ return new Promise(resolve => rl.question(query, resolve));
19
+ }
20
+
21
+ async function main() {
22
+ console.log('\n╔═══════════════════════════════════════╗');
23
+ console.log('║ Outlet ORM - Migration Manager ║');
24
+ console.log('╚═══════════════════════════════════════╝\n');
25
+
26
+ const command = process.argv[2];
27
+
28
+ if (command === 'make') {
29
+ await makeMigration();
30
+ rl.close();
31
+ return;
32
+ }
33
+
34
+ // Support non-interactive commands for automation and CI
35
+ const nonInteractive = new Set(['migrate', 'up', 'rollback', 'reset', 'refresh', 'fresh', 'status']);
36
+ if (nonInteractive.has(command)) {
37
+ const flags = parseFlags(process.argv.slice(3));
38
+ await runNonInteractive(command, flags);
39
+ rl.close();
40
+ return;
41
+ }
42
+
43
+ // Fallback to interactive menu
44
+ await runMigrationCommands();
45
+
46
+ rl.close();
47
+ }
48
+
49
+ /**
50
+ * Create a new migration file
51
+ */
52
+ async function makeMigration() {
53
+ const migrationName = process.argv[3];
54
+
55
+ if (!migrationName) {
56
+ console.error('✗ Error: Migration name is required');
57
+ console.log('Usage: outlet-migrate make <migration_name>');
58
+ console.log('Example: outlet-migrate make create_users_table');
59
+ return;
60
+ }
61
+
62
+ const migrationsDir = path.join(process.cwd(), 'database', 'migrations');
63
+
64
+ // Create migrations directory if it doesn't exist
65
+ try {
66
+ await fs.mkdir(migrationsDir, { recursive: true });
67
+ } catch (error) {
68
+ // Directory already exists - ignore error as recursive: true handles this
69
+ if (error.code !== 'EEXIST') {
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ // Generate timestamp
75
+ const timestamp = new Date().toISOString()
76
+ .replace(/[-:]/g, '')
77
+ .replace(/T/, '_')
78
+ .replace(/\..+/, '');
79
+
80
+ const fileName = `${timestamp}_${migrationName}.js`;
81
+ const filePath = path.join(migrationsDir, fileName);
82
+
83
+ // Determine if it's a create or alter migration
84
+ const isCreate = migrationName.includes('create_');
85
+ const tableName = extractTableName(migrationName);
86
+
87
+ const template = isCreate
88
+ ? getCreateMigrationTemplate(tableName)
89
+ : getAlterMigrationTemplate(tableName);
90
+
91
+ await fs.writeFile(filePath, template);
92
+
93
+ console.log(`✓ Migration created: ${fileName}`);
94
+ console.log(` Location: ${filePath}`);
95
+ }
96
+
97
+ /**
98
+ * Extract table name from migration name
99
+ */
100
+ function extractTableName(migrationName) {
101
+ // Extract table name from patterns like:
102
+ // create_users_table -> users
103
+ // add_email_to_users_table -> users
104
+ // alter_users_table -> users
105
+
106
+ const patterns = [
107
+ /create_(\w+)_table/,
108
+ /to_(\w+)_table/,
109
+ /alter_(\w+)_table/,
110
+ /(\w+)_table/
111
+ ];
112
+
113
+ for (const pattern of patterns) {
114
+ const match = migrationName.match(pattern);
115
+ if (match) {
116
+ return match[1];
117
+ }
118
+ }
119
+
120
+ return 'table_name';
121
+ }
122
+
123
+ /**
124
+ * Get migration template for creating a table
125
+ */
126
+ function getCreateMigrationTemplate(tableName) {
127
+ return `/**
128
+ * Migration: Create ${tableName} table
129
+ */
130
+
131
+ const Migration = require('../../lib/Migrations/Migration');
132
+
133
+ class Create${capitalize(tableName)}Table extends Migration {
134
+ /**
135
+ * Run the migrations
136
+ */
137
+ async up() {
138
+ const schema = this.getSchema();
139
+
140
+ await schema.create('${tableName}', (table) => {
141
+ table.id();
142
+ table.string('name');
143
+ table.timestamps();
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Reverse the migrations
149
+ */
150
+ async down() {
151
+ const schema = this.getSchema();
152
+ await schema.dropIfExists('${tableName}');
153
+ }
154
+ }
155
+
156
+ module.exports = Create${capitalize(tableName)}Table;
157
+ `;
158
+ }
159
+
160
+ /**
161
+ * Get migration template for altering a table
162
+ */
163
+ function getAlterMigrationTemplate(tableName) {
164
+ return `/**
165
+ * Migration: Alter ${tableName} table
166
+ */
167
+
168
+ const Migration = require('../../lib/Migrations/Migration');
169
+
170
+ class Alter${capitalize(tableName)}Table extends Migration {
171
+ /**
172
+ * Run the migrations
173
+ */
174
+ async up() {
175
+ const schema = this.getSchema();
176
+
177
+ await schema.table('${tableName}', (table) => {
178
+ // Add your column modifications here
179
+ // table.string('new_column');
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Reverse the migrations
185
+ */
186
+ async down() {
187
+ const schema = this.getSchema();
188
+
189
+ await schema.table('${tableName}', (table) => {
190
+ // Reverse your column modifications here
191
+ // table.dropColumn('new_column');
192
+ });
193
+ }
194
+ }
195
+
196
+ module.exports = Alter${capitalize(tableName)}Table;
197
+ `;
198
+ }
199
+
200
+ /**
201
+ * Capitalize first letter
202
+ */
203
+ function capitalize(str) {
204
+ return str.charAt(0).toUpperCase() + str.slice(1);
205
+ }
206
+
207
+ /**
208
+ * Simple flag parser for CLI args
209
+ * Supports formats:
210
+ * --key=value, --key value, -k value, and boolean flags like --yes/-y
211
+ */
212
+ function parseFlags(argv) {
213
+ const text = ` ${argv.join(' ')} `;
214
+ const flags = {};
215
+ // Booleans
216
+ if (/(^|\s)(--yes|-y)(\s|$)/.test(text)) flags.yes = true;
217
+ if (/(^|\s)(--force|-f)(\s|$)/.test(text)) flags.force = true;
218
+ // Steps with value: supports "--steps N", "--steps=N", "-s N"
219
+ const stepsRe = /(?:--steps(?:=|\s+)|-s\s+)(\S+)/;
220
+ const stepsMatch = stepsRe.exec(text);
221
+ if (stepsMatch) flags.steps = coerce(stepsMatch[1]);
222
+ return flags;
223
+ }
224
+
225
+ function coerce(val) {
226
+ if (val === 'true') return true;
227
+ if (val === 'false') return false;
228
+ const n = Number(val);
229
+ return Number.isNaN(n) ? val : n;
230
+ }
231
+
232
+ /**
233
+ * Run migration commands non-interactively
234
+ */
235
+ async function runNonInteractive(cmd, flags) {
236
+ // Load database configuration
237
+ const dbConfigPath = path.join(process.cwd(), 'database', 'config.js');
238
+
239
+ // Prefer database/config.js; if missing, allow env-based config via .env
240
+ let dbConfig;
241
+ try {
242
+ dbConfig = require(dbConfigPath);
243
+ } catch (error) {
244
+ // Fallback to env-based configuration
245
+ require('dotenv').config();
246
+ const env = process.env || {};
247
+ dbConfig = {
248
+ driver: env.DB_DRIVER || env.DATABASE_DRIVER,
249
+ host: env.DB_HOST,
250
+ port: env.DB_PORT ? Number(env.DB_PORT) : undefined,
251
+ user: env.DB_USER || env.DB_USERNAME,
252
+ password: env.DB_PASSWORD,
253
+ database: env.DB_DATABASE || env.DB_NAME || env.DB_FILE || env.SQLITE_DB || env.SQLITE_FILENAME
254
+ };
255
+ if (!dbConfig.driver) {
256
+ console.error('\n✗ Error: Could not load database configuration');
257
+ console.error(` Make sure ${dbConfigPath} exists OR provide .env variables like DB_DRIVER, DB_HOST, DB_DATABASE`);
258
+ console.error(' Run "outlet-init" to create the configuration');
259
+ console.error(` Details: ${error.message}`);
260
+ return;
261
+ }
262
+ }
263
+
264
+ const { DatabaseConnection } = require('../lib/Database/DatabaseConnection');
265
+ const MigrationManager = require('../lib/Migrations/MigrationManager');
266
+
267
+ const connection = new DatabaseConnection(dbConfig);
268
+ await connection.connect();
269
+
270
+ const manager = new MigrationManager(connection);
271
+
272
+ try {
273
+ switch (cmd) {
274
+ case 'migrate':
275
+ case 'up':
276
+ await manager.run();
277
+ break;
278
+
279
+ case 'rollback': {
280
+ const steps = Number(flags.steps) || 1;
281
+ await manager.rollback(steps);
282
+ break;
283
+ }
284
+
285
+ case 'reset': {
286
+ if (flags.yes || flags.force) {
287
+ await manager.reset();
288
+ } else {
289
+ console.error('✗ Refused to reset without --yes');
290
+ }
291
+ break;
292
+ }
293
+
294
+ case 'refresh': {
295
+ if (flags.yes || flags.force) {
296
+ await manager.refresh();
297
+ } else {
298
+ console.error('✗ Refused to refresh without --yes');
299
+ }
300
+ break;
301
+ }
302
+
303
+ case 'fresh': {
304
+ if (flags.yes || flags.force) {
305
+ await manager.fresh();
306
+ } else {
307
+ console.error('✗ Refused to fresh without --yes');
308
+ }
309
+ break;
310
+ }
311
+
312
+ case 'status':
313
+ await manager.status();
314
+ break;
315
+
316
+ default:
317
+ console.error(`✗ Unknown command: ${cmd}`);
318
+ }
319
+ } catch (error) {
320
+ console.error('\n✗ Migration error:', error.message);
321
+ console.error(error.stack);
322
+ }
323
+
324
+ await connection.disconnect();
325
+ }
326
+
327
+ /**
328
+ * Run migration commands (migrate, rollback, etc.)
329
+ */
330
+ async function runMigrationCommands() {
331
+ console.log('Select a migration command:\n');
332
+ console.log('1. migrate - Run all pending migrations');
333
+ console.log('2. rollback - Rollback the last batch of migrations');
334
+ console.log('3. reset - Rollback all migrations');
335
+ console.log('4. refresh - Reset and re-run all migrations');
336
+ console.log('5. fresh - Drop all tables and re-run migrations');
337
+ console.log('6. status - Show migration status');
338
+ console.log('0. Exit\n');
339
+
340
+ const choice = await question('Enter your choice: ');
341
+
342
+ if (choice === '0') {
343
+ console.log('Goodbye!');
344
+ return;
345
+ }
346
+
347
+ // Load database configuration
348
+ const dbConfigPath = path.join(process.cwd(), 'database', 'config.js');
349
+
350
+ let dbConfig;
351
+ try {
352
+ dbConfig = require(dbConfigPath);
353
+ } catch (error) {
354
+ require('dotenv').config();
355
+ const env = process.env || {};
356
+ dbConfig = {
357
+ driver: env.DB_DRIVER || env.DATABASE_DRIVER,
358
+ host: env.DB_HOST,
359
+ port: env.DB_PORT ? Number(env.DB_PORT) : undefined,
360
+ user: env.DB_USER || env.DB_USERNAME,
361
+ password: env.DB_PASSWORD,
362
+ database: env.DB_DATABASE || env.DB_NAME || env.DB_FILE || env.SQLITE_DB || env.SQLITE_FILENAME
363
+ };
364
+ if (!dbConfig.driver) {
365
+ console.error('\n✗ Error: Could not load database configuration');
366
+ console.error(` Make sure ${dbConfigPath} exists OR provide .env variables like DB_DRIVER, DB_HOST, DB_DATABASE`);
367
+ console.error(' Run "outlet-init" to create the configuration');
368
+ console.error(` Details: ${error.message}`);
369
+ return;
370
+ }
371
+ }
372
+
373
+ const { DatabaseConnection } = require('../lib/Database/DatabaseConnection');
374
+ const MigrationManager = require('../lib/Migrations/MigrationManager');
375
+
376
+ const connection = new DatabaseConnection(dbConfig);
377
+ await connection.connect();
378
+
379
+ const manager = new MigrationManager(connection);
380
+
381
+ try {
382
+ switch (choice) {
383
+ case '1':
384
+ await manager.run();
385
+ break;
386
+
387
+ case '2': {
388
+ const steps = await question('How many batches to rollback? (default: 1): ');
389
+ await manager.rollback(parseInt(steps) || 1);
390
+ break;
391
+ }
392
+
393
+ case '3': {
394
+ const confirmReset = await question('Are you sure you want to reset all migrations? (yes/no): ');
395
+ if (confirmReset.toLowerCase() === 'yes') {
396
+ await manager.reset();
397
+ } else {
398
+ console.log('Reset cancelled');
399
+ }
400
+ break;
401
+ }
402
+
403
+ case '4': {
404
+ const confirmRefresh = await question('Are you sure you want to refresh all migrations? (yes/no): ');
405
+ if (confirmRefresh.toLowerCase() === 'yes') {
406
+ await manager.refresh();
407
+ } else {
408
+ console.log('Refresh cancelled');
409
+ }
410
+ break;
411
+ }
412
+
413
+ case '5': {
414
+ const confirmFresh = await question('⚠️ WARNING: This will DROP ALL TABLES! Continue? (yes/no): ');
415
+ if (confirmFresh.toLowerCase() === 'yes') {
416
+ await manager.fresh();
417
+ } else {
418
+ console.log('Fresh cancelled');
419
+ }
420
+ break;
421
+ }
422
+
423
+ case '6':
424
+ await manager.status();
425
+ break;
426
+
427
+ default:
428
+ console.log('Invalid choice');
429
+ }
430
+ } catch (error) {
431
+ console.error('\n✗ Migration error:', error.message);
432
+ console.error(error.stack);
433
+ }
434
+
435
+ await connection.disconnect();
436
+ }
437
+
438
+ // Run the CLI
439
+ main().catch(error => {
440
+ console.error('Fatal error:', error);
441
+ process.exit(1);
442
+ });
@@ -0,0 +1,4 @@
1
+ // Re-export DatabaseConnection from src for CLI usage (named export for destructuring)
2
+ module.exports = {
3
+ DatabaseConnection: require('../../src/DatabaseConnection')
4
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Base Migration Class
3
+ * All migrations should extend this class
4
+ */
5
+
6
+ class Migration {
7
+ constructor(connection) {
8
+ this.connection = connection;
9
+ }
10
+
11
+ /**
12
+ * Run the migrations
13
+ */
14
+ async up() {
15
+ throw new Error('Migration up() method must be implemented');
16
+ }
17
+
18
+ /**
19
+ * Reverse the migrations
20
+ */
21
+ async down() {
22
+ throw new Error('Migration down() method must be implemented');
23
+ }
24
+
25
+ /**
26
+ * Get the migration name
27
+ */
28
+ static getName() {
29
+ return this.name;
30
+ }
31
+
32
+ /**
33
+ * Execute raw SQL
34
+ */
35
+ async execute(sql) {
36
+ return await this.connection.execute(sql);
37
+ }
38
+
39
+ /**
40
+ * Get the Schema builder
41
+ */
42
+ getSchema() {
43
+ const { Schema } = require('../Schema/Schema');
44
+ return new Schema(this.connection);
45
+ }
46
+ }
47
+
48
+ module.exports = Migration;