@itd-api/cache 0.1.0 → 0.3.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 +22 -14
- package/dist/index.cjs +102 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -54
- package/dist/index.d.ts +103 -54
- package/dist/index.js +100 -52
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NotificationUpdateType, isBuiltInOperationId } from "itd-api";
|
|
1
2
|
import { LRUCache } from "lru-cache";
|
|
2
3
|
//#region src/errors.ts
|
|
3
4
|
/** Ошибка настройки или использования плагина кэша. */
|
|
@@ -209,8 +210,32 @@ const CACHE_OPERATIONS = freezeOperations([
|
|
|
209
210
|
category: "platform"
|
|
210
211
|
},
|
|
211
212
|
{
|
|
212
|
-
id: "
|
|
213
|
+
id: "status.get",
|
|
213
214
|
category: "platform"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "shop.products.list",
|
|
218
|
+
category: "shop"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "shop.products.get",
|
|
222
|
+
category: "shop"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "shop.delivery.countries",
|
|
226
|
+
category: "shop"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "shop.delivery.cities",
|
|
230
|
+
category: "shop"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "shop.delivery.points",
|
|
234
|
+
category: "shop"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "shop.delivery.calculate",
|
|
238
|
+
category: "shop"
|
|
214
239
|
}
|
|
215
240
|
]);
|
|
216
241
|
const OPERATIONS = new Map(CACHE_OPERATIONS.map((operation) => [operation.id, operation]));
|
|
@@ -223,6 +248,20 @@ function cacheOperation(operationId) {
|
|
|
223
248
|
return OPERATIONS.get(operationId);
|
|
224
249
|
}
|
|
225
250
|
//#endregion
|
|
251
|
+
//#region src/policy.ts
|
|
252
|
+
/** Виды политики кэша, объявляемой в метаданных операции. */
|
|
253
|
+
const CachePolicyKind = Object.freeze({
|
|
254
|
+
Query: "query",
|
|
255
|
+
Mutation: "mutation"
|
|
256
|
+
});
|
|
257
|
+
/** Области изоляции данных кэша. */
|
|
258
|
+
const CachePolicyScope = Object.freeze({
|
|
259
|
+
Account: "account",
|
|
260
|
+
Session: "session"
|
|
261
|
+
});
|
|
262
|
+
/** Способы инвалидации кэша после мутации. */
|
|
263
|
+
const CacheInvalidation = Object.freeze({ All: "all" });
|
|
264
|
+
//#endregion
|
|
226
265
|
//#region src/mutations.ts
|
|
227
266
|
const POST_CONTENT = [
|
|
228
267
|
"posts.list",
|
|
@@ -336,37 +375,37 @@ const CACHE_MUTATIONS = Object.freeze([
|
|
|
336
375
|
},
|
|
337
376
|
{
|
|
338
377
|
operationId: "auth.signUp",
|
|
339
|
-
invalidates:
|
|
378
|
+
invalidates: CacheInvalidation.All
|
|
340
379
|
},
|
|
341
380
|
{
|
|
342
381
|
operationId: "auth.signIn",
|
|
343
|
-
invalidates:
|
|
382
|
+
invalidates: CacheInvalidation.All
|
|
344
383
|
},
|
|
345
384
|
{
|
|
346
385
|
operationId: "auth.verifyOtp",
|
|
347
|
-
invalidates:
|
|
386
|
+
invalidates: CacheInvalidation.All
|
|
348
387
|
},
|
|
349
388
|
{
|
|
350
389
|
operationId: "auth.logout",
|
|
351
|
-
invalidates:
|
|
390
|
+
invalidates: CacheInvalidation.All
|
|
352
391
|
},
|
|
353
392
|
{
|
|
354
393
|
operationId: "auth.resetPassword",
|
|
355
|
-
invalidates:
|
|
394
|
+
invalidates: CacheInvalidation.All
|
|
356
395
|
},
|
|
357
396
|
{
|
|
358
397
|
operationId: "auth.changePassword",
|
|
359
|
-
invalidates:
|
|
398
|
+
invalidates: CacheInvalidation.All
|
|
360
399
|
},
|
|
361
400
|
{
|
|
362
401
|
operationId: "auth.revokeSession",
|
|
363
402
|
invalidates: ["auth.sessions"],
|
|
364
|
-
scope:
|
|
403
|
+
scope: CachePolicyScope.Account
|
|
365
404
|
},
|
|
366
405
|
{
|
|
367
406
|
operationId: "auth.revokeOtherSessions",
|
|
368
407
|
invalidates: ["auth.sessions"],
|
|
369
|
-
scope:
|
|
408
|
+
scope: CachePolicyScope.Account
|
|
370
409
|
},
|
|
371
410
|
{
|
|
372
411
|
operationId: "posts.create",
|
|
@@ -491,22 +530,22 @@ const CACHE_MUTATIONS = Object.freeze([
|
|
|
491
530
|
{
|
|
492
531
|
operationId: "notifications.markRead",
|
|
493
532
|
invalidates: NOTIFICATIONS,
|
|
494
|
-
scope:
|
|
533
|
+
scope: CachePolicyScope.Account
|
|
495
534
|
},
|
|
496
535
|
{
|
|
497
536
|
operationId: "notifications.markReadBatch",
|
|
498
537
|
invalidates: NOTIFICATIONS,
|
|
499
|
-
scope:
|
|
538
|
+
scope: CachePolicyScope.Account
|
|
500
539
|
},
|
|
501
540
|
{
|
|
502
541
|
operationId: "notifications.markAllRead",
|
|
503
542
|
invalidates: NOTIFICATIONS,
|
|
504
|
-
scope:
|
|
543
|
+
scope: CachePolicyScope.Account
|
|
505
544
|
},
|
|
506
545
|
{
|
|
507
546
|
operationId: "notifications.updateSettings",
|
|
508
547
|
invalidates: ["notifications.getSettings"],
|
|
509
|
-
scope:
|
|
548
|
+
scope: CachePolicyScope.Account
|
|
510
549
|
},
|
|
511
550
|
{
|
|
512
551
|
operationId: "files.upload",
|
|
@@ -519,32 +558,32 @@ const CACHE_MUTATIONS = Object.freeze([
|
|
|
519
558
|
{
|
|
520
559
|
operationId: "verification.submit",
|
|
521
560
|
invalidates: ["verification.status"],
|
|
522
|
-
scope:
|
|
561
|
+
scope: CachePolicyScope.Account
|
|
523
562
|
},
|
|
524
563
|
{
|
|
525
564
|
operationId: "subscription.pay",
|
|
526
565
|
invalidates: SUBSCRIPTION,
|
|
527
|
-
scope:
|
|
566
|
+
scope: CachePolicyScope.Account
|
|
528
567
|
},
|
|
529
568
|
{
|
|
530
569
|
operationId: "subscription.setAutoRenewal",
|
|
531
570
|
invalidates: SUBSCRIPTION,
|
|
532
|
-
scope:
|
|
571
|
+
scope: CachePolicyScope.Account
|
|
533
572
|
},
|
|
534
573
|
{
|
|
535
574
|
operationId: "subscription.bindCard",
|
|
536
575
|
invalidates: SUBSCRIPTION,
|
|
537
|
-
scope:
|
|
576
|
+
scope: CachePolicyScope.Account
|
|
538
577
|
},
|
|
539
578
|
{
|
|
540
579
|
operationId: "subscription.setDefaultMethod",
|
|
541
580
|
invalidates: SUBSCRIPTION,
|
|
542
|
-
scope:
|
|
581
|
+
scope: CachePolicyScope.Account
|
|
543
582
|
},
|
|
544
583
|
{
|
|
545
584
|
operationId: "subscription.removeMethod",
|
|
546
585
|
invalidates: SUBSCRIPTION,
|
|
547
|
-
scope:
|
|
586
|
+
scope: CachePolicyScope.Account
|
|
548
587
|
},
|
|
549
588
|
{
|
|
550
589
|
operationId: "reports.create",
|
|
@@ -590,7 +629,7 @@ function resolveOptions(options) {
|
|
|
590
629
|
if (!Array.isArray(options.operations) || options.operations.length === 0) throw new CacheError("cache.operations должен содержать хотя бы одну операцию");
|
|
591
630
|
const operations = /* @__PURE__ */ new Set();
|
|
592
631
|
for (const operation of options.operations) {
|
|
593
|
-
if (typeof operation !== "string" || !isCacheOperationId(operation)) throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);
|
|
632
|
+
if (typeof operation !== "string" || isBuiltInOperationId(operation) && !isCacheOperationId(operation)) throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);
|
|
594
633
|
operations.add(operation);
|
|
595
634
|
}
|
|
596
635
|
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
@@ -622,7 +661,7 @@ function cacheMode(request) {
|
|
|
622
661
|
if (!CACHE_MODES.has(mode)) throw new CacheError(`cache должен быть '${CacheModes.Default}', '${CacheModes.Reload}' или '${CacheModes.NoStore}', получено: ${String(mode)}`);
|
|
623
662
|
return mode;
|
|
624
663
|
}
|
|
625
|
-
/** Создаёт TTL/LRU-кэш
|
|
664
|
+
/** Создаёт TTL/LRU-кэш нормализованных результатов itd-api. */
|
|
626
665
|
function cache(options) {
|
|
627
666
|
const config = resolveOptions(options);
|
|
628
667
|
const values = new LRUCache({
|
|
@@ -648,9 +687,10 @@ function cache(options) {
|
|
|
648
687
|
if (operations.length === 0) return;
|
|
649
688
|
const selected = /* @__PURE__ */ new Set();
|
|
650
689
|
for (const operation of operations) {
|
|
651
|
-
if (
|
|
652
|
-
|
|
653
|
-
|
|
690
|
+
if (typeof operation !== "string") throw new CacheError(`Неизвестная операция кэша: ${JSON.stringify(operation)}`);
|
|
691
|
+
const operationId = operation;
|
|
692
|
+
selected.add(operationId);
|
|
693
|
+
operationGenerations.set(operationId, (operationGenerations.get(operationId) ?? 0) + 1);
|
|
654
694
|
}
|
|
655
695
|
for (const [key, entry] of values.entries()) if (selected.has(entry.operation)) values.delete(key);
|
|
656
696
|
for (const [key, entry] of pending) if (selected.has(entry.operation)) pending.delete(key);
|
|
@@ -671,11 +711,24 @@ function cache(options) {
|
|
|
671
711
|
for (const [key, entry] of pending) if (entry.accountScope === accountScope && selected.has(entry.operation)) pending.delete(key);
|
|
672
712
|
};
|
|
673
713
|
const applyMutation = (accountScope, mutation) => {
|
|
674
|
-
if (mutation.invalidates ===
|
|
675
|
-
else if (mutation.scope ===
|
|
714
|
+
if (mutation.invalidates === CacheInvalidation.All) clearScope(accountScope);
|
|
715
|
+
else if (mutation.scope === CachePolicyScope.Account) invalidateScope(accountScope, mutation.invalidates);
|
|
676
716
|
else invalidate(...mutation.invalidates);
|
|
677
717
|
};
|
|
678
|
-
const
|
|
718
|
+
const invalidateNotificationStream = (stream, ...operations) => {
|
|
719
|
+
const identity = stream.getAuthIdentity();
|
|
720
|
+
const streamBaseUrl = stream.baseUrl;
|
|
721
|
+
const legacyScope = stream.getAuthScope();
|
|
722
|
+
const accountScope = identity?.userId ? JSON.stringify([streamBaseUrl, identity.userId]) : legacyScope !== void 0 ? JSON.stringify([streamBaseUrl, legacyScope]) : void 0;
|
|
723
|
+
if (accountScope === void 0) invalidate(...operations);
|
|
724
|
+
else invalidateScope(accountScope, operations);
|
|
725
|
+
};
|
|
726
|
+
const notificationMiddleware = async (context, next) => {
|
|
727
|
+
if (context.update.type === NotificationUpdateType.Notification) invalidateNotificationStream(context.stream, "notifications.list", "notifications.count");
|
|
728
|
+
else if (context.update.type === NotificationUpdateType.UnreadCount) invalidateNotificationStream(context.stream, "notifications.count");
|
|
729
|
+
await next();
|
|
730
|
+
};
|
|
731
|
+
const createTransformer = (installation, baseUrl, getAuthIdentity, getAuthScope, getOperation) => {
|
|
679
732
|
const fallbackAuthScope = JSON.stringify([baseUrl, `installation:${installation}`]);
|
|
680
733
|
const resolveIdentity = async () => {
|
|
681
734
|
const identity = await getAuthIdentity?.();
|
|
@@ -690,16 +743,24 @@ function cache(options) {
|
|
|
690
743
|
};
|
|
691
744
|
};
|
|
692
745
|
return async (request, next) => {
|
|
693
|
-
const
|
|
746
|
+
const policy = getOperation(request.operationId)?.annotations?.cache;
|
|
747
|
+
const operation = cacheOperation(request.operationId) ?? (policy?.kind === CachePolicyKind.Query ? {
|
|
748
|
+
id: request.operationId,
|
|
749
|
+
category: request.operationId.split(".", 1)[0] ?? "feature"
|
|
750
|
+
} : void 0);
|
|
694
751
|
const method = request.method.toUpperCase();
|
|
695
|
-
if (!(operation !== void 0 || method === "GET" || method === "HEAD")) {
|
|
696
|
-
const mutation = cacheMutation(request.operationId)
|
|
752
|
+
if (!(policy !== void 0 ? policy.kind === CachePolicyKind.Query : operation !== void 0 || method === "GET" || method === "HEAD")) {
|
|
753
|
+
const mutation = cacheMutation(request.operationId) ?? (policy?.kind === CachePolicyKind.Mutation ? {
|
|
754
|
+
operationId: request.operationId,
|
|
755
|
+
invalidates: policy.invalidates,
|
|
756
|
+
...policy.scope === void 0 ? {} : { scope: policy.scope }
|
|
757
|
+
} : void 0);
|
|
697
758
|
const startedIdentity = mutation ? await resolveIdentity() : void 0;
|
|
698
759
|
const result = await next(request);
|
|
699
760
|
if (mutation && startedIdentity) {
|
|
700
761
|
applyMutation(startedIdentity.accountScope, mutation);
|
|
701
762
|
const currentIdentity = await resolveIdentity();
|
|
702
|
-
if (currentIdentity.accountScope !== startedIdentity.accountScope && (mutation.invalidates ===
|
|
763
|
+
if (currentIdentity.accountScope !== startedIdentity.accountScope && (mutation.invalidates === CacheInvalidation.All || mutation.scope === CachePolicyScope.Account)) applyMutation(currentIdentity.accountScope, mutation);
|
|
703
764
|
} else clear();
|
|
704
765
|
return result;
|
|
705
766
|
}
|
|
@@ -709,7 +770,7 @@ function cache(options) {
|
|
|
709
770
|
const unscopedKey = buildCacheKey(operation.id, request);
|
|
710
771
|
if (unscopedKey === void 0) return next(request);
|
|
711
772
|
const identity = await resolveIdentity();
|
|
712
|
-
const scope = operation.id === "auth.sessions" ? identity.sessionScope : identity.accountScope;
|
|
773
|
+
const scope = operation.id === "auth.sessions" || policy?.kind === CachePolicyKind.Query && policy.scope === CachePolicyScope.Session ? identity.sessionScope : identity.accountScope;
|
|
713
774
|
const key = JSON.stringify([scope, unscopedKey]);
|
|
714
775
|
if (mode === CacheModes.Reload) {
|
|
715
776
|
const state = keyStates.get(key) ?? {
|
|
@@ -748,7 +809,7 @@ function cache(options) {
|
|
|
748
809
|
const result = await next(request);
|
|
749
810
|
const stored = cloneValue(result);
|
|
750
811
|
const currentIdentity = await resolveIdentity();
|
|
751
|
-
const currentScope = operation.id === "auth.sessions" ? currentIdentity.sessionScope : currentIdentity.accountScope;
|
|
812
|
+
const currentScope = operation.id === "auth.sessions" || policy?.kind === CachePolicyKind.Query && policy.scope === CachePolicyScope.Session ? currentIdentity.sessionScope : currentIdentity.accountScope;
|
|
752
813
|
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, {
|
|
753
814
|
accountScope: identity.accountScope,
|
|
754
815
|
operation: operation.id,
|
|
@@ -785,31 +846,18 @@ function cache(options) {
|
|
|
785
846
|
},
|
|
786
847
|
clear,
|
|
787
848
|
invalidate,
|
|
788
|
-
|
|
789
|
-
if (!stream || typeof stream.
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const streamBaseUrl = typeof stream.baseUrl === "string" && stream.baseUrl.length > 0 ? stream.baseUrl : void 0;
|
|
793
|
-
const legacyScope = typeof stream.getAuthScope === "function" ? stream.getAuthScope() : void 0;
|
|
794
|
-
const accountScope = identity?.userId && streamBaseUrl ? JSON.stringify([streamBaseUrl, identity.userId]) : legacyScope !== void 0 && streamBaseUrl ? JSON.stringify([streamBaseUrl, legacyScope]) : void 0;
|
|
795
|
-
if (accountScope === void 0) invalidate(...operations);
|
|
796
|
-
else invalidateScope(accountScope, operations);
|
|
797
|
-
};
|
|
798
|
-
invalidateStream("notifications.list", "notifications.count");
|
|
799
|
-
const offNotification = stream.on("notification", () => invalidateStream("notifications.list", "notifications.count"));
|
|
800
|
-
const offUnreadCount = stream.on("unreadCount", () => invalidateStream("notifications.count"));
|
|
801
|
-
return () => {
|
|
802
|
-
offNotification();
|
|
803
|
-
offUnreadCount();
|
|
804
|
-
};
|
|
849
|
+
attachNotificationEvents(stream) {
|
|
850
|
+
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");
|
|
851
|
+
invalidateNotificationStream(stream, "notifications.list", "notifications.count");
|
|
852
|
+
return stream.use(notificationMiddleware);
|
|
805
853
|
},
|
|
806
854
|
install({ operations, baseUrl, getAuthIdentity, getAuthScope }) {
|
|
807
855
|
installationSequence += 1;
|
|
808
|
-
operations.use(createTransformer(installationSequence, baseUrl, getAuthIdentity, getAuthScope));
|
|
856
|
+
operations.use(createTransformer(installationSequence, baseUrl, getAuthIdentity, getAuthScope, operations.get));
|
|
809
857
|
}
|
|
810
858
|
};
|
|
811
859
|
}
|
|
812
860
|
//#endregion
|
|
813
|
-
export { CACHE_OPERATIONS, CacheError, CacheModes, buildCacheKey, cache, cacheOperation, isCacheOperationId };
|
|
861
|
+
export { CACHE_OPERATIONS, CacheError, CacheInvalidation, CacheModes, CachePolicyKind, CachePolicyScope, buildCacheKey, cache, cacheOperation, isCacheOperationId };
|
|
814
862
|
|
|
815
863
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"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.js","names":[],"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\n { id: 'shop.products.list', category: 'shop' },\n { id: 'shop.products.get', category: 'shop' },\n { id: 'shop.delivery.countries', category: 'shop' },\n { id: 'shop.delivery.cities', category: 'shop' },\n { id: 'shop.delivery.points', category: 'shop' },\n { id: 'shop.delivery.calculate', category: 'shop' },\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itd-api/cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Кэширование и дедупликация запросов для itd-api",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"lru-cache": "^11.5.2"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"itd-api": ">=0.
|
|
66
|
+
"itd-api": ">=0.9.0 <1.0.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependenciesMeta": {
|
|
69
69
|
"itd-api": {
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
75
|
-
"@types/node": "^26.
|
|
76
|
-
"publint": "^0.3.
|
|
75
|
+
"@types/node": "^26.2.0",
|
|
76
|
+
"publint": "^0.3.23",
|
|
77
77
|
"tsdown": "^0.22.14",
|
|
78
78
|
"typescript": "^5.9.3",
|
|
79
79
|
"vitest": "^4.1.10"
|