@foxford/foxford-utils 1.0.1 → 1.1.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.
package/README.mdx ADDED
@@ -0,0 +1,318 @@
1
+ # Foxford Javascript Utils @foxford/foxford-utils
2
+
3
+ > Набор типизированных утилит, используемых в проектах Фоксфорда. Пакет совместим с **TypeScript** и **Flow** (через `.js.flow`-прокладки).
4
+ > Перенесено с https://github.com/netology-group/foxford-utils-js
5
+
6
+ ## Быстрый старт
7
+
8
+ ```bash
9
+ pnpm add @foxford/foxford-utils
10
+ # или
11
+ npm i @foxford/foxford-utils
12
+ ```
13
+
14
+ ESM:
15
+
16
+ ```ts
17
+ import { qid, pluralize } from '@foxford/foxford-utils'
18
+ ```
19
+
20
+ CJS:
21
+
22
+ ```js
23
+ const { qid, pluralize } = require('@foxford/foxford-utils')
24
+ ```
25
+
26
+ > Пакет публикует ESM и CJS билды, типы `.d.ts`, а также Flow-стабы `.js.flow`.
27
+
28
+ ---
29
+
30
+ ## Содержание
31
+
32
+ * [Архитектура и билды](#архитектура-и-билды)
33
+ * [Соглашения по коду](#соглашения-по-коду)
34
+ * [Справочник утилит (API Reference)](#справочник-утилит-api-reference)
35
+
36
+ * [ID/строки](#idстроки)
37
+ * [URL/формы/DOM](#urlформыdom)
38
+ * [Коллекции/объекты](#коллекцииобъекты)
39
+ * [Строки и форматирование](#строки-и-форматирование)
40
+ * [Цвета и градиенты](#цвета-и-градиенты)
41
+ * [Медиатипы/файлы](#медиатипыфайлы)
42
+ * [Разное](#разное)
43
+ * [Совместимость с Flow](#совместимость-с-flow)
44
+ * [Тестирование](#тестирование)
45
+ * [Вклад и релизы](#вклад-и-релизы)
46
+
47
+ ---
48
+
49
+ ## Архитектура и билды
50
+
51
+ * Монорепо: **Nx** + **pnpm workspaces**.
52
+ * Бандлер: **tsup** (esbuild) → выводит `dist/` с `cjs` и `esm`.
53
+ * Типы: из `*.ts` генерятся `.d.ts`. Для Flow публикуются `.js.flow`.
54
+ * Стили кода: **TypeScript**, **ESLint**, **Prettier**.
55
+
56
+ Скрипты (пример):
57
+
58
+ ```json
59
+ {
60
+ "build": "tsup src/index.ts --dts --format cjs,esm",
61
+ "dev": "tsup --watch",
62
+ "typecheck": "tsc -p tsconfig.build.json",
63
+ "test": "vitest"
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Соглашения по коду
70
+
71
+ * Все публичные API имеют TS-типизацию.
72
+ * Функции **не мутируют** входные данные, если не указано обратное.
73
+ * Утилиты маленькие и одноцелевые, покрыты тестами (vitest).
74
+
75
+ ---
76
+
77
+ ## Справочник утилит (API Reference)
78
+
79
+ ### ID/строки
80
+
81
+ #### `qid(length?: number = 6, number?: boolean = true): string`
82
+
83
+ Генерирует короткий человекочитаемый ID.
84
+
85
+ ```ts
86
+ import { qid } from '@foxford/foxford-utils'
87
+ const id = qid(8) // "aZ9fK3Lm"
88
+ ```
89
+
90
+ #### `uuid(): string`
91
+
92
+ Простой UUID-подобный идентификатор (группированный hex).
93
+
94
+ ```ts
95
+ import { uuid } from '@foxford/foxford-utils'
96
+ uuid() // "3fa85f64-5717-4562-b3fc-2c963f66afa6"
97
+ ```
98
+
99
+ ### URL/формы/DOM
100
+
101
+ #### `createPath(url: string, query?: Record<string, unknown>): string`
102
+
103
+ Строит путь с query-строкой, аккуратно работает с `#hash` и уже существующим `?query`.
104
+
105
+ ```ts
106
+ createPath('/library#top', { id: 1 }) // "/library?id=1#top"
107
+ ```
108
+
109
+ #### `serializeArray(form: HTMLFormElement): Array<{ name: string; value: string }>`
110
+
111
+ Поведение, близкое к `jQuery.serializeArray()`.
112
+
113
+ > Из защищённого JS окружения проверяйте, что передан DOM-элемент формы.
114
+
115
+ ```ts
116
+ if (form && typeof form === 'object' && (form as any).nodeName === 'FORM') {
117
+ const fields = serializeArray(form)
118
+ }
119
+ ```
120
+
121
+ #### `wrapFormFields(wrapper: string, formFieldsString: string, untouchables?: string[]): string`
122
+
123
+ Оборачивает имена полей формы: `a=1&b=2` → `user[a]=1&user[b]=2`, кроме исключений.
124
+
125
+ #### `matchesId<T extends { id: string | number }>(idToMatch: T['id']): (x: T) => boolean`
126
+
127
+ Хелпер для `.find()`/`.some()`.
128
+
129
+ ```ts
130
+ arr.find(matchesId(42))
131
+ ```
132
+
133
+ #### `scrollToElement(selector: string): boolean | void`
134
+
135
+ Скроллит окно к DOM-элементу. Возвращает `false`, если элемент не найден.
136
+
137
+ #### `rawMarkup(text: string): { __html: string }`
138
+
139
+ Удобно для `dangerouslySetInnerHTML` в React.
140
+
141
+ ```tsx
142
+ <div dangerouslySetInnerHTML={rawMarkup('<b>text</b>')} />
143
+ ```
144
+
145
+ ### Коллекции/объекты
146
+
147
+ #### `indexById<T extends { id: string | number }>(items: T[]): Record<string, T>`
148
+
149
+ Индексирует массив по `id`.
150
+
151
+ #### `indexByGroupId<T extends { group: { id: string | number } }>(items: T[]): Record<string, T>`
152
+
153
+ Индексирует по `group.id`.
154
+
155
+ #### `pickBy<T extends object>(obj: T, filter: (key: keyof T, value: T[keyof T]) => boolean): Partial<T>`
156
+
157
+ Фильтрует пары ключ/значение по предикату.
158
+
159
+ #### `merge<T extends object, S extends object>(target: T, source: S): T & S`
160
+
161
+ **Глубокий** мерж **с мутацией `target` и без мутации `source`**. Совмещает объекты рекурсивно.
162
+
163
+ ```ts
164
+ const out = merge({ a: { x: 1 } }, { a: { y: 2 } }) // { a: { x:1, y:2 } }
165
+ ```
166
+
167
+ ### Строки и форматирование
168
+
169
+ #### `makeShortNameFromFull(fullName?: string): string`
170
+
171
+ Из `Ф.И.О` делает короткую форму `И.О` (учитывает 2/3 части).
172
+
173
+ #### `extractDigits(str?: string): string`
174
+
175
+ Извлекает только цифры из строки.
176
+
177
+ #### `toCamelCase(obj: Record<string, string>): Record<string, string>`
178
+
179
+ Конвертирует ключи `snake_case` → `camelCase`.
180
+
181
+ #### `toUnderscoredCase(obj: Record<string, string>): Record<string, string>`
182
+
183
+ Конвертирует ключи `camelCase` → `snake_case`.
184
+
185
+ #### `capitalize(str: string): string`
186
+
187
+ Делает первую букву заглавной (безопасно для пустых строк).
188
+
189
+ #### `parseStringListToArray(input?: string): string[]`
190
+
191
+ Парсит маркированный строковый список (разделители `-`, `—`, `<br>`, `\n`) в массив.
192
+
193
+ #### `pluralize<N extends number, F extends readonly [string, string, string]>(n: N, forms: F): string`
194
+
195
+ Русские правила множественного числа.
196
+
197
+ ```ts
198
+ pluralize(1, ['штука', 'штуки', 'штук']) // "штука"
199
+ pluralize(2, ['штука', 'штуки', 'штук']) // "штуки"
200
+ pluralize(5, ['штука', 'штуки', 'штук']) // "штук"
201
+ ```
202
+
203
+ #### `presetPlural(type: 'tasks' | 'exercises' | 'members' | 'hours' | 'seconds' | 'minutes' | 'remains' | 'days' | 'points' | 'students' | 'studentsParentalCase', n: number): string`
204
+
205
+ Готовые пресеты словоформ на базе `pluralize`.
206
+
207
+ ### Цвета и градиенты
208
+
209
+ #### `colorLuminance(hex: string, lum?: number): string`
210
+
211
+ Корректирует яркость hex-цвета (`lum` от -1 до 1).
212
+
213
+ #### `getGradient(degree: string | number, color: string): string`
214
+
215
+ Возвращает CSS `linear-gradient` с чуть осветлённой/затемнённой парой одного цвета.
216
+
217
+ ```ts
218
+ getGradient('90deg', '3366ff') // "linear-gradient(90deg, #3a70ff, #2d5ce5)"
219
+ ```
220
+
221
+ ### Медиатипы/файлы
222
+
223
+ #### `getMimeTypes(files: Array<{ extensions: string[]; mime: string }>): string[]`
224
+
225
+ Извлекает список `mime`.
226
+
227
+ #### Наборы mime/accept/расширений
228
+
229
+ ```ts
230
+ mimeImages(): string[]
231
+ mimeFiles(): string[]
232
+ mimeAudio(): string[]
233
+ acceptFileTypes(): string[] // images + files
234
+ acceptAllTypes(): string[] // images + files + audio
235
+ acceptImagesExtensions(): string[]
236
+ acceptFilesExtensions(): string[]
237
+ acceptAudioExtensions(): string[]
238
+ ```
239
+
240
+ #### `getFilesSize(attachments: Array<{ size: number }>, files: Array<{ size: number }>): number`
241
+
242
+ Суммарный размер файлов в байтах.
243
+
244
+ #### `CURRENCY_DIRECTORY: Record<string, string>`
245
+
246
+ Соответствие валют → символы/буквы.
247
+
248
+ ### Разное
249
+
250
+ #### `transformOptionsForSelect<T extends { id: string | number; name: string; image_url?: string }>(options: T[]): Array<{ value: T['id']; label: string; image_url?: string }>`
251
+
252
+ Готовит данные для `<select>`.
253
+
254
+ #### `getRandomInt(min: number, max: number): number`
255
+
256
+ Случайное целое в `[min, max)`.
257
+
258
+ #### `debounce<T extends (...args: any[]) => any>(fn: T, ms: number): (...args: Parameters<T>) => void`
259
+
260
+ Декоратор debounce. Сохраняет `this` и аргументы.
261
+
262
+ ---
263
+
264
+ ## Совместимость с Flow
265
+
266
+ * Для каждого публичного модуля публикуется `.js.flow` с декларациями, чтобы потребители на Flow получали типы «из коробки».
267
+ * При добавлении новой утилиты: создайте `index.ts`, экспортируйте типы; добавьте одноимённый `index.js.flow` с совместимыми аннотациями.
268
+
269
+ Шаблон `.js.flow`:
270
+
271
+ ```js
272
+ // @flow
273
+ export function qid(length?: number, number?: boolean): string
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Тестирование
279
+
280
+ * Фреймворк: **Vitest**.
281
+ * Покрываем граничные случаи: пустые строки, `null/undefined`, экзотические Unicode (`\u2014` и т.п.), DOM-эмуляция через **jsdom**.
282
+
283
+ Запуск:
284
+
285
+ ```bash
286
+ pnpm test
287
+ ```
288
+
289
+ Пример теста:
290
+
291
+ ```ts
292
+ import { describe, it, expect } from 'vitest'
293
+ import { pluralize } from '@foxford/foxford-utils'
294
+
295
+ describe('pluralize', () => {
296
+ it('ru forms', () => {
297
+ expect(pluralize(1, ['штука', 'штуки', 'штук'])).toBe('штука')
298
+ expect(pluralize(2, ['штука', 'штуки', 'штук'])).toBe('штуки')
299
+ expect(pluralize(5, ['штука', 'штуки', 'штук'])).toBe('штук')
300
+ })
301
+ })
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Вклад и релизы
307
+
308
+ 1. Форк/ветка от `main`.
309
+ 2. Добавьте утилиту в `src/`, экспорт в `src/index.ts`.
310
+ 3. Напишите тесты (`vitest`), запустите `pnpm typecheck` и `pnpm test`.
311
+ 4. Обновите этот `README.mdx` (раздел API).
312
+ 5. Повысьте версию по semver, создайте changelog.
313
+
314
+ ---
315
+
316
+ ## Лицензия
317
+
318
+ MIT © Фоксфорд