@sv443-network/coreutils
Version:
Cross-platform, general-purpose, JavaScript core library for Node, Deno and the browser. Intended to be used in conjunction with `@sv443-network/userutils` and `@sv443-network/djsutils`, but can be used independently as well.
1,277 lines (1,261 loc) • 80 kB
JavaScript
/* umd */
(function (g, f) {
if ("object" == typeof exports && "object" == typeof module) {
module.exports = f();
} else if ("function" == typeof define && define.amd) {
define("CoreUtils", [], f);
} else if ("object" == typeof exports) {
exports["CoreUtils"] = f();
} else {
g["CoreUtils"] = f();
}
}(this, () => {
var exports = {};
var module = { exports };
(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("CoreUtils",f)}else {g["CoreUtils"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports};
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __pow = Math.pow;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// lib/index.ts
var lib_exports = {};
__export(lib_exports, {
BrowserStorageEngine: () => BrowserStorageEngine,
ChecksumMismatchError: () => ChecksumMismatchError,
CustomError: () => CustomError,
DataStore: () => DataStore,
DataStoreEngine: () => DataStoreEngine,
DataStoreSerializer: () => DataStoreSerializer,
DatedError: () => DatedError,
Debouncer: () => Debouncer,
FileStorageEngine: () => FileStorageEngine,
MigrationError: () => MigrationError,
NanoEmitter: () => NanoEmitter,
NetworkError: () => NetworkError,
PicoEmitter: () => PicoEmitter,
ScriptContextError: () => ScriptContextError,
ValidationError: () => ValidationError,
abtoa: () => abtoa,
atoab: () => atoab,
autoPlural: () => autoPlural,
bitSetHas: () => bitSetHas,
capitalize: () => capitalize,
clamp: () => clamp,
compress: () => compress,
computeHash: () => computeHash,
consumeGen: () => consumeGen,
consumeStringGen: () => consumeStringGen,
createProgressBar: () => createProgressBar,
createRecurringTask: () => createRecurringTask,
createTable: () => createTable,
darkenColor: () => darkenColor,
debounce: () => debounce,
decompress: () => decompress,
defaultPbChars: () => defaultPbChars,
defaultTableLineCharset: () => defaultTableLineCharset,
digitCount: () => digitCount,
fetchAdvanced: () => fetchAdvanced,
formatNumber: () => formatNumber,
getCallStack: () => getCallStack,
getListLength: () => getListLength,
getterifyObj: () => getterifyObj,
hexToRgb: () => hexToRgb,
insertValues: () => insertValues,
joinArrayReadable: () => joinArrayReadable,
lightenColor: () => lightenColor,
mapRange: () => mapRange,
overflowVal: () => overflowVal,
pauseFor: () => pauseFor,
pureObj: () => pureObj,
randRange: () => randRange,
randomId: () => randomId,
randomItem: () => randomItem,
randomItemIndex: () => randomItemIndex,
randomizeArray: () => randomizeArray,
rgbToHex: () => rgbToHex,
roundFixed: () => roundFixed,
scheduleExit: () => scheduleExit,
secsToTimeStr: () => secsToTimeStr,
setImmediateInterval: () => setImmediateInterval,
setImmediateTimeoutLoop: () => setImmediateTimeoutLoop,
takeRandomItem: () => takeRandomItem,
takeRandomItemIndex: () => takeRandomItemIndex,
truncStr: () => truncStr,
valsWithin: () => valsWithin
});
module.exports = __toCommonJS(lib_exports);
// lib/math.ts
function bitSetHas(bitSet, checkVal) {
return (bitSet & checkVal) === checkVal;
}
function clamp(value, min, max) {
if (typeof max !== "number") {
max = min;
min = 0;
}
return Math.max(Math.min(value, max), min);
}
function digitCount(num, withDecimals = true) {
num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
if (typeof num === "number" && isNaN(num))
return NaN;
const [intPart, decPart] = num.toString().split(".");
const intDigits = intPart === "0" ? 1 : Math.floor(Math.log10(Math.abs(Number(intPart))) + 1);
const decDigits = withDecimals && decPart ? decPart.length : 0;
return intDigits + decDigits;
}
function formatNumber(number, locale, format) {
return number.toLocaleString(
locale,
format === "short" ? {
notation: "compact",
compactDisplay: "short",
maximumFractionDigits: 1
} : {
style: "decimal",
maximumFractionDigits: 0
}
);
}
function mapRange(value, range1min, range1max, range2min, range2max) {
if (typeof range2min === "undefined" || typeof range2max === "undefined") {
range2max = range1max;
range1max = range1min;
range2min = range1min = 0;
}
if (Number(range1min) === 0 && Number(range2min) === 0)
return value * (range2max / range1max);
return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
}
function overflowVal(value, minOrMax, max) {
const min = typeof max === "number" ? minOrMax : 0;
max = typeof max === "number" ? max : minOrMax;
if (min > max)
throw new RangeError(`Parameter "min" can't be bigger than "max"`);
if (isNaN(value) || isNaN(min) || isNaN(max) || !isFinite(value) || !isFinite(min) || !isFinite(max))
return NaN;
if (value >= min && value <= max)
return value;
const range = max - min + 1;
const wrappedValue = ((value - min) % range + range) % range + min;
return wrappedValue;
}
function randRange(...args) {
let min, max, enhancedEntropy = false;
if (typeof args[0] === "number" && typeof args[1] === "number")
[min, max] = args;
else if (typeof args[0] === "number" && typeof args[1] !== "number") {
min = 0;
[max] = args;
} else
throw new TypeError(`Wrong parameter(s) provided - expected (number, boolean|undefined) or (number, number, boolean|undefined) but got (${args.map((a) => typeof a).join(", ")}) instead`);
if (typeof args[2] === "boolean")
enhancedEntropy = args[2];
else if (typeof args[1] === "boolean")
enhancedEntropy = args[1];
min = Number(min);
max = Number(max);
if (isNaN(min) || isNaN(max))
return NaN;
if (min > max)
throw new TypeError(`Parameter "min" can't be bigger than "max"`);
if (enhancedEntropy) {
const uintArr = new Uint8Array(1);
crypto.getRandomValues(uintArr);
return Number(Array.from(
uintArr,
(v) => Math.round(mapRange(v, 0, 255, min, max)).toString(10)
).join(""));
} else
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function roundFixed(num, fractionDigits) {
const scale = __pow(10, fractionDigits);
return Math.round(num * scale) / scale;
}
function valsWithin(a, b, dec = 1, withinRange = 0.5) {
return Math.abs(roundFixed(a, dec) - roundFixed(b, dec)) <= withinRange;
}
// lib/array.ts
function randomItem(array) {
return randomItemIndex(array)[0];
}
function randomItemIndex(array) {
if (array.length === 0)
return [void 0, void 0];
const idx = randRange(array.length - 1);
return [array[idx], idx];
}
function randomizeArray(array) {
const retArray = [...array];
if (array.length === 0)
return retArray;
for (let i = retArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[retArray[i], retArray[j]] = [retArray[j], retArray[i]];
}
return retArray;
}
function takeRandomItem(arr) {
var _a;
return (_a = takeRandomItemIndex(arr)) == null ? void 0 : _a[0];
}
function takeRandomItemIndex(arr) {
const [itm, idx] = randomItemIndex(arr);
if (idx === void 0)
return [void 0, void 0];
arr.splice(idx, 1);
return [itm, idx];
}
// lib/colors.ts
function darkenColor(color, percent, upperCase = false) {
var _a;
color = color.trim();
const darkenRgb = (r2, g2, b2, percent2) => {
r2 = Math.max(0, Math.min(255, r2 - r2 * percent2 / 100));
g2 = Math.max(0, Math.min(255, g2 - g2 * percent2 / 100));
b2 = Math.max(0, Math.min(255, b2 - b2 * percent2 / 100));
return [r2, g2, b2];
};
let r, g, b, a;
const isHexCol = color.match(/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/);
if (isHexCol)
[r, g, b, a] = hexToRgb(color);
else if (color.startsWith("rgb")) {
const rgbValues = (_a = color.match(/\d+(\.\d+)?/g)) == null ? void 0 : _a.map(Number);
if (!rgbValues)
throw new TypeError("Invalid RGB/RGBA color format");
[r, g, b, a] = rgbValues;
} else
throw new TypeError("Unsupported color format");
[r, g, b] = darkenRgb(r, g, b, percent);
if (isHexCol)
return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
else if (color.startsWith("rgba"))
return `rgba(${r}, ${g}, ${b}, ${a != null ? a : NaN})`;
else
return `rgb(${r}, ${g}, ${b})`;
}
function hexToRgb(hex) {
hex = (hex.startsWith("#") ? hex.slice(1) : hex).trim();
const a = hex.length === 8 || hex.length === 4 ? parseInt(hex.slice(-(hex.length / 4)), 16) / (hex.length === 8 ? 255 : 15) : void 0;
if (!isNaN(Number(a)))
hex = hex.slice(0, -(hex.length / 4));
if (hex.length === 3 || hex.length === 4)
hex = hex.split("").map((c) => c + c).join("");
const hexInt = parseInt(hex, 16);
const r = hexInt >> 16 & 255;
const g = hexInt >> 8 & 255;
const b = hexInt & 255;
return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), typeof a === "number" ? clamp(a, 0, 1) : void 0];
}
function lightenColor(color, percent, upperCase = false) {
return darkenColor(color, percent * -1, upperCase);
}
function rgbToHex(red, green, blue, alpha, withHash = true, upperCase = false) {
const toHexVal = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")[upperCase ? "toUpperCase" : "toLowerCase"]();
return `${withHash ? "#" : ""}${toHexVal(red)}${toHexVal(green)}${toHexVal(blue)}${alpha ? toHexVal(alpha * 255) : ""}`;
}
// lib/crypto.ts
function abtoa(buf) {
return btoa(
new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
);
}
function atoab(str) {
return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
}
function compress(input, compressionFormat, outputType = "string") {
return __async(this, null, function* () {
var _a;
const byteArray = input instanceof Uint8Array ? input : new TextEncoder().encode((_a = input == null ? void 0 : input.toString()) != null ? _a : String(input));
const comp = new CompressionStream(compressionFormat);
const writer = comp.writable.getWriter();
writer.write(byteArray);
writer.close();
const uintArr = new Uint8Array(yield new Response(comp.readable).arrayBuffer());
return outputType === "arrayBuffer" ? uintArr : abtoa(uintArr);
});
}
function decompress(input, compressionFormat, outputType = "string") {
return __async(this, null, function* () {
var _a;
const byteArray = input instanceof Uint8Array ? input : atoab((_a = input == null ? void 0 : input.toString()) != null ? _a : String(input));
const decomp = new DecompressionStream(compressionFormat);
const writer = decomp.writable.getWriter();
writer.write(byteArray);
writer.close();
const uintArr = new Uint8Array(yield new Response(decomp.readable).arrayBuffer());
return outputType === "arrayBuffer" ? uintArr : new TextDecoder().decode(uintArr);
});
}
function computeHash(input, algorithm = "SHA-256") {
return __async(this, null, function* () {
let data;
if (typeof input === "string") {
const encoder = new TextEncoder();
data = encoder.encode(input);
} else
data = input;
const hashBuffer = yield crypto.subtle.digest(algorithm, data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
return hashHex;
});
}
function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
if (length < 1)
throw new RangeError("The length argument must be at least 1");
if (radix < 2 || radix > 36)
throw new RangeError("The radix argument must be between 2 and 36");
let arr = [];
const caseArr = randomCase ? [0, 1] : [0];
if (enhancedEntropy) {
const uintArr = new Uint8Array(length);
crypto.getRandomValues(uintArr);
arr = Array.from(
uintArr,
(v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
);
} else {
arr = Array.from(
{ length },
() => Math.floor(Math.random() * radix).toString(radix)
);
}
if (!arr.some((v) => /[a-zA-Z]/.test(v)))
return arr.join("");
return arr.map((v) => caseArr[randRange(0, caseArr.length - 1, enhancedEntropy)] === 1 ? v.toUpperCase() : v).join("");
}
// lib/Errors.ts
var DatedError = class extends Error {
constructor(message, options) {
super(message, options);
__publicField(this, "date");
this.name = this.constructor.name;
this.date = /* @__PURE__ */ new Date();
}
};
var ChecksumMismatchError = class extends DatedError {
constructor(message, options) {
super(message, options);
this.name = "ChecksumMismatchError";
}
};
var CustomError = class extends DatedError {
constructor(name, message, options) {
super(message, options);
this.name = name;
}
};
var MigrationError = class extends DatedError {
constructor(message, options) {
super(message, options);
this.name = "MigrationError";
}
};
var ValidationError = class extends DatedError {
constructor(message, options) {
super(message, options);
this.name = "ValidationError";
}
};
var ScriptContextError = class extends DatedError {
constructor(message, options) {
super(message, options);
this.name = "ScriptContextError";
}
};
var NetworkError = class extends DatedError {
constructor(message, options) {
super(message, options);
this.name = "NetworkError";
}
};
// lib/misc.ts
function consumeGen(valGen, ...args) {
return __async(this, null, function* () {
return yield typeof valGen === "function" ? valGen(...args) : valGen;
});
}
function consumeStringGen(strGen, ...args) {
return __async(this, null, function* () {
return typeof strGen === "string" ? strGen : String(
typeof strGen === "function" ? yield strGen(...args) : strGen
);
});
}
function fetchAdvanced(_0) {
return __async(this, arguments, function* (input, options = {}) {
const _a = options, { timeout = 1e4, signal } = _a, restOpts = __objRest(_a, ["timeout", "signal"]);
const fetchOpts = __spreadValues({}, restOpts);
if (signal)
fetchOpts.signal = signal;
let timeoutId;
try {
const fetchPromise = fetch(input, fetchOpts);
if (timeout < 0)
return yield fetchPromise;
const res = yield Promise.race([
fetchPromise,
new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new DOMException("The operation timed out.", "TimeoutError")), timeout);
})
]);
clearTimeout(timeoutId);
return res;
} catch (err) {
clearTimeout(timeoutId);
throw new NetworkError("Error while calling fetch", { cause: err });
}
});
}
function getListLength(listLike, zeroOnInvalid = true) {
return "length" in listLike ? listLike.length : "size" in listLike ? listLike.size : "count" in listLike ? listLike.count : zeroOnInvalid ? 0 : NaN;
}
function pauseFor(time, signal, rejectOnAbort = false) {
return new Promise((res, rej) => {
const timeout = setTimeout(() => res(), time);
signal == null ? void 0 : signal.addEventListener("abort", () => {
clearTimeout(timeout);
rejectOnAbort ? rej(new CustomError("AbortError", "The pause was aborted")) : res();
});
});
}
function pureObj(obj) {
return Object.assign(/* @__PURE__ */ Object.create(null), obj != null ? obj : {});
}
function getterifyObj(obj, asCopy = false) {
const newObj = {};
for (const key in obj) {
Object.defineProperty(newObj, key, {
get: () => obj[key],
enumerable: true,
configurable: true
});
}
return asCopy ? structuredClone(newObj) : newObj;
}
function setImmediateInterval(callback, interval, signal) {
let intervalId;
const cleanup = () => clearInterval(intervalId);
const loop = () => {
if (signal == null ? void 0 : signal.aborted)
return cleanup();
callback();
};
signal == null ? void 0 : signal.addEventListener("abort", cleanup);
loop();
intervalId = setInterval(loop, interval);
}
function setImmediateTimeoutLoop(callback, interval, signal) {
let timeout;
const cleanup = () => clearTimeout(timeout);
const loop = () => __async(null, null, function* () {
if (signal == null ? void 0 : signal.aborted)
return cleanup();
yield callback();
timeout = setTimeout(loop, interval);
});
signal == null ? void 0 : signal.addEventListener("abort", cleanup);
loop();
}
function scheduleExit(code = 0, timeout = 0) {
if (timeout < 0)
throw new TypeError("Timeout must be a non-negative number");
let exit;
if (typeof process !== "undefined" && "exit" in process && typeof process.exit === "function")
exit = () => process.exit(code);
else if (typeof Deno !== "undefined" && "exit" in Deno && typeof Deno.exit === "function")
exit = () => Deno.exit(code);
else
throw new ScriptContextError("Cannot exit the process, no exit method available");
setTimeout(exit, timeout);
}
function getCallStack(asArray, lines = Infinity) {
var _a;
if (typeof lines !== "number" || isNaN(lines) || lines < 0)
throw new TypeError("lines parameter must be a non-negative number");
try {
throw new CustomError("GetCallStack", "Capturing a stack trace with CoreUtils.getCallStack(). If you see this anywhere, you can safely ignore it.");
} catch (err) {
const stack = ((_a = err.stack) != null ? _a : "").split("\n").map((line) => line.trim()).slice(2, lines + 2);
return asArray !== false ? stack : stack.join("\n");
}
}
function createRecurringTask(options) {
var _a;
let iterations = 0;
let aborted = false;
(_a = options.signal) == null ? void 0 : _a.addEventListener("abort", () => {
aborted = true;
}, { once: true });
const runRecurringTask = (initial = false) => __async(null, null, function* () {
var _a2, _b, _c;
if (aborted)
return;
try {
if (((_a2 = options.immediate) != null ? _a2 : true) || !initial) {
iterations++;
if ((_c = yield (_b = options.condition) == null ? void 0 : _b.call(options, iterations - 1)) != null ? _c : true) {
const val = yield options.task(iterations - 1);
if (options.onSuccess)
yield options.onSuccess(val, iterations - 1);
}
}
} catch (err) {
if (options.onError)
yield options.onError(err, iterations - 1);
if (options.abortOnError)
aborted = true;
if (!options.onError && !options.abortOnError)
throw err;
}
if (!aborted && (typeof options.maxIterations !== "number" || iterations < options.maxIterations))
setTimeout(runRecurringTask, options.timeout);
});
return runRecurringTask(true);
}
// lib/text.ts
function autoPlural(term, num, pluralType = "auto") {
if (typeof num !== "number") {
if ("length" in num)
num = num.length;
else if ("size" in num)
num = num.size;
else if ("count" in num)
num = num.count;
}
if (!["-s", "-ies"].includes(pluralType))
pluralType = "auto";
if (isNaN(num))
num = 2;
const pType = pluralType === "auto" ? String(term).endsWith("y") ? "-ies" : "-s" : pluralType;
switch (pType) {
case "-s":
return `${term}${num === 1 ? "" : "s"}`;
case "-ies":
return `${String(term).slice(0, -1)}${num === 1 ? "y" : "ies"}`;
}
}
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
var defaultPbChars = {
100: "\u2588",
75: "\u2593",
50: "\u2592",
25: "\u2591",
0: "\u2500"
};
function createProgressBar(percentage, barLength, chars = defaultPbChars) {
if (percentage === 100)
return chars[100].repeat(barLength);
const filledLength = Math.floor(percentage / 100 * barLength);
const remainingPercentage = percentage / 10 * barLength - filledLength;
let lastBlock = "";
if (remainingPercentage >= 0.75)
lastBlock = chars[75];
else if (remainingPercentage >= 0.5)
lastBlock = chars[50];
else if (remainingPercentage >= 0.25)
lastBlock = chars[25];
const filledBar = chars[100].repeat(filledLength);
const emptyBar = chars[0].repeat(barLength - filledLength - (lastBlock ? 1 : 0));
return `${filledBar}${lastBlock}${emptyBar}`;
}
function insertValues(input, ...values) {
return input.replace(/%\d/gm, (match) => {
var _a, _b;
const argIndex = Number(match.substring(1)) - 1;
return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
});
}
function joinArrayReadable(array, separators = ", ", lastSeparator = " and ") {
const arr = [...array];
if (arr.length === 0)
return "";
else if (arr.length === 1)
return String(arr[0]);
else if (arr.length === 2)
return arr.join(lastSeparator);
const lastItm = lastSeparator + arr[arr.length - 1];
arr.pop();
return arr.join(separators) + lastItm;
}
function secsToTimeStr(seconds) {
const isNegative = seconds < 0;
const s = Math.abs(seconds);
if (isNaN(s) || !isFinite(s))
throw new TypeError("The seconds argument must be a valid number");
const hrs = Math.floor(s / 3600);
const mins = Math.floor(s % 3600 / 60);
const secs = Math.floor(s % 60);
return (isNegative ? "-" : "") + [
hrs ? hrs + ":" : "",
String(mins).padStart(mins > 0 || hrs > 0 ? 2 : 1, "0"),
":",
String(secs).padStart(secs > 0 || mins > 0 || hrs > 0 || seconds === 0 ? 2 : 1, "0")
].join("");
}
function truncStr(input, length, endStr = "...") {
var _a;
const str = (_a = input == null ? void 0 : input.toString()) != null ? _a : String(input);
const finalStr = str.length > length ? str.substring(0, length - endStr.length) + endStr : str;
return finalStr.length > length ? finalStr.substring(0, length) : finalStr;
}
var defaultTableLineCharset = {
single: {
horizontal: "\u2500",
vertical: "\u2502",
topLeft: "\u250C",
topRight: "\u2510",
bottomLeft: "\u2514",
bottomRight: "\u2518",
leftT: "\u251C",
rightT: "\u2524",
topT: "\u252C",
bottomT: "\u2534",
cross: "\u253C"
},
double: {
horizontal: "\u2550",
vertical: "\u2551",
topLeft: "\u2554",
topRight: "\u2557",
bottomLeft: "\u255A",
bottomRight: "\u255D",
leftT: "\u2560",
rightT: "\u2563",
topT: "\u2566",
bottomT: "\u2569",
cross: "\u256C"
},
none: {
horizontal: " ",
vertical: " ",
topLeft: " ",
topRight: " ",
bottomLeft: " ",
bottomRight: " ",
leftT: " ",
rightT: " ",
topT: " ",
bottomT: " ",
cross: " "
}
};
function createTable(rows, options) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
const opts = __spreadValues({
columnAlign: "left",
truncateAbove: Infinity,
truncEndStr: "\u2026",
minPadding: 1,
lineStyle: "single",
applyCellStyle: () => void 0,
applyLineStyle: () => void 0,
lineCharset: defaultTableLineCharset
}, options != null ? options : {});
const defRange = (val, min, max) => clamp(typeof val !== "number" || isNaN(Number(val)) ? min : val, min, max);
opts.truncateAbove = defRange(opts.truncateAbove, 0, Infinity);
opts.minPadding = defRange(opts.minPadding, 0, Infinity);
const lnCh = opts.lineCharset[opts.lineStyle];
const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, "");
const stringRows = rows.map((row) => row.map((cell) => String(cell)));
const colCount = (_b = (_a = rows[0]) == null ? void 0 : _a.length) != null ? _b : 0;
if (colCount === 0 || stringRows.length === 0)
return "";
if (isFinite(opts.truncateAbove)) {
const truncAnsi = (str, maxVisible, endStr) => {
const limit = maxVisible - endStr.length;
if (limit <= 0)
return endStr.slice(0, maxVisible);
let visible = 0;
let result = "";
let i = 0;
let hasAnsi = false;
while (i < str.length) {
if (str[i] === "\x1B" && str[i + 1] === "[") {
const seqEnd = str.indexOf("m", i + 2);
if (seqEnd !== -1) {
result += str.slice(i, seqEnd + 1);
hasAnsi = true;
i = seqEnd + 1;
continue;
}
}
if (visible === limit) {
result += endStr;
if (hasAnsi)
result += "\x1B[0m";
return result;
}
result += str[i];
visible++;
i++;
}
return result;
};
for (const row of stringRows)
for (let j = 0; j < row.length; j++)
if (stripAnsi((_c = row[j]) != null ? _c : "").length > opts.truncateAbove)
row[j] = truncAnsi((_d = row[j]) != null ? _d : "", opts.truncateAbove, opts.truncEndStr);
}
const colWidths = Array.from(
{ length: colCount },
(_, j) => Math.max(0, ...stringRows.map((row) => {
var _a2;
return stripAnsi((_a2 = row[j]) != null ? _a2 : "").length;
}))
);
const applyLn = (i, j, ch) => {
var _a2;
const [before = "", after = ""] = (_a2 = opts.applyLineStyle(i, j)) != null ? _a2 : [];
return `${before}${ch}${after}`;
};
const buildBorderRow = (lineIdx, leftCh, midCh, rightCh) => {
var _a2;
let result = "";
let j = 0;
result += applyLn(lineIdx, j++, leftCh);
for (let col = 0; col < colCount; col++) {
const cellWidth = ((_a2 = colWidths[col]) != null ? _a2 : 0) + opts.minPadding * 2;
for (let ci = 0; ci < cellWidth; ci++)
result += applyLn(lineIdx, j++, lnCh.horizontal);
if (col < colCount - 1)
result += applyLn(lineIdx, j++, midCh);
}
result += applyLn(lineIdx, j++, rightCh);
return result;
};
const lines = [];
for (let rowIdx = 0; rowIdx < stringRows.length; rowIdx++) {
const row = (_e = stringRows[rowIdx]) != null ? _e : [];
const lineIdxBase = rowIdx * 3;
if (opts.lineStyle !== "none") {
lines.push(
rowIdx === 0 ? buildBorderRow(lineIdxBase, lnCh.topLeft, lnCh.topT, lnCh.topRight) : buildBorderRow(lineIdxBase, lnCh.leftT, lnCh.cross, lnCh.rightT)
);
}
let contentLine = "";
let j = 0;
contentLine += applyLn(lineIdxBase + 1, j++, lnCh.vertical);
for (let colIdx = 0; colIdx < colCount; colIdx++) {
const cell = (_f = row[colIdx]) != null ? _f : "";
const visLen = stripAnsi(cell).length;
const extra = ((_g = colWidths[colIdx]) != null ? _g : 0) - visLen;
const align = (_h = Array.isArray(opts.columnAlign) ? opts.columnAlign[colIdx] : opts.columnAlign) != null ? _h : "left";
let leftPad;
let rightPad;
switch (align) {
case "right":
leftPad = opts.minPadding + extra;
rightPad = opts.minPadding;
break;
case "centerLeft":
leftPad = opts.minPadding + Math.floor(extra / 2);
rightPad = opts.minPadding + Math.ceil(extra / 2);
break;
case "centerRight":
leftPad = opts.minPadding + Math.ceil(extra / 2);
rightPad = opts.minPadding + Math.floor(extra / 2);
break;
default:
leftPad = opts.minPadding;
rightPad = opts.minPadding + extra;
}
const [cellBefore = "", cellAfter = ""] = (_i = opts.applyCellStyle(rowIdx, colIdx)) != null ? _i : [];
contentLine += " ".repeat(leftPad) + cellBefore + cell + cellAfter + " ".repeat(rightPad);
contentLine += applyLn(lineIdxBase + 1, j++, lnCh.vertical);
}
lines.push(contentLine);
if (opts.lineStyle !== "none" && rowIdx === stringRows.length - 1)
lines.push(buildBorderRow(lineIdxBase + 2, lnCh.bottomLeft, lnCh.bottomT, lnCh.bottomRight));
}
return lines.join("\n");
}
// node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
var createNanoEvents = () => ({
emit(event, ...args) {
for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
callbacks[i](...args);
}
},
events: {},
on(event, cb) {
var _a;
;
((_a = this.events)[event] || (_a[event] = [])).push(cb);
return () => {
var _a2;
this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
};
}
});
// lib/PicoEmitter.ts
var PicoEmitter = class {
/**
* ⚠️ You cannot instantiate `PicoEmitter` directly, it's only meant for extending in your own classes. If you want a standalone emitter, use `NanoEmitter` instead.
*/
constructor(options = {}) {
/**
* The nanoevents emitter instance used internally.
* ⚠️ You should use the protected method `emitEvent()` instead of emitting directly through this, as it updates the catch-up memory for any events listed in `catchUpEvents`. Only use `this.events.emit()` if you're not using `catchUpEvents` or are doing manual memory management.
*/
__publicField(this, "events", createNanoEvents());
__publicField(this, "eventUnsubscribes", []);
__publicField(this, "emitterOptions");
/** Stores the latest arguments for each emitted event that's listed in `catchUpEvents`. */
__publicField(this, "catchUpMemory", /* @__PURE__ */ new Map());
this.emitterOptions = __spreadValues({}, options);
}
//#region emitEvent
/**
* Emits an event on this instance.
* You should use this over `this.events.emit()` in subclasses as it updates the catch-up memory for any event listed in `catchUpEvents`, so that listeners attached after emitting can still receive the latest value.
*/
emitEvent(event, ...args) {
var _a;
if ((_a = this.emitterOptions.catchUpEvents) == null ? void 0 : _a.includes(event))
this.catchUpMemory.set(event, args);
this.events.emit(event, ...args);
}
//#region on
/**
* Subscribes to an event and calls the callback when it's emitted.
* If the event has already been emitted and is listed in `catchUpEvents`, the callback will be called immediately with the latest emitted arguments (catch-up behaviour).
* @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
* @returns Returns a function that can be called to unsubscribe the event listener
* @example ```ts
* const emitter = new PicoEmitter<{
* foo: (bar: string) => void;
* }>({
* publicEmit: true,
* });
*
* let i = 0;
* const unsub = emitter.on("foo", (bar) => {
* // unsubscribe after 10 events:
* if(++i === 10) unsub();
* console.log(bar);
* });
*
* emitter.emit("foo", "bar");
* ```
*/
on(event, cb) {
let unsub;
const unsubProxy = () => {
if (!unsub)
return;
unsub();
this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
};
unsub = this.events.on(event, cb);
this.eventUnsubscribes.push(unsub);
const memory = this.catchUpMemory.get(event);
if (memory)
cb(...memory);
return unsubProxy;
}
//#region once
/**
* Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
* If the event has already been emitted and is listed in `catchUpEvents`, the callback will be called immediately with the latest emitted arguments (catch-up behaviour).
* @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
* @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
* @returns Returns a Promise that resolves with the event arguments when the event is emitted
* @example ```ts
* const emitter = new PicoEmitter<{
* foo: (bar: string) => void;
* }>();
*
* // Promise syntax:
* const [bar] = await emitter.once("foo");
* console.log(bar);
*
* // Callback syntax:
* emitter.once("foo", (bar) => console.log(bar));
* ```
*/
once(event, cb) {
const memory = this.catchUpMemory.get(event);
if (memory) {
const args = memory;
cb == null ? void 0 : cb(...args);
return Promise.resolve(args);
}
return new Promise((resolve) => {
let unsub;
const onceProxy = ((...args) => {
cb == null ? void 0 : cb(...args);
unsub == null ? void 0 : unsub();
resolve(args);
});
unsub = this.events.on(event, onceProxy);
this.eventUnsubscribes.push(unsub);
});
}
//#region onMulti
/**
* Allows subscribing to multiple events and calling the callback only when one of, all of, or a subset of the events are emitted, either continuously or only once.
* If any of the events have already been emitted and are listed in `catchUpEvents`, the callback will be called immediately if the criteria are met, with the latest emitted arguments (catch-up behaviour).
* @param options An object or array of objects with the following properties:
* `callback` (required) is the function that will be called when the conditions are met.
*
* Set `once` to true to call the callback only once for the first event (or set of events) that match the criteria, then stop listening.
* If `signal` is provided, the subscription will be canceled when the given signal is aborted.
*
* If `oneOf` is used, the callback will be called when any of the matching events are emitted.
* If `allOf` is used, the callback will be called after all of the matching events are emitted at least once, then any time any of them are emitted.
* If both `oneOf` and `allOf` are used together, the callback will be called when any of the `oneOf` events are emitted AND all of the `allOf` events have been emitted at least once.
* At least one of `oneOf` or `allOf` must be provided.
*
* @returns Returns a function that can be called to unsubscribe all listeners created by this call. Alternatively, pass an `AbortSignal` to all options objects to achieve the same effect or for finer control.
*/
onMulti(options) {
const allUnsubs = [];
const unsubAll = () => {
for (const unsub of allUnsubs)
unsub();
allUnsubs.splice(0, allUnsubs.length);
this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
};
for (const opts of Array.isArray(options) ? options : [options]) {
const optsWithDefaults = __spreadValues({
allOf: [],
oneOf: [],
once: false
}, opts);
const {
oneOf,
allOf,
once,
signal,
callback
} = optsWithDefaults;
if (signal == null ? void 0 : signal.aborted)
return unsubAll;
if (oneOf.length === 0 && allOf.length === 0)
throw new TypeError("PicoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
const curEvtUnsubs = [];
const checkUnsubAllEvt = (force = false) => {
if (!(signal == null ? void 0 : signal.aborted) && !force)
return;
for (const unsub of curEvtUnsubs)
unsub();
curEvtUnsubs.splice(0, curEvtUnsubs.length);
this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
};
const allOfEmitted = /* @__PURE__ */ new Set();
const allOfConditionMet = () => allOf.length === 0 || allOfEmitted.size === allOf.length;
for (const event of oneOf) {
const unsub = this.events.on(event, ((...args) => {
checkUnsubAllEvt();
if (allOfConditionMet()) {
callback(event, ...args);
if (once)
checkUnsubAllEvt(true);
}
}));
curEvtUnsubs.push(unsub);
}
for (const event of allOf) {
const unsub = this.events.on(event, ((...args) => {
checkUnsubAllEvt();
allOfEmitted.add(event);
if (allOfConditionMet() && (oneOf.length === 0 || oneOf.includes(event))) {
callback(event, ...args);
if (once)
checkUnsubAllEvt(true);
}
}));
curEvtUnsubs.push(unsub);
}
allUnsubs.push(() => checkUnsubAllEvt(true));
}
return unsubAll;
}
//#region unsubscribeAll
/** Unsubscribes all event listeners from this instance. Also clears the event catch-up memory. */
unsubscribeAll() {
for (const unsub of this.eventUnsubscribes)
unsub();
this.eventUnsubscribes = [];
this.catchUpMemory.clear();
}
};
// lib/NanoEmitter.ts
var NanoEmitter = class extends PicoEmitter {
/** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
constructor(options = {}) {
super(options);
__publicField(this, "events", createNanoEvents());
__publicField(this, "eventUnsubscribes", []);
__publicField(this, "emitterOptions");
/** Stores the last arguments for each event listed in `catchUpEvents` */
__publicField(this, "catchUpMemory", /* @__PURE__ */ new Map());
this.emitterOptions = __spreadValues({
publicEmit: false
}, options);
}
//#region emit
/**
* Emits an event on this instance.
* - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
* @param event The event to emit
* @param args The arguments to pass to the event listeners
* @returns Returns true if `publicEmit` is true and the event was emitted successfully
*/
emit(event, ...args) {
if (this.emitterOptions.publicEmit) {
this.emitEvent(event, ...args);
return true;
}
return false;
}
//#region unsubscribeAll
/** Unsubscribes all event listeners from this instance. Also clears the event catch-up memory. */
unsubscribeAll() {
super.unsubscribeAll();
}
};
// lib/DataStore.ts
var dsFmtVer = 1;
var DataStore = class extends NanoEmitter {
//#region constructor
/**
* Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
* Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
*
* - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
*
* @template TData The type of the data that is saved in persistent storage for the currently set format version (will be automatically inferred from `defaultData` if not provided) - **This has to be a JSON-compatible object!** (no undefined, circular references, etc.)
* @param opts The options for this DataStore instance
*/
constructor(opts) {
var _a, _b, _c;
super(opts.nanoEmitterOptions);
__publicField(this, "id");
__publicField(this, "formatVersion");
__publicField(this, "defaultData");
__publicField(this, "encodeData");
__publicField(this, "decodeData");
__publicField(this, "compressionFormat", "deflate-raw");
__publicField(this, "memoryCache");
__publicField(this, "engine");
__publicField(this, "keyPrefix");
__publicField(this, "options");
/**
* Whether all first-init checks should be done.
* This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
* This is set to `true` by default. Create a subclass and set it to `false` before calling {@linkcode loadData()} if you want to explicitly skip these checks.
*/
__publicField(this, "firstInit", true);
/** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
__publicField(this, "cachedData");
__publicField(this, "migrations");
__publicField(this, "migrateIds", []);
this.id = opts.id;
this.formatVersion = opts.formatVersion;
this.defaultData = opts.defaultData;
this.memoryCache = (_a = opts.memoryCache) != null ? _a : true;
this.cachedData = this.memoryCache ? opts.defaultData : {};
this.migrations = opts.migrations;
if (opts.migrateIds)
this.migrateIds = Array.isArray(opts.migrateIds) ? opts.migrateIds : [opts.migrateIds];
this.engine = typeof opts.engine === "function" ? opts.engine() : opts.engine;
this.keyPrefix = (_b = opts.keyPrefix) != null ? _b : "__ds-";
this.options = opts;
if ("encodeData" in opts && "decodeData" in opts && Array.isArray(opts.encodeData) && Array.isArray(opts.decodeData)) {
this.encodeData = [opts.encodeData[0], opts.encodeData[1]];
this.decodeData = [opts.decodeData[0], opts.decodeData[1]];
this.compressionFormat = (_c = opts.encodeData[0]) != null ? _c : null;
} else if (opts.compressionFormat === null) {
this.encodeData = void 0;
this.decodeData = void 0;
this.compressionFormat = null;
} else {
const fmt = typeof opts.compressionFormat === "string" ? opts.compressionFormat : "deflate-raw";
this.compressionFormat = fmt;
this.encodeData = [fmt, (data) => __async(this, null, function* () {
return yield compress(data, fmt, "string");
})];
this.decodeData = [fmt, (data) => __async(this, null, function* () {
return yield decompress(data, fmt, "string");
})];
}
this.engine.setDataStoreOptions({
id: this.id,
encodeData: this.encodeData,
decodeData: this.decodeData
});
}
//#region loadData
/**
* Loads the data saved in persistent storage into the in-memory cache and also returns a copy of it.
* Automatically populates persistent storage with default data if it doesn't contain any data yet.
* Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
*/
loadData() {
return __async(this, null, function* () {
var _a;
try {
if (this.firstInit) {
this.firstInit = false;
const dsVer = Number(yield this.engine.getValue("__ds_fmt_ver", 0));
const oldData = yield this.engine.getValue(`_uucfg-${this.id}`, null);
if (oldData) {
const oldVer = Number(yield this.engine.getValue(`_uucfgver-${this.id}`, NaN));
const oldEnc = yield this.engine.getValue(`_uucfgenc-${this.id}`, null);
const promises = [];
const migrateFmt = (oldKey, newKey, value) => {
promises.push(this.engine.setValue(newKey, value));
promises.push(this.engine.deleteValue(oldKey));
};
migrateFmt(`_uucfg-${this.id}`, `${this.keyPrefix}${this.id}-dat`, oldData);
if (!isNaN(oldVer))
migrateFmt(`_uucfgver-${this.id}`, `${this.keyPrefix}${this.id}-ver`, oldVer);
if (typeof oldEnc === "boolean" || oldEnc === "true" || oldEnc === "false" || typeof oldEnc === "number" || oldEnc === "0" || oldEnc === "1")
migrateFmt(`_uucfgenc-${this.id}`, `${this.keyPrefix}${this.id}-enf`, [0, "0", true, "true"].includes(oldEnc) ? (_a = this.compressionFormat) != null ? _a : null : null);
else {
promises.push(this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat));
promises.push(this.engine.deleteValue(`_uucfgenc-${this.id}`));
}
yield Promise.allSettled(promises);
}
if (isNaN(dsVer) || dsVer < dsFmtVer)
yield this.engine.setValue("__ds_fmt_ver", dsFmtVer);
}
if (this.migrateIds.length > 0) {
yield this.migrateId(this.migrateIds);
this.migrateIds = [];
}
const storedDataRaw = yield this.engine.getValue(`${this.keyPrefix}${this.id}-dat`, null);
const storedFmtVer = Number(yield this.engine.getValue(`${this.keyPrefix}${this.id}-ver`, NaN));
if (typeof storedDataRaw !== "string" && typeof storedDataRaw !== "object" || storedDataRaw === null || isNaN(storedFmtVer)) {
yield this.saveDefaultData(false);
const data = this.engine.deepCopy(this.defaultData);
this.emitEvent("loadData", data);
return data;
}
const storedData = storedDataRaw != null ? storedDataRaw : JSON.stringify(this.defaultData);
const encodingFmt = String(yield this.engine.getValue(`${this.keyPrefix}${this.id}-enf`, null));
const isEncoded = encodingFmt !== "null" && encodingFmt !== "false" && encodingFmt !== "0" && encodingFmt !== "" && encodingFmt !== null;
let parsed = typeof storedData === "string" ? yield this.engine.deserializeData(storedData, isEncoded) : storedData;
if (storedFmtVer < this.formatVersion && this.migrations)
parsed = yield this.runMigrations(parsed, storedFmtVer);
const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(parsed) : this.engine.deepCopy(parsed);
this.emitEvent("loadData", result);
return result;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
console.warn("Error while parsing JSON data, resetting it to the default value.", err);
this.emitEvent("error", error);
yield this.saveDefaultData();
return this.defaultData;
}
});
}
//#region getData
/**
* Returns a copy of the data from the in-memory cache.
* Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
* ⚠️ Only available when `memoryCache` is `true` (default). When set to `false`, this produces a type and runtime error - use {@linkcode loadData()} instead.
*/
getData() {
if (!this.memoryCache)
throw new DatedError("In-memory cache is disabled for this DataStore instance, so getData() can't be used. Please use loadData() instead.");
return this.engine.deepCopy(this.cachedData);
}
//#region setData
/** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
setData(data) {
const dataCopy = this.engine.deepCopy(data);
if (this.memoryCache) {
this.cachedData = data;
this.emitEvent("updateDataSync", dataCopy);
}
return new Promise((resolve) => __async(this, null, function* () {
const results = yield Promise.allSettled([
this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, yield this.engine.serializeData(data, this.encodingEnabled())),
this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
]);
if (results.every((r) => r.status === "fulfilled"))
this.emitEvent("updateData", dataCopy);
else {
const error = new Error("Error while saving data to persisten