misc.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. /**
  2. * @module lib/misc
  3. * This module contains miscellaneous functions that don't fit in another category - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#misc)
  4. */
  5. import type { Prettify, Stringifiable } from "./types.js";
  6. /** Any value that is list-like, i.e. has a numeric length, count or size property */
  7. export type ListWithLength = unknown[] | NodeList | { length: number } | { count: number } | { size: number };
  8. /**
  9. * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
  10. * @param word A word in singular form, to auto-convert to plural
  11. * @param num A number, or list-like value that has either a `length`, `count` or `size` property - does not support iterables
  12. */
  13. export function autoPlural(word: Stringifiable, num: number | ListWithLength): string {
  14. if(typeof num !== "number") {
  15. if(Array.isArray(num) || num instanceof NodeList)
  16. num = num.length;
  17. else if("length" in num)
  18. num = num.length;
  19. else if("count" in num)
  20. num = num.count;
  21. else if("size" in num)
  22. num = num.size;
  23. }
  24. return `${word}${num === 1 ? "" : "s"}`;
  25. }
  26. /**
  27. * Inserts the passed values into a string at the respective placeholders.
  28. * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
  29. * @param input The string to insert the values into
  30. * @param values The values to insert, in order, starting at `%1`
  31. */
  32. export function insertValues(input: string, ...values: Stringifiable[]): string {
  33. return input.replace(/%\d/gm, (match) => {
  34. const argIndex = Number(match.substring(1)) - 1;
  35. return (values[argIndex] ?? match)?.toString();
  36. });
  37. }
  38. /** Pauses async execution for the specified time in ms */
  39. export function pauseFor(time: number): Promise<void> {
  40. return new Promise<void>((res) => {
  41. setTimeout(() => res(), time);
  42. });
  43. }
  44. /** Options for the `fetchAdvanced()` function */
  45. export type FetchAdvancedOpts = Prettify<
  46. Partial<{
  47. /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
  48. timeout: number;
  49. }> & RequestInit
  50. >;
  51. /** Calls the fetch API with special options like a timeout */
  52. export async function fetchAdvanced(input: string | RequestInfo | URL, options: FetchAdvancedOpts = {}): Promise<Response> {
  53. const { timeout = 10000 } = options;
  54. const ctl = new AbortController();
  55. const { signal, ...restOpts } = options;
  56. signal?.addEventListener("abort", () => ctl.abort());
  57. let sigOpts: Partial<RequestInit> = {},
  58. id: ReturnType<typeof setTimeout> | undefined = undefined;
  59. if(timeout >= 0) {
  60. id = setTimeout(() => ctl.abort(), timeout);
  61. sigOpts = { signal: ctl.signal };
  62. }
  63. try {
  64. const res = await fetch(input, {
  65. ...restOpts,
  66. ...sigOpts,
  67. });
  68. typeof id !== "undefined" && clearTimeout(id);
  69. return res;
  70. }
  71. catch(err) {
  72. typeof id !== "undefined" && clearTimeout(id);
  73. throw new Error("Error while calling fetch", { cause: err });
  74. }
  75. }
  76. /**
  77. * A ValueGen value is either its type, a promise that resolves to its type, or a function that returns its type, either synchronous or asynchronous.
  78. * ValueGen allows for the utmost flexibility when applied to any type, as long as {@linkcode consumeGen()} is used to get the final value.
  79. * @template TValueType The type of the value that the ValueGen should yield
  80. */
  81. export type ValueGen<TValueType> = TValueType | Promise<TValueType> | (() => TValueType | Promise<TValueType>);
  82. /**
  83. * Turns a {@linkcode ValueGen} into its final value.
  84. * @template TValueType The type of the value that the ValueGen should yield
  85. */
  86. export async function consumeGen<TValueType>(valGen: ValueGen<TValueType>): Promise<TValueType> {
  87. return await (typeof valGen === "function"
  88. ? (valGen as (() => Promise<TValueType> | TValueType))()
  89. : valGen
  90. )as TValueType;
  91. }
  92. /**
  93. * A StringGen value is either a string, anything that can be converted to a string, or a function that returns one of the previous two, either synchronous or asynchronous, or a promise that returns a string.
  94. * StringGen allows for the utmost flexibility when dealing with strings, as long as {@linkcode consumeStringGen()} is used to get the final string.
  95. */
  96. export type StringGen = ValueGen<Stringifiable>;
  97. /**
  98. * Turns a {@linkcode StringGen} into its final string value.
  99. * @template TStrUnion The union of strings that the StringGen should yield - this allows for finer type control compared to {@linkcode consumeGen()}
  100. */
  101. export async function consumeStringGen<TStrUnion extends string>(strGen: StringGen): Promise<TStrUnion> {
  102. return (
  103. typeof strGen === "string"
  104. ? strGen
  105. : String(
  106. typeof strGen === "function"
  107. ? await strGen()
  108. : strGen
  109. )
  110. ) as TStrUnion;
  111. }