misc.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /** Represents any value that is either a string itself or can be converted to one (implicitly or explicitly) because it has a toString() method */
  2. export type Stringifiable = string | { toString(): string };
  3. /**
  4. * A type that offers autocomplete for the passed union but also allows any arbitrary value of the same type to be passed.
  5. * Supports unions of strings, numbers and objects.
  6. */
  7. export type LooseUnion<TUnion extends string | number | object> =
  8. (TUnion) | (
  9. TUnion extends string
  10. ? (string & {})
  11. : (
  12. TUnion extends number
  13. ? (number & {})
  14. : (
  15. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  16. TUnion extends Record<keyof any, unknown>
  17. ? (object & {})
  18. : never
  19. )
  20. )
  21. );
  22. /**
  23. * A type that allows all strings except for empty ones
  24. * @example
  25. * function foo<T extends string>(bar: NonEmptyString<T>) {
  26. * console.log(bar);
  27. * }
  28. */
  29. export type NonEmptyString<TString extends string> = TString extends "" ? never : TString;
  30. /**
  31. * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
  32. * @param word A word in singular form, to auto-convert to plural
  33. * @param num If this is an array or NodeList, the amount of items is used
  34. */
  35. export function autoPlural(word: Stringifiable, num: number | unknown[] | NodeList) {
  36. if(Array.isArray(num) || num instanceof NodeList)
  37. num = num.length;
  38. return `${word}${num === 1 ? "" : "s"}`;
  39. }
  40. /** Pauses async execution for the specified time in ms */
  41. export function pauseFor(time: number) {
  42. return new Promise<void>((res) => {
  43. setTimeout(() => res(), time);
  44. });
  45. }
  46. /**
  47. * Calls the passed {@linkcode func} after the specified {@linkcode timeout} in ms (defaults to 300).
  48. * Any subsequent calls to this function will reset the timer and discard all previous calls.
  49. */
  50. export function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout = 300) { // eslint-disable-line @typescript-eslint/no-explicit-any
  51. let timer: number | undefined;
  52. return function(...args: TArgs[]) {
  53. clearTimeout(timer);
  54. timer = setTimeout(() => func.apply(this, args), timeout) as unknown as number;
  55. };
  56. }
  57. /** Options for the `fetchAdvanced()` function */
  58. export type FetchAdvancedOpts = RequestInit & Partial<{
  59. /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
  60. timeout: number;
  61. }>;
  62. /** Calls the fetch API with special options like a timeout */
  63. export async function fetchAdvanced(url: string, options: FetchAdvancedOpts = {}) {
  64. const { timeout = 10000 } = options;
  65. const controller = new AbortController();
  66. const id = setTimeout(() => controller.abort(), timeout);
  67. const res = await fetch(url, {
  68. ...options,
  69. signal: controller.signal,
  70. });
  71. clearTimeout(id);
  72. return res;
  73. }
  74. /**
  75. * Inserts the passed values into a string at the respective placeholders.
  76. * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
  77. * @param str The string to insert the values into
  78. * @param values The values to insert, in order, starting at `%1`
  79. */
  80. export function insertValues(str: string, ...values: Stringifiable[]) {
  81. return str.replace(/%\d/gm, (match) => {
  82. const argIndex = Number(match.substring(1)) - 1;
  83. return (values[argIndex] ?? match)?.toString();
  84. });
  85. }