@63klabs/cache-data 1.2.2

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.
@@ -0,0 +1,416 @@
1
+ const { sanitize } = require("./utils");
2
+ const util = require('util');
3
+
4
+ /**
5
+ * A simple Debug and Logging class.
6
+ */
7
+ class DebugAndLog {
8
+
9
+ static #logLevel = -1;
10
+ static #expiration = -1;
11
+
12
+ static PROD = "PROD";
13
+ static TEST = "TEST";
14
+ static DEV = "DEV";
15
+
16
+ static ENVIRONMENTS = [DebugAndLog.PROD, DebugAndLog.TEST, DebugAndLog.DEV];
17
+
18
+ static ERROR = "ERROR"; // 0
19
+ static WARN = "WARN"; // 0
20
+ static LOG = "LOG"; // 0
21
+ static MSG = "MSG"; // 1
22
+ static DIAG = "DIAG"; // 3
23
+ static DEBUG = "DEBUG"; // 5
24
+
25
+ constructor() {
26
+ };
27
+
28
+ /**
29
+ * Set the log level.
30
+ * @param {number} logLevel 0 - 5
31
+ * @param {*} expiration YYYY-MM-DD HH:MM:SS format. Only set to specified level until this date
32
+ */
33
+ static setLogLevel(logLevel = -1, expiration = -1) {
34
+
35
+ if ( process.env.NODE_ENV === "production" && this.#logLevel > -1 ) {
36
+ DebugAndLog.warn("LogLevel already set, cannot reset. Ignoring call to DebugAndLog.setLogLevel("+logLevel+")");
37
+ } else {
38
+ if ( expiration !== -1 ) {
39
+ let time = new Date( expiration );
40
+ this.#expiration = time.toISOString();
41
+ } else {
42
+ this.#expiration = -1;
43
+ }
44
+
45
+ if ( logLevel === -1 || this.nonDefaultLogLevelExpired()) {
46
+ this.#logLevel = this.getDefaultLogLevel();
47
+ } else {
48
+ if ( logLevel > 0 && DebugAndLog.isProduction() ) {
49
+ DebugAndLog.warn("DebugAndLog: Production environment. Cannot set logLevel higher than 0. Ignoring call to DebugAndLog.setLogLevel("+logLevel+"). Default LogLevel override code should be removed before production");
50
+ this.#logLevel = this.getDefaultLogLevel();
51
+ } else {
52
+ this.#logLevel = logLevel;
53
+ DebugAndLog.msg("DebugAndLog: Override of log level default set: "+logLevel+". Default LogLevel override code should be removed before production");
54
+ if ( this.#expiration === -1 ) {
55
+ DebugAndLog.warn("DebugAndLog: Override of log level default set WITHOUT EXPIRATION. An expiration is recommended.");
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ };
62
+
63
+ static nonDefaultLogLevelExpired() {
64
+ let r = false;
65
+
66
+ if ( this.#expiration !== -1 ) {
67
+ let now = new Date();
68
+ if ( now.toISOString() > this.#expiration ) {
69
+ DebugAndLog.warn("DebugAndLog: Override of log level default expired. Call to DebugAndLog.setLogLevel() should be commented out or removed");
70
+ r = true;
71
+ }
72
+ }
73
+
74
+ return r;
75
+ }
76
+
77
+ /**
78
+ *
79
+ * @returns {string} The expiration date of the set log level
80
+ */
81
+ static getExpiration() {
82
+ return this.#expiration;
83
+ }
84
+
85
+ /**
86
+ *
87
+ * @returns {number} The current log level
88
+ */
89
+ static getLogLevel() {
90
+ if ( this.#logLevel === -1 ) {
91
+ this.setLogLevel();
92
+ }
93
+
94
+ return this.#logLevel;
95
+
96
+ }
97
+
98
+ /**
99
+ * Check process.env for an environment variable named
100
+ * env, deployEnvironment, environment, or stage. If they
101
+ * are not set it will return DebugAndLog.PROD which
102
+ * is considered safe (most restrictive)
103
+ * Note: This is the application environment, not the NODE_ENV
104
+ * @returns {string} The current environment.
105
+ */
106
+ static getEnv() {
107
+ var possibleVars = ["env", "deployEnvironment", "environment", "stage"]; // this is the application env, not the NODE_ENV
108
+ var env = DebugAndLog.PROD; // if env or deployEnvironment not set, fail to safe
109
+
110
+ if ( "env" in process ) {
111
+ for (let i in possibleVars) {
112
+ let e = possibleVars[i];
113
+ let uE = possibleVars[i].toUpperCase();
114
+ if (e in process.env && process.env[e] !== "" && process.env[e] !== null) {
115
+ env = process.env[e].toUpperCase();
116
+ break; // break out of the for loop
117
+ } else if (uE in process.env && process.env[uE] !== "" && process.env[uE] !== null) {
118
+ env = process.env[uE].toUpperCase();
119
+ break; // break out of the for loop
120
+ }
121
+ };
122
+ }
123
+ return (DebugAndLog.ENVIRONMENTS.includes(env) ? env : DebugAndLog.PROD);
124
+ };
125
+
126
+ /**
127
+ *
128
+ * @returns {number} log level
129
+ */
130
+ static getDefaultLogLevel() {
131
+ var possibleVars = ["detailedLogs", "logLevel"];
132
+ var logLevel = 0;
133
+
134
+ if ( DebugAndLog.isNotProduction() ) { // PROD is always at logLevel 0. Always.
135
+
136
+ if ( "env" in process ) {
137
+ for (let i in possibleVars) {
138
+ let lev = possibleVars[i];
139
+ let uLEV = possibleVars[i].toUpperCase();
140
+ if (lev in process.env && !(Number.isNaN(process.env[lev])) && process.env[lev] !== "" && process.env[lev] !== null) {
141
+ logLevel = Number(process.env[lev]);
142
+ break; // break out of the for loop
143
+ } else if (uLEV in process.env && !(Number.isNaN(process.env[uLEV])) && process.env[uLEV] !== "" && process.env[uLEV] !== null) {
144
+ logLevel = Number(process.env[uLEV]);
145
+ break; // break out of the for loop
146
+ }
147
+ };
148
+ }
149
+
150
+ }
151
+
152
+ return logLevel;
153
+ };
154
+
155
+ /**
156
+ *
157
+ * @returns {boolean}
158
+ */
159
+ static isNotProduction() {
160
+ return ( !DebugAndLog.isProduction() );
161
+ };
162
+
163
+ /**
164
+ *
165
+ * @returns {boolean}
166
+ */
167
+ static isProduction() {
168
+ return ( DebugAndLog.getEnv() === DebugAndLog.PROD );
169
+ };
170
+
171
+ /**
172
+ *
173
+ * @returns {boolean}
174
+ */
175
+ static isDevelopment() {
176
+ return ( DebugAndLog.getEnv() === DebugAndLog.DEV );
177
+ };
178
+
179
+ /**
180
+ *
181
+ * @returns {boolean}
182
+ */
183
+ static isTest() {
184
+ return ( DebugAndLog.getEnv() === DebugAndLog.TEST );
185
+ };
186
+
187
+ /**
188
+ * Write a log entry.
189
+ * The format used will be "[TAG] message"
190
+ * @param {string} tag This will appear first in the log in all caps between square brackets ex: [TAG]
191
+ * @param {string} message The message to be displayed. May also be a delimited log string
192
+ * @param {object|null} obj An object to include in the log entry
193
+ */
194
+ static async writeLog(tag, message, obj = null) {
195
+
196
+ const logLevels = {
197
+ error: console.error,
198
+ warn: console.warn,
199
+ log: console.log,
200
+ info: console.info,
201
+ debug: console.debug
202
+ };
203
+
204
+ const DEFAULT_LEVEL = 'info';
205
+ const FORMAT_WITH_OBJ = '[%s] %s | %s';
206
+ const FORMAT_WITHOUT_OBJ = '[%s] %s';
207
+
208
+ // const baseLog = function(level, tag, message, obj = null) {
209
+ // // Validate inputs
210
+ // if (typeof level !== 'string') {
211
+ // throw new TypeError('Log level must be a string');
212
+ // }
213
+
214
+ // // Ensure tag and message are strings
215
+ // const safeTag = String(tag || '');
216
+ // const safeMessage = String(message || '');
217
+
218
+ // // Validate log level is allowed
219
+ // if (!Object.prototype.hasOwnProperty.call(logLevels, level)) {
220
+ // level = 'info'; // Default to info if invalid level
221
+ // }
222
+
223
+ // const logFn = logLevels[level];
224
+
225
+ // try {
226
+ // let formattedMessage;
227
+ // if (obj !== null) {
228
+ // formattedMessage = util.format(
229
+ // '[%s] %s | %s',
230
+ // safeTag,
231
+ // safeMessage,
232
+ // util.inspect(sanitize(obj), { depth: null })
233
+ // );
234
+ // } else {
235
+ // formattedMessage = util.format(
236
+ // '[%s] %s',
237
+ // safeTag,
238
+ // safeMessage
239
+ // );
240
+ // }
241
+ // logFn(formattedMessage);
242
+ // } catch (error) {
243
+ // console.error('Logging failed:', error);
244
+ // }
245
+ // };
246
+ const baseLog = function(level, tag, message, obj = null) {
247
+ // Early return for invalid input
248
+ if (typeof level !== 'string') {
249
+ throw new TypeError('Log level must be a string');
250
+ }
251
+
252
+ // Use logical OR for faster undefined/null checks
253
+ const safeTag = String(tag || '');
254
+ const safeMessage = String(message || '');
255
+
256
+ // Direct property lookup is faster than hasOwnProperty
257
+ const logFn = logLevels[level] || logLevels[DEFAULT_LEVEL];
258
+
259
+ try {
260
+ // Single util.format call with conditional arguments
261
+ if (obj !== null) {
262
+ logFn(
263
+ util.format(
264
+ FORMAT_WITH_OBJ,
265
+ safeTag,
266
+ safeMessage,
267
+ util.inspect(sanitize(obj), { depth: null })
268
+ )
269
+ );
270
+ } else {
271
+ logFn(
272
+ util.format(
273
+ FORMAT_WITHOUT_OBJ,
274
+ safeTag,
275
+ safeMessage
276
+ )
277
+ );
278
+ }
279
+ } catch (error) {
280
+ console.error('Logging failed:', error);
281
+ }
282
+ };
283
+
284
+ // Create individual logging functions using the base function
285
+ const error = (tag, message, obj) => baseLog('error', tag, message, obj);
286
+ const warn = (tag, message, obj) => baseLog('warn', tag, message, obj);
287
+ const log = (tag, message, obj) => baseLog('log', tag, message, obj);
288
+ const info = (tag, message, obj) => baseLog('info', tag, message, obj);
289
+ const debug = (tag, message, obj) => baseLog('debug', tag, message, obj);
290
+
291
+ let lvl = DebugAndLog.getLogLevel();
292
+ tag = tag.toUpperCase();
293
+
294
+ // if ( obj !== null ) {
295
+ // let msgObj = obj;
296
+ // if ( Array.isArray(msgObj)) { msgObj = { array: msgObj};}
297
+ // if ( ""+msgObj === "[object Object]" || ""+msgObj === "[object Array]") {
298
+ // msgObj = JSON.stringify(sanitize(msgObj));
299
+ // }
300
+ // message += " | "+msgObj;
301
+ // }
302
+
303
+ switch (tag) {
304
+ case DebugAndLog.ERROR:
305
+ error(tag, message, obj);
306
+ break;
307
+ case DebugAndLog.WARN:
308
+ warn(tag, message, obj);
309
+ break;
310
+ case DebugAndLog.MSG:
311
+ if (lvl >= 1) { info(tag, message, obj); } // 1
312
+ break;
313
+ case DebugAndLog.DIAG:
314
+ if (lvl >= 3) { debug(tag, message, obj); } //3
315
+ break;
316
+ case DebugAndLog.DEBUG:
317
+ if (lvl >= 5) { debug(tag, message, obj); } //5
318
+ break;
319
+ default: // log
320
+ log(tag, message, obj);
321
+ break;
322
+ }
323
+
324
+ return true;
325
+ };
326
+
327
+ /**
328
+ * Level 5 - Verbose Values and Calculations and Stack Traces
329
+ * @param {string} message
330
+ * @param {object} obj
331
+ */
332
+ static async debug(message, obj = null) {
333
+ return DebugAndLog.writeLog(DebugAndLog.DEBUG, message, obj);
334
+ };
335
+
336
+ /**
337
+ * Level 3 - Verbose timing and counts
338
+ * @param {string} message
339
+ * @param {object} obj
340
+ */
341
+ static async diag(message, obj = null) {
342
+ return DebugAndLog.writeLog(DebugAndLog.DIAG, message, obj);
343
+ };
344
+
345
+ /**
346
+ * Level 1 - Short messages and status
347
+ * @param {string} message
348
+ * @param {object} obj
349
+ */
350
+ static async msg(message, obj = null) {
351
+ return DebugAndLog.writeLog(DebugAndLog.MSG, message, obj);
352
+ };
353
+
354
+ /**
355
+ * Level 1 - Short messages and status
356
+ * (same as DebugAndLog.msg() )
357
+ * @param {string} message
358
+ * @param {object} obj
359
+ */
360
+ static async message(message, obj = null) {
361
+ return DebugAndLog.msg(message, obj);
362
+ };
363
+
364
+ /**
365
+ * Level 0 - Production worthy log entries that are not errors or warnings
366
+ * These should be formatted in a consistent manner and typically only
367
+ * one entry produced per invocation. (Usually produced at the end of a
368
+ * script's execution)
369
+ * @param {string} message The message, either a text string or fields separated by | or another character you can use to parse your logs
370
+ * @param {string} tag Optional. The tag that appears at the start of the log. Default is LOG. In logs it will appear at the start within square brackets '[LOG] message' You can use this to filter when parsing log reports
371
+ * @param {object} obj
372
+ */
373
+ static async log(message, tag = DebugAndLog.LOG, obj = null) {
374
+ return DebugAndLog.writeLog(tag, message, obj);
375
+ };
376
+
377
+ /**
378
+ * Level 0 - Warnings
379
+ * Errors are handled and execution continues.
380
+ * ClientRequest validation should be done first, and if we received an invalid
381
+ * request, then a warning, not an error, should be logged even though an
382
+ * error is returned to the client (error is on client side, not here,
383
+ * but we want to keep track of client errors).
384
+ * Requests should be validated first before all other processing.
385
+ * @param {string} message
386
+ * @param {object} obj
387
+ */
388
+ static async warn(message, obj = null) {
389
+ DebugAndLog.writeLog(DebugAndLog.WARN, message, obj);
390
+ };
391
+
392
+ /**
393
+ * Level 0 - Warnings
394
+ * (same as DebugAndLog.warn() )
395
+ * @param {string} message
396
+ * @param {object} obj
397
+ */
398
+ static async warning(message, obj = null) {
399
+ DebugAndLog.warn(message, obj);
400
+ };
401
+
402
+ /**
403
+ * Level 0 - Errors
404
+ * Errors cannot be handled in a way that will allow continued execution.
405
+ * An error will be passed back to the client. If a client sent a bad
406
+ * request, send a warning instead.
407
+ * @param {string} message
408
+ * @param {object} obj
409
+ */
410
+ static async error(message, obj = null) {
411
+ DebugAndLog.writeLog(DebugAndLog.ERROR, message, obj);
412
+ };
413
+
414
+ };
415
+
416
+ module.exports = DebugAndLog;
@@ -0,0 +1,71 @@
1
+
2
+ /**
3
+ * Create an object that is able to return a copy and not
4
+ * a reference to its properties.
5
+ */
6
+ class ImmutableObject {
7
+
8
+ /**
9
+ *
10
+ * @param {object} obj The object you want to store as immutable. You can use keys for sub-objects to retreive those inner objects later
11
+ * @param {boolean} finalize Should we lock the object right away?
12
+ */
13
+ constructor(obj = null, finalize = false) {
14
+ this.obj = obj;
15
+ this.locked = false;
16
+ if ( finalize ) {
17
+ this.finalize();
18
+ }
19
+ };
20
+
21
+ /**
22
+ * Locks the object so it can't be changed.
23
+ */
24
+ lock() {
25
+ if ( !this.locked ) {
26
+ /* We'll stringify the object to break all references,
27
+ then change it back to an object */
28
+ this.obj = JSON.parse(JSON.stringify(this.obj));
29
+ this.locked = true;
30
+ }
31
+ };
32
+
33
+ /**
34
+ * Finalizes the object by immediately locking it
35
+ * @param {object|null} obj // The object you want to store as immutable. You can use keys for sub-objects to retreive those inner objects later
36
+ */
37
+ finalize(obj = null) {
38
+ if ( !this.locked ) {
39
+ if ( obj !== null ) { this.obj = obj; }
40
+ this.lock();
41
+ }
42
+ };
43
+
44
+ /**
45
+ *
46
+ * @returns A copy of the object, not a reference
47
+ */
48
+ toObject() {
49
+ return this.get();
50
+ }
51
+
52
+ /**
53
+ * Get a copy of the value, not a reference, via an object's key
54
+ * @param {string} key Key of the value you wish to return
55
+ * @returns {*} The value of the supplied key
56
+ */
57
+ get(key = "") {
58
+ /* we need to break the reference to the orig obj.
59
+ tried many methods but parse seems to be only one that works
60
+ https://itnext.io/can-json-parse-be-performance-improvement-ba1069951839
61
+ https://medium.com/coding-at-dawn/how-to-use-the-spread-operator-in-javascript-b9e4a8b06fab
62
+ */
63
+ //return {...this.connection[key]}; // doesn't make a deep copy
64
+ //return Object.assign({}, this.connection[key]);
65
+
66
+ return JSON.parse(JSON.stringify( (key === "" || !(key in this.obj)) ? this.obj : this.obj[key] ));
67
+
68
+ };
69
+ };
70
+
71
+ module.exports = ImmutableObject;