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,170 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ /**
7
+ * Storage
8
+ *
9
+ * The primary file storage facade.
10
+ *
11
+ * Usage:
12
+ * const { Storage } = require('millas/src');
13
+ *
14
+ * // Write a file
15
+ * await Storage.put('avatars/alice.jpg', buffer);
16
+ *
17
+ * // Read a file
18
+ * const buffer = await Storage.get('avatars/alice.jpg');
19
+ *
20
+ * // Check existence
21
+ * const ok = await Storage.exists('avatars/alice.jpg');
22
+ *
23
+ * // Delete
24
+ * await Storage.delete('avatars/alice.jpg');
25
+ *
26
+ * // URL
27
+ * const url = Storage.url('avatars/alice.jpg'); // /storage/avatars/alice.jpg
28
+ *
29
+ * // Use a different disk
30
+ * await Storage.disk('public').put('images/logo.png', buffer);
31
+ */
32
+ class Storage {
33
+ constructor() {
34
+ this._disks = new Map(); // name → driver instance
35
+ this._config = null;
36
+ this._default = 'local';
37
+ }
38
+
39
+ // ─── Configuration ─────────────────────────────────────────────────────────
40
+
41
+ configure(config) {
42
+ this._config = config;
43
+ this._default = config.default || 'local';
44
+ this._disks.clear();
45
+ }
46
+
47
+ // ─── Disk selector ─────────────────────────────────────────────────────────
48
+
49
+ /**
50
+ * Select a named disk.
51
+ * Storage.disk('public').put(...)
52
+ */
53
+ disk(name) {
54
+ return new DiskProxy(this._getDisk(name));
55
+ }
56
+
57
+ // ─── Default disk proxy methods ────────────────────────────────────────────
58
+
59
+ async put(filePath, content, options = {}) { return this._getDisk().put(filePath, content, options); }
60
+ async get(filePath) { return this._getDisk().get(filePath); }
61
+ async getString(filePath) { return this._getDisk().getString(filePath); }
62
+ async exists(filePath) { return this._getDisk().exists(filePath); }
63
+ async delete(filePath) { return this._getDisk().delete(filePath); }
64
+ async deleteDirectory(dirPath) { return this._getDisk().deleteDirectory(dirPath); }
65
+ async copy(from, to) { return this._getDisk().copy(from, to); }
66
+ async move(from, to) { return this._getDisk().move(from, to); }
67
+ async files(dirPath) { return this._getDisk().files(dirPath); }
68
+ async allFiles(dirPath) { return this._getDisk().allFiles(dirPath); }
69
+ async directories(dirPath) { return this._getDisk().directories(dirPath); }
70
+ async makeDirectory(dirPath) { return this._getDisk().makeDirectory(dirPath); }
71
+ async metadata(filePath) { return this._getDisk().metadata(filePath); }
72
+ url(filePath) { return this._getDisk().url(filePath); }
73
+ path(filePath) { return this._getDisk().path(filePath); }
74
+ stream(filePath, res, options) { return this._getDisk().stream(filePath, res, options); }
75
+
76
+ /**
77
+ * Store an uploaded file from a Multer/Busboy req.file object.
78
+ *
79
+ * const path = await Storage.putFile('avatars', req.file);
80
+ */
81
+ async putFile(directory, file, options = {}) {
82
+ const ext = path.extname(file.originalname || file.filename || '');
83
+ const filename = options.name
84
+ ? `${options.name}${ext}`
85
+ : `${this._uniqueId()}${ext}`;
86
+ const filePath = `${directory}/${filename}`.replace(/\/+/g, '/');
87
+
88
+ const content = file.buffer || file.path
89
+ ? (file.buffer || await require('fs-extra').readFile(file.path))
90
+ : Buffer.from('');
91
+
92
+ await this._getDisk().put(filePath, content);
93
+ return filePath;
94
+ }
95
+
96
+ /**
97
+ * Store a base64-encoded data URI.
98
+ *
99
+ * const path = await Storage.putDataUri('avatars', 'data:image/png;base64,...');
100
+ */
101
+ async putDataUri(directory, dataUri, filename) {
102
+ const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
103
+ if (!match) throw new Error('Invalid data URI');
104
+
105
+ const mimeType = match[1];
106
+ const buffer = Buffer.from(match[2], 'base64');
107
+ const ext = mimeType.split('/')[1] || 'bin';
108
+ const name = filename || `${this._uniqueId()}.${ext}`;
109
+ const filePath = `${directory}/${name}`.replace(/\/+/g, '/');
110
+
111
+ await this._getDisk().put(filePath, buffer);
112
+ return filePath;
113
+ }
114
+
115
+ // ─── Internal ──────────────────────────────────────────────────────────────
116
+
117
+ _getDisk(name) {
118
+ const diskName = name || this._default;
119
+
120
+ if (this._disks.has(diskName)) return this._disks.get(diskName);
121
+
122
+ const driver = this._buildDriver(diskName);
123
+ this._disks.set(diskName, driver);
124
+ return driver;
125
+ }
126
+
127
+ _buildDriver(name) {
128
+ const diskConf = this._config?.disks?.[name] || {};
129
+ const driverName = diskConf.driver || name || 'local';
130
+
131
+ switch (driverName) {
132
+ case 'local':
133
+ default: {
134
+ const LocalDriver = require('./drivers/LocalDriver');
135
+ return new LocalDriver(diskConf);
136
+ }
137
+ }
138
+ }
139
+
140
+ _uniqueId() {
141
+ return `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
142
+ }
143
+ }
144
+
145
+ // ── DiskProxy ─────────────────────────────────────────────────────────────────
146
+
147
+ class DiskProxy {
148
+ constructor(driver) { this._d = driver; }
149
+ async put(f, c, o) { return this._d.put(f, c, o); }
150
+ async get(f) { return this._d.get(f); }
151
+ async getString(f) { return this._d.getString(f); }
152
+ async exists(f) { return this._d.exists(f); }
153
+ async delete(f) { return this._d.delete(f); }
154
+ async deleteDirectory(d){ return this._d.deleteDirectory(d); }
155
+ async copy(f, t) { return this._d.copy(f, t); }
156
+ async move(f, t) { return this._d.move(f, t); }
157
+ async files(d) { return this._d.files(d); }
158
+ async allFiles(d) { return this._d.allFiles(d); }
159
+ async directories(d) { return this._d.directories(d); }
160
+ async makeDirectory(d){ return this._d.makeDirectory(d); }
161
+ async metadata(f) { return this._d.metadata(f); }
162
+ url(f) { return this._d.url(f); }
163
+ path(f) { return this._d.path(f); }
164
+ stream(f, r, o) { return this._d.stream(f, r, o); }
165
+ }
166
+
167
+ // Singleton
168
+ const storage = new Storage();
169
+ module.exports = storage;
170
+ module.exports.Storage = Storage;
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * LocalDriver
8
+ *
9
+ * Stores files on the local filesystem.
10
+ * Default driver — zero config required.
11
+ *
12
+ * STORAGE_DRIVER=local
13
+ */
14
+ class LocalDriver {
15
+ constructor(config = {}) {
16
+ this._root = config.root || path.join(process.cwd(), 'storage/uploads');
17
+ this._baseUrl = config.baseUrl || '/storage';
18
+ fs.ensureDirSync(this._root);
19
+ }
20
+
21
+ /**
22
+ * Write a file. Returns the stored path.
23
+ * @param {string} filePath — relative path inside storage
24
+ * @param {Buffer|string} content
25
+ * @param {object} options
26
+ */
27
+ async put(filePath, content, options = {}) {
28
+ const dest = this._abs(filePath);
29
+ await fs.ensureDir(path.dirname(dest));
30
+ await fs.writeFile(dest, content, options.encoding || null);
31
+ return filePath;
32
+ }
33
+
34
+ /**
35
+ * Read a file. Returns a Buffer.
36
+ */
37
+ async get(filePath) {
38
+ const abs = this._abs(filePath);
39
+ if (!(await fs.pathExists(abs))) {
40
+ throw new Error(`File not found: ${filePath}`);
41
+ }
42
+ return fs.readFile(abs);
43
+ }
44
+
45
+ /**
46
+ * Read a file as a UTF-8 string.
47
+ */
48
+ async getString(filePath) {
49
+ const buf = await this.get(filePath);
50
+ return buf.toString('utf8');
51
+ }
52
+
53
+ /**
54
+ * Check if a file exists.
55
+ */
56
+ async exists(filePath) {
57
+ return fs.pathExists(this._abs(filePath));
58
+ }
59
+
60
+ /**
61
+ * Delete a file.
62
+ */
63
+ async delete(filePath) {
64
+ const abs = this._abs(filePath);
65
+ if (await fs.pathExists(abs)) {
66
+ await fs.remove(abs);
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+
72
+ /**
73
+ * Delete a directory and all its contents.
74
+ */
75
+ async deleteDirectory(dirPath) {
76
+ await fs.remove(this._abs(dirPath));
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * Copy a file within storage.
82
+ */
83
+ async copy(from, to) {
84
+ await fs.ensureDir(path.dirname(this._abs(to)));
85
+ await fs.copy(this._abs(from), this._abs(to));
86
+ return to;
87
+ }
88
+
89
+ /**
90
+ * Move a file within storage.
91
+ */
92
+ async move(from, to) {
93
+ await fs.ensureDir(path.dirname(this._abs(to)));
94
+ await fs.move(this._abs(from), this._abs(to), { overwrite: true });
95
+ return to;
96
+ }
97
+
98
+ /**
99
+ * List all files in a directory.
100
+ */
101
+ async files(dirPath = '') {
102
+ const abs = this._abs(dirPath);
103
+ if (!(await fs.pathExists(abs))) return [];
104
+ const entries = await fs.readdir(abs, { withFileTypes: true });
105
+ return entries
106
+ .filter(e => e.isFile())
107
+ .map(e => path.join(dirPath, e.name).replace(/\\/g, '/'));
108
+ }
109
+
110
+ /**
111
+ * List all files recursively.
112
+ */
113
+ async allFiles(dirPath = '') {
114
+ const abs = this._abs(dirPath);
115
+ if (!(await fs.pathExists(abs))) return [];
116
+ return this._walk(abs, dirPath);
117
+ }
118
+
119
+ /**
120
+ * List all directories.
121
+ */
122
+ async directories(dirPath = '') {
123
+ const abs = this._abs(dirPath);
124
+ if (!(await fs.pathExists(abs))) return [];
125
+ const entries = await fs.readdir(abs, { withFileTypes: true });
126
+ return entries
127
+ .filter(e => e.isDirectory())
128
+ .map(e => path.join(dirPath, e.name).replace(/\\/g, '/'));
129
+ }
130
+
131
+ /**
132
+ * Create a directory.
133
+ */
134
+ async makeDirectory(dirPath) {
135
+ await fs.ensureDir(this._abs(dirPath));
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Get file metadata.
141
+ */
142
+ async metadata(filePath) {
143
+ const abs = this._abs(filePath);
144
+ const stat = await fs.stat(abs);
145
+ return {
146
+ path: filePath,
147
+ size: stat.size,
148
+ mimeType: this._mime(filePath),
149
+ lastModified: stat.mtime,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Get the public URL for a file.
155
+ */
156
+ url(filePath) {
157
+ return `${this._baseUrl}/${filePath}`.replace(/\/+/g, '/');
158
+ }
159
+
160
+ /**
161
+ * Get the absolute filesystem path.
162
+ */
163
+ path(filePath) {
164
+ return this._abs(filePath);
165
+ }
166
+
167
+ /**
168
+ * Stream a file to an Express response.
169
+ */
170
+ stream(filePath, res, options = {}) {
171
+ const abs = this._abs(filePath);
172
+ if (options.download) {
173
+ res.download(abs, path.basename(filePath));
174
+ } else {
175
+ res.sendFile(abs);
176
+ }
177
+ }
178
+
179
+ // ─── Internal ─────────────────────────────────────────────────────────────
180
+
181
+ _abs(filePath) {
182
+ return path.join(this._root, filePath || '');
183
+ }
184
+
185
+ async _walk(absDir, relDir) {
186
+ const entries = await fs.readdir(absDir, { withFileTypes: true });
187
+ const files = [];
188
+ for (const e of entries) {
189
+ const relPath = path.join(relDir, e.name).replace(/\\/g, '/');
190
+ if (e.isDirectory()) {
191
+ files.push(...await this._walk(path.join(absDir, e.name), relPath));
192
+ } else {
193
+ files.push(relPath);
194
+ }
195
+ }
196
+ return files;
197
+ }
198
+
199
+ _mime(filePath) {
200
+ const ext = path.extname(filePath).toLowerCase();
201
+ const types = {
202
+ '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
203
+ '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
204
+ '.pdf': 'application/pdf', '.txt': 'text/plain',
205
+ '.html': 'text/html', '.css': 'text/css',
206
+ '.js': 'application/javascript',
207
+ '.json': 'application/json',
208
+ '.zip': 'application/zip',
209
+ '.mp4': 'video/mp4', '.mp3': 'audio/mpeg',
210
+ };
211
+ return types[ext] || 'application/octet-stream';
212
+ }
213
+ }
214
+
215
+ module.exports = LocalDriver;
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ const Storage = require('./Storage');
4
+ const LocalDriver = require('./drivers/LocalDriver');
5
+
6
+ module.exports = { Storage, LocalDriver };