Source: index.js

// #region  F I L E
// <copyright file="mcode-log/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-log.js'
 *      @memberof mcode
 *      @created  January 2022-2024
 *      @author   Timothy McGuire, MicroCODE, Inc.
 *      @description >
 *      MicroCODE Shared App Logging Library
 *
 *      LICENSE:
 *      --------
 *      MIT License: MicroCODE.mcode-log
 *
 *      Copyright (c) 2022-2024 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 logging and debugging.
 *
 *
 *      REFERENCES:
 *      -----------
 *      1. MIT xPRO Course: Professional Certificate in Coding: Full Stack Development with MERN
 *
 *      2. List of ANSI Color Escape Sequences
 *         https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
 *
 *      3. Showing Line Numbers in console.log from Node.js
 *         https://stackoverflow.com/questions/45395369/how-to-get-console-log-line-numbers-shown-in-nodejs
 *
 *
 *
 *
 *      MODIFICATIONS:
 *      --------------
 *      Date:         By-Group:   Rev:    Description:
 *
 *      27-Jan-2022   TJM-MCODE  {0001}   New module for common reusable Javascript logging functions.
 *      05-Mar-2022   TJM-MCODE  {0002}   Documentation updates.
 *      04-May-0222   TJM-MCODE  {0003}   Corrected 'month' in timeStamp.
 *      03-Oct-2022   TJM-MCODE  {0004}   Added 'log()' to simplify console logging of app events.
 *      03-Oct-2022   TJM-MCODE  {0005}   Added use of 'vt' for colorizing Console Log entries.
 *      16-Oct-2022   TJM-MCODE  {0006}   Added 'success' as a severity.
 *      30-Oct-2023   TJM-MCODE  {0007}   Updated to TypeScript, reversed to pure JavaScript in Jan 2024.
 *      03-Dec-2023   TJM-MCODE  {0008}   Don't log 'debug' messages in staging or production mode.
 *      21-Jan-2024   TJM-MCODE  {0009}   Converted to a single ES6 Module (ESM) for use in both
 *                                        Frontend/Client and Backend/Server as a NodeJS package.
 *      01-Feb-2024   TJM-MCODE  {0010}   Changed to the Universal Module Definition (UMD) pattern to support AMD,
 *                                        CommonJS/Node.js, and browser global in our exported module.
 *      02-Mar-2024   TJM-MCODE  {0011}   Added 'logobj()', 'expobj()', 'isFunction()', 'hexify()', 'octify()', and 'colorizeLines()'
 *                                        all in the pursuit of a more complete and consistent logging and debugging experience,
 *                                        in both the Console, NPM, and the Browser's DevTools.
 *      06-Jul-2024   TJM-MCODE  {0012}   0.4.00 - moved all 'data' functions into sub-package 'mcode-data'.
 *      22-Aug-2024   TJM-MCODE  {0013}   0.4.04 - corrected 'colorizeLines()' to carry on embedded colors to following lines.
 *      22-Aug-2024   TJM-MCODE  {0014}   0.4.05 - corrected 'logify()' to accept all legal JSON Key names.
 *      19-Feb-2025   TJM-MCODE  {0015}   0.5.08 - updated 'resx()' to support returning non-db entity results,
 *                                                 to carry this common response code into our HTMX UI responses.
 *      21-Feb-2025   TJM-MCODE  {0016}   0.5.09 - optimized many functions, standardized on '' strings instead of a mix
 *                                                 of "" and '', now "" only used when embedded ' are needed.
 *                                               - fixed an issues in 'logify*()' with string arrays where element had embedded ".
 *      08-Mar-2025   TJM-MCODE  {0017}   0.5.10 - updated resx() handle HTTP Status 204 properly with '.end()'.
 *      16-Mar-2025   TJM-MCODE  {0018}   0.6.07 - Passing MODULE_NAME is now optional and the logging functions
 *                                                 all log complete source path and line # of the caller automatically.
 *      17-Apr-2025   TJM-MCODE  {0019}   0.6.08 - added 'logifyObject()' to handle all objects, including arrays and JSON.
 *      18-Apr-2025   TJM-MCODE  {0020}   0.6.09 - add support for passing 'data' thru 'resx()' to support HTML responses for
 *                                                 HTMX UI swaps, specifically to support an 'app-banner',
 *                                                 added 'fatal' severity to align with app-banner.
 *      24-Apr-2025   TJM-MCODE  {0021}   0.6.09 - added support for UUID 'Event Id' as optional data to be logged for traceability.
 *      03-Oct-2025   TJM-MCODE  {0022}   0.7.00 - added support for HTML output thru the use of a new 'ht' table used in
 *                                                 combination with the existing 'vt' table.
 *      16-Oct-2025   TJM-MCODE  {0023}   0.7.04 - fixed logify() to stop inserting blank line before and between JSON objects.
 *                                                 Also fixed logObj() to always use logifyObject(), even for simple arrays to
 *                                                 correct a unique indentation issue.
 *      19-Oct-2025   TJM-MCODE  {0024}   0.7.05 - fixed 'getFrom()' to work properly in non-NodeJS environments by checking for 'process.versions.node'.
 *
 *
 *
 *
 * NOTE: This module follow's MicroCODE's JavaScript Style Guide and Template JS file, see:
 *
 *       o  https://github.com/MicroCODEIncorporated/JavaScriptSG
 *       o  https://github.com/MicroCODEIncorporated/TemplatesJS
 *
 * ...be sure to check out the CTRL-SHIFT+K, +L, +J keybaord shortcuts in Visual Studio Code
 *    for taking advance of the #regions in this file and our templates.
 *
 *
 */
// #endregion

// #region  I M P O R T S

const _data = require('mcode-data');

const isNode = typeof process !== 'undefined' && process.versions?.node;
const path = isNode ? require('path') : null;
const packageJson = require('./package.json');

// #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-log.js';
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

const theme = process.env.THEME || 'dark'; // default to dark mode
const mode = process.env.NODE_ENV || 'development'; // default to development mode

/**
 * @namespace mcode
 * @desc mcode namespace containing functions and constants.
 */
