millas 0.2.21 → 0.2.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "millas",
3
- "version": "0.2.21",
3
+ "version": "0.2.24",
4
4
  "description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -51,7 +51,8 @@
51
51
  "nodemailer": "^6.9.0",
52
52
  "nunjucks": "^3.2.4",
53
53
  "ora": "5.4.1",
54
- "sqlite3": "5.1.6"
54
+ "sqlite3": "5.1.6",
55
+ "node-cron": "^4.2.1"
55
56
  },
56
57
  "peerDependencies": {
57
58
  "express": "^4.18.0",
@@ -104,11 +104,27 @@ class Admin {
104
104
 
105
105
  _setupNunjucks(expressApp) {
106
106
  const viewsDir = path.join(__dirname, 'views');
107
- const env = nunjucks.configure(viewsDir, {
108
- autoescape: true,
109
- express: expressApp,
110
- noCache: process.env.NODE_ENV !== 'production',
111
- });
107
+
108
+ // Check if nunjucks is already configured by the main app
109
+ const existingEnv = expressApp.get('nunjucksEnvironment');
110
+ let env;
111
+
112
+ if (existingEnv) {
113
+ // Create a new environment that includes both search paths
114
+ const mainAppViewsDir = expressApp.get('views');
115
+ env = nunjucks.configure([viewsDir, mainAppViewsDir], {
116
+ autoescape: true,
117
+ express: expressApp,
118
+ noCache: process.env.NODE_ENV !== 'production',
119
+ });
120
+ } else {
121
+ env = nunjucks.configure(viewsDir, {
122
+ autoescape: true,
123
+ express: expressApp,
124
+ noCache: process.env.NODE_ENV !== 'production',
125
+ });
126
+ expressApp.set('nunjucksEnvironment', env);
127
+ }
112
128
 
113
129
  const resolveFkSlug = (tableName) => {
114
130
  if (!tableName) return null;
package/src/cli.js CHANGED
@@ -3,6 +3,9 @@
3
3
  // Load .env before anything else so all commands have access to env vars
4
4
  require('dotenv').config();
5
5
 
6
+ // Set CLI mode globally for all commands
7
+ process.env.MILLAS_CLI_MODE = 'true';
8
+
6
9
  const { Command } = require('commander');
7
10
  const chalk = require('chalk');
8
11
  const program = new Command();
@@ -20,6 +23,7 @@ require('./commands/make')(program);
20
23
  require('./commands/migrate')(program);
21
24
  require('./commands/route')(program);
22
25
  require('./commands/queue')(program);
26
+ require('./commands/schedule')(program);
23
27
  require('./commands/createsuperuser')(program);
24
28
  require('./commands/lang')(program);
25
29
  require('./commands/key')(program);
@@ -26,8 +26,7 @@ module.exports = function (program) {
26
26
  process.env.MILLAS_ROUTE_LIST = 'true'; // suppress server listen
27
27
  let app;
28
28
  try {
29
- const bootstrap = require(bootstrapPath);
30
- app = bootstrap.app;
29
+ app = await require(bootstrapPath);
31
30
  } catch (e) {
32
31
  console.error(chalk.red(`\n ✖ Failed to load app: ${e.message}\n`));
33
32
  process.exit(1);
@@ -20,7 +20,7 @@ module.exports = function (program) {
20
20
 
21
21
  let route;
22
22
  try {
23
- const bootstrap = require(bootstrapPath);
23
+ const bootstrap = await require(bootstrapPath);
24
24
  route = bootstrap.route;
25
25
  } catch (err) {
26
26
  console.error(chalk.red(`\n ✖ Failed to load routes: ${err.message}\n`));
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+
7
+ module.exports = function (program) {
8
+
9
+ program
10
+ .command('schedule:list')
11
+ .description('Show all scheduled tasks and their next run times')
12
+ .action(async () => {
13
+ const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
14
+ if (!fs.existsSync(bootstrapPath)) {
15
+ console.error(chalk.red('\n ✖ Not inside a Millas project.\n'));
16
+ process.exit(1);
17
+ }
18
+
19
+ // Boot the app to get scheduler
20
+ let app;
21
+ try {
22
+ app = await require(bootstrapPath);
23
+ } catch (e) {
24
+ console.error(chalk.red(`\n ✖ Failed to load app: ${e.message}\n`));
25
+ process.exit(1);
26
+ }
27
+
28
+ const scheduler = app.make('scheduler');
29
+ const tasks = scheduler.getTasks();
30
+
31
+ console.log();
32
+ if (tasks.length === 0) {
33
+ console.log(chalk.gray(' No scheduled tasks found.'));
34
+ console.log(chalk.gray(' Create routes/schedule.js to define scheduled tasks.'));
35
+ } else {
36
+ console.log(chalk.bold(' Scheduled Tasks\n'));
37
+
38
+ for (const task of tasks) {
39
+ const status = task.isRunning() ? chalk.yellow('RUNNING') : chalk.green('READY');
40
+ const nextRun = task.lastRun
41
+ ? `Last: ${task.lastRun.toLocaleString()}`
42
+ : chalk.gray('Never run');
43
+
44
+ console.log(` ${chalk.cyan(task.jobClass.name.padEnd(30))} ${status.padEnd(15)} ${nextRun}`);
45
+
46
+ if (task.cronExpression) {
47
+ console.log(` ${chalk.gray('Cron:')} ${task.cronExpression}`);
48
+ }
49
+
50
+ if (Object.keys(task.parameters).length > 0) {
51
+ console.log(` ${chalk.gray('Params:')} ${JSON.stringify(task.parameters)}`);
52
+ }
53
+
54
+ if (task.failures.length > 0) {
55
+ const lastFailure = task.failures[task.failures.length - 1];
56
+ console.log(` ${chalk.red('Last failure:')} ${lastFailure.error} (${lastFailure.timestamp.toLocaleString()})`);
57
+ }
58
+
59
+ console.log();
60
+ }
61
+ }
62
+
63
+ process.exit(0);
64
+ });
65
+
66
+ program
67
+ .command('schedule:test <taskName>')
68
+ .description('Run a specific scheduled task immediately for testing')
69
+ .action(async (taskName) => {
70
+ const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
71
+ if (!fs.existsSync(bootstrapPath)) {
72
+ console.error(chalk.red('\n ✖ Not inside a Millas project.\n'));
73
+ process.exit(1);
74
+ }
75
+
76
+ // Boot the app
77
+ let app;
78
+ try {
79
+ app = await require(bootstrapPath);
80
+ } catch (e) {
81
+ console.error(chalk.red(`\n ✖ Failed to load app: ${e.message}\n`));
82
+ process.exit(1);
83
+ }
84
+
85
+ const scheduler = app.make('scheduler');
86
+ const tasks = scheduler.getTasks();
87
+ const task = tasks.find(t => t.jobClass.name === taskName);
88
+
89
+ if (!task) {
90
+ console.error(chalk.red(`\n ✖ Task "${taskName}" not found.\n`));
91
+ console.log(chalk.gray(' Available tasks:'));
92
+ tasks.forEach(t => console.log(chalk.gray(` - ${t.jobClass.name}`)));
93
+ console.log();
94
+ process.exit(1);
95
+ }
96
+
97
+ console.log(chalk.blue(`\n ▶ Running ${taskName}...\n`));
98
+
99
+ try {
100
+ await scheduler._executeTask(task, new Date());
101
+ console.log(chalk.green(` ✔ ${taskName} completed successfully.\n`));
102
+ } catch (error) {
103
+ console.error(chalk.red(` ✖ ${taskName} failed: ${error.message}\n`));
104
+ process.exit(1);
105
+ }
106
+
107
+ process.exit(0);
108
+ });
109
+
110
+ program
111
+ .command('make:task <name>')
112
+ .description('Generate a new scheduled task class')
113
+ .action(async (name) => {
114
+ const taskName = name.endsWith('Task') ? name : `${name}Task`;
115
+ const taskPath = path.resolve(process.cwd(), 'app', 'tasks', `${taskName}.js`);
116
+
117
+ // Ensure directory exists
118
+ await fs.ensureDir(path.dirname(taskPath));
119
+
120
+ // Check if file already exists
121
+ if (await fs.pathExists(taskPath)) {
122
+ console.error(chalk.red(`\n ✖ Task ${taskName} already exists.\n`));
123
+ process.exit(1);
124
+ }
125
+
126
+ // Generate task class
127
+ const template = `'use strict';
128
+
129
+ const { Job } = require('millas/core/queue');
130
+
131
+ /**
132
+ * ${taskName}
133
+ *
134
+ * Scheduled task that runs automatically based on the schedule defined in routes/schedule.js
135
+ *
136
+ * Usage in routes/schedule.js:
137
+ * Schedule.job(${taskName}).daily().at('09:00');
138
+ */
139
+ class ${taskName} extends Job {
140
+ /**
141
+ * Constructor - DI container will inject dependencies automatically
142
+ */
143
+ constructor(/* inject dependencies here */) {
144
+ super();
145
+ // Store injected dependencies
146
+ }
147
+
148
+ /**
149
+ * Execute the scheduled task
150
+ */
151
+ async handle() {
152
+ // Implement your scheduled task logic here
153
+ console.log('${taskName} is running...');
154
+ }
155
+
156
+ /**
157
+ * Handle task failure (optional)
158
+ */
159
+ async failed(error) {
160
+ console.error('${taskName} failed:', error.message);
161
+ }
162
+ }
163
+
164
+ module.exports = ${taskName};
165
+ `;
166
+
167
+ await fs.writeFile(taskPath, template);
168
+
169
+ console.log(chalk.green(`\n ✔ Task created: ${taskPath}`));
170
+ console.log(chalk.gray('\n Next steps:'));
171
+ console.log(chalk.gray(` 1. Implement the handle() method in ${taskName}`));
172
+ console.log(chalk.gray(` 2. Add the task to routes/schedule.js:`));
173
+ console.log(chalk.gray(` Schedule.job(${taskName}).daily().at('09:00');`));
174
+ console.log();
175
+ });
176
+ };
@@ -58,6 +58,7 @@ class AppInitializer {
58
58
  if (!process.env.MILLAS_CLI_MODE) {
59
59
  await this._serve();
60
60
  }
61
+
61
62
  return this._kernel;
62
63
  }
63
64
 
@@ -107,6 +108,12 @@ class AppInitializer {
107
108
 
108
109
  this._kernel._container.instance('basePath', basePath);
109
110
 
111
+ // ── View engine ─────────────────────────────────────────────────────────────────
112
+ // Auto-configure Nunjucks as the default template engine.
113
+ // Looks for views/ in the project root. Zero config required.
114
+ // Disable via config/app.js: { views: false }
115
+ this._setupViewEngine(expressApp, basePath, appConfig);
116
+
110
117
  // ── Static file serving ───────────────────────────────────────────────
111
118
  // Auto-serve each storage disk that has a baseUrl configured.
112
119
  // Mirrors Laravel's public disk serving — zero config required.
@@ -182,6 +189,65 @@ class AppInitializer {
182
189
 
183
190
  // ── Internal ───────────────────────────────────────────────────────────────
184
191
 
192
+ _setupViewEngine(expressApp, basePath, appConfig) {
193
+ const path = require('path');
194
+ const fs = require('fs');
195
+
196
+ // Opt-out: views: false in config/app.js
197
+ if (appConfig.views === false) return;
198
+
199
+ const viewsConfig = appConfig.views || {};
200
+ const viewsDir = path.isAbsolute(viewsConfig.path || '')
201
+ ? viewsConfig.path
202
+ : path.join(basePath, viewsConfig.path || 'resources/views');
203
+
204
+ // Don't configure if views directory doesn't exist
205
+ if (!fs.existsSync(viewsDir)) return;
206
+
207
+ // Serve public/ directory for CSS, JS, images used in views
208
+ const publicDir = path.join(basePath, viewsConfig.public || 'public');
209
+ if (fs.existsSync(publicDir)) {
210
+ expressApp.use(express.static(publicDir));
211
+ }
212
+
213
+ const engine = viewsConfig.engine || 'nunjucks';
214
+
215
+ if (engine === 'nunjucks') {
216
+ // nunjucks is a core Millas dependency — always available
217
+ const nunjucks = require('nunjucks');
218
+
219
+ // Check if nunjucks is already configured for this express app
220
+ const existingEnv = expressApp.get('nunjucksEnvironment');
221
+ let env;
222
+
223
+ if (existingEnv) {
224
+ // Admin panel will reconfigure with multiple search paths
225
+ env = existingEnv;
226
+ } else {
227
+ env = nunjucks.configure(viewsDir, {
228
+ autoescape: viewsConfig.autoescape ?? true,
229
+ watch: viewsConfig.watch ?? (process.env.NODE_ENV !== 'production'),
230
+ noCache: viewsConfig.noCache ?? (process.env.NODE_ENV !== 'production'),
231
+ throwOnUndefined: viewsConfig.throwOnUndefined ?? false,
232
+ express: expressApp,
233
+ });
234
+ // Store reference for later use
235
+ expressApp.set('nunjucksEnvironment', env);
236
+ }
237
+
238
+ // Support both .html and .njk extensions
239
+ expressApp.set('view engine', 'html');
240
+ expressApp.engine('html', env.render.bind(env));
241
+ expressApp.engine('njk', env.render.bind(env));
242
+ expressApp.set('views', viewsDir);
243
+
244
+ } else {
245
+ // Custom engine — user must configure it themselves via .use()
246
+ expressApp.set('views', viewsDir);
247
+ expressApp.set('view engine', engine);
248
+ }
249
+ }
250
+
185
251
  _serveStorageDisks(expressApp, basePath) {
186
252
  const express = require('express');
187
253
  const path = require('path');
@@ -286,10 +352,18 @@ class AppInitializer {
286
352
  if (p) providers.push(p);
287
353
  }
288
354
 
289
- // ── 9. i18nopt-in via config/app.js use_i18n: true ───────────────
355
+ // ── 9. Scheduleralways on unless explicitly disabled ──────────────
356
+ // Built-in task scheduler that runs alongside the HTTP server.
357
+ // Automatically loads and executes scheduled tasks from routes/schedule.js.
358
+ if (cfg.scheduler !== false) {
359
+ const p = load('../scheduler/SchedulerServiceProvider');
360
+ if (p) providers.push(p);
361
+ }
362
+
363
+ // ── 10. i18n — opt-in via config/app.js use_i18n: true ──────────────
290
364
  // Mirrors Django's USE_I18N = True in settings.py.
291
365
  // Booted last so translations are available in all request handlers.
292
- // ── 9. Encryption — always on (APP_KEY drives it) ────────────────────
366
+ // ── 10. Encryption — always on (APP_KEY drives it) ────────────────────
293
367
  // Mirrors Laravel: the encrypter is always bound so Crypt / Encrypt
294
368
  // facades work out of the box. If APP_KEY is absent a clear error is
295
369
  // thrown on first use, not at boot — apps without encryption still start.
@@ -134,17 +134,25 @@ class HttpServer {
134
134
 
135
135
  _handleSignals() {
136
136
  const shutdown = async (signal) => {
137
+ process.stdout.write(`\n Shutting down (${signal})…\n`);
138
+
139
+ if (typeof this._options.onShutdown === 'function') {
140
+ try {
141
+ await this._options.onShutdown();
142
+ } catch (err) {
143
+ console.error('Shutdown error:', err.message);
144
+ }
145
+ }
146
+
147
+ // Stop scheduler if it exists
148
+ try {
149
+ const scheduler = this._app._container.make('scheduler');
150
+ await scheduler.stop();
151
+ } catch {
152
+ // Scheduler not registered or already stopped
153
+ }
154
+
137
155
  process.exit(0);
138
- // process.stdout.write(`\n Shutting down (${signal})…\n`);
139
- //
140
- // if (typeof this._options.onShutdown === 'function') {
141
- // try {
142
- // await this._options.onShutdown();
143
- // } catch {
144
- // }
145
- // }
146
- //
147
- // await this._app.shutdown(0);
148
156
  };
149
157
 
150
158
  process.once('SIGTERM', () => shutdown("SIGTERM"));
@@ -70,4 +70,6 @@ module.exports = {
70
70
  // Support
71
71
  Str, FluentString,
72
72
  Log: require('../logger').Log,
73
+ // Scheduler
74
+ TaskScheduler: require('../scheduler').TaskScheduler,
73
75
  };
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const { TaskScheduler, SchedulerServiceProvider } = require('../scheduler');
4
+
5
+ module.exports = {
6
+ TaskScheduler,
7
+ SchedulerServiceProvider,
8
+ };
@@ -55,11 +55,12 @@ class ErrorRenderer {
55
55
  * Render an error to the response — JSON or HTML based on Accept header.
56
56
  */
57
57
  static render(err, req, res) {
58
- const status = err.status || err.statusCode || 500;
59
- const isDev = process.env.NODE_ENV !== 'production';
60
- const wantsHtml = ErrorRenderer._wantsHtml(req);
58
+ try {
59
+ const status = err.status || err.statusCode || 500;
60
+ const isDev = process.env.NODE_ENV !== 'production';
61
+ const wantsHtml = ErrorRenderer._wantsHtml(req);
61
62
 
62
- res.status(status);
63
+ res.status(status);
63
64
 
64
65
  if (!wantsHtml) {
65
66
  // ── JSON response ──────────────────────────────────────────────────────
@@ -73,6 +74,31 @@ class ErrorRenderer {
73
74
  return res.json(body);
74
75
  }
75
76
 
77
+
78
+ // Check for custom error view in resources/views/errors/
79
+ // e.g. resources/views/errors/404.html, resources/views/errors/500.html
80
+ const customView = ErrorRenderer._findCustomErrorView(status);
81
+ if (customView) {
82
+ try {
83
+ return res.render(customView, {
84
+ status,
85
+ message: err.message,
86
+ error: err,
87
+ isDev,
88
+ statusTitle: ErrorRenderer._statusTitle(status),
89
+ }, (renderErr, html) => {
90
+ if (renderErr) {
91
+ // View engine error — fall back to built-in pages
92
+ if (!isDev || status < 500 && !err._forceDebug) {
93
+ return res.send(ErrorRenderer._renderSimple(status, err.message));
94
+ }
95
+ return res.send(ErrorRenderer._renderDebug(err, req, status));
96
+ }
97
+ res.send(html);
98
+ });
99
+ } catch { /* view engine not configured — fall through */ }
100
+ }
101
+
76
102
  if (!isDev || status < 500 && !err._forceDebug) {
77
103
  // ── Production / 4xx HTML ──────────────────────────────────────────────
78
104
  return res.send(ErrorRenderer._renderSimple(status, err.message));
@@ -80,10 +106,32 @@ class ErrorRenderer {
80
106
 
81
107
  // ── Development HTML — full debug page ────────────────────────────────────
82
108
  res.send(ErrorRenderer._renderDebug(err, req, status));
109
+ }catch (e){
110
+ console.log(e)
111
+ res.send("something went wrong")
112
+ }
83
113
  }
84
114
 
85
115
  // ─── HTML renderers ────────────────────────────────────────────────────────
86
116
 
117
+ static _findCustomErrorView(status) {
118
+ const fs = require('fs');
119
+ const path = require('path');
120
+ const viewsDir = path.join(process.cwd(), 'resources', 'views', 'errors');
121
+ const candidates = [
122
+ path.join(viewsDir, `${status}.html`),
123
+ path.join(viewsDir, `${status}.njk`),
124
+ path.join(viewsDir, 'error.html'),
125
+ path.join(viewsDir, 'error.njk'),
126
+ ];
127
+ for (const candidate of candidates) {
128
+ if (fs.existsSync(candidate)) {
129
+ return `errors/${require('path').basename(candidate)}`;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
87
135
  static _renderSimple(status, message) {
88
136
  const title = ErrorRenderer._statusTitle(status);
89
137
  return `<!DOCTYPE html>
@@ -139,6 +187,24 @@ class ErrorRenderer {
139
187
  'Uptime': `${Math.floor(process.uptime())}s`,
140
188
  };
141
189
 
190
+ // Host details for EINVALIDHOST errors
191
+ const hostDetailsHtml = err._hostDetails ? `
192
+ <!-- Host Details -->
193
+ <div class="section">
194
+ <div class="section-header" onclick="toggle(this)">
195
+ <span>Host Configuration</span>
196
+ <span class="toggle">▾</span>
197
+ </div>
198
+ <div class="section-body">
199
+ <table class="info-table">
200
+ <tr><td>Received Host</td><td style="font-family:monospace;color:var(--red)">${_esc(err._hostDetails.receivedHost)}</td></tr>
201
+ <tr><td>Allowed Hosts</td><td><pre class="json-val">${_esc(JSON.stringify(err._hostDetails.allowedHosts, null, 2))}</pre></td></tr>
202
+ <tr><td>Environment</td><td>${_esc(err._hostDetails.environment)}</td></tr>
203
+ ${err._hostDetails.suggestion ? `<tr><td>Suggestion</td><td style="color:var(--blue)">${_esc(err._hostDetails.suggestion)}</td></tr>` : ''}
204
+ </table>
205
+ </div>
206
+ </div>` : '';
207
+
142
208
  return `<!DOCTYPE html>
143
209
  <html lang="en">
144
210
  <head>
@@ -466,6 +532,8 @@ class ErrorRenderer {
466
532
  </div>
467
533
  </div>
468
534
 
535
+ ${hostDetailsHtml}
536
+
469
537
  ${err.errors ? `
470
538
  <!-- Validation errors -->
471
539
  <div class="section">
@@ -81,7 +81,18 @@ class EventEmitter {
81
81
  async _invoke(handler, event) {
82
82
  // Listener class (has handle() on prototype)
83
83
  if (typeof handler === 'function' && typeof handler.prototype?.handle === 'function') {
84
- const inst = new handler();
84
+ // Resolve static inject dependencies from the container
85
+ let inst;
86
+ if (handler.inject && Array.isArray(handler.inject) && handler.inject.length) {
87
+ const Facade = require('../facades/Facade');
88
+ const container = Facade._container;
89
+ const deps = handler.inject.map(key => {
90
+ try { return container ? container.make(key) : undefined; } catch { return undefined; }
91
+ });
92
+ inst = new handler(...deps);
93
+ } else {
94
+ inst = new handler();
95
+ }
85
96
  if (handler.queue && this._queue) {
86
97
  const Job = require('../queue/Job');
87
98
  const q = this._queue;
@@ -1,43 +1,53 @@
1
1
  'use strict';
2
2
 
3
+ const { createFacade } = require('./Facade');
4
+
3
5
  /**
4
- * millas/facades/Database
5
- *
6
- * ORM models, field definitions, and query builder.
7
- *
8
- * const { Model, fields } = require('millas/facades/Database');
9
- *
10
- * class Post extends Model {
11
- * static table = 'posts';
12
- * static fields = {
13
- * id: fields.id(),
14
- * title: fields.string({ max: 255 }),
15
- * author: fields.ForeignKey('User', { relatedName: 'posts' }),
16
- * published: fields.boolean({ default: false }),
17
- * created_at: fields.timestamp(),
18
- * updated_at: fields.timestamp(),
19
- * };
20
- * }
6
+ * Database facade — Laravel-style DB access.
7
+ *
8
+ * Usage:
9
+ * const { Database } = require('millas');
10
+ * // or
11
+ * const Database = require('millas/facades/Database');
12
+ *
13
+ * // Raw SQL
14
+ * const users = await Database.select('SELECT * FROM users WHERE id = ?', [1]);
15
+ * await Database.insert('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);
16
+ * await Database.update('UPDATE users SET name = ? WHERE id = ?', ['Jane', 1]);
17
+ * await Database.delete('DELETE FROM users WHERE id = ?', [1]);
18
+ *
19
+ * // Query Builder (most common)
20
+ * const users = await Database.table('users').get();
21
+ * const user = await Database.table('users').where('id', 1).first();
22
+ * await Database.table('users').insert({ name: 'John', email: 'john@example.com' });
23
+ * await Database.table('users').where('id', 1).update({ name: 'Jane' });
24
+ * await Database.table('users').where('id', 1).delete();
25
+ *
26
+ * // Transactions
27
+ * await Database.transaction(async (trx) => {
28
+ * await trx.table('accounts').update({ balance: 100 });
29
+ * await trx.table('transactions').insert({ amount: 100 });
30
+ * });
31
+ *
32
+ * // Multiple connections
33
+ * await Database.connection('mysql').table('users').get();
34
+ *
35
+ * // Raw expressions
36
+ * await Database.table('users').select(Database.raw('COUNT(*) as total')).first();
37
+ *
38
+ * @class
39
+ * @property {function(string): *} table - Query builder for a table
40
+ * @property {function(string, array=): Promise<*>} raw - Execute raw SQL
41
+ * @property {function(string, array=): Promise<*>} select - SELECT query
42
+ * @property {function(string, array=): Promise<*>} insert - INSERT query
43
+ * @property {function(string, array=): Promise<*>} update - UPDATE query
44
+ * @property {function(string, array=): Promise<*>} delete - DELETE query
45
+ * @property {function(function): Promise<*>} transaction - Run queries in transaction
46
+ * @property {function(string=): *} connection - Get named connection
47
+ *
48
+ * @see src/orm/drivers/DatabaseManager.js
21
49
  */
50
+ class Database extends createFacade('db') {
51
+ }
22
52
 
23
- const {
24
- Model,
25
- fields,
26
- QueryBuilder,
27
- DatabaseManager,
28
- SchemaBuilder,
29
- MigrationRunner,
30
- ModelInspector,
31
- DatabaseServiceProvider,
32
- } = require('../core');
33
-
34
- module.exports = {
35
- Model,
36
- fields,
37
- QueryBuilder,
38
- DatabaseManager,
39
- SchemaBuilder,
40
- MigrationRunner,
41
- ModelInspector,
42
- DatabaseServiceProvider,
43
- };
53
+ module.exports = Database;