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
JavaScript
// #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
+----------