Source: main.js

'use strict'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { createHash } from 'crypto'
import { hostname, userInfo, type, platform, totalmem } from 'os'
dayjs.extend(utc)
dayjs.utc()
// console.log(dayjs.utc().isUTC());
const { arch, cwd, execPath } = process
const { bigint } = process.hrtime
const { PWD } = process.env

const hashAlgorithm = 'Whirlpool'
const hashEncoding = 'hex'

/**
 * A class that produces context stamp instances.
 */
class Constamp {
  #isCopy = false
  #template = Constamp.format
  #t = {
    t: dayjs().utc(),
    b: bigint()
  }
  #toJson = () => {
    const o = {
      t: {
        t: this.#t.t,
        b: this.#t.b.toString()
      }
    }
    const params = [this.#w, this.#d]
    const labels = ['w', 'd']
    for (let i = 0; i < params.length; i++) {
      const p = params[i]
      o[labels[i]] = p
    }
    return JSON.stringify(o)
  }
  #d = {
    cwd: cwd(),
    pwd: PWD,
    execPath
  }
  #w = {
    arch,
    user: userInfo(),
    hostname: hostname(),
    type: type(),
    platform: platform(),
    totalmem: totalmem()
  }
  #h = null
  #hashes = {}

  /**
   * constructor - Create an immutable stamp relating to various data, including the
   *
   * - **D**: execPath, cwd and pwd,
   * - **T**: time and hrtime, and
   * - **W**:  type, user info, arch, hostname, totalmem and platform.
   *
   * @param  {object} o = { format: null } parameters
   * @param  {?string} o.format a dayjs-recognizeable template string
   * @param  {?string} o.json a valid json string to copy from
   */
  constructor (o = { format: null, json: null }) {
    const formatJson = (j) => j.replace(/\s/g, '')
    const hexmatcher = /^[0-9a-f]+$/
    const { format, json } = o
    let hashes = null
    let djs = null
    try {
      if (json) {
        hashes = JSON.parse(json)
        if (['t', 'w', 'd'].every(v => (v in hashes) && (typeof hashes[v] === 'string'))) {
          djs = dayjs(hashes.t).utc()
          if (['w', 'd'].every(v => hexmatcher.test(hashes[v])) && djs.isValid()) {
            this.#isCopy = true
          }
        }
      }
    } catch (e) {
      throw e
    } finally {
      this.#template = (typeof format === 'string') ? format : this.#template
      this.#t.t = (this.#isCopy && djs) ? djs : this.#t.t
      const str = formatJson(this.#toJson())
      const hash = createHash(hashAlgorithm)
      hash.update(str)
      this.#h = hash.digest(hashEncoding)

      const ps = [ 'd', 'w' ]
      const vs = [ this.#d, this.#w ]
      ps.map((p, i) => {
        if (this.#isCopy) {
          this.#hashes[p] = hashes[p]
        } else {
          const h = createHash(hashAlgorithm)
          h.update(formatJson(JSON.stringify(vs[i])))
          this.#hashes[p] = h.digest(hashEncoding)
        }
      })
      Object.freeze(this.#hashes)
      Object.freeze(this.#t)
      Object.freeze(this.#d)
      Object.freeze(this.#w)
      Object.freeze(this)
    }
  }

  /**
   * get isCopy - was this instance copied from json?
   *
   * @return {boolean} true IFF it was made from a valid json string
   */
  get isCopy () {
    return this.#isCopy
  }
  /**
   * get time - the timestamp UTC epoch time as dayjs
   *
   * @return {dayjs} approximate time of instantiation
   * @see {@link https://github.com/iamkun/dayjs/blob/dev/docs/en/API-reference.md#format-formatstringwithtokens-string}
   */
  get time () {
    return this.#t.t
  }
  /**
   * get timeString - the formatted timestamp UTC epoch time
   *
   * @return {string} the formated approximate time of instantiation
   * @see {@link constructor}
   * @see {@link get time}
   * @see {@link https://github.com/iamkun/dayjs}
   */
  get timeString () {
    return this.#t.t.format(this.#template)
  }
  /**
   * get hrtime - the timestamp high-resolution real time
   *
   * @return {bigint} approximate real time of instantiation
   * @see {@link https://nodejs.org/api/process.html#process_process_hrtime_bigint}
   */
  get hrtime () {
    return this.#t.b
  }
  /**
   * get user - the logged-in user's name
   *
   * @return {string} user's name
   * @see {@link https://nodejs.org/api/process.html#process_process_env}
   */
  get user () {
    return this.#w.user
  }
  /**
   * get arch - operating system cpu architecture
   *
   * @return {string} architecture for which node.js binary was compiled
   * @see {@link https://nodejs.org/api/process.html#process_process_arch}
   */
  get arch () {
    return this.#w.arch
  }
  /**
   * get type - operating system type
   *
   * @return {string} operating system as returned by `uname(3)`
   * @see {@link https://nodejs.org/api/os.html#os_os_type}
   */
  get type () {
    return this.#w.type
  }
  /**
   * get hostname - system's host name
   *
   * @return {string} host name
   * @see {@link https://nodejs.org/api/os.html#os_os_hostname}
   */
  get hostname () {
    return this.#w.hostname
  }
  /**
   * get platform - operating system platform as set during node.js compile time
   *
   * @return {string} platform
   * @see {@link https://nodejs.org/api/os.html#os_os_platform}
   */
  get platform () {
    return this.#w.platform
  }
  /**
   * get totalmem - total system memory
   *
   * @return {number} integer number of bytes
   * @see {@link https://nodejs.org/api/os.html#os_os_totalmem}
   */
  get totalmem () {
    return this.#w.totalmem
  }
  /**
   * get cwd - current working directory of the Node.js process
   *
   * @return {string}  current working directory
   * @see {@link https://nodejs.org/api/process.html#process_process_cwd}
   */
  get cwd () {
    return this.#d.cwd
  }
  /**
   * get pwd - current working directory of the shell
   *
   * @return {string}  current working directory
   * @see {@link https://nodejs.org/api/process.html#process_process_env}
   */
  get pwd () {
    return this.#d.pwd
  }
  /**
   * get execPath - absolute pathname of the executable that started the Node.js process
   *
   * @return {string}  absolute pathname
   * @see {@link https://nodejs.org/api/process.html#process_process_execpath}
   */
  get execPath () {
    return this.#d.execPath
  }
  [Symbol.toPrimitive] (hint) {
    switch (hint) {
      case 'number':
        return this.valueOf()
      default:
        return this.toString()
    }
  }
  /**
   * toJson - serialize as a JSON string containg the fields:
   *
   * - **d**: hash,
   * - **t**: utc, and
   * - **w**: hash.
   *
   * @return {string} JSON
   */
  toJson () {
    return JSON.stringify({
      t: this.time,
      ...this.#hashes
    })
  }
  /**
   * toString - a hash string representation of instance
   *
   * @return {string}  hash hex
   */
  toString () {
    return this.#h
  }
  /**
   * sharesDWith - determine whether this instance has the same directories as another Constamp instance
   *
   * @param  {Constamp} b the other instance to compare with
   * @return {boolean} true IFF d hashes equal
   */
  sharesDWith (b) {
    if (!(b instanceof Constamp)) {
      return false
    }
    return this.#hashes.d === b.#hashes.d
  }
  /**
   * sharesTimeWith - determine whether this instance has the same epoch time as another Constamp instance
   *
   * @param  {Constamp} b the other instance to compare with
   * @return {boolean} true IFF timestamps equal
   */
  sharesTimeWith (b) {
    if (!(b instanceof Constamp)) {
      return false
    }
    return this.time.isSame(b.time)
  }
  /**
   * sharesWWith - determine whether this instance has the same who as another Constamp instance
   *
   * @param  {Constamp} b the other instance to compare with
   * @return {boolean} true IFF w hashes equal
   */
  sharesWWith (b) {
    if (!(b instanceof Constamp)) {
      return false
    }
    return this.#hashes.w === b.#hashes.w
  }
  /**
   * valueOf - the timestamp UTC epoch time as number
   *
   * @return {number}  UTC epoch ms
   * @see {@link https://github.com/iamkun/dayjs/blob/HEAD/docs/en/API-reference.md#unix-timestamp-milliseconds-valueof}
   */
  valueOf () {
    return this.#t.t.valueOf()
  }
}

Object.defineProperties(Constamp, {
  /**
   * the default timestamp format
   * @default 'YYYY-MM-DD HH:mm ss.SSS'
   * @memberof Constamp
   * @name format
   * @static
   */
  format: {
    value: 'YYYY-MM-DD HH:mm ss.SSS',
    enumerable: true,
    configurable: false,
    writable: false
  },
  /**
   * determine whether given arguments share dir locations
   * @memberof Constamp
   * @method
   * @name D
   * @param {Constamp} a The first Constamp to compare
   * @param {Constamp} b The other Constamp to compare
   * @static
   */
  D: {
    value: (a, b) => {
      return (a instanceof Constamp) && a.sharesDWith(b)
    },
    enumerable: true,
    configurable: false,
    writable: false
  },
  /**
   * determine whether given arguments share epoch timestamps
   * @memberof Constamp
   * @method
   * @name T
   * @param {Constamp} a The first Constamp to compare
   * @param {Constamp} b The other Constamp to compare
   * @static
   */
  T: {
    value: (a, b) => {
      return (a instanceof Constamp) && a.sharesTimeWith(b)
    },
    enumerable: true,
    configurable: false,
    writable: false
  },
  /**
   * determine whether given arguments share user/platform
   * @memberof Constamp
   * @method
   * @name W
   * @param {Constamp} a The first Constamp to compare
   * @param {Constamp} b The other Constamp to compare
   * @static
   */
  W: {
    value: (a, b) => {
      return (a instanceof Constamp) && a.sharesWWith(b)
    },
    enumerable: true,
    configurable: false,
    writable: false
  },
  /**
   * @memberof Constamp
   * @method
   * @name sameDirs
   * @see Constamp.D
   * @static
   */
  sameDirs: {
    get () {
      return Constamp.D
    },
    enumerable: true,
    configurable: false
  },
  /**
   * @memberof Constamp
   * @method
   * @name sameTimes
   * @see Constamp.T
   * @static
   */
  sameTimes: {
    get () {
      return Constamp.T
    },
    enumerable: true,
    configurable: false
  },
  /**
   * @memberof Constamp
   * @method
   * @name sameWhos
   * @see Constamp.W
   * @static
   */
  sameWhos: {
    get () {
      return Constamp.W
    },
    enumerable: true,
    configurable: false
  }
})

export {
  Constamp
}