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.
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/bin/millas.js +6 -0
- package/package.json +56 -0
- package/src/admin/Admin.js +617 -0
- package/src/admin/index.js +13 -0
- package/src/admin/resources/AdminResource.js +317 -0
- package/src/auth/Auth.js +254 -0
- package/src/auth/AuthController.js +188 -0
- package/src/auth/AuthMiddleware.js +67 -0
- package/src/auth/Hasher.js +51 -0
- package/src/auth/JwtDriver.js +74 -0
- package/src/auth/RoleMiddleware.js +44 -0
- package/src/cache/Cache.js +231 -0
- package/src/cache/drivers/FileDriver.js +152 -0
- package/src/cache/drivers/MemoryDriver.js +158 -0
- package/src/cache/drivers/NullDriver.js +27 -0
- package/src/cache/index.js +8 -0
- package/src/cli.js +27 -0
- package/src/commands/make.js +61 -0
- package/src/commands/migrate.js +174 -0
- package/src/commands/new.js +50 -0
- package/src/commands/queue.js +92 -0
- package/src/commands/route.js +93 -0
- package/src/commands/serve.js +50 -0
- package/src/container/Application.js +177 -0
- package/src/container/Container.js +281 -0
- package/src/container/index.js +13 -0
- package/src/controller/Controller.js +367 -0
- package/src/errors/HttpError.js +29 -0
- package/src/events/Event.js +39 -0
- package/src/events/EventEmitter.js +151 -0
- package/src/events/Listener.js +46 -0
- package/src/events/index.js +15 -0
- package/src/index.js +93 -0
- package/src/mail/Mail.js +210 -0
- package/src/mail/MailMessage.js +196 -0
- package/src/mail/TemplateEngine.js +150 -0
- package/src/mail/drivers/LogDriver.js +36 -0
- package/src/mail/drivers/MailgunDriver.js +84 -0
- package/src/mail/drivers/SendGridDriver.js +97 -0
- package/src/mail/drivers/SmtpDriver.js +67 -0
- package/src/mail/index.js +19 -0
- package/src/middleware/AuthMiddleware.js +46 -0
- package/src/middleware/CorsMiddleware.js +59 -0
- package/src/middleware/LogMiddleware.js +61 -0
- package/src/middleware/Middleware.js +36 -0
- package/src/middleware/MiddlewarePipeline.js +94 -0
- package/src/middleware/ThrottleMiddleware.js +61 -0
- package/src/orm/drivers/DatabaseManager.js +135 -0
- package/src/orm/fields/index.js +132 -0
- package/src/orm/index.js +19 -0
- package/src/orm/migration/MigrationRunner.js +216 -0
- package/src/orm/migration/ModelInspector.js +338 -0
- package/src/orm/migration/SchemaBuilder.js +173 -0
- package/src/orm/model/Model.js +371 -0
- package/src/orm/query/QueryBuilder.js +197 -0
- package/src/providers/AdminServiceProvider.js +40 -0
- package/src/providers/AuthServiceProvider.js +53 -0
- package/src/providers/CacheStorageServiceProvider.js +71 -0
- package/src/providers/DatabaseServiceProvider.js +45 -0
- package/src/providers/EventServiceProvider.js +34 -0
- package/src/providers/MailServiceProvider.js +51 -0
- package/src/providers/ProviderRegistry.js +82 -0
- package/src/providers/QueueServiceProvider.js +52 -0
- package/src/providers/ServiceProvider.js +45 -0
- package/src/queue/Job.js +135 -0
- package/src/queue/Queue.js +147 -0
- package/src/queue/drivers/DatabaseDriver.js +194 -0
- package/src/queue/drivers/SyncDriver.js +72 -0
- package/src/queue/index.js +16 -0
- package/src/queue/workers/QueueWorker.js +140 -0
- package/src/router/MiddlewareRegistry.js +82 -0
- package/src/router/Route.js +255 -0
- package/src/router/RouteGroup.js +19 -0
- package/src/router/RouteRegistry.js +55 -0
- package/src/router/Router.js +138 -0
- package/src/router/index.js +15 -0
- package/src/scaffold/generator.js +34 -0
- package/src/scaffold/maker.js +272 -0
- package/src/scaffold/templates.js +350 -0
- package/src/storage/Storage.js +170 -0
- package/src/storage/drivers/LocalDriver.js +215 -0
- package/src/storage/index.js +6 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const RouteGroup = require('./RouteGroup');
|
|
4
|
+
const RouteRegistry = require('./RouteRegistry');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Route
|
|
8
|
+
*
|
|
9
|
+
* The primary developer-facing API for defining routes.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* Route.get('/users', UserController, 'index')
|
|
13
|
+
* Route.post('/users', UserController, 'store')
|
|
14
|
+
* Route.resource('/users', UserController)
|
|
15
|
+
* Route.group({ prefix: '/api', middleware: ['auth'] }, () => { ... })
|
|
16
|
+
* Route.prefix('/v1').group(() => { ... })
|
|
17
|
+
*/
|
|
18
|
+
class Route {
|
|
19
|
+
constructor() {
|
|
20
|
+
this._registry = new RouteRegistry();
|
|
21
|
+
this._groupStack = []; // stack of active group contexts
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── HTTP Verbs ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
get(path, handler, method) {
|
|
27
|
+
return this._add('GET', path, handler, method);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
post(path, handler, method) {
|
|
31
|
+
return this._add('POST', path, handler, method);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
put(path, handler, method) {
|
|
35
|
+
return this._add('PUT', path, handler, method);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
patch(path, handler, method) {
|
|
39
|
+
return this._add('PATCH', path, handler, method);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
delete(path, handler, method) {
|
|
43
|
+
return this._add('DELETE', path, handler, method);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
options(path, handler, method) {
|
|
47
|
+
return this._add('OPTIONS', path, handler, method);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
any(path, handler, method) {
|
|
51
|
+
const verbs = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
52
|
+
verbs.forEach(v => this._add(v, path, handler, method));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Resource Routes ─────────────────────────────────────────────────────────
|
|
56
|
+
// Generates 5 conventional RESTful routes for a controller.
|
|
57
|
+
|
|
58
|
+
resource(path, ControllerClass, options = {}) {
|
|
59
|
+
const only = options.only;
|
|
60
|
+
const except = options.except || [];
|
|
61
|
+
|
|
62
|
+
const map = [
|
|
63
|
+
{ verb: 'GET', suffix: '', action: 'index', name: `${path}.index` },
|
|
64
|
+
{ verb: 'GET', suffix: '/:id', action: 'show', name: `${path}.show` },
|
|
65
|
+
{ verb: 'POST', suffix: '', action: 'store', name: `${path}.store` },
|
|
66
|
+
{ verb: 'PUT', suffix: '/:id', action: 'update', name: `${path}.update` },
|
|
67
|
+
{ verb: 'DELETE', suffix: '/:id', action: 'destroy', name: `${path}.destroy` },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const route of map) {
|
|
71
|
+
if (only && !only.includes(route.action)) continue;
|
|
72
|
+
if (except.includes(route.action)) continue;
|
|
73
|
+
this._add(route.verb, path + route.suffix, ControllerClass, route.action, route.name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Route Groups ────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Route.group({ prefix, middleware, name }, callback)
|
|
83
|
+
* Route.group(callback) — shorthand, no attributes
|
|
84
|
+
*/
|
|
85
|
+
group(attributes, callback) {
|
|
86
|
+
if (typeof attributes === 'function') {
|
|
87
|
+
callback = attributes;
|
|
88
|
+
attributes = {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const group = new RouteGroup(attributes, this._groupStack);
|
|
92
|
+
this._groupStack.push(group);
|
|
93
|
+
callback();
|
|
94
|
+
this._groupStack.pop();
|
|
95
|
+
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Fluent prefix() — returns a builder that defers to group()
|
|
101
|
+
* Route.prefix('/api/v1').middleware(['auth']).group(() => { ... })
|
|
102
|
+
*/
|
|
103
|
+
prefix(prefix) {
|
|
104
|
+
return new RouteGroupBuilder(this, { prefix });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Attach middleware to the next group or route
|
|
109
|
+
*/
|
|
110
|
+
middleware(middleware) {
|
|
111
|
+
return new RouteGroupBuilder(this, { middleware });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Named route prefix
|
|
116
|
+
*/
|
|
117
|
+
name(name) {
|
|
118
|
+
return new RouteGroupBuilder(this, { name });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Auth convenience ────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Register all standard auth routes under a given prefix.
|
|
125
|
+
*
|
|
126
|
+
* Route.auth() // registers under /auth
|
|
127
|
+
* Route.auth('/api/auth') // custom prefix
|
|
128
|
+
*
|
|
129
|
+
* Registers:
|
|
130
|
+
* POST /auth/register
|
|
131
|
+
* POST /auth/login
|
|
132
|
+
* POST /auth/logout
|
|
133
|
+
* GET /auth/me
|
|
134
|
+
* POST /auth/refresh
|
|
135
|
+
* POST /auth/forgot-password
|
|
136
|
+
* POST /auth/reset-password
|
|
137
|
+
*/
|
|
138
|
+
auth(prefix = '/auth') {
|
|
139
|
+
const AuthController = require('../auth/AuthController');
|
|
140
|
+
this.group({ prefix }, () => {
|
|
141
|
+
this.post('/register', AuthController, 'register');
|
|
142
|
+
this.post('/login', AuthController, 'login');
|
|
143
|
+
this.post('/logout', AuthController, 'logout');
|
|
144
|
+
this.get('/me', AuthController, 'me');
|
|
145
|
+
this.post('/refresh', AuthController, 'refresh');
|
|
146
|
+
this.post('/forgot-password', AuthController, 'forgotPassword');
|
|
147
|
+
this.post('/reset-password', AuthController, 'resetPassword');
|
|
148
|
+
});
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Internal ────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
_add(verb, path, handler, method, routeName) {
|
|
155
|
+
// Merge active group context
|
|
156
|
+
const context = this._mergeGroupStack();
|
|
157
|
+
|
|
158
|
+
// Build full path
|
|
159
|
+
const fullPath = this._joinPaths(context.prefix || '', path);
|
|
160
|
+
|
|
161
|
+
// Resolve middleware
|
|
162
|
+
const middleware = [
|
|
163
|
+
...(context.middleware || []),
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
// Build route name
|
|
167
|
+
const name = routeName
|
|
168
|
+
? (context.name ? context.name + '.' + routeName : routeName)
|
|
169
|
+
: null;
|
|
170
|
+
|
|
171
|
+
const entry = {
|
|
172
|
+
verb,
|
|
173
|
+
path: fullPath,
|
|
174
|
+
handler,
|
|
175
|
+
method, // string method name OR raw function
|
|
176
|
+
middleware,
|
|
177
|
+
name,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
this._registry.register(entry);
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_mergeGroupStack() {
|
|
185
|
+
return this._groupStack.reduce((merged, group) => {
|
|
186
|
+
// Prefix: concatenate
|
|
187
|
+
merged.prefix = this._joinPaths(merged.prefix || '', group.prefix || '');
|
|
188
|
+
// Middleware: accumulate
|
|
189
|
+
merged.middleware = [
|
|
190
|
+
...(merged.middleware || []),
|
|
191
|
+
...(group.middleware || []),
|
|
192
|
+
];
|
|
193
|
+
// Name: concatenate
|
|
194
|
+
merged.name = [merged.name, group.name].filter(Boolean).join('');
|
|
195
|
+
return merged;
|
|
196
|
+
}, {});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
_joinPaths(...parts) {
|
|
200
|
+
return '/' + parts
|
|
201
|
+
.map(p => p.replace(/^\/|\/$/g, ''))
|
|
202
|
+
.filter(Boolean)
|
|
203
|
+
.join('/');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── Public Accessors ────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
getRegistry() {
|
|
209
|
+
return this._registry;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
list() {
|
|
213
|
+
return this._registry.all();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── RouteGroupBuilder (fluent chain) ────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
class RouteGroupBuilder {
|
|
220
|
+
constructor(router, attrs) {
|
|
221
|
+
this._router = router;
|
|
222
|
+
this._attrs = attrs;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
prefix(prefix) {
|
|
226
|
+
this._attrs.prefix = this._joinPaths(this._attrs.prefix || '', prefix);
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
middleware(middleware) {
|
|
231
|
+
this._attrs.middleware = [
|
|
232
|
+
...(this._attrs.middleware || []),
|
|
233
|
+
...(Array.isArray(middleware) ? middleware : [middleware]),
|
|
234
|
+
];
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
name(name) {
|
|
239
|
+
this._attrs.name = (this._attrs.name || '') + name;
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
group(callback) {
|
|
244
|
+
return this._router.group(this._attrs, callback);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_joinPaths(...parts) {
|
|
248
|
+
return '/' + parts
|
|
249
|
+
.map(p => p.replace(/^\/|\/$/g, ''))
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.join('/');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = Route;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RouteGroup
|
|
5
|
+
*
|
|
6
|
+
* Represents a single group context pushed onto the group stack.
|
|
7
|
+
* Holds prefix, middleware array, and name prefix for the group.
|
|
8
|
+
*/
|
|
9
|
+
class RouteGroup {
|
|
10
|
+
constructor(attributes = {}) {
|
|
11
|
+
this.prefix = attributes.prefix || '';
|
|
12
|
+
this.middleware = Array.isArray(attributes.middleware)
|
|
13
|
+
? attributes.middleware
|
|
14
|
+
: (attributes.middleware ? [attributes.middleware] : []);
|
|
15
|
+
this.name = attributes.name || '';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = RouteGroup;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RouteRegistry
|
|
5
|
+
*
|
|
6
|
+
* Central store for all registered routes.
|
|
7
|
+
* Used by the Router to bind to Express, and by `millas route:list`.
|
|
8
|
+
*/
|
|
9
|
+
class RouteRegistry {
|
|
10
|
+
constructor() {
|
|
11
|
+
this._routes = [];
|
|
12
|
+
this._namedRoutes = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
register(entry) {
|
|
16
|
+
this._routes.push(entry);
|
|
17
|
+
|
|
18
|
+
if (entry.name) {
|
|
19
|
+
this._namedRoutes[entry.name] = entry;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
all() {
|
|
24
|
+
return [...this._routes];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
findByName(name) {
|
|
28
|
+
return this._namedRoutes[name] || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
findByPath(verb, path) {
|
|
32
|
+
return this._routes.find(
|
|
33
|
+
r => r.verb === verb.toUpperCase() && r.path === path
|
|
34
|
+
) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Return a formatted table for `millas route:list`
|
|
39
|
+
*/
|
|
40
|
+
toTable() {
|
|
41
|
+
return this._routes.map(r => ({
|
|
42
|
+
Method: r.verb.padEnd(7),
|
|
43
|
+
Path: r.path,
|
|
44
|
+
Handler: r.handler
|
|
45
|
+
? (typeof r.handler === 'function'
|
|
46
|
+
? r.handler.name || '<closure>'
|
|
47
|
+
: (r.handler.name || r.handler.toString()) + (r.method ? `@${r.method}` : ''))
|
|
48
|
+
: '<none>',
|
|
49
|
+
Middleware: (r.middleware || []).join(', ') || '—',
|
|
50
|
+
Name: r.name || '—',
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = RouteRegistry;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MiddlewareRegistry = require('./MiddlewareRegistry');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Router
|
|
7
|
+
*
|
|
8
|
+
* Takes a populated RouteRegistry and binds every route
|
|
9
|
+
* onto a live Express app instance.
|
|
10
|
+
*
|
|
11
|
+
* Also wraps async controller methods so unhandled promise
|
|
12
|
+
* rejections are forwarded to Express error handlers.
|
|
13
|
+
*/
|
|
14
|
+
class Router {
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} expressApp — the Express application
|
|
17
|
+
* @param {RouteRegistry} registry
|
|
18
|
+
* @param {MiddlewareRegistry} middlewareRegistry
|
|
19
|
+
*/
|
|
20
|
+
constructor(expressApp, registry, middlewareRegistry) {
|
|
21
|
+
this._app = expressApp;
|
|
22
|
+
this._registry = registry;
|
|
23
|
+
this._mw = middlewareRegistry || new MiddlewareRegistry();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Bind all registered routes onto the Express app.
|
|
28
|
+
*/
|
|
29
|
+
mount() {
|
|
30
|
+
const routes = this._registry.all();
|
|
31
|
+
|
|
32
|
+
for (const route of routes) {
|
|
33
|
+
this._bindRoute(route);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Mount 404 handler after all routes
|
|
37
|
+
this._app.use((req, res) => {
|
|
38
|
+
res.status(404).json({
|
|
39
|
+
error: 'Not Found',
|
|
40
|
+
message: `Cannot ${req.method} ${req.path}`,
|
|
41
|
+
status: 404,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Mount global error handler
|
|
46
|
+
this._app.use((err, req, res, _next) => {
|
|
47
|
+
const status = err.status || err.statusCode || 500;
|
|
48
|
+
const message = err.message || 'Internal Server Error';
|
|
49
|
+
|
|
50
|
+
if (status >= 500 && process.env.NODE_ENV !== 'production') {
|
|
51
|
+
console.error(err.stack);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
res.status(status).json({
|
|
55
|
+
error: status >= 500 ? 'Internal Server Error' : message,
|
|
56
|
+
message,
|
|
57
|
+
status,
|
|
58
|
+
// Validation errors (HttpError with errors field)
|
|
59
|
+
...(err.errors && { errors: err.errors }),
|
|
60
|
+
// Stack trace in development for 5xx only
|
|
61
|
+
...(status >= 500 && process.env.NODE_ENV !== 'production' && { stack: err.stack }),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Private ──────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
_bindRoute(route) {
|
|
69
|
+
const verb = route.verb.toLowerCase();
|
|
70
|
+
const path = route.path;
|
|
71
|
+
|
|
72
|
+
// Resolve middleware chain
|
|
73
|
+
const mwHandlers = this._resolveMiddleware(route.middleware || []);
|
|
74
|
+
|
|
75
|
+
// Resolve the terminal handler
|
|
76
|
+
const terminal = this._resolveHandler(route.handler, route.method);
|
|
77
|
+
|
|
78
|
+
// Register on Express: app.get(path, [...mw], handler)
|
|
79
|
+
this._app[verb](path, ...mwHandlers, terminal);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_resolveMiddleware(list) {
|
|
83
|
+
return list.map(alias => {
|
|
84
|
+
try {
|
|
85
|
+
return this._mw.resolve(alias);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.warn(`[Millas] Warning: ${err.message} — skipping.`);
|
|
88
|
+
return (_req, _res, next) => next();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_resolveHandler(handler, method) {
|
|
94
|
+
// Case 1: raw async/sync function
|
|
95
|
+
if (typeof handler === 'function' && !method) {
|
|
96
|
+
return this._wrapAsync(handler);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Case 2: controller class + method name string
|
|
100
|
+
if (typeof handler === 'function' && typeof method === 'string') {
|
|
101
|
+
const instance = new handler();
|
|
102
|
+
if (typeof instance[method] !== 'function') {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Method "${method}" not found on controller "${handler.name}".`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return this._wrapAsync(instance[method].bind(instance));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Case 3: already-instantiated object + method name
|
|
111
|
+
if (typeof handler === 'object' && handler !== null && typeof method === 'string') {
|
|
112
|
+
if (typeof handler[method] !== 'function') {
|
|
113
|
+
throw new Error(`Method "${method}" not found on handler object.`);
|
|
114
|
+
}
|
|
115
|
+
return this._wrapAsync(handler[method].bind(handler));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Case 4: plain object/function with no method (fallback)
|
|
119
|
+
if (typeof handler === 'function') {
|
|
120
|
+
return this._wrapAsync(handler);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new Error(`Invalid route handler: ${JSON.stringify(handler)}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Wrap an async function so rejections are forwarded to next(err).
|
|
128
|
+
* Sync functions pass through unchanged.
|
|
129
|
+
*/
|
|
130
|
+
_wrapAsync(fn) {
|
|
131
|
+
if (fn.constructor.name === 'AsyncFunction') {
|
|
132
|
+
return (req, res, next) => fn(req, res, next).catch(next);
|
|
133
|
+
}
|
|
134
|
+
return fn;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = Router;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Route = require('./Route');
|
|
4
|
+
const Router = require('./Router');
|
|
5
|
+
const RouteRegistry = require('./RouteRegistry');
|
|
6
|
+
const RouteGroup = require('./RouteGroup');
|
|
7
|
+
const MiddlewareRegistry = require('./MiddlewareRegistry');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
Route,
|
|
11
|
+
Router,
|
|
12
|
+
RouteRegistry,
|
|
13
|
+
RouteGroup,
|
|
14
|
+
MiddlewareRegistry,
|
|
15
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { getProjectFiles } = require('./templates');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generates a full Millas project at the given targetDir.
|
|
9
|
+
*/
|
|
10
|
+
async function generateProject(projectName, targetDir) {
|
|
11
|
+
const files = getProjectFiles(projectName);
|
|
12
|
+
|
|
13
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
14
|
+
const fullPath = path.join(targetDir, filePath);
|
|
15
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
16
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Create empty directories that need to exist but have no initial files
|
|
20
|
+
const emptyDirs = [
|
|
21
|
+
'storage/logs',
|
|
22
|
+
'storage/uploads',
|
|
23
|
+
'database/migrations',
|
|
24
|
+
'database/seeders',
|
|
25
|
+
'tests',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
for (const dir of emptyDirs) {
|
|
29
|
+
await fs.ensureDir(path.join(targetDir, dir));
|
|
30
|
+
await fs.writeFile(path.join(targetDir, dir, '.gitkeep'), '', 'utf8');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { generateProject };
|