@ryuzaki13/react-foundation-lib 1.4.0 → 1.4.2

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.
Files changed (101) hide show
  1. package/README.md +172 -114
  2. package/dist/chunks/{formatters-CBgrBeSA.js → formatters-D33Uh3si.js} +7 -8
  3. package/dist/chunks/{formatters-CBgrBeSA.js.map → formatters-D33Uh3si.js.map} +1 -1
  4. package/dist/chunks/odata-service-CWkHASdw.js +2211 -0
  5. package/dist/chunks/odata-service-CWkHASdw.js.map +1 -0
  6. package/dist/chunks/{validate-BW-7BZy_.js → validate-DyzojM0w.js} +2 -2
  7. package/dist/chunks/{validate-BW-7BZy_.js.map → validate-DyzojM0w.js.map} +1 -1
  8. package/dist/error-report/index.js +2 -2
  9. package/dist/excel/index.js +20 -21
  10. package/dist/excel/index.js.map +1 -1
  11. package/dist/formatters/index.js +4 -5
  12. package/dist/{odata → odata-service}/buildSeparatedArrays.d.ts +1 -1
  13. package/dist/odata-service/buildSeparatedArrays.d.ts.map +1 -0
  14. package/dist/{odata/types.d.ts → odata-service/collectionTypes.d.ts} +1 -1
  15. package/dist/odata-service/collectionTypes.d.ts.map +1 -0
  16. package/dist/{odata → odata-service}/dateUtils.d.ts +1 -1
  17. package/dist/odata-service/dateUtils.d.ts.map +1 -0
  18. package/dist/{odata → odata-service}/findCollectionPairs.d.ts +1 -1
  19. package/dist/odata-service/findCollectionPairs.d.ts.map +1 -0
  20. package/dist/odata-service/index.d.ts +5 -0
  21. package/dist/odata-service/index.d.ts.map +1 -1
  22. package/dist/odata-service/index.js +2 -2
  23. package/dist/{odata/store.d.ts → odata-service/odataCollectionStore.d.ts} +1 -1
  24. package/dist/odata-service/odataCollectionStore.d.ts.map +1 -0
  25. package/dist/query-client/index.js.map +1 -1
  26. package/dist/query-client/queryClient.d.ts +1 -3
  27. package/dist/query-client/queryClient.d.ts.map +1 -1
  28. package/package.json +1 -5
  29. package/src/array/README.mdx +1630 -0
  30. package/src/binary/README.mdx +1405 -0
  31. package/src/bounded-copy-stack/README.mdx +257 -0
  32. package/src/context-menu/README.mdx +326 -0
  33. package/src/copy/README.mdx +992 -0
  34. package/src/crypto/README.mdx +1052 -0
  35. package/src/currency/README.mdx +155 -0
  36. package/src/date-segments/README.mdx +319 -0
  37. package/src/dom/README.mdx +2179 -0
  38. package/src/error/README.mdx +95 -0
  39. package/src/error-report/README.mdx +612 -0
  40. package/src/excel/README.mdx +449 -0
  41. package/src/file/README.mdx +1957 -0
  42. package/src/fingerprint/README.mdx +100 -0
  43. package/src/form/README.mdx +101 -0
  44. package/src/formatters/README.mdx +469 -0
  45. package/src/formatters/boolean/README.mdx +494 -0
  46. package/src/formatters/date/README.mdx +732 -0
  47. package/src/formatters/number/README.mdx +542 -0
  48. package/src/formatters/pipeline/README.mdx +629 -0
  49. package/src/formatters/rowBased/README.mdx +486 -0
  50. package/src/formatters/strings/README.mdx +442 -0
  51. package/src/formatters/valueState/README.mdx +459 -0
  52. package/src/formulas/README.mdx +1283 -0
  53. package/src/foundationLib.mdx +70 -54
  54. package/src/hooks/README.mdx +988 -0
  55. package/src/http/README.mdx +126 -0
  56. package/src/media/README.mdx +1853 -0
  57. package/src/notifications/README.mdx +323 -0
  58. package/src/number-scale/README.mdx +318 -0
  59. package/src/odata-service/README.mdx +2916 -0
  60. package/src/presets/README.mdx +136 -0
  61. package/src/pwa/README.mdx +394 -0
  62. package/src/query-client/README.mdx +2500 -0
  63. package/src/range-output/README.mdx +161 -0
  64. package/src/seo/README.mdx +279 -0
  65. package/src/session-storage/README.mdx +125 -0
  66. package/src/string-comparison/README.mdx +96 -0
  67. package/src/table/README.mdx +3106 -0
  68. package/src/three-scene/README.mdx +248 -0
  69. package/src/types/README.mdx +172 -0
  70. package/src/utils/README.mdx +1724 -0
  71. package/src/validators/README.mdx +239 -0
  72. package/src/virtualizer/README.mdx +138 -0
  73. package/src/xml/README.mdx +149 -0
  74. package/dist/chunks/dateUtils-B5V5aXB-.js +0 -847
  75. package/dist/chunks/dateUtils-B5V5aXB-.js.map +0 -1
  76. package/dist/chunks/odata-service-B6TxBSLm.js +0 -1277
  77. package/dist/chunks/odata-service-B6TxBSLm.js.map +0 -1
  78. package/dist/odata/buildSeparatedArrays.d.ts.map +0 -1
  79. package/dist/odata/dateUtils.d.ts.map +0 -1
  80. package/dist/odata/findCollectionPairs.d.ts.map +0 -1
  81. package/dist/odata/index.d.ts +0 -6
  82. package/dist/odata/index.d.ts.map +0 -1
  83. package/dist/odata/index.js +0 -102
  84. package/dist/odata/index.js.map +0 -1
  85. package/dist/odata/store.d.ts.map +0 -1
  86. package/dist/odata/types.d.ts.map +0 -1
  87. package/src/binary/README.md +0 -110
  88. package/src/docs/browserAndReact.mdx +0 -150
  89. package/src/docs/filesAndExcel.mdx +0 -163
  90. package/src/docs/formatters-runtime.mdx +0 -455
  91. package/src/docs/formatters.mdx +0 -380
  92. package/src/docs/odataAndTables.mdx +0 -178
  93. package/src/docs/pureUtilities.mdx +0 -96
  94. package/src/docs/runtimeAndState.mdx +0 -155
  95. package/src/file/README.md +0 -289
  96. package/src/formatters/date/README.md +0 -520
  97. package/src/formatters/number/README.md +0 -123
  98. package/src/formatters/valueState/README.md +0 -275
  99. package/src/formulas/README.md +0 -42
  100. package/src/pwa/README.md +0 -638
  101. package/src/query-client/README.md +0 -565
