Source: index.js

"use strict";
/**
 * @file Snowman ID Generator
 * @version v0.0.8
 * @author Adam Mill <hismajesty@theroyalwhee.com>
 * @copyright Copyright 2021 Adam Mill
 * @license Apache-2.0
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.explode = exports.explodeId = exports.idStringSequence = exports.idSequence = exports.CastAs = void 0;
/**
 * Bits:
 * 00-00 (01) = Reserved.
 * 01-40 (40) = MS Timestamp (~34.8 years)
 * 41-51 (10) = Node (1024)
 * 52-64 (13) = Sequence (8192)
 */
/**
 * Imports.
 * @private
 */
const istype_1 = require("@theroyalwhee0/istype");
const constants_js_1 = require("./constants.js");
/**
 * Default Options.
 * @private
 */
const defaultOptions = {
    node: 0,
    offset: constants_js_1.DEFAULT_OFFSET,
    getTimestamp: Date.now,
};
/**
 * Types.
 * @deprecated
 */
var CastAs;
(function (CastAs) {
    CastAs["string"] = "string";
    CastAs["bigint"] = "bigint";
})(CastAs = exports.CastAs || (exports.CastAs = {}));
/**
 * ID Sequence generator.
 * @generator
 * @param {object} options Options, optional.
 * @param {number} options.node The numeric ID of the node (0-1023).
 * @param {number} options.offset The timestamp offset to use as zero time.
 * @param {'string'|'bigint'} options.as Deprecated: Cast generated ID. Defaults to bigint.
 * @param {function} options.getTimestamp A function that returns the current MS timestamp.
 * @param {boolean} options.singleNode Turn into a single node instance automatically populating node as sequence wraps.
 * @yields {bigint} The resulting ID.
 */
function* idSequence(options) {
    options = Object.assign({}, defaultOptions, options);
    const { as, node: nodeOpt, offset, getTimestamp, singleNode } = options;
    let seq = constants_js_1.MIN_SEQUENCE;
    let lastTimestamp;
    // NOTE: singleNode instances start at MAX_NODE and count down.
    let node = singleNode === true ? constants_js_1.MAX_NODE : BigInt(nodeOpt);
    while (1) {
        const now = getTimestamp();
        const timestamp = BigInt(now - offset);
        if (lastTimestamp !== timestamp) {
            seq = constants_js_1.MIN_SEQUENCE;
            lastTimestamp = timestamp;
            if (singleNode) {
                node = constants_js_1.MAX_NODE;
            }
        }
        const id = 0n +
            ((timestamp << constants_js_1.OFFSET_TIMESTAMP) & constants_js_1.MASK_TIMESTAMP) |
            ((node << constants_js_1.OFFSET_NODE) & constants_js_1.MASK_NODE) |
            ((seq << constants_js_1.OFFSET_SEQUENCE) & constants_js_1.MASK_SEQUENCE);
        if (timestamp < constants_js_1.MIN_TIMESTAMP || timestamp > constants_js_1.MAX_TIMESTAMP) {
            throw new Error(`Timestamp "${timestamp}" out of range."`);
        }
        else if (node < constants_js_1.MIN_NODE || node > constants_js_1.MAX_NODE) {
            throw new Error(`Node "${node}" is out of range.`);
        }
        else if (seq < constants_js_1.MIN_SEQUENCE || seq > constants_js_1.MAX_SEQUENCE) {
            throw new Error(`Sequence number "${seq}" is out of range.`);
        }
        else if (id < constants_js_1.MIN_ID || id > constants_js_1.MAX_ID) {
            throw new Error(`ID "${id}" is out of range.`);
        }
        if (as === CastAs.string) {
            yield '' + id;
        }
        else {
            yield id;
        }
        seq += 1n;
        if (singleNode && seq > constants_js_1.MAX_SEQUENCE) {
            node -= 1n;
            seq = constants_js_1.MIN_SEQUENCE;
        }
    }
    return;
}
exports.idSequence = idSequence;
/**
 * ID Sequence generator yielding strings instead of bigints.
 * @generator
 * @param {object} options Options, optional.
 * @param {number} options.node The numeric ID of the node (0-1023).
 * @param {number} options.offset The timestamp offset to use as zero time.
 * @param {function} options.getTimestamp A function that returns the current MS timestamp.
 * @param {boolean} options.singleNode Turn into a single node instance automatically populating node as sequence wraps.
 * @yields {string} The resulting ID as a string.
 */
function* idStringSequence(options) {
    options = options || {};
    const sequence = idSequence(options);
    while (1) {
        const { value } = sequence.next();
        yield '' + value;
    }
    return;
}
exports.idStringSequence = idStringSequence;
/**
 * Explode an ID into parts and valid flag,
 * @param {bigint|number|string} id An ID to explode and validate.
 * @param {object} options Options, optional.
 * @param {number} options.offset The timestamp offset to use as zero time.
 * @returns {array} Tuple of unix timestamp, node ID,
 * sequence ID, and valid. If invalid the numbers will be undefined.
 */
function explodeId(id, options) {
    options = Object.assign({}, defaultOptions, options);
    const { offset } = options;
    if (istype_1.isString(id) && /^[1-9][0-9]*$/.test(id)) {
        id = BigInt(id);
    }
    if (istype_1.isInteger(id)) {
        id = BigInt(id);
    }
    if (istype_1.isBigInt(id)) {
        const reserved = Number((id & constants_js_1.MASK_RESERVED) >> constants_js_1.OFFSET_RESERVED);
        const timestamp = Number((id & constants_js_1.MASK_TIMESTAMP) >> constants_js_1.OFFSET_TIMESTAMP);
        const node = Number((id & constants_js_1.MASK_NODE) >> constants_js_1.OFFSET_NODE);
        const sequence = Number((id & constants_js_1.MASK_SEQUENCE) >> constants_js_1.OFFSET_SEQUENCE);
        const isValid = reserved === constants_js_1.RESERVED &&
            (id >= constants_js_1.MIN_ID && id <= constants_js_1.MAX_ID) &&
            (timestamp >= constants_js_1.MIN_TIMESTAMP && timestamp <= constants_js_1.MAX_TIMESTAMP) &&
            (node >= constants_js_1.MIN_NODE && node <= constants_js_1.MAX_NODE) &&
            (sequence >= constants_js_1.MIN_SEQUENCE && sequence <= constants_js_1.MAX_SEQUENCE);
        const unixTimestamp = timestamp + offset;
        if (isValid) {
            return [unixTimestamp, node, sequence, true];
        }
    }
    return [undefined, undefined, undefined, false];
}
exports.explodeId = explodeId;
/**
 * Explode an ID into parts and valid flag,
 * @deprecated Use explodeId function instead.
 * @function explode
 * @param {any} id An ID to explode and validate.
 * @param {object} options Options, optional.
 * @param {number} options.offset The timestamp offset to use as zero time.
 * @returns {array} Tuple of unix timestamp, node ID,
 * sequence ID, and valid. If invalid the numbers will be undefined.
 */
exports.explode = explodeId;
//# sourceMappingURL=index.js.map