goaded.utils 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/batcher.js +32 -0
- package/cache.js +26 -0
- package/connector.js +30 -0
- package/db.js +190 -0
- package/diff.js +11 -0
- package/events.js +159 -0
- package/getter.js +21 -0
- package/hook.js +28 -0
- package/lazy.js +14 -0
- package/logger.js +81 -0
- package/lru.js +23 -0
- package/memo.js +18 -0
- package/middleware.js +16 -0
- package/package.json +17 -0
- package/pipeline.js +22 -0
- package/polls.js +19 -0
- package/pool.js +16 -0
- package/priority.js +24 -0
- package/queue.js +33 -0
- package/result.js +19 -0
- package/retry.js +23 -0
- package/services.js +106 -0
- package/signal.js +16 -0
- package/sleep.js +1 -0
- package/states.js +25 -0
package/batcher.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class Batcher {
|
|
2
|
+
constructor(handler, timeout = 50) {
|
|
3
|
+
this.handler = handler; // Function that takes an array of inputs
|
|
4
|
+
this.timeout = timeout;
|
|
5
|
+
this.queue = [];
|
|
6
|
+
this.timer = null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
add(item) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
this.queue.push({ item, resolve, reject });
|
|
12
|
+
if (!this.timer) {
|
|
13
|
+
this.timer = setTimeout(() => this.flush(), this.timeout);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async flush() {
|
|
19
|
+
const currentQueue = this.queue;
|
|
20
|
+
this.queue = [];
|
|
21
|
+
this.timer = null;
|
|
22
|
+
|
|
23
|
+
const items = currentQueue.map(q => q.item);
|
|
24
|
+
try {
|
|
25
|
+
const results = await this.handler(items); // Handler processes bulk
|
|
26
|
+
currentQueue.forEach((q, i) => q.resolve(results[i]));
|
|
27
|
+
} catch (err) {
|
|
28
|
+
currentQueue.forEach(q => q.reject(err));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
module.exports = Batcher;
|
package/cache.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class CacheManager {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.storage = new Map();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
set(key, value, ttlSecs = 300) {
|
|
7
|
+
const record = {
|
|
8
|
+
value,
|
|
9
|
+
expires: Date.now() + ttlSecs * 1000,
|
|
10
|
+
};
|
|
11
|
+
this.storage.set(key, JSON.stringify(record));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get(key) {
|
|
15
|
+
const raw = this.storage.get(key);
|
|
16
|
+
if (!raw) return null;
|
|
17
|
+
|
|
18
|
+
const record = JSON.parse(raw);
|
|
19
|
+
if (Date.now() > record.expires) {
|
|
20
|
+
this.storage.delete(key);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return record.value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
module.exports = CacheManager;
|
package/connector.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class Connector {
|
|
2
|
+
constructor(fn, failureThreshold = 3, cooldownMs = 5000) {
|
|
3
|
+
this.fn = fn;
|
|
4
|
+
this.threshold = failureThreshold;
|
|
5
|
+
this.cooldown = cooldownMs;
|
|
6
|
+
this.failures = 0;
|
|
7
|
+
this.lastFailureTime = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async fire(...args) {
|
|
11
|
+
if (this.failures >= this.threshold) {
|
|
12
|
+
if (Date.now() - this.lastFailureTime > this.cooldown) {
|
|
13
|
+
this.failures = 0; // Reset after cooldown
|
|
14
|
+
} else {
|
|
15
|
+
throw new Error("Circuit Open: Service Unavailable");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const result = await this.fn(...args);
|
|
21
|
+
this.failures = 0; // Success resets the count
|
|
22
|
+
return result;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
this.failures++;
|
|
25
|
+
this.lastFailureTime = Date.now();
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
module.exports = Connector;
|
package/db.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
2
|
+
|
|
3
|
+
class Db {
|
|
4
|
+
constructor(file = ':memory:') {
|
|
5
|
+
this.db = new sqlite3.Database(file);
|
|
6
|
+
this.Schema = class {
|
|
7
|
+
constructor(definition) {
|
|
8
|
+
this.definition = definition;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
model(name, schema) {
|
|
14
|
+
const db = this.db;
|
|
15
|
+
const tableName = name.toLowerCase() + 's';
|
|
16
|
+
const definition = schema.definition;
|
|
17
|
+
|
|
18
|
+
const createTableSql = `
|
|
19
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
info TEXT NOT NULL
|
|
22
|
+
)
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const tableReady = new Promise((resolve, reject) => {
|
|
26
|
+
db.run(createTableSql, (err) => {
|
|
27
|
+
if (err) {
|
|
28
|
+
console.error(`Error creating table ${tableName}:`, err.message);
|
|
29
|
+
reject(err);
|
|
30
|
+
} else {
|
|
31
|
+
resolve();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return class Model {
|
|
37
|
+
constructor(data) {
|
|
38
|
+
this._data = {};
|
|
39
|
+
|
|
40
|
+
Object.keys(definition).forEach(key => {
|
|
41
|
+
const rule = definition[key];
|
|
42
|
+
const inputVal = data[key];
|
|
43
|
+
|
|
44
|
+
if (inputVal === undefined && rule.default !== undefined) {
|
|
45
|
+
this._data[key] = typeof rule.default === 'function' ? rule.default() : rule.default;
|
|
46
|
+
} else if (inputVal !== undefined) {
|
|
47
|
+
this._data[key] = inputVal;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if(data.id) this._data.id = data.id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
JSONify() {
|
|
55
|
+
console.log(this._data);
|
|
56
|
+
return this._data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toJSON() {
|
|
60
|
+
return this._data;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static createProxy(instance) {
|
|
64
|
+
return new Proxy(instance, {
|
|
65
|
+
get(target, prop) {
|
|
66
|
+
if (prop in target) return target[prop];
|
|
67
|
+
return target._data[prop];
|
|
68
|
+
},
|
|
69
|
+
set(target, prop, value) {
|
|
70
|
+
if (prop in definition) {
|
|
71
|
+
target._data[prop] = value;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static validate(dataObject) {
|
|
80
|
+
const errors = {};
|
|
81
|
+
Object.keys(definition).forEach(key => {
|
|
82
|
+
let value = dataObject[key];
|
|
83
|
+
const rules = definition[key];
|
|
84
|
+
|
|
85
|
+
if (typeof value === 'string') {
|
|
86
|
+
if (rules.trim) value = value.trim();
|
|
87
|
+
if (rules.lowercase) value = value.toLowerCase();
|
|
88
|
+
if (rules.uppercase) value = value.toUpperCase();
|
|
89
|
+
dataObject[key] = value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (rules.required && (value === undefined || value === null || value === '')) {
|
|
93
|
+
errors[key] = `Path \`${key}\` is required.`;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (value === undefined || value === null) return;
|
|
98
|
+
|
|
99
|
+
if (rules.type === Number && isNaN(value)) errors[key] = "Not a number";
|
|
100
|
+
if (rules.type === Boolean && typeof value !== 'boolean') errors[key] = "Not a boolean";
|
|
101
|
+
|
|
102
|
+
if (rules.type === String) {
|
|
103
|
+
if (rules.enum && !rules.enum.includes(value))
|
|
104
|
+
errors[key] = `\`${value}\` is not allowed.`;
|
|
105
|
+
if (rules.match && !rules.match.test(value))
|
|
106
|
+
errors[key] = "Regex validation failed";
|
|
107
|
+
}
|
|
108
|
+
if (rules.type === Number) {
|
|
109
|
+
if (rules.min !== undefined && value < rules.min) errors[key] = `Min ${rules.min}`;
|
|
110
|
+
if (rules.max !== undefined && value > rules.max) errors[key] = `Max ${rules.max}`;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (Object.keys(errors).length > 0) throw { message: "Validation failed", errors };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async save() {
|
|
118
|
+
await tableReady;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
Model.validate(this._data);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return Promise.reject(e);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const jsonPayload = JSON.stringify(this._data);
|
|
128
|
+
const self = this;
|
|
129
|
+
|
|
130
|
+
if (this._data.id) {
|
|
131
|
+
const sql = `UPDATE ${tableName} SET info = ? WHERE id = ?`;
|
|
132
|
+
db.run(sql, [jsonPayload, this._data.id], function(err) {
|
|
133
|
+
if(err) reject(err);
|
|
134
|
+
else resolve(self);
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
const sql = `INSERT INTO ${tableName} (info) VALUES (?)`;
|
|
138
|
+
db.run(sql, [jsonPayload], function(err) {
|
|
139
|
+
if (err) reject(err);
|
|
140
|
+
else {
|
|
141
|
+
self._data.id = this.lastID;
|
|
142
|
+
resolve(Model.createProxy(self));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static async find(query = {}) {
|
|
150
|
+
await tableReady;
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
let sql = `SELECT id, info FROM ${tableName}`;
|
|
153
|
+
const params = [];
|
|
154
|
+
const keys = Object.keys(query);
|
|
155
|
+
|
|
156
|
+
if (keys.length > 0) {
|
|
157
|
+
const clauses = keys.map(key => {
|
|
158
|
+
if(!definition[key] && key !== 'id') return null;
|
|
159
|
+
if (key === 'id') return `id = ?`;
|
|
160
|
+
return `json_extract(info, '$.${key}') = ?`;
|
|
161
|
+
}).filter(x => x).join(' AND ');
|
|
162
|
+
|
|
163
|
+
if(clauses) sql += ` WHERE ${clauses}`;
|
|
164
|
+
keys.forEach(key => {
|
|
165
|
+
if(definition[key] || key === 'id') params.push(query[key]);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
db.all(sql, params, (err, rows) => {
|
|
170
|
+
if (err) reject(err);
|
|
171
|
+
else {
|
|
172
|
+
const results = rows.map(row => {
|
|
173
|
+
const data = JSON.parse(row.info);
|
|
174
|
+
data.id = row.id;
|
|
175
|
+
return Model.createProxy(new Model(data));
|
|
176
|
+
});
|
|
177
|
+
resolve(results);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static async create(data) {
|
|
184
|
+
const instance = Model.createProxy(new Model(data));
|
|
185
|
+
return await instance.save();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
module.exports = Db;
|
package/diff.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const deepDiff = (obj1, obj2) => {
|
|
2
|
+
const diff = {};
|
|
3
|
+
const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
|
|
4
|
+
keys.forEach(key => {
|
|
5
|
+
if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) {
|
|
6
|
+
diff[key] = { old: obj1[key], new: obj2[key] };
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
return diff;
|
|
10
|
+
};
|
|
11
|
+
module.exports = deepDiff;
|
package/events.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
class emitter {
|
|
2
|
+
listeners = [];
|
|
3
|
+
|
|
4
|
+
constructor() {}
|
|
5
|
+
|
|
6
|
+
on(event, callback) {
|
|
7
|
+
this.listeners.push({event, callback});
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
off(event, callback) {
|
|
12
|
+
this.listeners = this.listeners.filter(
|
|
13
|
+
listener => listener.event !== event || listener.callback !== callback
|
|
14
|
+
);
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
once(event, callback) {
|
|
19
|
+
const wrappedCallback = (...args) => {
|
|
20
|
+
callback(...args);
|
|
21
|
+
this.off(event, wrappedCallback);
|
|
22
|
+
};
|
|
23
|
+
this.on(event, wrappedCallback);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
emit(event, ...args) {
|
|
28
|
+
this.listeners.forEach(listener => {
|
|
29
|
+
if(listener.event === event) {
|
|
30
|
+
try {
|
|
31
|
+
listener.callback(...args);
|
|
32
|
+
} catch(error) {
|
|
33
|
+
console.error(`Error in event listener for "${event}":`, error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async emitAsync(event, ...args) {
|
|
40
|
+
const promises = this.listeners
|
|
41
|
+
.filter(listener => listener.event === event)
|
|
42
|
+
.map(listener => Promise.resolve(listener.callback(...args)));
|
|
43
|
+
|
|
44
|
+
await Promise.all(promises);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
emitOnce(event, ...args) {
|
|
48
|
+
this.listeners = this.listeners.filter(listener => {
|
|
49
|
+
if(listener.event === event) {
|
|
50
|
+
try {
|
|
51
|
+
listener.callback(...args);
|
|
52
|
+
} catch(error) {
|
|
53
|
+
console.error(`Error in event listener for "${event}":`, error);
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async emitOnceAsync(event, ...args) {
|
|
62
|
+
const matchingListeners = this.listeners.filter(
|
|
63
|
+
listener => listener.event === event
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
this.listeners = this.listeners.filter(
|
|
67
|
+
listener => listener.event !== event
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
await Promise.all(
|
|
71
|
+
matchingListeners.map(listener =>
|
|
72
|
+
Promise.resolve(listener.callback(...args))
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
emitRepeat(event, times, ...args) {
|
|
78
|
+
this.listeners.forEach(listener => {
|
|
79
|
+
if(listener.event === event) {
|
|
80
|
+
for(let i = 0; i < times; i++) {
|
|
81
|
+
try {
|
|
82
|
+
listener.callback(...args);
|
|
83
|
+
} catch(error) {
|
|
84
|
+
console.error(`Error in event listener for "${event}":`, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async emitRepeatAsync(event, times, ...args) {
|
|
92
|
+
const matchingListeners = this.listeners.filter(
|
|
93
|
+
listener => listener.event === event
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
for(let i = 0; i < times; i++) {
|
|
97
|
+
await Promise.all(
|
|
98
|
+
matchingListeners.map(listener =>
|
|
99
|
+
Promise.resolve(listener.callback(...args))
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class getter {
|
|
107
|
+
constructor() {
|
|
108
|
+
this.em = new emitter();
|
|
109
|
+
this.instances = [this.em];
|
|
110
|
+
|
|
111
|
+
this.global = {
|
|
112
|
+
emit: (message, ...args) => {
|
|
113
|
+
this.instances.forEach(instance => instance.emit(message, ...args));
|
|
114
|
+
},
|
|
115
|
+
on: (event, callback) => {
|
|
116
|
+
this.instances.forEach(instance => instance.on(event, callback));
|
|
117
|
+
},
|
|
118
|
+
emitAsync: async (message, ...args) => {
|
|
119
|
+
await Promise.all(
|
|
120
|
+
this.instances.map(instance => instance.emitAsync(message, ...args))
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
emitOnce: (message, ...args) => {
|
|
124
|
+
this.instances.forEach(instance => instance.emitOnce(message, ...args));
|
|
125
|
+
},
|
|
126
|
+
emitOnceAsync: async (message, ...args) => {
|
|
127
|
+
await Promise.all(
|
|
128
|
+
this.instances.map(instance => instance.emitOnceAsync(message, ...args))
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
emitRepeat: (event, times, ...args) => {
|
|
132
|
+
this.instances.forEach(instance => instance.emitRepeat(event, times, ...args));
|
|
133
|
+
},
|
|
134
|
+
emitRepeatAsync: async (event, times, ...args) => {
|
|
135
|
+
await Promise.all(
|
|
136
|
+
this.instances.map(instance => instance.emitRepeatAsync(event, times, ...args))
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
Object.freeze(this.global);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getClass() {
|
|
145
|
+
return emitter;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getInstance(num = 0) {
|
|
149
|
+
return this.instances[num];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getNewInstance() {
|
|
153
|
+
const newEm = new emitter();
|
|
154
|
+
this.instances.push(newEm);
|
|
155
|
+
return {'instance': newEm, 'num': this.instances.length - 1};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = new getter();
|
package/getter.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class getter {
|
|
2
|
+
constructor(obj) {
|
|
3
|
+
this.obj = new obj();
|
|
4
|
+
this.instances = [this.obj];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
getClass() {
|
|
8
|
+
return this.obj;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getInstance(num = 0) {
|
|
12
|
+
return this.instances[num];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getNewInstance() {
|
|
16
|
+
const newInst = new obj();
|
|
17
|
+
this.instances.push(newInst);
|
|
18
|
+
return {'instance': newInst, 'num': this.instances.length - 1};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
module.exports = getter;
|
package/hook.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// core/Hooks.js
|
|
2
|
+
class HookSystem {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.hooks = new Map();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Register a listener
|
|
8
|
+
tap(name, callback) {
|
|
9
|
+
if (!this.hooks.has(name)) {
|
|
10
|
+
this.hooks.set(name, []);
|
|
11
|
+
}
|
|
12
|
+
this.hooks.get(name).push(callback);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Run the hooks in sequence, passing data through them
|
|
16
|
+
async call(name, initialValue) {
|
|
17
|
+
const callbacks = this.hooks.get(name) || [];
|
|
18
|
+
let currentValue = initialValue;
|
|
19
|
+
|
|
20
|
+
for (const callback of callbacks) {
|
|
21
|
+
// The output of one becomes the input of the next
|
|
22
|
+
currentValue = await callback(currentValue);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return currentValue;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
module.exports = HookSystem
|
package/lazy.js
ADDED
package/logger.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const COLORS = {
|
|
2
|
+
reset: "\x1b[0m",
|
|
3
|
+
dim: "\x1b[2m",
|
|
4
|
+
fg: {
|
|
5
|
+
gray: "\x1b[90m",
|
|
6
|
+
cyan: "\x1b[36m",
|
|
7
|
+
green: "\x1b[32m",
|
|
8
|
+
yellow: "\x1b[33m",
|
|
9
|
+
red: "\x1b[31m",
|
|
10
|
+
magenta: "\x1b[35m",
|
|
11
|
+
white: "\x1b[37m",
|
|
12
|
+
vortex: "\x1b[38;5;129m",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const LEVELS = {
|
|
17
|
+
trace: 10,
|
|
18
|
+
debug: 20,
|
|
19
|
+
info: 30,
|
|
20
|
+
warn: 40,
|
|
21
|
+
error: 50,
|
|
22
|
+
fatal: 60,
|
|
23
|
+
};
|
|
24
|
+
const LEVEL_COLORS = {
|
|
25
|
+
trace: COLORS.fg.gray,
|
|
26
|
+
debug: COLORS.fg.cyan,
|
|
27
|
+
info: COLORS.fg.green,
|
|
28
|
+
warn: COLORS.fg.yellow,
|
|
29
|
+
error: COLORS.fg.red,
|
|
30
|
+
fatal: COLORS.fg.magenta,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Dont ask me how the FUCK this works.
|
|
34
|
+
function fmtDate(d = new Date()) {
|
|
35
|
+
const pad = (n, w = 2) => `${n}`.padStart(w, "0");
|
|
36
|
+
const yyyy = d.getFullYear();
|
|
37
|
+
const MM = pad(d.getMonth() + 1);
|
|
38
|
+
const dd = pad(d.getDate());
|
|
39
|
+
const hh = pad(d.getHours());
|
|
40
|
+
const mm = pad(d.getMinutes());
|
|
41
|
+
const ss = pad(d.getSeconds());
|
|
42
|
+
const SSS = pad(d.getMilliseconds(), 3);
|
|
43
|
+
return `${yyyy}-${MM}-${dd} ${hh}:${mm}:${ss}.${SSS}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function colorize(text, color) {
|
|
47
|
+
return `${color}${text}${COLORS.reset}`;
|
|
48
|
+
}
|
|
49
|
+
module.exports = function createLogger(namespace = "app") {
|
|
50
|
+
function log(level, ...args) {
|
|
51
|
+
const lvlNum = LEVELS[level];
|
|
52
|
+
|
|
53
|
+
const ts = colorize(fmtDate(), COLORS.dim);
|
|
54
|
+
const lvl = colorize(
|
|
55
|
+
`[${level.toUpperCase()}]`,
|
|
56
|
+
LEVEL_COLORS[level] || COLORS.fg.white
|
|
57
|
+
);
|
|
58
|
+
const mod = colorize(`Module: ${namespace}`, COLORS.fg.yellow);
|
|
59
|
+
const msg = util.format(...args);
|
|
60
|
+
|
|
61
|
+
const arw = colorize(">", COLORS.fg.vortex);
|
|
62
|
+
|
|
63
|
+
const line = `${lvl} ${arw} ${ts} ${arw} ${mod} ${arw} ${msg}`;
|
|
64
|
+
if (lvlNum >= LEVELS.error) {
|
|
65
|
+
console.error(line);
|
|
66
|
+
} else if (lvlNum >= LEVELS.warn) {
|
|
67
|
+
console.warn(line);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(line);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
trace: (...a) => log("trace", ...a),
|
|
75
|
+
debug: (...a) => log("debug", ...a),
|
|
76
|
+
info: (...a) => log("info", ...a),
|
|
77
|
+
warn: (...a) => log("warn", ...a),
|
|
78
|
+
error: (...a) => log("error", ...a),
|
|
79
|
+
fatal: (...a) => log("fatal", ...a),
|
|
80
|
+
};
|
|
81
|
+
};
|
package/lru.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class LRUCache {
|
|
2
|
+
constructor(limit = 100) {
|
|
3
|
+
this.limit = limit;
|
|
4
|
+
this.cache = new Map();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
get(key) {
|
|
8
|
+
if (!this.cache.has(key)) return null;
|
|
9
|
+
const val = this.cache.get(key);
|
|
10
|
+
this.cache.delete(key); // Refresh freshness
|
|
11
|
+
this.cache.set(key, val);
|
|
12
|
+
return val;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
set(key, val) {
|
|
16
|
+
if (this.cache.has(key)) this.cache.delete(key);
|
|
17
|
+
else if (this.cache.size === this.limit) {
|
|
18
|
+
this.cache.delete(this.cache.keys().next().value); // Evict oldest
|
|
19
|
+
}
|
|
20
|
+
this.cache.set(key, val);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
module.exports = LRUCache;
|
package/memo.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// core/Memoizer.js
|
|
2
|
+
const memoize = (fn, keyResolver) => {
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
|
|
5
|
+
return (...args) => {
|
|
6
|
+
// Default key is just the first argument, or a JSON string of all args
|
|
7
|
+
const key = keyResolver ? keyResolver(...args) : JSON.stringify(args);
|
|
8
|
+
|
|
9
|
+
if (cache.has(key)) {
|
|
10
|
+
return cache.get(key);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const result = fn(...args);
|
|
14
|
+
cache.set(key, result);
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
module.exports = memoize;
|
package/middleware.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class MiddlewareRunner {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.middlewares = [];
|
|
4
|
+
}
|
|
5
|
+
use(fn) { this.middlewares.push(fn); }
|
|
6
|
+
|
|
7
|
+
async run(context) {
|
|
8
|
+
const execute = async (index) => {
|
|
9
|
+
if (index < this.middlewares.length) {
|
|
10
|
+
await this.middlewares[index](context, () => execute(index + 1));
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
await execute(0);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
module.exports = MiddlewareRunner;
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"sqlite3": "^5.1.7"
|
|
4
|
+
},
|
|
5
|
+
"name": "goaded.utils",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"description": "",
|
|
8
|
+
"main": "services.js",
|
|
9
|
+
"devDependencies": {},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["utils"],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"type": "commonjs"
|
|
17
|
+
}
|
package/pipeline.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Pipeline {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.steps = [];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
use(step) {
|
|
7
|
+
this.steps.push(step);
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute(input) {
|
|
12
|
+
let result = input;
|
|
13
|
+
for (const step of this.steps) {
|
|
14
|
+
result = await step(result);
|
|
15
|
+
if (result === null || result === undefined) {
|
|
16
|
+
throw new Error('Pipeline broken: Step returned null/undefined');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
module.exports = Pipeline
|
package/polls.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const poll = async ({ fn, validate, interval, maxAttempts }) => {
|
|
2
|
+
let attempts = 0;
|
|
3
|
+
|
|
4
|
+
const execute = async (resolve, reject) => {
|
|
5
|
+
const result = await fn();
|
|
6
|
+
attempts++;
|
|
7
|
+
|
|
8
|
+
if (validate(result)) {
|
|
9
|
+
return resolve(result);
|
|
10
|
+
} else if (attempts === maxAttempts) {
|
|
11
|
+
return reject(new Error('Exceeded max attempts'));
|
|
12
|
+
} else {
|
|
13
|
+
setTimeout(execute, interval, resolve, reject);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return new Promise(execute);
|
|
18
|
+
};
|
|
19
|
+
module.exports = poll;
|
package/pool.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class ObjectPool {
|
|
2
|
+
constructor(factory, size) {
|
|
3
|
+
this.pool = [];
|
|
4
|
+
this.factory = factory;
|
|
5
|
+
for (let i = 0; i < size; i++) this.pool.push(factory());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
acquire() {
|
|
9
|
+
return this.pool.length > 0 ? this.pool.pop() : this.factory();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
release(obj) {
|
|
13
|
+
this.pool.push(obj);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
module.exports = ObjectPool;
|
package/priority.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class PriorityQueue {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.items = [];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
enqueue(element, priority) {
|
|
7
|
+
const node = { element, priority };
|
|
8
|
+
let added = false;
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
11
|
+
if (node.priority > this.items[i].priority) {
|
|
12
|
+
this.items.splice(i, 0, node);
|
|
13
|
+
added = true;
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (!added) this.items.push(node);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
dequeue() {
|
|
21
|
+
return this.items.shift()?.element;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
module.exports = PriorityQueue;
|
package/queue.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class TaskQueue {
|
|
2
|
+
constructor(concurrency = 3) {
|
|
3
|
+
this.concurrency = concurrency;
|
|
4
|
+
this.running = 0;
|
|
5
|
+
this.queue = [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
add(task) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
this.queue.push({ task, resolve, reject });
|
|
11
|
+
this.process();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async process() {
|
|
16
|
+
if (this.running >= this.concurrency || this.queue.length === 0) return;
|
|
17
|
+
|
|
18
|
+
this.running++;
|
|
19
|
+
const { task, resolve, reject } = this.queue.shift();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = await task();
|
|
23
|
+
resolve(result);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
reject(err);
|
|
26
|
+
} finally {
|
|
27
|
+
this.running--;
|
|
28
|
+
this.process(); // Pick up the next task
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = TaskQueue;
|
package/result.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Result {
|
|
2
|
+
static ok(data) {
|
|
3
|
+
return { success: true, data, error: null };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static fail(error) {
|
|
7
|
+
return { success: false, data: null, error };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static async fromPromise(promise) {
|
|
11
|
+
try {
|
|
12
|
+
const data = await promise;
|
|
13
|
+
return Result.ok(data);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
return Result.fail(e);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
module.exports = new Result();
|
package/retry.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const createLogger = require('./logger');
|
|
2
|
+
const logger = createLogger('retry');
|
|
3
|
+
const sleep = require('./sleep');
|
|
4
|
+
const retry = async (fn, options = {}) => {
|
|
5
|
+
const { retries = 3, delay = 20, factor = 2 } = options;
|
|
6
|
+
let attempt = 0;
|
|
7
|
+
|
|
8
|
+
while (attempt <= retries) {
|
|
9
|
+
try {
|
|
10
|
+
return await fn();
|
|
11
|
+
} catch (err) {
|
|
12
|
+
if (attempt >= retries) throw err;
|
|
13
|
+
|
|
14
|
+
const waitTime = delay * Math.pow(factor, attempt);
|
|
15
|
+
// Optional: Log the retry here using your custom logger
|
|
16
|
+
logger.warn(`Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`);
|
|
17
|
+
|
|
18
|
+
await sleep(delay);
|
|
19
|
+
attempt++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
module.exports = retry;
|
package/services.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// core/ServiceLocator.js
|
|
2
|
+
const cache = require('./cache');
|
|
3
|
+
const Connector = require('./connector');
|
|
4
|
+
const events = require('./events')
|
|
5
|
+
const hooks = require('./hook');
|
|
6
|
+
const createLogger = require('./logger');
|
|
7
|
+
const memoize = require('./memo');
|
|
8
|
+
const StateMachine = require('./states');
|
|
9
|
+
const pipeline = require('./pipeline');
|
|
10
|
+
const queue = require('./queue');
|
|
11
|
+
const retry = require('./retry');
|
|
12
|
+
const result = require('./result');
|
|
13
|
+
const sleep = require('./sleep');
|
|
14
|
+
const batcher = require('./batcher');
|
|
15
|
+
const poll = require('./polls');
|
|
16
|
+
const lru = require('./lru');
|
|
17
|
+
const priority = require('./priority');
|
|
18
|
+
const lazy = require('./lazy');
|
|
19
|
+
const pool = require('./pool');
|
|
20
|
+
const diff = require('./diff');
|
|
21
|
+
const middleware = require('./middleware');
|
|
22
|
+
const signal = require('./signal');
|
|
23
|
+
const getter = require('./getter');
|
|
24
|
+
const Db = require("./db");
|
|
25
|
+
class ServiceLocator {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.services = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
register(name, instance) {
|
|
31
|
+
if (this.services.has(name)) {
|
|
32
|
+
console.warn(`Service ${name} already registered, overwriting.`);
|
|
33
|
+
}
|
|
34
|
+
this.services.set(name, instance);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get(name) {
|
|
38
|
+
const service = this.services.get(name);
|
|
39
|
+
if (!service) {
|
|
40
|
+
throw new Error(`Service ${name} not found`);
|
|
41
|
+
}
|
|
42
|
+
return service;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class Services {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.utils = new ServiceLocator();
|
|
48
|
+
this.registerUtils();
|
|
49
|
+
this.active = new ServiceLocator();
|
|
50
|
+
this.registerActive();
|
|
51
|
+
this.getters = new ServiceLocator();
|
|
52
|
+
this.registerGetters();
|
|
53
|
+
}
|
|
54
|
+
registerGetters(){
|
|
55
|
+
const g = this.getters;
|
|
56
|
+
//same classes as active, but with getter for more customization
|
|
57
|
+
const gs = [{'name':"batcher",'obj':batcher},{'name':"cache",'obj':cache},{'name':"hooks",'obj':hooks},
|
|
58
|
+
{'name':"lru",'obj':lru},{'name':"queue",'obj':queue},
|
|
59
|
+
{'name':"signal",'obj':signal},{'name':"stateMachine",'obj':StateMachine},
|
|
60
|
+
{'name':"middleware",'obj':middleware},
|
|
61
|
+
{'name':"pool",'obj':pool}];
|
|
62
|
+
gs.forEach(item=>{
|
|
63
|
+
g.register(item.name, new getter(item.obj));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
registerActive(){
|
|
67
|
+
const a = this.active;
|
|
68
|
+
a.register('batcher', new batcher());
|
|
69
|
+
a.register('cache', new cache());
|
|
70
|
+
a.register('hooks',new hooks());
|
|
71
|
+
a.register('lru', new lru());
|
|
72
|
+
a.register('queue', new queue());
|
|
73
|
+
a.register('signal', new signal());
|
|
74
|
+
a.register('stateMachine', new StateMachine());
|
|
75
|
+
a.register('middleware', new middleware());
|
|
76
|
+
a.register('pool', new pool(() => ({}), 10));
|
|
77
|
+
a.register('db',new Db('database.db'));
|
|
78
|
+
}
|
|
79
|
+
registerUtils(){
|
|
80
|
+
this.utils.register('cache', cache);
|
|
81
|
+
this.utils.register('connector', Connector);
|
|
82
|
+
this.utils.register('events', events);
|
|
83
|
+
this.utils.register('hooks', hooks);
|
|
84
|
+
this.utils.register('logger', createLogger);
|
|
85
|
+
this.utils.register('memoize', memoize);
|
|
86
|
+
this.utils.register('stateMachine', StateMachine);
|
|
87
|
+
this.utils.register('pipeline', pipeline);
|
|
88
|
+
this.utils.register('queue', queue);
|
|
89
|
+
this.utils.register('retry', retry);
|
|
90
|
+
this.utils.register('result', result);
|
|
91
|
+
this.utils.register('sleep', sleep);
|
|
92
|
+
this.utils.register('ServiceLocator', ServiceLocator);
|
|
93
|
+
this.utils.register('batcher', batcher);
|
|
94
|
+
this.utils.register('poll', poll);
|
|
95
|
+
this.utils.register('lru', lru);
|
|
96
|
+
this.utils.register('priority',priority);
|
|
97
|
+
this.utils.register('lazy', lazy);
|
|
98
|
+
this.utils.register('pool', pool);
|
|
99
|
+
this.utils.register('diff', diff);
|
|
100
|
+
this.utils.register('middleware', middleware);
|
|
101
|
+
this.utils.register('signal',signal);
|
|
102
|
+
this.utils.register('getter', getter);
|
|
103
|
+
this.utils.register('db',Db);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
module.exports = new Services();
|
package/signal.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Signal {
|
|
2
|
+
constructor(val) {
|
|
3
|
+
this._val = val;
|
|
4
|
+
this.subs = new Set();
|
|
5
|
+
}
|
|
6
|
+
get value() { return this._val; }
|
|
7
|
+
set value(v) {
|
|
8
|
+
this._val = v;
|
|
9
|
+
this.subs.forEach(fn => fn(v));
|
|
10
|
+
}
|
|
11
|
+
subscribe(fn) {
|
|
12
|
+
this.subs.add(fn);
|
|
13
|
+
return () => this.subs.delete(fn);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
module.exports = Signal;
|
package/sleep.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = async (ms) => await new Promise((resolve) => setTimeout(resolve, ms));
|
package/states.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class StateMachine {
|
|
2
|
+
constructor(initialState, transitions) {
|
|
3
|
+
this.state = initialState;
|
|
4
|
+
this.transitions = transitions;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
transition(action) {
|
|
8
|
+
const currentStateConfig = this.transitions[this.state];
|
|
9
|
+
const nextState = currentStateConfig ? currentStateConfig[action] : null;
|
|
10
|
+
|
|
11
|
+
if (!nextState) {
|
|
12
|
+
console.warn(`Invalid transition: Cannot '${action}' from '${this.state}'`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.state = nextState;
|
|
17
|
+
return this.state;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
is(state) {
|
|
21
|
+
return this.state === state;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = StateMachine;
|