@@ -1,96 +0,0 @@
1
- import { Meta } from "@storybook/addon-docs/blocks";
2
-
3
- <Meta title="Foundation Lib/Чистые utilities и contracts" />
4
-
5
- # Чистые utilities и contracts
6
-
7
- Эти API не создают global state и обычно не требуют React. Они полезны в entity/feature code, если задача остаётся предметно-независимой. Не переносите в них доменные правила только ради повторного использования.
8
-
9
- ## Коллекции, порядок и ограниченная история
10
-
11
- ```ts
12
- import { arrayGroupBy, arrayUniqueBy, moveArrayItem } from "@ryuzaki13/react-foundation-lib/array";
13
- import { pushBoundedCopyStackItem } from "@ryuzaki13/react-foundation-lib/bounded-copy-stack";
14
- ```
15
-
16
- | API | Поведение |
17
- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
18
- | `arrayGroupBy`, `arrayGroupByToArray`, `arrayToMap` | Группируют/индексируют по полю объекта. `arrayToMap` оставляет первое значение ключа. |
19
- | `arrayUniqueBy`, `arrayDeduplicate` | Immutable deduplication с сохранением первого встретившегося значения/порядка. |
20
- | `filterAndDeduplicateIds`, `appendMissingIds`, `pickExistingMapValues` | Helpers для stable id lists и ordered Map values. |
21
- | `arraysEqual` | Строго сравнивает длину, порядок и значения по `===`. |
22
- | `moveItem`, `moveArrayItem`, `moveArrayItemByIndex` | Возвращают новую переставленную коллекцию; invalid indices не мутируют input. |
23
- | `normalizeObjects`, `normalizeStringArray`, `addUnique` | Explicit canonicalization для внешней/сохранённой структуры — не вызывайте её внутри literal draft setter. |
24
-
25
- `pushBoundedCopyStackItem(stack, input, options?)` добавляет/поднимает item по `fingerprint` и ограничивает размер (default `DEFAULT_BOUNDED_COPY_STACK_SIZE = 3`). `getBoundedCopyStackCandidates` фильтрует готовый stack predicate-ом. `BoundedCopyStackItem`, `BoundedCopyStackPushInput`, `BoundedCopyStackOptions` и `BoundedCopyStackPushResult` описывают его serializable contract.
26
-
27
- ## Значения, шкалы и календарные сегменты
28
-
29
- ```ts
30
- import { parseDateSegmentMask, indexedSegmentsToDate } from "@ryuzaki13/react-foundation-lib/date-segments";
31
- import { snapNumberScaleValue } from "@ryuzaki13/react-foundation-lib/number-scale";
32
- import { resolveRangeOutputValue } from "@ryuzaki13/react-foundation-lib/range-output";
33
- ```
34
-
35
- | Entry point | Основной API |
36
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
37
- | `date-segments` | `parseDateSegmentMask`, `isEditableDateSegment`, `dateToIndexedSegmentValues`, `areAllDateSegmentsEmpty`, `indexedSegmentsToDate`. `indexedSegmentsToDate` строго отвергает невозможную дату/время; `DateMaskSegment` описывает editable/literal segments. |
38
- | `number-scale` | `resolveNumberScaleBounds`, `clampNumberScaleValue`, `normalizeNumberScaleStep`, `prepareNumberScaleMarks`, `snapNumberScaleValueToStep`, `snapNumberScaleValueToMarks`, `snapNumberScaleValue`, `valueToNumberScalePercent`, `percentToNumberScaleValue`, `percentToSnappedNumberScaleValue`, `offsetNumberScaleValue`. |
39
- | `currency` | `resolveCurrencyModeFromODataParameters` и `resolveCurrencyAwareLabel`; это label policy по wrapped OData parameters, не currency formatter. |
40
- | `range-output` | `readRangeOutputValueFallback` и `resolveRangeOutputValue`; подставляет fallback только для `null` открытой границы и не меняет исходный UI range. |
41
- | `presets` | `normalizePresetIds`, `resolvePresetOptionsByIds`, `getPresetOption`, `PresetOption`; помогает на save/restore boundary для известного набора IDs. |
42
- | `string-comparison` | `compareStrings(left, right)` использует fixed Russian numeric/case-insensitive collation с deterministic fallback. |
43
-
44
- Для date/number UI formatting используйте `formatters`, а не `number-scale`/`currency`.
45
-
46
- ## Clone, строки, keyboard и trees
47
-
48
- ```ts
49
- import { cn, deepCloneWithoutFunctions, deepCopyWithoutFunctions, stableStringify } from "@ryuzaki13/react-foundation-lib/utils";
50
- ```
51
-
52
- | API | Контракт |
53
- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
- | `cn(...args)` | Собирает className из строк и conditional object. `classNames` — deprecated alias; новый код использует `cn`. |
55
- | `stableStringify(value)` | JSON-like stable serialization с сортировкой object keys — для key/comparison, не как универсальный persistence format. |
56
- | `deepCopyWithoutFunctions(value, options?)` | Копирует Date, arrays и plain objects, поддерживает cycles; функции пропускает. Unsupported object бросает `TypeError`. Предназначен для serializable plain config. |
57
- | `deepCloneWithoutFunctions(value, options?)` | Расширенный clone: Date, RegExp, Map, Set, cycles, sparse arrays и optional prototype. Функции пропускает. |
58
- | `createLazyComponent`, `childrenCount` | React-adjacent helpers для stable lazy component и correct children count. |
59
- | `keyboardActivationKeys`, `isKeyboardActivationKey`, `handleKeyboardActivation` | Keyboard activation contract для custom control. |
60
- | `getRovingFocusTargetIndex`, `findFirstEnabledIndex`, `findLastEnabledIndex`, `findNextEnabledIndex` | Pure navigation для listbox/select с disabled/wrap. |
61
- | `deepCopyNodes`, `deepCopyTree` | Копирование tree nodes с standard/custom children field. |
62
- | `toBase64`, `encodeBase64`, `logError` | Unicode-safe base64 и centralised error logging. |
63
-
64
- Clone не является нормализацией: он создаёт независимую структуру в пределах собственного contract и не должен применять trim, defaults, deduplication или reorder к пользовательскому draft. Для plain serializable config используйте `deepCopyWithoutFunctions`, а не `structuredClone`.
65
-
66
- ## Runtime guards и базовые типы
67
-
68
- ```ts
69
- import { asRecord, isRecord, isSafe, readPositiveInteger } from "@ryuzaki13/react-foundation-lib/validators";
70
- import type { BaseType, RangeType, RowRecord, State } from "@ryuzaki13/react-foundation-lib/types";
71
- ```
72
-
73
- | API | Когда использовать |
74
- | --------------------------------------------------- | -------------------------------------------------------------------------- |
75
- | `isSafe` | Убирает только `null`/`undefined` из union. |
76
- | `isObject`, `isRecord`, `asRecord`, `isPlainObject` | Narrow unknown external value до object/record; array не считается record. |
77
- | `readPositiveInteger` | Возвращает positive integer или `undefined` без unsafe cast. |
78
- | `validateUrl` | Возвращает validation result для строки URL. |
79
- | `IMAGE_EXTENSIONS`, `isImageExtension` | Проверка расширения без учёта регистра; не MIME/security validation. |
80
- | `isDomReference` | Floating UI reference → DOM `Element` guard. |
81
-
82
- `State`, `AbapBoolean`, `Primitive`, `BaseType`, `ArrayType`, `ObjectType`, `InputType`, `RangeType`, `RowRecord`, `isBaseType` — общие low-level contracts. Не используйте `RowRecord` как модель конкретной доменной сущности.
83
-
84
- ## Идентификаторы, HTTP, XML, SEO и form errors
85
-
86
- | Entry point | API |
87
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
- | `crypto` | `hashString`, `hashString128`, `hashString128Base64Url`, `stringToElementId`, `uuidv4`. Hash служит для stable client key, не криптографической защиты. |
89
- | `session-storage` | `getSessionStorageId(key, createId?)` — scoped browser-tab id; не authentication token. |
90
- | `http` | `createNoStoreHeaders(init?)` создаёт Headers с no-store; `createTextHttpResponse(options)` создаёт text `Response`. |
91
- | `xml` | `escapeXmlValue`, `buildXmlFields`, `getXmlTagText` для небольших XML fragments. Не применяйте regex helper как универсальный XML parser для untrusted complex XML. |
92
- | `seo` | `createSeoHead(options)`, `createRouteHead(type, path, params)` создают `meta`, `links`, JSON-LD `scripts`; host router/head owner решает, как применить результат. |
93
- | `form` | `readFirstFormError(errors?)` выбирает первое readable error message из unknown error array. |
94
- | `error` | `createMissingContextErrorMessage`, `createMissingContextError` формируют единообразную ошибку отсутствующего React Provider. |
95
-
96
- `fingerprint` и `notifications` описаны на странице Runtime/state, потому что имеют browser/global lifecycle.
@@ -1,155 +0,0 @@
1
- import { Meta } from "@storybook/addon-docs/blocks";
2
-
3
- <Meta title="Foundation Lib/Runtime, state и diagnostics" />
4
-
5
- # Runtime, state и diagnostics
6
-
7
- Этот раздел содержит инфраструктурные API, которые создаются в composition root host-приложения. Они не должны подключаться из каждой feature: persistence, BroadcastChannel, notifications и error reporting имеют глобальный lifecycle и приватные данные.
8
-
9
- ## `query-client`: TanStack Query инфраструктура
10
-
11
- ```ts
12
- import {
13
- configureQueryPersistenceProjectAdapter,
14
- createQueryClient,
15
- installReactQueryBroadcast,
16
- installSessionScopedQueryReset,
17
- persistedQueryMeta,
18
- resetSessionScopedQueries,
19
- sessionScopedQueryMeta
20
- } from "@ryuzaki13/react-foundation-lib/query-client";
21
- ```
22
-
23
- ### Composition root
24
-
25
- 1. До создания `QueryClient` вызовите `configureQueryPersistenceProjectAdapter` при необходимости задать application-specific `persistenceBuster` и `resolveSystemIdentifier`.
26
- 2. Создайте один `QueryClient` через `createQueryClient({ onQueryError?, onMutationError? })`.
27
- 3. В browser bootstrap установите sync/reset channels и обязательно сохраните их `cleanup` для shutdown/tests.
28
-
29
- ```ts
30
- configureQueryPersistenceProjectAdapter({
31
- persistenceBuster: "host-v1",
32
- resolveSystemIdentifier: () => "host-production"
33
- });
34
-
35
- const queryClient = createQueryClient({
36
- onQueryError: (error, query) => reportQueryError(error, query)
37
- });
38
-
39
- const broadcast = installReactQueryBroadcast(queryClient, {
40
- queriesChannel: "host-query-cache",
41
- eventsChannel: "host-query-events"
42
- });
43
- const sessionReset = installSessionScopedQueryReset(queryClient, {
44
- channelName: "host-session-reset"
45
- });
46
-
47
- // teardown: broadcast.cleanup(); sessionReset.cleanup();
48
- ```
49
-
50
- ### Persistence, session scope и broadcast
51
-
52
- | API | Когда использовать |
53
- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
54
- | `persistedQueryMeta` | `meta` query только для публичных, serializable данных, которым разрешено переживать restart. |
55
- | `sessionScopedQueryMeta` | `meta` query, чей результат зависит от текущей server session/role. Такой query никогда не сохраняется. |
56
- | `shouldPersistQuery(query)` | Predicate persistence: true лишь для `persist: true` без `sessionScoped`. |
57
- | `createIndexedDbQueryStorage(options?)` | Создаёт async IndexedDB storage либо `undefined`, если API недоступен. |
58
- | `createReactQueryPersister()` | Создаёт TanStack persister с 90-дневным max age или `undefined`. |
59
- | `installReactQueryBroadcast(queryClient, channels)` | Синхронизирует Query cache и explicit invalidate/set events между вкладками; возвращает `{ broadcast, cleanup }`. |
60
- | `broadcastCacheEvent(event)` / `setBroadcastFn(fn)` | Imperative bridge для explicit cache event. Настраивается один раз в composition root. |
61
- | `resetSessionScopedQueries(queryClient, { broadcast? })` | Отменяет, очищает и refetches active session-scoped queries после логина, логаута или смены роли. |
62
- | `installSessionScopedQueryReset(...)` | Рассылает только команду reset, не query data; возвращает cleanup. |
63
-
64
- Не отмечайте query обоими meta-флагами. Не кладите в persisted/broadcast cache tokens, персональные данные без принятой политики, DOM nodes, функции или class instances.
65
-
66
- ### Оптимистичная мутация
67
-
68
- `onMutateOptimistic`, `onErrorOptimistic`, `onSuccessOptimistic`, `onSettledOptimistic` — low-level helpers для одного известного cache key. `onMutateOptimistic` сначала отменяет запросы, сохраняет `previous`, затем применяет patch. Контекст обязательно передаётся в `onError` для rollback.
69
-
70
- ```ts
71
- useMutation({
72
- mutationFn: updateTodo,
73
- onMutate: (input) => onMutateOptimistic(queryClient, todoKey, (old) => patchTodo(old, input)),
74
- onError: (_error, _input, context) => onErrorOptimistic(queryClient, todoKey, context),
75
- onSettled: () => onSettledOptimistic(queryClient, todoKey, { refetchType: "active" })
76
- });
77
- ```
78
-
79
- `OnSettledOptimisticOptions.refetchType` defaults to `"none"`. Не применяйте helper к непонятно широкому key: cache strategy должен владеть entity/feature, а не shared infrastructure.
80
-
81
- ## `error-report`: безопасный diagnostic draft
82
-
83
- ```ts
84
- import {
85
- captureQueryErrorReport,
86
- captureRuntimeErrorReport,
87
- getErrorReportDrafts,
88
- installErrorReportBrowserBreadcrumbs,
89
- setErrorReportRuntimeErrorReporter,
90
- setErrorReportTransportErrorReporter
91
- } from "@ryuzaki13/react-foundation-lib/error-report";
92
- ```
93
-
94
- Пакет подготавливает, sanitizes и локально сохраняет diagnostic draft. Он **не** отправляет отчёт в backend сам: transport/delivery принадлежит соответствующему API-пакету или host integration.
95
-
96
- | Группа API | Контракт |
97
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
98
- | Environment | `isErrorReportingEnabled`, `getErrorReportEnvironment`, `getErrorReportClientEnvironment` дают browser/non-dev gate и минимальный runtime context. |
99
- | Safe values | `createDiagnosticValue`, `createDataShape`, `sanitizeDetail`, `createErrorInfo` превращают `unknown` в serializable diagnostic form. Не передавайте секреты даже в sanitizing API. |
100
- | Breadcrumbs | `addErrorReportBreadcrumb`, `getErrorReportBreadcrumbs`, `clearErrorReportBreadcrumbs`, `installErrorReportBrowserBreadcrumbs`. Установка browser listener возвращает cleanup. |
101
- | Capture | `captureQueryErrorReport`, `captureMutationErrorReport`, `captureRuntimeErrorReport` создают `ErrorReportDraft` с query/mutation/runtime diagnostics. |
102
- | Drafts | `captureErrorReportDraft`, `getErrorReportDraft`, `getErrorReportDrafts`, `updateErrorReportDraft` управляют локальными draft. |
103
- | Reporting bridge | `setErrorReportRuntimeErrorReporter` + `reportRuntimeError`; `setErrorReportTransportErrorReporter`, `reportTransportError`, `suppressTransportErrorReport`, `isTransportErrorReportScheduled`. |
104
- | Diagnostics | `collectQueryDiagnostics`, `collectMutationDiagnostics`, `collectQueryClientDiagnostics`, `collectPersistedQueryDiagnostics`. |
105
-
106
- ### Подключение reporter-а
107
-
108
- ```ts
109
- setErrorReportRuntimeErrorReporter(async (error, context) => {
110
- const draft = captureRuntimeErrorReport(error, context, queryClient);
111
- // Передайте draft в owner transport только после собственной privacy policy.
112
- await deliverDiagnosticDraft(await draft);
113
- });
114
- ```
115
-
116
- `ErrorReportPayload`, `ErrorReportDraft`, `ErrorReportBreadcrumb`, `ErrorReportErrorInfo`, `ErrorReportSafeValue` и context types — публичные contracts для typed delivery. Runtime capture в браузере coalesces одинаковые ошибки на коротком окне, чтобы flood не создавал много draft. Не дублируйте transport report: `capture*` корректно вызывает `suppressTransportErrorReport` для исходной ошибки.
117
-
118
- ## `notifications`: global imperative facade
119
-
120
- ```ts
121
- import { bindNotifications, createNotificationsStore, notify } from "@ryuzaki13/react-foundation-lib/notifications";
122
- ```
123
-
124
- ```ts
125
- const notificationsStore = createNotificationsStore();
126
- const unbind = bindNotifications(notificationsStore);
127
-
128
- notify.success("Сохранено");
129
- const progress = notify.progress("Загрузка…");
130
- progress.success("Готово");
131
-
132
- // teardown composition root: unbind();
133
- ```
134
-
135
- | API | Назначение |
136
- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
137
- | `createNotificationsStore()` | Создаёт Zustand store с max 6 items и timer cleanup. Вызов только в browser-compatible UI composition. |
138
- | `bindNotifications(store)` | Связывает единственный store с imperative `notify`; возвращает unbind. До bind любой `notify.*` бросает Error. |
139
- | `notify.push/upsert/update/dismiss/clear` | Imperative actions над `NotificationPushInput`/`NotificationUpdatePatch`. |
140
- | `notify.success/info/warning/error` | Удобные typed shortcuts. |
141
- | `notify.progress(message, opts?)` | Возвращает `{ id, update, success, error, dismiss }` для долгой операции. |
142
-
143
- `Notification`, `NotificationAction`, `NotificationType`, `NotificationTone`, `NotificationId` описывают данные, а не UI rendering. UI owner подписывается на `NotificationsStoreApi` и отвечает за accessibility/визуал.
144
-
145
- ## OData collection preferences, session и fingerprint
146
-
147
- ```ts
148
- import { odataCollectionConfig, useODataCollectionStore } from "@ryuzaki13/react-foundation-lib/odata";
149
- import { getSessionStorageId } from "@ryuzaki13/react-foundation-lib/session-storage";
150
- import { getClientFingerprint } from "@ryuzaki13/react-foundation-lib/fingerprint";
151
- ```
152
-
153
- `odataCollectionConfig` даёт selectors для общих UI-defaults (`maxVisibleItems`, min search lengths, debounce); `useODataCollectionStore` даёт state и setters. Это клиентские предпочтения, не backend data и не metadata cache.
154
-
155
- `getSessionStorageId(key, createId?)` возвращает стабильный ID одной вкладки из `sessionStorage`; при storage failure всё равно вернёт новый ID. `getClientFingerprint()` асинхронно возвращает fingerprint или `null`, если библиотека/API недоступны. Не используйте fingerprint как authentication/authorization credential.
@@ -1,289 +0,0 @@
1
- # Библиотека чтения файлов
2
-
3
- Библиотека для безопасного чтения файлов на клиенте через `FileReader` API с валидацией, поддержкой отмены и строгой типизацией.
4
-
5
- ## Структура
6
-
7
- ```
8
- src/shared/lib/file/
9
- ├── read-file/ — универсальное чтение любых файлов
10
- │ ├── types.ts
11
- │ ├── readFile.ts
12
- │ └── index.ts
13
- ├── image/ — чтение изображений (надстройка над read-file)
14
- │ ├── types.ts
15
- │ ├── readImageFile.ts
16
- │ └── index.ts
17
- └── index.ts
18
- ```
19
-
20
- Все экспорты доступны через `@/shared/lib`.
21
-
22
- ---
23
-
24
- ## `readFile` — чтение любых файлов
25
-
26
- Универсальная функция для чтения файлов. Поддерживает два режима: `data-url` (по умолчанию) и `array-buffer`.
27
-
28
- ### Сигнатуры
29
-
30
- ```ts
31
- // Чтение как Data URL (по умолчанию)
32
- readFile(file: File, opts?: { mode?: "data-url"; ... }): Promise<ReadFileAsDataUrlResult>
33
-
34
- // Чтение как ArrayBuffer
35
- readFile(file: File, opts: { mode: "array-buffer"; ... }): Promise<ReadFileAsArrayBufferResult>
36
- ```
37
-
38
- ### Опции (`ReadFileOptions`)
39
-
40
- | Параметр | Тип | По умолчанию | Описание |
41
- | ------------- | ------------------------------ | ------------ | ---------------------------------- |
42
- | `mode` | `"data-url" \| "array-buffer"` | `"data-url"` | Режим чтения файла |
43
- | `allowedMime` | `readonly string[]` | — | Список разрешённых MIME-типов |
44
- | `maxBytes` | `number` | — | Максимальный размер файла в байтах |
45
- | `signal` | `AbortSignal` | — | Сигнал для отмены операции |
46
-
47
- ### Результат
48
-
49
- **`ReadFileAsDataUrlResult`** (при `mode: "data-url"`):
50
-
51
- | Поле | Тип | Описание |
52
- | --------- | ------------ | ----------------------------- |
53
- | `mode` | `"data-url"` | Дискриминант режима |
54
- | `meta` | `FileMeta` | Метаданные файла |
55
- | `dataUrl` | `string` | Содержимое в формате Data URL |
56
- | `file` | `File` | Исходный объект `File` |
57
-
58
- **`ReadFileAsArrayBufferResult`** (при `mode: "array-buffer"`):
59
-
60
- | Поле | Тип | Описание |
61
- | -------- | ---------------- | ---------------------- |
62
- | `mode` | `"array-buffer"` | Дискриминант режима |
63
- | `meta` | `FileMeta` | Метаданные файла |
64
- | `buffer` | `ArrayBuffer` | Бинарное содержимое |
65
- | `file` | `File` | Исходный объект `File` |
66
-
67
- **`FileMeta`** — метаданные файла:
68
-
69
- | Поле | Тип | Описание |
70
- | -------------- | -------- | -------------------------------------- |
71
- | `mime` | `string` | MIME-тип файла |
72
- | `size` | `number` | Размер в байтах |
73
- | `name` | `string` | Имя файла |
74
- | `lastModified` | `number` | Время последнего изменения (timestamp) |
75
-
76
- ### Примеры
77
-
78
- **Чтение PDF-файла как Data URL для предпросмотра:**
79
-
80
- ```ts
81
- import { readFile, ReadFileError } from "@/shared/lib";
82
-
83
- async function handlePdfUpload(file: File) {
84
- try {
85
- const result = await readFile(file, {
86
- allowedMime: ["application/pdf"],
87
- maxBytes: 50 * 1024 * 1024, // 50 МБ
88
- });
89
-
90
- // result.mode === "data-url" (тип сужен автоматически)
91
- console.log(result.meta.name); // "document.pdf"
92
- console.log(result.meta.size); // 1234567
93
- previewIframe.src = result.dataUrl;
94
- } catch (error) {
95
- if (error instanceof ReadFileError) {
96
- console.error(error.code, error.message);
97
- }
98
- }
99
- }
100
- ```
101
-
102
- **Чтение файла как ArrayBuffer для отправки на сервер:**
103
-
104
- ```ts
105
- import { readFile } from "@/shared/lib";
106
-
107
- async function uploadDocument(file: File) {
108
- const result = await readFile(file, {
109
- mode: "array-buffer",
110
- maxBytes: 100 * 1024 * 1024, // 100 МБ
111
- });
112
-
113
- // result.buffer: ArrayBuffer (тип сужен автоматически)
114
- await fetch("/api/upload", {
115
- method: "POST",
116
- body: result.buffer,
117
- headers: { "Content-Type": result.meta.mime },
118
- });
119
- }
120
- ```
121
-
122
- **Чтение с поддержкой отмены:**
123
-
124
- ```ts
125
- import { readFile } from "@/shared/lib";
126
-
127
- const controller = new AbortController();
128
-
129
- // Отмена через 5 секунд
130
- setTimeout(() => controller.abort(), 5000);
131
-
132
- const result = await readFile(file, {
133
- signal: controller.signal,
134
- });
135
- ```
136
-
137
- **Чтение без ограничений (любой файл):**
138
-
139
- ```ts
140
- import { readFile } from "@/shared/lib";
141
-
142
- const result = await readFile(file);
143
- // result.meta.mime — реальный MIME-тип файла
144
- // result.dataUrl — Data URL для любого типа
145
- ```
146
-
147
- ---
148
-
149
- ## `readImageFile` — чтение изображений
150
-
151
- Надстройка над `readFile` для работы с изображениями. Добавляет:
152
-
153
- - Проверку что файл является изображением (`image/*`)
154
- - Типизацию `allowedMime` на `ImageMime` (только image-типы)
155
- - Автоматическое извлечение размеров изображения (`dimensions`) в режиме `data-url`
156
-
157
- ### Сигнатуры
158
-
159
- ```ts
160
- // Чтение как Data URL с размерами (по умолчанию)
161
- readImageFile(file: File, opts?: { mode?: "data-url"; ... }): Promise<ReadImageAsDataUrlResult>
162
-
163
- // Чтение как ArrayBuffer
164
- readImageFile(file: File, opts: { mode: "array-buffer"; ... }): Promise<ReadImageAsArrayBufferResult>
165
- ```
166
-
167
- ### Опции (`ReadImageOptions`)
168
-
169
- | Параметр | Тип | По умолчанию | Описание |
170
- | ------------- | ------------------------------ | ------------ | ---------------------------------- |
171
- | `mode` | `"data-url" \| "array-buffer"` | `"data-url"` | Режим чтения файла |
172
- | `allowedMime` | `readonly ImageMime[]` | — | Разрешённые image MIME-типы |
173
- | `maxBytes` | `number` | — | Максимальный размер файла в байтах |
174
- | `signal` | `AbortSignal` | — | Сигнал для отмены операции |
175
-
176
- **`ImageMime`** — допустимые значения:
177
- `"image/png"`, `"image/jpeg"`, `"image/webp"`, `"image/gif"`, `"image/svg+xml"`, `"image/avif"`
178
-
179
- ### Результат
180
-
181
- **`ReadImageAsDataUrlResult`** расширяет `ReadFileAsDataUrlResult`:
182
-
183
- | Поле | Тип | Описание |
184
- | ------------ | ------------------ | -------------------------------- |
185
- | `mode` | `"data-url"` | Дискриминант режима |
186
- | `meta` | `FileMeta` | Метаданные файла |
187
- | `dataUrl` | `string` | Содержимое в формате Data URL |
188
- | `file` | `File` | Исходный объект `File` |
189
- | `dimensions` | `ImageDimensions?` | Ширина и высота изображения (px) |
190
-
191
- **`ReadImageAsArrayBufferResult`** — идентичен `ReadFileAsArrayBufferResult`.
192
-
193
- ### Примеры
194
-
195
- **Загрузка фото с ограничениями:**
196
-
197
- ```ts
198
- import { readImageFile, ReadImageError } from "@/shared/lib";
199
-
200
- async function selectPhoto(file: File) {
201
- try {
202
- const result = await readImageFile(file, {
203
- allowedMime: ["image/jpeg", "image/png", "image/webp"],
204
- maxBytes: 10 * 1024 * 1024, // 10 МБ
205
- });
206
-
207
- console.log(result.dimensions); // { width: 1920, height: 1080 }
208
- preview.src = result.dataUrl;
209
- } catch (error) {
210
- if (error instanceof ReadImageError) {
211
- switch (error.code) {
212
- case "NOT_AN_IMAGE":
213
- alert("Выберите изображение");
214
- break;
215
- case "MIME_NOT_ALLOWED":
216
- alert("Формат не поддерживается");
217
- break;
218
- case "FILE_TOO_LARGE":
219
- alert("Файл слишком большой");
220
- break;
221
- }
222
- }
223
- }
224
- }
225
- ```
226
-
227
- **Чтение изображения как ArrayBuffer:**
228
-
229
- ```ts
230
- import { readImageFile } from "@/shared/lib";
231
-
232
- const result = await readImageFile(file, { mode: "array-buffer" });
233
- // result.buffer: ArrayBuffer
234
- // dimensions не извлекаются в этом режиме
235
- ```
236
-
237
- ---
238
-
239
- ## Обработка ошибок
240
-
241
- ### `ReadFileError`
242
-
243
- Базовый класс ошибок для `readFile`.
244
-
245
- | Код | Когда возникает |
246
- | ------------------ | ---------------------------------------------------- |
247
- | `NO_FILE` | Файл не передан |
248
- | `MIME_NOT_ALLOWED` | MIME-тип файла не входит в `allowedMime` |
249
- | `FILE_TOO_LARGE` | Размер файла превышает `maxBytes` |
250
- | `READ_ABORTED` | Чтение прервано через `AbortSignal` или `FileReader` |
251
- | `READ_FAILED` | Ошибка `FileReader` при чтении |
252
-
253
- ### `ReadImageError`
254
-
255
- Класс ошибок для `readImageFile`. Наследует все коды из `ReadFileErrorCode` и добавляет:
256
-
257
- | Код | Когда возникает |
258
- | --------------------- | ---------------------------------------------------------- |
259
- | `NOT_AN_IMAGE` | MIME-тип файла не начинается с `image/` |
260
- | `IMAGE_DECODE_FAILED` | Не удалось декодировать изображение для получения размеров |
261
-
262
- ### Пример обработки ошибок
263
-
264
- ```ts
265
- import { readFile, ReadFileError } from "@/shared/lib";
266
-
267
- try {
268
- const result = await readFile(file, { maxBytes: 5 * 1024 * 1024 });
269
- } catch (error) {
270
- if (error instanceof ReadFileError) {
271
- // error.code — строковый код ошибки
272
- // error.message — описание на русском
273
- // error.cause — оригинальная ошибка (если есть)
274
- console.error(`[${error.code}] ${error.message}`);
275
- }
276
- }
277
- ```
278
-
279
- ---
280
-
281
- ## Утилиты
282
-
283
- Экспортируются из `read-file` для переиспользования в надстройках:
284
-
285
- | Функция | Описание |
286
- | ----------------------------- | -------------------------------------------------------------------- |
287
- | `buildMeta(file)` | Извлекает `FileMeta` из объекта `File` |
288
- | `assertNotAborted(signal?)` | Бросает `ReadFileError("READ_ABORTED")` если сигнал отменён |
289
- | `attachAbort(signal, reader)` | Привязывает `AbortSignal` к `FileReader`, возвращает функцию отписки |