@itd-api/cache 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -14
- package/dist/index.cjs +104 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +104 -60
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -70,9 +70,10 @@ await itd.posts.get(postId, { cache: 'no-store' });
|
|
|
70
70
|
|
|
71
71
|
После успешной мутации плагин удаляет связанные читающие маршруты. Например, реакция на
|
|
72
72
|
пост сбрасывает кэш постов и статистики, но не затрагивает профили, файлы и настройки
|
|
73
|
-
платформы. Маршруты с общими данными инвалидируются во всех
|
|
74
|
-
поста одним аккаунтом должно быть видно остальным. Персональные
|
|
75
|
-
|
|
73
|
+
платформы. Маршруты с общими данными инвалидируются во всех аккаунтах экземпляра: изменение
|
|
74
|
+
поста одним аккаунтом должно быть видно остальным. Персональные настройки и уведомления
|
|
75
|
+
инвалидируются у всех копий клиента с тем же пользователем. Изменение сессий сбрасывает все
|
|
76
|
+
варианты `auth.sessions` этого аккаунта, включая варианты других сессий.
|
|
76
77
|
|
|
77
78
|
Известные запросы без зависимостей, включая telemetry и создание жалобы, кэш не меняют.
|
|
78
79
|
|
|
@@ -102,9 +103,13 @@ detachCache();
|
|
|
102
103
|
stream.disconnect();
|
|
103
104
|
```
|
|
104
105
|
|
|
105
|
-
Привязка сразу очищает `notifications.list` и `notifications.count
|
|
106
|
-
сбрасывает оба
|
|
107
|
-
|
|
106
|
+
Привязка сразу очищает `notifications.list` и `notifications.count` аккаунта, который
|
|
107
|
+
создал поток. Новое уведомление сбрасывает оба маршрута во всех копиях клиента с тем же
|
|
108
|
+
пользователем, событие `unreadCount` — их счётчик. Кэш других аккаунтов и остальные маршруты
|
|
109
|
+
поток не изменяет.
|
|
110
|
+
|
|
111
|
+
У стороннего realtime-объекта без доступных идентификатора пользователя и базового URL
|
|
112
|
+
используется безопасный fallback: маршруты уведомлений инвалидируются во всех аккаунтах.
|
|
108
113
|
|
|
109
114
|
## Несколько клиентов
|
|
110
115
|
|
|
@@ -125,21 +130,26 @@ clientB.use(shared);
|
|
|
125
130
|
// либо accounts.use(shared)
|
|
126
131
|
```
|
|
127
132
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
Копии клиента с одинаковыми базовым URL и пользователем используют общий раздел: готовые
|
|
134
|
+
ответы и одновременные запросы между ними объединяются. Это безопасно для персонализированных
|
|
135
|
+
полей `isLiked`, `isFollowing` и `isReposted`, потому что их значения принадлежат аккаунту,
|
|
136
|
+
а не конкретному access token.
|
|
137
|
+
|
|
138
|
+
Разные пользователи изолированы. Только `auth.sessions` дополнительно разделяется по сессии,
|
|
139
|
+
поскольку ответ отмечает текущую серверную сессию. Если идентификаторов пользователя или
|
|
140
|
+
сессии в токене нет, плагин использует безопасный уникальный раздел установки и ничего между
|
|
141
|
+
копиями не объединяет.
|
|
132
142
|
|
|
133
|
-
`maxEntries` ограничивает весь экземпляр `shared`, а `clear()
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
`maxEntries` ограничивает весь экземпляр `shared`, а `clear()` и `invalidate()` управляют
|
|
144
|
+
всеми его разделами. `attachRealtime()` затрагивает аккаунт создавшего поток клиента.
|
|
145
|
+
Смена сессии сохраняет общий кэш, смена пользователя автоматически выбирает другой раздел.
|
|
136
146
|
|
|
137
147
|
## Ключ
|
|
138
148
|
|
|
139
149
|
В ключ входят:
|
|
140
150
|
|
|
141
151
|
- имя маршрута, HTTP-метод и path;
|
|
142
|
-
-
|
|
152
|
+
- базовый URL и идентификатор пользователя; для `auth.sessions` также идентификатор сессии;
|
|
143
153
|
- `service` или разовый `baseUrl`;
|
|
144
154
|
- query и JSON-body;
|
|
145
155
|
- режим `raw`, `skipAuth` и опции других плагинов, влияющие на ответ.
|
package/dist/index.cjs
CHANGED
|
@@ -174,7 +174,7 @@ var CACHE_MUTATIONS = Object.freeze([
|
|
|
174
174
|
method: "DELETE",
|
|
175
175
|
path: /^\/api\/v1\/auth\/sessions(?:\/[^/]+)?$/,
|
|
176
176
|
invalidates: ["auth.sessions"],
|
|
177
|
-
scope: "
|
|
177
|
+
scope: "account"
|
|
178
178
|
},
|
|
179
179
|
// Посты и комментарии.
|
|
180
180
|
{ method: "POST", path: /^\/api\/posts$/, invalidates: POST_CONTENT },
|
|
@@ -216,13 +216,13 @@ var CACHE_MUTATIONS = Object.freeze([
|
|
|
216
216
|
method: "POST",
|
|
217
217
|
path: /^\/api\/notifications\/(?:[^/]+\/read|read-batch|read-all)$/,
|
|
218
218
|
invalidates: NOTIFICATIONS,
|
|
219
|
-
scope: "
|
|
219
|
+
scope: "account"
|
|
220
220
|
},
|
|
221
221
|
{
|
|
222
222
|
method: "PUT",
|
|
223
223
|
path: /^\/api\/notifications\/settings$/,
|
|
224
224
|
invalidates: ["notifications.getSettings"],
|
|
225
|
-
scope: "
|
|
225
|
+
scope: "account"
|
|
226
226
|
},
|
|
227
227
|
// Файлы и настройки аккаунта.
|
|
228
228
|
{ method: "POST", path: /^\/api\/files\/upload$/, invalidates: NOTHING },
|
|
@@ -235,37 +235,37 @@ var CACHE_MUTATIONS = Object.freeze([
|
|
|
235
235
|
method: "POST",
|
|
236
236
|
path: /^\/api\/verification\/submit$/,
|
|
237
237
|
invalidates: ["verification.status"],
|
|
238
|
-
scope: "
|
|
238
|
+
scope: "account"
|
|
239
239
|
},
|
|
240
240
|
{
|
|
241
241
|
method: "POST",
|
|
242
242
|
path: /^\/api\/v1\/subscription\/pay$/,
|
|
243
243
|
invalidates: SUBSCRIPTION,
|
|
244
|
-
scope: "
|
|
244
|
+
scope: "account"
|
|
245
245
|
},
|
|
246
246
|
{
|
|
247
247
|
method: "POST",
|
|
248
248
|
path: /^\/api\/v1\/subscription\/auto-renewal$/,
|
|
249
249
|
invalidates: SUBSCRIPTION,
|
|
250
|
-
scope: "
|
|
250
|
+
scope: "account"
|
|
251
251
|
},
|
|
252
252
|
{
|
|
253
253
|
method: "POST",
|
|
254
254
|
path: /^\/api\/v1\/subscription\/bind-card$/,
|
|
255
255
|
invalidates: SUBSCRIPTION,
|
|
256
|
-
scope: "
|
|
256
|
+
scope: "account"
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
259
|
method: "POST",
|
|
260
260
|
path: /^\/api\/v1\/subscription\/methods\/[^/]+\/default$/,
|
|
261
261
|
invalidates: SUBSCRIPTION,
|
|
262
|
-
scope: "
|
|
262
|
+
scope: "account"
|
|
263
263
|
},
|
|
264
264
|
{
|
|
265
265
|
method: "DELETE",
|
|
266
266
|
path: /^\/api\/v1\/subscription\/methods\/[^/]+$/,
|
|
267
267
|
invalidates: SUBSCRIPTION,
|
|
268
|
-
scope: "
|
|
268
|
+
scope: "account"
|
|
269
269
|
},
|
|
270
270
|
// Эти запросы не меняют ни один доступный для кэширования ответ.
|
|
271
271
|
{ method: "POST", path: /^\/api\/reports$/, invalidates: NOTHING },
|
|
@@ -441,8 +441,16 @@ function cacheRoute(method, path) {
|
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
// src/plugin.ts
|
|
444
|
+
var CacheModes = Object.freeze({
|
|
445
|
+
/** Отдать свежий кэш, иначе выполнить запрос и сохранить ответ. */
|
|
446
|
+
Default: "default",
|
|
447
|
+
/** Пропустить сохранённое значение, выполнить запрос и перезаписать кэш. */
|
|
448
|
+
Reload: "reload",
|
|
449
|
+
/** Не читать и не писать кэш для этого запроса. */
|
|
450
|
+
NoStore: "no-store"
|
|
451
|
+
});
|
|
444
452
|
var DEFAULT_MAX_ENTRIES = 500;
|
|
445
|
-
var CACHE_MODES =
|
|
453
|
+
var CACHE_MODES = new Set(Object.values(CacheModes));
|
|
446
454
|
function assertPositive(value, name, integer = false) {
|
|
447
455
|
if (!Number.isFinite(value) || value <= 0 || integer && !Number.isInteger(value)) {
|
|
448
456
|
throw new CacheError(
|
|
@@ -481,10 +489,10 @@ function cloneValue(value) {
|
|
|
481
489
|
}
|
|
482
490
|
}
|
|
483
491
|
function cacheMode(request) {
|
|
484
|
-
const mode = request.cache ??
|
|
492
|
+
const mode = request.cache ?? CacheModes.Default;
|
|
485
493
|
if (!CACHE_MODES.has(mode)) {
|
|
486
494
|
throw new CacheError(
|
|
487
|
-
`cache \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C '
|
|
495
|
+
`cache \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C '${CacheModes.Default}', '${CacheModes.Reload}' \u0438\u043B\u0438 '${CacheModes.NoStore}', \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${String(mode)}`
|
|
488
496
|
);
|
|
489
497
|
}
|
|
490
498
|
return mode;
|
|
@@ -499,12 +507,12 @@ function cache(options) {
|
|
|
499
507
|
});
|
|
500
508
|
const pending = /* @__PURE__ */ new Map();
|
|
501
509
|
const routeGenerations = /* @__PURE__ */ new Map();
|
|
502
|
-
const
|
|
503
|
-
const
|
|
510
|
+
const scopeGenerations = /* @__PURE__ */ new Map();
|
|
511
|
+
const scopeRouteGenerations = /* @__PURE__ */ new Map();
|
|
504
512
|
const keyStates = /* @__PURE__ */ new Map();
|
|
505
513
|
let generation = 0;
|
|
506
514
|
let installationSequence = 0;
|
|
507
|
-
const
|
|
515
|
+
const scopeRouteKey = (accountScope, route) => JSON.stringify([accountScope, route]);
|
|
508
516
|
const clear = () => {
|
|
509
517
|
generation += 1;
|
|
510
518
|
values.clear();
|
|
@@ -527,47 +535,48 @@ function cache(options) {
|
|
|
527
535
|
if (selected.has(entry.route)) pending.delete(key);
|
|
528
536
|
}
|
|
529
537
|
};
|
|
530
|
-
const
|
|
531
|
-
|
|
538
|
+
const clearScope = (accountScope) => {
|
|
539
|
+
scopeGenerations.set(accountScope, (scopeGenerations.get(accountScope) ?? 0) + 1);
|
|
532
540
|
for (const [key, entry] of values.entries()) {
|
|
533
|
-
if (entry.
|
|
541
|
+
if (entry.accountScope === accountScope) values.delete(key);
|
|
534
542
|
}
|
|
535
543
|
for (const [key, entry] of pending) {
|
|
536
|
-
if (entry.
|
|
544
|
+
if (entry.accountScope === accountScope) pending.delete(key);
|
|
537
545
|
}
|
|
538
546
|
};
|
|
539
|
-
const
|
|
547
|
+
const invalidateScope = (accountScope, routes) => {
|
|
540
548
|
if (routes.length === 0) return;
|
|
541
549
|
const selected = new Set(routes);
|
|
542
550
|
for (const route of selected) {
|
|
543
|
-
const key =
|
|
544
|
-
|
|
551
|
+
const key = scopeRouteKey(accountScope, route);
|
|
552
|
+
scopeRouteGenerations.set(key, (scopeRouteGenerations.get(key) ?? 0) + 1);
|
|
545
553
|
}
|
|
546
554
|
for (const [key, entry] of values.entries()) {
|
|
547
|
-
if (entry.
|
|
555
|
+
if (entry.accountScope === accountScope && selected.has(entry.route)) values.delete(key);
|
|
548
556
|
}
|
|
549
557
|
for (const [key, entry] of pending) {
|
|
550
|
-
if (entry.
|
|
558
|
+
if (entry.accountScope === accountScope && selected.has(entry.route)) pending.delete(key);
|
|
551
559
|
}
|
|
552
560
|
};
|
|
553
|
-
const applyMutation = (
|
|
561
|
+
const applyMutation = (accountScope, mutation) => {
|
|
554
562
|
if (mutation.invalidates === "all") {
|
|
555
|
-
|
|
556
|
-
} else if (mutation.scope === "
|
|
557
|
-
|
|
563
|
+
clearScope(accountScope);
|
|
564
|
+
} else if (mutation.scope === "account") {
|
|
565
|
+
invalidateScope(accountScope, mutation.invalidates);
|
|
558
566
|
} else {
|
|
559
567
|
invalidate(...mutation.invalidates);
|
|
560
568
|
}
|
|
561
569
|
};
|
|
562
|
-
const createTransformer = (installation, getAuthScope) => {
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
570
|
+
const createTransformer = (installation, baseUrl, getAuthIdentity, getAuthScope) => {
|
|
571
|
+
const fallbackAuthScope = JSON.stringify([baseUrl, `installation:${installation}`]);
|
|
572
|
+
const resolveIdentity = async () => {
|
|
573
|
+
const identity = await getAuthIdentity?.();
|
|
574
|
+
const accountScope = identity?.userId ? JSON.stringify([baseUrl, identity.userId]) : getAuthScope ? JSON.stringify([baseUrl, getAuthScope()]) : fallbackAuthScope;
|
|
575
|
+
const sessionScope = identity?.userId && identity.sessionId ? JSON.stringify([baseUrl, identity.userId, identity.sessionId]) : JSON.stringify([
|
|
576
|
+
accountScope,
|
|
577
|
+
getAuthScope ? getAuthScope() : `installation:${installation}`
|
|
578
|
+
]);
|
|
579
|
+
return { accountScope, sessionScope };
|
|
571
580
|
};
|
|
572
581
|
return async (rawRequest, next) => {
|
|
573
582
|
const request = rawRequest;
|
|
@@ -575,27 +584,39 @@ function cache(options) {
|
|
|
575
584
|
const method = request.method.toUpperCase();
|
|
576
585
|
const isRead = route !== void 0 || method === "GET" || method === "HEAD";
|
|
577
586
|
if (!isRead) {
|
|
578
|
-
const result = await next(request);
|
|
579
587
|
const mutation = cacheMutation(method, request.path);
|
|
580
|
-
|
|
581
|
-
|
|
588
|
+
const startedIdentity = mutation ? await resolveIdentity() : void 0;
|
|
589
|
+
const result = await next(request);
|
|
590
|
+
if (mutation && startedIdentity) {
|
|
591
|
+
applyMutation(startedIdentity.accountScope, mutation);
|
|
592
|
+
const currentIdentity = await resolveIdentity();
|
|
593
|
+
if (currentIdentity.accountScope !== startedIdentity.accountScope && (mutation.invalidates === "all" || mutation.scope === "account")) {
|
|
594
|
+
applyMutation(currentIdentity.accountScope, mutation);
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
clear();
|
|
598
|
+
}
|
|
582
599
|
return result;
|
|
583
600
|
}
|
|
584
601
|
if (!route || !config.routes.has(route.id)) return next(request);
|
|
585
602
|
const mode = cacheMode(request);
|
|
586
|
-
if (mode ===
|
|
603
|
+
if (mode === CacheModes.NoStore) return next(request);
|
|
587
604
|
const unscopedKey = buildCacheKey(route.id, request);
|
|
588
605
|
if (unscopedKey === void 0) return next(request);
|
|
589
|
-
const
|
|
606
|
+
const identity = await resolveIdentity();
|
|
607
|
+
const scope = route.id === "auth.sessions" ? identity.sessionScope : identity.accountScope;
|
|
590
608
|
const key = JSON.stringify([scope, unscopedKey]);
|
|
591
|
-
if (mode ===
|
|
592
|
-
const state = keyStates.get(key) ?? {
|
|
609
|
+
if (mode === CacheModes.Reload) {
|
|
610
|
+
const state = keyStates.get(key) ?? {
|
|
611
|
+
active: 0,
|
|
612
|
+
generation: 0
|
|
613
|
+
};
|
|
593
614
|
state.generation += 1;
|
|
594
615
|
keyStates.set(key, state);
|
|
595
616
|
values.delete(key);
|
|
596
617
|
pending.delete(key);
|
|
597
618
|
}
|
|
598
|
-
if (mode ===
|
|
619
|
+
if (mode === CacheModes.Default) {
|
|
599
620
|
const hit = values.get(key);
|
|
600
621
|
if (hit) {
|
|
601
622
|
const cloned = cloneValue(hit.value);
|
|
@@ -609,11 +630,14 @@ function cache(options) {
|
|
|
609
630
|
}
|
|
610
631
|
}
|
|
611
632
|
const startedGeneration = generation;
|
|
612
|
-
const
|
|
633
|
+
const startedScopeGeneration = scopeGenerations.get(identity.accountScope) ?? 0;
|
|
613
634
|
const startedRouteGeneration = routeGenerations.get(route.id) ?? 0;
|
|
614
|
-
const scopedRouteKey =
|
|
615
|
-
const
|
|
616
|
-
const keyState = keyStates.get(key) ?? {
|
|
635
|
+
const scopedRouteKey = scopeRouteKey(identity.accountScope, route.id);
|
|
636
|
+
const startedScopeRouteGeneration = scopeRouteGenerations.get(scopedRouteKey) ?? 0;
|
|
637
|
+
const keyState = keyStates.get(key) ?? {
|
|
638
|
+
active: 0,
|
|
639
|
+
generation: 0
|
|
640
|
+
};
|
|
617
641
|
keyState.active += 1;
|
|
618
642
|
keyStates.set(key, keyState);
|
|
619
643
|
const startedKeyGeneration = keyState.generation;
|
|
@@ -621,9 +645,14 @@ function cache(options) {
|
|
|
621
645
|
try {
|
|
622
646
|
const result = await next(request);
|
|
623
647
|
const stored = cloneValue(result);
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
648
|
+
const currentIdentity = await resolveIdentity();
|
|
649
|
+
const currentScope = route.id === "auth.sessions" ? currentIdentity.sessionScope : currentIdentity.accountScope;
|
|
650
|
+
if (stored.cacheable && currentScope === scope && generation === startedGeneration && (scopeGenerations.get(identity.accountScope) ?? 0) === startedScopeGeneration && (routeGenerations.get(route.id) ?? 0) === startedRouteGeneration && (scopeRouteGenerations.get(scopedRouteKey) ?? 0) === startedScopeRouteGeneration && keyState.generation === startedKeyGeneration) {
|
|
651
|
+
values.set(key, {
|
|
652
|
+
accountScope: identity.accountScope,
|
|
653
|
+
route: route.id,
|
|
654
|
+
value: stored.value
|
|
655
|
+
});
|
|
627
656
|
}
|
|
628
657
|
return { cacheable: stored.cacheable, value: result };
|
|
629
658
|
} finally {
|
|
@@ -631,8 +660,12 @@ function cache(options) {
|
|
|
631
660
|
if (keyState.active === 0 && keyStates.get(key) === keyState) keyStates.delete(key);
|
|
632
661
|
}
|
|
633
662
|
})();
|
|
634
|
-
const mayDeduplicate = mode ===
|
|
635
|
-
const entry = {
|
|
663
|
+
const mayDeduplicate = mode === CacheModes.Default && config.deduplicate && request.signal === void 0 && request.timeout === void 0;
|
|
664
|
+
const entry = {
|
|
665
|
+
accountScope: identity.accountScope,
|
|
666
|
+
route: route.id,
|
|
667
|
+
promise: load
|
|
668
|
+
};
|
|
636
669
|
if (mayDeduplicate) pending.set(key, entry);
|
|
637
670
|
try {
|
|
638
671
|
const loaded = await load;
|
|
@@ -655,26 +688,38 @@ function cache(options) {
|
|
|
655
688
|
if (!stream || typeof stream.on !== "function") {
|
|
656
689
|
throw new CacheError("attachRealtime() \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u043F\u043E\u0442\u043E\u043A \u0438\u0437 itd.realtime()");
|
|
657
690
|
}
|
|
658
|
-
|
|
691
|
+
const invalidateStream = (...routes) => {
|
|
692
|
+
const identity = typeof stream.getAuthIdentity === "function" ? stream.getAuthIdentity() : void 0;
|
|
693
|
+
const streamBaseUrl = typeof stream.baseUrl === "string" && stream.baseUrl.length > 0 ? stream.baseUrl : void 0;
|
|
694
|
+
const legacyScope = typeof stream.getAuthScope === "function" ? stream.getAuthScope() : void 0;
|
|
695
|
+
const accountScope = identity?.userId && streamBaseUrl ? JSON.stringify([streamBaseUrl, identity.userId]) : legacyScope !== void 0 && streamBaseUrl ? JSON.stringify([streamBaseUrl, legacyScope]) : void 0;
|
|
696
|
+
if (accountScope === void 0) invalidate(...routes);
|
|
697
|
+
else invalidateScope(accountScope, routes);
|
|
698
|
+
};
|
|
699
|
+
invalidateStream("notifications.list", "notifications.count");
|
|
659
700
|
const offNotification = stream.on(
|
|
660
701
|
"notification",
|
|
661
|
-
() =>
|
|
702
|
+
() => invalidateStream("notifications.list", "notifications.count")
|
|
703
|
+
);
|
|
704
|
+
const offUnreadCount = stream.on(
|
|
705
|
+
"unreadCount",
|
|
706
|
+
() => invalidateStream("notifications.count")
|
|
662
707
|
);
|
|
663
|
-
const offUnreadCount = stream.on("unreadCount", () => invalidate("notifications.count"));
|
|
664
708
|
return () => {
|
|
665
709
|
offNotification();
|
|
666
710
|
offUnreadCount();
|
|
667
711
|
};
|
|
668
712
|
},
|
|
669
|
-
install({ use, getAuthScope }) {
|
|
713
|
+
install({ use, baseUrl, getAuthIdentity, getAuthScope }) {
|
|
670
714
|
installationSequence += 1;
|
|
671
|
-
use(createTransformer(installationSequence, getAuthScope));
|
|
715
|
+
use(createTransformer(installationSequence, baseUrl, getAuthIdentity, getAuthScope));
|
|
672
716
|
}
|
|
673
717
|
};
|
|
674
718
|
}
|
|
675
719
|
|
|
676
720
|
exports.CACHE_ROUTES = CACHE_ROUTES;
|
|
677
721
|
exports.CacheError = CacheError;
|
|
722
|
+
exports.CacheModes = CacheModes;
|
|
678
723
|
exports.buildCacheKey = buildCacheKey;
|
|
679
724
|
exports.cache = cache;
|
|
680
725
|
exports.cacheRoute = cacheRoute;
|