free-framework 4.4.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/bin/free.js +118 -0
  4. package/cli/commands/build.js +124 -0
  5. package/cli/commands/deploy.js +143 -0
  6. package/cli/commands/devtools.js +210 -0
  7. package/cli/commands/doctor.js +72 -0
  8. package/cli/commands/install.js +28 -0
  9. package/cli/commands/make.js +74 -0
  10. package/cli/commands/migrate.js +67 -0
  11. package/cli/commands/new.js +54 -0
  12. package/cli/commands/serve.js +73 -0
  13. package/cli/commands/test.js +57 -0
  14. package/compiler/analyzer.js +102 -0
  15. package/compiler/generator.js +386 -0
  16. package/compiler/lexer.js +166 -0
  17. package/compiler/parser.js +410 -0
  18. package/database/model.js +6 -0
  19. package/database/orm.js +379 -0
  20. package/database/query-builder.js +179 -0
  21. package/index.js +51 -0
  22. package/package.json +80 -0
  23. package/plugins/auth.js +212 -0
  24. package/plugins/cache.js +85 -0
  25. package/plugins/chat.js +32 -0
  26. package/plugins/mail.js +53 -0
  27. package/plugins/metrics.js +126 -0
  28. package/plugins/payments.js +59 -0
  29. package/plugins/queue.js +139 -0
  30. package/plugins/search.js +51 -0
  31. package/plugins/storage.js +123 -0
  32. package/plugins/upload.js +62 -0
  33. package/router/router.js +57 -0
  34. package/runtime/app.js +14 -0
  35. package/runtime/client.js +254 -0
  36. package/runtime/cluster.js +35 -0
  37. package/runtime/edge.js +62 -0
  38. package/runtime/middleware/maintenance.js +54 -0
  39. package/runtime/middleware/security.js +30 -0
  40. package/runtime/server.js +130 -0
  41. package/runtime/validator.js +102 -0
  42. package/runtime/views/error.free +104 -0
  43. package/runtime/views/maintenance.free +0 -0
  44. package/template-engine/renderer.js +24 -0
  45. package/templates/app-template/.env +23 -0
  46. package/templates/app-template/app/Exceptions/Handler.js +65 -0
  47. package/templates/app-template/app/Http/Controllers/AuthController.free +91 -0
  48. package/templates/app-template/app/Http/Middleware/AuthGuard.js +46 -0
  49. package/templates/app-template/app/Services/Storage.js +75 -0
  50. package/templates/app-template/app/Services/Validator.js +91 -0
  51. package/templates/app-template/app/controllers/AuthController.free +42 -0
  52. package/templates/app-template/app/middleware/auth.js +25 -0
  53. package/templates/app-template/app/models/User.free +32 -0
  54. package/templates/app-template/app/routes.free +12 -0
  55. package/templates/app-template/app/styles.css +23 -0
  56. package/templates/app-template/app/views/counter.free +23 -0
  57. package/templates/app-template/app/views/header.free +13 -0
  58. package/templates/app-template/config/app.js +32 -0
  59. package/templates/app-template/config/auth.js +39 -0
  60. package/templates/app-template/config/database.js +54 -0
  61. package/templates/app-template/package.json +28 -0
  62. package/templates/app-template/resources/css/app.css +11 -0
  63. package/templates/app-template/resources/views/dashboard.free +25 -0
  64. package/templates/app-template/resources/views/home.free +26 -0
  65. package/templates/app-template/routes/api.free +22 -0
  66. package/templates/app-template/routes/web.free +25 -0
  67. package/templates/app-template/tailwind.config.js +21 -0
  68. package/templates/app-template/views/about.ejs +47 -0
  69. package/templates/app-template/views/home.ejs +52 -0
  70. package/templates/auth/login.html +144 -0
  71. package/templates/auth/register.html +146 -0
  72. package/utils/logger.js +20 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * plugins/queue.js
