@sv443-network/coreutils 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -74,10 +74,12 @@ __export(lib_exports, {
74
74
  consumeStringGen: () => consumeStringGen,
75
75
  createProgressBar: () => createProgressBar,
76
76
  createRecurringTask: () => createRecurringTask,
77
+ createTable: () => createTable,
77
78
  darkenColor: () => darkenColor,
78
79
  debounce: () => debounce,
79
80
  decompress: () => decompress,
80
81
  defaultPbChars: () => defaultPbChars,
82
+ defaultTableLineCharset: () => defaultTableLineCharset,
81
83
  digitCount: () => digitCount,
82
84
  fetchAdvanced: () => fetchAdvanced,
83
85
  formatNumber: () => formatNumber,
@@ -606,6 +608,173 @@ function truncStr(input, length, endStr = "...") {
606
608
  const finalStr = str.length > length ? str.substring(0, length - endStr.length) + endStr : str;
607
609
  return finalStr.length > length ? finalStr.substring(0, length) : finalStr;
608
610
  }
611
+ var defaultTableLineCharset = {
612
+ single: {
613
+ horizontal: "\u2500",
614
+ vertical: "\u2502",
615
+ topLeft: "\u250C",
616
+ topRight: "\u2510",
617
+ bottomLeft: "\u2514",
618
+ bottomRight: "\u2518",
619
+ leftT: "\u251C",
620
+ rightT: "\u2524",
621
+ topT: "\u252C",
622
+ bottomT: "\u2534",
623
+ cross: "\u253C"
624
+ },
625
+ double: {
626
+ horizontal: "\u2550",
627
+ vertical: "\u2551",
628
+ topLeft: "\u2554",
629
+ topRight: "\u2557",
630
+ bottomLeft: "\u255A",
631
+ bottomRight: "\u255D",
632
+ leftT: "\u2560",
633
+ rightT: "\u2563",
634
+ topT: "\u2566",
635
+ bottomT: "\u2569",
636
+ cross: "\u256C"
637
+ },
638
+ none: {
639
+ horizontal: " ",
640
+ vertical: " ",
641
+ topLeft: " ",
642
+ topRight: " ",
643
+ bottomLeft: " ",
644
+ bottomRight: " ",
645
+ leftT: " ",
646
+ rightT: " ",
647
+ topT: " ",
648
+ bottomT: " ",
649
+ cross: " "
650
+ }
651
+ };
652
+ function createTable(rows, options) {
653
+ var _a;
654
+ const opts = {
655
+ columnAlign: "left",
656
+ truncateAbove: Infinity,
657
+ truncEndStr: "\u2026",
658
+ minPadding: 1,
659
+ lineStyle: "single",
660
+ applyCellStyle: () => void 0,
661
+ applyLineStyle: () => void 0,
662
+ lineCharset: defaultTableLineCharset,
663
+ ...options ?? {}
664
+ };
665
+ const defRange = (val, min, max) => clamp(typeof val !== "number" || isNaN(Number(val)) ? min : val, min, max);
666
+ opts.truncateAbove = defRange(opts.truncateAbove, 0, Infinity);
667
+ opts.minPadding = defRange(opts.minPadding, 0, Infinity);
668
+ const lnCh = opts.lineCharset[opts.lineStyle];
669
+ const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, "");
670
+ const stringRows = rows.map((row) => row.map((cell) => String(cell)));
671
+ const colCount = ((_a = rows[0]) == null ? void 0 : _a.length) ?? 0;
672
+ if (colCount === 0 || stringRows.length === 0)
673
+ return "";
674
+ if (isFinite(opts.truncateAbove)) {
675
+ const truncAnsi = (str, maxVisible, endStr) => {
676
+ const limit = maxVisible - endStr.length;
677
+ if (limit <= 0)
678
+ return endStr.slice(0, maxVisible);
679
+ let visible = 0;
680
+ let result = "";
681
+ let i = 0;
682
+ let hasAnsi = false;
683
+ while (i < str.length) {
684
+ if (str[i] === "\x1B" && str[i + 1] === "[") {
685
+ const seqEnd = str.indexOf("m", i + 2);
686
+ if (seqEnd !== -1) {
687
+ result += str.slice(i, seqEnd + 1);
688
+ hasAnsi = true;
689
+ i = seqEnd + 1;
690
+ continue;
691
+ }
692
+ }
693
+ if (visible === limit) {
694
+ result += endStr;
695
+ if (hasAnsi)
696
+ result += "\x1B[0m";
697
+ return result;
698
+ }
699
+ result += str[i];
700
+ visible++;
701
+ i++;
702
+ }
703
+ return result;
704
+ };
705
+ for (const row of stringRows)
706
+ for (let j = 0; j < row.length; j++)
707
+ if (stripAnsi(row[j] ?? "").length > opts.truncateAbove)
708
+ row[j] = truncAnsi(row[j] ?? "", opts.truncateAbove, opts.truncEndStr);
709
+ }
710
+ const colWidths = Array.from(
711
+ { length: colCount },
712
+ (_, j) => Math.max(0, ...stringRows.map((row) => stripAnsi(row[j] ?? "").length))
713
+ );
714
+ const applyLn = (i, j, ch) => {
715
+ const [before = "", after = ""] = opts.applyLineStyle(i, j) ?? [];
716
+ return `${before}${ch}${after}`;
717
+ };
718
+ const buildBorderRow = (lineIdx, leftCh, midCh, rightCh) => {
719
+ let result = "";
720
+ let j = 0;
721
+ result += applyLn(lineIdx, j++, leftCh);
722
+ for (let col = 0; col < colCount; col++) {
723
+ const cellWidth = (colWidths[col] ?? 0) + opts.minPadding * 2;
724
+ for (let ci = 0; ci < cellWidth; ci++)
725
+ result += applyLn(lineIdx, j++, lnCh.horizontal);
726
+ if (col < colCount - 1)
727
+ result += applyLn(lineIdx, j++, midCh);
728
+ }
729
+ result += applyLn(lineIdx, j++, rightCh);
730
+ return result;
731
+ };
732
+ const lines = [];
733
+ for (let rowIdx = 0; rowIdx < stringRows.length; rowIdx++) {
734
+ const row = stringRows[rowIdx] ?? [];
735
+ const lineIdxBase = rowIdx * 3;
736
+ if (opts.lineStyle !== "none") {
737
+ lines.push(
738
+ rowIdx === 0 ? buildBorderRow(lineIdxBase, lnCh.topLeft, lnCh.topT, lnCh.topRight) : buildBorderRow(lineIdxBase, lnCh.leftT, lnCh.cross, lnCh.rightT)
739
+ );
740
+ }
741
+ let contentLine = "";
742
+ let j = 0;
743
+ contentLine += applyLn(lineIdxBase + 1, j++, lnCh.vertical);
744
+ for (let colIdx = 0; colIdx < colCount; colIdx++) {
745
+ const cell = row[colIdx] ?? "";
746
+ const visLen = stripAnsi(cell).length;
747
+ const extra = (colWidths[colIdx] ?? 0) - visLen;
748
+ const align = (Array.isArray(opts.columnAlign) ? opts.columnAlign[colIdx] : opts.columnAlign) ?? "left";
749
+ let leftPad;
750
+ let rightPad;
751
+ switch (align) {
752
+ case "right":
753
+ leftPad = opts.minPadding + extra;
754
+ rightPad = opts.minPadding;
755
+ break;
756
+ case "centerLeft":
757
+ leftPad = opts.minPadding + Math.floor(extra / 2);
758
+ rightPad = opts.minPadding + Math.ceil(extra / 2);
759
+ break;
760
+ case "centerRight":
761
+ leftPad = opts.minPadding + Math.ceil(extra / 2);
762
+ rightPad = opts.minPadding + Math.floor(extra / 2);
763
+ break;
764
+ default:
765
+ leftPad = opts.minPadding;
766
+ rightPad = opts.minPadding + extra;
767
+ }
768
+ const [cellBefore = "", cellAfter = ""] = opts.applyCellStyle(rowIdx, colIdx) ?? [];
769
+ contentLine += " ".repeat(leftPad) + cellBefore + cell + cellAfter + " ".repeat(rightPad);
770
+ contentLine += applyLn(lineIdxBase + 1, j++, lnCh.vertical);
771
+ }
772
+ lines.push(contentLine);
773
+ if (opts.lineStyle !== "none" && rowIdx === stringRows.length - 1)
774
+ lines.push(buildBorderRow(lineIdxBase + 2, lnCh.bottomLeft, lnCh.bottomT, lnCh.bottomRight));
775
+ }
776
+ return lines.join("\n");
777
+ }
609
778
 
610
779
  // node_modules/.pnpm/nanoevents@9.1.0/node_modules/nanoevents/index.js
611
780
  var createNanoEvents = () => ({
@@ -1316,6 +1485,7 @@ var FileStorageEngine = class extends DataStoreEngine {
1316
1485
  // lib/DataStoreSerializer.ts
1317
1486
  var DataStoreSerializer = class _DataStoreSerializer {
1318
1487
  stores;
1488
+ // eslint-disable-line @typescript-eslint/no-explicit-any
1319
1489
  options;
1320
1490
  constructor(stores, options = {}) {
1321
1491
  if (!crypto || !crypto.subtle)
@@ -1558,7 +1728,166 @@ function debounce(fn, timeout = 200, type = "immediate", nanoEmitterOptions) {
1558
1728
  func.debouncer = debouncer;
1559
1729
  return func;
1560
1730
  }
1731
+ sistent data of the DataStore instances into the in-memory cache.
1732
+ * Also triggers the migration process if the data format has changed.
1733
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be loaded
1734
+ * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
1735
+ */
1736
+ loadStoresData(stores) {
1737
+ return __async(this, null, function* () {
1738
+ return Promise.allSettled(
1739
+ this.getStoresFiltered(stores).map((store) => __async(this, null, function* () {
1740
+ return {
1741
+ id: store.id,
1742
+ data: yield store.loadData()
1743
+ };
1744
+ }))
1745
+ );
1746
+ });
1747
+ }
1748
+ /**
1749
+ * Resets the persistent and in-memory data of the DataStore instances to their default values.
1750
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
1751
+ */
1752
+ resetStoresData(stores) {
1753
+ return __async(this, null, function* () {
1754
+ return Promise.allSettled(
1755
+ this.getStoresFiltered(stores).map((store) => store.saveDefaultData())
1756
+ );
1757
+ });
1758
+ }
1759
+ /**
1760
+ * Deletes the persistent data of the DataStore instances.
1761
+ * Leaves the in-memory data untouched.
1762
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
1763
+ */
1764
+ deleteStoresData(stores) {
1765
+ return __async(this, null, function* () {
1766
+ return Promise.allSettled(
1767
+ this.getStoresFiltered(stores).map((store) => store.deleteData())
1768
+ );
1769
+ });
1770
+ }
1771
+ /** Checks if a given value is an array of SerializedDataStore objects */
1772
+ static isSerializedDataStoreObjArray(obj) {
1773
+ return Array.isArray(obj) && obj.every((o) => typeof o === "object" && o !== null && "id" in o && "data" in o && "formatVersion" in o && "encoded" in o);
1774
+ }
1775
+ /** Checks if a given value is a SerializedDataStore object */
1776
+ static isSerializedDataStoreObj(obj) {
1777
+ return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
1778
+ }
1779
+ /** Returns the DataStore instances whose IDs match the provided array or function */
1780
+ getStoresFiltered(stores) {
1781
+ return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
1782
+ }
1783
+ };
1561
1784
 
1785
+ // lib/Debouncer.ts
1786
+ var Debouncer = class extends NanoEmitter {
1787
+ /**
1788
+ * Creates a new debouncer with the specified timeout and edge type.
1789
+ * @param timeout Timeout in milliseconds between letting through calls - defaults to 200
1790
+ * @param type The edge type to use for the debouncer - see {@linkcode DebouncerType} for details or [the documentation for an explanation and diagram](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer) - defaults to "immediate"
1791
+ */
1792
+ constructor(timeout = 200, type = "immediate", nanoEmitterOptions) {
1793
+ super(nanoEmitterOptions);
1794
+ this.timeout = timeout;
1795
+ this.type = type;
1796
+ /** All registered listener functions and the time they were attached */
1797
+ __publicField(this, "listeners", []);
1798
+ /** The currently active timeout */
1799
+ __publicField(this, "activeTimeout");
1800
+ /** The latest queued call */
1801
+ __publicField(this, "queuedCall");
1802
+ }
1803
+ //#region listeners
1804
+ /** Adds a listener function that will be called on timeout */
1805
+ addListener(fn) {
1806
+ this.listeners.push(fn);
1807
+ }
1808
+ /** Removes the listener with the specified function reference */
1809
+ removeListener(fn) {
1810
+ const idx = this.listeners.findIndex((l) => l === fn);
1811
+ idx !== -1 && this.listeners.splice(idx, 1);
1812
+ }
1813
+ /** Removes all listeners */
1814
+ removeAllListeners() {
1815
+ this.listeners = [];
1816
+ }
1817
+ /** Returns all registered listeners */
1818
+ getListeners() {
1819
+ return this.listeners;
1820
+ }
1821
+ //#region timeout
1822
+ /** Sets the timeout for the debouncer */
1823
+ setTimeout(timeout) {
1824
+ this.events.emit("change", this.timeout = timeout, this.type);
1825
+ }
1826
+ /** Returns the current timeout */
1827
+ getTimeout() {
1828
+ return this.timeout;
1829
+ }
1830
+ /** Whether the timeout is currently active, meaning any latest call to the {@linkcode call()} method will be queued */
1831
+ isTimeoutActive() {
1832
+ return typeof this.activeTimeout !== "undefined";
1833
+ }
1834
+ //#region type
1835
+ /** Sets the edge type for the debouncer */
1836
+ setType(type) {
1837
+ this.events.emit("change", this.timeout, this.type = type);
1838
+ }
1839
+ /** Returns the current edge type */
1840
+ getType() {
1841
+ return this.type;
1842
+ }
1843
+ //#region call
1844
+ /** Use this to call the debouncer with the specified arguments that will be passed to all listener functions registered with {@linkcode addListener()} */
1845
+ call(...args) {
1846
+ const cl = (...a) => {
1847
+ this.queuedCall = void 0;
1848
+ this.events.emit("call", ...a);
1849
+ this.listeners.forEach((l) => l.call(this, ...a));
1850
+ };
1851
+ const setRepeatTimeout = () => {
1852
+ this.activeTimeout = setTimeout(() => {
1853
+ if (this.queuedCall) {
1854
+ this.queuedCall();
1855
+ setRepeatTimeout();
1856
+ } else
1857
+ this.activeTimeout = void 0;
1858
+ }, this.timeout);
1859
+ };
1860
+ switch (this.type) {
1861
+ case "immediate":
1862
+ if (typeof this.activeTimeout === "undefined") {
1863
+ cl(...args);
1864
+ setRepeatTimeout();
1865
+ } else
1866
+ this.queuedCall = () => cl(...args);
1867
+ break;
1868
+ case "idle":
1869
+ if (this.activeTimeout)
1870
+ clearTimeout(this.activeTimeout);
1871
+ this.activeTimeout = setTimeout(() => {
1872
+ cl(...args);
1873
+ this.activeTimeout = void 0;
1874
+ }, this.timeout);
1875
+ break;
1876
+ default:
1877
+ throw new TypeError(`Invalid debouncer type: ${this.type}`);
1878
+ }
1879
+ }
1880
+ };
1881
+ function debounce(fn, timeout = 200, type = "immediate", nanoEmitterOptions) {
1882
+ const debouncer = new Debouncer(timeout, type, nanoEmitterOptions);
1883
+ debouncer.addListener(fn);
1884
+ const func = ((...args) => debouncer.call(...args));
1885
+ func.debouncer = debouncer;
1886
+ return func;
1887
+ }
1888
+
1889
+ if(__exports != exports)module.exports = exports;return module.exports}));
1890
+ //# sourceMappingURL=CoreUtils.umd.js.map
1562
1891
 
1563
1892
 
1564
1893
  if (typeof module.exports == "object" && typeof exports == "object") {
@@ -2,7 +2,7 @@
2
2
  * @module DataStoreSerializer
3
3
  * This module contains the DataStoreSerializer class, which allows you to import and export serialized DataStore data - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#datastoreserializer)
4
4
  */
5
- import type { DataStore, DataStoreData } from "./DataStore.ts";
5
+ import type { DataStore } from "./DataStore.ts";
6
6
  /** Options for the DataStoreSerializer class */
7
7
  export type DataStoreSerializerOptions = {
8
8
  /** Whether to add a checksum to the exported data. Defaults to `true` */
@@ -44,9 +44,9 @@ export type StoreFilter = string[] | ((id: string) => boolean);
44
44
  * - ⚠️ Needs to run in a secure context (HTTPS) due to the use of the SubtleCrypto API if checksumming is enabled.
45
45
  */
46
46
  export declare class DataStoreSerializer {
47
- protected stores: DataStore<DataStoreData, boolean>[];
47
+ protected stores: DataStore<any, boolean>[];
48
48
  protected options: Required<DataStoreSerializerOptions>;
49
- constructor(stores: DataStore<DataStoreData, boolean>[], options?: DataStoreSerializerOptions);
49
+ constructor(stores: DataStore<any, boolean>[], options?: DataStoreSerializerOptions);
50
50
  /**
51
51
  * Calculates the checksum of a string. Uses {@linkcode computeHash()} with SHA-256 and digests as a hex string by default.
52
52
  * Override this in a subclass if a custom checksum method is needed.
@@ -118,5 +118,5 @@ export declare class DataStoreSerializer {
118
118
  /** Checks if a given value is a SerializedDataStore object */
119
119
  static isSerializedDataStoreObj(obj: unknown): obj is SerializedDataStore;
120
120
  /** Returns the DataStore instances whose IDs match the provided array or function */
121
- protected getStoresFiltered(stores?: StoreFilter): DataStore<DataStoreData, boolean>[];
121
+ protected getStoresFiltered(stores?: StoreFilter): DataStore<any, boolean>[];
122
122
  }
@@ -39,3 +39,68 @@ export declare function joinArrayReadable(array: unknown[], separators?: string,
39
39
  export declare function secsToTimeStr(seconds: number): string;
40
40
  /** Truncates a string if it exceeds `length` and inserts `endStr` at the end (empty string to disable), so that the final string doesn't exceed the given length */
41
41
  export declare function truncStr(input: Stringifiable, length: number, endStr?: string): string;
42
+ /**
43
+ * Border styles for the `lineStyle` option of the {@linkcode createTable} function.
44
+ *
45
+ * | Style | Example |
46
+ * | :-- | :-- |
47
+ * | `single` | `┌──────┬──────┐` |
48
+ * | `double` | `╔══════╦══════╗` |
49
+ * | `none` | |
50
+ */
51
+ export type TableLineStyle = "single" | "double" | "none";
52
+ /**
53
+ * How to align cell content in a column for the `columnAlign` option of the {@linkcode createTable} function.
54
+ *
55
+ * | Alignment | Example | Description |
56
+ * | :-- | :-- | :-- |
57
+ * | `left` | `│.Text....│` | Hugs the left border, with padding determined by `minPadding`. |
58
+ * | `centerLeft` | `│..Text...│` | In the center, but if the padding is uneven, favors the left side. |
59
+ * | `centerRight` | `│...Text..│` | In the center, but if the padding is uneven, favors the right side. |
60
+ * | `right` | `│....Text.│` | Hugs the right border, with padding determined by `minPadding`. |
61
+ */
62
+ export type TableColumnAlign = "left" | "centerLeft" | "centerRight" | "right";
63
+ /** Options for the {@linkcode createTable} function. */
64
+ export type TableOptions = {
65
+ /** Alignment for each column, either a single value to apply to all columns or an array of values for each column. Defaults to `left`. */
66
+ columnAlign?: TableColumnAlign | TableColumnAlign[];
67
+ /** If set, cell content that exceeds this width will be truncated with the value of `truncEndStr` (defaults to `…`) so that the final cell content doesn't exceed this width. */
68
+ truncateAbove?: number;
69
+ /** The string to append to truncated cell content if `truncateAbove` is set. Defaults to `…`. */
70
+ truncEndStr?: string;
71
+ /** Minimum padding to add to the left and right of cell content, regardless of alignment settings. Defaults to 1, set to 0 to disable. */
72
+ minPadding?: number;
73
+ /** Which kind of line characters to use for the border of the table. Defaults to `single` (`┌──────┬──────┐`). */
74
+ lineStyle?: TableLineStyle;
75
+ /** Can be used to change the characters used for the lines dividing cells in the table. If not set, {@linkcode defaultTableLineCharset} will be used. */
76
+ lineCharset?: TableLineCharset;
77
+ /**
78
+ * Can be used to add custom values like ANSI color codes to the lines dividing cells.
79
+ * Function gets passed the row index (i) and column index (j) of the character being rendered.
80
+ * Note that each row renders three rows of characters, so the row index (i) is not the same as the index of the row in the input array, but can be used to determine it with `Math.floor(i / 3)`.
81
+ * The first value of the returned tuple will be added before the line character and the second value will be added after.
82
+ * Return an empty array to not apply any custom styling.
83
+ */
84
+ applyLineStyle?: (i: number, j: number) => [before?: string, after?: string] | void;
85
+ /**
86
+ * Can be used to add custom values like ANSI color codes to the cell content.
87
+ * Function gets passed the row index (i) and column index (j) of the cell being rendered.
88
+ * Note: cell width is calculated before applying this style, so characters with non-zero width will mess up the alignment and border placement.
89
+ * The first value of the returned tuple will be added before the cell content and the second value will be added after.
90
+ * Return an empty array to not apply any custom styling.
91
+ */
92
+ applyCellStyle?: (i: number, j: number) => [before?: string, after?: string] | void;
93
+ };
94
+ /** Characters to use for the lines dividing cells in the table generated by {@linkcode createTable}, based on the `lineStyle` option. */
95
+ export type TableLineStyleChars = Record<"horizontal" | "vertical" | `${"top" | "bottom"}${"Left" | "Right"}` | `${"left" | "right" | "top" | "bottom"}T` | "cross", string>;
96
+ /** The characters to use for the lines dividing cells in the table generated by {@linkcode createTable}, based on the `lineStyle` option. */
97
+ export type TableLineCharset = Record<TableLineStyle, TableLineStyleChars>;
98
+ /** The default characters to use for the lines dividing cells in the table generated by {@linkcode createTable}, based on the `lineStyle` option. */
99
+ export declare const defaultTableLineCharset: TableLineCharset;
100
+ /**
101
+ * Creates an ASCII table string from the given rows and options.
102
+ * Supports `\x1b` ANSI color codes in cell content: they are ignored for width calculation, included in the final output, and handled correctly during truncation (escape sequences are never split; any open color code is closed with a reset).
103
+ * @param rows Array of tuples, where each tuple represents a row and its values. The first tuple is used to determine the column count.
104
+ * @param options Object with options for customizing the table output, such as column alignment, truncation, padding and line styles.
105
+ */
106
+ export declare function createTable<TRow extends [...Stringifiable[]]>(rows: TRow[], options?: TableOptions): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/coreutils",
3
3
  "libName": "@sv443-network/coreutils",
4
- "version": "3.4.0",
4
+ "version": "3.5.0",
5
5
  "description": "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.",
6
6
  "main": "dist/CoreUtils.cjs",
7
7
  "module": "dist/CoreUtils.mjs",