const mcode = {

    /**
     * @const vt
     * @memberof mcode
     * @desc Colors constants for changing Console appearance ala DEC's VT52 + VT100 + VT220.
     * @example
        ANSI Color Escape Sequence

        \x1b[***m  -- where '***' is a series of command codes separated by semi-colons (;).

        Code	Effect -- notes
        ------------------------------------------------------------------------------
        0	    Reset / Normal -- all attributes off
        1	    Bold -- increased intensity
        2	    Faint -- decreased intensity - not widely supported
        3	    Italic -- not widely supported, sometimes treated as inverse
        4	    Underline
        5	    Slow Blink -- less than 150 per minute
        6	    Rapid Blink -- MS-DOS ANSI.SYS; 150+ per minute; not widely supported
        7	    Reverse video -- swap foreground and background colors
        8	    Conceal -- not widely supported.
        9	    Crossed-out -- characters legible, but marked for deletion. Not widely supported
        10	    Primary font (default)
        11–19	Alternate font -- select alternate font n-10
        20	    Fraktur -- hardly ever supported
        21	    Bold off or Double Underline -- bold off not widely supported; double underline hardly ever supported
        22	    Normal color or intensity -- neither bold nor faint
        23	    Not italic, not Fraktur
        24	    Underline roundOff -- not singly or doubly underlined
        25	    Blink off
        27	    Inverse off
        28	    Reveal	conceal off
        29	    Not crossed out
        30–37	Set foreground color -- see color table below
        38	    Set foreground color -- next arguments are 5;<n> or 2;<r>;<g>;<b>, see below
        39	    Default foreground color -- implementation defined (according to standard)
        40–47   Set background color -- see color table below
        48	    Set background color -- next arguments are 5;<n> or 2;<r>;<g>;<b>, see below
        49	    Default background color -- implementation defined (according to standard)
        51	    Framed
        52	    Encircled
        53	    Overlined
        54	    Not framed or encircled
        55	    Not overlined
        60	    ideogram underline -- hardly ever supported
        61	    ideogram double underline -- hardly ever supported
        62	    ideogram overline -- hardly ever supported
        63	    ideogram double overline -- hardly ever supported
        64	    ideogram stress marking	hardly ever supported
        65	    ideogram attributes off	reset the effects of all of 60-64
        90–97	Set bright foreground color	aixterm (not in standard)
        100–107	Set bright background color	aixterm (not in standard)
     *
     */
    vt:
    {
        notice: "This is a test string for logifying 'mcode' as an object during testing.",

        // common effects, predefined ANSI escape sequences
        reset: '\x1b[0m',
        bold: '\x1b[1m',
        bright: '\x1b[1m',
        dim: '\x1b[2m',
        faint: '\x1b[2m',
        italic: '\x1b[3m',
        underscore: '\x1b[4m',
        underline: '\x1b[4m',
        blink: '\x1b[5m',
        blink_slow: '\x1b[5m',
        blink_fast: '\x1b[6m',
        reverse: '\x1b[7m',
        hidden: '\x1b[8m',
        conceal: '\x1b[8m',
        strikethru: '\x1b[9m',
        crossed_out: '\x1b[9m',

        // foreground colors
        fg: {
            black: '\x1b[30m',
            red: '\x1b[31m',
            green: '\x1b[32m',
            yellow: '\x1b[33m',
            blue: '\x1b[34m',
            magenta: '\x1b[35m',
            cyan: '\x1b[36m',
            white: '\x1b[37m',
        },

        // background colors
        bg: {
            black: '\x1b[40m',
            red: '\x1b[41m',
            green: '\x1b[42m',
            yellow: '\x1b[43m',
            blue: '\x1b[44m',
            magenta: '\x1b[45m',
            cyan: '\x1b[46m',
            white: '\x1b[47m',
        },

        // colors for event severity:   dark        light
        gray: (theme === 'dark') ? '\x1b[90m' : '\x1b[30m',  // gray
        errr: (theme === 'dark') ? '\x1b[91m' : '\x1b[31m',  // red
        good: (theme === 'dark') ? '\x1b[92m' : '\x1b[32m',  // green
        warn: (theme === 'dark') ? '\x1b[93m' : '\x1b[33m',  // yellow
        cold: (theme === 'dark') ? '\x1b[94m' : '\x1b[34m',  // blue
        dead: (theme === 'dark') ? '\x1b[95m' : '\x1b[35m',  // magenta
        code: (theme === 'dark') ? '\x1b[96m' : '\x1b[36m',  // cyan
        info: (theme === 'dark') ? '\x1b[97m' : '\x1b[37m',  // white
        dbug: (theme === 'dark') ? '\x1b[38;2;255;140;0m' : '\x1b[38;5;208m',  // orange

        // custom JSON colors -- see 'logify()' for use
        punc: '\x1b[96m\x1b[1m',  // string value - CYAN, BOLD
        key: '\x1b[97m',  // key name - WHITE
        string: '\x1b[96m',  // string value - CYAN
        integer: '\x1b[94m',  // integer value - BLUE
        real: '\x1b[92m',  // floating point value - GREEN
        bigint: '\x1b[95m',  // bigint value - MAGENTA
        true: '\x1b[92m',  // boolean true - BRIGHT GREEN (LIME)
        false: '\x1b[91m',  // boolean false - RED
        null: '\x1b[90m',  // null value - GRAY
        value: '\x1b[93m',  // fallback for other values - YELLOW
        nl: '\n'  // newline
    },

    /**
     * @const ht
     * @memberof mcode
     * @desc HTML inline style constants for generating HTML with colorized appearance similar to the VT ANSI colors.
     * @example
        Supported HTML equivalents for ANSI Color Escape Sequence

        Code	    Effect -- notes
        ------------------------------------------------------------------------------
        reset       end span
        bold        font-weight: 600
        bright      font-weight: 600
        dim         opacity: 0.6
        faint       opacity: 0.6
        italic      font-style: italic
        underscore  text-decoration: underline
        underline   text-decoration: underline
        blink       animation: blink 1s infinite

        blink_slow  animation: blink 2s infinite
        blink_fast  animation: blink 0.5s infinite
        reverse     filter: invert(1)
        hidden      visibility: hidden
        conceal     visibility: hidden
        strikethru  text-decoration: line-through
        crossed_out text-decoration: line-through
     */
    ht:
    {
        notice: "This is a test string for HTML-ifying 'mcode' as an object during testing.",

        // common effects, HTML inline styles
        reset: '</span>',
        bold: '<span style="font-weight: 600;">',
        bright: '<span style="font-weight: 600;">',
        dim: '<span style="opacity: 0.6;">',
        faint: '<span style="opacity: 0.6;">',
        italic: '<span style="font-style: italic;">',
        underscore: '<span style="text-decoration: underline;">',
        underline: '<span style="text-decoration: underline;">',
        blink: '<span style="animation: blink 1s infinite;">',
        blink_slow: '<span style="animation: blink 2s infinite;">',
        blink_fast: '<span style="animation: blink 0.5s infinite;">',
        reverse: '<span style="filter: invert(1);">',
        hidden: '<span style="visibility: hidden;">',
        conceal: '<span style="visibility: hidden;">',
        strikethru: '<span style="text-decoration: line-through;">',
        crossed_out: '<span style="text-decoration: line-through;">',

        // foreground colors
        fg: {
            black: '<span style="color: black;">',
            red: '<span style="color: red;">',
            green: '<span style="color: green;">',
            yellow: '<span style="color: yellow;">',
            blue: '<span style="color: blue;">',
            magenta: '<span style="color: magenta;">',
            cyan: '<span style="color: cyan;">',
            white: '<span style="color: white;">',
        },

        // background colors
        bg: {
            black: '<span style="background-color: black;">',
            red: '<span style="background-color: red;">',
            green: '<span style="background-color: green;">',
            yellow: '<span style="background-color: yellow;">',
            blue: '<span style="background-color: blue;">',
            magenta: '<span style="background-color: magenta;">',
            cyan: '<span style="background-color: cyan;">',
            white: '<span style="background-color: white;">',
        },

        // colors for event severity:            dark                                 light     themes
        gray: (theme === 'dark') ? '<span style="color: #999999;">' : '<span style="color: #333333;">',  // gray
        errr: (theme === 'dark') ? '<span style="color: #ff6b6b;">' : '<span style="color: #d32f2f;">',  // red
        good: (theme === 'dark') ? '<span style="color: #51cf66;">' : '<span style="color: #388e3c;">',  // green
        warn: (theme === 'dark') ? '<span style="color: #efff3b;">' : '<span style="color: #cbc800;">',  // yellow
        cold: (theme === 'dark') ? '<span style="color: #339af0;">' : '<span style="color: #1976d2;">',  // blue
        dead: (theme === 'dark') ? '<span style="color: #e599f7;">' : '<span style="color: #7b1fa2;">',  // magenta
        code: (theme === 'dark') ? '<span style="color: #3bc9db;">' : '<span style="color: #0097a7;">',  // cyan
        info: (theme === 'dark') ? '<span style="color: #f8f9fa;">' : '<span style="color: #212529;">',  // white/black
        dbug: (theme === 'dark') ? '<span style="color: #f2c200;">' : '<span style="color: #f57c00;">',  // orange

        // custom JSON colors -- see 'logifyHtml()' for use
        punc: '<span style="font-weight: 500; color: #00ffff;">',  // punctuation - CYAN, BOLD
        key: '<span style="font-weight: 300; color: #f8f9fa;">',  // key name - WHITE
        string: '<span style="font-weight: 500; color: #00cccc;">',  // string value - CYAN, DARKER
        integer: '<span style="font-weight: 600; color: #8bd6ffff;">',  // integer value - BLUE
        real: '<span style="font-weight: 600; color: #a0ff86;">',  // floating point value - GREEN
        bigint: '<span style="font-weight: 600; color: #cf86ff;">',  // bigint value - MAGENTA
        true: '<span style="font-weight: 600; color: #00ff00;">',  // boolean true - LIME
        false: '<span style="font-weight: 600; color: #ff0000;">',  // boolean false - RED
        null: '<span style="font-weight: 600; color: #7c7c7c;">',  // null value - GRAY
        value: '<span style="font-weight: 600; color: #ffbf00;">',  // fallback for other values - ORANGE
        nl: '<br/>'  // newline
    },

    /**
     * @func resolveSeverity
     * @memberof mcode
     * @desc Resolves a severity level to its corresponding text, icon, color, and prefix.
     * This is used throughout to ensure consistency in the logged output.
     * @api public
     * @param {string} severity The severity level to resolve.
     * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt'.
     * @returns {object} An object containing the resolved text, icon, color, and prefix.
     */
    resolveSeverity: function (severity, vx = mcode.vt)
    {
        const normalized = (severity ?? '').toString().toLowerCase();
        const severityMap = [
            {keys: ['i', 'inf', 'info'], text: 'info', icon: '📣', colorKey: 'info', prefix: 'i'},
            {keys: ['w', 'wrn', 'warn', 'warning'], text: 'warn', icon: '⚠️', colorKey: 'warn', prefix: '!'},
            {keys: ['e', 'err', 'error'], text: 'error', icon: '⛔', colorKey: 'errr', prefix: 'x'},
            {keys: ['x', 'exp', 'crash', 'fatal', 'exception'], text: 'exception', icon: '💀', colorKey: 'dead', prefix: '*'},
            {keys: ['s', 'ack', 'done', 'success'], text: 'success', icon: '✅', colorKey: 'good', prefix: '✓'},
            {keys: ['d', 'dbg', 'dbug', 'debug'], text: 'debug', icon: '🎃', colorKey: 'dbug', prefix: 'µ'}
        ];

        const entry = severityMap.find((item) => item.keys.includes(normalized))
            || {text: 'undefined', icon: '❓', colorKey: 'punc', prefix: '?'};

        const colorValue = `${vx.reset}${vx[entry.colorKey] ?? vx.punc}`;

        return {
            text: entry.text,
            icon: entry.icon,
            color: colorValue,
            prefix: entry.prefix
        };
    },

    /**
     * @func extractEntity
     * @memberof mcode
     * @api private
     * @desc Extracts, capitalizes, and returns the ENTITY name of a source module,
     * by MicroCODE's convention this is APP of app.controller.js, USER of user.view.js, etc.
     * @param {string} relativePath location of the source code module.
     * @returns Just the first part--before 1st '.'--of the source file.
     */
    extractEntity: function (relativePath)
    {
        // Extract the file name (last part of the path)
        const parts = relativePath.split(/[\\/]/); // Split on both forward and backslashes
        const fileName = parts.pop(); // Get last element (file name)

        // Extract the part before the first dot (.), return ready for message header as [ENTITY]
        return fileName.split('.')[0].toUpperCase();
    },

    /**
     * @func getFrom
     * @memberof mcode
     * @api private
     * @desc Helper function for log() to determine module and line of caller.
     * @param {string} source the module name from the mcode.log() call as a default (optional).
     * @returns (2) strings: 'appModule' and 'module:line' of mcode.log() caller.
     */
    getFrom: function (source)
    {
        let appModule = 'APP';
        let moduleLine = 'UNKNOWN';

        if (typeof source !== 'string' || source.trim() === '') return [appModule, moduleLine];

        // Get <app-base-dir> (3 levels up from <baseDir>/node_modules/mcode-log)
        const baseDir = isNode ? path.resolve(__dirname, '../../..') : '';
        appModule = source.split(/[\.,:;!?\s]+/)[0].toUpperCase();

        // ƒ Convert absolute file path to relative to baseDir for NODE environment
        const toRelative = (filePath) =>
        {
            if (!filePath) return source;
            if (!isNode || filePath.startsWith('node:')) return filePath;

            return path.relative(baseDir, filePath);
        };

        if (typeof Error.prepareStackTrace !== 'function')
        {
            // Fallback for non-V8 engines -- crawl a string stack trace
            const stack = new Error().stack.split('\n');

            for (let i = 2; i < stack.length; i++)
            {
                if (!stack[i].includes('index.js'))
                {
                    const match = stack[i].match(/\(([^)]+):(\d+):\d+\)/);
                    if (match)
                    {
                        // convert to relative path to keep short but informative
                        const filePath = match[1];
                        const lineNumber = match[2];
                        let relativePath = (filePath.includes('node:'))
                            ? filePath
                            : toRelative(filePath);
                        moduleLine = `${relativePath}:${lineNumber}`;
                        appModule = this.extractEntity(relativePath);
                    }
                }
            }
        }
        else
        {
            // V8 Engine, use structured stack trace for speed, save the original handler (string generator)
            const prepareStackTrace = Error.prepareStackTrace;

            try
            {
                // Override to return structured stack trace
                Error.prepareStackTrace = (_, stack) => stack;
                const stack = new Error().stack;

                if (stack && stack.length > 2)
                {
                    const caller = stack.find((s) => !s.getFileName().includes('index.js'));

                    if (caller)
                    {
                        // convert to relative path to keep short but informative
                        const filePath = caller.getFileName();
                        const lineNumber = caller.getLineNumber();
                        let relativePath = (filePath.includes('node:'))
                            ? filePath
                            : toRelative(filePath);
                        moduleLine = `${relativePath}:${lineNumber}`;
                        appModule = this.extractEntity(relativePath);
                    }
                }
            }
            finally
            {
                // ensure restoration no matter what
                Error.prepareStackTrace = prepareStackTrace;
            }
        }

        return [appModule, moduleLine];
    },

    /**
     * @func log/logHtml
     * @memberof mcode
     * @desc Logs App Events to the Console in a standardized format.
     * @api public
     * @param {object} message pre-formatted message to be logged.
     * @param {string} source where the message orginated.
     * @param {string} severity Event.Severity: 'info', 'warn', 'error', 'exception', and 'success'.
     * @param {string} error [Optional] error message from another source.
     * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI.
     * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML.
     * @returns {string} '{severity}: {message}' for display in UI.
     *
     * @example
     *      mcode.log('This is a test message.', 'myModule', 'info');
     *      mcode.log('object', object, 'myModule);
     */
    logHtml: function (message = '<no message>', source = '<unknown>.js', severity = 'debug', error = null, event_id = null)
    {
        return mcode.log(message, source, severity, error, event_id, mcode.ht);
    },
    log: function (message = '<no message>', source = '<unknown>.js', severity = 'debug', error = null, event_id = null, vx = mcode.vt)
    {
        let logText = [];  // build the response as an array for speed
        let status = `${severity}: ${message}`;
        let logifiedMessage = '';

        // support mcode.log(object) directly -- {0023}
        if (_data.isObject(message)) return mcode.logobj('', message, source, severity, event_id, vx);

        // support mcode.log('This object is bad', object) directly -- {0023}
        if (_data.isObject(source))
        {
            // If event_id looks like a source string (not a UUID), use it as source
            const evtIsSrc = (typeof event_id === 'string' && !event_id.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i));
            const src = evtIsSrc ? event_id : '<unknown>.js';
            const evt = !evtIsSrc ? event_id : null;
            return mcode.logobj(message, source, src, severity, evt, vx);
        }

        // do not log 'debug' messages in production mode - {0008}
        if ((severity === 'debug') && (mode === 'production')) return status;

        // flatten the message object to strings for logging...
        if (_data.isArray(message))
        {
            logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message, vx), vx);
        }
        else if (_data.isJson(message))
        {
            logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message, vx), vx);
        }
        else if (_data.isFunction(message))
        {
            logifiedMessage = `${vx.nl}` + `${message}`;
        }
        else
        {
            logifiedMessage = message;
        }
        const [appModule, moduleLine] = this.getFrom(source);

        const {text: sevText, icon: sevIcon, color: sevColor, prefix: sevPrefix} = mcode.resolveSeverity(severity, vx);

        logText.push(`${vx.reset}${vx.dim}++${vx.nl}${vx.reset}${vx.dim}`);
        logText.push(`${vx.reset}${vx.dim} ${sevPrefix} 「mcode」: ${sevColor}${sevIcon} [${appModule}] '${mcode.colorizeLines(logifiedMessage, sevColor, vx)}'`);
        logText.push(`${vx.nl}`);

        let logifiedError = false;
        if (error)
        {
            if (_data.isObject(error))
            {
                logifiedError = mcode.logify(mcode.logifyObject(error, vx), vx);
            }
            else if (_data.isJson(error))
            {
                logifiedError = mcode.logify(mcode.logifyObject(error, vx), vx);
            }
            else
            {
                logifiedError = error;
            }
            status += ` ERROR: ${mcode.simplify(logifiedError)}`;
        }

        if (logifiedError)
        {
            logifiedError = mcode.colorizeLines(logifiedError, sevColor, vx);

            logText.push(`${vx.reset}${vx.dim}     error: ${vx.reset}${sevColor}${logifiedError}${vx.nl}`);
        }
        if (event_id)
        {
            const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

            logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
            logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
        }
        logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
        logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
        logText.push(`${vx.reset}${vx.dim}  severity: ${vx.reset}${sevColor}${sevText}${vx.reset}${vx.nl}`);
        logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

        // Output to console or return HTML based on vx parameter
        const output = logText.join('');

        if (vx === mcode.ht)
        {
            // Wrap HTML output in div with severity color
            const codeColor = sevColor.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
            return `<div style="color: ${codeColor};">${output}</div>`;
        }
        else
        {
            // Set severity color for VT output
            console.log(`${sevColor}${output}${vx.reset}`);
            return status;  // for caller to use as needed
        }
    },

    /**
     * @func logobj/logobjHtml
     * @memberof mcode
     * @desc Logs a labeled Object to the Console in a standardized format.
     * @api public
     * @param {string} objName the name of the Object and/or a message to precede it in the log.
     * @param {object} obj javaScript Object to log.
     * @param {string} source where the Object orginated.
     * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI.
     * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML.
     * @returns {string} '{objName}: {obj}' for display in UI.
     *
     * @example
     *            mcode.logobj('myObject', myObject, 'myModule');
     *            mcode.obj('myObject', myObject, 'myModule');
     */
    logobjHtml: function (objName, obj, source = '<undefined>.js', severity = 'info', event_id = null)
    {
        return mcode.logobj(objName, obj, source, severity, event_id, mcode.ht);
    },
    logobj: function (objName, obj, source = '<undefined>.js', severity = 'info', event_id = null, vx = mcode.vt)
    {
        let logText = [];  // build the response as an array for speed
        let logifiedMessage = '';
        const [appModule, moduleLine] = this.getFrom(source);
        const {text: sevText, icon: sevIcon, color: sevColor, prefix: sevPrefix} = mcode.resolveSeverity(severity, vx);

        const logObjName = (typeof objName === 'string' && objName.trim() !== '') ? `${sevColor}${objName}:${vx.reset}${vx.nl}` : '';

        // 'Logifiy' all objects passed to this function...
        if (_data.isArray(obj))
        {
            logifiedMessage = `${sevColor}{array}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + mcode.logify(mcode.logifyObject(obj, vx), vx);
        }
        else if (_data.isObject(obj))
        {
            logifiedMessage = `${sevColor}{${(typeof obj)}}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + mcode.logify(mcode.logifyObject(obj, vx), vx);
        }
        else if (_data.isJson(obj))
        {
            logifiedMessage = `${sevColor}{json}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + mcode.logify(mcode.logifyObject(obj, vx), vx);
        }
        else
        {
            logifiedMessage = `${sevColor}{${(typeof obj)}}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + obj;
        }

        // Apply severity color to the entire message
        logifiedMessage = mcode.colorizeLines(logifiedMessage, sevColor, vx);

        logText.push(`${vx.reset}${vx.dim}++${vx.nl}`);
        logText.push(`${vx.reset}${vx.dim} ${sevPrefix} 「mcode」: ${sevColor}${sevIcon} [${appModule}] '${logifiedMessage}'${vx.nl}`);

        if (event_id)
        {
            const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

            logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
            logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
        }
        logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
        logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
        logText.push(`${vx.reset}${vx.dim}  severity: ${vx.reset}${sevColor}${sevText}${vx.reset}${vx.nl}`);
        logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

        // Output to console or return HTML based on vx parameter
        const output = logText.join('');

        if (vx === mcode.ht)
        {
            // Wrap HTML output in div with severity color using !important to override nested spans
            const codeColor = sevColor.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
            return `<div style="color: ${codeColor} !important;">${output}</div>`;
        }
        else
        {
            // Set severity color for VT output
            console.log(`${sevColor}${output}${vx.reset}`);
        }
    },

    // convenient abbreviations of all the logged severities...
    info: function (message, source, event_id) {mcode.log(message, source, 'info', null, event_id);},
    warn: function (message, source, event_id) {mcode.log(message, source, 'warn', null, event_id);},
    crash: function (message, source, event_id) {mcode.log(message, source, 'exception', null, event_id);},
    fatal: function (message, source, event_id) {mcode.log(message, source, 'exception', null, event_id);},
    done: function (message, source, event_id) {mcode.log(message, source, 'success', null, event_id);},
    debug: function (message, source, event_id) {mcode.log(message, source, 'debug', null, event_id);},
    success: function (message, source, event_id) {mcode.log(message, source, 'success', null, event_id);},

    // error() takes an optional 'error' parameter for logging underlying error information
    error: function (message, source, error, event_id) {mcode.log(message, source, 'error', error, event_id);},

    /**
     * @func ready
     * @memberof mcode
     * @desc Logs a message to the Console when the module is loaded to show version.
     */
    ready: function ()
    {
        this.log(`MicroCODE ${MODULE_NAME} v${packageJson.version} is loaded, mode: ${mode}, theme: ${theme}.`, MODULE_NAME, 'success');
    },

    /**
     * @func exp/expHtml
     * @memberof mcode
     * @desc logs an exception to the Console in a standardized format and a stack dump.
     * @api public
     * @param {object} message pre-formatted message to be logged.
     * @param {string} source where the message orginated.
     * @param {string} exception the underlying exception object/trace that was caught.
     * @param {string} exptrace the underlying exception object/trace that was caught... if 'source' is an object to log.
     * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI.
     * @returns {string} 'message: {message} - exception: {exception}' for display in UI.
     */
    expHtml: function (message = '<no message>', source = '<unknown>.js', exception = {}, exptrace = {}, event_id = null)
    {
        return mcode.exp(message, source, exception, exptrace, event_id, mcode.ht);
    },
    exp: function (message = '<no message>', source = '<unknown>.js', exception = {}, exptrace = {}, event_id = null, vx = mcode.vt)
    {
        let logText = [];  // build the response as an array for speed
        let logifiedMessage = '';
        let logifiedException = '';
        let isExpObject = false;

        // support mcode.log(object) directly -- {0023}
        if (_data.isObject(message)) return mcode.expobj('', message, source, exception, event_id, vx);

        // if an exception object has been passed without source, shift parameters...
        if (!_data.isString(source))
        {
            exception = source;
            source = '<unknown>.js';
        }

        if (_data.isJson(message))
        {
            logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message));
        }
        else
        {
            logifiedMessage = message;
        }

        // flatten the exception object to strings for logging...
        if (_data.isObject(exception))
        {
            isExpObject = true;

            if (exception.stack)
            {
                // colorized the passed the stack trace...
                logifiedException = mcode.colorizeLines(exception.stack, vx.gray);
            }
            else
            {
                // treat as an Object, not a stack trace and show in default colors...
                logifiedException = `${vx.reset}` + mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc);
            }
        }
        else if (_data.isJson(exception))
        {
            // treat as JSON, not a stack trace and show in default colors...
            logifiedException = `${vx.reset}` + mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc);
        }
        else
        {
            // treat as a string, not a stack trace or object and show in gray...
            logifiedException = `${vx.reset}${vx.gray}` + exception;
            logifiedException = mcode.colorizeLines(logifiedException, vx.gray);
        }

        const [appModule, moduleLine] = this.getFrom(source);

        let sevColor = vx.reset;
        sevColor += vx.dead;

        // created a simplified exception message for the log entry...
        const loggedException = ' exception: ' + mcode.colorizeLines(mcode.simplify(logifiedException), vx.dead);

        // if 'loggedException' contains a stack trace, log it as an 'exception w/stack'
        if (loggedException.includes('Error:') && loggedException.includes('at '))
        {
            isExpObject = true;
        }

        if (isExpObject)
        {
            logText.push(`${vx.reset}${vx.dim}++${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}${sevColor} exception:${vx.nl}`);
            logText.push(logifiedException + `${vx.nl}`);
            if (event_id)
            {
                const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

                logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
                logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
            }
            logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
            logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
            logText.push(`${vx.reset}${vx.dim}  severity: ${sevColor}exception w/stack${vx.reset}${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

            const output = logText.join('');
            if (vx === mcode.ht)
            {
                const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
                return `<div style="color: ${codeColor};">${output}</div>`;
            }
            else
            {
                console.log(output);
                return `${message} ${exception}`;  // for caller to return
            }
        }
        else
        {
            logText.push(`${vx.reset}${vx.dim}++${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}${sevColor}${loggedException}${vx.gray}${vx.nl}`);
            logText.push(mcode.colorizeLines(`call stack: ${new Error().stack}${vx.nl}`, vx.gray));
            if (event_id)
            {
                const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

                logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
                logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
            }
            logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
            logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
            logText.push(`${vx.reset}${vx.dim}  severity: ${sevColor}exception w/trace${vx.reset}${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

            const output = logText.join('');
            if (vx === mcode.ht)
            {
                const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
                return `<div style="color: ${codeColor};">${output}</div>`;
            }
            else
            {
                console.log(output);
                return `${message} ${exception}`;  // for caller to return
            }
        }
    },

    /**
     * @func expobj/expobjHtml
     * @memberof mcode
     * @desc Logs a labeled Object to the Console in a standardized format, with an associated exception and stack dump.
     * @api public
     * @param {string} objName the name of the Object and/or a message to precede it in the log.
     * @param {object} obj javaScript Object to log.
     * @param {string} source where the Object orginated.
     * @param {string} exception the underlying exception message that was caught.
     * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI.
     * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML.
     * @returns {string} 'message: {message} - exception: {exception}' for display in UI.
     *
     * @example
     *            mcode.expobj('myObject', myObject, 'myModule', err);  // from within a 'catch (err)' block
     */
    expobjHtml: function (objName = '<no name>', obj = {}, source = '<unknown>.js', exception = {}, event_id = null)
    {
        return mcode.expobj(objName, obj, source, exception, event_id, mcode.ht);
    },
    expobj: function (objName = '<no name>', obj = {}, source = '<unknown>.js', exception = {}, event_id = null, vx = mcode.vt)
    {
        let logText = [];  // build the response as an array for speed
        let logifiedMessage = '';
        let logifiedException = '';
        const logObjName = (typeof objName === 'string' && objName.trim() !== '') ? `${objName}:${vx.nl}` : '';

        let isExpObject = false;

        // flatten the message object to strings for logging...
        if (_data.isArray(obj))
        {
            logifiedMessage = `{array}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + mcode.logify(mcode.logifyObject(obj), vx);
        }
        else if (_data.isObject(obj))
        {
            logifiedMessage = `{${(typeof obj)}}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + mcode.logify(mcode.logifyObject(obj), vx);
        }
        else if (_data.isJson(obj))
        {
            logifiedMessage = `{json}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + mcode.logify(mcode.logifyObject(obj), vx);
        }
        else
        {
            logifiedMessage = `{${(typeof obj)}}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + obj;
        }

        // flatten the exception object to strings for logging...
        if (_data.isObject(exception))
        {
            isExpObject = true;

            if (exception.stack)
            {
                // colorized the passed the stack trace...
                logifiedException = mcode.colorizeLines(exception.stack, vx.gray);
            }
            else
            {
                logifiedException = `${vx.reset}` + mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc);
            }
        }
        else if (_data.isJson(exception))
        {
            logifiedException = mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc);
        }
        else
        {
            logifiedException = mcode.colorizeLines(exception, vx.gray);
        }

        const [appModule, moduleLine] = this.getFrom(source);

        let sevColor = vx.reset;
        sevColor += vx.dead;

        // created a simplified exception message for the log entry...
        const loggedException = 'exception: ' + mcode.simplify(logifiedException);

        // if 'loggedException' contains a stack trace, log it as an 'exception w/stack'
        if (loggedException.includes('Error:') && loggedException.includes('at '))
        {
            isExpObject = true;
            source = `${sevColor}exception${vx.reset}`;
        }

        if (isExpObject)
        {
            logText.push(`${vx.reset}${vx.dim}++${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}${sevColor}exception:${vx.nl}`);
            logText.push(`${vx.reset}` + logifiedException + `${vx.nl}`);
            if (event_id)
            {
                const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

                logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
                logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
            }
            logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
            logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
            logText.push(`${vx.reset}${vx.dim}  severity: ${sevColor}exception w/stack${vx.reset}${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

            const output = logText.join('');
            if (vx === mcode.ht)
            {
                const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
                return `<div style="color: ${codeColor};">${output}</div>`;
            }
            else
            {
                console.log(output);
                return `Object: ${objName} ${exception}`;  // for caller to return
            }
        }
        else
        {
            logText.push(`${vx.reset}${vx.dim}++${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}${sevColor}${loggedException}${vx.gray}${vx.nl}`);
            logText.push(mcode.colorizeLines(`call stack: ${new Error().stack}${vx.nl}`, vx.gray));
            if (event_id)
            {
                const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

                logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
                logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
            }
            logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
            logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
            logText.push(`${vx.reset}${vx.dim}  severity: ${sevColor}exception w/trace${vx.reset}${vx.nl}`);
            logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

            const output = logText.join('');
            if (vx === mcode.ht)
            {
                const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
                return `<div style="color: ${codeColor};">${output}</div>`;
            }
            else
            {
                console.log(output);
                return `Object: ${objName} ${exception}`;  // for caller to return
            }
        }
    },

    /**
     * @typedef {object} resxData
     * @property {number} status - HTTP Status Code
     * @property {string} message - Message to log to console / file
     * @property {string} data - Response data to be send to Frontend/Client/Browser - optionally a HTML component
     * @property {string} entity - [optional] The DB Entity name associated with this Response/Event
     * @property {string} endpoint - [optional] The API Endpoint name associated with this Response/Event
     * @property {string} error - [optional] Any error message associated with this response/event
     * @property {UUID} _id - [optional] The DB Record UUID associated with this Response/Event
     * @property {UUID} event_id - [optional] A unique Event UUID for traceability from UI to LOG.
     */

    /**
     * @func resx
     * @memberof mcode
     * @desc 'res' extension - logs an http response and returns the response result.
     * @api public
     * @param {object} res the response object.
     * @param {string} action the action that was being performed.
     * @param {object} resxData the response: datatype = 'resxData'.
     * @param {string} source where the message orginated.
     * @returns {object} the response object.
     */
    resx: function (res = {}, action = 'none', resxData = {}, source = '<unknown>.js>')
    {
        // example   DB Entity: READ [200] OK,  Entity: 'user' _id: nnnn-nnnn-nnnn-nnnn  or  Array: (n)
        // example HTML Result: READ [200] OK,  Endpoint: 'account.settings'
        const _id = resxData?._id || resxData.data?._id || resxData.data?.id || '<_id?>';
        const count_id = _data.isArray(resxData.data) ? `Array: (${resxData.data.length})` : (_id != '') ? `_id: ${_id}` : ``;
        const event_id = resxData?.event_id || null;

        const entity = resxData?.entity;
        const endpoint = resxData?.endpoint || `<endpoint?>`;
        const caller = (entity) ? `Entity: ${entity} ${count_id}` : `Endpoint: ${endpoint}`;
        const status = resxData?.status || 0;
        const message = `${action.toUpperCase()} ${_data.httpStatus(status)},  ${caller}`;

        // status to severity: 100s = 'info', 200s = 'success', etc.
        const severity = _data.httpSeverity(status);

        if (_data.isHtml(resxData?.data))
        {
            if (severity === 'fatal')
            {
                // show exception that caused this in the response...
                this.exp(message, source, resxData?.error?.message, resxData?.error?.stack, event_id);
            }
            else
            {
                // all severities other than 'fatal' exception are logged
                // with color and icons based on 'severity'
                this.log(message, source, severity, resxData?.error?.message, event_id);
            }

            // send HTML directly to the Frontend (HTMX Support)
            return res.status(resxData.status).send(resxData.data);
        }
        else
        {
            if (resxData.error)
            {
                // returning an error in the response...
                this.exp(message, source, resxData.error, null, event_id);
                return res.status(resxData.status).send({message: message, error: resxData.error});
            }
            if (resxData.data)
            {
                if (entity)
                {
                    // returning Entity data in the response...
                    this.log(message, source, severity, null, event_id);
                    return res.status(resxData.status).send({message: message, data: resxData.data});
                }
                else
                {
                    // returning a direct Endpoint *result*, like HTML/HTMX - {0015}
                    this.log(message, source, severity, null, event_id);
                    return res.status(resxData.status).send(resxData.data);
                }
            }

            // log the response...
            this.log(message, source, severity, null, event_id);

            // handle 'No Content' (204) response - {0017}
            if (resxData.status === 204)
            {
                return res.status(204).end();  // to end request without a body and prevent client retry
            }

            // all other responses...
            return res.status(resxData.status).send({message: message});
        }
    },

    /**
     * @func trace
     * @memberof mcode
     * @desc logs 'function call' showing call patterns to the Console in a standardized format.
     * @api public
     * @param {object} message pre-formatted message to be logged.
     * @param {string} source where the message orginated.
     * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI.
     * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML.
     * @returns nothing.
     */
    traceHtml: function (message = '<no message>', source = '<unknown>.js', event_id = null)
    {
        return mcode.trace(message, source, event_id, mcode.ht);
    },
    trace: function (message = '<no message>', source = '<unknown>.js', event_id = null, vx = mcode.vt)
    {
        let logText = [];  // build the response as an array for speed
        let logifiedMessage = '';

        // flatten the message object to strings for logging...
        if (_data.isArray(message))
        {
            logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message), vx);
        }
        else if (_data.isObject(message))
        {
            logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message), vx);
        }
        else if (_data.isJson(message))
        {
            logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message), vx);
        }
        else
        {
            logifiedMessage = message;
        }

        const [appModule, moduleLine] = this.getFrom(source);

        let sevColor = vx.reset + vx.punc;

        // Function calls are always logged as 'Info'
        logText.push(`${vx.reset}${vx.dim}++${vx.nl}`);
        logText.push(`${vx.reset}${vx.dim} µ 「mcode」: ${sevColor}🔍 [${appModule}] '${logifiedMessage}'${vx.reset}${vx.gray}${vx.nl}`);
        logText.push(mcode.colorizeLines(`call stack: ${new Error().stack}${vx.nl}`, vx.gray));
        if (event_id)
        {
            const uuidInfo = _data.uuidDecode(event_id);  // see mcode.data package

            logText.push(`${vx.reset}${vx.dim}     event: ${vx.reset}${sevColor}${event_id}${vx.reset}`);
            logText.push(`${vx.reset}${vx.dim}    node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`);  // aligned to 'time:'
        }
        logText.push(`${vx.reset}${vx.dim}      time: ${vx.reset}${mcode.timeStamp()}`);
        logText.push(`${vx.reset}${vx.dim}      from: ${vx.reset}${moduleLine}`);
        logText.push(`${vx.reset}${vx.dim}  severity: ${sevColor}trace${vx.reset}${vx.nl}`);
        logText.push(`${vx.reset}${vx.dim}--${vx.reset}`);

        const output = logText.join('');
        if (vx === mcode.ht)
        {
            const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db';
            return `<div style="color: ${codeColor};">${output}</div>`;
        }
        else
        {
            console.log(output);
        }
    },

    /**
     * @func simplify
     * @memberof mcode
     * @desc Strips a string of BRACES, BRACKETS, QUOTES, etc.
     * @api public
     * @param {object} object the string to be simplified to data
     * @returns {string} the simplified text
     */
    simplify: function (object)
    {
        if (_data.isUndefined(object))
        {
            return 'undefined';
        }

        // flatten the message object to strings for logging...
        if (_data.isObject(object))
        {
            // do not use JSON.stringify(object, null, 4)
            // --it's output is horrible, produce our own here in 'simplify()'
            object = JSON.stringify(object);
        }

        let simplifiedText = '';
        let inValue = false;
        let inEscape = false;
        let c = ' ';
        let clast = ' ';

        for (let i = 0; i < object.length; i++)
        {
            clast = c;
            c = object[i];

            // detect VT52,100,200 escape sequence
            if (c === '\x1b')
            {
                inEscape = true;
                continue;
            }
            // skip entire escape sequence
            if (inEscape)
            {
                if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')))
                {
                    inEscape = false;
                }
                continue;
            }
            switch (c)
            {
                case '{':
                case '}':
                case '[':
                case ']':
                    inValue = false;
                    c = ' ';
                    break;
                case '"':
                    c = ' ';
                    break;
                case ':':
                    simplifiedText += c;
                    if (!inValue)
                    {
                        simplifiedText += ' ';
                        c = ' ';
                    }
                    inValue = true;
                    break;
                case ',':
                    simplifiedText += c;
                    simplifiedText += ' ';
                    c = ' ';
                    inValue = false;
                    break;
                case '\n':
                case '\t':
                    c = ' ';
                    break;  // strip newlines and tabs
                case ' ':
                    if (clast != ' ')
                    {
                        simplifiedText += c;
                    }
                    break;
                default:
                    simplifiedText += c;
                    break;
            }
        }

        return simplifiedText;
    },

    /**
     * @func simplifyObject
     * @memberof mcode
     * @desc Strips an object of BRACES, BRACKETS, QUOTES, etc.
     * @api public
     * @param {object} objectToSimplify the object to be formatted for the event log
     * @returns {string} the simplified object
     */
    simplifyObject: function (objectToSimplify)
    {
        // do not use JSON.stringify(object, null, 4) -- it's output is horrible, use our own
        return mcode.simplify(JSON.stringify(objectToSimplify));
    },

    /**
     * @func logify/logifyHtml
     * @memberof mcode
     * @desc Formats a string of BRACES, BRACKETS, QUOTES, for display in the EVENT LOG.
     * No formatting occurs until the opening brace '{' of the JSON Data. VT Escape sequences are stripped.
     * @api public
     * @param {string} textToLogify the string to be formatted for the event log
     * @returns {string} the logified text
     */
    logifyHtml: function (textToLogify)
    {
        return mcode.logify(textToLogify, mcode.ht);
    },
    logify: function (textToLogify, vx = mcode.vt)
    {
        let inJson = false;  // start formatting when we hit the first '{' or '['
        let inValue = false;  // handle 'true, false, null, or number' as-is
        let inString = false;  // handle "quoted strings" as-is
        let inLiteral = false;  // take internal text as-is
        let inKey = false;  // track when we're processing a key name
        let expectingValue = false;  // track when we just processed a colon and expect a value
        let contextStack = [];  // stack to track nested object/array contexts

        let logText = [];  // build the response as an array for speed
        let tabStop = 0;  // indent level for formatting
        let lineEmpty = true;  // controls indent() output

        // ƒ to remove surrounding " " from key names only.
        let keyPairs = (jsonString) =>
        {
            // loop backward through the string, building a new copy, remove " " from key names
            let newString = '';
            let inKey = false;
            let inKeyName = false;
            let inString = false;
            let c = '';

            for (let i = jsonString.length - 1; i >= 0; i--)
            {
                c = jsonString[i];

                if (inString)
                {
                    if (c === '"')
                    {
                        inString = false;
                        newString = c + newString;
                        continue;
                    }
                    newString = c + newString;
                    continue;
                }
                if (inKeyName)
                {
                    if (c === '"')
                    {
                        inKeyName = false;
                        inKey = false;
                        continue;
                    }
                    else
                    {
                        newString = c + newString;
                        continue;
                    }
                }
                if (inKey)
                {
                    if (c === '"')
                    {
                        inKeyName = true;
                        continue;
                    }
                    else
                    {
                        newString = c + newString;
                        continue;
                    }
                }
                if (c === '"')
                {
                    newString = c + newString;
                    inString = true;
                    continue;
                }
                if (c === ':')
                {
                    inKey = true;
                }
                newString = c + newString;
            }

            return newString;
        };

        // ƒ to indent the JSON
        let indent = () =>
        {
            let newline = '';
            if (!lineEmpty)
            {
                newline += `${vx.nl}` + `${vx.reset}`;
                for (let index = 0; index < tabStop; index++)
                {
                    newline += '    ';  // 4-space tab-stop
                }
                lineEmpty = true;
            }
            return newline;
        };

        // ƒ to check for legal value name characters
        let isKeyChar = (c) =>
        {
            const code = c.charCodeAt(0);
            return (c !== '"') && (c !== ':') && (code >= 32 && code <= 126);
        };

        // ƒ to check for alpha-numeric characters
        let isValueChar = (c) =>
        {
            return (c === '-') || (c === '_') || (c === ' ') || (c === '$') || (c === '.') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
        };

        // ƒ to get the appropriate color for a value based on its content
        let getValueColor = (valueText) =>
        {
            const trimmed = valueText.trim();

            // Check for boolean values
            if (trimmed === 'true') return vx.true;
            if (trimmed === 'false') return vx.false;

            // Check for null
            if (trimmed === 'null') return vx.null;

            // Check for BigInt first (ends with 'n') - must come before integer check
            if (/^\d+n$/.test(trimmed))
            {
                // BigInt (ends with 'n')
                return vx.bigint;
            }

            // Check for numbers
            if (/^-?\d+$/.test(trimmed))
            {
                // Integer
                return vx.integer;
            }
            if (/^-?\d*\.\d+([eE][+-]?\d+)?$/.test(trimmed))
            {
                // Float/real number
                return vx.real;
            }

            // Fallback to generic value color
            return vx.value;
        };

        // S T A R T   P R O C E S S I N G   T H E   J S O N   S T R I N G
        textToLogify = keyPairs(textToLogify);

        let cc = '';  // 'cc' - current character, vs. 'c' - temporary character

        for (let i = 0; i < textToLogify.length; i++)
        {
            cc = textToLogify[i];

            if (textToLogify.substring(i, i + 2) === '\\\\')
            {
                // take backslash as-is
                logText.push(cc);
                logText.push(cc);
                i++; // skip the next '\'
                continue;
            }

            if (!inString && textToLogify.substring(i, i + 2) === `\${vt.nl}`)
            {
                logText.push(indent());
                lineEmpty = false;
                i++; // skip the 'n'
                continue;
            }

            if (inLiteral)
            {
                logText.push(cc);
                if (cc === '}')
                {
                    inLiteral = false;
                }
                continue;
            }

            if (textToLogify.substring(i, i + 2) === '${')
            {
                inLiteral = true;
                logText.push(cc);
                continue;
            }

            if (!inString && !inJson && (cc === '{' || cc === '['))
            {
                inJson = true;
                --i;  // reprocess '{' or '[' as JSON
                continue;
            }

            if (inKey)
            {
                if (!isKeyChar(cc))
                {
                    // End of key, add reset and continue
                    logText.push(`${vx.reset}`);
                    inKey = false;
                    --i;  // reprocess the character that ended the key
                }
                else
                {
                    logText.push(cc);
                }
                continue;
            }

            if (inValue)
            {
                if (!isValueChar(cc))
                {
                    // We've reached the end of a value, find what we just collected and colorize it
                    // Look back through logText to find the start of this value
                    let valueStartIndex = logText.length;
                    let collectedValue = '';

                    // Scan backwards to find where the value started
                    for (let j = logText.length - 1; j >= 0; j--)
                    {
                        const char = logText[j];
                        if (char && char.length === 1 && isValueChar(char))
                        {
                            collectedValue = char + collectedValue;
                            valueStartIndex = j;
                        }
                        else
                        {
                            break;
                        }
                    }

                    if (collectedValue)
                    {
                        // Remove the collected characters from logText
                        logText.splice(valueStartIndex);

                        // Add the value with appropriate color
                        const valueColor = getValueColor(collectedValue);
                        logText.push(`${valueColor}${collectedValue}${vx.reset}`);
                    }

                    inValue = false;
                    expectingValue = false;
                    // Don't use --i, instead continue with current character processing
                    // Fall through to process the current character normally
                }
                else
                {
                    logText.push(cc);
                    continue;
                }
            }

            if (inString)
            {
                if (cc === '"')
                {
                    inString = false;
                    cc = '\"' + `${vx.reset}`;
                }
                else if (cc === '\n')
                {
                    // Handle newlines within strings by adding proper indentation
                    logText.push(cc);
                    // Add indentation for the next line within the string
                    logText.push(`${vx.reset}`);
                    for (let index = 0; index < tabStop; index++)
                    {
                        logText.push('    ');  // 4-space tab-stop to match key indentation
                    }
                    logText.push(`${vx.string}`);  // Restore string color after indentation
                    continue;
                }
                logText.push(cc);
                continue;
            }

            if (!inJson)
            {
                logText.push(cc);
                lineEmpty = false;
                continue;
            }

            switch (cc)
            {
                case '{':
                    lineEmpty = true;  // prevent blank line before/between { JSON Objects }
                    logText.push(indent() + `${vx.punc}{${vx.reset}`);
                    lineEmpty = false;
                    tabStop++;
                    logText.push(indent());
                    contextStack.push('object');  // push object context
                    expectingValue = false;  // objects start with keys, not values
                    break;
                case '[':
                    logText.push(indent() + `${vx.punc}[${vx.reset}`);
                    lineEmpty = false;
                    tabStop++;
                    logText.push(indent());
                    contextStack.push('array');   // push array context
                    expectingValue = true;  // arrays contain values (an object being a value), not key-value pairs
                    break;
                case '}':
                    tabStop--;
                    logText.push(indent() + `${vx.punc}}${vx.reset}`);
                    lineEmpty = false;
                    inJson = tabStop > 0;
                    contextStack.pop();  // pop object context
                    // Reset expectingValue based on current context
                    const currentContext = contextStack[contextStack.length - 1];
                    expectingValue = currentContext === 'array';
                    break;
                case ']':
                    tabStop--;
                    logText.push(indent() + `${vx.punc}]${vx.reset}`);
                    lineEmpty = false;
                    contextStack.pop();  // pop array context
                    // Reset expectingValue based on current context
                    const currentContextAfterArray = contextStack[contextStack.length - 1];
                    expectingValue = currentContextAfterArray === 'array';
                    break;
                case ',':
                    logText.push(`${vx.reset}${vx.punc},${vx.reset}` + indent());
                    lineEmpty = false;
                    // In array context, we're still expecting values after a comma
                    // In object context, we're expecting a key after a comma
                    const currentCtx = contextStack[contextStack.length - 1];
                    expectingValue = currentCtx === 'array';
                    break;
                case ':':
                    logText.push(`${vx.punc}:${vx.reset} `);
                    lineEmpty = false;
                    expectingValue = true;
                    break;
                case '"':
                    logText.push(`${vx.string}`);
                    logText.push('\"');
                    lineEmpty = false;
                    inString = true;
                    break;
                case ' ':
                    lineEmpty = false;
                    break;
                default:
                    if (isKeyChar(cc))
                    {
                        if (expectingValue)
                        {
                            inValue = true;  // true, false, null, or number
                            // We'll collect the value and then colorize it appropriately
                            logText.push(cc);
                        }
                        else
                        {
                            // This is a key name
                            inKey = true;
                            logText.push(`${vx.key}${cc}`);
                        }
                        lineEmpty = false;
                    }
                    break;
            }
        }

        return logText.join('');
    },

    /**
     * @func logifyObject
     * @memberof mcode
     * @desc Converts a JSON Object into loggable text, like JSON.stringify() but with more control.
     * @api public
     * @param {object} objectToLogify
     * @param {object|array} vxOrParentObjects the color table (mcode.vt or mcode.ht) or parentObjects array for backward compatibility.
     * @param {array} parentObjects internal parameter for circular reference detection.
     * @returns {string} the logified object
     */
    logifyObject: function (objectToLogify, vxOrParentObjects = [''], parentObjects = null)
    {
        // Handle parameter overloading: determine if second parameter is vx or parentObjects
        let vx = null;
        let actualParentObjects = [''];

        if (Array.isArray(vxOrParentObjects))
        {
            // Old signature: (objectToLogify, parentObjects)
            actualParentObjects = vxOrParentObjects;
        }
        else
        {
            // New signature: (objectToLogify, vx, parentObjects)
            vx = vxOrParentObjects;
            actualParentObjects = parentObjects || [''];
        }

        // ƒ to check for and handle circular references
        const isCyclic = (member) =>
        {
            if (actualParentObjects.includes(member))
            {
                return '"<self-reference>"';  // 'true'
            }
            return false;
        };

        // ƒ to handle non-object types
        const handleNonObject = (value) =>
        {
            if (value === null)
            {
                return `null`;
            }
            if (typeof value === 'string')
            {
                // detect JSON objects that have been escaped and convert them back to JSON
                if (value.startsWith(`{`) && value.endsWith(`}`))
                {
                    // convert to JSON representation
                    return value.replaceAll('\\"', '"');
                }
                else
                {
                    // standardize text for enclosing "s.
                    return `"${value.replaceAll(`"`, `'`)}"`;
                }
            }
            if (typeof value === 'function')
            {
                return `"ƒ ${value.name}"`;
            }
            if (typeof value === 'symbol')
            {
                return '"<symbol>"';
            }
            if (typeof value === 'bigint')
            {
                return value.toString() + 'n';
            }
            if (typeof value === 'number')
            {
                return value.toString();
            }
            if (typeof value === 'boolean')
            {
                return value.toString();
            }
            if (typeof value === 'undefined')
            {
                return '"<undefined>"';
            }

            return '<unknown>';
        };

        // ƒ to detect Fetch API Headers, Request, and Response objects
        const isFetchHeaders = (value) =>
            typeof Headers === 'function' && value instanceof Headers;

        // ƒ to convert Headers to simple object
        const headersToObject = (headers) =>
        {
            if (!headers) return {};

            const result = {};

            if (typeof headers.forEach === 'function')
            {
                headers.forEach((headerValue, headerName) =>
                {
                    result[headerName] = headerValue;
                });
            }
            else if (typeof headers.entries === 'function')
            {
                for (const [headerName, headerValue] of headers.entries())
                {
                    result[headerName] = headerValue;
                }
            }

            return result;
        };

        // ƒ to detect Fetch API Response objects
        const isFetchResponse = (value) =>
            typeof Response === 'function' && value instanceof Response;

        // ƒ to detect Fetch API Request objects
        const isFetchRequest = (value) =>
            typeof Request === 'function' && value instanceof Request;

        // ƒ to convert Response to simple object
        const summarizeResponse = (response) =>
        {
            return {
                ok: response.ok,
                status: response.status,
                statusText: response.statusText,
                redirected: response.redirected,
                type: response.type,
                url: response.url,
                bodyUsed: response.bodyUsed,
                headers: headersToObject(response.headers),
                body: response.bodyUsed ? '<body consumed>' : '<body available via response.clone()>'
            };
        };

        // ƒ to convert Request to simple object
        const summarizeRequest = (request) =>
        {
            return {
                method: request.method,
                url: request.url,
                cache: request.cache,
                credentials: request.credentials,
                integrity: request.integrity,
                keepalive: request.keepalive,
                mode: request.mode,
                redirect: request.redirect,
                referrer: request.referrer,
                referrerPolicy: request.referrerPolicy,
                bodyUsed: request.bodyUsed,
                headers: headersToObject(request.headers)
            };
        };

        // ƒ to recursively stringify an object
        const recursiveStringify = (currentObject) =>
        {
            // Handle non-object types
            if (typeof currentObject !== 'object' || currentObject === null)
            {
                return handleNonObject(currentObject);
            }

            if (_data.isTimeStamp(currentObject))
            {
                return `"${this.timeStamp(now = currentObject, local = true)}"`;
            }

            // special case for File objects which cannot be completely stringified
            if (typeof File !== 'undefined' && currentObject instanceof File)
            {
                let file = currentObject;
                const date = new Date(file.lastModified);

                // change File to a simple object
                currentObject = {
                    name: file.name,
                    size: file.size,
                    date: date.toString()
                };
            }

            if (isFetchHeaders(currentObject))
            {
                actualParentObjects.pop();

                return recursiveStringify(headersToObject(currentObject));
            }

            if (isFetchResponse(currentObject))
            {
                actualParentObjects.pop();

                return recursiveStringify(summarizeResponse(currentObject));
            }

            if (isFetchRequest(currentObject))
            {
                actualParentObjects.pop();

                return recursiveStringify(summarizeRequest(currentObject));
            }

            // Detect and handle circular references
            let cyclicCheck = isCyclic(currentObject);
            if (cyclicCheck)
            {
                return cyclicCheck;
            }

            // Keep track of parent objects to detect circular references
            actualParentObjects.push(currentObject);

            let result;
            if (Array.isArray(currentObject))
            {
                // ƒ to handle array members
                result = currentObject.map((item) => recursiveStringify(item)).join(',');

                actualParentObjects.pop();

                return `[${result}]`;
            }
            else
            {
                // ƒ to handle object members
                result = Object.keys(currentObject).map((key) =>
                {
                    let value = currentObject[key];

                    // Skip functions, symbols, and undefined properties
                    if (typeof value === 'function'
                        || typeof value === 'symbol'
                        || typeof value === 'undefined'
                        || typeof value === 'null')
                    {
                        return `"${key}":${handleNonObject(value)}`;
                    }

                    return `"${key}":${recursiveStringify(value)}`;

                }).filter(Boolean).join(',');

                actualParentObjects.pop();

                return `{${result}}`;
            }
        };

        // stringify the object, recursively
        return recursiveStringify(objectToLogify);
    },

    /**
     * @func listifyObject
     * @memberof mcode
     * @desc Converts a JSON Object into a HTML or JSX List.
     * @api public
     * @param {Object} objectToListify the object to be converted to a HTML List.
     * @param {string} outputType how to out the list: 'html' or 'jsx'.
     * @returns {string} the HTML List code.
     */
    listifyObject: function (objectToListify, outputType = 'html')
    {
        let listifiedText = '';
        let keyIndex = 0;

        if (outputType === 'jsx')
        {
            listifiedText += '<ul className="list-group">';

            Object.entries(objectToListify).forEach(([key, value]) =>
            {
                // ƒ to convert array element to text, simplify for display, and add to LIST...
                listifiedText += `<li className="list-group-item" key="${keyIndex++}">${key}: ${value}</li>`;
            });

            listifiedText += '</ul>';
        }
        else
        {
            Object.entries(objectToListify).forEach(([key, value]) =>
            {
                // ƒ to convert array element to text, simplify for display, and add to LIST...
                listifiedText += `<li className="list-group-item" key="${keyIndex++}">${key}: ${value}</li>`;
            });
        }

        return listifiedText;
    },

    /**
     * @func listifyArray
     * @memberof mcode
     * @desc Converts an array of text items into a HTML or JSX List.
     * @api public
     * @param {Array<any>} arrayToListify the array to be converted to a HTML List.
     * @param {string} outputType how to out the list: 'html' or 'jsx'.
     * @returns {string} the HTML List code.
     */
    listifyArray: function (arrayToListify, outputType = 'html')
    {
        let listText = [];
        let keyIndex = 0;

        if (outputType === 'jsx')
        {
            listText.push(`<ul className="list-group">`);

            arrayToListify.forEach(element =>
            {
                // ƒ to convert array element to text, simplify for display, and add to LIST...
                listText.push(`<li className="list-group-item" key="${keyIndex++}">${mcode.simplifyObject(element)}</li>`);
            });

            listText.push('</ul>');
        }
        else
        {
            arrayToListify.forEach(element =>
            {
                // ƒ to convert array element to text, simplify for display, and add to LIST...
                listText.push(`<li className="list-group-item" key="${keyIndex++}">${mcode.simplifyObject(element)}</li>`);
            });
        }

        return listText.join('');
    },

    /**
     * @func colorizeLines
     * @memberof mcode
     * @desc Colorizes each line of a string using VT escape sequences or HTML inline styles.
     * This is useful for log messages that span multiple lines and need to maintain color, the console
     * does not do this automatically. HTML does this automatically so that mode just returns the input unchanged.
     * @api public
     * @param {string} inputLines the string to be colorized.
     * @param {string} vtColor the VT escape sequence to use for colorizing the lines.
     * @param {object} vx the color table (mcode.vt or mcode.ht) - optional, defaults to VT.
     * @returns {string} the colorized string.
     * @example
     *          mcode.colorizeLines('Hello, World!${vx.nl}This is fun!', mcode.vt.red);  // returns: '\u001b[31mHello, World!\u001b[31mThis is fun!'
     */
    colorizeLines: function (inputLines, vtColor, vx = null)
    {
        // If vx is provided and it's the HTML table, return unchanged since HTML doesn't need line color carry-over
        if (vx === mcode.ht) return inputLines;

        // Default VT behavior
        // Split the input string into lines
        const lineArray = inputLines?.split(`${vx ? vx.nl : mcode.vt.nl}`);

        let currentColor = vtColor;

        // for each line in the array, find the last escape sequence and apply that color to
        // all the lines that follow until a new escape sequence is found at the end of a line {0013}
        for (let i = 0; i < lineArray?.length; i++)
        {
            // Apply the color to each line
            lineArray[i] = `${currentColor}${lineArray[i]}`;

            // pick up the last color in the line we just added...
            currentColor = lineArray[i].match(/\u001b\[\d+m/g)?.pop() || currentColor;
        }

        // Rejoin the colorized lines into a single string
        return lineArray ? lineArray.join(`${vx ? vx.nl : mcode.vt.nl}`) : '';
    },

    /**
     * @func timeStamp
     * @memberof mcode
     * @desc Generates a timestamp string: YYYY-MM-DD Day HH:MM:SS.mmm.
     * @api public
     * @param {boolean} local [Optional] determines whether or not local time is used, if not it returns use UTC.
     * @returns {string} 'YYYY-MM-DD Day HH:MM:SS.mmm UTC|Local'.
     */
    timeStamp: function (now = new Date(), local = true)
    {
        // ƒ to make sure all fields are fixed length with leading zeros
        const leadingZeros = (number, length) =>
        {
            let numberField = '' + number;
            while (numberField.length < length)
            {
                numberField = '0' + numberField;
            }
            return numberField;
        };

        if (local)
        {
            let dayofweek = WEEKDAYS[now.getDay()];           // 3-letter day of week
            let year = now.getFullYear();                     // 4-digit year
            let month = MONTHS[now.getMonth()];               // 3-letter month of year
            let day = leadingZeros(now.getDate(), 2);         // 2-digit day
            let hours = leadingZeros(now.getHours(), 2);      // 2-digit hour
            let minutes = leadingZeros(now.getMinutes(), 2);  // 2-digit minute
            let seconds = leadingZeros(now.getSeconds(), 2);  // 2-digit second
            let ms = leadingZeros(now.getMilliseconds(), 3);  // 3-digit millisecond

            return `${year}-${month}-${day} ${dayofweek} ${hours}:${minutes}:${seconds}.${ms} Local`;
        }
        else
        {
            let dayofweek = WEEKDAYS[now.getUTCDay()];           // 3-letter day of week
            let year = now.getUTCFullYear();                     // 4-digit year
            let month = MONTHS[now.getUTCMonth()];               // 3-letter month of year
            let day = leadingZeros(now.getUTCDate(), 2);         // 2-digit day
            let hours = leadingZeros(now.getUTCHours(), 2);      // 2-digit hour
            let minutes = leadingZeros(now.getUTCMinutes(), 2);  // 2-digit minute
            let seconds = leadingZeros(now.getUTCSeconds(), 2);  // 2-digit second
            let ms = leadingZeros(now.getUTCMilliseconds(), 3);  // 3-digit millisecond

            return `${year}-${month}-${day} ${dayofweek} ${hours}:${minutes}:${seconds}.${ms} UTC`;
        }
    },

    // #region  U T C / Z U L U   T I M E   U T I L I T I E S

    /**
     * @static
     * @public
     * @constant {object} TIME_FORMATS - Available time display formats
     * @memberof mcode
     */
    TIME_FORMATS: Object.freeze({
        MILITARY: 'military',
        MERIDIAN: 'meridian',
        UTC: 'utc'
    }),

    /**
     * @static
     * @public
     * @method getCurrentZuluTime
     * @memberof mcode
     * @desc Gets the current time as UTC/Zulu timestamp in milliseconds.
     * @returns {number} Current UTC time in milliseconds
     */
    getCurrentZuluTime: function ()
    {
        return Date.now(); // Date.now() already returns UTC milliseconds
    },

    /**
     * @static
     * @public
     * @method parseZuluTime
     * @memberof mcode
     * @desc Parses UTC/Zulu timestamps ending with 'Z' into Date objects with validation.
     * @param {string|number|Date} timestamp - The timestamp to parse (supports 'Z' time, ISO strings, milliseconds, or Date objects)
     * @returns {Date|null} Parsed Date object or null if invalid
     */
    parseZuluTime: function (timestamp)
    {
        if (!timestamp) return null;
        if (timestamp instanceof Date) return isNaN(timestamp.getTime()) ? null : new Date(timestamp.getTime());
        if (typeof timestamp === 'number') return Number.isFinite(timestamp) ? new Date(timestamp) : null;
        if (typeof timestamp === 'string')
        {
            // Handle ISO strings with 'Z' suffix or standard ISO format
            try
            {
                const parsed = new Date(timestamp);
                return isNaN(parsed.getTime()) ? null : parsed;
            }
            catch
            {
                return null;
            }
        }

        return null;
    },

    /**
     * @static
     * @public
     * @method formatMilitaryTime
     * @memberof mcode
     * @desc Formats a timestamp for display as local military time (24-hour format).
     * @param {string|number|Date} timestamp - The timestamp to format
     * @returns {string} Formatted time string in local military format (HH:MM:SS)
     */
    formatMilitaryTime: function (timestamp)
    {
        const date = this.parseZuluTime(timestamp);
        if (!date) return '--:--:--';

        return date.toLocaleTimeString('en-US', {
            hour12: false,
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit'
        });
    },

    /**
     * @static
     * @public
     * @method formatMeridianTime
     * @memberof mcode
     * @desc Formats a timestamp for display as local meridian time (12-hour format with AM/PM).
     * @param {string|number|Date} timestamp - The timestamp to format
     * @returns {string} Formatted time string in local meridian format
     */
    formatMeridianTime: function (timestamp)
    {
        const date = this.parseZuluTime(timestamp);
        if (!date) return '--:--:-- --';

        return date.toLocaleTimeString('en-US', {
            hour12: true,
            hour: 'numeric',
            minute: '2-digit',
            second: '2-digit'
        });
    },

    /**
     * @static
     * @public
     * @method formatUTCTime
     * @memberof mcode
     * @desc Formats a timestamp for display as UTC time.
     * @param {string|number|Date} timestamp - The timestamp to format
     * @returns {string} Formatted time string in UTC format (HH:MM:SS UTC)
     */
    formatUTCTime: function (timestamp)
    {
        const date = this.parseZuluTime(timestamp);
        if (!date) return '--:--:-- UTC';

        return date.toUTCString().split(' ')[4] + ' UTC'; // Extract just the time portion
    },

    /**
     * @static
     * @public
     * @method formatTimeByMode
     * @memberof mcode
     * @desc Formats a timestamp according to the specified display mode.
     * @param {string|number|Date} timestamp - The timestamp to format
     * @param {string} displayMode - Display format from TIME_FORMATS
     * @returns {string} Formatted time string
     */
    formatTimeByMode: function (timestamp, displayMode = this.TIME_FORMATS.MILITARY)
    {
        switch (displayMode)
        {
            case this.TIME_FORMATS.MERIDIAN:
                return this.formatMeridianTime(timestamp);
            case this.TIME_FORMATS.UTC:
                return this.formatUTCTime(timestamp);
            case this.TIME_FORMATS.MILITARY:
            default:
                return this.formatMilitaryTime(timestamp);
        }
    },

    /**
     * @static
     * @public
     * @method isTimestampInView
     * @memberof mcode
     * @desc Determines if a timestamp is LATE or EARLY compared to current UTC time and a time window.
     * @param {string|number|Date} timestamp - The timestamp to check
     * @param {number} windowMs - Time window in milliseconds for "on-time" timestamps
     * @returns {string|null} 'LATE', 'EARLY', or null if on-time
     */
    isTimestampInView: function (timestamp, windowMs = 30000)
    {
        const thisTime = this.parseZuluTime(timestamp);
        if (!thisTime) return 'INVALID';

        const thisTimeMs = thisTime.getTime();
        const currentZuluMs = this.getCurrentZuluTime();
        const windowStart = currentZuluMs - windowMs;

        if (thisTimeMs > currentZuluMs) return 'EARLY'; // timestamp is in the future
        if (thisTimeMs < windowStart) return 'LATE'; // timestamp is older than time window

        return 'VISIBLE'; // timestamp is within acceptable time window
    },

    // #endregion
};

