'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
}