millas 0.2.9 → 0.2.10

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.9",
3
+ "version": "0.2.10",
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": {
@@ -0,0 +1,307 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const Application = require('./Application');
5
+
6
+ /**
7
+ * MillasApp
8
+ *
9
+ * A clean factory wrapper around Application that wires up Express,
10
+ * core providers, routes, and the admin panel automatically.
11
+ *
12
+ * Reduces bootstrap/app.js from ~60 lines to ~8.
13
+ *
14
+ * ── Usage ───────────────────────────────────────────────────────────────────
15
+ *
16
+ * require('dotenv').config();
17
+ * const { MillasApp } = require('millas');
18
+ * const AppServiceProvider = require('../providers/AppServiceProvider');
19
+ *
20
+ * const app = MillasApp.create()
21
+ * .providers([AppServiceProvider])
22
+ * .routes(Route => {
23
+ * require('../routes/web')(Route);
24
+ * require('../routes/api')(Route);
25
+ * })
26
+ * .withAdmin();
27
+ *
28
+ * module.exports = app.start();
29
+ *
30
+ * ── Escape hatches ──────────────────────────────────────────────────────────
31
+ *
32
+ * // Access the raw Express app
33
+ * app.express.use(someExpressMiddleware());
34
+ *
35
+ * // Access the DI container
36
+ * app.container.make('db');
37
+ *
38
+ * // Access the underlying Application kernel
39
+ * app.kernel
40
+ *
41
+ * ── Advanced ────────────────────────────────────────────────────────────────
42
+ *
43
+ * MillasApp.create({
44
+ * // Disable a core provider
45
+ * mail: false,
46
+ * queue: false,
47
+ *
48
+ * // Custom admin prefix
49
+ * admin: { prefix: '/cms', title: 'My CMS' },
50
+ *
51
+ * // Add Express middleware before routes
52
+ * expressMiddleware: [cors(), helmet()],
53
+ *
54
+ * // Called once the app is listening
55
+ * onStart: (port, host) => console.log(`Running on ${host}:${port}`),
56
+ * })
57
+ */
58
+ class MillasApp {
59
+
60
+ // ─── Factory ──────────────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Create a new MillasApp instance.
64
+ * @param {object} [options]
65
+ * @param {boolean} [options.database=true] — register DatabaseServiceProvider
66
+ * @param {boolean} [options.cache=true] — register CacheServiceProvider
67
+ * @param {boolean} [options.storage=true] — register StorageServiceProvider
68
+ * @param {boolean} [options.mail=true] — register MailServiceProvider
69
+ * @param {boolean} [options.queue=true] — register QueueServiceProvider
70
+ * @param {boolean} [options.events=true] — register EventServiceProvider
71
+ * @param {boolean} [options.logging=true] — register LogServiceProvider
72
+ * @param {Array} [options.expressMiddleware] — Express middleware applied before routes
73
+ * @param {Function}[options.onStart] — callback(port, host) after listen
74
+ */
75
+ static create(options = {}) {
76
+ return new MillasApp(options);
77
+ }
78
+
79
+ constructor(options = {}) {
80
+ this._options = options;
81
+ this._userProviders = [];
82
+ this._routesCb = null;
83
+ this._adminOptions = null; // null = no admin; object/true = mount admin
84
+ this._expressApp = null;
85
+ this._kernel = null;
86
+ this._started = false;
87
+ }
88
+
89
+ // ─── Fluent API ───────────────────────────────────────────────────────────
90
+
91
+ /**
92
+ * Register application service providers.
93
+ *
94
+ * .providers([AppServiceProvider, PaymentServiceProvider])
95
+ */
96
+ providers(list = []) {
97
+ this._userProviders.push(...list);
98
+ return this;
99
+ }
100
+
101
+ /**
102
+ * Define application routes.
103
+ *
104
+ * .routes(Route => {
105
+ * require('../routes/web')(Route);
106
+ * require('../routes/api')(Route);
107
+ * })
108
+ */
109
+ routes(callback) {
110
+ this._routesCb = callback;
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Register a named middleware alias.
116
+ * Must be called before start().
117
+ *
118
+ * .middleware('verified', EmailVerifiedMiddleware)
119
+ */
120
+ middleware(alias, handler) {
121
+ if (!this._pendingMiddleware) this._pendingMiddleware = [];
122
+ this._pendingMiddleware.push({ alias, handler });
123
+ return this;
124
+ }
125
+
126
+ /**
127
+ * Mount the admin panel.
128
+ *
129
+ * .withAdmin() // default /admin
130
+ * .withAdmin({ prefix: '/cms' }) // custom prefix
131
+ * .withAdmin({ prefix: '/admin', auth: { users: [...] } })
132
+ */
133
+ withAdmin(options = {}) {
134
+ this._adminOptions = options;
135
+ return this;
136
+ }
137
+
138
+ // ─── Start ────────────────────────────────────────────────────────────────
139
+
140
+ /**
141
+ * Boot all providers, mount routes, and start listening.
142
+ * Returns an object with { app, expressApp, route } for module.exports.
143
+ *
144
+ * Safe to await if you need to run code after boot:
145
+ * const { app } = await MillasApp.create()...start();
146
+ */
147
+ start() {
148
+ // Build Express + Application kernel
149
+ this._expressApp = express();
150
+ this._expressApp.use(express.json());
151
+ this._expressApp.use(express.urlencoded({ extended: true }));
152
+
153
+ // Apply any extra Express middleware (e.g. helmet, cors)
154
+ for (const mw of (this._options.expressMiddleware || [])) {
155
+ this._expressApp.use(mw);
156
+ }
157
+
158
+ this._kernel = new Application(this._expressApp);
159
+
160
+ // Register pending middleware aliases
161
+ for (const { alias, handler } of (this._pendingMiddleware || [])) {
162
+ this._kernel.middleware(alias, handler);
163
+ }
164
+
165
+ // Register core providers (auto, unless disabled)
166
+ const coreProviders = this._buildCoreProviders();
167
+ this._kernel.providers([...coreProviders, ...this._userProviders]);
168
+
169
+ // Register routes
170
+ if (this._routesCb) {
171
+ this._kernel.routes(this._routesCb);
172
+ }
173
+
174
+ // Boot asynchronously
175
+ const self = this;
176
+ const boot = (async () => {
177
+ await self._kernel.boot();
178
+
179
+ if (!process.env.MILLAS_ROUTE_LIST) {
180
+ self._kernel.mountRoutes();
181
+
182
+ // Admin panel (mounted between routes and fallbacks)
183
+ if (self._adminOptions !== null) {
184
+ const adminOpts = typeof self._adminOptions === 'object'
185
+ ? self._adminOptions
186
+ : {};
187
+
188
+ try {
189
+ const Admin = require('../admin/Admin');
190
+ if (Object.keys(adminOpts).length) {
191
+ Admin.configure(adminOpts);
192
+ }
193
+ Admin.mount(self._expressApp);
194
+ } catch (err) {
195
+ process.stderr.write(`[millas] Admin mount failed: ${err.message}\n`);
196
+ }
197
+ }
198
+
199
+ self._kernel.mountFallbacks();
200
+
201
+ const onStart = self._options.onStart || null;
202
+ self._kernel.listen(undefined, undefined, onStart || undefined);
203
+ }
204
+
205
+ self._started = true;
206
+ })();
207
+
208
+ // Expose the promise for testing / programmatic use
209
+ this._bootPromise = boot;
210
+
211
+ return this;
212
+ }
213
+
214
+ /**
215
+ * Await the boot promise. Useful in tests or scripts.
216
+ *
217
+ * const app = await MillasApp.create().routes(...).start().ready();
218
+ */
219
+ async ready() {
220
+ await (this._bootPromise || Promise.resolve());
221
+ return this;
222
+ }
223
+
224
+ // ─── Escape hatches ───────────────────────────────────────────────────────
225
+
226
+ /** The raw Express application instance. */
227
+ get express() { return this._expressApp; }
228
+
229
+ /** The underlying Application kernel. */
230
+ get kernel() { return this._kernel; }
231
+
232
+ /** The DI container. */
233
+ get container() { return this._kernel?._container; }
234
+
235
+ /** The Route instance (for programmatic route registration). */
236
+ get route() { return this._kernel?._route; }
237
+
238
+ // ─── module.exports helper ────────────────────────────────────────────────
239
+
240
+ /**
241
+ * Returns a plain object suitable for module.exports.
242
+ * Matches the shape existing code expects.
243
+ */
244
+ toModule() {
245
+ const self = this;
246
+ return {
247
+ app: this._kernel,
248
+ expressApp: this._expressApp,
249
+ get route() { return self._kernel?._route; },
250
+ };
251
+ }
252
+
253
+ // ─── Internal ─────────────────────────────────────────────────────────────
254
+
255
+ _buildCoreProviders() {
256
+ const opts = this._options;
257
+ const providers = [];
258
+
259
+ // Helper — require a provider, silently skip if not found
260
+ const load = (path) => {
261
+ try { return require(path); } catch { return null; }
262
+ };
263
+
264
+ // Logging — first so all other providers can use Log
265
+ if (opts.logging !== false) {
266
+ const p = load('../providers/LogServiceProvider');
267
+ if (p) providers.push(p);
268
+ }
269
+
270
+ // Database
271
+ if (opts.database !== false) {
272
+ const p = load('../providers/DatabaseServiceProvider');
273
+ if (p) providers.push(p);
274
+ }
275
+
276
+ // Cache + Storage
277
+ if (opts.cache !== false || opts.storage !== false) {
278
+ const p = load('../providers/CacheStorageServiceProvider');
279
+ if (p) {
280
+ if (opts.cache !== false && p.CacheServiceProvider) providers.push(p.CacheServiceProvider);
281
+ if (opts.storage !== false && p.StorageServiceProvider) providers.push(p.StorageServiceProvider);
282
+ }
283
+ }
284
+
285
+ // Mail
286
+ if (opts.mail !== false) {
287
+ const p = load('../providers/MailServiceProvider');
288
+ if (p) providers.push(p);
289
+ }
290
+
291
+ // Queue
292
+ if (opts.queue !== false) {
293
+ const p = load('../providers/QueueServiceProvider');
294
+ if (p) providers.push(p);
295
+ }
296
+
297
+ // Events
298
+ if (opts.events !== false) {
299
+ const p = load('../providers/EventServiceProvider');
300
+ if (p) providers.push(p);
301
+ }
302
+
303
+ return providers;
304
+ }
305
+ }
306
+
307
+ module.exports = MillasApp;
@@ -2,12 +2,14 @@
2
2
 
3
3
  const Container = require('./Container');
4
4
  const Application = require('./Application');
5
+ const MillasApp = require('./MillasApp');
5
6
  const ServiceProvider = require('../providers/ServiceProvider');
6
7
  const ProviderRegistry = require('../providers/ProviderRegistry');
7
8
 
8
9
  module.exports = {
9
10
  Container,
10
11
  Application,
12
+ MillasApp,
11
13
  ServiceProvider,
12
14
  ProviderRegistry,
13
15
  };
package/src/index.js CHANGED
@@ -1,21 +1,5 @@
1
1
  'use strict';
2
2
 
3
- // ── Logger ────────────────────────────────────────────────────────
4
- const {
5
- Log,
6
- Logger,
7
- LEVELS,
8
- LEVEL_NAMES,
9
- PrettyFormatter,
10
- JsonFormatter,
11
- SimpleFormatter,
12
- ConsoleChannel,
13
- FileChannel,
14
- NullChannel,
15
- StackChannel,
16
- } = require('./logger/index');
17
- const LogServiceProvider = require('./providers/LogServiceProvider');
18
-
19
3
  // ── HTTP Layer ────────────────────────────────────────────────────
20
4
  const Controller = require('./controller/Controller');
21
5
  const Middleware = require('./middleware/Middleware');
@@ -28,6 +12,7 @@ const HttpError = require('./errors/HttpError');
28
12
  // ── DI Container ─────────────────────────────────────────────────
29
13
  const Container = require('./container/Container');
30
14
  const Application = require('./container/Application');
15
+ const MillasApp = require('./container/MillasApp');
31
16
  const ServiceProvider = require('./providers/ServiceProvider');
32
17
  const ProviderRegistry = require('./providers/ProviderRegistry');
33
18
 
@@ -76,16 +61,11 @@ const Storage = require('./storage/Storage');
76
61
  const LocalDriver = require('./storage/drivers/LocalDriver');
77
62
 
78
63
  module.exports = {
79
- // Logger
80
- Log, Logger, LEVELS, LEVEL_NAMES,
81
- PrettyFormatter, JsonFormatter, SimpleFormatter,
82
- ConsoleChannel, FileChannel, NullChannel, StackChannel,
83
- LogServiceProvider,
84
64
  // HTTP
85
65
  Controller, Middleware, MiddlewarePipeline,
86
66
  CorsMiddleware, ThrottleMiddleware, LogMiddleware, HttpError,
87
67
  // DI
88
- Container, Application, ServiceProvider, ProviderRegistry,
68
+ Container, Application, MillasApp, ServiceProvider, ProviderRegistry,
89
69
  // ORM
90
70
  Model, fields, QueryBuilder, DatabaseManager, SchemaBuilder,
91
71
  MigrationRunner, ModelInspector, DatabaseServiceProvider,
@@ -88,77 +88,18 @@ module.exports = {
88
88
 
89
89
  require('dotenv').config();
90
90
 
91
- const express = require('express');
92
-
93
- function resolveMillas() {
94
- try { return require('millas/src'); } catch {}
95
- try {
96
- const path = require('path');
97
- const cliPath = require.resolve('millas/bin/millas.js');
98
- const millasSrc = path.join(path.dirname(cliPath), '..', 'src', 'index.js');
99
- return require(millasSrc);
100
- } catch {}
101
- throw new Error('Cannot find millas. Run: npm install millas');
102
- }
103
-
104
- const {
105
- Application,
106
- Admin,
107
- LogServiceProvider,
108
- CacheServiceProvider,
109
- StorageServiceProvider,
110
- MailServiceProvider,
111
- QueueServiceProvider,
112
- EventServiceProvider,
113
- } = resolveMillas();
114
-
91
+ const { MillasApp } = require('millas');
115
92
  const AppServiceProvider = require('../providers/AppServiceProvider');
116
93
 
117
- const expressApp = express();
118
- expressApp.use(express.json());
119
- expressApp.use(express.urlencoded({ extended: true }));
120
-
121
- // ── Build the Millas application kernel ─────────────────────────
122
- const app = new Application(expressApp);
123
-
124
- // ── Register service providers ──────────────────────────────────
125
- app.providers([
126
- LogServiceProvider, // first — so Log works in all other providers
127
- CacheServiceProvider,
128
- StorageServiceProvider,
129
- MailServiceProvider,
130
- QueueServiceProvider,
131
- EventServiceProvider,
132
- AppServiceProvider,
133
- ]);
134
-
135
- // ── Define routes ────────────────────────────────────────────────
136
- app.routes(Route => {
137
- require('../routes/web')(Route);
138
- require('../routes/api')(Route);
139
- });
140
-
141
- // ── Boot + mount + listen ────────────────────────────────────────
142
- (async () => {
143
- await app.boot();
144
-
145
- if (!process.env.MILLAS_ROUTE_LIST) {
146
- // Register app routes first (without 404 handler yet)
147
- app.mountRoutes();
148
-
149
- // Admin panel mounts here — before the 404 fallback
150
- // To disable: comment out this line
151
- // To change path: Admin.configure({ prefix: '/cms' });
152
- Admin.mount(expressApp);
153
-
154
- // Add 404 + error handlers LAST
155
- app.mountFallbacks();
156
-
157
- app.listen();
158
- }
159
- })();
94
+ const app = MillasApp.create()
95
+ .providers([AppServiceProvider])
96
+ .routes(Route => {
97
+ require('../routes/web')(Route);
98
+ require('../routes/api')(Route);
99
+ })
100
+ .withAdmin();
160
101
 
161
- module.exports = { app, expressApp, get route() { return app.route; } };
102
+ module.exports = app.start();
162
103
  `,
163
104
 
164
105
  // ─── routes/web.js ────────────────────────────────────────────
@@ -198,38 +139,6 @@ module.exports = function (Route) {
198
139
 
199
140
  });
200
141
  };
201
- `,
202
-
203
- // ─── config/logging.js ────────────────────────────────────────
204
- 'config/logging.js': `'use strict';
205
-
206
- /**
207
- * Logging Configuration
208
- *
209
- * Levels (lowest → highest): verbose | debug | info | warn | error | wtf
210
- * Channels: 'console' | 'file' | 'null'
211
- * Formats: 'pretty' (coloured) | 'simple' (plain text) | 'json' (structured)
212
- */
213
- module.exports = {
214
- level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
215
-
216
- defaultTag: process.env.APP_NAME || 'App',
217
-
218
- channels: [
219
- {
220
- driver: 'console',
221
- format: 'pretty',
222
- },
223
- {
224
- driver: 'file',
225
- format: 'simple',
226
- path: 'storage/logs',
227
- prefix: '${projectName}',
228
- level: 'warn',
229
- maxFiles: 30,
230
- },
231
- ],
232
- };
233
142
  `,
234
143
 
235
144
  // ─── config/app.js ────────────────────────────────────────────
@@ -332,25 +241,15 @@ module.exports = {
332
241
  // ─── providers/AppServiceProvider.js ──────────────────────────
333
242
  'providers/AppServiceProvider.js': `'use strict';
334
243
 
335
- function resolveMillas() {
336
- try { return require('millas/src'); } catch {}
337
- try {
338
- const path = require('path');
339
- const cli = require.resolve('millas/bin/millas.js');
340
- return require(path.join(path.dirname(cli), '..', 'src', 'index.js'));
341
- } catch {}
342
- throw new Error('Cannot find millas. Run: npm install millas');
343
- }
344
-
345
- const { ServiceProvider } = resolveMillas();
244
+ const { ServiceProvider } = require('millas');
346
245
 
347
246
  /**
348
247
  * AppServiceProvider
349
248
  *
350
249
  * Register and bootstrap your application services here.
351
250
  *
352
- * register() — bind things into the container
353
- * boot() — called after all providers have registered
251
+ * register() — bind things into the container (synchronous)
252
+ * boot() — called after all providers have registered (async OK)
354
253
  */
355
254
  class AppServiceProvider extends ServiceProvider {
356
255
  register(container) {
@@ -360,7 +259,13 @@ class AppServiceProvider extends ServiceProvider {
360
259
  }
361
260
 
362
261
  async boot(container, app) {
363
- // const logger = container.make('Logger');
262
+ // const { Log } = require('millas');
263
+ // Log.tag('AppServiceProvider').i('Booted');
264
+
265
+ // Register resources in the Admin panel:
266
+ // const { Admin, AdminResource } = require('millas');
267
+ // const Post = require('../app/models/Post');
268
+ // Admin.register(Post);
364
269
  }
365
270
  }
366
271