@ylsoo/core 1.1.0 → 2.0.1
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/README.md +50 -67
- package/index.d.ts +100 -49
- package/package.json +12 -8
- package/src/core/analytics.js +66 -0
- package/src/core/cache.js +31 -0
- package/src/core/i18n.js +42 -0
- package/src/core/logger.js +39 -0
- package/src/core/state.js +67 -0
- package/src/core/storage.js +99 -0
- package/src/engine/config.js +54 -0
- package/src/engine/features.js +59 -0
- package/src/events/bus.js +69 -0
- package/src/http/client.js +49 -0
- package/src/index.js +68 -0
- package/src/resilience/breaker.js +73 -0
- package/src/router/domRouter.js +131 -0
- package/src/security/crypto.js +67 -0
- package/src/utils/helpers.js +43 -0
- package/src/utils/queue.js +50 -0
- package/src/utils/time.js +46 -0
- package/src/validation/schema.js +53 -0
- package/index.js +0 -202
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class YlsooHelpers {
|
|
2
|
+
sleep(ms) {
|
|
3
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
deepClone(obj) {
|
|
7
|
+
if (typeof structuredClone === 'function') {
|
|
8
|
+
return structuredClone(obj);
|
|
9
|
+
}
|
|
10
|
+
return JSON.parse(JSON.stringify(obj));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
debounce(func, wait) {
|
|
14
|
+
let timeout;
|
|
15
|
+
return function executedFunction(...args) {
|
|
16
|
+
const later = () => {
|
|
17
|
+
clearTimeout(timeout);
|
|
18
|
+
func(...args);
|
|
19
|
+
};
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
timeout = setTimeout(later, wait);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
isEmpty(value) {
|
|
26
|
+
if (value === null || value === undefined) return true;
|
|
27
|
+
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
|
|
28
|
+
if (typeof value === 'object') {
|
|
29
|
+
if (value instanceof Map || value instanceof Set) return value.size === 0;
|
|
30
|
+
return Object.keys(value).length === 0;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
capitalize(str) {
|
|
36
|
+
if (typeof str !== 'string' || str.length === 0) {
|
|
37
|
+
return str;
|
|
38
|
+
}
|
|
39
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { YlsooHelpers };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class YlsooTaskQueue {
|
|
2
|
+
/**
|
|
3
|
+
* Limit concurrency of massive promises automatically
|
|
4
|
+
* @param {number} concurrencyLimit
|
|
5
|
+
*/
|
|
6
|
+
constructor(concurrencyLimit = 5) {
|
|
7
|
+
this.concurrencyLimit = concurrencyLimit;
|
|
8
|
+
this.activeCount = 0;
|
|
9
|
+
this.queue = [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add a function that returns a Promise to the queue
|
|
14
|
+
* @param {Function} asyncTask
|
|
15
|
+
* @returns {Promise<*>}
|
|
16
|
+
*/
|
|
17
|
+
add(asyncTask) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
this.queue.push({
|
|
20
|
+
task: asyncTask,
|
|
21
|
+
resolve,
|
|
22
|
+
reject
|
|
23
|
+
});
|
|
24
|
+
this._next();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get remaining() {
|
|
29
|
+
return this.queue.length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_next() {
|
|
33
|
+
if (this.activeCount >= this.concurrencyLimit || this.queue.length === 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.activeCount++;
|
|
38
|
+
const { task, resolve, reject } = this.queue.shift();
|
|
39
|
+
|
|
40
|
+
task()
|
|
41
|
+
.then(resolve)
|
|
42
|
+
.catch(reject)
|
|
43
|
+
.finally(() => {
|
|
44
|
+
this.activeCount--;
|
|
45
|
+
this._next();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { YlsooTaskQueue };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class YlsooTime {
|
|
2
|
+
/**
|
|
3
|
+
* Extremely lightweight date formatter (replaces heavy Moment.js)
|
|
4
|
+
* @param {Date|number|string} date
|
|
5
|
+
* @param {string} formatStr (e.g. "YYYY-MM-DD HH:mm:ss")
|
|
6
|
+
*/
|
|
7
|
+
format(date, formatStr = "YYYY-MM-DD") {
|
|
8
|
+
const d = new Date(date);
|
|
9
|
+
if (isNaN(d.getTime())) return null;
|
|
10
|
+
|
|
11
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
12
|
+
|
|
13
|
+
return formatStr
|
|
14
|
+
.replace('YYYY', d.getFullYear())
|
|
15
|
+
.replace('YY', String(d.getFullYear()).slice(-2))
|
|
16
|
+
.replace('MM', pad(d.getMonth() + 1))
|
|
17
|
+
.replace('DD', pad(d.getDate()))
|
|
18
|
+
.replace('HH', pad(d.getHours()))
|
|
19
|
+
.replace('mm', pad(d.getMinutes()))
|
|
20
|
+
.replace('ss', pad(d.getSeconds()));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Fast "time ago" algorithm
|
|
25
|
+
* @param {Date|number|string} date
|
|
26
|
+
*/
|
|
27
|
+
timeAgo(date) {
|
|
28
|
+
const d = new Date(date);
|
|
29
|
+
const seconds = Math.floor((new Date() - d) / 1000);
|
|
30
|
+
|
|
31
|
+
let interval = Math.floor(seconds / 31536000);
|
|
32
|
+
if (interval >= 1) return interval + " year" + (interval > 1 ? "s" : "") + " ago";
|
|
33
|
+
interval = Math.floor(seconds / 2592000);
|
|
34
|
+
if (interval >= 1) return interval + " month" + (interval > 1 ? "s" : "") + " ago";
|
|
35
|
+
interval = Math.floor(seconds / 86400);
|
|
36
|
+
if (interval >= 1) return interval + " day" + (interval > 1 ? "s" : "") + " ago";
|
|
37
|
+
interval = Math.floor(seconds / 3600);
|
|
38
|
+
if (interval >= 1) return interval + " hour" + (interval > 1 ? "s" : "") + " ago";
|
|
39
|
+
interval = Math.floor(seconds / 60);
|
|
40
|
+
if (interval >= 1) return interval + " minute" + (interval > 1 ? "s" : "") + " ago";
|
|
41
|
+
|
|
42
|
+
return Math.floor(seconds) + " seconds ago";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { YlsooTime };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
class YlsooValidator {
|
|
2
|
+
/**
|
|
3
|
+
* Validate an object against a strict schema mapping
|
|
4
|
+
* @param {*} data
|
|
5
|
+
* @param {object} schema
|
|
6
|
+
* @throws Error on validation failure
|
|
7
|
+
* @returns {boolean} true if valid
|
|
8
|
+
*/
|
|
9
|
+
validate(data, schema) {
|
|
10
|
+
if (typeof data !== 'object' || data === null) {
|
|
11
|
+
throw new Error('[Ylsoo Validation] Target is not an object.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (const [key, rules] of Object.entries(schema)) {
|
|
15
|
+
const value = data[key];
|
|
16
|
+
|
|
17
|
+
// Handle raw type strings (e.g. { age: 'number' })
|
|
18
|
+
const type = typeof rules === 'string' ? rules : rules.type;
|
|
19
|
+
|
|
20
|
+
// Check required
|
|
21
|
+
const isRequired = typeof rules === 'object' ? rules.required !== false : true;
|
|
22
|
+
if (value === undefined || value === null) {
|
|
23
|
+
if (isRequired) throw new Error(`[Ylsoo Validation] Missing required key: ${key}`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check Types
|
|
28
|
+
if (type === 'array') {
|
|
29
|
+
if (!Array.isArray(value)) throw new Error(`[Ylsoo Validation] Type mismatch. ${key} must be an array.`);
|
|
30
|
+
} else if (type === 'email') {
|
|
31
|
+
if (typeof value !== 'string' || !/^\S+@\S+\.\S+$/.test(value)) {
|
|
32
|
+
throw new Error(`[Ylsoo Validation] Type mismatch. ${key} must be a valid email.`);
|
|
33
|
+
}
|
|
34
|
+
} else if (type === 'object') {
|
|
35
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
36
|
+
throw new Error(`[Ylsoo Validation] Type mismatch. ${key} must be an object.`);
|
|
37
|
+
}
|
|
38
|
+
// Recursive dive
|
|
39
|
+
if (rules.schema) {
|
|
40
|
+
this.validate(value, rules.schema);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
if (typeof value !== type) {
|
|
44
|
+
throw new Error(`[Ylsoo Validation] Type mismatch. ${key} must be a ${type}.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { YlsooValidator };
|
package/index.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @ylsoo/core
|
|
3
|
-
* Premium utility functions for Ylsoo projects
|
|
4
|
-
* Zero dependency, extremely fast, robust.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
class YlsooLogger {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.colors = {
|
|
10
|
-
reset: "\x1b[0m",
|
|
11
|
-
info: "\x1b[36m", // Cyan
|
|
12
|
-
success: "\x1b[32m", // Green
|
|
13
|
-
warn: "\x1b[33m", // Yellow
|
|
14
|
-
error: "\x1b[31m", // Red
|
|
15
|
-
ylsoo: "\x1b[35m" // Magenta
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
_format(level, message, color) {
|
|
20
|
-
const time = new Date().toISOString().split('T')[1].slice(0, -1);
|
|
21
|
-
return `${this.colors.ylsoo}[Ylsoo]${this.colors.reset} ${color}[${level.toUpperCase()}]${this.colors.reset} ${message}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
info(message) {
|
|
25
|
-
console.log(this._format('info', message, this.colors.info));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
success(message) {
|
|
29
|
-
console.log(this._format('success', message, this.colors.success));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
warn(message) {
|
|
33
|
-
console.warn(this._format('warn', message, this.colors.warn));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
error(message) {
|
|
37
|
-
console.error(this._format('error', message, this.colors.error));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class YlsooCache {
|
|
42
|
-
constructor() {
|
|
43
|
-
this.store = new Map();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Set a key-value pair with an optional Time-To-Live
|
|
48
|
-
* @param {string} key
|
|
49
|
-
* @param {*} value
|
|
50
|
-
* @param {number} ttlMs - Time to live in milliseconds
|
|
51
|
-
*/
|
|
52
|
-
set(key, value, ttlMs = 0) {
|
|
53
|
-
const expiresAt = ttlMs > 0 ? Date.now() + ttlMs : null;
|
|
54
|
-
this.store.set(key, { value, expiresAt });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get(key) {
|
|
58
|
-
const item = this.store.get(key);
|
|
59
|
-
if (!item) return null;
|
|
60
|
-
|
|
61
|
-
if (item.expiresAt && Date.now() > item.expiresAt) {
|
|
62
|
-
this.store.delete(key);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return item.value;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
delete(key) {
|
|
69
|
-
return this.store.delete(key);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
clear() {
|
|
73
|
-
this.store.clear();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
class YlsooHttp {
|
|
78
|
-
/**
|
|
79
|
-
* Premium wrapper for fetch
|
|
80
|
-
* @param {string} endpoint
|
|
81
|
-
* @param {object} options
|
|
82
|
-
* @returns {Promise<any>}
|
|
83
|
-
*/
|
|
84
|
-
async request(endpoint, options = {}) {
|
|
85
|
-
const defaultHeaders = {
|
|
86
|
-
'Content-Type': 'application/json',
|
|
87
|
-
'User-Agent': 'Ylsoo-Core/1.1'
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const config = {
|
|
91
|
-
...options,
|
|
92
|
-
headers: {
|
|
93
|
-
...defaultHeaders,
|
|
94
|
-
...options.headers
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
if (config.body && typeof config.body === 'object') {
|
|
99
|
-
config.body = JSON.stringify(config.body);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const response = await fetch(endpoint, config);
|
|
104
|
-
const isJson = response.headers.get('content-type')?.includes('application/json');
|
|
105
|
-
|
|
106
|
-
const data = isJson ? await response.json() : await response.text();
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
throw {
|
|
110
|
-
status: response.status,
|
|
111
|
-
message: response.statusText,
|
|
112
|
-
data
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return data;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
throw new Error(`[Ylsoo HTTP Error]: ${error.message || JSON.stringify(error)}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
get(endpoint, headers = {}) {
|
|
123
|
-
return this.request(endpoint, { method: 'GET', headers });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
post(endpoint, body, headers = {}) {
|
|
127
|
-
return this.request(endpoint, { method: 'POST', body, headers });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
class YlsooCore {
|
|
132
|
-
constructor() {
|
|
133
|
-
this.version = '1.1.0';
|
|
134
|
-
this.logger = new YlsooLogger();
|
|
135
|
-
this.cache = new YlsooCache();
|
|
136
|
-
this.http = new YlsooHttp();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Sleep for a given amount of time (Promise based)
|
|
141
|
-
* @param {number} ms
|
|
142
|
-
*/
|
|
143
|
-
sleep(ms) {
|
|
144
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Deep clone an object natively
|
|
149
|
-
* @param {object} obj
|
|
150
|
-
*/
|
|
151
|
-
deepClone(obj) {
|
|
152
|
-
if (typeof structuredClone === 'function') {
|
|
153
|
-
return structuredClone(obj);
|
|
154
|
-
}
|
|
155
|
-
return JSON.parse(JSON.stringify(obj));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Creates a debounced function that delays invoking func until after wait milliseconds have elapsed
|
|
160
|
-
* @param {Function} func
|
|
161
|
-
* @param {number} wait
|
|
162
|
-
*/
|
|
163
|
-
debounce(func, wait) {
|
|
164
|
-
let timeout;
|
|
165
|
-
return function executedFunction(...args) {
|
|
166
|
-
const later = () => {
|
|
167
|
-
clearTimeout(timeout);
|
|
168
|
-
func(...args);
|
|
169
|
-
};
|
|
170
|
-
clearTimeout(timeout);
|
|
171
|
-
timeout = setTimeout(later, wait);
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Simple utility to verify if a value is semantically empty
|
|
177
|
-
* @param {*} value
|
|
178
|
-
*/
|
|
179
|
-
isEmpty(value) {
|
|
180
|
-
if (value === null || value === undefined) return true;
|
|
181
|
-
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
|
|
182
|
-
if (typeof value === 'object') {
|
|
183
|
-
if (value instanceof Map || value instanceof Set) return value.size === 0;
|
|
184
|
-
return Object.keys(value).length === 0;
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Simple utility to capitalize a string
|
|
191
|
-
* @param {string} str - The string to capitalize
|
|
192
|
-
* @returns {string} Capitalized string
|
|
193
|
-
*/
|
|
194
|
-
capitalize(str) {
|
|
195
|
-
if (typeof str !== 'string' || str.length === 0) {
|
|
196
|
-
return str;
|
|
197
|
-
}
|
|
198
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
module.exports = new YlsooCore();
|