@nemigo/helpers 0.3.3 → 0.4.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.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Параметры создания куки для {@link CookieCTX}
3
+ */
4
+ export interface CookieParams {
5
+ /**
6
+ * Срок действия куки в днях
7
+ *
8
+ * @default 15
9
+ */
10
+ days?: number;
11
+ /**
12
+ * - Strict: Куки будут отправляться только при навигации внутри сайта
13
+ * - Lax: Куки доступны для навигации с одного сайта на другой
14
+ * - None: Куки доступны для всех запросов, требуется параметр Secure
15
+ *
16
+ * @default "Lax"
17
+ */
18
+ policy?: "Strict" | "Lax" | "None";
19
+ }
20
+ /**
21
+ * Серверные readonly-куки для {@link CookieCTX}
22
+ */
23
+ export interface ServerCookie {
24
+ name: string;
25
+ value: string;
26
+ }
27
+ export interface ICookieCTX {
28
+ /**
29
+ * Инициализирует серверные куки
30
+ */
31
+ init(cookies: ServerCookie[]): void;
32
+ /**
33
+ * Устанавливает куку. На сервере игнорируется
34
+ */
35
+ set(name: string, value: string, params: CookieParams): void;
36
+ /**
37
+ * Получает значение куки по его названию
38
+ *
39
+ * @param name - Название куки
40
+ * @returns Значение куки или undefined, если кука не найдена
41
+ */
42
+ get(name: string): string | undefined;
43
+ /**
44
+ * Удаляет куку по имени. На сервере игнорируется
45
+ */
46
+ delete(name: string): void;
47
+ /**
48
+ * Очищает серверные куки
49
+ */
50
+ clear(): void;
51
+ }
52
+ export declare class CookieCTX implements ICookieCTX {
53
+ private server;
54
+ constructor(cookies?: ServerCookie[]);
55
+ init(cookies?: ServerCookie[]): void;
56
+ get(name: string): string | undefined;
57
+ set(name: string, value: string, params?: CookieParams): void;
58
+ delete(name: string): void;
59
+ clear(): void;
60
+ }
package/dist/cookie.js ADDED
@@ -0,0 +1,48 @@
1
+ import { isSSR } from "./html.js";
2
+ export class CookieCTX {
3
+ server = new Map();
4
+ constructor(cookies) {
5
+ this.init(cookies);
6
+ }
7
+ init(cookies = []) {
8
+ for (const item of cookies)
9
+ this.server.set(item.name, item.value);
10
+ }
11
+ get(name) {
12
+ const encoded = encodeURIComponent(name);
13
+ if (isSSR)
14
+ return this.server.get(encoded);
15
+ const key = encoded + "=";
16
+ const cookies = document.cookie.split(";");
17
+ for (const item of cookies) {
18
+ const cookie = item.trim();
19
+ if (cookie.startsWith(key)) {
20
+ const value = cookie.substring(key.length);
21
+ return decodeURIComponent(value);
22
+ }
23
+ }
24
+ }
25
+ set(name, value, params = {}) {
26
+ if (isSSR)
27
+ return;
28
+ const { days = 15, policy = "Lax" } = params;
29
+ const date = new Date();
30
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
31
+ const expires = `expires=${date.toUTCString()}`;
32
+ const encoded = {
33
+ name: encodeURIComponent(name),
34
+ value: encodeURIComponent(value),
35
+ };
36
+ const result = `${encoded.name}=${encoded.value};${expires};path=/;SameSite=${policy}`;
37
+ document.cookie = policy === "None" ? result + ";Secure" : result;
38
+ }
39
+ delete(name) {
40
+ if (isSSR)
41
+ return;
42
+ const encodedName = encodeURIComponent(name);
43
+ document.cookie = `${encodedName}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
44
+ }
45
+ clear() {
46
+ this.server.clear();
47
+ }
48
+ }
package/dist/datetime.js CHANGED
@@ -99,9 +99,7 @@ export const formatDateTime = (datetime = Date.now(), format = "DD.DM.DY TH:TM")
99
99
  * const timestamp = toTimeStamp("11.02.2012", "13:45");
100
100
  * console.log(timestamp); // Выведет {@link Timestamp} для "11.02.2012 13:45"
101
101
  */
102
- export const toTimeStamp =
103
- // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
104
- (date, time = "00:00") => {
102
+ export const toTimeStamp = (date, time = "00:00") => {
105
103
  const [day, month, year] = date.split(".").map(Number);
106
104
  const [hours, minutes] = time.split(":").map(Number);
107
105
  // Проверка на корректность ввода (если хотя бы одно значение не число)
@@ -0,0 +1,28 @@
1
+ import { Promoter } from "./promoter.js";
2
+ import type { RID } from "./types.js";
3
+ /**
4
+ * Класс `Emitter` реализует простую систему событийного взаимодействия.
5
+ * Позволяет подписываться на события, уведомлять и удалять обработчики
6
+ *
7
+ * @template Events - Схема событий вида `{ [key]: data }`
8
+ */
9
+ export declare class Emitter<Events extends object> extends Promoter<{
10
+ type: keyof Events;
11
+ data: Events[keyof Events];
12
+ }> {
13
+ /**
14
+ * Вызывает все обработчики, связанные с указанным событием
15
+ */
16
+ dispatch<K extends keyof Events>(target: K, data: Events[K]): void;
17
+ /**
18
+ * Добавляет обработчик для события
19
+ */
20
+ on<K extends keyof Events>(event: K, call: (data: Events[K]) => void, params?: {
21
+ key?: RID;
22
+ once?: boolean;
23
+ }): () => void;
24
+ /**
25
+ * Удаляет обработчик события по ключу. Если не указан, очищаются все обработчики
26
+ */
27
+ un(key?: RID): boolean;
28
+ }
@@ -0,0 +1,33 @@
1
+ import { Promoter } from "./promoter.js";
2
+ /**
3
+ * Класс `Emitter` реализует простую систему событийного взаимодействия.
4
+ * Позволяет подписываться на события, уведомлять и удалять обработчики
5
+ *
6
+ * @template Events - Схема событий вида `{ [key]: data }`
7
+ */
8
+ export class Emitter extends Promoter {
9
+ /**
10
+ * Вызывает все обработчики, связанные с указанным событием
11
+ */
12
+ dispatch(target, data) {
13
+ super.notify({ type: target, data });
14
+ }
15
+ /**
16
+ * Добавляет обработчик для события
17
+ */
18
+ on(event, call, params = {}) {
19
+ const { key = Symbol(), once } = params;
20
+ return super.sub((payload) => {
21
+ if (payload.type === event) {
22
+ // @ts-expect-error <оно не может корректно сузить тип>
23
+ call(payload.data);
24
+ }
25
+ }, { key, once });
26
+ }
27
+ /**
28
+ * Удаляет обработчик события по ключу. Если не указан, очищаются все обработчики
29
+ */
30
+ un(key) {
31
+ return super.unsub(key);
32
+ }
33
+ }
@@ -12,3 +12,69 @@ import type { PathLike, Stats } from "node:fs";
12
12
  declare function exists(path: PathLike, expected?: "file" | "folder", strict?: boolean): Promise<Stats | null>;
13
13
  declare function exists(path: PathLike, expected: "file" | "folder" | undefined, strict: true): Promise<Stats>;
14
14
  export { exists };
15
+ /**
16
+ * Опции для функции {@link remove}
17
+ */
18
+ export interface RemoveOptions {
19
+ /**
20
+ * Если `true` и это была не пустая папка, бросит ошибку.
21
+ * Параметр `expected` приводится к `"folder"`
22
+ *
23
+ * @default false
24
+ */
25
+ empty?: boolean;
26
+ /**
27
+ * Проверка перед удалением
28
+ *
29
+ * @default undefined
30
+ */
31
+ expected?: "file" | "folder";
32
+ }
33
+ /**
34
+ * Удаляет файл или папку по указанному пути
35
+ *
36
+ * @param path - Путь к файлу или папке
37
+ * @param options - Опции поведения {@link RemoveOptions}
38
+ *
39
+ * @returns {@link Stats}, если путь существовал и был удалён, иначе `null`
40
+ * @throws {Error} Если путь не существует или не соответствует ожидаемому типу
41
+ */
42
+ export declare function remove(path: PathLike, options?: RemoveOptions): Promise<Stats | null>;
43
+ /**
44
+ * Опции для функции {@link remake}
45
+ */
46
+ export interface RemakeOptions {
47
+ /**
48
+ * Если `true` и это была не пустая папка, бросит ошибку
49
+ *
50
+ * @default false
51
+ */
52
+ empty?: boolean;
53
+ /**
54
+ * Если `false` и нет родительской папки, бросит ошибку
55
+ *
56
+ * @default true
57
+ */
58
+ recursive?: boolean;
59
+ }
60
+ /**
61
+ * Удаляет (если существовала) и заново создаёт папку по указанному пути
62
+ *
63
+ * @param path - Путь к папке
64
+ * @param options - Опции поведения {@link RemakeOptions}
65
+ *
66
+ * @returns {@link Stats}, если папка существовала и была удалена, иначе `null`
67
+ * @throws {Error} Если путь не существует или не является папкой
68
+ */
69
+ export declare function remake(path: PathLike, options?: RemakeOptions): Promise<Stats | null>;
70
+ /**
71
+ * Сравнивает содержимое файла с ожидаемым значением
72
+ *
73
+ * @param path - Путь к файлу
74
+ * @param expected - Ожидаемое содержимое файла
75
+ * @param [strict=true] - Если `true`, выбрасывает ошибку, если файл не существует. Если `false`, возвращает `false`
76
+ *
77
+ * @returns `true`, если содержимое файла совпадает с ожидаемым, иначе `false`
78
+ * @throws {Error} Если `strict = true` и файл не существует
79
+ */
80
+ export declare function compare(path: PathLike, expected: string, strict?: boolean): Promise<boolean>;
package/dist/explorer.js CHANGED
@@ -1,4 +1,4 @@
1
- import { stat } from "node:fs/promises";
1
+ import { mkdir, readdir, readFile, rm, stat } from "node:fs/promises";
2
2
  async function exists(path, expected, strict = false) {
3
3
  let existed;
4
4
  try {
@@ -19,3 +19,65 @@ async function exists(path, expected, strict = false) {
19
19
  return existed;
20
20
  }
21
21
  export { exists };
22
+ /**
23
+ * Удаляет файл или папку по указанному пути
24
+ *
25
+ * @param path - Путь к файлу или папке
26
+ * @param options - Опции поведения {@link RemoveOptions}
27
+ *
28
+ * @returns {@link Stats}, если путь существовал и был удалён, иначе `null`
29
+ * @throws {Error} Если путь не существует или не соответствует ожидаемому типу
30
+ */
31
+ export async function remove(path, options = {}) {
32
+ const { empty = false, expected } = options;
33
+ let existed;
34
+ if (empty) {
35
+ existed = await exists(path, "folder");
36
+ if (!existed)
37
+ return null;
38
+ const entries = await readdir(path, { withFileTypes: true });
39
+ if (entries.length > 0) {
40
+ throw new Error(`Ожидалось, что папка "${path}" будет пуста`);
41
+ }
42
+ }
43
+ else {
44
+ existed = await exists(path, expected);
45
+ if (!existed)
46
+ return null;
47
+ }
48
+ await rm(path, { recursive: true });
49
+ return existed;
50
+ }
51
+ /**
52
+ * Удаляет (если существовала) и заново создаёт папку по указанному пути
53
+ *
54
+ * @param path - Путь к папке
55
+ * @param options - Опции поведения {@link RemakeOptions}
56
+ *
57
+ * @returns {@link Stats}, если папка существовала и была удалена, иначе `null`
58
+ * @throws {Error} Если путь не существует или не является папкой
59
+ */
60
+ export async function remake(path, options = {}) {
61
+ const { empty = false, recursive = true } = options;
62
+ const existed = await remove(path, { empty, expected: "folder" });
63
+ await mkdir(path, { recursive });
64
+ return existed;
65
+ }
66
+ //...
67
+ /**
68
+ * Сравнивает содержимое файла с ожидаемым значением
69
+ *
70
+ * @param path - Путь к файлу
71
+ * @param expected - Ожидаемое содержимое файла
72
+ * @param [strict=true] - Если `true`, выбрасывает ошибку, если файл не существует. Если `false`, возвращает `false`
73
+ *
74
+ * @returns `true`, если содержимое файла совпадает с ожидаемым, иначе `false`
75
+ * @throws {Error} Если `strict = true` и файл не существует
76
+ */
77
+ export async function compare(path, expected, strict = true) {
78
+ const existed = await exists(path, "file", strict);
79
+ if (!existed)
80
+ return false;
81
+ const content = await readFile(path, "utf-8");
82
+ return content === expected;
83
+ }
package/dist/files.d.ts CHANGED
@@ -1 +1,14 @@
1
1
  export declare const fileToBase64: (file: File) => Promise<string>;
2
+ /**
3
+ * Проверяет, является ли файл изображением (исключая SVG)
4
+ *
5
+ * @para file - Объект файла
6
+ * @returns true, если файл является изображением
7
+ */
8
+ export declare const isImage: (file: File) => boolean;
9
+ /**
10
+ * Преобразует размер файла в человекочитаемый формат
11
+ *
12
+ * @param size - Размер файла в байтах
13
+ */
14
+ export declare const getFileSize: (size: number) => string;
package/dist/files.js CHANGED
@@ -4,3 +4,38 @@ export const fileToBase64 = (file) => new Promise((resolve, reject) => {
4
4
  reader.onerror = reject;
5
5
  reader.readAsDataURL(file);
6
6
  });
7
+ //...
8
+ /**
9
+ * Проверяет, является ли файл изображением (исключая SVG)
10
+ *
11
+ * @para file - Объект файла
12
+ * @returns true, если файл является изображением
13
+ */
14
+ export const isImage = (file) => file.type.startsWith("image/") && !file.type.includes("svg");
15
+ //...
16
+ const UNITS = [
17
+ "B",
18
+ "kB",
19
+ "MB",
20
+ "GB",
21
+ "TB",
22
+ "PB",
23
+ "EB",
24
+ "ZB",
25
+ "YB",
26
+ ];
27
+ /**
28
+ * Преобразует размер файла в человекочитаемый формат
29
+ *
30
+ * @param size - Размер файла в байтах
31
+ */
32
+ export const getFileSize = (size) => {
33
+ const bytes = Math.round(size);
34
+ if (bytes === 0)
35
+ return "0 B";
36
+ if (bytes < 1000)
37
+ return bytes.toString() + " B";
38
+ const exponent = Math.min(Math.floor(Math.log10(bytes) / 3), UNITS.length - 1);
39
+ const result = bytes / 1000 ** exponent;
40
+ return Math.round(result) + " " + UNITS[exponent];
41
+ };
@@ -0,0 +1,50 @@
1
+ import type { CanBePromise, RID, Timestamp } from "./types.js";
2
+ /**
3
+ * Тип фьючерса: таймер или интервал
4
+ */
5
+ export type FutureType = "timer" | "interval";
6
+ /**
7
+ * Фьючерс для {@link Future}
8
+ */
9
+ export interface FutureOptions {
10
+ /**
11
+ * Тип фьючерса
12
+ *
13
+ * @default {@link FutureType}
14
+ */
15
+ type?: FutureType;
16
+ /**
17
+ * Уникальный ключ фьючерса
18
+ */
19
+ key?: RID;
20
+ /**
21
+ * Задержка ({@link Timestamp}). Если не указана, используется значение по умолчанию
22
+ */
23
+ delay?: number;
24
+ }
25
+ /**
26
+ * Класс для управления таймерами и интервалами
27
+ */
28
+ export declare class Future {
29
+ readonly delay: Timestamp;
30
+ /**
31
+ * @param [delay=5min] - Задержка по умолчанию
32
+ */
33
+ constructor(delay?: Timestamp);
34
+ private timers;
35
+ private intervals;
36
+ private get;
37
+ /**
38
+ * Очищает таймеры или интервалы
39
+ */
40
+ clear(type?: FutureType, key?: RID): void;
41
+ /**
42
+ * Сброс фьючерса
43
+ */
44
+ private reset;
45
+ /**
46
+ * Удаляет предыдущий фьючерс по ключу (если он был) и стартует новый.
47
+ * Функция будет обёрнута в {@link boundary} при вызове
48
+ */
49
+ run(call: () => CanBePromise, { type, key, delay }?: FutureOptions): void;
50
+ }
package/dist/future.js ADDED
@@ -0,0 +1,59 @@
1
+ //...
2
+ /**
3
+ * Класс для управления таймерами и интервалами
4
+ */
5
+ export class Future {
6
+ delay = 5 * 60 * 1000;
7
+ /**
8
+ * @param [delay=5min] - Задержка по умолчанию
9
+ */
10
+ constructor(delay = 5 * 60 * 1000) {
11
+ this.delay = delay;
12
+ }
13
+ timers = new Map();
14
+ intervals = new Map();
15
+ get(type) {
16
+ if (type === "timer")
17
+ return this.timers;
18
+ return this.intervals;
19
+ }
20
+ /**
21
+ * Очищает таймеры или интервалы
22
+ */
23
+ clear(type, key) {
24
+ if (type) {
25
+ const map = this.get(type);
26
+ key ? this.reset(type, map, key) : map.clear();
27
+ }
28
+ else {
29
+ this.timers.clear();
30
+ this.intervals.clear();
31
+ }
32
+ }
33
+ /**
34
+ * Сброс фьючерса
35
+ */
36
+ reset(type, map, key) {
37
+ const id = map.get(key);
38
+ if (!id)
39
+ return;
40
+ type === "timer" ? clearTimeout(id) : clearInterval(id);
41
+ map.delete(key);
42
+ }
43
+ /**
44
+ * Удаляет предыдущий фьючерс по ключу (если он был) и стартует новый.
45
+ * Функция будет обёрнута в {@link boundary} при вызове
46
+ */
47
+ run(call, { type = "timer", key = Symbol(), delay = this.delay } = {}) {
48
+ if (type === "timer") {
49
+ this.reset(type, this.timers, key);
50
+ const timer = setTimeout(call, delay);
51
+ this.timers.set(key, timer);
52
+ }
53
+ else {
54
+ this.reset(type, this.intervals, key);
55
+ const timer = setInterval(call, delay);
56
+ this.intervals.set(key, timer);
57
+ }
58
+ }
59
+ }
package/dist/html.d.ts CHANGED
@@ -49,6 +49,31 @@ export declare const unselect: () => void;
49
49
  * @returns Функция для удаления обработчика события
50
50
  */
51
51
  export declare const unselectByMousedown: () => (() => void);
52
+ declare global {
53
+ interface WindowEventMap {
54
+ rescroll: CustomEvent;
55
+ }
56
+ }
57
+ /**
58
+ * Создаёт обработчик события "rescroll" для прокрутки элемента наверх с плавной анимацией
59
+ *
60
+ * @param {HTMLElement} [element=document.documentElement] - Элемент, для которого привязывается обработчик
61
+ * @param {EventListenerOptions} [options] - Дополнительные параметры
62
+ *
63
+ * @returns Функция для удаления обработчика события
64
+ */
65
+ export declare const createRescrollListener: (element?: HTMLElement, options?: EventListenerOptions) => (() => void);
66
+ /**
67
+ * Генерирует и пробрасывает событие "rescroll" на указанный элемент или окно
68
+ *
69
+ * @param {HTMLElement | Window} [element=window] - Элемент или окно, на которое будет отправлено событие
70
+ * @returns {boolean} Возвращает результат метода dispatchEvent
71
+ */
72
+ export declare const rescrollDispatch: (element?: HTMLElement | Window) => boolean;
73
+ /**
74
+ * Автоматически изменяет высоту текстового поля (`textarea`) в зависимости от его содержимого
75
+ */
76
+ export declare const autoAreaHeight: (area?: HTMLTextAreaElement) => void;
52
77
  /**
53
78
  * Преобразование `{@link FormData}` в объект
54
79
  *
package/dist/html.js CHANGED
@@ -84,6 +84,37 @@ export const unselect = () => document.getSelection()?.removeAllRanges();
84
84
  * @returns Функция для удаления обработчика события
85
85
  */
86
86
  export const unselectByMousedown = () => createEventListener("mousedown", unselect, { once: true });
87
+ /**
88
+ * Создаёт обработчик события "rescroll" для прокрутки элемента наверх с плавной анимацией
89
+ *
90
+ * @param {HTMLElement} [element=document.documentElement] - Элемент, для которого привязывается обработчик
91
+ * @param {EventListenerOptions} [options] - Дополнительные параметры
92
+ *
93
+ * @returns Функция для удаления обработчика события
94
+ */
95
+ export const createRescrollListener = (element = document.documentElement, options) => createEventListener("rescroll",
96
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
97
+ () => element?.scrollTo?.({ top: 0, behavior: "smooth" }), options);
98
+ /**
99
+ * Генерирует и пробрасывает событие "rescroll" на указанный элемент или окно
100
+ *
101
+ * @param {HTMLElement | Window} [element=window] - Элемент или окно, на которое будет отправлено событие
102
+ * @returns {boolean} Возвращает результат метода dispatchEvent
103
+ */
104
+ export const rescrollDispatch = (element = window) =>
105
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
106
+ element?.dispatchEvent?.(new CustomEvent("rescroll"));
107
+ //...
108
+ /**
109
+ * Автоматически изменяет высоту текстового поля (`textarea`) в зависимости от его содержимого
110
+ */
111
+ export const autoAreaHeight = (area) => {
112
+ if (!area)
113
+ return;
114
+ area.style.height = "auto";
115
+ area.style.height = `${area.scrollHeight}px`;
116
+ area.scrollTop = area.scrollHeight;
117
+ };
87
118
  //...
88
119
  /**
89
120
  * Преобразование `{@link FormData}` в объект
package/dist/humanly.d.ts CHANGED
@@ -3,3 +3,7 @@ export type RU_CASES = "nominative" | "genitive" | "dative" | "accusative" | "ab
3
3
  * Форма слова 'рубль' в правильном падеже и числе в зависимости от количества
4
4
  */
5
5
  export declare const Rubles: (value: number, form?: RU_CASES) => string;
6
+ /**
7
+ * Форма слова 'секунда' в правильном падеже и числе в зависимости от количества
8
+ */
9
+ export declare const Seconds: (value: number, form?: RU_CASES) => string;
package/dist/humanly.js CHANGED
@@ -44,3 +44,50 @@ export const Rubles = (value, form = "nominative") => {
44
44
  }
45
45
  return RublesMainSwitch(form);
46
46
  };
47
+ //...
48
+ /**
49
+ * Основные формы слова 'секунда'
50
+ */
51
+ const SecondsMainSwitch = (form) => {
52
+ switch (form) {
53
+ case "dative":
54
+ return "секундам";
55
+ case "ablative":
56
+ return "секундами";
57
+ default:
58
+ return "секунд";
59
+ }
60
+ };
61
+ /**
62
+ * Форма слова 'секунда' в правильном падеже и числе в зависимости от количества
63
+ */
64
+ export const Seconds = (value, form = "nominative") => {
65
+ const lastTwoDigits = Math.abs(value % 100);
66
+ if (lastTwoDigits > 4 && lastTwoDigits < 21)
67
+ return SecondsMainSwitch(form);
68
+ const lastDigit = Math.abs(value % 10);
69
+ if (lastDigit >= 2 && lastDigit <= 4) {
70
+ switch (form) {
71
+ case "nominative":
72
+ case "accusative":
73
+ return "секунды";
74
+ default:
75
+ return SecondsMainSwitch(form);
76
+ }
77
+ }
78
+ if (lastDigit === 1) {
79
+ switch (form) {
80
+ case "nominative":
81
+ return "секунда";
82
+ case "accusative":
83
+ return "секунду";
84
+ case "genitive":
85
+ return "секунды";
86
+ case "dative":
87
+ return "секунде";
88
+ case "ablative":
89
+ return "секундой";
90
+ }
91
+ }
92
+ return SecondsMainSwitch(form);
93
+ };
package/dist/index.d.ts CHANGED
@@ -14,6 +14,8 @@ export declare const parsify: <T>(v: T) => T;
14
14
  * Приведение значения к массиву
15
15
  */
16
16
  export declare const arrayble: <T>(v: CanBeArray<T>) => T[];
17
+ export declare const isObject: (obj: unknown) => obj is object;
18
+ export declare const isSameType: <A = unknown>(a: A, b: unknown) => b is A;
17
19
  /**
18
20
  * Тип разделения для функции {@link partition}
19
21
  */
@@ -31,3 +33,17 @@ export interface Partition<T> {
31
33
  * Разделяет массив на два по условию
32
34
  */
33
35
  export declare const partition: <T>(arr: T[], filter: (item: T) => boolean, initial?: Partition<T>) => Partition<T>;
36
+ /**
37
+ * Удаляет `null` и `undefined` и оставляет только уникальные строки
38
+ */
39
+ export declare const setify: (arr: (string | undefined | null)[]) => string[];
40
+ /**
41
+ * Удаляет дубликаты объектов из массива на основе указанного ключа
42
+ *
43
+ * @template T - Тип элементов массива
44
+ * @template K - Ключ объекта, по которому определяется уникальность
45
+ * @param {T[]} arr - Исходный массив объектов
46
+ * @param {K} [key="id"] - Ключ, по которому будут удаляться дубликаты
47
+ * @returns {T[]} Новый массив без дубликатов
48
+ */
49
+ export declare const unify: <T, K extends keyof T>(arr: T[], key?: K) => T[];
package/dist/index.js CHANGED
@@ -17,7 +17,25 @@ export const parsify = (v) => {
17
17
  * Приведение значения к массиву
18
18
  */
19
19
  export const arrayble = (v) => (Array.isArray(v) ? v : [v]);
20
+ //...
21
+ export const isObject = (obj) => obj !== null && typeof obj === "object";
22
+ export const isSameType = (a, b) => Object.prototype.toString.call(a) === Object.prototype.toString.call(b);
20
23
  /**
21
24
  * Разделяет массив на два по условию
22
25
  */
23
26
  export const partition = (arr, filter, initial = { result: [], excluded: [] }) => arr.reduce((acc, item) => (acc[filter(item) ? "result" : "excluded"].push(item), acc), initial);
27
+ //...
28
+ /**
29
+ * Удаляет `null` и `undefined` и оставляет только уникальные строки
30
+ */
31
+ export const setify = (arr) => [...new Set(arr.filter((item) => typeof item === "string"))];
32
+ /**
33
+ * Удаляет дубликаты объектов из массива на основе указанного ключа
34
+ *
35
+ * @template T - Тип элементов массива
36
+ * @template K - Ключ объекта, по которому определяется уникальность
37
+ * @param {T[]} arr - Исходный массив объектов
38
+ * @param {K} [key="id"] - Ключ, по которому будут удаляться дубликаты
39
+ * @returns {T[]} Новый массив без дубликатов
40
+ */
41
+ export const unify = (arr, key = "id") => [...new Map(arr.map((item) => [item[key], item])).values()];