nicola-framework 1.0.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/core/Core.js +84 -0
- package/core/Remote.js +158 -0
- package/database/Connection.js +41 -0
- package/database/Driver.js +38 -0
- package/database/Model.js +48 -0
- package/database/QueryBuilder.js +96 -0
- package/database/Schema.js +24 -0
- package/database/dialects/Mysql.js +0 -0
- package/database/dialects/Postgres.js +162 -0
- package/database/index.js +20 -0
- package/dev-tools/DevRunner.js +6 -0
- package/dev-tools/LiveCurrent.js +38 -0
- package/index.js +31 -0
- package/middlewares/BlackBox.js +15 -0
- package/middlewares/EasyCors.js +19 -0
- package/middlewares/Insulator.js +24 -0
- package/middlewares/Shadowgraph.js +13 -0
- package/middlewares/Teleforce.js +11 -0
- package/package.json +33 -0
- package/security/Coherer.js +53 -0
- package/security/Regulator.js +38 -0
- package/templates/error.js +36 -0
package/core/Core.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import http from 'http'
|
|
2
|
+
import Remote from './Remote.js';
|
|
3
|
+
import BlackBox from '../middlewares/BlackBox.js'
|
|
4
|
+
import Shadowgraph from '../middlewares/Shadowgraph.js';
|
|
5
|
+
import Teleforce from '../middlewares/Teleforce.js';
|
|
6
|
+
import EasyCors from '../middlewares/EasyCors.js';
|
|
7
|
+
class Core extends Remote {
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
listen(port, callback) {
|
|
17
|
+
|
|
18
|
+
const server = http.createServer((req, res) => {
|
|
19
|
+
const myURL = new URL(req.url, 'http://' + req.headers.host);
|
|
20
|
+
const pathURL = myURL.pathname;
|
|
21
|
+
const urlParams = Object.fromEntries(myURL.searchParams);
|
|
22
|
+
|
|
23
|
+
req.url = pathURL;
|
|
24
|
+
req.query = urlParams;
|
|
25
|
+
|
|
26
|
+
Shadowgraph(req, res, () => {
|
|
27
|
+
this.__addHelper(res);
|
|
28
|
+
EasyCors(req, res, () =>{
|
|
29
|
+
|
|
30
|
+
Teleforce(req, res, () => {
|
|
31
|
+
const done = (err) => {
|
|
32
|
+
if (!err) {
|
|
33
|
+
res.statusCode = 404;
|
|
34
|
+
res.end('Not Found')
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
BlackBox.ignite(err, req, res);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let dataString = ''
|
|
42
|
+
req.on('data', chunk => {
|
|
43
|
+
dataString += chunk;
|
|
44
|
+
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
req.on('end', () => {
|
|
48
|
+
try {
|
|
49
|
+
if (dataString) {
|
|
50
|
+
req.body = JSON.parse(dataString);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
req.body = {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
req.body = {}
|
|
58
|
+
}
|
|
59
|
+
this.handle(req, res, done);
|
|
60
|
+
|
|
61
|
+
});
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
server.listen(port, callback);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
__addHelper(res) {
|
|
70
|
+
res.json = (data) => {
|
|
71
|
+
res.setHeader('Content-Type', 'application/json');
|
|
72
|
+
res.end(JSON.stringify(data));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
res.send = (data) => {
|
|
76
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
77
|
+
res.end(data);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default Core;
|
package/core/Remote.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
|
|
2
|
+
class Remote {
|
|
3
|
+
|
|
4
|
+
constructor() {
|
|
5
|
+
this.stack = []
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
__add(method, path, handlers) {
|
|
9
|
+
const results = this.parsePath(path)
|
|
10
|
+
const allHandlers = handlers.flat(Infinity);
|
|
11
|
+
if (allHandlers.length === 1 && allHandlers[0] instanceof Remote) {
|
|
12
|
+
this.stack.push({
|
|
13
|
+
method: method.toUpperCase(),
|
|
14
|
+
path: path,
|
|
15
|
+
handler: allHandlers[0]
|
|
16
|
+
})
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const runner = (req, res, routerNext) => {
|
|
21
|
+
let idx = 0
|
|
22
|
+
const internalNext = (err) => {
|
|
23
|
+
if (err) return routerNext(err);
|
|
24
|
+
|
|
25
|
+
const fn = allHandlers[idx];
|
|
26
|
+
idx++;
|
|
27
|
+
if (!fn) {
|
|
28
|
+
return routerNext();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
fn(req, res, internalNext);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
internalNext(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
internalNext();
|
|
39
|
+
}
|
|
40
|
+
if (results.regex) {
|
|
41
|
+
this.stack.push({
|
|
42
|
+
method: method.toUpperCase(),
|
|
43
|
+
path: path,
|
|
44
|
+
regex: results.regex,
|
|
45
|
+
keys: results.keys,
|
|
46
|
+
handler: runner
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.stack.push({
|
|
51
|
+
method: method.toUpperCase(),
|
|
52
|
+
path: results,
|
|
53
|
+
handler: runner
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get(path, ...handlers) {
|
|
59
|
+
this.__add('GET', path, handlers);
|
|
60
|
+
}
|
|
61
|
+
post(path, ...handlers) {
|
|
62
|
+
this.__add('POST', path, handlers);
|
|
63
|
+
}
|
|
64
|
+
put(path, ...handlers) {
|
|
65
|
+
this.__add('PUT', path, handlers);
|
|
66
|
+
}
|
|
67
|
+
patch(path, ...handlers) {
|
|
68
|
+
this.__add('PATCH', path, handlers);
|
|
69
|
+
}
|
|
70
|
+
delete(path, ...handlers) {
|
|
71
|
+
this.__add('DELETE', path, handlers);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
use(path, ...handlers) {
|
|
76
|
+
if (typeof path == 'function') {
|
|
77
|
+
this.__add('USE', '/', [path, ...handlers])
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.__add('USE', path, handlers);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
handle(req, res, done) {
|
|
86
|
+
let index = 0
|
|
87
|
+
let match = false
|
|
88
|
+
const next = (err) => {
|
|
89
|
+
if (err) {
|
|
90
|
+
return done(err)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const route = this.stack[index]
|
|
95
|
+
index++;
|
|
96
|
+
if (!route) {
|
|
97
|
+
return done()
|
|
98
|
+
}
|
|
99
|
+
const middleware = route.method === 'USE' && req.url.startsWith(route.path);
|
|
100
|
+
const rutaCoincidence = route.path === req.url && route.method === req.method;
|
|
101
|
+
match = route.regex && route.regex.test(req.url) && route.method === req.method;
|
|
102
|
+
if(match){
|
|
103
|
+
if(route.regex){
|
|
104
|
+
const valueCatch = req.url.match(route.regex)
|
|
105
|
+
req.params = {}
|
|
106
|
+
route.keys.forEach((key, index) => {
|
|
107
|
+
req.params[key]= valueCatch[index + 1];
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (rutaCoincidence || middleware || match) {
|
|
112
|
+
if (route.handler instanceof Remote) {
|
|
113
|
+
const urlBackup = req.url;
|
|
114
|
+
req.url = req.url.slice(route.path.length) || '/'
|
|
115
|
+
const done = (err) => {
|
|
116
|
+
req.url = urlBackup
|
|
117
|
+
next(err)
|
|
118
|
+
}
|
|
119
|
+
route.handler.handle(req, res, done);
|
|
120
|
+
|
|
121
|
+
console.log('Esto es un sub router')
|
|
122
|
+
} else {
|
|
123
|
+
|
|
124
|
+
route.handler(req, res, next);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
next()
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
next(err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
next()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
parsePath(path) {
|
|
141
|
+
if (!path.includes(':')) {
|
|
142
|
+
return path;
|
|
143
|
+
}
|
|
144
|
+
if (path.includes(':')) {
|
|
145
|
+
const keys = []
|
|
146
|
+
let pathString = path.replace(/:(\w+)/g, (match, paramName) => {
|
|
147
|
+
keys.push(paramName);
|
|
148
|
+
|
|
149
|
+
return '([^/]+)';
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const regex = new RegExp(`^${pathString}$`)
|
|
153
|
+
return { regex, keys };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default Remote;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Postgres from "./dialects/Postgres.js";
|
|
2
|
+
|
|
3
|
+
class Connection {
|
|
4
|
+
static client = null
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
static async connect() {
|
|
8
|
+
const DB_DRIVER = process.env.DB_DRIVER
|
|
9
|
+
const config = {
|
|
10
|
+
user: process.env.DB_USER,
|
|
11
|
+
password: process.env.DB_PASS,
|
|
12
|
+
host: process.env.DB_HOST,
|
|
13
|
+
port: process.env.DB_PORT,
|
|
14
|
+
database: process.env.DB_NAME
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if(DB_DRIVER === 'postgres'){
|
|
18
|
+
this.client = new Postgres(config)
|
|
19
|
+
await this.client.connect()
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if(DB_DRIVER === 'mysql'){
|
|
23
|
+
await this.__connectMysql()
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
throw new Error("Driver no soportado" + DB_DRIVER)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
static async __connectMysql(){
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static async query(sql, params){
|
|
36
|
+
return this.client.query(sql, params);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export default Connection;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Driver {
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
constructor(config){
|
|
5
|
+
this.config = config
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async connect(){
|
|
9
|
+
throw new Error("Method 'connect()' must be implemented");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async query(sql, params){
|
|
13
|
+
throw new Error("Method 'query()' must be implemented");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async disconnect(){
|
|
17
|
+
throw new Error("Method 'disconnect()' must be implemented");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
compileSelect(builder){
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
compileInsert(builder, data){
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
compileUpdate(builder, data){
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
compileDelete(builder){
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export default Driver;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import QueryBuilder from "./QueryBuilder.js";
|
|
2
|
+
import Schema from "./Schema.js";
|
|
3
|
+
|
|
4
|
+
class Model {
|
|
5
|
+
static tableName = null
|
|
6
|
+
static schema = {}
|
|
7
|
+
static query(){
|
|
8
|
+
return new QueryBuilder(this.tableName)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static select(...params){
|
|
12
|
+
return this.query().select(...params);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static where(...params){
|
|
16
|
+
return this.query().where(...params);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static all(){
|
|
20
|
+
return this.query().get();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static create(...params){
|
|
24
|
+
Schema.validate(...params, this.schema)
|
|
25
|
+
return this.query().insert(...params);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static update(...params){
|
|
29
|
+
return this.query().update(...params);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static delete(){
|
|
33
|
+
return this.query().delete();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static orderBy(...params){
|
|
37
|
+
return this.query().orderby(...params);
|
|
38
|
+
}
|
|
39
|
+
static limit(...params){
|
|
40
|
+
return this.query().limit(...params);
|
|
41
|
+
}
|
|
42
|
+
static offset(...params){
|
|
43
|
+
return this.query().offset(...params);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export default Model;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import Connection from "./Connection.js";
|
|
2
|
+
|
|
3
|
+
class QueryBuilder {
|
|
4
|
+
constructor(db_name) {
|
|
5
|
+
this.table = db_name;
|
|
6
|
+
this.columns = ["*"];
|
|
7
|
+
this.conditions = [];
|
|
8
|
+
this.bindings = [];
|
|
9
|
+
this.orders= [];
|
|
10
|
+
this.limitCount= null;
|
|
11
|
+
this.offsetCount = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
select(columns) {
|
|
15
|
+
if (Array.isArray(columns)) {
|
|
16
|
+
this.columns = columns;
|
|
17
|
+
}
|
|
18
|
+
if (typeof columns === "string") {
|
|
19
|
+
let columnsSplit = columns.split(",");
|
|
20
|
+
let cleanCols = [];
|
|
21
|
+
for (const col of columnsSplit) {
|
|
22
|
+
cleanCols.push(col.trim());
|
|
23
|
+
}
|
|
24
|
+
this.columns = cleanCols;
|
|
25
|
+
}
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
where(column, operator, value) {
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
value = operator;
|
|
32
|
+
operator = "=";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let totalIndex = this.bindings.length + 1;
|
|
36
|
+
|
|
37
|
+
this.conditions.push({
|
|
38
|
+
column: column,
|
|
39
|
+
operator: operator,
|
|
40
|
+
bindingsIndex: totalIndex,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.bindings.push(value);
|
|
44
|
+
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async get() {
|
|
49
|
+
const { sql, bindings } = Connection.client.compileSelect(this);
|
|
50
|
+
const result = await Connection.query(sql, bindings);
|
|
51
|
+
return result.rows;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async insert(data) {
|
|
55
|
+
const { sql, bindings } = Connection.client.compileInsert(this, data);
|
|
56
|
+
const result = await Connection.query(sql, bindings);
|
|
57
|
+
return result.rows[0];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async update(data) {
|
|
61
|
+
const { sql, bindings } = Connection.client.compileUpdate(this, data);
|
|
62
|
+
const result = await Connection.query(sql, bindings);
|
|
63
|
+
return result.count;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async delete() {
|
|
67
|
+
const { sql, bindings } = Connection.client.compileDelete(this);
|
|
68
|
+
const result = await Connection.query(sql, bindings);
|
|
69
|
+
return result.count;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
orderBy(column, direction = "ASC"){
|
|
73
|
+
this.orders.push({
|
|
74
|
+
column: column,
|
|
75
|
+
direction: direction.toUpperCase()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
limit(number){
|
|
82
|
+
this.limitCount = number
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
offset(number){
|
|
88
|
+
this.offsetCount = number
|
|
89
|
+
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default QueryBuilder;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class Schema{
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
static validate(data, rules){
|
|
8
|
+
for(const field in rules){
|
|
9
|
+
const rule = rules[field]
|
|
10
|
+
const value = data[field]
|
|
11
|
+
|
|
12
|
+
if(rule.required === true && (value === undefined || value === null || value === '')){
|
|
13
|
+
throw new Error(`Field ${field} is required`)
|
|
14
|
+
}
|
|
15
|
+
if(typeof value !== rule.type && value !== undefined && value !== null){
|
|
16
|
+
throw new Error(`Field ${field} must be a ${rule.type}`)
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default Schema;
|
|
File without changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import Driver from "../Driver.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Postgres extends Driver {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
super(config);
|
|
7
|
+
this.client = null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async connect() {
|
|
11
|
+
try {
|
|
12
|
+
const pg = await import('pg')
|
|
13
|
+
const { Pool } = pg.default || pg
|
|
14
|
+
|
|
15
|
+
const config = {
|
|
16
|
+
user: this.config.user,
|
|
17
|
+
password: this.config.password,
|
|
18
|
+
host: this.config.host,
|
|
19
|
+
port: this.config.port,
|
|
20
|
+
database: this.config.database
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.env.DB_SSLMODE) {
|
|
24
|
+
const sslMode = process.env.DB_SSLMODE.toLowerCase();
|
|
25
|
+
|
|
26
|
+
if (sslMode === 'require') {
|
|
27
|
+
config.ssl = { rejectUnauthorized: false };
|
|
28
|
+
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
|
29
|
+
config.ssl = { rejectUnauthorized: true };
|
|
30
|
+
} else if (sslMode === 'disable' || sslMode === 'prefer') {
|
|
31
|
+
config.ssl = false;
|
|
32
|
+
} else {
|
|
33
|
+
config.ssl = { rejectUnauthorized: false };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.client = new Pool(config)
|
|
38
|
+
|
|
39
|
+
await this.client.query('SELECT NOW()')
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (e.code === 'ERR_MODULE_NOT_FOUND') {
|
|
43
|
+
console.log('Por favor utiliza el comando npm install pg')
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(e)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async query(sql, params) {
|
|
53
|
+
if (!this.client) {
|
|
54
|
+
return console.log("Dynamo: No hay ninguna conexion activa")
|
|
55
|
+
}
|
|
56
|
+
const result = await this.client.query(sql, params);
|
|
57
|
+
return {
|
|
58
|
+
rows: result.rows,
|
|
59
|
+
count: result.rowCount
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
compileSelect(builder) {
|
|
64
|
+
const stringColums = builder.columns.join(', ')
|
|
65
|
+
let ordersBy = ''
|
|
66
|
+
let limit = ''
|
|
67
|
+
let offset = ''
|
|
68
|
+
if(builder.orders.length > 0){
|
|
69
|
+
const OrdesArray = builder.orders.map(o => {
|
|
70
|
+
return ` ${o.column} ${o.direction}`
|
|
71
|
+
})
|
|
72
|
+
ordersBy =' ORDER BY ' + OrdesArray.join(',')
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
if(builder.limitCount !== null){
|
|
76
|
+
limit = ` LIMIT ${builder.limitCount}`
|
|
77
|
+
}
|
|
78
|
+
if(builder.offsetCount !== null){
|
|
79
|
+
offset = ` OFFSET ${builder.offsetCount}`
|
|
80
|
+
}
|
|
81
|
+
const where = this._buildWhere(builder.conditions)
|
|
82
|
+
const sql = `SELECT ${stringColums} FROM ${builder.table} ${where} ${ordersBy} ${limit} ${offset}`
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
sql: sql,
|
|
86
|
+
bindings: builder.bindings
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_buildWhere(conditions) {
|
|
91
|
+
let whereQuery = ''
|
|
92
|
+
|
|
93
|
+
if (conditions.length > 0) {
|
|
94
|
+
const query = conditions.map(condition => {
|
|
95
|
+
return ` ${condition.column} ${condition.operator} $${condition.bindingsIndex}`
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return whereQuery = 'WHERE ' + query.join(' AND ')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return whereQuery
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
compileInsert(builder, data) {
|
|
106
|
+
let columns = Object.keys(data);
|
|
107
|
+
columns = columns.join(', ')
|
|
108
|
+
let values = Object.values(data)
|
|
109
|
+
let bindingsIndex = []
|
|
110
|
+
values.forEach(v => {
|
|
111
|
+
let index = builder.bindings.length + 1
|
|
112
|
+
bindingsIndex.push(`$${index}`)
|
|
113
|
+
builder.bindings.push(v)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
bindingsIndex = bindingsIndex.join(', ')
|
|
117
|
+
|
|
118
|
+
let sql = `INSERT INTO ${builder.table} (${columns}) VALUES (${bindingsIndex}) RETURNING *`
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
sql: sql,
|
|
122
|
+
bindings: builder.bindings
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
compileUpdate(builder, data) {
|
|
127
|
+
const columns = Object.keys(data)
|
|
128
|
+
const values = Object.values(data)
|
|
129
|
+
let stringColums = []
|
|
130
|
+
let whereQuery = ''
|
|
131
|
+
let index = 0
|
|
132
|
+
columns.forEach((c, i) => {
|
|
133
|
+
index = builder.bindings.length + 1
|
|
134
|
+
builder.bindings.push(values[i])
|
|
135
|
+
stringColums.push(`${c} = $${index}`)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const where = this._buildWhere(builder.conditions)
|
|
139
|
+
|
|
140
|
+
stringColums = stringColums.join(', ')
|
|
141
|
+
|
|
142
|
+
const sql = `UPDATE ${builder.table} SET ${stringColums} ${where}`
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
sql:sql,
|
|
146
|
+
bindings: builder.bindings
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
compileDelete(builder) {
|
|
151
|
+
const where = this._buildWhere(builder.conditions)
|
|
152
|
+
|
|
153
|
+
const sql = `DELETE FROM ${builder.table} ${where}`
|
|
154
|
+
|
|
155
|
+
return{
|
|
156
|
+
sql:sql,
|
|
157
|
+
bindings: builder.bindings
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default Postgres;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Model from './Model.js'
|
|
2
|
+
import QueryBuilder from './QueryBuilder.js'
|
|
3
|
+
import Connection from './Connection.js'
|
|
4
|
+
|
|
5
|
+
class Dynamo{
|
|
6
|
+
static async connect(){
|
|
7
|
+
return Connection.connect()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static async query(sql, params){
|
|
11
|
+
return Connection.query(sql, params)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
static Model = Model;
|
|
16
|
+
static QueryBuilder = QueryBuilder;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export default Dynamo;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'fs/promises'
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
class LiveCurrent{
|
|
5
|
+
constructor(entryPoint){
|
|
6
|
+
this.entryPoint = entryPoint;
|
|
7
|
+
this.process = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
boot(){
|
|
11
|
+
this.watch()
|
|
12
|
+
this.ignite()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
ignite(){
|
|
16
|
+
this.process= spawn('node', [this.entryPoint], {stdio: 'inherit'});
|
|
17
|
+
console.log(chalk.greenBright(`LiveCurrent: Iniciando Servidor...`));
|
|
18
|
+
}
|
|
19
|
+
reload(){
|
|
20
|
+
if(this.process){
|
|
21
|
+
this.process.kill()
|
|
22
|
+
this.ignite()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
watch(){
|
|
27
|
+
fs.watch(process.cwd(), {recursive: true}, (eventType, filename) => {
|
|
28
|
+
if(filename.includes('node_modules') || filename === ''){
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
this.reload()
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export default LiveCurrent;
|
package/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tesla Framework - Main Export
|
|
3
|
+
*
|
|
4
|
+
* Punto de entrada principal del framework
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Core
|
|
8
|
+
export { default as Tesla } from './core/Core.js';
|
|
9
|
+
export { default as Remote } from './core/Remote.js';
|
|
10
|
+
|
|
11
|
+
// Middlewares
|
|
12
|
+
export { default as Shadowgraph } from './middlewares/Shadowgraph.js';
|
|
13
|
+
export { default as Teleforce } from './middlewares/Teleforce.js';
|
|
14
|
+
export { default as BlackBox } from './middlewares/BlackBox.js';
|
|
15
|
+
export { default as EasyCors } from './middlewares/EasyCors.js';
|
|
16
|
+
export { default as Insulator } from './middlewares/Insulator.js';
|
|
17
|
+
|
|
18
|
+
// Security
|
|
19
|
+
export { default as Coherer } from './security/Coherer.js';
|
|
20
|
+
export { default as Regulator } from './security/Regulator.js';
|
|
21
|
+
|
|
22
|
+
// Dev Tools
|
|
23
|
+
export { default as LiveCurrent } from './dev-tools/LiveCurrent.js';
|
|
24
|
+
export { default as DevRunner } from './dev-tools/DevRunner.js';
|
|
25
|
+
|
|
26
|
+
// Database (Dynamo ORM)
|
|
27
|
+
export { default as Dynamo } from './database/index.js';
|
|
28
|
+
|
|
29
|
+
// Default export (Tesla Core)
|
|
30
|
+
import Tesla from './core/Core.js';
|
|
31
|
+
export default Tesla;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { error } from '../templates/error.js'
|
|
2
|
+
class BlackBox{
|
|
3
|
+
constructor(){
|
|
4
|
+
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
static ignite(err, req , res){
|
|
8
|
+
|
|
9
|
+
res.writeHead(500, {'Content-Type': 'text/html'})
|
|
10
|
+
res.end(error(err.message, err.stack))
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export default BlackBox;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const EasyCors = (req, res, next) =>{
|
|
4
|
+
res.setHeader('Access-Control-Allow-Origin','*')
|
|
5
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH')
|
|
6
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
|
7
|
+
|
|
8
|
+
if(req.method === 'OPTIONS'){
|
|
9
|
+
res.statusCode = 204
|
|
10
|
+
res.end()
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
else{
|
|
14
|
+
next()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export default EasyCors;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
const Insulator = (schema) => {
|
|
3
|
+
|
|
4
|
+
return (req, res, next) => {
|
|
5
|
+
for (const key in schema) {
|
|
6
|
+
const expectedType = schema[key];
|
|
7
|
+
const value = req.body[key]
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
res.statusCode = 400;
|
|
10
|
+
res.end(`Falta campo: ${key}`)
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (typeof value !== expectedType) {
|
|
14
|
+
res.statusCode = 400;
|
|
15
|
+
res.end(`El campo ${key} debe ser ${expectedType}`)
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
next()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export default Insulator;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
const Shadowgraph = (req, res, next) =>{
|
|
4
|
+
const inicio = Date.now()
|
|
5
|
+
res.on('finish', () =>{
|
|
6
|
+
const duracion = Date.now() - inicio
|
|
7
|
+
console.log(chalk.green(`[${req.method}] ${req.url} - ${res.statusCode} ${res.statusMessage} - ${duracion}ms`))
|
|
8
|
+
})
|
|
9
|
+
next()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export default Shadowgraph;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const Teleforce = (req, res, next) =>{
|
|
4
|
+
res.removeHeader('X-Powered-By');
|
|
5
|
+
res.setHeader('X-Content-Type-Options', 'nosniff')
|
|
6
|
+
res.setHeader('X-Frame-Options', 'Deny')
|
|
7
|
+
res.setHeader('X-XSS-Protection', '1')
|
|
8
|
+
next()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default Teleforce;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nicola-framework",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-dependency web framework for Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./core": "./core/Core.js",
|
|
10
|
+
"./router": "./core/Remote.js",
|
|
11
|
+
"./middlewares/*": "./middlewares/*.js",
|
|
12
|
+
"./security/*": "./security/*.js",
|
|
13
|
+
"./database": "./database/index.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"framework",
|
|
20
|
+
"web",
|
|
21
|
+
"http",
|
|
22
|
+
"server",
|
|
23
|
+
"router",
|
|
24
|
+
"middleware",
|
|
25
|
+
"zero-dependency",
|
|
26
|
+
"orm"
|
|
27
|
+
],
|
|
28
|
+
"author": "Your Name",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"chalk": "^5.6.2"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import Regulator from './Regulator.js';
|
|
3
|
+
|
|
4
|
+
class Coherer{
|
|
5
|
+
constructor(){
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
static SECRET = process.env.TESLA_SECRET || 'tesla_secret_dev_key'
|
|
9
|
+
|
|
10
|
+
static codec(jsonData){
|
|
11
|
+
const dataString = JSON.stringify(jsonData);
|
|
12
|
+
const buffer = Buffer.from(dataString);
|
|
13
|
+
return buffer.toString('base64url')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static sign(Payload){
|
|
17
|
+
const payloadB64 = this.codec(Payload);
|
|
18
|
+
const header = {
|
|
19
|
+
alg: 'HS256',
|
|
20
|
+
typ: 'JWT'
|
|
21
|
+
}
|
|
22
|
+
const headerB64 = this.codec(header)
|
|
23
|
+
|
|
24
|
+
const data = headerB64 + '.' + payloadB64
|
|
25
|
+
|
|
26
|
+
const signature = crypto.createHmac('sha256', this.SECRET)
|
|
27
|
+
.update(data)
|
|
28
|
+
.digest('base64url')
|
|
29
|
+
|
|
30
|
+
return data + '.' + signature
|
|
31
|
+
}
|
|
32
|
+
static verify(data){
|
|
33
|
+
const [headerB64, payloadB64, signature] = data.token.split('.');
|
|
34
|
+
|
|
35
|
+
const dataToCheck = headerB64 + '.' + payloadB64
|
|
36
|
+
|
|
37
|
+
const signatureToChecks = crypto.createHmac('sha256', this.SECRET)
|
|
38
|
+
.update(dataToCheck)
|
|
39
|
+
.digest('base64url')
|
|
40
|
+
|
|
41
|
+
if(signature === signatureToChecks){
|
|
42
|
+
let decodedPayload = Buffer.from(payloadB64, 'base64url').toString('utf-8')
|
|
43
|
+
decodedPayload = JSON.parse(decodedPayload)
|
|
44
|
+
return decodedPayload
|
|
45
|
+
}
|
|
46
|
+
else{
|
|
47
|
+
throw new Error('Token Invalido')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export default Coherer;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Regulator{
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
static load(){
|
|
10
|
+
const CURRENT_DIR = path.join(process.cwd(), ".env")
|
|
11
|
+
|
|
12
|
+
if(fs.existsSync(CURRENT_DIR)){
|
|
13
|
+
const envFile= fs.readFileSync(CURRENT_DIR, 'utf-8');
|
|
14
|
+
let lineEnv = envFile.split('\n')
|
|
15
|
+
lineEnv.forEach(line => {
|
|
16
|
+
let lineClean = line.trim()
|
|
17
|
+
if(lineClean.length === 0 || lineClean.startsWith('#')){
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let index = lineClean.indexOf('=')
|
|
21
|
+
if(index === -1){
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let key = lineClean.substring(0, index);
|
|
26
|
+
let value = lineClean.substring(index + 1);
|
|
27
|
+
|
|
28
|
+
key = key.trim()
|
|
29
|
+
value = value.trim()
|
|
30
|
+
|
|
31
|
+
process.env[key] = value;
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default Regulator;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export const error = (message, stack) =>{
|
|
4
|
+
|
|
5
|
+
return `
|
|
6
|
+
<html>
|
|
7
|
+
<head>
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
background-color: #111;
|
|
11
|
+
color: #fff;
|
|
12
|
+
font-family: monospace;
|
|
13
|
+
padding: 20px;
|
|
14
|
+
}
|
|
15
|
+
h1 {
|
|
16
|
+
color: #ff3333;
|
|
17
|
+
}
|
|
18
|
+
pre {
|
|
19
|
+
background: #222;
|
|
20
|
+
padding: 15px;
|
|
21
|
+
border-radius: 5px;
|
|
22
|
+
overflow-x: auto;
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div class = "error-box">
|
|
28
|
+
<h1> Reporte de Error </h1>
|
|
29
|
+
<h2>${message}</h2>
|
|
30
|
+
</div>
|
|
31
|
+
<h3> Reporte de Error </h3>
|
|
32
|
+
<pre>${stack}</pre>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
`
|
|
36
|
+
}
|