@itd-api/cache 0.1.0 → 0.2.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.md +21 -13
- package/dist/index.cjs +78 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -54
- package/dist/index.d.ts +85 -54
- package/dist/index.js +76 -52
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ TTL/LRU-кэш и дедупликация одновременных запро
|
|
|
12
12
|
npm install itd-api @itd-api/cache
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Поддерживается `itd-api >=0.
|
|
15
|
+
Поддерживается `itd-api >=0.8.0 <1.0.0`: пакет использует отдельное поле
|
|
16
16
|
`RequestOptions.extensions` и стабильный `operationId` запроса.
|
|
17
17
|
|
|
18
18
|
## Быстрый старт
|
|
@@ -33,13 +33,17 @@ const first = await itd.posts.get(postId); // запрос к API
|
|
|
33
33
|
const second = await itd.posts.get(postId); // ответ из кэша
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Операции подключаемых модулей тоже можно кэшировать. Например, модуль Pixel Battle задаёт
|
|
37
|
+
`CachePolicyKind.Query` в `annotations.cache`, а пользователь добавляет полный `operationId`
|
|
38
|
+
в `operations`.
|
|
39
|
+
|
|
36
40
|
Кэшируются только перечисленные операции. Query, path и body входят в ключ, поэтому
|
|
37
41
|
разные страницы ленты, профили и наборы идентификаторов хранятся отдельно. Одинаковые
|
|
38
42
|
запросы, запущенные одновременно, выполняют один сетевой вызов.
|
|
39
43
|
|
|
40
|
-
Имена в `operations` —
|
|
41
|
-
правила кэша. Низкоуровневый `itd.request()` без явного
|
|
42
|
-
совпадение
|
|
44
|
+
Имена в `operations` — постоянные `operationId`, а не HTTP-пути. Изменение маршрута не ломает
|
|
45
|
+
правила кэша. Низкоуровневый `itd.request()` без явного идентификатора операции считается
|
|
46
|
+
операцией `raw`; совпадение URL со встроенным ресурсом не включает кэш автоматически.
|
|
43
47
|
|
|
44
48
|
## Настройки
|
|
45
49
|
|
|
@@ -100,11 +104,11 @@ cached.clear();
|
|
|
100
104
|
`clear()` очищает всё хранилище. Оба метода защищены от гонки: запрос, начатый до очистки,
|
|
101
105
|
не запишет устаревший результат после неё.
|
|
102
106
|
|
|
103
|
-
##
|
|
107
|
+
## События
|
|
104
108
|
|
|
105
109
|
```ts
|
|
106
|
-
const stream = itd.
|
|
107
|
-
const detachCache = cached.
|
|
110
|
+
const stream = itd.notifications.events;
|
|
111
|
+
const detachCache = cached.attachNotificationEvents(stream);
|
|
108
112
|
|
|
109
113
|
await stream.connect();
|
|
110
114
|
|
|
@@ -118,8 +122,12 @@ stream.disconnect();
|
|
|
118
122
|
пользователем, событие `unreadCount` — их счётчик. Кэш других аккаунтов и остальные операции
|
|
119
123
|
поток не изменяет.
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
Инвалидация подключается как промежуточный обработчик. Вызывайте
|
|
126
|
+
`attachNotificationEvents()` до прикладных обработчиков, которые могут не вызвать `next()`:
|
|
127
|
+
тогда отфильтрованное для интерфейса обновление всё равно не оставит устаревший кэш.
|
|
128
|
+
|
|
129
|
+
У стороннего событийного объекта без доступных идентификатора пользователя и базового URL
|
|
130
|
+
операции уведомлений инвалидируются во всех аккаунтах.
|
|
123
131
|
|
|
124
132
|
## Несколько клиентов
|
|
125
133
|
|
|
@@ -151,7 +159,7 @@ clientB.use(shared);
|
|
|
151
159
|
копиями не объединяет.
|
|
152
160
|
|
|
153
161
|
`maxEntries` ограничивает весь экземпляр `shared`, а `clear()` и `invalidate()` управляют
|
|
154
|
-
всеми его разделами. `
|
|
162
|
+
всеми его разделами. `attachNotificationEvents()` затрагивает аккаунт создавшего поток клиента.
|
|
155
163
|
Смена сессии сохраняет общий кэш, смена пользователя автоматически выбирает другой раздел.
|
|
156
164
|
|
|
157
165
|
## Ключ
|
|
@@ -161,11 +169,11 @@ clientB.use(shared);
|
|
|
161
169
|
- имя операции, HTTP-метод и path;
|
|
162
170
|
- базовый URL и идентификатор пользователя; для `auth.sessions` также идентификатор сессии;
|
|
163
171
|
- `service` или разовый `baseUrl`;
|
|
164
|
-
-
|
|
172
|
+
- параметры строки запроса и JSON-тело;
|
|
165
173
|
- режим `raw`, `skipAuth` и опции других плагинов, влияющие на ответ.
|
|
166
174
|
|
|
167
175
|
Токен и заголовки в ключ не входят. Также не учитываются `signal`, `timeout` и настройки
|
|
168
|
-
повторов.
|
|
176
|
+
повторов. Запрос с несериализуемым JSON-телом выполняется без кэширования.
|
|
169
177
|
|
|
170
178
|
Ответ хранится как независимая копия: изменение полученного объекта не меняет следующие
|
|
171
179
|
результаты.
|
|
@@ -184,7 +192,7 @@ clientB.use(shared);
|
|
|
184
192
|
| Files | `files.get` |
|
|
185
193
|
| Subscription | `subscription.status`, `subscription.methods` |
|
|
186
194
|
| Verification | `verification.status` |
|
|
187
|
-
| Platform | `platform.changelog`, `platform.announcements`, `platform.portal`, `
|
|
195
|
+
| Platform | `platform.changelog`, `platform.announcements`, `platform.portal`, `status.get` |
|
|
188
196
|
|
|
189
197
|
Страницы, загружаемые итераторами, используют операцию соответствующего списочного метода:
|
|
190
198
|
`posts.iterate()` — `posts.list`, `users.iterateFollowers()` — `users.followers` и так
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let itd_api = require("itd-api");
|
|
2
3
|
let lru_cache = require("lru-cache");
|
|
3
4
|
//#region src/errors.ts
|
|
4
5
|
/** Ошибка настройки или использования плагина кэша. */
|
|
@@ -210,7 +211,7 @@ const CACHE_OPERATIONS = freezeOperations([
|
|
|
210
211
|
category: "platform"
|
|
211
212
|
},
|
|
212
213
|
{
|
|
213
|
-
id: "
|
|
214
|
+
id: "status.get",
|
|
214
215
|
category: "platform"
|
|
215
216
|
}
|
|
216
217
|
]);
|
|
@@ -224,6 +225,20 @@ function cacheOperation(operationId) {
|
|
|
224
225
|
return OPERATIONS.get(operationId);
|
|
225
226
|
}
|
|
226
227
|
//#endregion
|
|
228
|
+
//#region src/policy.ts
|
|
229
|
+
/** Виды политики кэша, объявляемой в метаданных операции. */
|
|
230
|
+
const CachePolicyKind = Object.freeze({
|
|
231
|
+
Query: "query",
|
|
232
|
+
Mutation: "mutation"
|
|
233
|
+
});
|
|
234
|
+
/** Области изоляции данных кэша. */
|
|
235
|
+
const CachePolicyScope = Object.freeze({
|
|
236
|
+
Account: "account",
|
|
237
|
+
Session: "session"
|
|
238
|
+
});
|
|
239
|
+
/** Способы инвалидации кэша после мутации. */
|
|
240
|
+
const CacheInvalidation = Object.freeze({ All: "all" });
|
|
241
|
+
//#endregion
|
|
227
242
|
//#region src/mutations.ts
|
|
228
243
|
const POST_CONTENT = [
|
|
229
244
|
"posts.list",
|
|
@@ -337,37 +352,37 @@ const CACHE_MUTATIONS = Object.freeze([
|
|
|
337
352
|
},
|
|
338
353
|
{
|
|
339
354
|
operationId: "auth.signUp",
|
|
340
|
-
invalidates:
|
|
355
|
+
invalidates: CacheInvalidation.All
|
|
341
356
|
},
|
|
342
357
|
{
|
|
343
358
|
operationId: "auth.signIn",
|
|
344
|
-
invalidates:
|
|
359
|
+
invalidates: CacheInvalidation.All
|
|
345
360
|
},
|
|
346
361
|
{
|
|
347
362
|
operationId: "auth.verifyOtp",
|
|
348
|
-
invalidates:
|
|
363
|
+
invalidates: CacheInvalidation.All
|
|
349
364
|
},
|
|
350
365
|
{
|
|
351
366
|
operationId: "auth.logout",
|
|
352
|
-
invalidates:
|
|
367
|
+
invalidates: CacheInvalidation.All
|
|
353
368
|
},
|
|
354
369
|
{
|
|
355
370
|
operationId: "auth.resetPassword",
|
|
356
|
-
invalidates:
|
|
371
|
+
invalidates: CacheInvalidation.All
|
|
357
372
|
},
|
|
358
373
|
{
|
|
359
374
|
operationId: "auth.changePassword",
|
|
360
|
-
invalidates:
|
|
375
|
+
invalidates: CacheInvalidation.All
|
|
361
376
|
},
|
|
362
377
|
{
|
|
363
378
|
operationId: "auth.revokeSession",
|
|
364
379
|
invalidates: ["auth.sessions"],
|
|
365
|
-
scope:
|
|
380
|
+
scope: CachePolicyScope.Account
|
|
366
381
|
},
|
|
367
382
|
{
|
|
368
383
|
operationId: "auth.revokeOtherSessions",
|
|
369
384
|
invalidates: ["auth.sessions"],
|
|
370
|
-
scope:
|
|
385
|
+
scope: CachePolicyScope.Account
|
|
371
386
|
},
|
|
372
387
|
{
|
|
373
388
|
operationId: "posts.create",
|
|
@@ -492,22 +507,22 @@ const CACHE_MUTATIONS = Object.freeze([
|
|
|
492
507
|
{
|
|
493
508
|
operationId: "notifications.markRead",
|
|
494
509
|
invalidates: NOTIFICATIONS,
|
|
495
|
-
scope:
|
|
510
|
+
scope: CachePolicyScope.Account
|
|
496
511
|
},
|
|
497
512
|
{
|
|
498
513
|
operationId: "notifications.markReadBatch",
|
|
499
514
|
invalidates: NOTIFICATIONS,
|
|
500
|
-
scope:
|
|
515
|
+
scope: CachePolicyScope.Account
|
|
501
516
|
},
|
|
502
517
|
{
|
|
503
518
|
operationId: "notifications.markAllRead",
|
|
504
519
|
invalidates: NOTIFICATIONS,
|
|
505
|
-
scope:
|
|
520
|
+
scope: CachePolicyScope.Account
|
|
506
521
|
},
|
|
507
522
|
{
|
|
508
523
|
operationId: "notifications.updateSettings",
|
|
509
524
|
invalidates: ["notifications.getSettings"],
|
|
510
|
-
scope:
|
|
525
|
+
scope: CachePolicyScope.Account
|
|
511
526
|
},
|
|
512
527
|
{
|
|
513
528
|
operationId: "files.upload",
|
|
@@ -520,32 +535,32 @@ const CACHE_MUTATIONS = Object.freeze([
|
|
|
520
535
|
{
|
|
521
536
|
operationId: "verification.submit",
|
|
522
537
|
invalidates: ["verification.status"],
|
|
523
|
-
scope:
|
|
538
|
+
scope: CachePolicyScope.Account
|
|
524
539
|
},
|
|
525
540
|
{
|
|
526
541
|
operationId: "subscription.pay",
|
|
527
542
|
invalidates: SUBSCRIPTION,
|
|
528
|
-
scope:
|
|
543
|
+
scope: CachePolicyScope.Account
|
|
529
544
|
},
|
|
530
545
|
{
|
|
531
546
|
operationId: "subscription.setAutoRenewal",
|
|
532
547
|
invalidates: SUBSCRIPTION,
|
|
533
|
-
scope:
|
|
548
|
+
scope: CachePolicyScope.Account
|
|
534
549
|
},
|
|
535
550
|
{
|
|
536
551
|
operationId: "subscription.bindCard",
|
|
537
552
|
invalidates: SUBSCRIPTION,
|
|
538
|
-
scope:
|
|
553
|
+
scope: CachePolicyScope.Account
|
|
539
554
|
},
|
|
540
555
|
{
|
|
541
556
|
operationId: "subscription.setDefaultMethod",
|
|
542
557
|
invalidates: SUBSCRIPTION,
|
|
543
|
-
scope:
|
|
558
|
+
scope: CachePolicyScope.Account
|
|
544
559
|
},
|
|
545
560
|
{
|
|
546
561
|
operationId: "subscription.removeMethod",
|
|
547
562
|
invalidates: SUBSCRIPTION,
|
|
548
|
-
scope:
|
|
563
|
+
scope: CachePolicyScope.Account
|
|
549
564
|
},
|
|
550
565
|
{
|
|
551
566
|
operationId: "reports.create",
|
|
@@ -591,7 +606,7 @@ function resolveOptions(options) {
|
|
|
591
606
|
if (!Array.isArray(options.operations) || options.operations.length === 0) throw new CacheError("cache.operations должен содержать хотя бы одну операцию");
|
|
592
607
|
const operations = /* @__PURE__ */ new Set();
|
|
593
608
|
for (const operation of options.operations) {
|
|
594
|
-
if (typeof operation !== "string" || !isCacheOperationId(operation)) throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);
|
|
609
|
+
if (typeof operation !== "string" || (0, itd_api.isBuiltInOperationId)(operation) && !isCacheOperationId(operation)) throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);
|
|
595
610
|
operations.add(operation);
|
|
596
611
|
}
|
|
597
612
|
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
@@ -623,7 +638,7 @@ function cacheMode(request) {
|
|
|
623
638
|
if (!CACHE_MODES.has(mode)) throw new CacheError(`cache должен быть '${CacheModes.Default}', '${CacheModes.Reload}' или '${CacheModes.NoStore}', получено: ${String(mode)}`);
|
|
624
639
|
return mode;
|
|
625
640
|
}
|
|
626
|
-
/** Создаёт TTL/LRU-кэш
|
|
641
|
+
/** Создаёт TTL/LRU-кэш нормализованных результатов itd-api. */
|
|
627
642
|
function cache(options) {
|
|
628
643
|
const config = resolveOptions(options);
|
|
629
644
|
const values = new lru_cache.LRUCache({
|
|
@@ -649,9 +664,10 @@ function cache(options) {
|
|
|
649
664
|
if (operations.length === 0) return;
|
|
650
665
|
const selected = /* @__PURE__ */ new Set();
|
|
651
666
|
for (const operation of operations) {
|
|
652
|
-
if (
|
|
653
|
-
|
|
654
|
-
|
|
667
|
+
if (typeof operation !== "string") throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);
|
|
668
|
+
const operationId = operation;
|
|
669
|
+
selected.add(operationId);
|
|
670
|
+
operationGenerations.set(operationId, (operationGenerations.get(operationId) ?? 0) + 1);
|
|
655
671
|
}
|
|
656
672
|
for (const [key, entry] of values.entries()) if (selected.has(entry.operation)) values.delete(key);
|
|
657
673
|
for (const [key, entry] of pending) if (selected.has(entry.operation)) pending.delete(key);
|
|
@@ -672,11 +688,24 @@ function cache(options) {
|
|
|
672
688
|
for (const [key, entry] of pending) if (entry.accountScope === accountScope && selected.has(entry.operation)) pending.delete(key);
|
|
673
689
|
};
|
|
674
690
|
const applyMutation = (accountScope, mutation) => {
|
|
675
|
-
if (mutation.invalidates ===
|
|
676
|
-
else if (mutation.scope ===
|
|
691
|
+
if (mutation.invalidates === CacheInvalidation.All) clearScope(accountScope);
|
|
692
|
+
else if (mutation.scope === CachePolicyScope.Account) invalidateScope(accountScope, mutation.invalidates);
|
|
677
693
|
else invalidate(...mutation.invalidates);
|
|
678
694
|
};
|
|
679
|
-
const
|
|
695
|
+
const invalidateNotificationStream = (stream, ...operations) => {
|
|
696
|
+
const identity = stream.getAuthIdentity();
|
|
697
|
+
const streamBaseUrl = stream.baseUrl;
|
|
698
|
+
const legacyScope = stream.getAuthScope();
|
|
699
|
+
const accountScope = identity?.userId ? JSON.stringify([streamBaseUrl, identity.userId]) : legacyScope !== void 0 ? JSON.stringify([streamBaseUrl, legacyScope]) : void 0;
|
|
700
|
+
if (accountScope === void 0) invalidate(...operations);
|
|
701
|
+
else invalidateScope(accountScope, operations);
|
|
702
|
+
};
|
|
703
|
+
const notificationMiddleware = async (context, next) => {
|
|
704
|
+
if (context.update.type === itd_api.NotificationUpdateType.Notification) invalidateNotificationStream(context.stream, "notifications.list", "notifications.count");
|
|
705
|
+
else if (context.update.type === itd_api.NotificationUpdateType.UnreadCount) invalidateNotificationStream(context.stream, "notifications.count");
|
|
706
|
+
await next();
|
|
707
|
+
};
|
|
708
|
+
const createTransformer = (installation, baseUrl, getAuthIdentity, getAuthScope, getOperation) => {
|
|
680
709
|
const fallbackAuthScope = JSON.stringify([baseUrl, `installation:${installation}`]);
|
|
681
710
|
const resolveIdentity = async () => {
|
|
682
711
|
const identity = await getAuthIdentity?.();
|
|
@@ -691,16 +720,24 @@ function cache(options) {
|
|
|
691
720
|
};
|
|
692
721
|
};
|
|
693
722
|
return async (request, next) => {
|
|
694
|
-
const
|
|
723
|
+
const policy = getOperation(request.operationId)?.annotations?.cache;
|
|
724
|
+
const operation = cacheOperation(request.operationId) ?? (policy?.kind === CachePolicyKind.Query ? {
|
|
725
|
+
id: request.operationId,
|
|
726
|
+
category: request.operationId.split(".", 1)[0] ?? "feature"
|
|
727
|
+
} : void 0);
|
|
695
728
|
const method = request.method.toUpperCase();
|
|
696
|
-
if (!(operation !== void 0 || method === "GET" || method === "HEAD")) {
|
|
697
|
-
const mutation = cacheMutation(request.operationId)
|
|
729
|
+
if (!(policy !== void 0 ? policy.kind === CachePolicyKind.Query : operation !== void 0 || method === "GET" || method === "HEAD")) {
|
|
730
|
+
const mutation = cacheMutation(request.operationId) ?? (policy?.kind === CachePolicyKind.Mutation ? {
|
|
731
|
+
operationId: request.operationId,
|
|
732
|
+
invalidates: policy.invalidates,
|
|
733
|
+
...policy.scope === void 0 ? {} : { scope: policy.scope }
|
|
734
|
+
} : void 0);
|
|
698
735
|
const startedIdentity = mutation ? await resolveIdentity() : void 0;
|
|
699
736
|
const result = await next(request);
|
|
700
737
|
if (mutation && startedIdentity) {
|
|
701
738
|
applyMutation(startedIdentity.accountScope, mutation);
|
|
702
739
|
const currentIdentity = await resolveIdentity();
|
|
703
|
-
if (currentIdentity.accountScope !== startedIdentity.accountScope && (mutation.invalidates ===
|
|
740
|
+
if (currentIdentity.accountScope !== startedIdentity.accountScope && (mutation.invalidates === CacheInvalidation.All || mutation.scope === CachePolicyScope.Account)) applyMutation(currentIdentity.accountScope, mutation);
|
|
704
741
|
} else clear();
|
|
705
742
|
return result;
|
|
706
743
|
}
|
|
@@ -710,7 +747,7 @@ function cache(options) {
|
|
|
710
747
|
const unscopedKey = buildCacheKey(operation.id, request);
|
|
711
748
|
if (unscopedKey === void 0) return next(request);
|
|
712
749
|
const identity = await resolveIdentity();
|
|
713
|
-
const scope = operation.id === "auth.sessions" ? identity.sessionScope : identity.accountScope;
|
|
750
|
+
const scope = operation.id === "auth.sessions" || policy?.kind === CachePolicyKind.Query && policy.scope === CachePolicyScope.Session ? identity.sessionScope : identity.accountScope;
|
|
714
751
|
const key = JSON.stringify([scope, unscopedKey]);
|
|
715
752
|
if (mode === CacheModes.Reload) {
|
|
716
753
|
const state = keyStates.get(key) ?? {
|
|
@@ -749,7 +786,7 @@ function cache(options) {
|
|
|
749
786
|
const result = await next(request);
|
|
750
787
|
const stored = cloneValue(result);
|
|
751
788
|
const currentIdentity = await resolveIdentity();
|
|
752
|
-
const currentScope = operation.id === "auth.sessions" ? currentIdentity.sessionScope : currentIdentity.accountScope;
|
|
789
|
+
const currentScope = operation.id === "auth.sessions" || policy?.kind === CachePolicyKind.Query && policy.scope === CachePolicyScope.Session ? currentIdentity.sessionScope : currentIdentity.accountScope;
|
|
753
790
|
if (stored.cacheable && currentScope === scope && generation === startedGeneration && (scopeGenerations.get(identity.accountScope) ?? 0) === startedScopeGeneration && (operationGenerations.get(operation.id) ?? 0) === startedOperationGeneration && (scopeOperationGenerations.get(scopedOperationKey) ?? 0) === startedScopeOperationGeneration && keyState.generation === startedKeyGeneration) values.set(key, {
|
|
754
791
|
accountScope: identity.accountScope,
|
|
755
792
|
operation: operation.id,
|
|
@@ -786,34 +823,24 @@ function cache(options) {
|
|
|
786
823
|
},
|
|
787
824
|
clear,
|
|
788
825
|
invalidate,
|
|
789
|
-
|
|
790
|
-
if (!stream || typeof stream.
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const streamBaseUrl = typeof stream.baseUrl === "string" && stream.baseUrl.length > 0 ? stream.baseUrl : void 0;
|
|
794
|
-
const legacyScope = typeof stream.getAuthScope === "function" ? stream.getAuthScope() : void 0;
|
|
795
|
-
const accountScope = identity?.userId && streamBaseUrl ? JSON.stringify([streamBaseUrl, identity.userId]) : legacyScope !== void 0 && streamBaseUrl ? JSON.stringify([streamBaseUrl, legacyScope]) : void 0;
|
|
796
|
-
if (accountScope === void 0) invalidate(...operations);
|
|
797
|
-
else invalidateScope(accountScope, operations);
|
|
798
|
-
};
|
|
799
|
-
invalidateStream("notifications.list", "notifications.count");
|
|
800
|
-
const offNotification = stream.on("notification", () => invalidateStream("notifications.list", "notifications.count"));
|
|
801
|
-
const offUnreadCount = stream.on("unreadCount", () => invalidateStream("notifications.count"));
|
|
802
|
-
return () => {
|
|
803
|
-
offNotification();
|
|
804
|
-
offUnreadCount();
|
|
805
|
-
};
|
|
826
|
+
attachNotificationEvents(stream) {
|
|
827
|
+
if (!stream || typeof stream.use !== "function" || typeof stream.getAuthIdentity !== "function" || typeof stream.getAuthScope !== "function" || typeof stream.baseUrl !== "string") throw new CacheError("attachNotificationEvents() принимает канал itd.notifications.events");
|
|
828
|
+
invalidateNotificationStream(stream, "notifications.list", "notifications.count");
|
|
829
|
+
return stream.use(notificationMiddleware);
|
|
806
830
|
},
|
|
807
831
|
install({ operations, baseUrl, getAuthIdentity, getAuthScope }) {
|
|
808
832
|
installationSequence += 1;
|
|
809
|
-
operations.use(createTransformer(installationSequence, baseUrl, getAuthIdentity, getAuthScope));
|
|
833
|
+
operations.use(createTransformer(installationSequence, baseUrl, getAuthIdentity, getAuthScope, operations.get));
|
|
810
834
|
}
|
|
811
835
|
};
|
|
812
836
|
}
|
|
813
837
|
//#endregion
|
|
814
838
|
exports.CACHE_OPERATIONS = CACHE_OPERATIONS;
|
|
815
839
|
exports.CacheError = CacheError;
|
|
840
|
+
exports.CacheInvalidation = CacheInvalidation;
|
|
816
841
|
exports.CacheModes = CacheModes;
|
|
842
|
+
exports.CachePolicyKind = CachePolicyKind;
|
|
843
|
+
exports.CachePolicyScope = CachePolicyScope;
|
|
817
844
|
exports.buildCacheKey = buildCacheKey;
|
|
818
845
|
exports.cache = cache;
|
|
819
846
|
exports.cacheOperation = cacheOperation;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["LRUCache"],"sources":["../src/errors.ts","../src/key.ts","../src/operations.ts","../src/mutations.ts","../src/plugin.ts"],"sourcesContent":["/** Ошибка настройки или использования плагина кэша. */\nexport class CacheError extends Error {\n override readonly name = 'CacheError';\n}\n","import type { OperationRequestOptions } from 'itd-api';\nimport type { CacheOperationId } from './operations.js';\n\nconst OMITTED_FIELDS = new Set([\n 'method',\n 'operationId',\n 'path',\n 'service',\n 'baseUrl',\n 'query',\n 'body',\n 'headers',\n 'raw',\n 'skipAuth',\n 'signal',\n 'timeout',\n 'retry',\n 'retrySafety',\n 'skipQueue',\n 'skipAuthRefresh',\n 'extensions',\n]);\n\n/** Строка query в том же порядке и с теми же правилами, что использует itd-api. */\nfunction queryKey(query: OperationRequestOptions['query']): string {\n if (!query) return '';\n\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) search.append(key, String(item));\n } else {\n search.append(key, String(value));\n }\n }\n\n return search.toString();\n}\n\n/**\n * Собирает ключ из значений, влияющих на адрес, тело или разобранный ответ.\n *\n * Заголовки и транспортные опции намеренно не входят. Если тело либо опция другого\n * плагина не сериализуются как JSON, запрос выполняется без кэша.\n */\nexport function buildCacheKey(\n operation: CacheOperationId,\n request: OperationRequestOptions,\n): string | undefined {\n const extras: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(request)) {\n if (!OMITTED_FIELDS.has(key) && value !== undefined) extras[key] = value;\n }\n const { cache: _cacheMode, ...extensions } = request.extensions ?? {};\n if (Object.keys(extensions).length > 0) extras.extensions = extensions;\n\n try {\n return JSON.stringify({\n operation,\n method: request.method.toUpperCase(),\n service: request.service ?? null,\n baseUrl: request.baseUrl ?? null,\n path: request.path,\n query: queryKey(request.query),\n body: request.body ?? null,\n raw: request.raw ?? false,\n skipAuth: request.skipAuth ?? null,\n extras,\n });\n } catch {\n return undefined;\n }\n}\n","import type { BuiltInOperationId, OperationId } from 'itd-api';\n\n/** Описание читающей операции itd-api. */\nexport interface CacheOperation {\n /** Стабильный ID операции и публичное имя для настройки плагина. */\n id: BuiltInOperationId;\n /** Раздел клиента. */\n category: string;\n}\n\nfunction freezeOperations<const T extends readonly CacheOperation[]>(\n operations: T,\n): Readonly<{ readonly [K in keyof T]: Readonly<T[K]> }> {\n for (const operation of operations) Object.freeze(operation);\n return Object.freeze(operations);\n}\n\n/** Читающие операции, которые можно кэшировать. */\nexport const CACHE_OPERATIONS = freezeOperations([\n { id: 'auth.sessions', category: 'auth' },\n\n { id: 'users.me', category: 'users' },\n { id: 'users.checkUsername', category: 'users' },\n { id: 'users.search', category: 'users' },\n { id: 'users.whoToFollow', category: 'users' },\n { id: 'users.topClans', category: 'users' },\n { id: 'users.followers', category: 'users' },\n { id: 'users.following', category: 'users' },\n { id: 'users.blocked', category: 'users' },\n { id: 'users.getPrivacy', category: 'users' },\n { id: 'users.pins', category: 'users' },\n { id: 'users.followStatus', category: 'users' },\n { id: 'users.get', category: 'users' },\n\n { id: 'posts.list', category: 'posts' },\n { id: 'posts.likedByUser', category: 'posts' },\n { id: 'posts.byUser', category: 'posts' },\n { id: 'posts.comments', category: 'posts' },\n { id: 'posts.stats', category: 'posts' },\n { id: 'posts.get', category: 'posts' },\n\n { id: 'comments.replies', category: 'comments' },\n\n { id: 'notifications.list', category: 'notifications' },\n { id: 'notifications.count', category: 'notifications' },\n { id: 'notifications.getSettings', category: 'notifications' },\n\n { id: 'hashtags.search', category: 'hashtags' },\n { id: 'hashtags.trending', category: 'hashtags' },\n { id: 'hashtags.posts', category: 'hashtags' },\n\n { id: 'search.all', category: 'search' },\n { id: 'files.get', category: 'files' },\n\n { id: 'subscription.status', category: 'subscription' },\n { id: 'subscription.methods', category: 'subscription' },\n { id: 'verification.status', category: 'verification' },\n\n { id: 'platform.changelog', category: 'platform' },\n { id: 'platform.announcements', category: 'platform' },\n { id: 'platform.portal', category: 'platform' },\n { id: 'platform.status', category: 'platform' },\n] as const satisfies readonly CacheOperation[]);\n\n/** Имя операции, доступное в `cache({ operations: … })`. */\nexport type CacheOperationId = (typeof CACHE_OPERATIONS)[number]['id'];\n\n/** Раздел операции. */\nexport type CacheOperationCategory = (typeof CACHE_OPERATIONS)[number]['category'];\n\nconst OPERATIONS = new Map<OperationId, (typeof CACHE_OPERATIONS)[number]>(\n CACHE_OPERATIONS.map((operation) => [operation.id, operation]),\n);\n\n/** Проверяет публичное имя кэшируемой операции. */\nexport function isCacheOperationId(value: string): value is CacheOperationId {\n return OPERATIONS.has(value as OperationId);\n}\n\n/** Находит читающую операцию по стабильному семантическому ID. */\nexport function cacheOperation(\n operationId: OperationId,\n): (typeof CACHE_OPERATIONS)[number] | undefined {\n return OPERATIONS.get(operationId);\n}\n","import type { BuiltInOperationId, OperationId } from 'itd-api';\nimport type { CacheOperationId } from './operations.js';\n\nexport type MutationInvalidation = readonly CacheOperationId[] | 'all';\n\nexport interface CacheMutation {\n operationId: BuiltInOperationId;\n invalidates: MutationInvalidation;\n /** По умолчанию зависимые операции удаляются у всех аккаунтов общего экземпляра. */\n scope?: 'account' | undefined;\n}\n\nconst POST_CONTENT = [\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n 'posts.likedByUser',\n 'posts.comments',\n 'posts.stats',\n 'comments.replies',\n 'hashtags.search',\n 'hashtags.trending',\n 'hashtags.posts',\n 'search.all',\n 'users.me',\n 'users.get',\n 'users.pins',\n] as const satisfies readonly CacheOperationId[];\n\nconst POST_REACTIONS = [\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n 'posts.likedByUser',\n 'posts.stats',\n 'hashtags.posts',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst COMMENTS = [\n 'posts.list',\n 'posts.get',\n 'posts.comments',\n 'posts.stats',\n 'comments.replies',\n 'hashtags.posts',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst PROFILE = [\n 'users.me',\n 'users.get',\n 'users.checkUsername',\n 'users.search',\n 'users.whoToFollow',\n 'users.topClans',\n 'users.followers',\n 'users.following',\n 'users.blocked',\n 'users.getPrivacy',\n 'users.pins',\n 'users.followStatus',\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n 'posts.likedByUser',\n 'posts.comments',\n 'comments.replies',\n 'hashtags.posts',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst FOLLOWING = [\n 'users.me',\n 'users.get',\n 'users.search',\n 'users.whoToFollow',\n 'users.followers',\n 'users.following',\n 'users.followStatus',\n 'posts.list',\n 'posts.byUser',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst BLOCKS = [\n 'users.me',\n 'users.get',\n 'users.search',\n 'users.whoToFollow',\n 'users.followers',\n 'users.following',\n 'users.blocked',\n 'users.followStatus',\n 'posts.list',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst PINS = [\n 'users.me',\n 'users.get',\n 'users.pins',\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n] as const satisfies readonly CacheOperationId[];\n\nconst NOTIFICATIONS = [\n 'notifications.list',\n 'notifications.count',\n] as const satisfies readonly CacheOperationId[];\n\nconst SUBSCRIPTION = [\n 'subscription.status',\n 'subscription.methods',\n] as const satisfies readonly CacheOperationId[];\n\nconst NOTHING = [] as const satisfies readonly CacheOperationId[];\n\n/**\n * Известные изменяющие запросы и читающие операции, чьи ответы они могут изменить.\n *\n * Каталог намеренно консервативен: лучше удалить несколько связанных списков, чем оставить\n * персонализированное поле или вложенный объект устаревшим.\n */\nconst CACHE_MUTATIONS = Object.freeze([\n // Авторизация и сессии.\n { operationId: 'auth.refresh', invalidates: NOTHING },\n { operationId: 'auth.resendOtp', invalidates: NOTHING },\n { operationId: 'auth.forgotPassword', invalidates: NOTHING },\n { operationId: 'auth.signUp', invalidates: 'all' },\n { operationId: 'auth.signIn', invalidates: 'all' },\n { operationId: 'auth.verifyOtp', invalidates: 'all' },\n { operationId: 'auth.logout', invalidates: 'all' },\n { operationId: 'auth.resetPassword', invalidates: 'all' },\n { operationId: 'auth.changePassword', invalidates: 'all' },\n {\n operationId: 'auth.revokeSession',\n invalidates: ['auth.sessions'],\n scope: 'account',\n },\n {\n operationId: 'auth.revokeOtherSessions',\n invalidates: ['auth.sessions'],\n scope: 'account',\n },\n\n // Посты и комментарии.\n { operationId: 'posts.create', invalidates: POST_CONTENT },\n { operationId: 'posts.update', invalidates: POST_CONTENT },\n { operationId: 'posts.remove', invalidates: POST_CONTENT },\n { operationId: 'posts.restore', invalidates: POST_CONTENT },\n { operationId: 'posts.like', invalidates: POST_REACTIONS },\n { operationId: 'posts.unlike', invalidates: POST_REACTIONS },\n { operationId: 'posts.repost', invalidates: POST_CONTENT },\n { operationId: 'posts.unrepost', invalidates: POST_CONTENT },\n { operationId: 'posts.pin', invalidates: PINS },\n { operationId: 'posts.unpin', invalidates: PINS },\n { operationId: 'posts.vote', invalidates: POST_REACTIONS },\n { operationId: 'posts.comment', invalidates: COMMENTS },\n { operationId: 'comments.reply', invalidates: COMMENTS },\n { operationId: 'comments.update', invalidates: COMMENTS },\n { operationId: 'comments.remove', invalidates: COMMENTS },\n { operationId: 'comments.restore', invalidates: COMMENTS },\n { operationId: 'comments.like', invalidates: COMMENTS },\n { operationId: 'comments.unlike', invalidates: COMMENTS },\n\n // Профиль и связи между пользователями.\n { operationId: 'users.updateMe', invalidates: PROFILE },\n { operationId: 'users.deactivate', invalidates: PROFILE },\n { operationId: 'users.restore', invalidates: PROFILE },\n { operationId: 'users.createProfile', invalidates: PROFILE },\n { operationId: 'users.follow', invalidates: FOLLOWING },\n { operationId: 'users.unfollow', invalidates: FOLLOWING },\n { operationId: 'users.block', invalidates: BLOCKS },\n { operationId: 'users.unblock', invalidates: BLOCKS },\n {\n operationId: 'users.updatePrivacy',\n invalidates: ['users.me', 'users.get', 'users.getPrivacy'],\n },\n { operationId: 'users.setPin', invalidates: PINS },\n { operationId: 'users.removePin', invalidates: PINS },\n\n // Уведомления.\n {\n operationId: 'notifications.markRead',\n invalidates: NOTIFICATIONS,\n scope: 'account',\n },\n {\n operationId: 'notifications.markReadBatch',\n invalidates: NOTIFICATIONS,\n scope: 'account',\n },\n {\n operationId: 'notifications.markAllRead',\n invalidates: NOTIFICATIONS,\n scope: 'account',\n },\n {\n operationId: 'notifications.updateSettings',\n invalidates: ['notifications.getSettings'],\n scope: 'account',\n },\n\n // Файлы и настройки аккаунта.\n { operationId: 'files.upload', invalidates: NOTHING },\n {\n operationId: 'files.remove',\n invalidates: ['files.get', ...POST_CONTENT],\n },\n {\n operationId: 'verification.submit',\n invalidates: ['verification.status'],\n scope: 'account',\n },\n {\n operationId: 'subscription.pay',\n invalidates: SUBSCRIPTION,\n scope: 'account',\n },\n {\n operationId: 'subscription.setAutoRenewal',\n invalidates: SUBSCRIPTION,\n scope: 'account',\n },\n {\n operationId: 'subscription.bindCard',\n invalidates: SUBSCRIPTION,\n scope: 'account',\n },\n {\n operationId: 'subscription.setDefaultMethod',\n invalidates: SUBSCRIPTION,\n scope: 'account',\n },\n {\n operationId: 'subscription.removeMethod',\n invalidates: SUBSCRIPTION,\n scope: 'account',\n },\n\n // Эти запросы не меняют ни один доступный для кэширования ответ.\n { operationId: 'reports.create', invalidates: NOTHING },\n { operationId: 'telemetry.dwell', invalidates: NOTHING },\n { operationId: 'telemetry.interaction', invalidates: NOTHING },\n] as const satisfies readonly CacheMutation[]);\n\n// Списки инвалидации разделяются несколькими мутациями, а `cacheMutation()` отдаёт\n// дескриптор наружу. Замораживаем и его, и список, чтобы случайное изменение одного\n// результата не переписало каталог для всех остальных операций.\nfor (const mutation of CACHE_MUTATIONS) {\n Object.freeze(mutation.invalidates);\n Object.freeze(mutation);\n}\n\nconst MUTATIONS = new Map<OperationId, CacheMutation>(\n CACHE_MUTATIONS.map((mutation) => [mutation.operationId, mutation]),\n);\n\n/** Находит известную мутацию по стабильному семантическому ID. */\nexport function cacheMutation(operationId: OperationId): CacheMutation | undefined {\n return MUTATIONS.get(operationId);\n}\n","import type {\n AuthIdentity,\n ClientPlugin,\n ItdRealtime,\n OperationRequestOptions,\n OperationTransformer,\n RealtimeContext,\n Unsubscribe,\n} from 'itd-api';\nimport { LRUCache } from 'lru-cache';\nimport { CacheError } from './errors.js';\nimport { buildCacheKey } from './key.js';\nimport { type CacheMutation, cacheMutation } from './mutations.js';\nimport { type CacheOperationId, cacheOperation, isCacheOperationId } from './operations.js';\n\n/** Режимы кэширования отдельного запроса. */\nexport const CacheModes = Object.freeze({\n /** Отдать свежий кэш, иначе выполнить запрос и сохранить ответ. */\n Default: 'default',\n /** Пропустить сохранённое значение, выполнить запрос и перезаписать кэш. */\n Reload: 'reload',\n /** Не читать и не писать кэш для этого запроса. */\n NoStore: 'no-store',\n} as const);\n\n/** Поведение кэша для отдельного запроса. */\nexport type CacheMode = (typeof CacheModes)[keyof typeof CacheModes];\n\n/** Настройки плагина. */\nexport interface CacheOptions {\n /** Сколько миллисекунд хранить успешный ответ. */\n ttl: number;\n /** Какие операции itd-api кэшировать. */\n operations: readonly CacheOperationId[];\n /** Максимальное количество ответов. По умолчанию 500. */\n maxEntries?: number | undefined;\n /** Объединять ли одновременные одинаковые запросы. По умолчанию `true`. */\n deduplicate?: boolean | undefined;\n}\n\n/** Плагин и управление созданным им хранилищем. */\nexport interface CachePlugin extends ClientPlugin {\n /** Количество готовых ответов во всех разделах кэша. */\n readonly size: number;\n /** Удаляет все ответы и не даёт выполняющимся запросам вернуть устаревший результат. */\n clear(): void;\n /** Удаляет все варианты названных операций во всех подключённых клиентах. */\n invalidate(...operations: CacheOperationId[]): void;\n /**\n * Очищает список и счётчик уведомлений по событиям realtime.\n *\n * Сразу удаляет прежние значения и возвращает функцию отписки.\n */\n attachRealtime<C extends RealtimeContext>(stream: ItdRealtime<C>): Unsubscribe;\n}\n\ninterface CacheEntry {\n accountScope: string;\n operation: CacheOperationId;\n value: unknown;\n}\n\ninterface LoadedValue {\n cacheable: boolean;\n value: unknown;\n}\n\ninterface CacheIdentity {\n accountScope: string;\n sessionScope: string;\n}\n\ninterface PendingEntry {\n accountScope: string;\n operation: CacheOperationId;\n promise: Promise<LoadedValue>;\n}\n\ninterface KeyState {\n active: number;\n generation: number;\n}\n\nconst DEFAULT_MAX_ENTRIES = 500;\nconst CACHE_MODES: ReadonlySet<string> = new Set(Object.values(CacheModes));\n\nfunction assertPositive(value: number, name: string, integer = false): void {\n if (!Number.isFinite(value) || value <= 0 || (integer && !Number.isInteger(value))) {\n throw new CacheError(\n `${name} должен быть ${integer ? 'целым ' : ''}положительным числом, получено: ${value}`,\n );\n }\n}\n\nfunction resolveOptions(options: CacheOptions): {\n ttl: number;\n operations: ReadonlySet<CacheOperationId>;\n maxEntries: number;\n deduplicate: boolean;\n} {\n if (!options || typeof options !== 'object') {\n throw new CacheError('cache() принимает объект настроек');\n }\n\n assertPositive(options.ttl, 'cache.ttl');\n\n if (!Array.isArray(options.operations) || options.operations.length === 0) {\n throw new CacheError('cache.operations должен содержать хотя бы одну операцию');\n }\n\n const operations = new Set<CacheOperationId>();\n for (const operation of options.operations as readonly string[]) {\n if (typeof operation !== 'string' || !isCacheOperationId(operation)) {\n throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);\n }\n operations.add(operation);\n }\n\n const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n assertPositive(maxEntries, 'cache.maxEntries', true);\n\n const deduplicate = options.deduplicate ?? true;\n if (typeof deduplicate !== 'boolean') {\n throw new CacheError(`cache.deduplicate должен быть boolean, получено: ${deduplicate}`);\n }\n\n return { ttl: options.ttl, operations, maxEntries, deduplicate };\n}\n\nfunction cloneValue(value: unknown): LoadedValue {\n try {\n return { cacheable: true, value: structuredClone(value) };\n } catch {\n return { cacheable: false, value };\n }\n}\n\nfunction cacheMode(request: OperationRequestOptions): CacheMode {\n const mode = request.extensions?.cache ?? CacheModes.Default;\n if (!CACHE_MODES.has(mode)) {\n throw new CacheError(\n `cache должен быть '${CacheModes.Default}', '${CacheModes.Reload}' или '${CacheModes.NoStore}', получено: ${String(mode)}`,\n );\n }\n return mode;\n}\n\n/** Создаёт TTL/LRU-кэш разобранных ответов itd-api. */\nexport function cache(options: CacheOptions): CachePlugin {\n const config = resolveOptions(options);\n const values = new LRUCache<string, CacheEntry>({\n max: config.maxEntries,\n ttl: config.ttl,\n updateAgeOnGet: false,\n allowStale: false,\n });\n const pending = new Map<string, PendingEntry>();\n const operationGenerations = new Map<CacheOperationId, number>();\n const scopeGenerations = new Map<string, number>();\n const scopeOperationGenerations = new Map<string, number>();\n const keyStates = new Map<string, KeyState>();\n let generation = 0;\n let installationSequence = 0;\n\n const scopeOperationKey = (accountScope: string, operation: CacheOperationId): string =>\n JSON.stringify([accountScope, operation]);\n\n const clear = (): void => {\n generation += 1;\n values.clear();\n pending.clear();\n };\n\n const invalidate = (...operations: CacheOperationId[]): void => {\n if (operations.length === 0) return;\n\n const selected = new Set<CacheOperationId>();\n for (const operation of operations as readonly string[]) {\n if (!isCacheOperationId(operation)) {\n throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);\n }\n selected.add(operation);\n operationGenerations.set(operation, (operationGenerations.get(operation) ?? 0) + 1);\n }\n\n for (const [key, entry] of values.entries()) {\n if (selected.has(entry.operation)) values.delete(key);\n }\n for (const [key, entry] of pending) {\n if (selected.has(entry.operation)) pending.delete(key);\n }\n };\n\n const clearScope = (accountScope: string): void => {\n scopeGenerations.set(accountScope, (scopeGenerations.get(accountScope) ?? 0) + 1);\n\n for (const [key, entry] of values.entries()) {\n if (entry.accountScope === accountScope) values.delete(key);\n }\n for (const [key, entry] of pending) {\n if (entry.accountScope === accountScope) pending.delete(key);\n }\n };\n\n const invalidateScope = (accountScope: string, operations: readonly CacheOperationId[]): void => {\n if (operations.length === 0) return;\n\n const selected = new Set(operations);\n for (const operation of selected) {\n const key = scopeOperationKey(accountScope, operation);\n scopeOperationGenerations.set(key, (scopeOperationGenerations.get(key) ?? 0) + 1);\n }\n\n for (const [key, entry] of values.entries()) {\n if (entry.accountScope === accountScope && selected.has(entry.operation)) values.delete(key);\n }\n for (const [key, entry] of pending) {\n if (entry.accountScope === accountScope && selected.has(entry.operation)) pending.delete(key);\n }\n };\n\n const applyMutation = (accountScope: string, mutation: CacheMutation): void => {\n if (mutation.invalidates === 'all') {\n clearScope(accountScope);\n } else if (mutation.scope === 'account') {\n invalidateScope(accountScope, mutation.invalidates);\n } else {\n invalidate(...mutation.invalidates);\n }\n };\n\n const createTransformer = (\n installation: number,\n baseUrl: string,\n getAuthIdentity: (() => Promise<AuthIdentity>) | undefined,\n getAuthScope: (() => string) | undefined,\n ): OperationTransformer => {\n const fallbackAuthScope = JSON.stringify([baseUrl, `installation:${installation}`]);\n\n const resolveIdentity = async (): Promise<CacheIdentity> => {\n const identity = await getAuthIdentity?.();\n const accountScope = identity?.userId\n ? JSON.stringify([baseUrl, identity.userId])\n : getAuthScope\n ? JSON.stringify([baseUrl, getAuthScope()])\n : fallbackAuthScope;\n const sessionScope =\n identity?.userId && identity.sessionId\n ? JSON.stringify([baseUrl, identity.userId, identity.sessionId])\n : JSON.stringify([\n accountScope,\n getAuthScope ? getAuthScope() : `installation:${installation}`,\n ]);\n return { accountScope, sessionScope };\n };\n\n return async (request, next) => {\n const operation = cacheOperation(request.operationId);\n const method = request.method.toUpperCase();\n const isRead = operation !== undefined || method === 'GET' || method === 'HEAD';\n\n if (!isRead) {\n const mutation = cacheMutation(request.operationId);\n const startedIdentity = mutation ? await resolveIdentity() : undefined;\n const result = await next(request);\n if (mutation && startedIdentity) {\n applyMutation(startedIdentity.accountScope, mutation);\n const currentIdentity = await resolveIdentity();\n if (\n currentIdentity.accountScope !== startedIdentity.accountScope &&\n (mutation.invalidates === 'all' || mutation.scope === 'account')\n ) {\n applyMutation(currentIdentity.accountScope, mutation);\n }\n } else {\n clear();\n }\n return result;\n }\n\n if (!operation || !config.operations.has(operation.id)) return next(request);\n\n const mode = cacheMode(request);\n if (mode === CacheModes.NoStore) return next(request);\n\n const unscopedKey = buildCacheKey(operation.id, request);\n if (unscopedKey === undefined) return next(request);\n\n const identity = await resolveIdentity();\n const scope =\n operation.id === 'auth.sessions' ? identity.sessionScope : identity.accountScope;\n const key = JSON.stringify([scope, unscopedKey]);\n\n if (mode === CacheModes.Reload) {\n // Прежняя загрузка только этого ключа не должна перезаписать принудительное обновление.\n const state = keyStates.get(key) ?? {\n active: 0,\n generation: 0,\n };\n state.generation += 1;\n keyStates.set(key, state);\n values.delete(key);\n pending.delete(key);\n }\n\n if (mode === CacheModes.Default) {\n const hit = values.get(key);\n if (hit) {\n const cloned = cloneValue(hit.value);\n if (cloned.cacheable) return cloned.value;\n values.delete(key);\n }\n\n const existing = pending.get(key);\n if (\n config.deduplicate &&\n request.signal === undefined &&\n request.timeout === undefined &&\n existing\n ) {\n const loaded = await existing.promise;\n return cloneValue(loaded.value).value;\n }\n }\n\n const startedGeneration = generation;\n const startedScopeGeneration = scopeGenerations.get(identity.accountScope) ?? 0;\n const startedOperationGeneration = operationGenerations.get(operation.id) ?? 0;\n const scopedOperationKey = scopeOperationKey(identity.accountScope, operation.id);\n const startedScopeOperationGeneration =\n scopeOperationGenerations.get(scopedOperationKey) ?? 0;\n const keyState = keyStates.get(key) ?? {\n active: 0,\n generation: 0,\n };\n keyState.active += 1;\n keyStates.set(key, keyState);\n const startedKeyGeneration = keyState.generation;\n const load = (async (): Promise<LoadedValue> => {\n try {\n const result = await next(request);\n const stored = cloneValue(result);\n const currentIdentity = await resolveIdentity();\n const currentScope =\n operation.id === 'auth.sessions'\n ? currentIdentity.sessionScope\n : currentIdentity.accountScope;\n\n if (\n stored.cacheable &&\n currentScope === scope &&\n generation === startedGeneration &&\n (scopeGenerations.get(identity.accountScope) ?? 0) === startedScopeGeneration &&\n (operationGenerations.get(operation.id) ?? 0) === startedOperationGeneration &&\n (scopeOperationGenerations.get(scopedOperationKey) ?? 0) ===\n startedScopeOperationGeneration &&\n keyState.generation === startedKeyGeneration\n ) {\n values.set(key, {\n accountScope: identity.accountScope,\n operation: operation.id,\n value: stored.value,\n });\n }\n\n // Снимок уже отделён для кэша; инициатор получает независимый исходный ответ сети.\n return { cacheable: stored.cacheable, value: result };\n } finally {\n keyState.active -= 1;\n if (keyState.active === 0 && keyStates.get(key) === keyState) keyStates.delete(key);\n }\n })();\n\n const mayDeduplicate =\n mode === CacheModes.Default &&\n config.deduplicate &&\n request.signal === undefined &&\n request.timeout === undefined;\n const entry: PendingEntry = {\n accountScope: identity.accountScope,\n operation: operation.id,\n promise: load,\n };\n if (mayDeduplicate) pending.set(key, entry);\n\n try {\n const loaded = await load;\n return loaded.value;\n } finally {\n if (pending.get(key) === entry) pending.delete(key);\n }\n };\n };\n\n return {\n name: 'cache',\n get size() {\n values.purgeStale();\n return values.size;\n },\n clear,\n invalidate,\n attachRealtime(stream) {\n if (!stream || typeof stream.on !== 'function') {\n throw new CacheError('attachRealtime() принимает поток из itd.realtime()');\n }\n\n const invalidateStream = (...operations: CacheOperationId[]): void => {\n const identity =\n typeof stream.getAuthIdentity === 'function' ? stream.getAuthIdentity() : undefined;\n const streamBaseUrl =\n typeof stream.baseUrl === 'string' && stream.baseUrl.length > 0\n ? stream.baseUrl\n : undefined;\n const legacyScope =\n typeof stream.getAuthScope === 'function' ? stream.getAuthScope() : undefined;\n const accountScope =\n identity?.userId && streamBaseUrl\n ? JSON.stringify([streamBaseUrl, identity.userId])\n : legacyScope !== undefined && streamBaseUrl\n ? JSON.stringify([streamBaseUrl, legacyScope])\n : undefined;\n if (accountScope === undefined) invalidate(...operations);\n else invalidateScope(accountScope, operations);\n };\n\n invalidateStream('notifications.list', 'notifications.count');\n const offNotification = stream.on('notification', () =>\n invalidateStream('notifications.list', 'notifications.count'),\n );\n const offUnreadCount = stream.on('unreadCount', () =>\n invalidateStream('notifications.count'),\n );\n\n return () => {\n offNotification();\n offUnreadCount();\n };\n },\n install({ operations, baseUrl, getAuthIdentity, getAuthScope }) {\n installationSequence += 1;\n operations.use(\n createTransformer(installationSequence, baseUrl, getAuthIdentity, getAuthScope),\n );\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["isBuiltInOperationId","LRUCache","NotificationUpdateType"],"sources":["../src/errors.ts","../src/key.ts","../src/operations.ts","../src/policy.ts","../src/mutations.ts","../src/plugin.ts"],"sourcesContent":["/** Ошибка настройки или использования плагина кэша. */\nexport class CacheError extends Error {\n override readonly name = 'CacheError';\n}\n","import type { OperationId, OperationRequestOptions } from 'itd-api';\n\nconst OMITTED_FIELDS = new Set([\n 'method',\n 'operationId',\n 'path',\n 'service',\n 'baseUrl',\n 'query',\n 'body',\n 'headers',\n 'raw',\n 'skipAuth',\n 'signal',\n 'timeout',\n 'retry',\n 'retrySafety',\n 'skipQueue',\n 'skipAuthRefresh',\n 'extensions',\n]);\n\n/** Строка query в том же порядке и с теми же правилами, что использует itd-api. */\nfunction queryKey(query: OperationRequestOptions['query']): string {\n if (!query) return '';\n\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) search.append(key, String(item));\n } else {\n search.append(key, String(value));\n }\n }\n\n return search.toString();\n}\n\n/**\n * Собирает ключ из значений, влияющих на адрес, тело или разобранный ответ.\n *\n * Заголовки и транспортные опции намеренно не входят. Если тело либо опция другого\n * плагина не сериализуются как JSON, запрос выполняется без кэша.\n */\nexport function buildCacheKey(\n operation: OperationId,\n request: OperationRequestOptions,\n): string | undefined {\n const extras: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(request)) {\n if (!OMITTED_FIELDS.has(key) && value !== undefined) extras[key] = value;\n }\n const { cache: _cacheMode, ...extensions } = request.extensions ?? {};\n if (Object.keys(extensions).length > 0) extras.extensions = extensions;\n\n try {\n return JSON.stringify({\n operation,\n method: request.method.toUpperCase(),\n service: request.service ?? null,\n baseUrl: request.baseUrl ?? null,\n path: request.path,\n query: queryKey(request.query),\n body: request.body ?? null,\n raw: request.raw ?? false,\n skipAuth: request.skipAuth ?? null,\n extras,\n });\n } catch {\n return undefined;\n }\n}\n","import type { OperationId } from 'itd-api';\n\n/** Описание читающей операции itd-api. */\nexport interface CacheOperation {\n /** Стабильный ID операции и публичное имя для настройки плагина. */\n id: OperationId;\n /** Раздел клиента. */\n category: string;\n}\n\nfunction freezeOperations<const T extends readonly CacheOperation[]>(\n operations: T,\n): Readonly<{ readonly [K in keyof T]: Readonly<T[K]> }> {\n for (const operation of operations) Object.freeze(operation);\n return Object.freeze(operations);\n}\n\n/** Читающие операции, которые можно кэшировать. */\nexport const CACHE_OPERATIONS = freezeOperations([\n { id: 'auth.sessions', category: 'auth' },\n\n { id: 'users.me', category: 'users' },\n { id: 'users.checkUsername', category: 'users' },\n { id: 'users.search', category: 'users' },\n { id: 'users.whoToFollow', category: 'users' },\n { id: 'users.topClans', category: 'users' },\n { id: 'users.followers', category: 'users' },\n { id: 'users.following', category: 'users' },\n { id: 'users.blocked', category: 'users' },\n { id: 'users.getPrivacy', category: 'users' },\n { id: 'users.pins', category: 'users' },\n { id: 'users.followStatus', category: 'users' },\n { id: 'users.get', category: 'users' },\n\n { id: 'posts.list', category: 'posts' },\n { id: 'posts.likedByUser', category: 'posts' },\n { id: 'posts.byUser', category: 'posts' },\n { id: 'posts.comments', category: 'posts' },\n { id: 'posts.stats', category: 'posts' },\n { id: 'posts.get', category: 'posts' },\n\n { id: 'comments.replies', category: 'comments' },\n\n { id: 'notifications.list', category: 'notifications' },\n { id: 'notifications.count', category: 'notifications' },\n { id: 'notifications.getSettings', category: 'notifications' },\n\n { id: 'hashtags.search', category: 'hashtags' },\n { id: 'hashtags.trending', category: 'hashtags' },\n { id: 'hashtags.posts', category: 'hashtags' },\n\n { id: 'search.all', category: 'search' },\n { id: 'files.get', category: 'files' },\n\n { id: 'subscription.status', category: 'subscription' },\n { id: 'subscription.methods', category: 'subscription' },\n { id: 'verification.status', category: 'verification' },\n\n { id: 'platform.changelog', category: 'platform' },\n { id: 'platform.announcements', category: 'platform' },\n { id: 'platform.portal', category: 'platform' },\n { id: 'status.get', category: 'platform' },\n] as const satisfies readonly CacheOperation[]);\n\n/** Имя операции, доступное в `cache({ operations: … })`. */\nexport type CacheOperationId = (typeof CACHE_OPERATIONS)[number]['id'];\n\n/** Раздел операции. */\nexport type CacheOperationCategory = (typeof CACHE_OPERATIONS)[number]['category'];\n\nconst OPERATIONS = new Map<OperationId, (typeof CACHE_OPERATIONS)[number]>(\n CACHE_OPERATIONS.map((operation) => [operation.id, operation]),\n);\n\n/** Проверяет публичное имя кэшируемой операции. */\nexport function isCacheOperationId(value: string): value is CacheOperationId {\n return OPERATIONS.has(value as OperationId);\n}\n\n/** Находит читающую операцию по стабильному семантическому ID. */\nexport function cacheOperation(\n operationId: OperationId,\n): (typeof CACHE_OPERATIONS)[number] | undefined {\n return OPERATIONS.get(operationId);\n}\n","/** Виды политики кэша, объявляемой в метаданных операции. */\nexport const CachePolicyKind = Object.freeze({\n Query: 'query',\n Mutation: 'mutation',\n} as const);\nexport type CachePolicyKind = (typeof CachePolicyKind)[keyof typeof CachePolicyKind];\n\n/** Области изоляции данных кэша. */\nexport const CachePolicyScope = Object.freeze({\n Account: 'account',\n Session: 'session',\n} as const);\nexport type CachePolicyScope = (typeof CachePolicyScope)[keyof typeof CachePolicyScope];\n\n/** Способы инвалидации кэша после мутации. */\nexport const CacheInvalidation = Object.freeze({\n All: 'all',\n} as const);\nexport type CacheInvalidation = (typeof CacheInvalidation)[keyof typeof CacheInvalidation];\n","import type { OperationId } from 'itd-api';\nimport type { CacheOperationId } from './operations.js';\nimport { CacheInvalidation, CachePolicyScope } from './policy.js';\n\nexport type MutationInvalidation = readonly OperationId[] | CacheInvalidation;\n\nexport interface CacheMutation {\n operationId: OperationId;\n invalidates: MutationInvalidation;\n /** По умолчанию зависимые операции удаляются у всех аккаунтов общего экземпляра. */\n scope?: typeof CachePolicyScope.Account | undefined;\n}\n\nconst POST_CONTENT = [\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n 'posts.likedByUser',\n 'posts.comments',\n 'posts.stats',\n 'comments.replies',\n 'hashtags.search',\n 'hashtags.trending',\n 'hashtags.posts',\n 'search.all',\n 'users.me',\n 'users.get',\n 'users.pins',\n] as const satisfies readonly CacheOperationId[];\n\nconst POST_REACTIONS = [\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n 'posts.likedByUser',\n 'posts.stats',\n 'hashtags.posts',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst COMMENTS = [\n 'posts.list',\n 'posts.get',\n 'posts.comments',\n 'posts.stats',\n 'comments.replies',\n 'hashtags.posts',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst PROFILE = [\n 'users.me',\n 'users.get',\n 'users.checkUsername',\n 'users.search',\n 'users.whoToFollow',\n 'users.topClans',\n 'users.followers',\n 'users.following',\n 'users.blocked',\n 'users.getPrivacy',\n 'users.pins',\n 'users.followStatus',\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n 'posts.likedByUser',\n 'posts.comments',\n 'comments.replies',\n 'hashtags.posts',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst FOLLOWING = [\n 'users.me',\n 'users.get',\n 'users.search',\n 'users.whoToFollow',\n 'users.followers',\n 'users.following',\n 'users.followStatus',\n 'posts.list',\n 'posts.byUser',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst BLOCKS = [\n 'users.me',\n 'users.get',\n 'users.search',\n 'users.whoToFollow',\n 'users.followers',\n 'users.following',\n 'users.blocked',\n 'users.followStatus',\n 'posts.list',\n 'search.all',\n] as const satisfies readonly CacheOperationId[];\n\nconst PINS = [\n 'users.me',\n 'users.get',\n 'users.pins',\n 'posts.list',\n 'posts.get',\n 'posts.byUser',\n] as const satisfies readonly CacheOperationId[];\n\nconst NOTIFICATIONS = [\n 'notifications.list',\n 'notifications.count',\n] as const satisfies readonly CacheOperationId[];\n\nconst SUBSCRIPTION = [\n 'subscription.status',\n 'subscription.methods',\n] as const satisfies readonly CacheOperationId[];\n\nconst NOTHING = [] as const satisfies readonly CacheOperationId[];\n\n/**\n * Известные изменяющие запросы и читающие операции, чьи ответы они могут изменить.\n *\n * Каталог намеренно консервативен: лучше удалить несколько связанных списков, чем оставить\n * персонализированное поле или вложенный объект устаревшим.\n */\nconst CACHE_MUTATIONS = Object.freeze([\n // Авторизация и сессии.\n { operationId: 'auth.refresh', invalidates: NOTHING },\n { operationId: 'auth.resendOtp', invalidates: NOTHING },\n { operationId: 'auth.forgotPassword', invalidates: NOTHING },\n { operationId: 'auth.signUp', invalidates: CacheInvalidation.All },\n { operationId: 'auth.signIn', invalidates: CacheInvalidation.All },\n { operationId: 'auth.verifyOtp', invalidates: CacheInvalidation.All },\n { operationId: 'auth.logout', invalidates: CacheInvalidation.All },\n { operationId: 'auth.resetPassword', invalidates: CacheInvalidation.All },\n { operationId: 'auth.changePassword', invalidates: CacheInvalidation.All },\n {\n operationId: 'auth.revokeSession',\n invalidates: ['auth.sessions'],\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'auth.revokeOtherSessions',\n invalidates: ['auth.sessions'],\n scope: CachePolicyScope.Account,\n },\n\n // Посты и комментарии.\n { operationId: 'posts.create', invalidates: POST_CONTENT },\n { operationId: 'posts.update', invalidates: POST_CONTENT },\n { operationId: 'posts.remove', invalidates: POST_CONTENT },\n { operationId: 'posts.restore', invalidates: POST_CONTENT },\n { operationId: 'posts.like', invalidates: POST_REACTIONS },\n { operationId: 'posts.unlike', invalidates: POST_REACTIONS },\n { operationId: 'posts.repost', invalidates: POST_CONTENT },\n { operationId: 'posts.unrepost', invalidates: POST_CONTENT },\n { operationId: 'posts.pin', invalidates: PINS },\n { operationId: 'posts.unpin', invalidates: PINS },\n { operationId: 'posts.vote', invalidates: POST_REACTIONS },\n { operationId: 'posts.comment', invalidates: COMMENTS },\n { operationId: 'comments.reply', invalidates: COMMENTS },\n { operationId: 'comments.update', invalidates: COMMENTS },\n { operationId: 'comments.remove', invalidates: COMMENTS },\n { operationId: 'comments.restore', invalidates: COMMENTS },\n { operationId: 'comments.like', invalidates: COMMENTS },\n { operationId: 'comments.unlike', invalidates: COMMENTS },\n\n // Профиль и связи между пользователями.\n { operationId: 'users.updateMe', invalidates: PROFILE },\n { operationId: 'users.deactivate', invalidates: PROFILE },\n { operationId: 'users.restore', invalidates: PROFILE },\n { operationId: 'users.createProfile', invalidates: PROFILE },\n { operationId: 'users.follow', invalidates: FOLLOWING },\n { operationId: 'users.unfollow', invalidates: FOLLOWING },\n { operationId: 'users.block', invalidates: BLOCKS },\n { operationId: 'users.unblock', invalidates: BLOCKS },\n {\n operationId: 'users.updatePrivacy',\n invalidates: ['users.me', 'users.get', 'users.getPrivacy'],\n },\n { operationId: 'users.setPin', invalidates: PINS },\n { operationId: 'users.removePin', invalidates: PINS },\n\n // Уведомления.\n {\n operationId: 'notifications.markRead',\n invalidates: NOTIFICATIONS,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'notifications.markReadBatch',\n invalidates: NOTIFICATIONS,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'notifications.markAllRead',\n invalidates: NOTIFICATIONS,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'notifications.updateSettings',\n invalidates: ['notifications.getSettings'],\n scope: CachePolicyScope.Account,\n },\n\n // Файлы и настройки аккаунта.\n { operationId: 'files.upload', invalidates: NOTHING },\n {\n operationId: 'files.remove',\n invalidates: ['files.get', ...POST_CONTENT],\n },\n {\n operationId: 'verification.submit',\n invalidates: ['verification.status'],\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'subscription.pay',\n invalidates: SUBSCRIPTION,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'subscription.setAutoRenewal',\n invalidates: SUBSCRIPTION,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'subscription.bindCard',\n invalidates: SUBSCRIPTION,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'subscription.setDefaultMethod',\n invalidates: SUBSCRIPTION,\n scope: CachePolicyScope.Account,\n },\n {\n operationId: 'subscription.removeMethod',\n invalidates: SUBSCRIPTION,\n scope: CachePolicyScope.Account,\n },\n\n // Эти запросы не меняют ни один доступный для кэширования ответ.\n { operationId: 'reports.create', invalidates: NOTHING },\n { operationId: 'telemetry.dwell', invalidates: NOTHING },\n { operationId: 'telemetry.interaction', invalidates: NOTHING },\n] as const satisfies readonly CacheMutation[]);\n\n// Списки инвалидации разделяются несколькими мутациями, а `cacheMutation()` отдаёт\n// дескриптор наружу. Замораживаем и его, и список, чтобы случайное изменение одного\n// результата не переписало каталог для всех остальных операций.\nfor (const mutation of CACHE_MUTATIONS) {\n Object.freeze(mutation.invalidates);\n Object.freeze(mutation);\n}\n\nconst MUTATIONS = new Map<OperationId, CacheMutation>(\n CACHE_MUTATIONS.map((mutation) => [mutation.operationId, mutation]),\n);\n\n/** Находит известную мутацию по стабильному семантическому ID. */\nexport function cacheMutation(operationId: OperationId): CacheMutation | undefined {\n return MUTATIONS.get(operationId);\n}\n","import type {\n AuthIdentity,\n ClientPlugin,\n EventMiddleware,\n NotificationEventContext,\n NotificationEvents,\n OperationId,\n OperationMetadata,\n OperationRequestOptions,\n OperationTransformer,\n Unsubscribe,\n} from 'itd-api';\nimport { isBuiltInOperationId, NotificationUpdateType } from 'itd-api';\nimport { LRUCache } from 'lru-cache';\nimport { CacheError } from './errors.js';\nimport { buildCacheKey } from './key.js';\nimport { type CacheMutation, cacheMutation } from './mutations.js';\nimport { cacheOperation, isCacheOperationId } from './operations.js';\nimport { CacheInvalidation, CachePolicyKind, CachePolicyScope } from './policy.js';\n\n/** Режимы кэширования отдельного запроса. */\nexport const CacheModes = Object.freeze({\n /** Отдать свежий кэш, иначе выполнить запрос и сохранить ответ. */\n Default: 'default',\n /** Пропустить сохранённое значение, выполнить запрос и перезаписать кэш. */\n Reload: 'reload',\n /** Не читать и не писать кэш для этого запроса. */\n NoStore: 'no-store',\n} as const);\n\n/** Поведение кэша для отдельного запроса. */\nexport type CacheMode = (typeof CacheModes)[keyof typeof CacheModes];\n\n/** Настройки плагина. */\nexport interface CacheOptions {\n /** Сколько миллисекунд хранить успешный ответ. */\n ttl: number;\n /** Какие операции itd-api кэшировать. */\n operations: readonly OperationId[];\n /** Максимальное количество ответов. По умолчанию 500. */\n maxEntries?: number | undefined;\n /** Объединять ли одновременные одинаковые запросы. По умолчанию `true`. */\n deduplicate?: boolean | undefined;\n}\n\n/** Плагин и управление созданным им хранилищем. */\nexport interface CachePlugin extends ClientPlugin {\n /** Количество готовых ответов во всех разделах кэша. */\n readonly size: number;\n /** Удаляет все ответы и не даёт выполняющимся запросам вернуть устаревший результат. */\n clear(): void;\n /** Удаляет все варианты названных операций во всех подключённых клиентах. */\n invalidate(...operations: OperationId[]): void;\n /**\n * Подключает инвалидацию к нормализованным событиям уведомлений.\n *\n * Удаляет сохранённые список и счётчик, затем отслеживает обновления через промежуточный\n * обработчик. Подключайте его до обработчиков, способных остановить цепочку.\n */\n attachNotificationEvents<C extends NotificationEventContext>(\n stream: NotificationEvents<C>,\n ): Unsubscribe;\n}\n\ninterface CacheEntry {\n accountScope: string;\n operation: OperationId;\n value: unknown;\n}\n\ninterface LoadedValue {\n cacheable: boolean;\n value: unknown;\n}\n\ninterface CacheIdentity {\n accountScope: string;\n sessionScope: string;\n}\n\ntype NotificationStreamIdentity = Pick<\n NotificationEvents,\n 'baseUrl' | 'getAuthIdentity' | 'getAuthScope'\n>;\n\ninterface PendingEntry {\n accountScope: string;\n operation: OperationId;\n promise: Promise<LoadedValue>;\n}\n\ninterface KeyState {\n active: number;\n generation: number;\n}\n\nconst DEFAULT_MAX_ENTRIES = 500;\nconst CACHE_MODES: ReadonlySet<string> = new Set(Object.values(CacheModes));\n\nfunction assertPositive(value: number, name: string, integer = false): void {\n if (!Number.isFinite(value) || value <= 0 || (integer && !Number.isInteger(value))) {\n throw new CacheError(\n `${name} должен быть ${integer ? 'целым ' : ''}положительным числом, получено: ${value}`,\n );\n }\n}\n\nfunction resolveOptions(options: CacheOptions): {\n ttl: number;\n operations: ReadonlySet<OperationId>;\n maxEntries: number;\n deduplicate: boolean;\n} {\n if (!options || typeof options !== 'object') {\n throw new CacheError('cache() принимает объект настроек');\n }\n\n assertPositive(options.ttl, 'cache.ttl');\n\n if (!Array.isArray(options.operations) || options.operations.length === 0) {\n throw new CacheError('cache.operations должен содержать хотя бы одну операцию');\n }\n\n const operations = new Set<OperationId>();\n for (const operation of options.operations as readonly string[]) {\n if (\n typeof operation !== 'string' ||\n (isBuiltInOperationId(operation) && !isCacheOperationId(operation))\n ) {\n throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);\n }\n operations.add(operation as OperationId);\n }\n\n const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n assertPositive(maxEntries, 'cache.maxEntries', true);\n\n const deduplicate = options.deduplicate ?? true;\n if (typeof deduplicate !== 'boolean') {\n throw new CacheError(`cache.deduplicate должен быть boolean, получено: ${deduplicate}`);\n }\n\n return { ttl: options.ttl, operations, maxEntries, deduplicate };\n}\n\nfunction cloneValue(value: unknown): LoadedValue {\n try {\n return { cacheable: true, value: structuredClone(value) };\n } catch {\n return { cacheable: false, value };\n }\n}\n\nfunction cacheMode(request: OperationRequestOptions): CacheMode {\n const mode = request.extensions?.cache ?? CacheModes.Default;\n if (!CACHE_MODES.has(mode)) {\n throw new CacheError(\n `cache должен быть '${CacheModes.Default}', '${CacheModes.Reload}' или '${CacheModes.NoStore}', получено: ${String(mode)}`,\n );\n }\n return mode;\n}\n\n/** Создаёт TTL/LRU-кэш нормализованных результатов itd-api. */\nexport function cache(options: CacheOptions): CachePlugin {\n const config = resolveOptions(options);\n const values = new LRUCache<string, CacheEntry>({\n max: config.maxEntries,\n ttl: config.ttl,\n updateAgeOnGet: false,\n allowStale: false,\n });\n const pending = new Map<string, PendingEntry>();\n const operationGenerations = new Map<OperationId, number>();\n const scopeGenerations = new Map<string, number>();\n const scopeOperationGenerations = new Map<string, number>();\n const keyStates = new Map<string, KeyState>();\n let generation = 0;\n let installationSequence = 0;\n\n const scopeOperationKey = (accountScope: string, operation: OperationId): string =>\n JSON.stringify([accountScope, operation]);\n\n const clear = (): void => {\n generation += 1;\n values.clear();\n pending.clear();\n };\n\n const invalidate = (...operations: OperationId[]): void => {\n if (operations.length === 0) return;\n\n const selected = new Set<OperationId>();\n for (const operation of operations as readonly string[]) {\n if (typeof operation !== 'string') {\n throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);\n }\n const operationId = operation as OperationId;\n selected.add(operationId);\n operationGenerations.set(operationId, (operationGenerations.get(operationId) ?? 0) + 1);\n }\n\n for (const [key, entry] of values.entries()) {\n if (selected.has(entry.operation)) values.delete(key);\n }\n for (const [key, entry] of pending) {\n if (selected.has(entry.operation)) pending.delete(key);\n }\n };\n\n const clearScope = (accountScope: string): void => {\n scopeGenerations.set(accountScope, (scopeGenerations.get(accountScope) ?? 0) + 1);\n\n for (const [key, entry] of values.entries()) {\n if (entry.accountScope === accountScope) values.delete(key);\n }\n for (const [key, entry] of pending) {\n if (entry.accountScope === accountScope) pending.delete(key);\n }\n };\n\n const invalidateScope = (accountScope: string, operations: readonly OperationId[]): void => {\n if (operations.length === 0) return;\n\n const selected = new Set(operations);\n for (const operation of selected) {\n const key = scopeOperationKey(accountScope, operation);\n scopeOperationGenerations.set(key, (scopeOperationGenerations.get(key) ?? 0) + 1);\n }\n\n for (const [key, entry] of values.entries()) {\n if (entry.accountScope === accountScope && selected.has(entry.operation)) values.delete(key);\n }\n for (const [key, entry] of pending) {\n if (entry.accountScope === accountScope && selected.has(entry.operation)) pending.delete(key);\n }\n };\n\n const applyMutation = (accountScope: string, mutation: CacheMutation): void => {\n if (mutation.invalidates === CacheInvalidation.All) {\n clearScope(accountScope);\n } else if (mutation.scope === CachePolicyScope.Account) {\n invalidateScope(accountScope, mutation.invalidates);\n } else {\n invalidate(...mutation.invalidates);\n }\n };\n\n const invalidateNotificationStream = (\n stream: NotificationStreamIdentity,\n ...operations: OperationId[]\n ): void => {\n const identity = stream.getAuthIdentity();\n const streamBaseUrl = stream.baseUrl;\n const legacyScope = stream.getAuthScope();\n const accountScope = identity?.userId\n ? JSON.stringify([streamBaseUrl, identity.userId])\n : legacyScope !== undefined\n ? JSON.stringify([streamBaseUrl, legacyScope])\n : undefined;\n\n if (accountScope === undefined) invalidate(...operations);\n else invalidateScope(accountScope, operations);\n };\n\n const notificationMiddleware: EventMiddleware<NotificationEventContext> = async (\n context,\n next,\n ) => {\n if (context.update.type === NotificationUpdateType.Notification) {\n invalidateNotificationStream(context.stream, 'notifications.list', 'notifications.count');\n } else if (context.update.type === NotificationUpdateType.UnreadCount) {\n invalidateNotificationStream(context.stream, 'notifications.count');\n }\n\n await next();\n };\n\n const createTransformer = (\n installation: number,\n baseUrl: string,\n getAuthIdentity: (() => Promise<AuthIdentity>) | undefined,\n getAuthScope: (() => string) | undefined,\n getOperation: (operationId: OperationId) => OperationMetadata | undefined,\n ): OperationTransformer => {\n const fallbackAuthScope = JSON.stringify([baseUrl, `installation:${installation}`]);\n\n const resolveIdentity = async (): Promise<CacheIdentity> => {\n const identity = await getAuthIdentity?.();\n const accountScope = identity?.userId\n ? JSON.stringify([baseUrl, identity.userId])\n : getAuthScope\n ? JSON.stringify([baseUrl, getAuthScope()])\n : fallbackAuthScope;\n const sessionScope =\n identity?.userId && identity.sessionId\n ? JSON.stringify([baseUrl, identity.userId, identity.sessionId])\n : JSON.stringify([\n accountScope,\n getAuthScope ? getAuthScope() : `installation:${installation}`,\n ]);\n return { accountScope, sessionScope };\n };\n\n return async (request, next) => {\n const policy = getOperation(request.operationId)?.annotations?.cache;\n const builtInOperation = cacheOperation(request.operationId);\n const operation =\n builtInOperation ??\n (policy?.kind === CachePolicyKind.Query\n ? { id: request.operationId, category: request.operationId.split('.', 1)[0] ?? 'feature' }\n : undefined);\n const method = request.method.toUpperCase();\n const isRead =\n policy !== undefined\n ? policy.kind === CachePolicyKind.Query\n : operation !== undefined || method === 'GET' || method === 'HEAD';\n\n if (!isRead) {\n const mutation =\n cacheMutation(request.operationId) ??\n (policy?.kind === CachePolicyKind.Mutation\n ? {\n operationId: request.operationId,\n invalidates: policy.invalidates,\n ...(policy.scope === undefined ? {} : { scope: policy.scope }),\n }\n : undefined);\n const startedIdentity = mutation ? await resolveIdentity() : undefined;\n const result = await next(request);\n if (mutation && startedIdentity) {\n applyMutation(startedIdentity.accountScope, mutation);\n const currentIdentity = await resolveIdentity();\n if (\n currentIdentity.accountScope !== startedIdentity.accountScope &&\n (mutation.invalidates === CacheInvalidation.All ||\n mutation.scope === CachePolicyScope.Account)\n ) {\n applyMutation(currentIdentity.accountScope, mutation);\n }\n } else {\n clear();\n }\n return result;\n }\n\n if (!operation || !config.operations.has(operation.id)) return next(request);\n\n const mode = cacheMode(request);\n if (mode === CacheModes.NoStore) return next(request);\n\n const unscopedKey = buildCacheKey(operation.id, request);\n if (unscopedKey === undefined) return next(request);\n\n const identity = await resolveIdentity();\n const scope =\n operation.id === 'auth.sessions' ||\n (policy?.kind === CachePolicyKind.Query && policy.scope === CachePolicyScope.Session)\n ? identity.sessionScope\n : identity.accountScope;\n const key = JSON.stringify([scope, unscopedKey]);\n\n if (mode === CacheModes.Reload) {\n // Прежняя загрузка только этого ключа не должна перезаписать принудительное обновление.\n const state = keyStates.get(key) ?? {\n active: 0,\n generation: 0,\n };\n state.generation += 1;\n keyStates.set(key, state);\n values.delete(key);\n pending.delete(key);\n }\n\n if (mode === CacheModes.Default) {\n const hit = values.get(key);\n if (hit) {\n const cloned = cloneValue(hit.value);\n if (cloned.cacheable) return cloned.value;\n values.delete(key);\n }\n\n const existing = pending.get(key);\n if (\n config.deduplicate &&\n request.signal === undefined &&\n request.timeout === undefined &&\n existing\n ) {\n const loaded = await existing.promise;\n return cloneValue(loaded.value).value;\n }\n }\n\n const startedGeneration = generation;\n const startedScopeGeneration = scopeGenerations.get(identity.accountScope) ?? 0;\n const startedOperationGeneration = operationGenerations.get(operation.id) ?? 0;\n const scopedOperationKey = scopeOperationKey(identity.accountScope, operation.id);\n const startedScopeOperationGeneration =\n scopeOperationGenerations.get(scopedOperationKey) ?? 0;\n const keyState = keyStates.get(key) ?? {\n active: 0,\n generation: 0,\n };\n keyState.active += 1;\n keyStates.set(key, keyState);\n const startedKeyGeneration = keyState.generation;\n const load = (async (): Promise<LoadedValue> => {\n try {\n const result = await next(request);\n const stored = cloneValue(result);\n const currentIdentity = await resolveIdentity();\n const currentScope =\n operation.id === 'auth.sessions' ||\n (policy?.kind === CachePolicyKind.Query && policy.scope === CachePolicyScope.Session)\n ? currentIdentity.sessionScope\n : currentIdentity.accountScope;\n\n if (\n stored.cacheable &&\n currentScope === scope &&\n generation === startedGeneration &&\n (scopeGenerations.get(identity.accountScope) ?? 0) === startedScopeGeneration &&\n (operationGenerations.get(operation.id) ?? 0) === startedOperationGeneration &&\n (scopeOperationGenerations.get(scopedOperationKey) ?? 0) ===\n startedScopeOperationGeneration &&\n keyState.generation === startedKeyGeneration\n ) {\n values.set(key, {\n accountScope: identity.accountScope,\n operation: operation.id,\n value: stored.value,\n });\n }\n\n // Снимок уже отделён для кэша; инициатор получает независимый исходный ответ сети.\n return { cacheable: stored.cacheable, value: result };\n } finally {\n keyState.active -= 1;\n if (keyState.active === 0 && keyStates.get(key) === keyState) keyStates.delete(key);\n }\n })();\n\n const mayDeduplicate =\n mode === CacheModes.Default &&\n config.deduplicate &&\n request.signal === undefined &&\n request.timeout === undefined;\n const entry: PendingEntry = {\n accountScope: identity.accountScope,\n operation: operation.id,\n promise: load,\n };\n if (mayDeduplicate) pending.set(key, entry);\n\n try {\n const loaded = await load;\n return loaded.value;\n } finally {\n if (pending.get(key) === entry) pending.delete(key);\n }\n };\n };\n\n return {\n name: 'cache',\n get size() {\n values.purgeStale();\n return values.size;\n },\n clear,\n invalidate,\n attachNotificationEvents(stream) {\n if (\n !stream ||\n typeof stream.use !== 'function' ||\n typeof stream.getAuthIdentity !== 'function' ||\n typeof stream.getAuthScope !== 'function' ||\n typeof stream.baseUrl !== 'string'\n ) {\n throw new CacheError('attachNotificationEvents() принимает канал itd.notifications.events');\n }\n\n invalidateNotificationStream(stream, 'notifications.list', 'notifications.count');\n return stream.use(notificationMiddleware);\n },\n install({ operations, baseUrl, getAuthIdentity, getAuthScope }) {\n installationSequence += 1;\n operations.use(\n createTransformer(\n installationSequence,\n baseUrl,\n getAuthIdentity,\n getAuthScope,\n operations.get,\n ),\n );\n },\n };\n}\n"],"mappings":"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"}
|