lieko-express 0.0.7 → 0.0.8
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/lieko-express.js +147 -479
- package/package.json +2 -2
package/lieko-express.js
CHANGED
|
@@ -3,410 +3,15 @@ const net = require("net");
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class Schema {
|
|
17
|
-
constructor(rules) {
|
|
18
|
-
this.rules = rules;
|
|
19
|
-
this.fields = rules;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
validate(data) {
|
|
23
|
-
const errors = [];
|
|
24
|
-
for (const [field, validators] of Object.entries(this.rules)) {
|
|
25
|
-
const value = data[field];
|
|
26
|
-
for (const validator of validators) {
|
|
27
|
-
const error = validator(value, field, data);
|
|
28
|
-
if (error) {
|
|
29
|
-
errors.push(error);
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (errors.length > 0) throw new ValidationError(errors);
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const validators = {
|
|
40
|
-
required: (message = 'Field is required') => {
|
|
41
|
-
return (value, field) => {
|
|
42
|
-
if (value === undefined || value === null || value === '') {
|
|
43
|
-
return { field, message, type: 'required' };
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
requiredTrue: (message = 'Field must be true') => {
|
|
50
|
-
return (value, field) => {
|
|
51
|
-
const normalized = value === true || value === 'true' || value === '1' || value === 1;
|
|
52
|
-
if (!normalized) {
|
|
53
|
-
return { field, message, type: 'requiredTrue' };
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
optional: () => {
|
|
60
|
-
return () => null;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
string: (message = 'Field must be a string') => {
|
|
64
|
-
return (value, field) => {
|
|
65
|
-
if (value !== undefined && typeof value !== 'string') {
|
|
66
|
-
return { field, message, type: 'string' };
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
number: (message = 'Field must be a number') => {
|
|
73
|
-
return (value, field) => {
|
|
74
|
-
if (value !== undefined && typeof value !== 'number') {
|
|
75
|
-
return { field, message, type: 'number' };
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
boolean: (message = 'Field must be a boolean') => {
|
|
82
|
-
return (value, field) => {
|
|
83
|
-
if (value === undefined || value === null || value === '') return null;
|
|
84
|
-
|
|
85
|
-
const validTrue = ['true', true, 1, '1'];
|
|
86
|
-
const validFalse = ['false', false, 0, '0'];
|
|
87
|
-
|
|
88
|
-
const isValid = validTrue.includes(value) || validFalse.includes(value);
|
|
89
|
-
|
|
90
|
-
if (!isValid) {
|
|
91
|
-
return { field, message, type: 'boolean' };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return null;
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
integer: (message = 'Field must be an integer') => {
|
|
99
|
-
return (value, field) => {
|
|
100
|
-
if (value !== undefined && !Number.isInteger(value)) {
|
|
101
|
-
return { field, message, type: 'integer' };
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
positive: (message = 'Field must be positive') => {
|
|
108
|
-
return (value, field) => {
|
|
109
|
-
if (value !== undefined && value <= 0) {
|
|
110
|
-
return { field, message, type: 'positive' };
|
|
111
|
-
}
|
|
112
|
-
return null;
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
negative: (message = 'Field must be negative') => {
|
|
117
|
-
return (value, field) => {
|
|
118
|
-
if (value !== undefined && value >= 0) {
|
|
119
|
-
return { field, message, type: 'negative' };
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
};
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
email: (message = 'Invalid email format') => {
|
|
126
|
-
return (value, field) => {
|
|
127
|
-
if (value !== undefined && value !== null) {
|
|
128
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
129
|
-
if (!emailRegex.test(value)) {
|
|
130
|
-
return { field, message, type: 'email' };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
};
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
min: (minValue, message) => {
|
|
138
|
-
return (value, field) => {
|
|
139
|
-
if (value !== undefined && value !== null) {
|
|
140
|
-
if (typeof value === 'string' && value.length < minValue) {
|
|
141
|
-
return {
|
|
142
|
-
field,
|
|
143
|
-
message: message || `Field must be at least ${minValue} characters`,
|
|
144
|
-
type: 'min'
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
if (typeof value === 'number' && value < minValue) {
|
|
148
|
-
return {
|
|
149
|
-
field,
|
|
150
|
-
message: message || `Field must be at least ${minValue}`,
|
|
151
|
-
type: 'min'
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
};
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
max: (maxValue, message) => {
|
|
160
|
-
return (value, field) => {
|
|
161
|
-
if (value !== undefined && value !== null) {
|
|
162
|
-
if (typeof value === 'string' && value.length > maxValue) {
|
|
163
|
-
return {
|
|
164
|
-
field,
|
|
165
|
-
message: message || `Field must be at most ${maxValue} characters`,
|
|
166
|
-
type: 'max'
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
if (typeof value === 'number' && value > maxValue) {
|
|
170
|
-
return {
|
|
171
|
-
field,
|
|
172
|
-
message: message || `Field must be at most ${maxValue}`,
|
|
173
|
-
type: 'max'
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
length: (n, message) => {
|
|
182
|
-
return (value, field) => {
|
|
183
|
-
if (typeof value === 'string' && value.length !== n) {
|
|
184
|
-
return {
|
|
185
|
-
field,
|
|
186
|
-
message: message || `Field must be exactly ${n} characters`,
|
|
187
|
-
type: 'length'
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
};
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
minLength: (minLength, message) => {
|
|
195
|
-
return (value, field) => {
|
|
196
|
-
if (value !== undefined && value !== null && typeof value === 'string') {
|
|
197
|
-
if (value.length < minLength) {
|
|
198
|
-
return {
|
|
199
|
-
field,
|
|
200
|
-
message: message || `Field must be at least ${minLength} characters`,
|
|
201
|
-
type: 'minLength'
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
};
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
maxLength: (maxLength, message) => {
|
|
210
|
-
return (value, field) => {
|
|
211
|
-
if (value !== undefined && value !== null && typeof value === 'string') {
|
|
212
|
-
if (value.length > maxLength) {
|
|
213
|
-
return {
|
|
214
|
-
field,
|
|
215
|
-
message: message || `Field must be at most ${maxLength} characters`,
|
|
216
|
-
type: 'maxLength'
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
pattern: (regex, message = 'Invalid format') => {
|
|
225
|
-
return (value, field) => {
|
|
226
|
-
if (value !== undefined && value !== null && typeof value === 'string') {
|
|
227
|
-
if (!regex.test(value)) {
|
|
228
|
-
return { field, message, type: 'pattern' };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return null;
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
oneOf: (allowedValues, message) => {
|
|
236
|
-
return (value, field) => {
|
|
237
|
-
if (value !== undefined && value !== null) {
|
|
238
|
-
if (!allowedValues.includes(value)) {
|
|
239
|
-
return {
|
|
240
|
-
field,
|
|
241
|
-
message: message || `Field must be one of: ${allowedValues.join(', ')}`,
|
|
242
|
-
type: 'oneOf'
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return null;
|
|
247
|
-
};
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
notOneOf: (values, message) => {
|
|
251
|
-
return (value, field) => {
|
|
252
|
-
if (values.includes(value)) {
|
|
253
|
-
return {
|
|
254
|
-
field,
|
|
255
|
-
message: message || `Field cannot be one of: ${values.join(', ')}`,
|
|
256
|
-
type: 'notOneOf'
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
return null;
|
|
260
|
-
};
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
custom: (validatorFn, message = 'Validation failed') => {
|
|
264
|
-
return (value, field, data) => {
|
|
265
|
-
const isValid = validatorFn(value, data);
|
|
266
|
-
if (!isValid) {
|
|
267
|
-
return { field, message, type: 'custom' };
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
};
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
equal: (expectedValue, message) => {
|
|
274
|
-
return (value, field) => {
|
|
275
|
-
if (value !== expectedValue) {
|
|
276
|
-
return {
|
|
277
|
-
field,
|
|
278
|
-
message: message || `Field must be equal to ${expectedValue}`,
|
|
279
|
-
type: 'equal'
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
return null;
|
|
283
|
-
};
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
mustBeTrue: (message = 'This field must be accepted') => {
|
|
287
|
-
return (value, field) => {
|
|
288
|
-
const normalized = value === true || value === 'true' || value === '1' || value === 1;
|
|
289
|
-
if (!normalized) {
|
|
290
|
-
return { field, message, type: 'mustBeTrue' };
|
|
291
|
-
}
|
|
292
|
-
return null;
|
|
293
|
-
};
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
mustBeFalse: (message = 'This field must be declined') => {
|
|
297
|
-
return (value, field) => {
|
|
298
|
-
const normalized = value === false || value === 'false' || value === '0' || value === 0;
|
|
299
|
-
if (!normalized) {
|
|
300
|
-
return { field, message, type: 'mustBeFalse' };
|
|
301
|
-
}
|
|
302
|
-
return null;
|
|
303
|
-
};
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
date: (message = 'Invalid date') => {
|
|
307
|
-
return (value, field) => {
|
|
308
|
-
if (!value) return null;
|
|
309
|
-
const date = new Date(value);
|
|
310
|
-
if (isNaN(date.getTime())) {
|
|
311
|
-
return { field, message, type: 'date' };
|
|
312
|
-
}
|
|
313
|
-
return null;
|
|
314
|
-
};
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
before: (limit, message) => {
|
|
318
|
-
return (value, field) => {
|
|
319
|
-
if (!value) return null;
|
|
320
|
-
const d1 = new Date(value);
|
|
321
|
-
const d2 = new Date(limit);
|
|
322
|
-
if (isNaN(d1) || d1 >= d2) {
|
|
323
|
-
return {
|
|
324
|
-
field,
|
|
325
|
-
message: message || `Date must be before ${limit}`,
|
|
326
|
-
type: 'before'
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
return null;
|
|
330
|
-
};
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
after: (limit, message) => {
|
|
334
|
-
return (value, field) => {
|
|
335
|
-
if (!value) return null;
|
|
336
|
-
const d1 = new Date(value);
|
|
337
|
-
const d2 = new Date(limit);
|
|
338
|
-
if (isNaN(d1) || d1 <= d2) {
|
|
339
|
-
return {
|
|
340
|
-
field,
|
|
341
|
-
message: message || `Date must be after ${limit}`,
|
|
342
|
-
type: 'after'
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
return null;
|
|
346
|
-
};
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
startsWith: (prefix, message) => {
|
|
350
|
-
return (value, field) => {
|
|
351
|
-
if (typeof value === 'string' && !value.startsWith(prefix)) {
|
|
352
|
-
return {
|
|
353
|
-
field,
|
|
354
|
-
message: message || `Field must start with "${prefix}"`,
|
|
355
|
-
type: 'startsWith'
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
return null;
|
|
359
|
-
};
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
endsWith: (suffix, message) => {
|
|
363
|
-
return (value, field) => {
|
|
364
|
-
if (typeof value === 'string' && !value.endsWith(suffix)) {
|
|
365
|
-
return {
|
|
366
|
-
field,
|
|
367
|
-
message: message || `Field must end with "${suffix}"`,
|
|
368
|
-
type: 'endsWith'
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
function validate(schema) {
|
|
377
|
-
return (req, res, next) => {
|
|
378
|
-
try {
|
|
379
|
-
schema.validate(req.body);
|
|
380
|
-
next();
|
|
381
|
-
} catch (error) {
|
|
382
|
-
if (error instanceof ValidationError) {
|
|
383
|
-
return res.status(400).json({
|
|
384
|
-
success: false,
|
|
385
|
-
message: 'Validation failed',
|
|
386
|
-
errors: error.errors
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
throw error;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
6
|
+
const {
|
|
7
|
+
Schema,
|
|
8
|
+
ValidationError,
|
|
9
|
+
validators,
|
|
10
|
+
validate,
|
|
11
|
+
validatePartial
|
|
12
|
+
} = require('./lib/schema');
|
|
393
13
|
|
|
394
|
-
|
|
395
|
-
const partial = {};
|
|
396
|
-
|
|
397
|
-
for (const field in schema.fields) {
|
|
398
|
-
const rules = schema.fields[field];
|
|
399
|
-
|
|
400
|
-
const filtered = rules.filter(v =>
|
|
401
|
-
v.name !== 'required' &&
|
|
402
|
-
v.name !== 'requiredTrue' &&
|
|
403
|
-
v.name !== 'mustBeTrue'
|
|
404
|
-
);
|
|
405
|
-
partial[field] = [validators.optional(), ...filtered];
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return new Schema(partial);
|
|
409
|
-
}
|
|
14
|
+
process.env.UV_THREADPOOL_SIZE = require('os').availableParallelism();
|
|
410
15
|
|
|
411
16
|
class LiekoExpress {
|
|
412
17
|
constructor() {
|
|
@@ -424,10 +29,11 @@ class LiekoExpress {
|
|
|
424
29
|
strictTrailingSlash: true,
|
|
425
30
|
allowTrailingSlash: true,
|
|
426
31
|
views: path.join(process.cwd(), "views"),
|
|
427
|
-
"view engine":
|
|
32
|
+
"view engine": "html"
|
|
428
33
|
};
|
|
429
34
|
|
|
430
35
|
this.engines = {};
|
|
36
|
+
this.engines['.html'] = this._defaultHtmlEngine.bind(this);
|
|
431
37
|
|
|
432
38
|
this.bodyParserOptions = {
|
|
433
39
|
json: {
|
|
@@ -551,6 +157,33 @@ class LiekoExpress {
|
|
|
551
157
|
}
|
|
552
158
|
}
|
|
553
159
|
|
|
160
|
+
_logCorsDebug(req, opts) {
|
|
161
|
+
if (!opts.debug) return;
|
|
162
|
+
|
|
163
|
+
console.log("\n[CORS DEBUG]");
|
|
164
|
+
console.log("Request:", req.method, req.url);
|
|
165
|
+
console.log("Origin:", req.headers.origin || "none");
|
|
166
|
+
|
|
167
|
+
console.log("Applied CORS Policy:");
|
|
168
|
+
console.log(" - Access-Control-Allow-Origin:", opts.origin);
|
|
169
|
+
console.log(" - Access-Control-Allow-Methods:", opts.methods.join(", "));
|
|
170
|
+
console.log(" - Access-Control-Allow-Headers:", opts.headers.join(", "));
|
|
171
|
+
|
|
172
|
+
if (opts.credentials) {
|
|
173
|
+
console.log(" - Access-Control-Allow-Credentials: true");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (opts.exposedHeaders?.length) {
|
|
177
|
+
console.log(" - Access-Control-Expose-Headers:", opts.exposedHeaders.join(", "));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(" - Max-Age:", opts.maxAge);
|
|
181
|
+
|
|
182
|
+
if (req.method === "OPTIONS") {
|
|
183
|
+
console.log("Preflight request handled with status 204\n");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
554
187
|
debug(value = true) {
|
|
555
188
|
if (typeof value === 'string') {
|
|
556
189
|
value = value.toLowerCase() === 'true';
|
|
@@ -1202,7 +835,7 @@ ${cyan} (req, res, next) => {
|
|
|
1202
835
|
filePath = indexPath;
|
|
1203
836
|
break;
|
|
1204
837
|
}
|
|
1205
|
-
} catch (e) {}
|
|
838
|
+
} catch (e) { }
|
|
1206
839
|
}
|
|
1207
840
|
}
|
|
1208
841
|
|
|
@@ -1215,7 +848,7 @@ ${cyan} (req, res, next) => {
|
|
|
1215
848
|
filePath = testPath;
|
|
1216
849
|
found = true;
|
|
1217
850
|
break;
|
|
1218
|
-
} catch (e) {}
|
|
851
|
+
} catch (e) { }
|
|
1219
852
|
}
|
|
1220
853
|
if (!found) return next();
|
|
1221
854
|
} else if (!stats) {
|
|
@@ -1242,7 +875,7 @@ ${cyan} (req, res, next) => {
|
|
|
1242
875
|
stats = indexStats;
|
|
1243
876
|
break;
|
|
1244
877
|
}
|
|
1245
|
-
} catch (e) {}
|
|
878
|
+
} catch (e) { }
|
|
1246
879
|
}
|
|
1247
880
|
|
|
1248
881
|
if (stats.isDirectory()) {
|
|
@@ -1352,6 +985,10 @@ ${cyan} (req, res, next) => {
|
|
|
1352
985
|
}
|
|
1353
986
|
|
|
1354
987
|
const finalHandler = handlers[handlers.length - 1];
|
|
988
|
+
if (!finalHandler) {
|
|
989
|
+
throw new Error(`Route handler is undefined for ${method} ${path}`);
|
|
990
|
+
}
|
|
991
|
+
|
|
1355
992
|
const routeMiddlewares = handlers.slice(0, -1);
|
|
1356
993
|
|
|
1357
994
|
routeMiddlewares.forEach(mw => {
|
|
@@ -1363,7 +1000,7 @@ ${cyan} (req, res, next) => {
|
|
|
1363
1000
|
const paths = Array.isArray(path) ? path : [path];
|
|
1364
1001
|
|
|
1365
1002
|
paths.forEach(original => {
|
|
1366
|
-
let p = String(original).trim();
|
|
1003
|
+
let p = String(original).trim();
|
|
1367
1004
|
p = p.replace(/\/+/g, '/');
|
|
1368
1005
|
|
|
1369
1006
|
if (p !== '/' && p.endsWith('/')) {
|
|
@@ -1383,7 +1020,7 @@ ${cyan} (req, res, next) => {
|
|
|
1383
1020
|
path: p,
|
|
1384
1021
|
originalPath: original,
|
|
1385
1022
|
handler: finalHandler,
|
|
1386
|
-
handlerName: finalHandler.name || 'anonymous',
|
|
1023
|
+
handlerName: (finalHandler && finalHandler.name) || 'anonymous',
|
|
1387
1024
|
middlewares: routeMiddlewares,
|
|
1388
1025
|
pattern: this._pathToRegex(p),
|
|
1389
1026
|
allowTrailingSlash: this.settings.allowTrailingSlash ?? false,
|
|
@@ -1447,7 +1084,6 @@ ${cyan} (req, res, next) => {
|
|
|
1447
1084
|
}
|
|
1448
1085
|
}
|
|
1449
1086
|
}
|
|
1450
|
-
|
|
1451
1087
|
return null;
|
|
1452
1088
|
}
|
|
1453
1089
|
|
|
@@ -1780,6 +1416,16 @@ ${cyan} (req, res, next) => {
|
|
|
1780
1416
|
|
|
1781
1417
|
req.originalUrl = req.url;
|
|
1782
1418
|
req.xhr = (req.headers['x-requested-with'] || '').toLowerCase() === 'xmlhttprequest';
|
|
1419
|
+
|
|
1420
|
+
req.get = (name) => {
|
|
1421
|
+
if (typeof name !== 'string') return undefined;
|
|
1422
|
+
const lower = name.toLowerCase();
|
|
1423
|
+
for (const key in req.headers) {
|
|
1424
|
+
if (key.toLowerCase() === lower) return req.headers[key];
|
|
1425
|
+
}
|
|
1426
|
+
return undefined;
|
|
1427
|
+
};
|
|
1428
|
+
req.header = req.get;
|
|
1783
1429
|
}
|
|
1784
1430
|
|
|
1785
1431
|
_enhanceResponse(req, res) {
|
|
@@ -1862,35 +1508,39 @@ ${cyan} (req, res, next) => {
|
|
|
1862
1508
|
if (!ext) {
|
|
1863
1509
|
ext = this.settings['view engine'];
|
|
1864
1510
|
if (!ext) {
|
|
1865
|
-
|
|
1511
|
+
ext = '.html';
|
|
1512
|
+
viewPath = view + ext;
|
|
1513
|
+
} else {
|
|
1514
|
+
if (!ext.startsWith('.')) ext = '.' + ext;
|
|
1515
|
+
viewPath = view + ext;
|
|
1866
1516
|
}
|
|
1867
|
-
if (!ext.startsWith('.')) ext = '.' + ext;
|
|
1868
|
-
viewPath = view + ext;
|
|
1869
1517
|
}
|
|
1870
1518
|
|
|
1871
1519
|
const viewsDir = this.settings.views || path.join(process.cwd(), 'views');
|
|
1872
1520
|
let fullPath = path.join(viewsDir, viewPath);
|
|
1873
|
-
|
|
1874
1521
|
let fileExists = false;
|
|
1875
1522
|
try {
|
|
1876
1523
|
await fs.promises.access(fullPath);
|
|
1877
1524
|
fileExists = true;
|
|
1878
1525
|
} catch (err) {
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1526
|
+
const extensions = ['.html', '.ejs', '.pug', '.hbs'];
|
|
1527
|
+
for (const tryExt of extensions) {
|
|
1528
|
+
if (tryExt === ext) continue;
|
|
1529
|
+
const tryPath = fullPath.replace(new RegExp(ext.replace('.', '\\.') + '$'), tryExt);
|
|
1530
|
+
try {
|
|
1531
|
+
await fs.promises.access(tryPath);
|
|
1532
|
+
fullPath = tryPath;
|
|
1533
|
+
ext = tryExt;
|
|
1534
|
+
fileExists = true;
|
|
1535
|
+
break;
|
|
1536
|
+
} catch (err2) { }
|
|
1886
1537
|
}
|
|
1887
1538
|
}
|
|
1888
1539
|
|
|
1889
1540
|
if (!fileExists) {
|
|
1890
1541
|
const error = new Error(
|
|
1891
|
-
`View "${view}" not found
|
|
1892
|
-
|
|
1893
|
-
`- ${fullPath.replace(new RegExp(ext.replace('.', '\\.') + '$'), '.html')}`
|
|
1542
|
+
`View "${view}" not found in views directory "${viewsDir}".\n` +
|
|
1543
|
+
`Tried: ${fullPath}`
|
|
1894
1544
|
);
|
|
1895
1545
|
error.code = 'ENOENT';
|
|
1896
1546
|
if (callback) return callback(error);
|
|
@@ -1900,24 +1550,38 @@ ${cyan} (req, res, next) => {
|
|
|
1900
1550
|
const renderEngine = this.engines[ext];
|
|
1901
1551
|
|
|
1902
1552
|
if (!renderEngine) {
|
|
1903
|
-
throw new Error(
|
|
1553
|
+
throw new Error(
|
|
1554
|
+
`No engine registered for extension "${ext}".\n` +
|
|
1555
|
+
`Use app.engine("${ext}", renderFunction) to register one.`
|
|
1556
|
+
);
|
|
1904
1557
|
}
|
|
1905
1558
|
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (
|
|
1909
|
-
|
|
1910
|
-
|
|
1559
|
+
return new Promise((resolve, reject) => {
|
|
1560
|
+
renderEngine(fullPath, locals, (err, html) => {
|
|
1561
|
+
if (err) {
|
|
1562
|
+
if (callback) {
|
|
1563
|
+
callback(err);
|
|
1564
|
+
resolve();
|
|
1565
|
+
} else {
|
|
1566
|
+
reject(err);
|
|
1567
|
+
}
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1911
1570
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1571
|
+
if (callback) {
|
|
1572
|
+
callback(null, html);
|
|
1573
|
+
resolve();
|
|
1574
|
+
} else {
|
|
1575
|
+
/*
|
|
1576
|
+
HBS cause error header already sent here ??
|
|
1577
|
+
*/
|
|
1578
|
+
//res.statusCode = statusCode || 200;
|
|
1579
|
+
//res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
1580
|
+
//responseSent = true;
|
|
1581
|
+
res.html(html);
|
|
1582
|
+
resolve();
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1921
1585
|
});
|
|
1922
1586
|
|
|
1923
1587
|
} catch (error) {
|
|
@@ -1929,17 +1593,15 @@ ${cyan} (req, res, next) => {
|
|
|
1929
1593
|
}
|
|
1930
1594
|
};
|
|
1931
1595
|
|
|
1932
|
-
|
|
1933
1596
|
res.json = (data) => {
|
|
1934
1597
|
if (responseSent) return res;
|
|
1935
1598
|
|
|
1936
1599
|
const json = JSON.stringify(data);
|
|
1937
1600
|
const length = Buffer.byteLength(json);
|
|
1938
1601
|
|
|
1939
|
-
res.writeHead(statusCode, buildHeaders('application/json; charset=utf-8', length));
|
|
1602
|
+
res.writeHead(statusCode || 200, buildHeaders('application/json; charset=utf-8', length));
|
|
1940
1603
|
|
|
1941
1604
|
responseSent = true;
|
|
1942
|
-
statusCode = 200;
|
|
1943
1605
|
return res.end(json);
|
|
1944
1606
|
};
|
|
1945
1607
|
|
|
@@ -1964,15 +1626,14 @@ ${cyan} (req, res, next) => {
|
|
|
1964
1626
|
|
|
1965
1627
|
const length = Buffer.byteLength(body);
|
|
1966
1628
|
|
|
1967
|
-
res.writeHead(statusCode, buildHeaders(contentType, length));
|
|
1629
|
+
res.writeHead(statusCode || 200, buildHeaders(contentType, length));
|
|
1968
1630
|
|
|
1969
1631
|
responseSent = true;
|
|
1970
|
-
statusCode = 200;
|
|
1971
1632
|
return res.end(body);
|
|
1972
1633
|
};
|
|
1973
1634
|
|
|
1974
|
-
res.html = function (html, status
|
|
1975
|
-
res.statusCode = status;
|
|
1635
|
+
res.html = function (html, status) {
|
|
1636
|
+
res.statusCode = status !== undefined ? status : (statusCode || 200);
|
|
1976
1637
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
1977
1638
|
res.end(html);
|
|
1978
1639
|
};
|
|
@@ -2067,6 +1728,42 @@ ${cyan} (req, res, next) => {
|
|
|
2067
1728
|
};
|
|
2068
1729
|
}
|
|
2069
1730
|
|
|
1731
|
+
_defaultHtmlEngine(filePath, locals, callback) {
|
|
1732
|
+
fs.readFile(filePath, 'utf-8', (err, content) => {
|
|
1733
|
+
if (err) return callback(err);
|
|
1734
|
+
|
|
1735
|
+
let rendered = content;
|
|
1736
|
+
|
|
1737
|
+
Object.keys(locals).forEach(key => {
|
|
1738
|
+
if (locals[key] !== undefined && locals[key] !== null) {
|
|
1739
|
+
const safeRegex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
|
1740
|
+
const unsafeRegex = new RegExp(`{{{\\s*${key}\\s*}}}`, 'g');
|
|
1741
|
+
|
|
1742
|
+
if (safeRegex.test(rendered)) {
|
|
1743
|
+
const escaped = this._escapeHtml(String(locals[key]));
|
|
1744
|
+
rendered = rendered.replace(safeRegex, escaped);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
if (unsafeRegex.test(rendered)) {
|
|
1748
|
+
rendered = rendered.replace(unsafeRegex, String(locals[key]));
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
callback(null, rendered);
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
_escapeHtml(text) {
|
|
1758
|
+
if (typeof text !== 'string') return text;
|
|
1759
|
+
return text
|
|
1760
|
+
.replace(/&/g, '&')
|
|
1761
|
+
.replace(/</g, '<')
|
|
1762
|
+
.replace(/>/g, '>')
|
|
1763
|
+
.replace(/"/g, '"')
|
|
1764
|
+
.replace(/'/g, ''');
|
|
1765
|
+
}
|
|
1766
|
+
|
|
2070
1767
|
async _runMiddleware(handler, req, res) {
|
|
2071
1768
|
return new Promise((resolve, reject) => {
|
|
2072
1769
|
const next = (err) => err ? reject(err) : resolve();
|
|
@@ -2144,34 +1841,7 @@ ${cyan} (req, res, next) => {
|
|
|
2144
1841
|
logLines.push('---------------------------------------------');
|
|
2145
1842
|
console.log('\n' + logLines.join('\n') + '\n');
|
|
2146
1843
|
}
|
|
2147
|
-
|
|
2148
|
-
_logCorsDebug(req, opts) {
|
|
2149
|
-
if (!opts.debug) return;
|
|
2150
|
-
|
|
2151
|
-
console.log("\n[CORS DEBUG]");
|
|
2152
|
-
console.log("Request:", req.method, req.url);
|
|
2153
|
-
console.log("Origin:", req.headers.origin || "none");
|
|
2154
|
-
|
|
2155
|
-
console.log("Applied CORS Policy:");
|
|
2156
|
-
console.log(" - Access-Control-Allow-Origin:", opts.origin);
|
|
2157
|
-
console.log(" - Access-Control-Allow-Methods:", opts.methods.join(", "));
|
|
2158
|
-
console.log(" - Access-Control-Allow-Headers:", opts.headers.join(", "));
|
|
2159
|
-
|
|
2160
|
-
if (opts.credentials) {
|
|
2161
|
-
console.log(" - Access-Control-Allow-Credentials: true");
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
if (opts.exposedHeaders?.length) {
|
|
2165
|
-
console.log(" - Access-Control-Expose-Headers:", opts.exposedHeaders.join(", "));
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
console.log(" - Max-Age:", opts.maxAge);
|
|
2169
|
-
|
|
2170
|
-
if (req.method === "OPTIONS") {
|
|
2171
|
-
console.log("Preflight request handled with status 204\n");
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
|
|
1844
|
+
|
|
2175
1845
|
listRoutes() {
|
|
2176
1846
|
const routeEntries = [];
|
|
2177
1847
|
|
|
@@ -2242,6 +1912,7 @@ ${cyan} (req, res, next) => {
|
|
|
2242
1912
|
console.log(` \x1b[36m${r.method.padEnd(7)}\x1b[0m \x1b[33m${pathStr}\x1b[0m \x1b[90m(mw: ${r.mw})\x1b[0m`);
|
|
2243
1913
|
}
|
|
2244
1914
|
}
|
|
1915
|
+
|
|
2245
1916
|
listen() {
|
|
2246
1917
|
const args = Array.from(arguments);
|
|
2247
1918
|
const server = createServer(this._handleRequest.bind(this));
|
|
@@ -2262,13 +1933,10 @@ function Router() {
|
|
|
2262
1933
|
|
|
2263
1934
|
module.exports = Lieko;
|
|
2264
1935
|
module.exports.Router = Router;
|
|
1936
|
+
|
|
2265
1937
|
module.exports.Schema = Schema;
|
|
2266
|
-
module.exports.
|
|
1938
|
+
module.exports.createSchema = (...args) => new Schema(...args);
|
|
2267
1939
|
module.exports.validators = validators;
|
|
2268
1940
|
module.exports.validate = validate;
|
|
2269
1941
|
module.exports.validatePartial = validatePartial;
|
|
2270
1942
|
module.exports.ValidationError = ValidationError;
|
|
2271
|
-
module.exports.static = function (root, options) {
|
|
2272
|
-
const app = new LiekoExpress();
|
|
2273
|
-
return app.static(root, options);
|
|
2274
|
-
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lieko-express",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/eiwSrvt/lieko-express"
|
|
7
7
|
},
|
|
8
8
|
"homepage": "https://github.com/eiwSrvt/lieko-express",
|
|
9
|
-
"description": "Lieko-express — A Modern, Minimal,
|
|
9
|
+
"description": "Lieko-express — A Modern, Minimal, express-like Framework for Node.js",
|
|
10
10
|
"main": "lieko-express.js",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "echo \"Error: no test specified\" && exit 1"
|