sehawq.db 4.0.3 → 4.0.5
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/.github/workflows/npm-publish.yml +30 -30
- package/LICENSE +21 -21
- package/index.js +1 -1
- package/package.json +36 -36
- package/readme.md +413 -413
- package/src/core/Database.js +294 -294
- package/src/core/Events.js +285 -285
- package/src/core/IndexManager.js +813 -813
- package/src/core/Persistence.js +375 -375
- package/src/core/QueryEngine.js +447 -447
- package/src/core/Storage.js +321 -321
- package/src/core/Validator.js +324 -324
- package/src/index.js +115 -115
- package/src/performance/Cache.js +338 -338
- package/src/performance/LazyLoader.js +354 -354
- package/src/performance/MemoryManager.js +495 -495
- package/src/server/api.js +687 -687
- package/src/server/websocket.js +527 -527
- package/src/utils/benchmark.js +51 -51
- package/src/utils/dot-notation.js +247 -247
- package/src/utils/helpers.js +275 -275
- package/src/utils/profiler.js +70 -70
- package/src/version.js +37 -37
package/src/utils/helpers.js
CHANGED
|
@@ -1,276 +1,276 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helper Functions - The Swiss Army knife of utilities 🔧
|
|
3
|
-
*
|
|
4
|
-
* Random useful stuff that makes life easier
|
|
5
|
-
* Collected from years of "oh, I need that again" moments
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
class Helpers {
|
|
9
|
-
/**
|
|
10
|
-
* Deep clone an object
|
|
11
|
-
*/
|
|
12
|
-
static deepClone(obj) {
|
|
13
|
-
if (obj === null || typeof obj !== 'object') return obj;
|
|
14
|
-
if (obj instanceof Date) return new Date(obj.getTime());
|
|
15
|
-
if (obj instanceof Array) return obj.map(item => Helpers.deepClone(item));
|
|
16
|
-
|
|
17
|
-
const cloned = {};
|
|
18
|
-
for (const key in obj) {
|
|
19
|
-
if (obj.hasOwnProperty(key)) {
|
|
20
|
-
cloned[key] = Helpers.deepClone(obj[key]);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return cloned;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Deep merge objects
|
|
28
|
-
*/
|
|
29
|
-
static deepMerge(target, ...sources) {
|
|
30
|
-
if (!sources.length) return target;
|
|
31
|
-
const source = sources.shift();
|
|
32
|
-
|
|
33
|
-
if (Helpers.isObject(target) && Helpers.isObject(source)) {
|
|
34
|
-
for (const key in source) {
|
|
35
|
-
if (Helpers.isObject(source[key])) {
|
|
36
|
-
if (!target[key]) Object.assign(target, { [key]: {} });
|
|
37
|
-
Helpers.deepMerge(target[key], source[key]);
|
|
38
|
-
} else {
|
|
39
|
-
Object.assign(target, { [key]: source[key] });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return Helpers.deepMerge(target, ...sources);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if value is an object
|
|
49
|
-
*/
|
|
50
|
-
static isObject(item) {
|
|
51
|
-
return item && typeof item === 'object' && !Array.isArray(item);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Generate random ID
|
|
56
|
-
*/
|
|
57
|
-
static generateId(length = 16) {
|
|
58
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
59
|
-
let result = '';
|
|
60
|
-
for (let i = 0; i < length; i++) {
|
|
61
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
62
|
-
}
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Sleep/delay function
|
|
68
|
-
*/
|
|
69
|
-
static sleep(ms) {
|
|
70
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Debounce function
|
|
75
|
-
*/
|
|
76
|
-
static debounce(func, wait, immediate = false) {
|
|
77
|
-
let timeout;
|
|
78
|
-
return function executedFunction(...args) {
|
|
79
|
-
const later = () => {
|
|
80
|
-
timeout = null;
|
|
81
|
-
if (!immediate) func(...args);
|
|
82
|
-
};
|
|
83
|
-
const callNow = immediate && !timeout;
|
|
84
|
-
clearTimeout(timeout);
|
|
85
|
-
timeout = setTimeout(later, wait);
|
|
86
|
-
if (callNow) func(...args);
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Throttle function
|
|
92
|
-
*/
|
|
93
|
-
static throttle(func, limit) {
|
|
94
|
-
let inThrottle;
|
|
95
|
-
return function(...args) {
|
|
96
|
-
if (!inThrottle) {
|
|
97
|
-
func.apply(this, args);
|
|
98
|
-
inThrottle = true;
|
|
99
|
-
setTimeout(() => inThrottle = false, limit);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Check if value is empty
|
|
106
|
-
*/
|
|
107
|
-
static isEmpty(value) {
|
|
108
|
-
if (value === null || value === undefined) return true;
|
|
109
|
-
if (typeof value === 'string') return value.trim().length === 0;
|
|
110
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
111
|
-
if (Helpers.isObject(value)) return Object.keys(value).length === 0;
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get object size (bytes estimation)
|
|
117
|
-
*/
|
|
118
|
-
static getObjectSize(obj) {
|
|
119
|
-
const seen = new WeakSet();
|
|
120
|
-
|
|
121
|
-
function sizeOf(obj) {
|
|
122
|
-
if (obj === null || obj === undefined) return 0;
|
|
123
|
-
if (seen.has(obj)) return 0;
|
|
124
|
-
|
|
125
|
-
seen.add(obj);
|
|
126
|
-
|
|
127
|
-
switch (typeof obj) {
|
|
128
|
-
case 'number':
|
|
129
|
-
return 8;
|
|
130
|
-
case 'string':
|
|
131
|
-
return obj.length * 2;
|
|
132
|
-
case 'boolean':
|
|
133
|
-
return 4;
|
|
134
|
-
case 'object':
|
|
135
|
-
if (Array.isArray(obj)) {
|
|
136
|
-
return obj.reduce((size, item) => size + sizeOf(item), 0);
|
|
137
|
-
} else if (obj instanceof Map) {
|
|
138
|
-
let size = 0;
|
|
139
|
-
for (const [key, value] of obj) {
|
|
140
|
-
size += sizeOf(key) + sizeOf(value);
|
|
141
|
-
}
|
|
142
|
-
return size;
|
|
143
|
-
} else if (obj instanceof Set) {
|
|
144
|
-
let size = 0;
|
|
145
|
-
for (const value of obj) {
|
|
146
|
-
size += sizeOf(value);
|
|
147
|
-
}
|
|
148
|
-
return size;
|
|
149
|
-
} else {
|
|
150
|
-
let size = 0;
|
|
151
|
-
for (const key in obj) {
|
|
152
|
-
if (obj.hasOwnProperty(key)) {
|
|
153
|
-
size += sizeOf(key) + sizeOf(obj[key]);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return size;
|
|
157
|
-
}
|
|
158
|
-
default:
|
|
159
|
-
return 0;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return sizeOf(obj);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Format bytes to human readable
|
|
168
|
-
*/
|
|
169
|
-
static formatBytes(bytes, decimals = 2) {
|
|
170
|
-
if (bytes === 0) return '0 Bytes';
|
|
171
|
-
|
|
172
|
-
const k = 1024;
|
|
173
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
174
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
175
|
-
|
|
176
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
177
|
-
|
|
178
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Safe JSON parse
|
|
183
|
-
*/
|
|
184
|
-
static safeJsonParse(str, defaultValue = null) {
|
|
185
|
-
try {
|
|
186
|
-
return JSON.parse(str);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
return defaultValue;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Safe JSON stringify
|
|
194
|
-
*/
|
|
195
|
-
static safeJsonStringify(obj, defaultValue = '{}') {
|
|
196
|
-
try {
|
|
197
|
-
return JSON.stringify(obj);
|
|
198
|
-
} catch (error) {
|
|
199
|
-
return defaultValue;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Filter object properties
|
|
205
|
-
*/
|
|
206
|
-
static filterObject(obj, predicate) {
|
|
207
|
-
const result = {};
|
|
208
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
209
|
-
if (predicate(value, key)) {
|
|
210
|
-
result[key] = value;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return result;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Map object properties
|
|
218
|
-
*/
|
|
219
|
-
static mapObject(obj, mapper) {
|
|
220
|
-
const result = {};
|
|
221
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
222
|
-
result[key] = mapper(value, key);
|
|
223
|
-
}
|
|
224
|
-
return result;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Retry function with exponential backoff
|
|
229
|
-
*/
|
|
230
|
-
static async retry(fn, retries = 3, delay = 1000) {
|
|
231
|
-
try {
|
|
232
|
-
return await fn();
|
|
233
|
-
} catch (error) {
|
|
234
|
-
if (retries === 0) throw error;
|
|
235
|
-
await Helpers.sleep(delay);
|
|
236
|
-
return Helpers.retry(fn, retries - 1, delay * 2);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Generate timestamp
|
|
242
|
-
*/
|
|
243
|
-
static timestamp() {
|
|
244
|
-
return Date.now();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Generate UUID v4
|
|
249
|
-
*/
|
|
250
|
-
static uuid() {
|
|
251
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
252
|
-
const r = Math.random() * 16 | 0;
|
|
253
|
-
const v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
254
|
-
return v.toString(16);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Check if running in Node.js
|
|
260
|
-
*/
|
|
261
|
-
static isNode() {
|
|
262
|
-
return typeof process !== 'undefined' &&
|
|
263
|
-
process.versions != null &&
|
|
264
|
-
process.versions.node != null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Check if running in browser
|
|
269
|
-
*/
|
|
270
|
-
static isBrowser() {
|
|
271
|
-
return typeof window !== 'undefined' &&
|
|
272
|
-
typeof window.document !== 'undefined';
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Helper Functions - The Swiss Army knife of utilities 🔧
|
|
3
|
+
*
|
|
4
|
+
* Random useful stuff that makes life easier
|
|
5
|
+
* Collected from years of "oh, I need that again" moments
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class Helpers {
|
|
9
|
+
/**
|
|
10
|
+
* Deep clone an object
|
|
11
|
+
*/
|
|
12
|
+
static deepClone(obj) {
|
|
13
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
14
|
+
if (obj instanceof Date) return new Date(obj.getTime());
|
|
15
|
+
if (obj instanceof Array) return obj.map(item => Helpers.deepClone(item));
|
|
16
|
+
|
|
17
|
+
const cloned = {};
|
|
18
|
+
for (const key in obj) {
|
|
19
|
+
if (obj.hasOwnProperty(key)) {
|
|
20
|
+
cloned[key] = Helpers.deepClone(obj[key]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return cloned;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Deep merge objects
|
|
28
|
+
*/
|
|
29
|
+
static deepMerge(target, ...sources) {
|
|
30
|
+
if (!sources.length) return target;
|
|
31
|
+
const source = sources.shift();
|
|
32
|
+
|
|
33
|
+
if (Helpers.isObject(target) && Helpers.isObject(source)) {
|
|
34
|
+
for (const key in source) {
|
|
35
|
+
if (Helpers.isObject(source[key])) {
|
|
36
|
+
if (!target[key]) Object.assign(target, { [key]: {} });
|
|
37
|
+
Helpers.deepMerge(target[key], source[key]);
|
|
38
|
+
} else {
|
|
39
|
+
Object.assign(target, { [key]: source[key] });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Helpers.deepMerge(target, ...sources);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if value is an object
|
|
49
|
+
*/
|
|
50
|
+
static isObject(item) {
|
|
51
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate random ID
|
|
56
|
+
*/
|
|
57
|
+
static generateId(length = 16) {
|
|
58
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
59
|
+
let result = '';
|
|
60
|
+
for (let i = 0; i < length; i++) {
|
|
61
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sleep/delay function
|
|
68
|
+
*/
|
|
69
|
+
static sleep(ms) {
|
|
70
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Debounce function
|
|
75
|
+
*/
|
|
76
|
+
static debounce(func, wait, immediate = false) {
|
|
77
|
+
let timeout;
|
|
78
|
+
return function executedFunction(...args) {
|
|
79
|
+
const later = () => {
|
|
80
|
+
timeout = null;
|
|
81
|
+
if (!immediate) func(...args);
|
|
82
|
+
};
|
|
83
|
+
const callNow = immediate && !timeout;
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
timeout = setTimeout(later, wait);
|
|
86
|
+
if (callNow) func(...args);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Throttle function
|
|
92
|
+
*/
|
|
93
|
+
static throttle(func, limit) {
|
|
94
|
+
let inThrottle;
|
|
95
|
+
return function(...args) {
|
|
96
|
+
if (!inThrottle) {
|
|
97
|
+
func.apply(this, args);
|
|
98
|
+
inThrottle = true;
|
|
99
|
+
setTimeout(() => inThrottle = false, limit);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if value is empty
|
|
106
|
+
*/
|
|
107
|
+
static isEmpty(value) {
|
|
108
|
+
if (value === null || value === undefined) return true;
|
|
109
|
+
if (typeof value === 'string') return value.trim().length === 0;
|
|
110
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
111
|
+
if (Helpers.isObject(value)) return Object.keys(value).length === 0;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get object size (bytes estimation)
|
|
117
|
+
*/
|
|
118
|
+
static getObjectSize(obj) {
|
|
119
|
+
const seen = new WeakSet();
|
|
120
|
+
|
|
121
|
+
function sizeOf(obj) {
|
|
122
|
+
if (obj === null || obj === undefined) return 0;
|
|
123
|
+
if (seen.has(obj)) return 0;
|
|
124
|
+
|
|
125
|
+
seen.add(obj);
|
|
126
|
+
|
|
127
|
+
switch (typeof obj) {
|
|
128
|
+
case 'number':
|
|
129
|
+
return 8;
|
|
130
|
+
case 'string':
|
|
131
|
+
return obj.length * 2;
|
|
132
|
+
case 'boolean':
|
|
133
|
+
return 4;
|
|
134
|
+
case 'object':
|
|
135
|
+
if (Array.isArray(obj)) {
|
|
136
|
+
return obj.reduce((size, item) => size + sizeOf(item), 0);
|
|
137
|
+
} else if (obj instanceof Map) {
|
|
138
|
+
let size = 0;
|
|
139
|
+
for (const [key, value] of obj) {
|
|
140
|
+
size += sizeOf(key) + sizeOf(value);
|
|
141
|
+
}
|
|
142
|
+
return size;
|
|
143
|
+
} else if (obj instanceof Set) {
|
|
144
|
+
let size = 0;
|
|
145
|
+
for (const value of obj) {
|
|
146
|
+
size += sizeOf(value);
|
|
147
|
+
}
|
|
148
|
+
return size;
|
|
149
|
+
} else {
|
|
150
|
+
let size = 0;
|
|
151
|
+
for (const key in obj) {
|
|
152
|
+
if (obj.hasOwnProperty(key)) {
|
|
153
|
+
size += sizeOf(key) + sizeOf(obj[key]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return size;
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return sizeOf(obj);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format bytes to human readable
|
|
168
|
+
*/
|
|
169
|
+
static formatBytes(bytes, decimals = 2) {
|
|
170
|
+
if (bytes === 0) return '0 Bytes';
|
|
171
|
+
|
|
172
|
+
const k = 1024;
|
|
173
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
174
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
175
|
+
|
|
176
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
177
|
+
|
|
178
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Safe JSON parse
|
|
183
|
+
*/
|
|
184
|
+
static safeJsonParse(str, defaultValue = null) {
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(str);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return defaultValue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Safe JSON stringify
|
|
194
|
+
*/
|
|
195
|
+
static safeJsonStringify(obj, defaultValue = '{}') {
|
|
196
|
+
try {
|
|
197
|
+
return JSON.stringify(obj);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return defaultValue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Filter object properties
|
|
205
|
+
*/
|
|
206
|
+
static filterObject(obj, predicate) {
|
|
207
|
+
const result = {};
|
|
208
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
209
|
+
if (predicate(value, key)) {
|
|
210
|
+
result[key] = value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Map object properties
|
|
218
|
+
*/
|
|
219
|
+
static mapObject(obj, mapper) {
|
|
220
|
+
const result = {};
|
|
221
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
222
|
+
result[key] = mapper(value, key);
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Retry function with exponential backoff
|
|
229
|
+
*/
|
|
230
|
+
static async retry(fn, retries = 3, delay = 1000) {
|
|
231
|
+
try {
|
|
232
|
+
return await fn();
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (retries === 0) throw error;
|
|
235
|
+
await Helpers.sleep(delay);
|
|
236
|
+
return Helpers.retry(fn, retries - 1, delay * 2);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Generate timestamp
|
|
242
|
+
*/
|
|
243
|
+
static timestamp() {
|
|
244
|
+
return Date.now();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Generate UUID v4
|
|
249
|
+
*/
|
|
250
|
+
static uuid() {
|
|
251
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
252
|
+
const r = Math.random() * 16 | 0;
|
|
253
|
+
const v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
254
|
+
return v.toString(16);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if running in Node.js
|
|
260
|
+
*/
|
|
261
|
+
static isNode() {
|
|
262
|
+
return typeof process !== 'undefined' &&
|
|
263
|
+
process.versions != null &&
|
|
264
|
+
process.versions.node != null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if running in browser
|
|
269
|
+
*/
|
|
270
|
+
static isBrowser() {
|
|
271
|
+
return typeof window !== 'undefined' &&
|
|
272
|
+
typeof window.document !== 'undefined';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
276
|
module.exports = Helpers;
|
package/src/utils/profiler.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
const fs = require("fs").promises;
|
|
2
|
-
|
|
3
|
-
class Storage {
|
|
4
|
-
constructor(filePath) {
|
|
5
|
-
this.filePath = filePath;
|
|
6
|
-
this.writeQueue = [];
|
|
7
|
-
this.isWriting = false;
|
|
8
|
-
this.debounceTimer = null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async init() {
|
|
12
|
-
try {
|
|
13
|
-
await fs.access(this.filePath);
|
|
14
|
-
} catch {
|
|
15
|
-
await fs.writeFile(this.filePath, JSON.stringify({}), "utf8");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const content = await fs.readFile(this.filePath, "utf8");
|
|
20
|
-
return JSON.parse(content);
|
|
21
|
-
} catch {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Debounced write - waits 100ms before writing
|
|
27
|
-
async write(data, immediate = false) {
|
|
28
|
-
if (immediate) {
|
|
29
|
-
return this._writeNow(data);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Clear existing timer
|
|
33
|
-
if (this.debounceTimer) {
|
|
34
|
-
clearTimeout(this.debounceTimer);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Set new timer
|
|
38
|
-
return new Promise((resolve) => {
|
|
39
|
-
this.debounceTimer = setTimeout(async () => {
|
|
40
|
-
await this._writeNow(data);
|
|
41
|
-
resolve();
|
|
42
|
-
}, 100);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async _writeNow(data) {
|
|
47
|
-
const tmpPath = `${this.filePath}.tmp`;
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
await fs.writeFile(tmpPath, JSON.stringify(data, null, 2), "utf8");
|
|
51
|
-
await fs.rename(tmpPath, this.filePath);
|
|
52
|
-
} catch (err) {
|
|
53
|
-
// Cleanup temp file if it exists
|
|
54
|
-
try {
|
|
55
|
-
await fs.unlink(tmpPath);
|
|
56
|
-
} catch {}
|
|
57
|
-
throw err;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async backup(backupPath, data) {
|
|
62
|
-
await fs.writeFile(backupPath, JSON.stringify(data, null, 2), "utf8");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async restore(backupPath) {
|
|
66
|
-
const content = await fs.readFile(backupPath, "utf8");
|
|
67
|
-
return JSON.parse(content);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
1
|
+
const fs = require("fs").promises;
|
|
2
|
+
|
|
3
|
+
class Storage {
|
|
4
|
+
constructor(filePath) {
|
|
5
|
+
this.filePath = filePath;
|
|
6
|
+
this.writeQueue = [];
|
|
7
|
+
this.isWriting = false;
|
|
8
|
+
this.debounceTimer = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async init() {
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(this.filePath);
|
|
14
|
+
} catch {
|
|
15
|
+
await fs.writeFile(this.filePath, JSON.stringify({}), "utf8");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Debounced write - waits 100ms before writing
|
|
27
|
+
async write(data, immediate = false) {
|
|
28
|
+
if (immediate) {
|
|
29
|
+
return this._writeNow(data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Clear existing timer
|
|
33
|
+
if (this.debounceTimer) {
|
|
34
|
+
clearTimeout(this.debounceTimer);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Set new timer
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
this.debounceTimer = setTimeout(async () => {
|
|
40
|
+
await this._writeNow(data);
|
|
41
|
+
resolve();
|
|
42
|
+
}, 100);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async _writeNow(data) {
|
|
47
|
+
const tmpPath = `${this.filePath}.tmp`;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await fs.writeFile(tmpPath, JSON.stringify(data, null, 2), "utf8");
|
|
51
|
+
await fs.rename(tmpPath, this.filePath);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// Cleanup temp file if it exists
|
|
54
|
+
try {
|
|
55
|
+
await fs.unlink(tmpPath);
|
|
56
|
+
} catch {}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async backup(backupPath, data) {
|
|
62
|
+
await fs.writeFile(backupPath, JSON.stringify(data, null, 2), "utf8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async restore(backupPath) {
|
|
66
|
+
const content = await fs.readFile(backupPath, "utf8");
|
|
67
|
+
return JSON.parse(content);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
71
|
module.exports = Storage;
|