@ryuzaki13/react-foundation-lib 1.4.1 → 1.4.3
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.md +154 -100
- package/dist/chunks/{formatters-CBgrBeSA.js → formatters-D33Uh3si.js} +7 -8
- package/dist/chunks/{formatters-CBgrBeSA.js.map → formatters-D33Uh3si.js.map} +1 -1
- package/dist/chunks/odata-service-CWkHASdw.js +2211 -0
- package/dist/chunks/odata-service-CWkHASdw.js.map +1 -0
- package/dist/chunks/{validate-BW-7BZy_.js → validate-DyzojM0w.js} +2 -2
- package/dist/chunks/{validate-BW-7BZy_.js.map → validate-DyzojM0w.js.map} +1 -1
- package/dist/error-report/index.js +2 -2
- package/dist/excel/index.js +20 -21
- package/dist/excel/index.js.map +1 -1
- package/dist/formatters/index.js +4 -5
- package/dist/{odata → odata-service}/buildSeparatedArrays.d.ts +1 -1
- package/dist/odata-service/buildSeparatedArrays.d.ts.map +1 -0
- package/dist/{odata/types.d.ts → odata-service/collectionTypes.d.ts} +1 -1
- package/dist/odata-service/collectionTypes.d.ts.map +1 -0
- package/dist/{odata → odata-service}/dateUtils.d.ts +1 -1
- package/dist/odata-service/dateUtils.d.ts.map +1 -0
- package/dist/{odata → odata-service}/findCollectionPairs.d.ts +1 -1
- package/dist/odata-service/findCollectionPairs.d.ts.map +1 -0
- package/dist/odata-service/index.d.ts +5 -0
- package/dist/odata-service/index.d.ts.map +1 -1
- package/dist/odata-service/index.js +2 -2
- package/dist/{odata/store.d.ts → odata-service/odataCollectionStore.d.ts} +1 -1
- package/dist/odata-service/odataCollectionStore.d.ts.map +1 -0
- package/dist/query-client/index.js.map +1 -1
- package/dist/query-client/queryClient.d.ts +1 -3
- package/dist/query-client/queryClient.d.ts.map +1 -1
- package/package.json +1 -5
- package/src/bounded-copy-stack/README.mdx +257 -0
- package/src/context-menu/README.mdx +326 -0
- package/src/currency/README.mdx +155 -0
- package/src/date-segments/README.mdx +319 -0
- package/src/error/README.mdx +95 -0
- package/src/error-report/README.mdx +612 -0
- package/src/excel/README.mdx +449 -0
- package/src/fingerprint/README.mdx +100 -0
- package/src/form/README.mdx +101 -0
- package/src/foundationLib.mdx +70 -54
- package/src/hooks/README.mdx +988 -0
- package/src/http/README.mdx +126 -0
- package/src/media/README.mdx +1853 -0
- package/src/notifications/README.mdx +323 -0
- package/src/number-scale/README.mdx +318 -0
- package/src/odata-service/README.mdx +2916 -0
- package/src/presets/README.mdx +136 -0
- package/src/pwa/README.mdx +394 -0
- package/src/query-client/README.mdx +2500 -0
- package/src/range-output/README.mdx +161 -0
- package/src/seo/README.mdx +279 -0
- package/src/session-storage/README.mdx +125 -0
- package/src/string-comparison/README.mdx +96 -0
- package/src/table/README.mdx +3106 -0
- package/src/three-scene/README.mdx +248 -0
- package/src/types/README.mdx +172 -0
- package/src/utils/README.mdx +1724 -0
- package/src/validators/README.mdx +239 -0
- package/src/virtualizer/README.mdx +138 -0
- package/src/xml/README.mdx +149 -0
- package/dist/chunks/dateUtils-B5V5aXB-.js +0 -847
- package/dist/chunks/dateUtils-B5V5aXB-.js.map +0 -1
- package/dist/chunks/odata-service-B6TxBSLm.js +0 -1277
- package/dist/chunks/odata-service-B6TxBSLm.js.map +0 -1
- package/dist/odata/buildSeparatedArrays.d.ts.map +0 -1
- package/dist/odata/dateUtils.d.ts.map +0 -1
- package/dist/odata/findCollectionPairs.d.ts.map +0 -1
- package/dist/odata/index.d.ts +0 -6
- package/dist/odata/index.d.ts.map +0 -1
- package/dist/odata/index.js +0 -102
- package/dist/odata/index.js.map +0 -1
- package/dist/odata/store.d.ts.map +0 -1
- package/dist/odata/types.d.ts.map +0 -1
- package/src/pwa/README.md +0 -638
- package/src/query-client/README.md +0 -565
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Foundation Lib/Data/Presets" />
|
|
4
|
+
|
|
5
|
+
# Общие preset helpers через `@ryuzaki13/react-foundation-lib/presets`
|
|
6
|
+
|
|
7
|
+
Модуль проверяет восстановленные preset ids, сопоставляет их с option objects и ищет одну option по id.
|
|
8
|
+
|
|
9
|
+
## Установка и импорт
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { getPresetOption, normalizePresetIds, resolvePresetOptionsByIds } from "@ryuzaki13/react-foundation-lib/presets";
|
|
13
|
+
|
|
14
|
+
import type { PresetOption } from "@ryuzaki13/react-foundation-lib/presets";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Модуль не зависит от React/browser APIs.
|
|
18
|
+
|
|
19
|
+
## Базовый контракт
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
type PresetOption<TId extends string = string> = {
|
|
23
|
+
id: TId;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Ваш тип может добавлять поля:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
type ReportPreset = PresetOption<"daily" | "monthly"> & {
|
|
32
|
+
icon: string;
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## `normalizePresetIds`
|
|
37
|
+
|
|
38
|
+
Функция предназначена для явной boundary восстановления сохранённого `unknown`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
type PresetId = "first" | "second" | "third";
|
|
42
|
+
|
|
43
|
+
function isPresetId(value: unknown): value is PresetId {
|
|
44
|
+
return value === "first" || value === "second" || value === "third";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
normalizePresetIds(["second", "unknown", "second", "first"], isPresetId);
|
|
48
|
+
// ["second", "first"].
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Алгоритм:
|
|
52
|
+
|
|
53
|
+
- если `value` не массив — возвращается копия `fallbackIds`;
|
|
54
|
+
- если это массив — fallback не используется, даже для пустого массива;
|
|
55
|
+
- элементы проверяются переданным type guard;
|
|
56
|
+
- invalid ids пропускаются;
|
|
57
|
+
- дубликаты удаляются;
|
|
58
|
+
- сохраняется порядок первого допустимого вхождения.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
normalizePresetIds(undefined, isPresetId, ["third"]); // ["third"].
|
|
62
|
+
normalizePresetIds([], isPresetId, ["third"]); // [].
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Это normalizer сохранённого payload. Не вызывайте его внутри обычного input/store setter, если пользовательский draft должен храниться буквально.
|
|
66
|
+
|
|
67
|
+
## `resolvePresetOptionsByIds`
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const options = [
|
|
71
|
+
{ id: "first", label: "Первый" },
|
|
72
|
+
{ id: "second", label: "Второй" },
|
|
73
|
+
{ id: "third", label: "Третий" }
|
|
74
|
+
] as const;
|
|
75
|
+
|
|
76
|
+
const selected = resolvePresetOptionsByIds(["third", "first", "third", "missing"], options);
|
|
77
|
+
// objects third, first.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Правила:
|
|
81
|
+
|
|
82
|
+
- порядок берётся из `presetIds`, а не из `options`;
|
|
83
|
+
- повторные ids добавляются один раз;
|
|
84
|
+
- отсутствующие в `options` ids пропускаются;
|
|
85
|
+
- возвращается новый массив;
|
|
86
|
+
- сами option objects не клонируются;
|
|
87
|
+
- если в `options` повторяется id, `Map` оставляет последнюю option с этим id.
|
|
88
|
+
|
|
89
|
+
## `getPresetOption`
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
getPresetOption("second", options);
|
|
93
|
+
// { id: "second", label: "Второй" }.
|
|
94
|
+
|
|
95
|
+
getPresetOption("missing", options); // null.
|
|
96
|
+
getPresetOption(undefined, options); // null.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Функция использует первое совпадение `options.find`. Пустой id `""` считается отсутствующим и возвращает `null`.
|
|
100
|
+
|
|
101
|
+
## Save/restore recipe
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
function restorePresetIds(value: unknown): PresetId[] {
|
|
105
|
+
return normalizePresetIds(value, isPresetId, ["daily"]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildPresetView(ids: readonly PresetId[]) {
|
|
109
|
+
return resolvePresetOptionsByIds(ids, PRESET_OPTIONS);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Разделяйте:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
unknown persisted payload ── normalizePresetIds ──► canonical ids
|
|
117
|
+
literal UI draft ── clone only ──► literal copy
|
|
118
|
+
canonical ids ── resolve... ──► option objects for UI
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API и ошибки
|
|
122
|
+
|
|
123
|
+
| Export | Результат |
|
|
124
|
+
| --------------------------- | ------------------------------------ |
|
|
125
|
+
| `PresetOption` | Минимальный `{ id, label }` contract |
|
|
126
|
+
| `normalizePresetIds` | Проверенные уникальные ids |
|
|
127
|
+
| `resolvePresetOptionsByIds` | Options в порядке ids |
|
|
128
|
+
| `getPresetOption` | Option или `null` |
|
|
129
|
+
|
|
130
|
+
Типичные ошибки:
|
|
131
|
+
|
|
132
|
+
- Ожидать fallback для пустого массива.
|
|
133
|
+
- Считать result options глубокими копиями.
|
|
134
|
+
- Использовать normalizer во время пользовательского ввода.
|
|
135
|
+
- Передавать type guard, который делает unsafe cast вместо runtime-проверки.
|
|
136
|
+
- Ожидать, что неизвестный id будет создан автоматически.
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Foundation Lib/Infrastructure/PWA" />
|
|
4
|
+
|
|
5
|
+
# PWA
|
|
6
|
+
|
|
7
|
+
Модуль PWA состоит из двух публичных входов с разной ответственностью:
|
|
8
|
+
|
|
9
|
+
| Import path | Что содержит |
|
|
10
|
+
| ----------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
11
|
+
| `@ryuzaki13/react-foundation-lib/pwa` | React-hook обновления Service Worker и все функции cache policy |
|
|
12
|
+
| `@ryuzaki13/react-foundation-lib/pwa/sw-cache-policy` | Только чистый parser/builder политики `x-sw-cache`, без React update-flow |
|
|
13
|
+
|
|
14
|
+
Используйте точный subpath. Внутренний файл `serviceWorker` не является самостоятельным публичным export, даже если отдельные функции используются самим hook.
|
|
15
|
+
|
|
16
|
+
## Установка
|
|
17
|
+
|
|
18
|
+
Для `useServiceWorkerUpdate` нужен React. Cache policy не имеет React-зависимости.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @ryuzaki13/react-foundation-lib react
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Часть 1. Обновление Service Worker
|
|
25
|
+
|
|
26
|
+
### Задача hook
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { useServiceWorkerUpdate } from "@ryuzaki13/react-foundation-lib/pwa";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Hook:
|
|
33
|
+
|
|
34
|
+
- регистрирует Service Worker в production;
|
|
35
|
+
- проверяет обновление сразу после запуска;
|
|
36
|
+
- затем повторяет проверку каждые 5 минут;
|
|
37
|
+
- автоматически применяет накопившееся обновление вскоре после обычного открытия или reload;
|
|
38
|
+
- для обновления, найденного во время активной сессии, сообщает UI о доступной версии;
|
|
39
|
+
- очищает interval, startup timeout и подписку при размонтировании.
|
|
40
|
+
|
|
41
|
+
### Быстрый старт
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useServiceWorkerUpdate } from "@ryuzaki13/react-foundation-lib/pwa";
|
|
45
|
+
|
|
46
|
+
export function ServiceWorkerUpdateBanner() {
|
|
47
|
+
const { hasUpdate, applyUpdate, dismiss } = useServiceWorkerUpdate();
|
|
48
|
+
|
|
49
|
+
if (!hasUpdate) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<aside role="status" aria-live="polite">
|
|
55
|
+
<p>Доступна новая версия приложения.</p>
|
|
56
|
+
<button type="button" onClick={applyUpdate}>
|
|
57
|
+
Обновить
|
|
58
|
+
</button>
|
|
59
|
+
<button type="button" onClick={dismiss}>
|
|
60
|
+
Позже
|
|
61
|
+
</button>
|
|
62
|
+
</aside>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Разместите этот компонент один раз в корневом client shell приложения. Несколько экземпляров создадут несколько периодических проверок.
|
|
68
|
+
|
|
69
|
+
### Возвращаемое значение
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const {
|
|
73
|
+
hasUpdate, // boolean
|
|
74
|
+
applyUpdate, // () => boolean
|
|
75
|
+
dismiss // () => void
|
|
76
|
+
} = useServiceWorkerUpdate();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`hasUpdate` становится `true`, когда найден registration с `waiting` worker.
|
|
80
|
+
|
|
81
|
+
`applyUpdate()`:
|
|
82
|
+
|
|
83
|
+
1. отправляет waiting worker сообщение `{ type: "SKIP_WAITING" }`;
|
|
84
|
+
2. запоминает reload в `sessionStorage`;
|
|
85
|
+
3. ожидает смену controller и перезагружает страницу;
|
|
86
|
+
4. если другой клиент уже активировал worker и `waiting` исчез, выполняет fallback reload.
|
|
87
|
+
|
|
88
|
+
Возвращается `true`, если применение или fallback reload запущены, иначе `false`.
|
|
89
|
+
|
|
90
|
+
`dismiss()` только скрывает текущий баннер. Он не удаляет waiting worker, не отменяет обновление и не запрещает следующей проверке снова показать баннер.
|
|
91
|
+
|
|
92
|
+
## Требования к Service Worker приложения
|
|
93
|
+
|
|
94
|
+
Библиотека управляет клиентской стороной, но сам файл worker принадлежит приложению. Для полного update-flow он должен выполнять два контракта.
|
|
95
|
+
|
|
96
|
+
### Обработать `SKIP_WAITING`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
self.addEventListener("message", (event) => {
|
|
100
|
+
if (event.data?.type === "SKIP_WAITING") {
|
|
101
|
+
void self.skipWaiting();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Без этого нажатие «Обновить» не активирует ожидающий worker немедленно.
|
|
107
|
+
|
|
108
|
+
### Сообщить вкладкам об активации
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
self.addEventListener("activate", (event) => {
|
|
112
|
+
event.waitUntil(
|
|
113
|
+
(async () => {
|
|
114
|
+
await self.clients.claim();
|
|
115
|
+
const clients = await self.clients.matchAll({ type: "window" });
|
|
116
|
+
|
|
117
|
+
for (const client of clients) {
|
|
118
|
+
client.postMessage({ type: "SW_ACTIVATED_BY_CLIENT" });
|
|
119
|
+
}
|
|
120
|
+
})()
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Это сообщение помогает синхронно перезагрузить другие открытые вкладки. Конкретная стратегия `claim` и рассылки должна соответствовать архитектуре worker приложения.
|
|
126
|
+
|
|
127
|
+
## Compile-time конфигурация
|
|
128
|
+
|
|
129
|
+
Потребитель обязан определить при сборке:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
declare const __DEV__: boolean;
|
|
133
|
+
declare const __SERVICE_WORKER_SCOPE__: string;
|
|
134
|
+
declare const __SERVICE_WORKER_URL__: string;
|
|
135
|
+
declare const __SERVICE_WORKER_UPDATE_RELOAD_COUNT_KEY__: string;
|
|
136
|
+
declare const __SERVICE_WORKER_MAX_AUTO_UPDATE_RELOADS__: number;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Пример для Vite:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { defineConfig } from "vite";
|
|
143
|
+
|
|
144
|
+
export default defineConfig(({ mode }) => ({
|
|
145
|
+
define: {
|
|
146
|
+
__DEV__: JSON.stringify(mode !== "production"),
|
|
147
|
+
__SERVICE_WORKER_SCOPE__: JSON.stringify("/app/"),
|
|
148
|
+
__SERVICE_WORKER_URL__: JSON.stringify("/app/sw.js"),
|
|
149
|
+
__SERVICE_WORKER_UPDATE_RELOAD_COUNT_KEY__: JSON.stringify("my-app:sw-update-reload-count"),
|
|
150
|
+
__SERVICE_WORKER_MAX_AUTO_UPDATE_RELOADS__: JSON.stringify(3)
|
|
151
|
+
}
|
|
152
|
+
}));
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Значения должны быть литералами в собранном клиентском коде. Если сборщик не заменит идентификаторы, приложение получит `ReferenceError`.
|
|
156
|
+
|
|
157
|
+
### Для чего нужен предел auto reload
|
|
158
|
+
|
|
159
|
+
После открытия/reload hook даёт обновлениям 30 секунд на автоматическое применение. Счётчик в sessionStorage ограничивает цепочку перезагрузок значением `__SERVICE_WORKER_MAX_AUTO_UPDATE_RELOADS__`.
|
|
160
|
+
|
|
161
|
+
Это защищает от бесконечного reload-loop при проблемном деплое. После 30-секундного startup-окна счётчик очищается, а дальнейшие обновления показываются через UI.
|
|
162
|
+
|
|
163
|
+
## Development, SSR и browser boundary
|
|
164
|
+
|
|
165
|
+
В режиме `__DEV__ === true` effect сразу завершается: Service Worker не регистрируется и интервал не запускается.
|
|
166
|
+
|
|
167
|
+
Hook использует `window`, `navigator`, `performance`, `sessionStorage` и API Service Worker. Подключайте его только в браузерном React-дереве. Сам effect не выполняется при SSR, но compile-time константы всё равно должны быть доступны в bundle.
|
|
168
|
+
|
|
169
|
+
Ошибки регистрации и проверки логируются, а наружу не выбрасываются. Отсутствие поддержки `navigator.serviceWorker` приводит к спокойному `hasUpdate: false`.
|
|
170
|
+
|
|
171
|
+
## Жизненный цикл обновления
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
mount
|
|
175
|
+
→ register URL + scope
|
|
176
|
+
→ проверить existing waiting worker
|
|
177
|
+
→ вызвать registration.update()
|
|
178
|
+
→ если update найден в первые 30 секунд после navigate/reload:
|
|
179
|
+
автоматически отправить SKIP_WAITING, пока не исчерпан лимит
|
|
180
|
+
→ иначе установить hasUpdate = true
|
|
181
|
+
→ пользователь выбирает applyUpdate или dismiss
|
|
182
|
+
→ каждые 5 минут новая проверка
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Навигация считается startup-кандидатом только для `PerformanceNavigationTiming.type` со значением `"navigate"` или `"reload"`.
|
|
186
|
+
|
|
187
|
+
## Часть 2. Политика SW-кэша
|
|
188
|
+
|
|
189
|
+
Эта часть описывает компактное текстовое значение, которое transport и Service Worker могут использовать как согласованный cache profile.
|
|
190
|
+
|
|
191
|
+
Для кода без React импортируйте узкий subpath:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import {
|
|
195
|
+
buildSwCachePolicyValue,
|
|
196
|
+
normalizeSwCacheName,
|
|
197
|
+
parseSwCachePolicy,
|
|
198
|
+
resolveSwCacheCacheNameByPolicy,
|
|
199
|
+
type SwCachePolicyDraft
|
|
200
|
+
} from "@ryuzaki13/react-foundation-lib/pwa/sw-cache-policy";
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Формат политики
|
|
204
|
+
|
|
205
|
+
Поддерживаются три режима:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
off
|
|
209
|
+
ttl=<duration>[;max=<count>][;name=<name>]
|
|
210
|
+
bust=<duration>[;max=<count>][;name=<name>]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Примеры:
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
off
|
|
217
|
+
ttl=10m
|
|
218
|
+
ttl=24h;max=200;name=references
|
|
219
|
+
bust=7d;name=metadata
|
|
220
|
+
ttl=forever;max=50
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Опции разрешено разделять `;` или `,`. Имена ключей и режимы разбираются без учёта регистра.
|
|
224
|
+
|
|
225
|
+
`ttl` и `bust` — метки профиля. Конкретное отличие их runtime-поведения реализует Service Worker/transport потребителя; parser только возвращает разобранный `mode`.
|
|
226
|
+
|
|
227
|
+
## Единицы времени
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
type SwCacheDurationUnit = "ms" | "s" | "m" | "h" | "d";
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Поддерживаются положительные целые и десятичные значения, например `500ms`, `1.5h`, `7d`. Максимальный конечный TTL — 365 дней. Специальное значение `forever` превращается в `Infinity`.
|
|
234
|
+
|
|
235
|
+
Ноль, отрицательное число, запись без единицы, scientific notation и срок больше 365 дней считаются неверными.
|
|
236
|
+
|
|
237
|
+
## `parseSwCachePolicy(value)`
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const policy = parseSwCachePolicy("ttl=10m;max=200;name=UI Data");
|
|
241
|
+
|
|
242
|
+
// {
|
|
243
|
+
// mode: "ttl",
|
|
244
|
+
// amount: 10,
|
|
245
|
+
// unit: "m",
|
|
246
|
+
// name: "ui-data",
|
|
247
|
+
// maxEntries: 200,
|
|
248
|
+
// resolvedMaxEntries: 200,
|
|
249
|
+
// ttlMs: 600000,
|
|
250
|
+
// cacheName: "odata-ui-data-10m-max200"
|
|
251
|
+
// }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Пустое или полностью неверное значение возвращает `undefined`. Строка `off` возвращает `{ mode: "off" }`.
|
|
255
|
+
|
|
256
|
+
Для опций поддерживаются алиасы:
|
|
257
|
+
|
|
258
|
+
| Смысл | Допустимые ключи |
|
|
259
|
+
| ---------------- | ------------------------------ |
|
|
260
|
+
| Максимум записей | `maxEntries`, `max`, `entries` |
|
|
261
|
+
| Имя профиля | `cacheName`, `cache`, `name` |
|
|
262
|
+
|
|
263
|
+
Неизвестные опции игнорируются. Если одна опция повторяется, используется последнее значение из-за семантики `Map`.
|
|
264
|
+
|
|
265
|
+
### `maxEntries`
|
|
266
|
+
|
|
267
|
+
Допустимо положительное целое число. Значение ограничивается максимумом `5000`. Неверное значение не делает всю политику неверной: оно просто отбрасывается.
|
|
268
|
+
|
|
269
|
+
`resolvedMaxEntries` всегда содержит фактический лимит для `ttl`/`bust`: переданный либо значение по умолчанию `100`.
|
|
270
|
+
|
|
271
|
+
При этом cache name включает суффикс `-maxN` только для явно переданного валидного лимита. Дефолтные `100` в имени не записываются.
|
|
272
|
+
|
|
273
|
+
## `normalizeSwCacheName(value)`
|
|
274
|
+
|
|
275
|
+
Нормализует имя профиля:
|
|
276
|
+
|
|
277
|
+
1. удаляет пробелы по краям;
|
|
278
|
+
2. переводит в нижний регистр;
|
|
279
|
+
3. заменяет группы недопустимых символов на `-`;
|
|
280
|
+
4. удаляет крайние дефисы;
|
|
281
|
+
5. возвращает `undefined`, если ничего не осталось.
|
|
282
|
+
|
|
283
|
+
Разрешены латинские буквы, цифры, `_` и `-`.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
normalizeSwCacheName(" UI Data / Main "); // "ui-data-main"
|
|
287
|
+
normalizeSwCacheName("---"); // undefined
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## `resolveSwCacheCacheNameByPolicy(value)`
|
|
291
|
+
|
|
292
|
+
Возвращает вычисленное имя cache только для валидных режимов `ttl` и `bust`.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
resolveSwCacheCacheNameByPolicy("ttl=24h;name=ref");
|
|
296
|
+
// "odata-ref-24h"
|
|
297
|
+
|
|
298
|
+
resolveSwCacheCacheNameByPolicy("off"); // undefined
|
|
299
|
+
resolveSwCacheCacheNameByPolicy("ttl=wrong"); // undefined
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Имя — устойчивый технический профиль, но формат может быть частью cache-versioning. Не собирайте его вручную.
|
|
303
|
+
|
|
304
|
+
## `buildSwCachePolicyValue(draft)`
|
|
305
|
+
|
|
306
|
+
Собирает каноническую строку из данных формы.
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const draft: SwCachePolicyDraft = {
|
|
310
|
+
mode: "ttl",
|
|
311
|
+
amount: 10,
|
|
312
|
+
unit: "m",
|
|
313
|
+
name: "UI Data",
|
|
314
|
+
maxEntries: 200
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
buildSwCachePolicyValue(draft);
|
|
318
|
+
// "ttl=10m;max=200;name=ui-data"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Правила builder:
|
|
322
|
+
|
|
323
|
+
- для `off` всегда возвращает `"off"`;
|
|
324
|
+
- неверный `amount` заменяет на `24`;
|
|
325
|
+
- отсутствующую `unit` заменяет на `h`;
|
|
326
|
+
- неверный `maxEntries` отбрасывает, больше `5000` ограничивает;
|
|
327
|
+
- имя нормализует через `normalizeSwCacheName`;
|
|
328
|
+
- порядок опций всегда `max`, затем `name`.
|
|
329
|
+
|
|
330
|
+
Builder не ограничивает вычисленный срок 365 днями. Например, draft `400d` будет собран в строку, но parser затем вернёт `undefined`. Если данные приходят из свободной формы, проверьте результат повторным `parseSwCachePolicy` перед сохранением.
|
|
331
|
+
|
|
332
|
+
## Пример формы настройки
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
function savePolicy(draft: SwCachePolicyDraft) {
|
|
336
|
+
const value = buildSwCachePolicyValue(draft);
|
|
337
|
+
const parsed = parseSwCachePolicy(value);
|
|
338
|
+
|
|
339
|
+
if (!parsed) {
|
|
340
|
+
throw new Error("Не удалось собрать допустимую cache policy");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Безопасность кэша
|
|
348
|
+
|
|
349
|
+
Политика только описывает срок и профиль. Она не определяет, **что разрешено кэшировать**.
|
|
350
|
+
|
|
351
|
+
Service Worker приложения обязан отдельно учитывать:
|
|
352
|
+
|
|
353
|
+
- пользователя, tenant и authorization context;
|
|
354
|
+
- HTTP-метод: mutation-запросы обычно не кэшируются;
|
|
355
|
+
- чувствительные персональные данные;
|
|
356
|
+
- response status и content type;
|
|
357
|
+
- очистку cache при logout;
|
|
358
|
+
- версионирование схемы ответа.
|
|
359
|
+
|
|
360
|
+
Не используйте общий cache name для ответов разных пользователей, если ключ cache не содержит безопасно разделяющий контекст.
|
|
361
|
+
|
|
362
|
+
## Частые ошибки
|
|
363
|
+
|
|
364
|
+
### Импорт внутреннего `serviceWorker.ts`
|
|
365
|
+
|
|
366
|
+
Он не объявлен в `package.json#exports`. Публичный update API — `useServiceWorkerUpdate` из `/pwa`.
|
|
367
|
+
|
|
368
|
+
### Ожидать, что `dismiss()` отменит update
|
|
369
|
+
|
|
370
|
+
Waiting worker остаётся установленным. Следующая проверка может снова показать UI.
|
|
371
|
+
|
|
372
|
+
### Не обработать `SKIP_WAITING` в worker
|
|
373
|
+
|
|
374
|
+
Кнопка применения отправит сообщение, но worker не активируется до обычного lifecycle браузера.
|
|
375
|
+
|
|
376
|
+
### Путать TTL parser с cache implementation
|
|
377
|
+
|
|
378
|
+
Функции policy не читают Cache Storage, не перехватывают fetch и не удаляют записи. Это обязанность Service Worker.
|
|
379
|
+
|
|
380
|
+
### Считать `forever` безопасным default
|
|
381
|
+
|
|
382
|
+
`ttlMs: Infinity` требует явной стратегии bust/invalidation. Без неё клиент может долго видеть устаревшие данные.
|
|
383
|
+
|
|
384
|
+
## Краткая карта API
|
|
385
|
+
|
|
386
|
+
| Экспорт | Публичный path | Назначение |
|
|
387
|
+
| ----------------------------------- | -------------- | -------------------------------------------------- |
|
|
388
|
+
| `useServiceWorkerUpdate()` | `/pwa` | Регистрация, проверка и UI-состояние обновления SW |
|
|
389
|
+
| `parseSwCachePolicy()` | оба | Разобрать текстовую политику |
|
|
390
|
+
| `buildSwCachePolicyValue()` | оба | Собрать каноническую строку из draft |
|
|
391
|
+
| `normalizeSwCacheName()` | оба | Подготовить безопасную часть cache name |
|
|
392
|
+
| `resolveSwCacheCacheNameByPolicy()` | оба | Получить имя cache из валидной политики |
|
|
393
|
+
| `ParsedSwCachePolicy` | оба | Тип разобранного результата |
|
|
394
|
+
| `SwCachePolicyDraft` | оба | Тип данных формы/builder |
|