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.
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/bin/free.js +118 -0
- package/cli/commands/build.js +124 -0
- package/cli/commands/deploy.js +143 -0
- package/cli/commands/devtools.js +210 -0
- package/cli/commands/doctor.js +72 -0
- package/cli/commands/install.js +28 -0
- package/cli/commands/make.js +74 -0
- package/cli/commands/migrate.js +67 -0
- package/cli/commands/new.js +54 -0
- package/cli/commands/serve.js +73 -0
- package/cli/commands/test.js +57 -0
- package/compiler/analyzer.js +102 -0
- package/compiler/generator.js +386 -0
- package/compiler/lexer.js +166 -0
- package/compiler/parser.js +410 -0
- package/database/model.js +6 -0
- package/database/orm.js +379 -0
- package/database/query-builder.js +179 -0
- package/index.js +51 -0
- package/package.json +80 -0
- package/plugins/auth.js +212 -0
- package/plugins/cache.js +85 -0
- package/plugins/chat.js +32 -0
- package/plugins/mail.js +53 -0
- package/plugins/metrics.js +126 -0
- package/plugins/payments.js +59 -0
- package/plugins/queue.js +139 -0
- package/plugins/search.js +51 -0
- package/plugins/storage.js +123 -0
- package/plugins/upload.js +62 -0
- package/router/router.js +57 -0
- package/runtime/app.js +14 -0
- package/runtime/client.js +254 -0
- package/runtime/cluster.js +35 -0
- package/runtime/edge.js +62 -0
- package/runtime/middleware/maintenance.js +54 -0
- package/runtime/middleware/security.js +30 -0
- package/runtime/server.js +130 -0
- package/runtime/validator.js +102 -0
- package/runtime/views/error.free +104 -0
- package/runtime/views/maintenance.free +0 -0
- package/template-engine/renderer.js +24 -0
- package/templates/app-template/.env +23 -0
- package/templates/app-template/app/Exceptions/Handler.js +65 -0
- package/templates/app-template/app/Http/Controllers/AuthController.free +91 -0
- package/templates/app-template/app/Http/Middleware/AuthGuard.js +46 -0
- package/templates/app-template/app/Services/Storage.js +75 -0
- package/templates/app-template/app/Services/Validator.js +91 -0
- package/templates/app-template/app/controllers/AuthController.free +42 -0
- package/templates/app-template/app/middleware/auth.js +25 -0
- package/templates/app-template/app/models/User.free +32 -0
- package/templates/app-template/app/routes.free +12 -0
- package/templates/app-template/app/styles.css +23 -0
- package/templates/app-template/app/views/counter.free +23 -0
- package/templates/app-template/app/views/header.free +13 -0
- package/templates/app-template/config/app.js +32 -0
- package/templates/app-template/config/auth.js +39 -0
- package/templates/app-template/config/database.js +54 -0
- package/templates/app-template/package.json +28 -0
- package/templates/app-template/resources/css/app.css +11 -0
- package/templates/app-template/resources/views/dashboard.free +25 -0
- package/templates/app-template/resources/views/home.free +26 -0
- package/templates/app-template/routes/api.free +22 -0
- package/templates/app-template/routes/web.free +25 -0
- package/templates/app-template/tailwind.config.js +21 -0
- package/templates/app-template/views/about.ejs +47 -0
- package/templates/app-template/views/home.ejs +52 -0
- package/templates/auth/login.html +144 -0
- package/templates/auth/register.html +146 -0
- package/utils/logger.js +20 -0
package/plugins/queue.js
ADDED
|
@@ -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;
|
package/router/router.js
ADDED
|
@@ -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
|
+
};
|