UNPKG

@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
/* 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