mastercontroller 1.3.13 → 1.3.15
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/MasterAction.js +302 -62
- package/MasterActionFilters.js +556 -82
- package/MasterControl.js +77 -44
- package/MasterCors.js +61 -19
- package/MasterPipeline.js +29 -6
- package/MasterRequest.js +579 -102
- package/MasterRouter.js +446 -75
- package/MasterSocket.js +380 -15
- package/MasterTemp.js +292 -10
- package/MasterTimeout.js +420 -64
- package/MasterTools.js +478 -77
- package/README.md +505 -0
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -29
- package/.github/workflows/ci.yml +0 -317
- package/PERFORMANCE_SECURITY_AUDIT.md +0 -677
- package/SENIOR_ENGINEER_AUDIT.md +0 -2477
- package/VERIFICATION_CHECKLIST.md +0 -726
- package/log/mastercontroller.log +0 -2
- package/test-json-empty-body.js +0 -76
- package/test-raw-body-preservation.js +0 -128
- package/test-v1.3.4-fixes.js +0 -129
package/MasterTemp.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
// version 0.0
|
|
1
|
+
// version 0.1.0 - FAANG-level refactor with bug fixes and complete feature set
|
|
2
2
|
|
|
3
|
+
const { logger } = require('./error/MasterErrorLogger');
|
|
4
|
+
|
|
5
|
+
// Configuration Constants
|
|
6
|
+
const TEMP_CONFIG = {
|
|
7
|
+
MAX_KEY_LENGTH: 255,
|
|
8
|
+
MAX_VALUE_SIZE: 10 * 1024 * 1024, // 10MB
|
|
9
|
+
MAX_KEYS: 10000
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* MasterTemp - Temporary data storage utility
|
|
14
|
+
*
|
|
15
|
+
* Provides a simple key-value store for temporary data within a request lifecycle.
|
|
16
|
+
* Thread-safe when used per-request (each request gets its own instance).
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Type-safe storage with validation
|
|
20
|
+
* - Reserved key protection
|
|
21
|
+
* - Prototype pollution prevention
|
|
22
|
+
* - Size limits for DoS protection
|
|
23
|
+
* - Complete CRUD operations
|
|
24
|
+
* - Utility methods (keys, size, isEmpty)
|
|
25
|
+
*
|
|
26
|
+
* @class MasterTemp
|
|
27
|
+
*/
|
|
3
28
|
class MasterTemp{
|
|
4
29
|
|
|
5
30
|
temp = {};
|
|
31
|
+
_reservedKeys = new Set(['temp', '_master', '__masterCache', '_reservedKeys', 'add', 'get', 'has', 'clear', 'clearAll', 'keys', 'size', 'isEmpty', 'toJSON']);
|
|
6
32
|
|
|
7
33
|
// Lazy-load master to avoid circular dependency (Google-style lazy initialization)
|
|
8
34
|
get _master() {
|
|
@@ -12,26 +38,282 @@ class MasterTemp{
|
|
|
12
38
|
return this.__masterCache;
|
|
13
39
|
}
|
|
14
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Validate key name for security and correctness
|
|
43
|
+
*
|
|
44
|
+
* @private
|
|
45
|
+
* @param {string} name - Key name to validate
|
|
46
|
+
* @throws {TypeError} If name is not a string
|
|
47
|
+
* @throws {Error} If name is empty, reserved, too long, or contains dangerous characters
|
|
48
|
+
*/
|
|
49
|
+
_validateKey(name) {
|
|
50
|
+
if (typeof name !== 'string') {
|
|
51
|
+
throw new TypeError('Key name must be a string');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!name || name.trim() === '') {
|
|
55
|
+
throw new Error('Key name cannot be empty');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (this._reservedKeys.has(name)) {
|
|
59
|
+
throw new Error(`Key name '${name}' is reserved and cannot be used`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (name.length > TEMP_CONFIG.MAX_KEY_LENGTH) {
|
|
63
|
+
throw new Error(`Key name exceeds maximum length (${TEMP_CONFIG.MAX_KEY_LENGTH} characters)`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Prevent prototype pollution
|
|
67
|
+
if (name === '__proto__' || name === 'constructor' || name === 'prototype') {
|
|
68
|
+
throw new Error(`Key name '${name}' is forbidden (prototype pollution protection)`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check for dangerous characters
|
|
72
|
+
if (/[<>{}[\]\\^`|]/.test(name)) {
|
|
73
|
+
throw new Error(`Key name contains invalid characters: ${name}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate value size for DoS protection
|
|
79
|
+
*
|
|
80
|
+
* @private
|
|
81
|
+
* @param {*} data - Value to validate
|
|
82
|
+
* @throws {Error} If value exceeds maximum size
|
|
83
|
+
*/
|
|
84
|
+
_validateValue(data) {
|
|
85
|
+
try {
|
|
86
|
+
const jsonStr = JSON.stringify(data);
|
|
87
|
+
if (jsonStr.length > TEMP_CONFIG.MAX_VALUE_SIZE) {
|
|
88
|
+
throw new Error(`Value exceeds maximum size (${TEMP_CONFIG.MAX_VALUE_SIZE} bytes)`);
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (e.message.includes('circular')) {
|
|
92
|
+
throw new Error('Value contains circular references and cannot be stored');
|
|
93
|
+
}
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Add or update temporary data
|
|
100
|
+
*
|
|
101
|
+
* @param {string} name - Key name for the data
|
|
102
|
+
* @param {*} data - Data to store (any JSON-serializable value)
|
|
103
|
+
* @returns {boolean} True if successful, false otherwise
|
|
104
|
+
* @throws {TypeError} If name is not a string
|
|
105
|
+
* @throws {Error} If name is reserved, invalid, or value is too large
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* temp.add('userId', 123);
|
|
109
|
+
* temp.add('userData', { name: 'John', email: 'john@example.com' });
|
|
110
|
+
* temp.add('items', [1, 2, 3]);
|
|
111
|
+
*/
|
|
15
112
|
add(name, data){
|
|
113
|
+
try {
|
|
114
|
+
this._validateKey(name);
|
|
115
|
+
this._validateValue(data);
|
|
16
116
|
|
|
17
|
-
|
|
18
|
-
this
|
|
117
|
+
// Check max keys limit
|
|
118
|
+
if (!this.has(name) && this.size() >= TEMP_CONFIG.MAX_KEYS) {
|
|
119
|
+
throw new Error(`Maximum number of keys (${TEMP_CONFIG.MAX_KEYS}) exceeded`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// CRITICAL FIX: Store in this.temp[name] not this[name]
|
|
123
|
+
this.temp[name] = data;
|
|
124
|
+
|
|
125
|
+
logger.debug({
|
|
126
|
+
code: 'MC_TEMP_ADD',
|
|
127
|
+
message: 'Temporary data added',
|
|
128
|
+
key: name
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return true;
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger.error({
|
|
135
|
+
code: 'MC_TEMP_ADD_ERROR',
|
|
136
|
+
message: 'Failed to add temporary data',
|
|
137
|
+
key: name,
|
|
138
|
+
error: error.message
|
|
139
|
+
});
|
|
140
|
+
throw error;
|
|
19
141
|
}
|
|
20
|
-
|
|
21
|
-
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get temporary data by key
|
|
146
|
+
*
|
|
147
|
+
* @param {string} name - Key name
|
|
148
|
+
* @param {*} [defaultValue] - Default value if key doesn't exist
|
|
149
|
+
* @returns {*} Stored value or defaultValue if not found
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const userId = temp.get('userId');
|
|
153
|
+
* const theme = temp.get('theme', 'dark'); // Returns 'dark' if not set
|
|
154
|
+
*/
|
|
155
|
+
get(name, defaultValue = undefined) {
|
|
156
|
+
try {
|
|
157
|
+
this._validateKey(name);
|
|
158
|
+
return this.has(name) ? this.temp[name] : defaultValue;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
logger.warn({
|
|
161
|
+
code: 'MC_TEMP_GET_ERROR',
|
|
162
|
+
message: 'Failed to get temporary data',
|
|
163
|
+
key: name,
|
|
164
|
+
error: error.message
|
|
165
|
+
});
|
|
166
|
+
return defaultValue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if key exists in temporary storage
|
|
172
|
+
*
|
|
173
|
+
* @param {string} name - Key name to check
|
|
174
|
+
* @returns {boolean} True if key exists, false otherwise
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* if (temp.has('userId')) {
|
|
178
|
+
* console.log('User ID is set');
|
|
179
|
+
* }
|
|
180
|
+
*/
|
|
181
|
+
has(name) {
|
|
182
|
+
try {
|
|
183
|
+
this._validateKey(name);
|
|
184
|
+
return Object.prototype.hasOwnProperty.call(this.temp, name);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Delete a single key from temporary storage
|
|
192
|
+
*
|
|
193
|
+
* @param {string} name - Key name to delete
|
|
194
|
+
* @returns {boolean} True if key was deleted, false if it didn't exist
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* temp.clear('userId'); // Remove userId from storage
|
|
198
|
+
*/
|
|
199
|
+
clear(name) {
|
|
200
|
+
try {
|
|
201
|
+
this._validateKey(name);
|
|
202
|
+
|
|
203
|
+
if (this.has(name)) {
|
|
204
|
+
delete this.temp[name];
|
|
205
|
+
|
|
206
|
+
logger.debug({
|
|
207
|
+
code: 'MC_TEMP_CLEAR',
|
|
208
|
+
message: 'Temporary data cleared',
|
|
209
|
+
key: name
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false;
|
|
216
|
+
|
|
217
|
+
} catch (error) {
|
|
218
|
+
logger.error({
|
|
219
|
+
code: 'MC_TEMP_CLEAR_ERROR',
|
|
220
|
+
message: 'Failed to clear temporary data',
|
|
221
|
+
key: name,
|
|
222
|
+
error: error.message
|
|
223
|
+
});
|
|
224
|
+
return false;
|
|
22
225
|
}
|
|
23
226
|
}
|
|
24
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Clear all temporary data
|
|
230
|
+
*
|
|
231
|
+
* @returns {number} Number of keys cleared
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* const cleared = temp.clearAll();
|
|
235
|
+
* console.log(`Cleared ${cleared} keys`);
|
|
236
|
+
*/
|
|
25
237
|
clearAll(){
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
238
|
+
try {
|
|
239
|
+
const count = this.size();
|
|
240
|
+
|
|
241
|
+
// CRITICAL FIX: Iterate over this.temp, not this
|
|
242
|
+
for (const key in this.temp) {
|
|
243
|
+
if (Object.prototype.hasOwnProperty.call(this.temp, key)) {
|
|
29
244
|
delete this.temp[key];
|
|
30
245
|
}
|
|
31
246
|
}
|
|
32
|
-
|
|
247
|
+
|
|
248
|
+
logger.debug({
|
|
249
|
+
code: 'MC_TEMP_CLEAR_ALL',
|
|
250
|
+
message: 'All temporary data cleared',
|
|
251
|
+
count
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return count;
|
|
255
|
+
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logger.error({
|
|
258
|
+
code: 'MC_TEMP_CLEAR_ALL_ERROR',
|
|
259
|
+
message: 'Failed to clear all temporary data',
|
|
260
|
+
error: error.message
|
|
261
|
+
});
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get all keys in temporary storage
|
|
268
|
+
*
|
|
269
|
+
* @returns {string[]} Array of key names
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* const keys = temp.keys();
|
|
273
|
+
* console.log('Stored keys:', keys); // ['userId', 'theme', 'items']
|
|
274
|
+
*/
|
|
275
|
+
keys() {
|
|
276
|
+
return Object.keys(this.temp);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get number of keys in temporary storage
|
|
281
|
+
*
|
|
282
|
+
* @returns {number} Number of stored keys
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* console.log(`Storage contains ${temp.size()} items`);
|
|
286
|
+
*/
|
|
287
|
+
size() {
|
|
288
|
+
return Object.keys(this.temp).length;
|
|
33
289
|
}
|
|
34
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Check if temporary storage is empty
|
|
293
|
+
*
|
|
294
|
+
* @returns {boolean} True if no keys stored, false otherwise
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* if (temp.isEmpty()) {
|
|
298
|
+
* console.log('No temporary data stored');
|
|
299
|
+
* }
|
|
300
|
+
*/
|
|
301
|
+
isEmpty() {
|
|
302
|
+
return this.size() === 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Convert temporary storage to plain JSON object
|
|
307
|
+
*
|
|
308
|
+
* @returns {Object} Plain object containing all key-value pairs
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* const snapshot = temp.toJSON();
|
|
312
|
+
* console.log(JSON.stringify(snapshot));
|
|
313
|
+
*/
|
|
314
|
+
toJSON() {
|
|
315
|
+
return { ...this.temp };
|
|
316
|
+
}
|
|
35
317
|
}
|
|
36
318
|
|
|
37
|
-
module.exports = { MasterTemp };
|
|
319
|
+
module.exports = { MasterTemp };
|