3
+ * Advanced Async Queue Plugin for Free Framework.
4
+ * Drivers: memory, redis.
5
+ * Features: concurrency control, persistence, retry with backoff, delayed jobs, cron.
6
+ */
7
+
8
+ function QueuePlugin(config = {}) {
9
+ const driver = config.driver || 'memory';
10
+ const MAX_WORKERS = config.concurrency || 5;
11
+
12
+ const jobHandlers = {};
13
+ const cronJobs = {};
14
+ let activeWorkers = 0;
15
+ let processed = 0;
16
+ let failed = 0;
17
+
18
+ // Internal In-Memory Queue (Driver: memory)
19
+ const pendingLocal = [];
20
+
21
+ return {
22
+ install(server) {
23
+ let redis = null;
24
+ if (driver === 'redis') {
25
+ const Redis = (() => {
26
+ try { return require('ioredis'); }
27
+ catch { throw new Error('QueuePlugin redis driver: run npm install ioredis'); }
28
+ })();
29
+ redis = new Redis(config.redis || process.env.REDIS_URL || 'redis://127.0.0.1:6379');
30
+ }
31
+
32
+ async function runJob(job) {
33
+ const { name, payload, maxRetries = 3, attempt = 1 } = job;
34
+ const handler = jobHandlers[name];
35
+ if (!handler) {
36
+ console.warn(`[Queue] No handler for job: "${name}"`);
37
+ return;
38
+ }
39
+ try {
40
+ await handler(payload, { attempt, maxRetries });
41
+ processed++;
42
+ } catch (err) {
43
+ console.error(`[Queue] Job "${name}" attempt ${attempt} failed: ${err.message}`);
44
+ if (attempt <= maxRetries) {
45
+ const delay = Math.pow(2, attempt - 1) * 1000;
46
+ setTimeout(() => dispatchInternal(name, payload, { ...job, attempt: attempt + 1, delay }), delay);
47
+ } else {
48
+ failed++;
49
+ console.error(`[Queue] Job "${name}" permanently failed.`);
50
+ }
51
+ }
52
+ }
53
+
54
+ async function runNext() {
55
+ if (activeWorkers >= MAX_WORKERS) return;
56
+
57
+ let job = null;
58
+ if (driver === 'redis') {
59
+ const raw = await redis.lpop('free:queue:pending');
60
+ if (raw) job = JSON.parse(raw);
61
+ } else {
62
+ job = pendingLocal.shift();
63
+ }
64
+
65
+ if (!job) return;
66
+
67
+ activeWorkers++;
68
+ runJob(job).finally(() => {
69
+ activeWorkers--;
70
+ runNext();
71
+ });
72
+ }
73
+
74
+ async function dispatchInternal(name, payload = {}, opts = {}) {
75
+ const job = {
76
+ name,
77
+ payload,
78
+ maxRetries: opts.retries ?? 3,
79
+ attempt: opts.attempt || 1,
80
+ priority: opts.priority || 0
81
+ };
82
+
83
+ if (driver === 'redis') {
84
+ await redis.rpush('free:queue:pending', JSON.stringify(job));
85
+ } else {
86
+ pendingLocal.push(job);
87
+ pendingLocal.sort((a, b) => (b.priority || 0) - (a.priority || 0));
88
+ }
89
+ runNext();
90
+ }
91
+
92
+ server.queue = {
93
+ register(name, handler) {
94
+ jobHandlers[name] = handler;
95
+ console.log(`⚡ Queue job registered: "${name}"`);
96
+ // Start worker loop if not already running
97
+ runNext();
98
+ },
99
+
100
+ dispatch(name, payload = {}, opts = {}) {
101
+ if (opts.delay > 0) {
102
+ setTimeout(() => dispatchInternal(name, payload, opts), opts.delay);
103
+ } else {
104
+ dispatchInternal(name, payload, opts);
105
+ }
106
+ },
107
+
108
+ cron(name, intervalMs, fn) {
109
+ if (cronJobs[name]) clearInterval(cronJobs[name]);
110
+ cronJobs[name] = setInterval(async () => {
111
+ try { await fn(); }
112
+ catch (e) { console.error(`[Cron] "${name}" error:`, e.message); }
113
+ }, intervalMs);
114
+ console.log(`⏰ Cron "${name}" every ${intervalMs}ms`);
115
+ },
116
+
117
+ stats() {
118
+ return {
119
+ driver,
120
+ activeWorkers,
121
+ maxWorkers: MAX_WORKERS,
122
+ processed,
123
+ failed,
124
+ registeredJobs: Object.keys(jobHandlers)
125
+ };
126
+ },
127
+ };
128
+
129
+ console.log(`🔁 Queue Plugin installed. Driver: ${driver}, Concurrency: ${MAX_WORKERS}`);
130
+
131
+ // Initial trigger for Redis workers
132
+ if (driver === 'redis') {
133
+ setInterval(() => runNext(), 1000); // Poll every second if idle
134
+ }
135
+ }
136
+ };
137
+ }
138
+
139
+ module.exports = QueuePlugin;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * plugins/search.js
3
+ * Full-text Search Plugin for Free Framework.
4
+ * Abstraction layer for SQLite FTS5, Meilisearch, or Algolia.
5
+ */
6
+
7
+ function SearchPlugin(config = {}) {
8
+ const driver = config.driver || 'sqlite'; // sqlite | meilisearch
9
+
10
+ return {
11
+ install(server) {
12
+ server.search = {
13
+ /**
14
+ * Search for records in a model.
15
+ * @param {Model} model - The ORM Model to search
16
+ * @param {string} query - The search query string
17
+ * @param {string[]} fields - Fields to search against
18
+ */
19
+ async query(model, query, fields = ['title', 'content']) {
20
+ if (driver === 'sqlite') {
21
+ // Basic SQLite search fallback (using LIKE or FTS if configured)
22
+ let q = model.query();
23
+ fields.forEach((field, index) => {
24
+ if (index === 0) q = q.where(field, 'like', `%${query}%`);
25
+ else q = q.orWhere(field, 'like', `%${query}%`);
26
+ });
27
+ return await q.limit(20);
28
+ }
29
+
30
+ if (driver === 'meilisearch') {
31
+ // Future: Integration with Meilisearch SDK
32
+ return [];
33
+ }
34
+
35
+ return [];
36
+ },
37
+
38
+ /**
39
+ * Index a record (if driver supports external indexing)
40
+ */
41
+ async index(model, record) {
42
+ // Logic for Meilisearch/Algolia
43
+ }
44
+ };
45
+
46
+ console.log(`🔍 Search Plugin installed. Driver: ${driver}`);
47
+ }
48
+ };
49
+ }
50
+
51
+ module.exports = SearchPlugin;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * plugins/storage.js
3
+ * File Storage Plugin for Free Framework.
4
+ * Drivers: local (filesystem), s3 (AWS S3 / S3-compatible).
5
+ *
6
+ * Usage:
7
+ * await server.storage.put("images/photo.jpg", fileBuffer);
8
+ * await server.storage.get("images/photo.jpg"); // returns Buffer
9
+ * server.storage.url("images/photo.jpg"); // public URL
10
+ * await server.storage.delete("images/photo.jpg");
11
+ * await server.storage.exists("images/photo.jpg"); // boolean
12
+ */
13
+
14
+ const fs = require('fs-extra');
15
+ const path = require('path');
16
+ const crypto = require('crypto');
17
+
18
+ function StoragePlugin(config = {}) {
19
+ const driver = config.driver || 'local';
20
+
21
+ return {
22
+ install(server) {
23
+ let backend;
24
+
25
+ if (driver === 's3') {
26
+ // S3 driver — requires: npm install @aws-sdk/client-s3
27
+ const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, HeadObjectCommand } = (() => {
28
+ try { return require('@aws-sdk/client-s3'); }
29
+ catch { throw new Error('Storage S3 driver: run npm install @aws-sdk/client-s3'); }
30
+ })();
31
+
32
+ const client = new S3Client({
33
+ region: config.region || process.env.AWS_REGION || 'us-east-1',
34
+ credentials: {
35
+ accessKeyId: config.accessKeyId || process.env.AWS_ACCESS_KEY_ID,
36
+ secretAccessKey: config.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY,
37
+ },
38
+ endpoint: config.endpoint, // for S3-compatible like MinIO
39
+ });
40
+ const bucket = config.bucket || process.env.AWS_BUCKET;
41
+
42
+ backend = {
43
+ async put(key, body, contentType = 'application/octet-stream') {
44
+ await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: body, ContentType: contentType }));
45
+ return key;
46
+ },
47
+ async get(key) {
48
+ const response = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
49
+ const chunks = [];
50
+ for await (const chunk of response.Body) chunks.push(chunk);
51
+ return Buffer.concat(chunks);
52
+ },
53
+ async delete(key) {
54
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
55
+ },
56
+ async exists(key) {
57
+ try { await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key })); return true; }
58
+ catch { return false; }
59
+ },
60
+ url(key) {
61
+ if (config.baseUrl) return `${config.baseUrl}/${key}`;
62
+ return `https://${bucket}.s3.amazonaws.com/${key}`;
63
+ },
64
+ };
65
+ } else {
66
+ // Local filesystem driver
67
+ const root = path.join(process.cwd(), config.root || 'storage/app');
68
+ fs.ensureDirSync(root);
69
+
70
+ backend = {
71
+ async put(key, body) {
72
+ const dest = path.join(root, key);
73
+ fs.ensureDirSync(path.dirname(dest));
74
+ await fs.writeFile(dest, body);
75
+ return key;
76
+ },
77
+ async get(key) {
78
+ return fs.readFile(path.join(root, key));
79
+ },
80
+ async delete(key) {
81
+ await fs.remove(path.join(root, key));
82
+ },
83
+ async exists(key) {
84
+ return fs.pathExists(path.join(root, key));
85
+ },
86
+ url(key) {
87
+ // Publicly accessible URL (assumes storage/app is served)
88
+ const base = config.baseUrl || process.env.APP_URL || 'http://localhost:3000';
89
+ return `${base}/storage/${key}`;
90
+ },
91
+ };
92
+
93
+ // Serve local storage via the server
94
+ server.app.get('/storage/*', async (req, res) => {
95
+ const key = req.path.replace('/storage/', '');
96
+ const filePath = path.join(root, key);
97
+ if (await fs.pathExists(filePath)) {
98
+ res.send(await fs.readFile(filePath));
99
+ } else {
100
+ res.status(404).send('Not found');
101
+ }
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Generate a unique key for a file upload.
107
+ * @param {string} originalName - e.g. "photo.jpg"
108
+ * @param {string} [folder] - optional sub-folder, e.g. "avatars"
109
+ */
110
+ backend.generateKey = (originalName, folder = '') => {
111
+ const ext = path.extname(originalName);
112
+ const hash = crypto.randomBytes(8).toString('hex');
113
+ const name = `${Date.now()}-${hash}${ext}`;
114
+ return folder ? `${folder}/${name}` : name;
115
+ };
116
+
117
+ server.storage = backend;
118
+ console.log(`📦 Storage Plugin installed. Driver: ${driver}`);
119
+ }
120
+ };
121
+ }
122
+
123
+ module.exports = StoragePlugin;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * plugins/upload.js
3
+ * High-performance Multipart Upload plugin for Free Framework.
4
+ */
5
+ const fs = require('fs-extra');
6
+ const path = require('path');
7
+
8
+ function UploadPlugin(name, config) {
9
+ return {
10
+ install(server) {
11
+ const destPath = path.join(process.cwd(), config.path || 'uploads');
12
+ fs.ensureDirSync(destPath);
13
+
14
+ // maxSize is parsed logic: '10mb' -> bytes
15
+ let MAX_SIZE_BYTES = 10 * 1024 * 1024; // Default 10mb
16
+ if (config.maxSize && config.maxSize.toLowerCase().endsWith('mb')) {
17
+ MAX_SIZE_BYTES = parseInt(config.maxSize) * 1024 * 1024;
18
+ } else if (config.maxSize && config.maxSize.toLowerCase().endsWith('gb')) {
19
+ MAX_SIZE_BYTES = parseInt(config.maxSize) * 1024 * 1024 * 1024;
20
+ }
21
+
22
+ server.registerMiddleware(`upload_${name}`, async (req, res, next) => {
23
+ req.files = {};
24
+ try {
25
+ await req.multipart(async (field) => {
26
+ if (field.file) {
27
+ const ext = path.extname(field.file.name);
28
+ const newFilename = `${Date.now()}-${Math.round(Math.random() * 1E9)}${ext}`;
29
+ const savePath = path.join(destPath, newFilename);
30
+
31
+ // Prevent memory bloat, write directly to disk via streams
32
+ await field.write(savePath);
33
+
34
+ req.files[field.name] = {
35
+ name: field.file.name,
36
+ path: savePath,
37
+ size: fs.statSync(savePath).size
38
+ };
39
+
40
+ // Size validation logic
41
+ if (req.files[field.name].size > MAX_SIZE_BYTES) {
42
+ fs.unlinkSync(savePath); // Delete oversized
43
+ throw new Error('File too large');
44
+ }
45
+ } else {
46
+ // Collect text fields
47
+ if (!req.body) req.body = {};
48
+ req.body[field.name] = field.value;
49
+ }
50
+ });
51
+ next();
52
+ } catch (e) {
53
+ res.status(400).send("Upload Failed: " + e.message);
54
+ }
55
+ });
56
+
57
+ console.log(`📁 Upload Plugin installed. 'upload_${name}' middleware created.`);
58
+ }
59
+ };
60
+ }
61
+
62
+ module.exports = UploadPlugin;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * router/router.js
3
+ * Advanced Router with support for Groups, Prefixes, and Middleware chaining.
4
+ */
5
+
6
+ class Router {
7
+ constructor(server, prefix = '', middleware = []) {
8
+ this.server = server;
9
+ this.prefix = prefix;
10
+ this.middleware = middleware;
11
+ }
12
+
13
+ /**
14
+ * Create a new route group
15
+ * @param {Object} options - { prefix, middleware }
16
+ * @param {Function} callback - Group definition callback
17
+ */
18
+ group(options, callback) {
19
+ const newPrefix = this.prefix + (options.prefix || '');
20
+ const newMiddleware = [...this.middleware, ...(options.middleware || [])];
21
+ const subRouter = new Router(this.server, newPrefix, newMiddleware);
22
+ callback(subRouter);
23
+ }
24
+
25
+ _add(method, path, middlewares, handler) {
26
+ const fullPath = (this.prefix + path).replace(/\/+/g, '/') || '/';
27
+ const fullMiddleware = [...this.middleware, ...middlewares];
28
+ this.server.route(method, fullPath, fullMiddleware, handler);
29
+ }
30
+
31
+ get(path, ...args) {
32
+ const handler = args.pop();
33
+ this._add('GET', path, args, handler);
34
+ }
35
+
36
+ post(path, ...args) {
37
+ const handler = args.pop();
38
+ this._add('POST', path, args, handler);
39
+ }
40
+
41
+ put(path, ...args) {
42
+ const handler = args.pop();
43
+ this._add('PUT', path, args, handler);
44
+ }
45
+
46
+ delete(path, ...args) {
47
+ const handler = args.pop();
48
+ this._add('DELETE', path, args, handler);
49
+ }
50
+
51
+ patch(path, ...args) {
52
+ const handler = args.pop();
53
+ this._add('PATCH', path, args, handler);
54
+ }
55
+ }
56
+
57
+ module.exports = { Router };
package/runtime/app.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * runtime/app.js
3
+ * Entry point for any Free application runtime.
4
+ */
5
+
6
+ const { FreeServer } = require("./server");
7
+ const { Router } = require("../router/router");
8
+ const { Model } = require("../database/model");
9
+
10
+ module.exports = {
11
+ FreeServer,
12
+ Router,
13
+ Model
14
+ };