sehawq.db 2.4.2 → 4.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/.github/workflows/npm-publish.yml +1 -1
- package/index.js +2 -0
- package/package.json +28 -7
- package/readme.md +342 -235
- package/src/core/Database.js +295 -0
- package/src/core/Events.js +286 -0
- package/src/core/IndexManager.js +814 -0
- package/src/core/Persistence.js +376 -0
- package/src/core/QueryEngine.js +448 -0
- package/src/core/Storage.js +322 -0
- package/src/core/Validator.js +325 -0
- package/src/index.js +90 -469
- package/src/performance/Cache.js +339 -0
- package/src/performance/LazyLoader.js +355 -0
- package/src/performance/MemoryManager.js +496 -0
- package/src/server/api.js +688 -0
- package/src/server/websocket.js +528 -0
- package/src/utils/benchmark.js +52 -0
- package/src/utils/dot-notation.js +248 -0
- package/src/utils/helpers.js +276 -0
- package/src/utils/profiler.js +71 -0
- package/src/version.js +38 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dot Notation Parser - For nested object access 🔍
|
|
3
|
+
*
|
|
4
|
+
* Turns user.profile.name into actual value
|
|
5
|
+
* Because brackets are so 2010 😄
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class DotNotation {
|
|
9
|
+
/**
|
|
10
|
+
* Get value using dot notation
|
|
11
|
+
*/
|
|
12
|
+
static get(obj, path, defaultValue = undefined) {
|
|
13
|
+
if (!this.isObject(obj) || typeof path !== 'string') {
|
|
14
|
+
return defaultValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const keys = path.split('.');
|
|
18
|
+
let current = obj;
|
|
19
|
+
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
if (current === null || current === undefined) {
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Handle array indices
|
|
26
|
+
if (Array.isArray(current) && !isNaN(key)) {
|
|
27
|
+
current = current[parseInt(key)];
|
|
28
|
+
} else if (this.isObject(current)) {
|
|
29
|
+
current = current[key];
|
|
30
|
+
} else {
|
|
31
|
+
return defaultValue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return current !== undefined ? current : defaultValue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set value using dot notation
|
|
40
|
+
*/
|
|
41
|
+
static set(obj, path, value) {
|
|
42
|
+
if (!this.isObject(obj) || typeof path !== 'string') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const keys = path.split('.');
|
|
47
|
+
let current = obj;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
50
|
+
const key = keys[i];
|
|
51
|
+
const nextKey = keys[i + 1];
|
|
52
|
+
|
|
53
|
+
// Create nested objects if they don't exist
|
|
54
|
+
if (current[key] === undefined || current[key] === null) {
|
|
55
|
+
// Check if next key is numeric (array)
|
|
56
|
+
current[key] = !isNaN(nextKey) ? [] : {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
current = current[key];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const lastKey = keys[keys.length - 1];
|
|
63
|
+
|
|
64
|
+
// Handle array indices
|
|
65
|
+
if (Array.isArray(current) && !isNaN(lastKey)) {
|
|
66
|
+
const index = parseInt(lastKey);
|
|
67
|
+
current[index] = value;
|
|
68
|
+
} else {
|
|
69
|
+
current[lastKey] = value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if path exists
|
|
77
|
+
*/
|
|
78
|
+
static has(obj, path) {
|
|
79
|
+
if (!this.isObject(obj) || typeof path !== 'string') {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const keys = path.split('.');
|
|
84
|
+
let current = obj;
|
|
85
|
+
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
if (current === null || current === undefined) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Array.isArray(current) && !isNaN(key)) {
|
|
92
|
+
const index = parseInt(key);
|
|
93
|
+
if (index < 0 || index >= current.length) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
current = current[index];
|
|
97
|
+
} else if (this.isObject(current)) {
|
|
98
|
+
if (!current.hasOwnProperty(key)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
current = current[key];
|
|
102
|
+
} else {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Delete value using dot notation
|
|
112
|
+
*/
|
|
113
|
+
static delete(obj, path) {
|
|
114
|
+
if (!this.isObject(obj) || typeof path !== 'string') {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const keys = path.split('.');
|
|
119
|
+
let current = obj;
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
122
|
+
const key = keys[i];
|
|
123
|
+
|
|
124
|
+
if (current[key] === undefined || current[key] === null) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
current = current[key];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const lastKey = keys[keys.length - 1];
|
|
132
|
+
|
|
133
|
+
if (Array.isArray(current) && !isNaN(lastKey)) {
|
|
134
|
+
const index = parseInt(lastKey);
|
|
135
|
+
if (index >= 0 && index < current.length) {
|
|
136
|
+
current.splice(index, 1);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
} else if (this.isObject(current)) {
|
|
140
|
+
if (current.hasOwnProperty(lastKey)) {
|
|
141
|
+
delete current[lastKey];
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get all paths in an object
|
|
151
|
+
*/
|
|
152
|
+
static getAllPaths(obj, prefix = '') {
|
|
153
|
+
if (!this.isObject(obj)) return [];
|
|
154
|
+
|
|
155
|
+
const paths = [];
|
|
156
|
+
|
|
157
|
+
for (const key in obj) {
|
|
158
|
+
if (obj.hasOwnProperty(key)) {
|
|
159
|
+
const currentPath = prefix ? `${prefix}.${key}` : key;
|
|
160
|
+
const value = obj[key];
|
|
161
|
+
|
|
162
|
+
if (this.isObject(value) && !Array.isArray(value)) {
|
|
163
|
+
// Recursively get paths for nested objects
|
|
164
|
+
paths.push(...this.getAllPaths(value, currentPath));
|
|
165
|
+
} else if (Array.isArray(value)) {
|
|
166
|
+
// Handle arrays - include index paths
|
|
167
|
+
paths.push(currentPath);
|
|
168
|
+
for (let i = 0; i < value.length; i++) {
|
|
169
|
+
const arrayPath = `${currentPath}.${i}`;
|
|
170
|
+
if (this.isObject(value[i])) {
|
|
171
|
+
paths.push(...this.getAllPaths(value[i], arrayPath));
|
|
172
|
+
} else {
|
|
173
|
+
paths.push(arrayPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
paths.push(currentPath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return paths;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Flatten object using dot notation
|
|
187
|
+
*/
|
|
188
|
+
static flatten(obj) {
|
|
189
|
+
const result = {};
|
|
190
|
+
|
|
191
|
+
function flattenHelper(current, path) {
|
|
192
|
+
if (DotNotation.isObject(current) && !Array.isArray(current)) {
|
|
193
|
+
for (const key in current) {
|
|
194
|
+
if (current.hasOwnProperty(key)) {
|
|
195
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
196
|
+
flattenHelper(current[key], newPath);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else if (Array.isArray(current)) {
|
|
200
|
+
for (let i = 0; i < current.length; i++) {
|
|
201
|
+
const newPath = `${path}.${i}`;
|
|
202
|
+
flattenHelper(current[i], newPath);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
result[path] = current;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
flattenHelper(obj, '');
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Unflatten object (reverse of flatten)
|
|
215
|
+
*/
|
|
216
|
+
static unflatten(flatObj) {
|
|
217
|
+
const result = {};
|
|
218
|
+
|
|
219
|
+
for (const path in flatObj) {
|
|
220
|
+
if (flatObj.hasOwnProperty(path)) {
|
|
221
|
+
this.set(result, path, flatObj[path]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Check if value is an object
|
|
230
|
+
*/
|
|
231
|
+
static isObject(value) {
|
|
232
|
+
return value !== null && typeof value === 'object';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Parse path into keys array
|
|
237
|
+
*/
|
|
238
|
+
static parsePath(path) {
|
|
239
|
+
if (typeof path !== 'string') return [];
|
|
240
|
+
|
|
241
|
+
// Handle array indices and nested objects
|
|
242
|
+
return path.split('.').map(key => {
|
|
243
|
+
return !isNaN(key) ? parseInt(key) : key;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = DotNotation;
|
|
@@ -0,0 +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
|
+
|
|
276
|
+
module.exports = Helpers;
|
|
@@ -0,0 +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
|
+
|
|
71
|
+
module.exports = Storage;
|
package/src/version.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SehawqDB Version Information
|
|
3
|
+
*
|
|
4
|
+
* Because knowing your version is kinda important 😅
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const packageJson = require('../package.json');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
version: packageJson.version,
|
|
11
|
+
name: packageJson.name,
|
|
12
|
+
description: packageJson.description,
|
|
13
|
+
author: packageJson.author,
|
|
14
|
+
license: packageJson.license,
|
|
15
|
+
|
|
16
|
+
getVersion() {
|
|
17
|
+
return this.version;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
getInfo() {
|
|
21
|
+
return {
|
|
22
|
+
name: this.name,
|
|
23
|
+
version: this.version,
|
|
24
|
+
description: this.description,
|
|
25
|
+
author: this.author,
|
|
26
|
+
license: this.license
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Compatibility check
|
|
31
|
+
isCompatibleWith(version) {
|
|
32
|
+
const current = this.version.split('.').map(Number);
|
|
33
|
+
const target = version.split('.').map(Number);
|
|
34
|
+
|
|
35
|
+
// Major version must match, minor can be equal or higher
|
|
36
|
+
return current[0] === target[0] && current[1] >= target[1];
|
|
37
|
+
}
|
|
38
|
+
};
|