@itd-api/cache 0.0.1

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/dist/index.cjs ADDED
@@ -0,0 +1,683 @@
1
+ 'use strict';
2
+
3
+ var lruCache = require('lru-cache');
4
+
5
+ // src/errors.ts
6
+ var CacheError = class extends Error {
7
+ name = "CacheError";
8
+ };
9
+
10
+ // src/key.ts
11
+ var OMITTED_FIELDS = /* @__PURE__ */ new Set([
12
+ "method",
13
+ "path",
14
+ "service",
15
+ "baseUrl",
16
+ "query",
17
+ "body",
18
+ "headers",
19
+ "raw",
20
+ "skipAuth",
21
+ "signal",
22
+ "timeout",
23
+ "retry",
24
+ "skipQueue",
25
+ "skipAuthRefresh",
26
+ "cache"
27
+ ]);
28
+ function queryKey(query) {
29
+ if (!query) return "";
30
+ const search = new URLSearchParams();
31
+ for (const [key, value] of Object.entries(query)) {
32
+ if (value === void 0 || value === null) continue;
33
+ if (Array.isArray(value)) {
34
+ for (const item of value) search.append(key, String(item));
35
+ } else {
36
+ search.append(key, String(value));
37
+ }
38
+ }
39
+ return search.toString();
40
+ }
41
+ function buildCacheKey(route, request) {
42
+ const extras = {};
43
+ for (const [key, value] of Object.entries(request)) {
44
+ if (!OMITTED_FIELDS.has(key) && value !== void 0) extras[key] = value;
45
+ }
46
+ try {
47
+ return JSON.stringify({
48
+ route,
49
+ method: request.method.toUpperCase(),
50
+ service: request.service ?? null,
51
+ baseUrl: request.baseUrl ?? null,
52
+ path: request.path,
53
+ query: queryKey(request.query),
54
+ body: request.body ?? null,
55
+ raw: request.raw ?? false,
56
+ skipAuth: request.skipAuth ?? null,
57
+ extras
58
+ });
59
+ } catch {
60
+ return void 0;
61
+ }
62
+ }
63
+
64
+ // src/mutations.ts
65
+ var POST_CONTENT = [
66
+ "posts.list",
67
+ "posts.get",
68
+ "posts.byUser",
69
+ "posts.likedByUser",
70
+ "posts.comments",
71
+ "posts.stats",
72
+ "comments.replies",
73
+ "hashtags.search",
74
+ "hashtags.trending",
75
+ "hashtags.posts",
76
+ "search.all",
77
+ "users.me",
78
+ "users.get",
79
+ "users.pins"
80
+ ];
81
+ var POST_REACTIONS = [
82
+ "posts.list",
83
+ "posts.get",
84
+ "posts.byUser",
85
+ "posts.likedByUser",
86
+ "posts.stats",
87
+ "hashtags.posts",
88
+ "search.all"
89
+ ];
90
+ var COMMENTS = [
91
+ "posts.list",
92
+ "posts.get",
93
+ "posts.comments",
94
+ "posts.stats",
95
+ "comments.replies",
96
+ "hashtags.posts",
97
+ "search.all"
98
+ ];
99
+ var PROFILE = [
100
+ "users.me",
101
+ "users.get",
102
+ "users.checkUsername",
103
+ "users.search",
104
+ "users.whoToFollow",
105
+ "users.topClans",
106
+ "users.followers",
107
+ "users.following",
108
+ "users.blocked",
109
+ "users.getPrivacy",
110
+ "users.pins",
111
+ "users.followStatus",
112
+ "posts.list",
113
+ "posts.get",
114
+ "posts.byUser",
115
+ "posts.likedByUser",
116
+ "posts.comments",
117
+ "comments.replies",
118
+ "hashtags.posts",
119
+ "search.all"
120
+ ];
121
+ var FOLLOWING = [
122
+ "users.me",
123
+ "users.get",
124
+ "users.search",
125
+ "users.whoToFollow",
126
+ "users.followers",
127
+ "users.following",
128
+ "users.followStatus",
129
+ "posts.list",
130
+ "posts.byUser",
131
+ "search.all"
132
+ ];
133
+ var BLOCKS = [
134
+ "users.me",
135
+ "users.get",
136
+ "users.search",
137
+ "users.whoToFollow",
138
+ "users.followers",
139
+ "users.following",
140
+ "users.blocked",
141
+ "users.followStatus",
142
+ "posts.list",
143
+ "search.all"
144
+ ];
145
+ var PINS = [
146
+ "users.me",
147
+ "users.get",
148
+ "users.pins",
149
+ "posts.list",
150
+ "posts.get",
151
+ "posts.byUser"
152
+ ];
153
+ var NOTIFICATIONS = [
154
+ "notifications.list",
155
+ "notifications.count"
156
+ ];
157
+ var SUBSCRIPTION = [
158
+ "subscription.status",
159
+ "subscription.methods"
160
+ ];
161
+ var NOTHING = [];
162
+ var CACHE_MUTATIONS = Object.freeze([
163
+ // Авторизация и сессии.
164
+ { method: "POST", path: /^\/api\/v1\/auth\/refresh$/, invalidates: NOTHING },
165
+ { method: "POST", path: /^\/api\/v1\/auth\/resend-otp$/, invalidates: NOTHING },
166
+ { method: "POST", path: /^\/api\/v1\/auth\/forgot-password$/, invalidates: NOTHING },
167
+ { method: "POST", path: /^\/api\/v1\/auth\/sign-up$/, invalidates: "all" },
168
+ { method: "POST", path: /^\/api\/v1\/auth\/sign-in$/, invalidates: "all" },
169
+ { method: "POST", path: /^\/api\/v1\/auth\/verify-otp$/, invalidates: "all" },
170
+ { method: "POST", path: /^\/api\/v1\/auth\/logout$/, invalidates: "all" },
171
+ { method: "POST", path: /^\/api\/v1\/auth\/reset-password$/, invalidates: "all" },
172
+ { method: "POST", path: /^\/api\/v1\/auth\/change-password$/, invalidates: "all" },
173
+ {
174
+ method: "DELETE",
175
+ path: /^\/api\/v1\/auth\/sessions(?:\/[^/]+)?$/,
176
+ invalidates: ["auth.sessions"],
177
+ scope: "installation"
178
+ },
179
+ // Посты и комментарии.
180
+ { method: "POST", path: /^\/api\/posts$/, invalidates: POST_CONTENT },
181
+ { method: "PUT", path: /^\/api\/posts\/[^/]+$/, invalidates: POST_CONTENT },
182
+ { method: "DELETE", path: /^\/api\/posts\/[^/]+$/, invalidates: POST_CONTENT },
183
+ { method: "POST", path: /^\/api\/posts\/[^/]+\/restore$/, invalidates: POST_CONTENT },
184
+ { method: "POST", path: /^\/api\/posts\/[^/]+\/like$/, invalidates: POST_REACTIONS },
185
+ { method: "DELETE", path: /^\/api\/posts\/[^/]+\/like$/, invalidates: POST_REACTIONS },
186
+ { method: "POST", path: /^\/api\/posts\/[^/]+\/repost$/, invalidates: POST_CONTENT },
187
+ { method: "DELETE", path: /^\/api\/posts\/[^/]+\/repost$/, invalidates: POST_CONTENT },
188
+ { method: "POST", path: /^\/api\/posts\/[^/]+\/pin$/, invalidates: PINS },
189
+ { method: "DELETE", path: /^\/api\/posts\/[^/]+\/pin$/, invalidates: PINS },
190
+ { method: "POST", path: /^\/api\/posts\/[^/]+\/poll\/vote$/, invalidates: POST_REACTIONS },
191
+ { method: "POST", path: /^\/api\/posts\/[^/]+\/comments$/, invalidates: COMMENTS },
192
+ { method: "POST", path: /^\/api\/comments\/[^/]+\/replies$/, invalidates: COMMENTS },
193
+ { method: "PATCH", path: /^\/api\/comments\/[^/]+$/, invalidates: COMMENTS },
194
+ { method: "DELETE", path: /^\/api\/comments\/[^/]+$/, invalidates: COMMENTS },
195
+ { method: "POST", path: /^\/api\/comments\/[^/]+\/restore$/, invalidates: COMMENTS },
196
+ { method: "POST", path: /^\/api\/comments\/[^/]+\/like$/, invalidates: COMMENTS },
197
+ { method: "DELETE", path: /^\/api\/comments\/[^/]+\/like$/, invalidates: COMMENTS },
198
+ // Профиль и связи между пользователями.
199
+ { method: "PUT", path: /^\/api\/users\/me$/, invalidates: PROFILE },
200
+ { method: "DELETE", path: /^\/api\/users\/me$/, invalidates: PROFILE },
201
+ { method: "POST", path: /^\/api\/users\/me\/restore$/, invalidates: PROFILE },
202
+ { method: "POST", path: /^\/api\/users\/profile$/, invalidates: PROFILE },
203
+ { method: "POST", path: /^\/api\/users\/[^/]+\/follow$/, invalidates: FOLLOWING },
204
+ { method: "DELETE", path: /^\/api\/users\/[^/]+\/follow$/, invalidates: FOLLOWING },
205
+ { method: "POST", path: /^\/api\/users\/[^/]+\/block$/, invalidates: BLOCKS },
206
+ { method: "DELETE", path: /^\/api\/users\/[^/]+\/block$/, invalidates: BLOCKS },
207
+ {
208
+ method: "PUT",
209
+ path: /^\/api\/users\/me\/privacy$/,
210
+ invalidates: ["users.me", "users.get", "users.getPrivacy"]
211
+ },
212
+ { method: "PUT", path: /^\/api\/users\/me\/pin$/, invalidates: PINS },
213
+ { method: "DELETE", path: /^\/api\/users\/me\/pin$/, invalidates: PINS },
214
+ // Уведомления.
215
+ {
216
+ method: "POST",
217
+ path: /^\/api\/notifications\/(?:[^/]+\/read|read-batch|read-all)$/,
218
+ invalidates: NOTIFICATIONS,
219
+ scope: "installation"
220
+ },
221
+ {
222
+ method: "PUT",
223
+ path: /^\/api\/notifications\/settings$/,
224
+ invalidates: ["notifications.getSettings"],
225
+ scope: "installation"
226
+ },
227
+ // Файлы и настройки аккаунта.
228
+ { method: "POST", path: /^\/api\/files\/upload$/, invalidates: NOTHING },
229
+ {
230
+ method: "DELETE",
231
+ path: /^\/api\/files\/[^/]+$/,
232
+ invalidates: ["files.get", ...POST_CONTENT]
233
+ },
234
+ {
235
+ method: "POST",
236
+ path: /^\/api\/verification\/submit$/,
237
+ invalidates: ["verification.status"],
238
+ scope: "installation"
239
+ },
240
+ {
241
+ method: "POST",
242
+ path: /^\/api\/v1\/subscription\/pay$/,
243
+ invalidates: SUBSCRIPTION,
244
+ scope: "installation"
245
+ },
246
+ {
247
+ method: "POST",
248
+ path: /^\/api\/v1\/subscription\/auto-renewal$/,
249
+ invalidates: SUBSCRIPTION,
250
+ scope: "installation"
251
+ },
252
+ {
253
+ method: "POST",
254
+ path: /^\/api\/v1\/subscription\/bind-card$/,
255
+ invalidates: SUBSCRIPTION,
256
+ scope: "installation"
257
+ },
258
+ {
259
+ method: "POST",
260
+ path: /^\/api\/v1\/subscription\/methods\/[^/]+\/default$/,
261
+ invalidates: SUBSCRIPTION,
262
+ scope: "installation"
263
+ },
264
+ {
265
+ method: "DELETE",
266
+ path: /^\/api\/v1\/subscription\/methods\/[^/]+$/,
267
+ invalidates: SUBSCRIPTION,
268
+ scope: "installation"
269
+ },
270
+ // Эти запросы не меняют ни один доступный для кэширования ответ.
271
+ { method: "POST", path: /^\/api\/reports$/, invalidates: NOTHING },
272
+ { method: "POST", path: /^\/api\/v1\/[ix]$/, invalidates: NOTHING }
273
+ ]);
274
+ function cacheMutation(method, path) {
275
+ const normalized = method.toUpperCase();
276
+ return CACHE_MUTATIONS.find(
277
+ (mutation) => mutation.method === normalized && mutation.path.test(path)
278
+ );
279
+ }
280
+
281
+ // src/routes.ts
282
+ var CACHE_ROUTES = Object.freeze([
283
+ { id: "auth.sessions", category: "auth", method: "GET", path: /^\/api\/v1\/auth\/sessions$/ },
284
+ { id: "users.me", category: "users", method: "GET", path: /^\/api\/users\/me$/ },
285
+ {
286
+ id: "users.checkUsername",
287
+ category: "users",
288
+ method: "GET",
289
+ path: /^\/api\/users\/check-username$/
290
+ },
291
+ { id: "users.search", category: "users", method: "GET", path: /^\/api\/users\/search$/ },
292
+ {
293
+ id: "users.whoToFollow",
294
+ category: "users",
295
+ method: "GET",
296
+ path: /^\/api\/users\/suggestions\/who-to-follow$/
297
+ },
298
+ {
299
+ id: "users.topClans",
300
+ category: "users",
301
+ method: "GET",
302
+ path: /^\/api\/users\/stats\/top-clans$/
303
+ },
304
+ {
305
+ id: "users.followers",
306
+ category: "users",
307
+ method: "GET",
308
+ path: /^\/api\/users\/[^/]+\/followers$/
309
+ },
310
+ {
311
+ id: "users.following",
312
+ category: "users",
313
+ method: "GET",
314
+ path: /^\/api\/users\/[^/]+\/following$/
315
+ },
316
+ {
317
+ id: "users.blocked",
318
+ category: "users",
319
+ method: "GET",
320
+ path: /^\/api\/users\/me\/blocked$/
321
+ },
322
+ {
323
+ id: "users.getPrivacy",
324
+ category: "users",
325
+ method: "GET",
326
+ path: /^\/api\/users\/me\/privacy$/
327
+ },
328
+ { id: "users.pins", category: "users", method: "GET", path: /^\/api\/users\/me\/pins$/ },
329
+ {
330
+ id: "users.followStatus",
331
+ category: "users",
332
+ method: "POST",
333
+ path: /^\/api\/users\/follow-status$/
334
+ },
335
+ { id: "users.get", category: "users", method: "GET", path: /^\/api\/users\/[^/]+$/ },
336
+ { id: "posts.list", category: "posts", method: "GET", path: /^\/api\/posts$/ },
337
+ {
338
+ id: "posts.likedByUser",
339
+ category: "posts",
340
+ method: "GET",
341
+ path: /^\/api\/posts\/user\/[^/]+\/liked$/
342
+ },
343
+ {
344
+ id: "posts.byUser",
345
+ category: "posts",
346
+ method: "GET",
347
+ path: /^\/api\/posts\/user\/[^/]+$/
348
+ },
349
+ {
350
+ id: "posts.comments",
351
+ category: "posts",
352
+ method: "GET",
353
+ path: /^\/api\/posts\/[^/]+\/comments$/
354
+ },
355
+ { id: "posts.stats", category: "posts", method: "POST", path: /^\/api\/posts\/stats$/ },
356
+ { id: "posts.get", category: "posts", method: "GET", path: /^\/api\/posts\/[^/]+$/ },
357
+ {
358
+ id: "comments.replies",
359
+ category: "comments",
360
+ method: "GET",
361
+ path: /^\/api\/comments\/[^/]+\/replies$/
362
+ },
363
+ {
364
+ id: "notifications.list",
365
+ category: "notifications",
366
+ method: "GET",
367
+ path: /^\/api\/notifications\/$/
368
+ },
369
+ {
370
+ id: "notifications.count",
371
+ category: "notifications",
372
+ method: "GET",
373
+ path: /^\/api\/notifications\/count$/
374
+ },
375
+ {
376
+ id: "notifications.getSettings",
377
+ category: "notifications",
378
+ method: "GET",
379
+ path: /^\/api\/notifications\/settings$/
380
+ },
381
+ {
382
+ id: "hashtags.search",
383
+ category: "hashtags",
384
+ method: "GET",
385
+ path: /^\/api\/hashtags$/
386
+ },
387
+ {
388
+ id: "hashtags.trending",
389
+ category: "hashtags",
390
+ method: "GET",
391
+ path: /^\/api\/hashtags\/trending$/
392
+ },
393
+ {
394
+ id: "hashtags.posts",
395
+ category: "hashtags",
396
+ method: "GET",
397
+ path: /^\/api\/hashtags\/[^/]+\/posts$/
398
+ },
399
+ { id: "search.all", category: "search", method: "GET", path: /^\/api\/search$/ },
400
+ { id: "files.get", category: "files", method: "GET", path: /^\/api\/files\/[^/]+$/ },
401
+ {
402
+ id: "subscription.status",
403
+ category: "subscription",
404
+ method: "GET",
405
+ path: /^\/api\/v1\/subscription\/$/
406
+ },
407
+ {
408
+ id: "subscription.methods",
409
+ category: "subscription",
410
+ method: "GET",
411
+ path: /^\/api\/v1\/subscription\/methods$/
412
+ },
413
+ {
414
+ id: "verification.status",
415
+ category: "verification",
416
+ method: "GET",
417
+ path: /^\/api\/verification\/status$/
418
+ },
419
+ {
420
+ id: "platform.changelog",
421
+ category: "platform",
422
+ method: "GET",
423
+ path: /^\/api\/platform\/changelog$/
424
+ },
425
+ {
426
+ id: "platform.announcements",
427
+ category: "platform",
428
+ method: "GET",
429
+ path: /^\/api\/platform\/announcements$/
430
+ },
431
+ { id: "platform.portal", category: "platform", method: "GET", path: /^\/api\/v1\/portal$/ },
432
+ { id: "platform.status", category: "platform", method: "GET", path: /^\/api\/status$/ }
433
+ ]);
434
+ var ROUTE_IDS = new Set(CACHE_ROUTES.map((route) => route.id));
435
+ function isCacheRouteId(value) {
436
+ return ROUTE_IDS.has(value);
437
+ }
438
+ function cacheRoute(method, path) {
439
+ const normalized = method.toUpperCase();
440
+ return CACHE_ROUTES.find((route) => route.method === normalized && route.path.test(path));
441
+ }
442
+
443
+ // src/plugin.ts
444
+ var DEFAULT_MAX_ENTRIES = 500;
445
+ var CACHE_MODES = /* @__PURE__ */ new Set(["default", "reload", "no-store"]);
446
+ function assertPositive(value, name, integer = false) {
447
+ if (!Number.isFinite(value) || value <= 0 || integer && !Number.isInteger(value)) {
448
+ throw new CacheError(
449
+ `${name} \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C ${integer ? "\u0446\u0435\u043B\u044B\u043C " : ""}\u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${value}`
450
+ );
451
+ }
452
+ }
453
+ function resolveOptions(options) {
454
+ if (!options || typeof options !== "object") {
455
+ throw new CacheError("cache() \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u043E\u0431\u044A\u0435\u043A\u0442 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A");
456
+ }
457
+ assertPositive(options.ttl, "cache.ttl");
458
+ if (!Array.isArray(options.routes) || options.routes.length === 0) {
459
+ throw new CacheError("cache.routes \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u043C\u0430\u0440\u0448\u0440\u0443\u0442");
460
+ }
461
+ const routes = /* @__PURE__ */ new Set();
462
+ for (const route of options.routes) {
463
+ if (typeof route !== "string" || !isCacheRouteId(route)) {
464
+ throw new CacheError(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u043A\u044D\u0448\u0430: ${JSON.stringify(route)}`);
465
+ }
466
+ routes.add(route);
467
+ }
468
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
469
+ assertPositive(maxEntries, "cache.maxEntries", true);
470
+ const deduplicate = options.deduplicate ?? true;
471
+ if (typeof deduplicate !== "boolean") {
472
+ throw new CacheError(`cache.deduplicate \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C boolean, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${deduplicate}`);
473
+ }
474
+ return { ttl: options.ttl, routes, maxEntries, deduplicate };
475
+ }
476
+ function cloneValue(value) {
477
+ try {
478
+ return { cacheable: true, value: structuredClone(value) };
479
+ } catch {
480
+ return { cacheable: false, value };
481
+ }
482
+ }
483
+ function cacheMode(request) {
484
+ const mode = request.cache ?? "default";
485
+ if (!CACHE_MODES.has(mode)) {
486
+ throw new CacheError(
487
+ `cache \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C 'default', 'reload' \u0438\u043B\u0438 'no-store', \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${String(mode)}`
488
+ );
489
+ }
490
+ return mode;
491
+ }
492
+ function cache(options) {
493
+ const config = resolveOptions(options);
494
+ const values = new lruCache.LRUCache({
495
+ max: config.maxEntries,
496
+ ttl: config.ttl,
497
+ updateAgeOnGet: false,
498
+ allowStale: false
499
+ });
500
+ const pending = /* @__PURE__ */ new Map();
501
+ const routeGenerations = /* @__PURE__ */ new Map();
502
+ const installationGenerations = /* @__PURE__ */ new Map();
503
+ const installationRouteGenerations = /* @__PURE__ */ new Map();
504
+ const keyStates = /* @__PURE__ */ new Map();
505
+ let generation = 0;
506
+ let installationSequence = 0;
507
+ const installationRouteKey = (installation, route) => `${installation}:${route}`;
508
+ const clear = () => {
509
+ generation += 1;
510
+ values.clear();
511
+ pending.clear();
512
+ };
513
+ const invalidate = (...routes) => {
514
+ if (routes.length === 0) return;
515
+ const selected = /* @__PURE__ */ new Set();
516
+ for (const route of routes) {
517
+ if (!isCacheRouteId(route)) {
518
+ throw new CacheError(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u043A\u044D\u0448\u0430: ${JSON.stringify(route)}`);
519
+ }
520
+ selected.add(route);
521
+ routeGenerations.set(route, (routeGenerations.get(route) ?? 0) + 1);
522
+ }
523
+ for (const [key, entry] of values.entries()) {
524
+ if (selected.has(entry.route)) values.delete(key);
525
+ }
526
+ for (const [key, entry] of pending) {
527
+ if (selected.has(entry.route)) pending.delete(key);
528
+ }
529
+ };
530
+ const clearInstallation = (installation) => {
531
+ installationGenerations.set(installation, (installationGenerations.get(installation) ?? 0) + 1);
532
+ for (const [key, entry] of values.entries()) {
533
+ if (entry.installation === installation) values.delete(key);
534
+ }
535
+ for (const [key, entry] of pending) {
536
+ if (entry.installation === installation) pending.delete(key);
537
+ }
538
+ };
539
+ const invalidateInstallation = (installation, routes) => {
540
+ if (routes.length === 0) return;
541
+ const selected = new Set(routes);
542
+ for (const route of selected) {
543
+ const key = installationRouteKey(installation, route);
544
+ installationRouteGenerations.set(key, (installationRouteGenerations.get(key) ?? 0) + 1);
545
+ }
546
+ for (const [key, entry] of values.entries()) {
547
+ if (entry.installation === installation && selected.has(entry.route)) values.delete(key);
548
+ }
549
+ for (const [key, entry] of pending) {
550
+ if (entry.installation === installation && selected.has(entry.route)) pending.delete(key);
551
+ }
552
+ };
553
+ const applyMutation = (installation, mutation) => {
554
+ if (mutation.invalidates === "all") {
555
+ clearInstallation(installation);
556
+ } else if (mutation.scope === "installation") {
557
+ invalidateInstallation(installation, mutation.invalidates);
558
+ } else {
559
+ invalidate(...mutation.invalidates);
560
+ }
561
+ };
562
+ const createTransformer = (installation, getAuthScope) => {
563
+ let observedAuthScope;
564
+ const resolveScope = () => {
565
+ const authScope = getAuthScope ? getAuthScope() : "default";
566
+ if (observedAuthScope !== void 0 && observedAuthScope !== authScope) {
567
+ clearInstallation(installation);
568
+ }
569
+ observedAuthScope = authScope;
570
+ return JSON.stringify([installation, authScope]);
571
+ };
572
+ return async (rawRequest, next) => {
573
+ const request = rawRequest;
574
+ const route = cacheRoute(request.method, request.path);
575
+ const method = request.method.toUpperCase();
576
+ const isRead = route !== void 0 || method === "GET" || method === "HEAD";
577
+ if (!isRead) {
578
+ const result = await next(request);
579
+ const mutation = cacheMutation(method, request.path);
580
+ if (mutation) applyMutation(installation, mutation);
581
+ else clear();
582
+ return result;
583
+ }
584
+ if (!route || !config.routes.has(route.id)) return next(request);
585
+ const mode = cacheMode(request);
586
+ if (mode === "no-store") return next(request);
587
+ const unscopedKey = buildCacheKey(route.id, request);
588
+ if (unscopedKey === void 0) return next(request);
589
+ const scope = resolveScope();
590
+ const key = JSON.stringify([scope, unscopedKey]);
591
+ if (mode === "reload") {
592
+ const state = keyStates.get(key) ?? { active: 0, generation: 0 };
593
+ state.generation += 1;
594
+ keyStates.set(key, state);
595
+ values.delete(key);
596
+ pending.delete(key);
597
+ }
598
+ if (mode === "default") {
599
+ const hit = values.get(key);
600
+ if (hit) {
601
+ const cloned = cloneValue(hit.value);
602
+ if (cloned.cacheable) return cloned.value;
603
+ values.delete(key);
604
+ }
605
+ const existing = pending.get(key);
606
+ if (config.deduplicate && request.signal === void 0 && request.timeout === void 0 && existing) {
607
+ const loaded = await existing.promise;
608
+ return cloneValue(loaded.value).value;
609
+ }
610
+ }
611
+ const startedGeneration = generation;
612
+ const startedInstallationGeneration = installationGenerations.get(installation) ?? 0;
613
+ const startedRouteGeneration = routeGenerations.get(route.id) ?? 0;
614
+ const scopedRouteKey = installationRouteKey(installation, route.id);
615
+ const startedInstallationRouteGeneration = installationRouteGenerations.get(scopedRouteKey) ?? 0;
616
+ const keyState = keyStates.get(key) ?? { active: 0, generation: 0 };
617
+ keyState.active += 1;
618
+ keyStates.set(key, keyState);
619
+ const startedKeyGeneration = keyState.generation;
620
+ const load = (async () => {
621
+ try {
622
+ const result = await next(request);
623
+ const stored = cloneValue(result);
624
+ const currentScope = resolveScope();
625
+ if (stored.cacheable && currentScope === scope && generation === startedGeneration && (installationGenerations.get(installation) ?? 0) === startedInstallationGeneration && (routeGenerations.get(route.id) ?? 0) === startedRouteGeneration && (installationRouteGenerations.get(scopedRouteKey) ?? 0) === startedInstallationRouteGeneration && keyState.generation === startedKeyGeneration) {
626
+ values.set(key, { installation, route: route.id, value: stored.value });
627
+ }
628
+ return { cacheable: stored.cacheable, value: result };
629
+ } finally {
630
+ keyState.active -= 1;
631
+ if (keyState.active === 0 && keyStates.get(key) === keyState) keyStates.delete(key);
632
+ }
633
+ })();
634
+ const mayDeduplicate = mode === "default" && config.deduplicate && request.signal === void 0 && request.timeout === void 0;
635
+ const entry = { installation, route: route.id, promise: load };
636
+ if (mayDeduplicate) pending.set(key, entry);
637
+ try {
638
+ const loaded = await load;
639
+ return loaded.value;
640
+ } finally {
641
+ if (pending.get(key) === entry) pending.delete(key);
642
+ }
643
+ };
644
+ };
645
+ return {
646
+ name: "cache",
647
+ optionKeys: ["cache"],
648
+ get size() {
649
+ values.purgeStale();
650
+ return values.size;
651
+ },
652
+ clear,
653
+ invalidate,
654
+ attachRealtime(stream) {
655
+ if (!stream || typeof stream.on !== "function") {
656
+ throw new CacheError("attachRealtime() \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u043F\u043E\u0442\u043E\u043A \u0438\u0437 itd.realtime()");
657
+ }
658
+ invalidate("notifications.list", "notifications.count");
659
+ const offNotification = stream.on(
660
+ "notification",
661
+ () => invalidate("notifications.list", "notifications.count")
662
+ );
663
+ const offUnreadCount = stream.on("unreadCount", () => invalidate("notifications.count"));
664
+ return () => {
665
+ offNotification();
666
+ offUnreadCount();
667
+ };
668
+ },
669
+ install({ use, getAuthScope }) {
670
+ installationSequence += 1;
671
+ use(createTransformer(installationSequence, getAuthScope));
672
+ }
673
+ };
674
+ }
675
+
676
+ exports.CACHE_ROUTES = CACHE_ROUTES;
677
+ exports.CacheError = CacheError;
678
+ exports.buildCacheKey = buildCacheKey;
679
+ exports.cache = cache;
680
+ exports.cacheRoute = cacheRoute;
681
+ exports.isCacheRouteId = isCacheRouteId;
682
+ //# sourceMappingURL=index.cjs.map
683
+ //# sourceMappingURL=index.cjs.map