// #endregion

// #region  M E T H O D - E X P O R T S

// Immediately Invoked Function Expression (IIFE) invoked on 'this' which
// represents the global object (window in a browser, global in Node.js).
// This IIFE returns the 'mcode' object to be assigned to the global object.
// The Universal Module Definition (UMD) pattern supports Asynchronous Module Definition (AMD),
// CommonJS / Node.js, and Browser 'global' usage. {0010}
(
    /**
     * @function (IIFE)
     * @description Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, and browser global
     * @param {any} root the global object (window, self, global, etc.) being updated.
     * @param {any} factory a function that returns the exports of the module. This function is invoked in
     * the context of the global object when the module is loaded. The return value of this function is used
     * as the exported value of the module when it's not being used with AMD or Node.js module systems.
     * This function is where you define what your module exports.
     */
    function (root, factory)
    {
        if (typeof define === 'function' && define.amd)
        {
            // AMD. Register as an anonymous module.
            define([], factory);
        }
        else if (typeof module === 'object' && module.exports)
        {
            // NODE. Does not work with strict CommonJS, but
            // only CommonJS-like environments that support module.exports, like Node.
            module.exports = factory();
        }
        else
        {
            // BROWSER. Window globals (root is 'window').
            root.mcode = factory();
        }

    }(  // root: the global object (window, self, global, etc.)
        (typeof self !== 'undefined') ? self : this,

        // factory: a function that returns the exports of the module
        function () {return mcode;})
);

// #endregion

// #endregion
// #endregion