millas 0.1.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/bin/millas.js +6 -0
  4. package/package.json +56 -0
  5. package/src/admin/Admin.js +617 -0
  6. package/src/admin/index.js +13 -0
  7. package/src/admin/resources/AdminResource.js +317 -0
  8. package/src/auth/Auth.js +254 -0
  9. package/src/auth/AuthController.js +188 -0
  10. package/src/auth/AuthMiddleware.js +67 -0
  11. package/src/auth/Hasher.js +51 -0
  12. package/src/auth/JwtDriver.js +74 -0
  13. package/src/auth/RoleMiddleware.js +44 -0
  14. package/src/cache/Cache.js +231 -0
  15. package/src/cache/drivers/FileDriver.js +152 -0
  16. package/src/cache/drivers/MemoryDriver.js +158 -0
  17. package/src/cache/drivers/NullDriver.js +27 -0
  18. package/src/cache/index.js +8 -0
  19. package/src/cli.js +27 -0
  20. package/src/commands/make.js +61 -0
  21. package/src/commands/migrate.js +174 -0
  22. package/src/commands/new.js +50 -0
  23. package/src/commands/queue.js +92 -0
  24. package/src/commands/route.js +93 -0
  25. package/src/commands/serve.js +50 -0
  26. package/src/container/Application.js +177 -0
  27. package/src/container/Container.js +281 -0
  28. package/src/container/index.js +13 -0
  29. package/src/controller/Controller.js +367 -0
  30. package/src/errors/HttpError.js +29 -0
  31. package/src/events/Event.js +39 -0
  32. package/src/events/EventEmitter.js +151 -0
  33. package/src/events/Listener.js +46 -0
  34. package/src/events/index.js +15 -0
  35. package/src/index.js +93 -0
  36. package/src/mail/Mail.js +210 -0
  37. package/src/mail/MailMessage.js +196 -0
  38. package/src/mail/TemplateEngine.js +150 -0
  39. package/src/mail/drivers/LogDriver.js +36 -0
  40. package/src/mail/drivers/MailgunDriver.js +84 -0
  41. package/src/mail/drivers/SendGridDriver.js +97 -0
  42. package/src/mail/drivers/SmtpDriver.js +67 -0
  43. package/src/mail/index.js +19 -0
  44. package/src/middleware/AuthMiddleware.js +46 -0
  45. package/src/middleware/CorsMiddleware.js +59 -0
  46. package/src/middleware/LogMiddleware.js +61 -0
  47. package/src/middleware/Middleware.js +36 -0
  48. package/src/middleware/MiddlewarePipeline.js +94 -0
  49. package/src/middleware/ThrottleMiddleware.js +61 -0
  50. package/src/orm/drivers/DatabaseManager.js +135 -0
  51. package/src/orm/fields/index.js +132 -0
  52. package/src/orm/index.js +19 -0
  53. package/src/orm/migration/MigrationRunner.js +216 -0
  54. package/src/orm/migration/ModelInspector.js +338 -0
  55. package/src/orm/migration/SchemaBuilder.js +173 -0
  56. package/src/orm/model/Model.js +371 -0
  57. package/src/orm/query/QueryBuilder.js +197 -0
  58. package/src/providers/AdminServiceProvider.js +40 -0
  59. package/src/providers/AuthServiceProvider.js +53 -0
  60. package/src/providers/CacheStorageServiceProvider.js +71 -0
  61. package/src/providers/DatabaseServiceProvider.js +45 -0
  62. package/src/providers/EventServiceProvider.js +34 -0
  63. package/src/providers/MailServiceProvider.js +51 -0
  64. package/src/providers/ProviderRegistry.js +82 -0
  65. package/src/providers/QueueServiceProvider.js +52 -0
  66. package/src/providers/ServiceProvider.js +45 -0
  67. package/src/queue/Job.js +135 -0
  68. package/src/queue/Queue.js +147 -0
  69. package/src/queue/drivers/DatabaseDriver.js +194 -0
  70. package/src/queue/drivers/SyncDriver.js +72 -0
  71. package/src/queue/index.js +16 -0
  72. package/src/queue/workers/QueueWorker.js +140 -0
  73. package/src/router/MiddlewareRegistry.js +82 -0
  74. package/src/router/Route.js +255 -0
  75. package/src/router/RouteGroup.js +19 -0
  76. package/src/router/RouteRegistry.js +55 -0
  77. package/src/router/Router.js +138 -0
  78. package/src/router/index.js +15 -0
  79. package/src/scaffold/generator.js +34 -0
  80. package/src/scaffold/maker.js +272 -0
  81. package/src/scaffold/templates.js +350 -0
  82. package/src/storage/Storage.js +170 -0
  83. package/src/storage/drivers/LocalDriver.js +215 -0
  84. package/src/storage/index.js +6 -0
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
7
+
8
+ function resolveAppPath(...segments) {
9
+ return path.resolve(process.cwd(), ...segments);
10
+ }
11
+
12
+ function timestamp() {
13
+ return new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14);
14
+ }
15
+
16
+ function pascalCase(str) {
17
+ return str.charAt(0).toUpperCase() + str.slice(1);
18
+ }
19
+
20
+ async function write(filePath, content) {
21
+ await fs.ensureDir(path.dirname(filePath));
22
+ if (await fs.pathExists(filePath)) {
23
+ throw new Error(`File already exists: ${filePath}`);
24
+ }
25
+ await fs.writeFile(filePath, content, 'utf8');
26
+ return filePath;
27
+ }
28
+
29
+ // ─── Generators ───────────────────────────────────────────────────────────────
30
+
31
+ async function makeController(name, options = {}) {
32
+ const className = pascalCase(name.endsWith('Controller') ? name : `${name}Controller`);
33
+ const filePath = resolveAppPath('app/controllers', `${className}.js`);
34
+
35
+ const resource = options.resource;
36
+
37
+ const content = resource
38
+ ? `'use strict';
39
+
40
+ const { Controller } = require('millas/src');
41
+
42
+ /**
43
+ * ${className}
44
+ *
45
+ * Resource controller — handles CRUD operations.
46
+ */
47
+ class ${className} extends Controller {
48
+ /** GET /${name.toLowerCase()}s */
49
+ async index(req, res) {
50
+ return this.ok(res, { data: [] });
51
+ }
52
+
53
+ /** GET /${name.toLowerCase()}s/:id */
54
+ async show(req, res) {
55
+ const id = this.param(req, 'id');
56
+ return this.ok(res, { data: { id } });
57
+ }
58
+
59
+ /** POST /${name.toLowerCase()}s */
60
+ async store(req, res) {
61
+ const data = await this.validate(req, {
62
+ // 'name': 'required|string|max:255',
63
+ });
64
+ return this.created(res, { data });
65
+ }
66
+
67
+ /** PUT /${name.toLowerCase()}s/:id */
68
+ async update(req, res) {
69
+ const id = this.param(req, 'id');
70
+ const data = this.except(req, ['id']);
71
+ return this.ok(res, { data: { id, ...data } });
72
+ }
73
+
74
+ /** DELETE /${name.toLowerCase()}s/:id */
75
+ async destroy(req, res) {
76
+ return this.noContent(res);
77
+ }
78
+ }
79
+
80
+ module.exports = ${className};
81
+ `
82
+ : `'use strict';
83
+
84
+ const { Controller } = require('millas/src');
85
+
86
+ /**
87
+ * ${className}
88
+ */
89
+ class ${className} extends Controller {
90
+ async index(req, res) {
91
+ return this.ok(res, { message: 'Hello from ${className}' });
92
+ }
93
+ }
94
+
95
+ module.exports = ${className};
96
+ `;
97
+
98
+ return write(filePath, content);
99
+ }
100
+
101
+ async function makeModel(name, options = {}) {
102
+ const className = pascalCase(name);
103
+ const tableName = name.toLowerCase() + 's';
104
+ const filePath = resolveAppPath('app/models', `${className}.js`);
105
+
106
+ const content = `'use strict';
107
+
108
+ const { Model, fields } = require('millas/src');
109
+
110
+ /**
111
+ * ${className} Model
112
+ *
113
+ * Represents the "${tableName}" table.
114
+ * Run: millas makemigrations — to generate the migration.
115
+ * Run: millas migrate — to apply it.
116
+ */
117
+ class ${className} extends Model {
118
+ static table = '${tableName}';
119
+
120
+ static fields = {
121
+ id: fields.id(),
122
+ created_at: fields.timestamp(),
123
+ updated_at: fields.timestamp(),
124
+ };
125
+ }
126
+
127
+ module.exports = ${className};
128
+ `;
129
+
130
+ const result = await write(filePath, content);
131
+
132
+ if (options.migration) {
133
+ await makeMigration(`create_${tableName}_table`);
134
+ }
135
+
136
+ return result;
137
+ }
138
+
139
+ async function makeMiddleware(name) {
140
+ const className = pascalCase(name.endsWith('Middleware') ? name : `${name}Middleware`);
141
+ const filePath = resolveAppPath('app/middleware', `${className}.js`);
142
+
143
+ const content = `'use strict';
144
+
145
+ const { Middleware } = require('millas/src');
146
+
147
+ /**
148
+ * ${className}
149
+ *
150
+ * Usage:
151
+ * 1. Register in bootstrap/app.js:
152
+ * middlewareRegistry.register('${name.toLowerCase()}', ${className});
153
+ *
154
+ * 2. Apply to routes:
155
+ * Route.middleware(['${name.toLowerCase()}']).group(() => { ... });
156
+ */
157
+ class ${className} extends Middleware {
158
+ /**
159
+ * Handle the incoming request.
160
+ * Call next() to continue, or return a response to halt the chain.
161
+ */
162
+ async handle(req, res, next) {
163
+ // Your middleware logic here
164
+
165
+ next();
166
+ }
167
+ }
168
+
169
+ module.exports = ${className};
170
+ `;
171
+
172
+ return write(filePath, content);
173
+ }
174
+
175
+ async function makeService(name) {
176
+ const className = pascalCase(name.endsWith('Service') ? name : `${name}Service`);
177
+ const filePath = resolveAppPath('app/services', `${className}.js`);
178
+
179
+ const content = `'use strict';
180
+
181
+ /**
182
+ * ${className}
183
+ *
184
+ * Business logic service.
185
+ * Register in AppServiceProvider:
186
+ * container.bind('${className}', ${className});
187
+ */
188
+ class ${className} {
189
+ constructor() {
190
+ // Inject dependencies here (Phase 4)
191
+ }
192
+ }
193
+
194
+ module.exports = ${className};
195
+ `;
196
+
197
+ return write(filePath, content);
198
+ }
199
+
200
+ async function makeJob(name) {
201
+ const className = pascalCase(name.endsWith('Job') ? name : `${name}Job`);
202
+ const filePath = resolveAppPath('app/jobs', `${className}.js`);
203
+
204
+ const content = `'use strict';
205
+
206
+ /**
207
+ * ${className}
208
+ *
209
+ * Background job — dispatched via:
210
+ * dispatch(new ${className}(payload))
211
+ *
212
+ * Phase 9: Queue system will process this job.
213
+ */
214
+ class ${className} {
215
+ constructor(payload = {}) {
216
+ this.payload = payload;
217
+ }
218
+
219
+ /**
220
+ * Execute the job.
221
+ */
222
+ async handle() {
223
+ // Your job logic here
224
+ console.log('${className} running with:', this.payload);
225
+ }
226
+ }
227
+
228
+ module.exports = ${className};
229
+ `;
230
+
231
+ return write(filePath, content);
232
+ }
233
+
234
+ async function makeMigration(name) {
235
+ const fileName = `${timestamp()}_${name}.js`;
236
+ const filePath = resolveAppPath('database/migrations', fileName);
237
+
238
+ const content = `'use strict';
239
+
240
+ /**
241
+ * Migration: ${name}
242
+ */
243
+ module.exports = {
244
+ async up(db) {
245
+ // Write your migration here
246
+ // Example:
247
+ // await db.schema.createTable('users', (table) => {
248
+ // table.id();
249
+ // table.string('name');
250
+ // table.string('email').unique();
251
+ // table.timestamps();
252
+ // });
253
+ },
254
+
255
+ async down(db) {
256
+ // Rollback logic here
257
+ // await db.schema.dropTableIfExists('users');
258
+ },
259
+ };
260
+ `;
261
+
262
+ return write(filePath, content);
263
+ }
264
+
265
+ module.exports = {
266
+ makeController,
267
+ makeModel,
268
+ makeMiddleware,
269
+ makeService,
270
+ makeJob,
271
+ makeMigration,
272
+ };
@@ -0,0 +1,350 @@
1
+ 'use strict';
2
+
3
+ function getProjectFiles(projectName) {
4
+ return {
5
+
6
+ // ─── package.json ─────────────────────────────────────────────
7
+ 'package.json': JSON.stringify({
8
+ name: projectName,
9
+ version: '1.0.0',
10
+ description: `A Millas application`,
11
+ main: 'bootstrap/app.js',
12
+ scripts: {
13
+ start: 'node bootstrap/app.js',
14
+ dev: 'millas serve',
15
+ },
16
+ dependencies: {
17
+ express: '^4.18.2',
18
+ dotenv: '^16.0.3',
19
+ },
20
+ }, null, 2),
21
+
22
+ // ─── .env ─────────────────────────────────────────────────────
23
+ '.env': `APP_NAME=${projectName}
24
+ APP_ENV=development
25
+ APP_PORT=3000
26
+ APP_KEY=
27
+
28
+ DB_CONNECTION=sqlite
29
+ DB_HOST=127.0.0.1
30
+ DB_PORT=3306
31
+ DB_DATABASE=database/database.sqlite
32
+ DB_USERNAME=root
33
+ DB_PASSWORD=
34
+
35
+ MAIL_DRIVER=smtp
36
+ MAIL_HOST=smtp.mailtrap.io
37
+ MAIL_PORT=2525
38
+ MAIL_USERNAME=
39
+ MAIL_PASSWORD=
40
+
41
+ QUEUE_DRIVER=sync
42
+
43
+ CACHE_DRIVER=memory
44
+ `,
45
+
46
+ // ─── .env.example ─────────────────────────────────────────────
47
+ '.env.example': `APP_NAME=${projectName}
48
+ APP_ENV=development
49
+ APP_PORT=3000
50
+ APP_KEY=
51
+
52
+ DB_CONNECTION=sqlite
53
+ DB_HOST=127.0.0.1
54
+ DB_PORT=3306
55
+ DB_DATABASE=database/database.sqlite
56
+ DB_USERNAME=root
57
+ DB_PASSWORD=
58
+ `,
59
+
60
+ // ─── .gitignore ───────────────────────────────────────────────
61
+ '.gitignore': `node_modules/
62
+ .env
63
+ storage/logs/*.log
64
+ storage/uploads/*
65
+ !storage/uploads/.gitkeep
66
+ database/database.sqlite
67
+ `,
68
+
69
+ // ─── millas.config.js ─────────────────────────────────────────
70
+ 'millas.config.js': `'use strict';
71
+
72
+ module.exports = {
73
+ /*
74
+ |--------------------------------------------------------------------------
75
+ | Millas Framework Configuration
76
+ |--------------------------------------------------------------------------
77
+ */
78
+ providers: [
79
+ './providers/AppServiceProvider',
80
+ ],
81
+ };
82
+ `,
83
+
84
+ // ─── bootstrap/app.js ─────────────────────────────────────────
85
+ 'bootstrap/app.js': `'use strict';
86
+
87
+ require('dotenv').config();
88
+
89
+ const express = require('express');
90
+ const { Application } = require('millas/src');
91
+ const AppServiceProvider = require('../providers/AppServiceProvider');
92
+
93
+ const expressApp = express();
94
+ expressApp.use(express.json());
95
+ expressApp.use(express.urlencoded({ extended: true }));
96
+
97
+ // ── Build the Millas application kernel ─────────────────────────
98
+ const app = new Application(expressApp);
99
+
100
+ // ── Register service providers ──────────────────────────────────
101
+ app.providers([
102
+ AppServiceProvider,
103
+ // Add more providers here as you build your app
104
+ ]);
105
+
106
+ // ── Define routes ────────────────────────────────────────────────
107
+ app.routes(Route => {
108
+ require('../routes/web')(Route);
109
+ require('../routes/api')(Route);
110
+ });
111
+
112
+ // ── Boot + mount + listen ────────────────────────────────────────
113
+ (async () => {
114
+ await app.boot();
115
+
116
+ if (!process.env.MILLAS_ROUTE_LIST) {
117
+ app.mount();
118
+ app.listen();
119
+ }
120
+ })();
121
+
122
+ module.exports = { app, expressApp };
123
+ `,
124
+
125
+ // ─── routes/web.js ────────────────────────────────────────────
126
+ 'routes/web.js': `'use strict';
127
+
128
+ /**
129
+ * Web Routes
130
+ *
131
+ * Define your web-facing routes here using the Millas Route API.
132
+ *
133
+ * Route.get('/path', ControllerClass, 'method')
134
+ * Route.get('/path', (req, res) => res.json({ ... })) // closure
135
+ * Route.resource('/posts', PostController) // full CRUD
136
+ * Route.group({ prefix: '/admin', middleware: ['auth'] }, () => { ... })
137
+ */
138
+ module.exports = function (Route) {
139
+ Route.get('/', (req, res) => {
140
+ res.json({
141
+ framework: 'Millas',
142
+ version: '0.1.0',
143
+ message: 'Welcome to your Millas application!',
144
+ docs: 'https://millas.dev/docs',
145
+ });
146
+ });
147
+ };
148
+ `,
149
+
150
+ // ─── routes/api.js ────────────────────────────────────────────
151
+ 'routes/api.js': `'use strict';
152
+
153
+ /**
154
+ * API Routes
155
+ *
156
+ * All routes here are prefixed with /api.
157
+ * Add Route.middleware(['auth']) to protect routes.
158
+ */
159
+ module.exports = function (Route) {
160
+ Route.prefix('/api').group(() => {
161
+
162
+ Route.get('/health', (req, res) => {
163
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
164
+ });
165
+
166
+ });
167
+ };
168
+ `,
169
+
170
+ // ─── config/app.js ────────────────────────────────────────────
171
+ 'config/app.js': `'use strict';
172
+
173
+ module.exports = {
174
+ name: process.env.APP_NAME || 'Millas',
175
+ env: process.env.APP_ENV || 'development',
176
+ port: parseInt(process.env.APP_PORT, 10) || 3000,
177
+ key: process.env.APP_KEY || '',
178
+ debug: process.env.APP_ENV !== 'production',
179
+ timezone: 'UTC',
180
+ locale: 'en',
181
+ };
182
+ `,
183
+
184
+ // ─── config/database.js ───────────────────────────────────────
185
+ 'config/database.js': `'use strict';
186
+
187
+ module.exports = {
188
+ default: process.env.DB_CONNECTION || 'sqlite',
189
+
190
+ connections: {
191
+ sqlite: {
192
+ driver: 'sqlite',
193
+ database: process.env.DB_DATABASE || 'database/database.sqlite',
194
+ },
195
+ mysql: {
196
+ driver: 'mysql',
197
+ host: process.env.DB_HOST || '127.0.0.1',
198
+ port: parseInt(process.env.DB_PORT, 10) || 3306,
199
+ database: process.env.DB_DATABASE || 'millas',
200
+ username: process.env.DB_USERNAME || 'root',
201
+ password: process.env.DB_PASSWORD || '',
202
+ },
203
+ postgres: {
204
+ driver: 'postgres',
205
+ host: process.env.DB_HOST || '127.0.0.1',
206
+ port: parseInt(process.env.DB_PORT, 10) || 5432,
207
+ database: process.env.DB_DATABASE || 'millas',
208
+ username: process.env.DB_USERNAME || 'postgres',
209
+ password: process.env.DB_PASSWORD || '',
210
+ },
211
+ },
212
+ };
213
+ `,
214
+
215
+ // ─── config/auth.js ───────────────────────────────────────────
216
+ 'config/auth.js': `'use strict';
217
+
218
+ module.exports = {
219
+ default: 'jwt',
220
+
221
+ guards: {
222
+ jwt: {
223
+ driver: 'jwt',
224
+ secret: process.env.APP_KEY || 'change-me',
225
+ expiresIn: '7d',
226
+ },
227
+ session: {
228
+ driver: 'session',
229
+ },
230
+ },
231
+
232
+ providers: {
233
+ users: {
234
+ model: 'User',
235
+ },
236
+ },
237
+
238
+ passwordReset: {
239
+ expiresIn: '1h',
240
+ },
241
+ };
242
+ `,
243
+
244
+ // ─── config/mail.js ───────────────────────────────────────────
245
+ 'config/mail.js': `'use strict';
246
+
247
+ module.exports = {
248
+ default: process.env.MAIL_DRIVER || 'smtp',
249
+
250
+ drivers: {
251
+ smtp: {
252
+ host: process.env.MAIL_HOST || 'smtp.mailtrap.io',
253
+ port: parseInt(process.env.MAIL_PORT, 10) || 2525,
254
+ username: process.env.MAIL_USERNAME || '',
255
+ password: process.env.MAIL_PASSWORD || '',
256
+ encryption: 'tls',
257
+ },
258
+ },
259
+
260
+ from: {
261
+ address: process.env.MAIL_FROM_ADDRESS || 'hello@millas.dev',
262
+ name: process.env.MAIL_FROM_NAME || 'Millas',
263
+ },
264
+ };
265
+ `,
266
+
267
+ // ─── providers/AppServiceProvider.js ──────────────────────────
268
+ 'providers/AppServiceProvider.js': `'use strict';
269
+
270
+ const { ServiceProvider } = require('millas/src');
271
+
272
+ /**
273
+ * AppServiceProvider
274
+ *
275
+ * Register and bootstrap your application's services here.
276
+ *
277
+ * register() — bind things into the container
278
+ * boot() — called after all providers have registered;
279
+ * safe to resolve other bindings here
280
+ */
281
+ class AppServiceProvider extends ServiceProvider {
282
+ register(container) {
283
+ // container.bind('UserService', UserService);
284
+ // container.singleton('Mailer', MailService);
285
+ // container.instance('Config', require('../config/app'));
286
+ }
287
+
288
+ async boot(container, app) {
289
+ // const logger = container.make('Logger');
290
+ // logger.info('App booted');
291
+ }
292
+ }
293
+
294
+ module.exports = AppServiceProvider;
295
+ `,
296
+
297
+ // ─── app/controllers/.gitkeep ────────────────────────────────
298
+ 'app/controllers/.gitkeep': '',
299
+ 'app/models/.gitkeep': '',
300
+ 'app/services/.gitkeep': '',
301
+ 'app/middleware/.gitkeep': '',
302
+ 'app/jobs/.gitkeep': '',
303
+
304
+ // ─── README.md ────────────────────────────────────────────────
305
+ 'README.md': `# ${projectName}
306
+
307
+ A [Millas](https://millas.dev) application.
308
+
309
+ ## Getting Started
310
+
311
+ \`\`\`bash
312
+ # Start the development server
313
+ millas serve
314
+
315
+ # Generate a controller
316
+ millas make:controller UserController
317
+
318
+ # Generate a model
319
+ millas make:model User
320
+
321
+ # Run migrations
322
+ millas migrate
323
+ \`\`\`
324
+
325
+ ## Project Structure
326
+
327
+ \`\`\`
328
+ app/
329
+ controllers/ # HTTP controllers
330
+ models/ # ORM models
331
+ services/ # Business logic
332
+ middleware/ # HTTP middleware
333
+ jobs/ # Background jobs
334
+ bootstrap/
335
+ app.js # Application entry point
336
+ config/ # Configuration files
337
+ database/
338
+ migrations/ # Database migrations
339
+ seeders/ # Database seeders
340
+ routes/
341
+ web.js # Web routes
342
+ api.js # API routes
343
+ storage/ # Logs, uploads
344
+ providers/ # Service providers
345
+ \`\`\`
346
+ `,
347
+ };
348
+ }
349
+
350
+ module.exports = { getProjectFiles };