UNPKG

mcode-data

Version:

Our Data Handling functions. These support type checking and type coercion and conversion of Objects--ints, string, number, JSON, any--to commonize data handling and type checking.

1,325 lines (1,190 loc) 79.1 kB
// #region F I L E // <copyright file="mcode-data/index.js" company="MicroCODE Incorporated">Copyright © 2022-2024 MicroCODE, Inc. Troy, MI</copyright><author>Timothy J. McGuire</author> // #region M O D U L E // #region D O C U M E N T A T I O N /** * Project: MicroCODE MERN Applications * Customer: Internal + MIT xPRO Course * @module 'mcode-data.js' * @memberof mcode * @created January 2022-2024 * @author Timothy McGuire, MicroCODE, Inc. * @description > * MicroCODE Common Data Utitlities * * LICENSE: * -------- * MIT License: MicroCODE.mcode-data * * Copyright (c) 2022-2025 Timothy McGuire, MicroCODE, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * * DESCRIPTION: * ------------ * This module implements the MicroCODE's Common JavaScript functions for data handling. * * * REFERENCES: * ----------- * 1. MIT xPRO Course: Professional Certificate in Coding: Full Stack Development with MERN. * * 2. LADDERS® source code: MACRO-11, MACRO-32, C#, and JavaScript. * * * * * MODIFICATIONS: * -------------- * Date: By-Group: Rev: Description: * * 30-Jan-2024 TJM-MCODE {0001} New module for common reusable JavaScript data handling functions. * 01-Feb-2024 TJM-MCODE {0002} Changed to the Universal Module Definition (UMD) pattern to support AMD, * CommonJS/Node.js, and browser global in our exported module. * 01-Feb-2024 TJM-MCODE {0003} Swap() and Call() now throw an error if the 'keys' and 'values' lists are not the same length, * instead of logging the error and returning a default value. * 22-Aug-2024 TJM-MCODE {0004} Corrected isJson() to rely on 1st character being '{' to determine JSON string * it was returning true for any string that contained a '{' character, * this was signaling 'true' for HTMX templates that contained '{{variable}}'. * 05-Oct-2024 TJM-MCODE {0005} Added 'uuidDecode()' function to decode UUID strings into their component parts. * * 19-Feb-2025 TJM-MCODE {0006} v0.5.08 - updated 'httpStatus()' to use a STATIC copy of HTTP codes JSON for speed. * - updated all UUID string lists to STATIC as well. * 17-Apr-2025 TJM-MCODE {0007} v0.5.09 - added 'isHtml()' function to check if a string is HTML, * updated 'isJson()' to use same check. * 23-Oct-2025 TJM-MCODE {0008} v0.6.03 - added 'classExport()' to return the created class constructor to support SSR * and ESM module loading, especially in HTMX applications. * Also added: sleep(), ifNumber(), encodeJson(), generateRandomId(). * * * * NOTE: This module follows MicroCODE's JavaScript Style Guide and Template JS file, see: * * o https://github.com/MicroCODEIncorporated/JavaScriptSG * o https://github.com/MicroCODEIncorporated/TemplatesJS * */ // #endregion // #region I M P O R T S // #endregion // #region T Y P E S // #endregion // #region I N T E R F A C E S // #endregion // #region C O N S T A N T S, F U N C T I O N S – P U B L I C // MicroCODE: define this module's name for our 'mcode-log' package const MODULE_NAME = 'mcode-data.js'; // Static copy of HTTP codes for httpStatus() function for speed - {0006} const HTTP_CODES = { //1xx: Informational responses (CYAN) "100": "Continue", "101": "Switching Protocols", "102": "Processing", "103": "Early Hints", "104": "Checkpoint (WebDAV)", "105": "Switch Proxy (WebDAV)", "106": "Processing (WebDAV)", "107": "Handoff (WebDAV)", //2xx: Success responses (GREEN) "200": "OK", // for READ "201": "Created", //for CREATE "202": "Accepted", // for PUT/PATCH/DELETE "203": "Non-Authoritative Information", "204": "No Content", "205": "Reset Content", "206": "Partial Content", "207": "Multi-Status", "208": "Already Reported", "226": "IM Used", //3xx: Redirection responses (YELLOW) "300": "Multiple Choices", "301": "Moved Permanently", "302": "Found", "303": "See Other", "304": "Not Modified", "305": "Use Proxy", "307": "Temporary Redirect", "308": "Permanent Redirect", "309": "Resume Incomplete (WebDAV)", "310": "Too Many Redirects", //4xx: Client error responses (RED) "400": "Bad Request", // missing Record Fields during CREATE or PUT "401": "Unauthorized", "402": "Payment Required", "403": "Forbidden", "404": "Not Found", // no record to update during PATCH or PUT "405": "Method Not Allowed", "406": "Not Acceptable", "407": "Proxy Authentication Required", "408": "Request Timeout", "409": "Conflict", "410": "Gone", "411": "Length Required", "412": "Precondition Failed", "413": "Payload Too Large", "414": "URI Too Long", "415": "Unsupported Media Type", "416": "Range Not Satisfiable", "417": "Expectation Failed", "418": "I'm a Teapot", // indicates that the server refuses to brew coffee because it is a teapot. (An April fool’s joke from 1998) "419": "Page Expired (Laravel)", "421": "Misdirected Request", "422": "Unprocessable Entity", "423": "Locked", "424": "Failed Dependency", "425": "Too Early", "426": "Upgrade Required", "428": "Precondition Required", "429": "Too Many Requests", "431": "Request Header Fields Too Large", "440": "Login Timeout (Microsoft)", "444": "Connection Closed Without Response (NGINX)", "449": "Retry With (Microsoft - IIS specific)", "450": "Blocked by Windows Parental Controls (Microsoft)", "451": "Unavailable For Legal Reasons", "499": "Client Closed Request (NGINX)", //5xx: Server error responses (MAGENTA) "500": "Internal Server Error", // unexpected Code Exceptions "501": "Not Implemented", // hit stubs or calls to non-existent endpoints "502": "Bad Gateway", "503": "Service Unavailable", "504": "Gateway Timeout", "505": "HTTP Version Not Supported", "506": "Variant Also Negotiates", "507": "Insufficient Storage", "508": "Loop Detected", "509": "Bandwidth Limit Exceeded (Apache/cPanel)", "510": "Not Extended", "511": "Network Authentication Required", "520": "Unknown Error (Cloudflare)", "521": "Web Server Is Down (Cloudflare)", "522": "Connection Timed Out (Cloudflare)", "523": "Origin Is Unreachable (Cloudflare)", "524": "A Timeout Occurred (Cloudflare)", "525": "SSL Handshake Failed (Cloudflare)", "526": "Invalid SSL Certificate (Cloudflare)", "527": "Railgun Error (Cloudflare)", "530": "Origin DNS Error (Cloudflare-FTP)", "598": "Network Read Timeout Error (Unofficial)", "599": "Network Connect Timeout Error (Azure/AWS)", }; // U U I D - V A R I A N T S const UUID_VARIANTS = [ "NCS compatibility", "NCS compatibility", "RFC 4122, RFC 9562", "Microsoft GUIDs", ]; // U U I D - V E R S I O N S // NCS compatibility (variant 0, 1) const UUID_VAR1_VERSIONS = [ "Undefined / Reserved / NIL", // 0 " NCS Security Version", // 1 " NCS Security Version", // 2 " NCS Security Version", // 3 " NCS Security Version", // 4 " NCS Security Version", // 5 " NCS Security Version", // 6 " NCS Security Version", // 7 " NCS Security Version", // 8 " NCS Security Version", // 9 " NCS Security Version", // 10 " NCS Security Version", // 11 " NCS Security Version", // 12 " NCS Security Version", // 13 " NCS Security Version", // 14 "Undefined / Reserved / MAX", // 15 ]; // RFC 4122 (Leach-Salz) (variant 2) const UUID_VAR2_VERSIONS = [ "Undefined / Reserved / NIL", // 0 "Gregorian Unordered Timestamp", // 1 "DCE Security (POSIX)", // 2 "Name-Based (MD5 Hash)", // 3 "Random-Based Number", // 4 "Name Based (SHA-1 Hash)", // 5 "Gregorian Ordered Timestamp", // 6 "Unix Epoch Timestamp", // 7 "Custom Encoding Format", // 8 "Reserved for future defnition", // 9 "Reserved for future defnition", // 10 "Reserved for future defnition", // 11 "Reserved for future defnition", // 12 "Reserved for future defnition", // 13 "Reserved for future defnition", // 14 "Undefined / Reserved / MAX", // 15 ]; // Microsoft GUIDs (variant 3) const UUID_VAR3_VERSIONS = [ "Undefined / Reserved / NIL", // 0 "Microsoft GUID Version 1", // 1 "Microsoft GUID Version 2", // 2 "Microsoft GUID Version 3", // 3 "Microsoft GUID Version 4", // 4 "Microsoft GUID Version 5", // 5 "Microsoft GUID Version 6", // 6 "Microsoft GUID Version 7", // 7 "Microsoft GUID Version 8", // 8 "Microsoft GUID Version 9", // 9 "Microsoft GUID Version 10", // 10 "Microsoft GUID Version 11", // 11 "Microsoft GUID Version 12", // 12 "Microsoft GUID Version 13", // 13 "Microsoft GUID Version 14", // 14 "Undefined / Reserved / MAX", // 15 ]; /** * @namespace mcode * @desc mcode namespace containing functions and constants. */ const mcode = { /** * @func property * @memberof mcode * @desc Creates a property with get, set methods, validator, and on change events. * @api public * @param {object} options optional params to define the property * {initial: value, readonly: boolean, immutable: boolean, validator: function, onChange: function}. * @returns {object} a property object with get, set, type, reset, isReadOnly, and isImmutable methods. */ property: function (options = {}) { // internal variables const _initial = options?.initial; const _readonly = options.readonly || false; const _immutable = options.immutable || false; // optional functions const _validator = options.validator || function () {return true;}; const _onChange = options.onChange || function () {}; // the actual value of the property let _value = options.initial || undefined; return { get: function () { return _value; }, set: function (value) { if (_immutable && (_value !== undefined)) { throw new Error('「mcode.property」 This property is immutable.'); } if (_readonly) { throw new Error('「mcode.property」 This property is read-only.'); } if (_validator(value)) { const _last = _value; _value = value; _onChange(_last, _value); } else { throw new Error('「mcode.property」 Invalid property value.'); } }, type: function () { return typeof _value; }, reset: function () { const _last = _value; _value = _initial; _onChange(_last, _value); }, isReadOnly: function () { return _readonly; }, isImmutable: function () { return _immutable; }, hasChanged: function () { return _value !== _initial; } }; }, /** * @func getProperty * @memberof mcode * @api private * @desc Safely resolve a nested property using an array of keys. * @param {object} source - Root object from which to read. * @param {Array<string>} path - Ordered list of keys to traverse. * @returns {any} Resolved value or undefined when not found. */ getProperty: function (source, path) { return path.reduce((keyPath, key) => { if (keyPath && Object.prototype.hasOwnProperty.call(keyPath, key)) { return keyPath[key]; } return undefined; }, source); }, /** * @func setProperty * @memberof mcode * @desc Assign a nested property on the target object, creating scopes as needed. * @api private * @param {object} target - Object receiving the assignment. * @param {Array<string>} path - Ordered list of keys to reach the leaf property. * @param {any} value - Value to assign at the terminal path segment. * @returns {void} */ setProperty: function (target, path, value) { let current = target; for (let index = 0; index < path.length - 1; index += 1) { const key = path[index]; if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[path[path.length - 1]] = value; }, /** * @func contextProperty * @memberof mcode * @desc Define a getter/setter proxy that syncs instance properties to context paths. * @api public * @param {Class} instance - Class instance being updated, must have a 'context' object and 'syncDomContext()' method. * @param {string} property - Property name exposed on the instance. * @param {Array<string>} path - Path segments within the context object. * @param {object} [options] - Optional configuration flags. * @param {boolean} [options.sync=true] - Whether to resync the DOM context when setting. * @returns {void} */ contextProperty: function (instance, property, path, options = {}) { const {sync = true} = options; if (!instance || typeof instance !== 'object') { throw new Error('[mcode] Invalid instance provided to contextProperty.'); } if (!instance.syncDomContext || typeof instance.syncDomContext !== 'function') { throw new Error('[mcode] Invalid syncDomContext() method provided to contextProperty.'); } if (!instance.context || typeof instance.context !== 'object') { throw new Error('[mcode] Invalid context object provided to contextProperty.'); } Object.defineProperty(instance, property, { configurable: true, enumerable: false, get() { return mcode.getProperty(instance?.context, path); }, set(value) { mcode.setProperty(instance?.context, path, value); if (sync) { instance?.syncDomContext(); } } }); }, /** * @func isString * @memberof mcode * @desc Checks whether or not a object is a JS String. * @api public * @param {object} jsObject JavaScript object to be tested * @returns {boolean} a value indicating whether or not the object is a STRING. */ isString: function (jsObject) { return Object.prototype.toString.call(jsObject) === '[object String]'; }, /** * @method isObject * @memberof mcode * @desc Checks whether or not a object is a JS Object. * @api public * @param {object} jsObject JavaScript object to be tested * @returns {boolean} a value indicating whether or not the object is an OBJECT. */ isObject: function (jsObject) { if (mcode.isString(jsObject)) return false; return typeof jsObject === 'object' && jsObject !== null && !Array.isArray(jsObject) && typeof jsObject !== 'function'; }, /** * @method isArray * @memberof mcode * @desc Checks whether or not an object is a JS Array. * @api public * @param {object} jsObject JavaScript object to be tested * @returns {boolean} a value indicating whether or not the object is an ARRAY. */ isArray: function (jsObject) { return Array.isArray(jsObject); }, /** * @method isFunction * @memberof mcode * @desc Checks whether or not an object is a JS Function. * @api public * @param {object} jsObject JavaScript object to be tested * @returns {boolean} a value indicating whether or not the object is a FUNCTION. */ isFunction: function (jsObject) { if (mcode.isString(jsObject)) return false; return typeof jsObject === 'function'; }, /** * @func isNumber * @memberof mcode * @desc Checks whether or not an object is a JS Number and *not* NaN. * @api public * @param {any} numberToCheck as a number of some type * @returns {boolean} a value indicating whether or not the object is valid Number. */ isNumber: function (numberToCheck) { // NOTE: this compare will fail for NaN // eslint-disable-next-line no-self-compare return (typeof numberToCheck === 'number') && (numberToCheck === numberToCheck); }, /** * @function ifNumber * @memberof mcode * @desc Checks if a value is a valid number; if not, returns a default value. * @param {number} value The value to check. * @param {number} defaultValue The default value to return if the check fails. * @returns {number} The original value if valid, otherwise the default value. */ ifNumber: function (value, defaultValue = 0) { return Number.isFinite(value) ? value : defaultValue; }, /** * @func isNaN * @memberof mcode * @desc Checks whether or not an object is a double-precision 'Not-a-Number (Nan)'. * @api public * @param {any} numberToCheck as a number of some type * @returns {boolean} a value indicating whether or not the object is NaN. */ isNaN: function (numberToCheck) { // NOTE: this compare will always be true for NaN, and only for NaN // eslint-disable-next-line no-self-compare return (numberToCheck !== numberToCheck); }, /** * @func isJson * @memberof mcode * @desc Quickly checks a string for embedded JSON data. * @api public * @param {object} object string to be tested * @returns {boolean} a value indicating whether or not the object is a JSON string. */ isJson: function (object) { try { if (typeof object !== 'string') return false; // if this string begins with '{' and ends with '}' its very likely legal JSON code const trimmed = object.trim(); return trimmed.startsWith('{') && trimmed.endsWith('}'); } catch { return false; // *not* JSON and not parsable } }, /** * @func isHtml * @memberof mcode * @desc Quickly checks a string for HTML content. * @api public * @param {object} object string to be tested * @returns {boolean} a value indicating whether or not the object is HTML string. */ isHtml: function (object) { try { if (typeof object !== 'string') return false; // if this string begins with '<' and ends with '>' its very likely legal HTML code const trimmed = object.trim(); return trimmed.startsWith('<') && trimmed.endsWith('>'); } catch { return false; // *not* HTML and not parsable } }, /** * @func isDomAvailable * @memberof mcode * @desc Determine whether DOM APIs are available in the current environment. * @api public * @returns {boolean} True when document access is supported. */ isDomAvailable: function () { return typeof document !== 'undefined' && typeof document.getElementById === 'function'; }, /** * @func isUndefined * @memberof mcode * @desc Checks whether or not an object is a 'undefined'. * @api public * @param {any} objectToCheck as a variable of some type * @returns {boolean} a value indicating whether or not the object is UNDEFINED. */ isUndefined: function (objectToCheck) { // return true if 'objectToCheck' is UNDEFINED or NULL return ((typeof objectToCheck === 'undefined') || (objectToCheck === null)); }, /** * @func isDate * @memberof mcode * @desc Checks whether or not an object is a JS Date. * @param {object} objectToCheck * @returns {boolean} a value indicating whether or not the object is a DATE. */ isDate: function (objectToCheck) { // return true if 'objectToCheck' is DATE Value return (objectToCheck instanceof Date); }, /** * @func isTimeStamp * @memberof mcode * @desc Checks whether or not an object is a Timestampe, i.e.: a JS Date. * @param {object} objectToCheck * @returns {boolean} a value indicating whether or not the object is a TIMESTAMP. */ isTimeStamp: function (objectToCheck) { // return true if 'objectToCheck' is DATE/TIMESTAMP Value return (objectToCheck instanceof Date); }, /** * @func octify * @memberof mcode * @desc Converts a string to an octal string. * @api public * @param {string} text - The string to be converted to an octal string of bytes. * @returns {string} The octal string. * @example * mcode.octify('Hello, World!'); // returns: "110 145 154 154 157 54 40 127 157 162 154 144 41" */ octify(text) { const buffer = Buffer.from(text, 'utf8'); // Convert each byte to its octal representation const octArray = [...buffer].map((byte) => byte.toString(8).padStart(3, '0')); // Join the octal values into a string separated by spaces return octArray.join(' '); }, /** * @func hexify * @memberof mcode * @desc Converts a string to a hexadecinal string of bytes. * @api public * @param {string} text the string to be converted to a hex string. * @returns {string} the hex string. * @example * mcode.hexify('Hello, World!'); // returns: "48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21" */ hexify(text) { const buffer = Buffer.from(text, 'utf8'); const hex = buffer.toString('hex'); // Format the hex string into groups of 2 characters (1 byte) return hex.match(/.{1,2}/g).join(' '); }, /** * @func default * @memberof mcode * @desc Evaluates a param and returns a default if the param is null, undefined, * or empty (strings, arrays, objects, or primitives). * @api public * @param {any} anyItem The JavaScript entity being tested. * @param {any} defaultItem The item to be returned if 'anyItem' is null, undefined, or empty. * @returns {any} The original item or the default if empty/null/undefined. * @example * err = mcode.default(err, 'Undefined error occurred'); // default to recognizable message * data = mcode.default(data, []); // Default to empty array * config = mcode.default(config, {}); // Default to empty object * count = mcode.default(count, 1); // Default to 1 if count is 0 * * log(mcode.default(undefined, 'Default')); // 'Default' * log(mcode.default(null, 'Default')); // 'Default' * log(mcode.default('', 'Default')); // 'Default' * log(mcode.default(0, 1)); // 1 (0 is 'empty', returns default) * log(mcode.default(42, 1)); // 42 (non-zero number, returns original) * log(mcode.default([], [])); // [] * log(mcode.default(['item'], ['default'])); // ['item'] * log(mcode.default({}, {})); // {} * log(mcode.default({ key: 'value' }, { def: 'default' })); * // { key: 'value' } * log(mcode.default(true, false)); // true * log(mcode.default('Hello', 'Default')); // 'Hello' */ default(anyItem, defaultItem) { // handle null, undefined, or empty values if (anyItem === null || anyItem === undefined) { return defaultItem; } // handle empty strings if (typeof anyItem === 'string' && anyItem === '') { return defaultItem; } // handle unassigned values (0 is treated as 'unassigned') if (typeof anyItem === 'number' && anyItem === 0) { return defaultItem; } // handle empty arrays if (Array.isArray(anyItem) && anyItem.length === 0) { return defaultItem; } // Handle empty objects (excluding null prototypes or non-object types) if (typeof anyItem === 'object' && anyItem !== null && Object.keys(anyItem).length === 0) { return defaultItem; } // return the original item for all other cases (primitives, non-empty arrays/objects, etc.) return anyItem; }, /** * @func extractId * @memberof mcode * @desc Extracts an alpha-numberic ID Field from a string, intended to be a unique portion of a common string. * @param {string} objectName typically a file name, but can be any string, to extract an ID Field from. * @returns {string} the extracted ID Field. * * @example * * Rules for extracting the ID Field: * * 1. The ID Field is assumed to be the first alpha-numeric field in the string. * 2. The ID Field is assumed to be Letters + Numbers, with no spaces or special characters. * 3. The ID Field is assumed to be either at the beginning or end of the string, or separated by non-alpha-numeric characters. * 4. The ID Field could have lowercase 'placeholders' for numbers, like 'PxCy' or 'PnCn' for 'P1C2'. * * const str1 = "CG_BRKE01_20231116.L5K"; * const str2 = "CG_BRKE03_20231116.L5K"; * * log(extractIdField(str1)); // Expected output: "BRKE01" * log(extractIdField(str2)); // Expected output: "BRKE03" * * const str1 = "EP_GPT13TZ1_20231115_0800.L5K"; * const str2 = "EP_GPT13TZ2_20231113_1600.L5K"; * * log(extractIdField(str1)); // Expected output: "GPT13TZ1" * log(extractIdField(str2)); // Expected output: "GPT13TZ2" * * const str1 = "SEP_P1C2_GMP_ARL.L5K"; * const str2 = "SEP_P3C0_GMP_ARL.L5K"; * * log(extractIdField(str1)); // Expected output: "P1C2" * log(extractIdField(str2)); // Expected output: "P3C0" * * const str1 = "SEP_P1C2_GMP_ARL.L5K"; * const str2 = "SEP_PxCy_GMP.L5K"; * * log(extractIdField(str1)); // Expected output: "P1C2" * log(extractIdField(str2)); // Expected output: "PxCy" * */ extractId: function (objectName) { let idField = ''; let inAlphaNumeric = false; let isLetter = false; let isLowerL = false; let isNumber = false; let hasLetters = false; let hasNumbers = false; let si = 0; // ƒ to check for upper case letter const isUpper = (objectName, i) => { // if 'i' is outside 'objectName' return false if (i < 0 || i >= objectName.length) { return false; } return (objectName[i] >= 'A' && objectName[i] <= 'Z'); }; // scan the string for the first alpha-numeric field for (let i = 0; i < objectName.length; i++) { isNumber = (objectName[i] >= '0' && objectName[i] <= '9'); isLetter = (objectName[i] >= 'A' && objectName[i] <= 'Z'); isLowerL = (objectName[i] >= 'a' && objectName[i] <= 'z'); if (isNumber || isLetter || isLowerL) { if (!inAlphaNumeric) { inAlphaNumeric = true; si = i; } idField += objectName[i]; hasLetters = hasLetters || isLetter || isLowerL; hasNumbers = hasNumbers || isNumber; // Check for 'lower case numeric placeholder' like 'PxCy' or 'PnCn' for 'P1C2' if (isUpper(objectName, i - 1) && isUpper(objectName, i + 1) && isLowerL) { hasNumbers = true; // treat the single lower case letter between upper case letters as a number placeholder } } else { // hit non Alpha-Numeric character if (inAlphaNumeric && hasLetters && hasNumbers) { idField = objectName.substring(si, i); break; // we have the field we want, exit to return it } else { // current field does not meet criteria, reset and continue si = 0; idField = ''; inAlphaNumeric = false; hasLetters = false; hasNumbers = false; } } } return idField || ''; }, /** * @func httpStatus * @memberof mcode * @desc Returns the text for a given HTTP status code. * @param {var} httpCode the HTTP status code to translate. * @returns {string} a value representing the English meaning of a HTTP status code, formatted for display. */ httpStatus: function (httpCode) { // return the translated HTTP status code // example: `[HTTP] 404: Not Found` return (`[HTTP] ${httpCode}: ` + HTTP_CODES[httpCode] || 'Unknown HTTP Status'); }, /** * @func httpSeverity * @memberof mcode * @desc Returns the mcode.log() severity (as text) for a given HTTP status code. * @param {var} httpCode the HTTP status code to translate. * @returns {string} a value representing the text name for logging the proper severity. */ httpSeverity: function (httpCode) { if (httpCode >= 500) { return 'fatal'; // these severity must match mcode.log() definitions and app-banner.module.css } else if (httpCode >= 400) { return 'error'; } else if (httpCode >= 300) { return 'warning'; } else if (httpCode >= 200) { return 'success'; } else if (httpCode >= 100) { return 'info'; } return 'none'; }, /** * @func uuidDecode * @memberof mcode * @desc Decodes a UUID string into its component parts, this supports RFC 4122 and RFC 9562 UUIDs. * @param {string} uuid a UUID string in the format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', * @param {boolean} localTime a flag to indicate if the timestamp should be converted to local time for display. * @returns {string} a JSON object containing the decoded UUID parts. */ uuidDecode: function (uuid, localTime = false) { if (typeof uuid !== 'string' || !/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(uuid)) { return { error: 'Invalid UUID format' }; } // L I S T S - by UUID Variant and their Versions // ƒ Table of UUID Variants const uuidVariants = [ uuidvar1, // NCS compatibility uuidvar1, // NCS compatibility uuidvar2, // RFC 4122, RFC 9562 uuidvar3, // Microsoft GUIDs ]; // ƒ Table of UUID Layouts for Variant #0/1 (NCS compatibility) const uuidvar1Layouts = [ uuidvarNIL, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvar1reserved, uuidvarMAX, ]; // ƒ Table of UUID Layouts for Variant #2 (RFC 4122, RFC 9562) const uuidvar2Layouts = [ uuidvarNIL, uuidvar2v1, uuidvar2v2, uuidvar2v3, uuidvar2v4, uuidvar2v5, uuidvar2v6, uuidvar2v7, uuidvar2v8, uuidvar2reserved, uuidvar2reserved, uuidvar2reserved, uuidvar2reserved, uuidvar2reserved, uuidvar2reserved, uuidvarMAX ]; // ƒ Table of UUID Layouts for Variant #3 (GUID) const uuidvar3Layouts = [ uuidvarNIL, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvar3reserved, uuidvarMAX, ]; // L A Y O U T S - by UUID Variant and their Versions // ƒ UUID Variant #0 and #1 - NCS compatibility function uuidvar1() { versionText = UUID_VAR1_VERSIONS[version]; return uuidvar1Layouts[version](); } // ƒ UUID Variant #2 - RFC 4122 (Leach-Salz) function uuidvar2() { versionText = UUID_VAR2_VERSIONS[version]; return uuidvar2Layouts[version](); } // ƒ UUID Variant #3 - Microsoft GUIDs function uuidvar3() { versionText = UUID_VAR3_VERSIONS[version]; return uuidvar3Layouts[version](); } // V E R S I O N S - by UUID Variant // ƒ UUIDvar*v0 - Unused / Reserved for 00000000-0000-0000-0000-000000000000 function uuidvarNIL() { // decoded by default as a generic UUID return uuidGenericLayout(); } // ƒ UUIDvar*v15 - Unused / Reserved for FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF function uuidvarMAX() { // decoded by default as a generic UUID return uuidGenericLayout(); } // ƒ UUIDv1, UUIDv1 - 14 - Unused / reserved UUID variants function uuidvar1reserved() { // decoded by default as a generic UUID return uuidGenericLayout(); } // ƒ UUIDv2, UUIDv9-15 - Unused / reserved UUID variants function uuidvar2reserved() { // decoded by default as a generic UUID return uuidGenericLayout(); } // D E C O D E R S - by UUID Variant and their Versions // ƒ to build a timestamp string in the format "YYYY-MMM-DD HH:MM:SS.mmm.uuu UTC" function makeTimestamp(time_high, time_mid, time_low, epochInMs, localTime, version) { try { // Handle UUID version 1 (time-based) const timeHigh = BigInt(parseInt(time_high, 16)); const timeMid = BigInt(parseInt(time_mid, 16)); const timeLow = BigInt(parseInt(time_low, 16)); let timeValue, timeValueInMs, millisecondsSinceEpoch, timeDate; switch (version) { case 1: // Combine the time fields into a 60-bit value using BigInt timeValue = (timeHigh << 48n) | (timeMid << 32n) | timeLow; // UUID uses 100-nanosecond intervals, so we convert the timeValue to milliseconds timeValueInMs = timeValue / 10000n; // Add time since UUID epoch to the precomputed epoch offset millisecondsSinceEpoch = Number(timeValueInMs + epochInMs); // Convert to a JavaScript Date object timeDate = new Date(millisecondsSinceEpoch); break; case 6: // Combine the time fields into a 60-bit value using BigInt timeValue = (timeHigh << 28n) | (timeMid << 12n) | timeLow; // UUID uses 100-nanosecond intervals, so we convert the timeValue to milliseconds timeValueInMs = timeValue / 10000n; // Add time since UUID epoch to the precomputed epoch offset millisecondsSinceEpoch = Number(timeValueInMs + epochInMs); // Convert to a JavaScript Date object timeDate = new Date(millisecondsSinceEpoch); break; case 7: // Combine the 48-bit timestamp from the first two fields timeValue = (timeHigh << 16n) | timeLow; // Convert the Unix timestamp (milliseconds since Unix epoch) timeDate = new Date(Number(timeValue)); break; default: // Combine the time fields into a 60-bit value using BigInt timeValue = 0; break; } // Extract milliseconds and microseconds const milliseconds = timeDate.getMilliseconds(); const microseconds = (version === 7) ? 0 : Number((timeValue % 10000n) / 10n); if (localTime) { // Format the value to "YYYY-MMM-DD HH:MM:SS.mmm.uuu AM/PM" const datePart = timeDate.toLocaleDateString('en-US', {year: 'numeric', month: 'short', day: '2-digit'}); const timePart = timeDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); const ampm = timePart.slice(-2); // AM/PM part const timeNoMeridian = timePart.slice(0, -3); // Time without AM/PM timestampText = `${datePart} ${timeNoMeridian}.${milliseconds.toString().padStart(3, '0')}.${microseconds.toString().padStart(3, '0')} ${ampm}`; } else { // Format the value to "YYYY-MMM-DD HH:MM:SS.mmm.uuu UTC" timestampText = timeDate.toISOString() .replace('T', ' ') .replace('Z', '') .replace(/\.\d{3}/, `.${milliseconds.toString().padStart(3, '0')}.${microseconds.toString().padStart(3, '0')} UTC`); } } catch { // If there is an error, return the raw timestamp timestampText = `${time_high}${time_mid}${time_low} - decode error`; } return timestampText; } // ƒ UUIDv1 - Unordered Time-based UUID function uuidvar2v1() { /* UUIDv1 Field and Bit Layout - Gregorian Unordered Time-based UUID: 3 2 1 0 0 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +---------------+---------------+---------------+---------------+ | time_low | +---------------+---------------+--------+------+---------------+ | time_mid | ver | time_high | ----+-----------+---------------+--------+------+---------------+ |var| clock_seq | node | +---+-----------+---------------+---------------+---------------+ | node | +---------------+---------------+---------------+---------------+ */ const time_low = part_a1; const time_mid = part_a0; const time_high = part_b; const time = `${time_high}${time_mid}${time_low}`; const clock_seq = part_c2; const clockSeq = BigInt(parseInt(clock_seq, 16)); const node = part_c1 + part_c0; // Determine if the node is multicast based on the first bit of 'node' const multicastBit = parseInt(node.substring(0, 1), 16) >> 3; // Extract multicast flag (1 bit) from node const multicast = multicastBit === 1; const multicastText = multicast ? 'Multicast' : 'Unicast'; // The reset is based on UUID Variant and the Version of that Variant... let nodeType; let nodeValue; // Multicast or Unicast Addressed node nodeType = multicast ? 'MAC Address (Multicast)' : 'MAC Address (Unicast)'; nodeValue = node.match(/.{2}/g).join(':'); // The epoch is October 15, 1582 (start of UUID time) const epochInMs = BigInt(Date.UTC(1582, 9, 15)); // Get 'YYYY-MMM-DD HH:MM:SS.mmm.uuu UTC' const timestampText = makeTimestamp(time_high, time_mid, time_low, epochInMs, localTime, 1); // Return Variant 2 UUIDv1 Decoded return { UUID: uuid, Variant: variant, VariantText: variantText, Version: version, VersionText: versionText, Value1Name: "Timestamp", Value1: timestampText, Value2Name: "Clock Sequence", Value2: clockSeq, Value3Name: "", Value3: "", Value4Name: "Node Type", Value4: nodeType, Value5Name: "Node Address", Value5: `${nodeValue}`, Value6Name: "", Value6: "", NumberName: "Random Number", NumberText: `0x${time_high}${time_mid}${time_low}`, time_low: time_low, time_mid: time_mid, time_high: time_high, time: time, clock_seq: clock_seq, multicast: multicast, multicastText: multicastText, node: node, macid: nodeValue }; } // ƒ UUIDv2 - DCE Security (POSIX) UUID function uuidvar2v2() { /* UUIDv2 Field and Bit Layout - DCE Security (POSIX) UUID: 3 2 1 0 0 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +---------------+---------------+---------------+---------------+ | part_a1 | +---------------+---------------+--------+------+---------------+ | part_a0 | ver | part_b | ----+-----------+---------------+--------+------+---------------+ |var| part_c2 | part_c1 | +---+-----------+---------------+---------------+---------------+ | part_c0 | +---------------+---------------+---------------+---------------+ */ // {TBD} - not documented in RFC 9562 return uuidGenericLayout(); } // ƒ UUIDv3 - Name-Based (MD5 Hash) UUID function uuidvar2v3() { /* UUIDv3 Field and Bit Layout - Name-Based (MD5 Hash) UUID: 3 2 1 0 0 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +---------------+---------------+---------------+---------------+ | md5_high_1 | +---------------+---------------+--------+------+---------------+ | md5_high_0 | ver | md5_mid | ----+-----------+---------------+--------+------+---------------+ |var| md5_low_2 | md5_low_1 | +---+-----------+---------------+---------------+---------------+ | md5_low_0 | +---------------+---------------+---------------+---------------+ */ const md5_high_1 = part_a1; const md5_high_0 = part_a0; const md5_high = `${md5_high_1}${md5_high_0}`; const md5_mid = part_b; const md5_low_2 = part_c2; const md5_low_1 = part_c1; const md5_low_0 = part_c0; const md5_low = `${md5_low_2}${md5_low_1}${md5_low_0}`; const md5 = `${md5_high}${md5_mid}${md5_low}`; // Return Variant 2 UUIDv1 Decoded return { UUID: uuid, Variant: variant, VariantText: variantText, Version: version, VersionText: versionText, Value1Name: "md5_high", Value1: md5_high, Value2Name: "md5_mid", Value2: md5_mid, Value3Name: "md5_low", Value3: md5_low, Value4Name: "", Value4: '', Value5Name: "", Value5: '', Value6Name: "", Value6: '', NumberName: "MD5 Hash", NumberText: `0x${md5}`, md5_high: md5_high, md5_mid: md5_mid, md5_low: md5_low, MD5: md5 }; } // ƒ UUIDv4 - Random-Based Number UUID function uuidvar2v4() { /* UUIDv4 Field and Bit Layout - Random-Based Number UUID: 3 2 1 0 0 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +----------