@appweaver/core 1.0.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/LICENSE +1 -0
- package/README.md +7 -0
- package/app/application.d.ts +43 -0
- package/app/application.js +112 -0
- package/app/create-app.d.ts +32 -0
- package/app/create-app.js +38 -0
- package/app/index.d.ts +2 -0
- package/app/index.js +18 -0
- package/app/lifecycle-manager.d.ts +20 -0
- package/app/lifecycle-manager.js +42 -0
- package/app/load-modules.d.ts +10 -0
- package/app/load-modules.js +50 -0
- package/app/load-providers.d.ts +8 -0
- package/app/load-providers.js +33 -0
- package/cache/cache-service.d.ts +72 -0
- package/cache/cache-service.js +202 -0
- package/cache/cache.d.ts +12 -0
- package/cache/cache.js +184 -0
- package/cache/caching.d.ts +8 -0
- package/cache/caching.js +72 -0
- package/cache/eviction/eviction-index.d.ts +35 -0
- package/cache/eviction/eviction-index.js +2 -0
- package/cache/eviction/fifo-eviction-index.d.ts +7 -0
- package/cache/eviction/fifo-eviction-index.js +28 -0
- package/cache/eviction/index.d.ts +4 -0
- package/cache/eviction/index.js +20 -0
- package/cache/eviction/lfu-eviction-index.d.ts +8 -0
- package/cache/eviction/lfu-eviction-index.js +82 -0
- package/cache/eviction/lru-eviction-index.d.ts +8 -0
- package/cache/eviction/lru-eviction-index.js +81 -0
- package/cache/index.d.ts +2 -0
- package/cache/index.js +18 -0
- package/cache/memory-cache.d.ts +4 -0
- package/cache/memory-cache.js +12 -0
- package/cache/redis-cache.d.ts +4 -0
- package/cache/redis-cache.js +12 -0
- package/context/context.d.ts +2 -0
- package/context/context.js +13 -0
- package/context/dependency-injection.d.ts +98 -0
- package/context/dependency-injection.js +276 -0
- package/context/index.d.ts +2 -0
- package/context/index.js +18 -0
- package/database/create-client.d.ts +11 -0
- package/database/create-client.js +142 -0
- package/database/index.d.ts +1 -0
- package/database/index.js +17 -0
- package/database/prisma-database.d.ts +15 -0
- package/database/prisma-database.js +53 -0
- package/errors/error-handler.d.ts +3 -0
- package/errors/error-handler.js +24 -0
- package/errors/error-schema.d.ts +32 -0
- package/errors/error-schema.js +31 -0
- package/errors/http-error.d.ts +21 -0
- package/errors/http-error.js +33 -0
- package/errors/index.d.ts +3 -0
- package/errors/index.js +19 -0
- package/events/node-events.d.ts +7 -0
- package/events/node-events.js +39 -0
- package/export/export-service.d.ts +19 -0
- package/export/export-service.js +154 -0
- package/export/index.d.ts +1 -0
- package/export/index.js +17 -0
- package/factory/create-model.d.ts +2 -0
- package/factory/create-model.js +366 -0
- package/factory/create-policy.d.ts +2 -0
- package/factory/create-policy.js +16 -0
- package/factory/create-routes.d.ts +2 -0
- package/factory/create-routes.js +21 -0
- package/factory/create-service.d.ts +3 -0
- package/factory/create-service.js +97 -0
- package/factory/index.d.ts +4 -0
- package/factory/index.js +20 -0
- package/health/health-routes.d.ts +2 -0
- package/health/health-routes.js +36 -0
- package/health/health-schema.d.ts +45 -0
- package/health/health-schema.js +52 -0
- package/health/health-service.d.ts +38 -0
- package/health/health-service.js +82 -0
- package/health/index.d.ts +2 -0
- package/health/index.js +18 -0
- package/index.d.ts +15 -0
- package/index.js +31 -0
- package/mailer/email-service.d.ts +36 -0
- package/mailer/email-service.js +89 -0
- package/mailer/index.d.ts +1 -0
- package/mailer/index.js +17 -0
- package/mailer/json-mailer.d.ts +7 -0
- package/mailer/json-mailer.js +32 -0
- package/mailer/smtp-mailer.d.ts +7 -0
- package/mailer/smtp-mailer.js +43 -0
- package/memory/in-memory.d.ts +24 -0
- package/memory/in-memory.js +164 -0
- package/memory/redis.d.ts +26 -0
- package/memory/redis.js +158 -0
- package/package.json +68 -0
- package/prisma/client/browser.d.ts +34 -0
- package/prisma/client/browser.js +56 -0
- package/prisma/client/client.d.ts +53 -0
- package/prisma/client/client.js +70 -0
- package/prisma/client/commonInputTypes.d.ts +395 -0
- package/prisma/client/commonInputTypes.js +11 -0
- package/prisma/client/enums.d.ts +1 -0
- package/prisma/client/enums.js +11 -0
- package/prisma/client/internal/class.d.ts +187 -0
- package/prisma/client/internal/class.js +85 -0
- package/prisma/client/internal/prismaNamespace.d.ts +987 -0
- package/prisma/client/internal/prismaNamespace.js +193 -0
- package/prisma/client/internal/prismaNamespaceBrowser.d.ts +125 -0
- package/prisma/client/internal/prismaNamespaceBrowser.js +164 -0
- package/prisma/client/models/ApiKey.d.ts +1167 -0
- package/prisma/client/models/ApiKey.js +2 -0
- package/prisma/client/models/File.d.ts +1248 -0
- package/prisma/client/models/File.js +2 -0
- package/prisma/client/models/OneTimeToken.d.ts +1083 -0
- package/prisma/client/models/OneTimeToken.js +2 -0
- package/prisma/client/models/Permission.d.ts +1209 -0
- package/prisma/client/models/Permission.js +2 -0
- package/prisma/client/models/Role.d.ts +1209 -0
- package/prisma/client/models/Role.js +2 -0
- package/prisma/client/models/Seeder.d.ts +1023 -0
- package/prisma/client/models/Seeder.js +2 -0
- package/prisma/client/models.d.ts +7 -0
- package/prisma/client/models.js +2 -0
- package/queue/bull-queue.d.ts +37 -0
- package/queue/bull-queue.js +207 -0
- package/queue/memory-queue.d.ts +54 -0
- package/queue/memory-queue.js +283 -0
- package/resource/index.d.ts +4 -0
- package/resource/index.js +20 -0
- package/resource/resource-loader.d.ts +26 -0
- package/resource/resource-loader.js +161 -0
- package/resource/resource-routes.d.ts +3 -0
- package/resource/resource-routes.js +174 -0
- package/resource/resource-schema.d.ts +33 -0
- package/resource/resource-schema.js +186 -0
- package/resource/resource-service.d.ts +89 -0
- package/resource/resource-service.js +772 -0
- package/resources.d.ts +9 -0
- package/resources.js +24 -0
- package/scheduler/cron-scheduler.d.ts +12 -0
- package/scheduler/cron-scheduler.js +73 -0
- package/security/account/account-routes.d.ts +2 -0
- package/security/account/account-routes.js +104 -0
- package/security/account/account-schema.d.ts +290 -0
- package/security/account/account-schema.js +166 -0
- package/security/account/account-service.d.ts +88 -0
- package/security/account/account-service.js +218 -0
- package/security/account/index.d.ts +3 -0
- package/security/account/index.js +19 -0
- package/security/api-key/api-key-auth.d.ts +4 -0
- package/security/api-key/api-key-auth.js +66 -0
- package/security/api-key/index.d.ts +1 -0
- package/security/api-key/index.js +17 -0
- package/security/auth-routes.d.ts +2 -0
- package/security/auth-routes.js +80 -0
- package/security/auth-schema.d.ts +215 -0
- package/security/auth-schema.js +129 -0
- package/security/auth-service.d.ts +117 -0
- package/security/auth-service.js +272 -0
- package/security/auth.d.ts +3 -0
- package/security/auth.js +127 -0
- package/security/basic/basic-auth.d.ts +4 -0
- package/security/basic/basic-auth.js +32 -0
- package/security/basic/index.d.ts +1 -0
- package/security/basic/index.js +17 -0
- package/security/create-auth-resources.d.ts +6 -0
- package/security/create-auth-resources.js +129 -0
- package/security/helper.d.ts +127 -0
- package/security/helper.js +329 -0
- package/security/index.d.ts +4 -0
- package/security/index.js +20 -0
- package/security/jwt/index.d.ts +2 -0
- package/security/jwt/index.js +18 -0
- package/security/jwt/jwt-auth.d.ts +4 -0
- package/security/jwt/jwt-auth.js +57 -0
- package/security/jwt/jwt-keys.d.ts +7 -0
- package/security/jwt/jwt-keys.js +43 -0
- package/security/oauth2/create-oauth2-plugin.d.ts +17 -0
- package/security/oauth2/create-oauth2-plugin.js +85 -0
- package/security/oauth2/index.d.ts +3 -0
- package/security/oauth2/index.js +19 -0
- package/security/oauth2/oauth2-custom.d.ts +7 -0
- package/security/oauth2/oauth2-custom.js +40 -0
- package/security/oauth2/oauth2-facebook.d.ts +1 -0
- package/security/oauth2/oauth2-facebook.js +30 -0
- package/security/oauth2/oauth2-google.d.ts +1 -0
- package/security/oauth2/oauth2-google.js +28 -0
- package/security/oauth2/oauth2-schema.d.ts +10 -0
- package/security/oauth2/oauth2-schema.js +53 -0
- package/security/recaptcha/index.d.ts +2 -0
- package/security/recaptcha/index.js +18 -0
- package/security/recaptcha/recaptcha-verify.d.ts +1 -0
- package/security/recaptcha/recaptcha-verify.js +33 -0
- package/security/recaptcha/recaptcha.d.ts +2 -0
- package/security/recaptcha/recaptcha.js +22 -0
- package/security/resources/api-key/model.d.ts +2 -0
- package/security/resources/api-key/model.js +63 -0
- package/security/resources/api-key/policy.d.ts +2 -0
- package/security/resources/api-key/policy.js +26 -0
- package/security/resources/api-key/routes.d.ts +2 -0
- package/security/resources/api-key/routes.js +15 -0
- package/security/resources/api-key/service.d.ts +3 -0
- package/security/resources/api-key/service.js +33 -0
- package/security/resources/one-time-token/model.d.ts +2 -0
- package/security/resources/one-time-token/model.js +32 -0
- package/security/resources/permission/model.d.ts +2 -0
- package/security/resources/permission/model.js +16 -0
- package/security/resources/role/model.d.ts +2 -0
- package/security/resources/role/model.js +22 -0
- package/security/store/database-security-store.d.ts +5 -0
- package/security/store/database-security-store.js +51 -0
- package/security/store/redis-security-store.d.ts +5 -0
- package/security/store/redis-security-store.js +32 -0
- package/seeder/create-seeder.d.ts +21 -0
- package/seeder/create-seeder.js +17 -0
- package/seeder/index.d.ts +2 -0
- package/seeder/index.js +18 -0
- package/seeder/resources/seeder/model.d.ts +2 -0
- package/seeder/resources/seeder/model.js +38 -0
- package/seeder/seeder.d.ts +20 -0
- package/seeder/seeder.js +175 -0
- package/server/create-server.d.ts +20 -0
- package/server/create-server.js +160 -0
- package/server/index.d.ts +4 -0
- package/server/index.js +20 -0
- package/server/info-route.d.ts +2 -0
- package/server/info-route.js +18 -0
- package/server/info-schema.d.ts +37 -0
- package/server/info-schema.js +19 -0
- package/server/register-model.d.ts +2 -0
- package/server/register-model.js +12 -0
- package/server/register-plugin.d.ts +2 -0
- package/server/register-plugin.js +13 -0
- package/server/register-route.d.ts +3 -0
- package/server/register-route.js +74 -0
- package/server/schemas.d.ts +3 -0
- package/server/schemas.js +30 -0
- package/server/swagger.d.ts +3 -0
- package/server/swagger.js +122 -0
- package/storage/file-route.d.ts +2 -0
- package/storage/file-route.js +39 -0
- package/storage/file-schema.d.ts +9 -0
- package/storage/file-schema.js +42 -0
- package/storage/file-service.d.ts +81 -0
- package/storage/file-service.js +384 -0
- package/storage/filesystem-storage.d.ts +10 -0
- package/storage/filesystem-storage.js +128 -0
- package/storage/index.d.ts +2 -0
- package/storage/index.js +18 -0
- package/storage/resources/file/model.d.ts +2 -0
- package/storage/resources/file/model.js +75 -0
- package/types/application-context.d.ts +20 -0
- package/types/application-context.js +2 -0
- package/types/auth.d.ts +31 -0
- package/types/auth.js +2 -0
- package/types/fastify-extensions.d.ts +24 -0
- package/types/fastify-extensions.js +4 -0
- package/types/generated.d.ts +193 -0
- package/types/generated.js +3 -0
- package/types/index.d.ts +4 -0
- package/types/index.js +20 -0
- package/types/server.d.ts +4 -0
- package/types/server.js +2 -0
- package/utils/export-util.d.ts +8 -0
- package/utils/export-util.js +25 -0
- package/utils/file-util.d.ts +64 -0
- package/utils/file-util.js +141 -0
- package/utils/index.d.ts +3 -0
- package/utils/index.js +19 -0
- package/utils/schema-util.d.ts +20 -0
- package/utils/schema-util.js +37 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CacheService = void 0;
|
|
4
|
+
const common_1 = require("@appweaver/common");
|
|
5
|
+
const flatted_1 = require("flatted");
|
|
6
|
+
const context_1 = require("../context");
|
|
7
|
+
class CacheService {
|
|
8
|
+
/** @internal */
|
|
9
|
+
_cache = (0, context_1.inject)(common_1.Cache);
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves the cache instance associated with this service.
|
|
12
|
+
*
|
|
13
|
+
* @return {Cache} The current cache instance.
|
|
14
|
+
*/
|
|
15
|
+
get cache() {
|
|
16
|
+
return this._cache;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves a cached value associated with the specified key.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} key - The key used to look up the cached value.
|
|
22
|
+
* @return A promise that resolves to the cached value if the key exists, or null if the key is not found.
|
|
23
|
+
*/
|
|
24
|
+
async getCachedValue(key) {
|
|
25
|
+
const exists = await this._cache.has(key);
|
|
26
|
+
if (exists) {
|
|
27
|
+
common_1.logger.debug({ key }, 'Retrieved value from cache');
|
|
28
|
+
return this._cache.get(key);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Adds a key-value pair to the cache if the key does not already exist.
|
|
34
|
+
* Optionally allows specifying a time-to-live (TTL) for the cached item.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} key - The unique key to identify the value in the cache.
|
|
37
|
+
* @param {Object} value - The value to store in the cache.
|
|
38
|
+
* @param {number} [ttl] - Optional time-to-live for the cache entry in seconds.
|
|
39
|
+
* @param {boolean} [replace=false] - Optional flag that indicates if the existing cache entry should be replaced
|
|
40
|
+
* with the new value and ttl.
|
|
41
|
+
* @return {Promise<boolean>} A promise that resolves to true if the key-value pair was added, or false if the key
|
|
42
|
+
* already exists.
|
|
43
|
+
*/
|
|
44
|
+
async addToCache(key, value, ttl, replace = false) {
|
|
45
|
+
const exists = await this._cache.has(key);
|
|
46
|
+
if (exists && !replace) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
common_1.logger.debug({ key, ttl, replace }, 'Added value to cache');
|
|
50
|
+
return this._cache.set(key, value, ttl);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Removes a cached value associated with the specified key from the cache store.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} key - The key associated with the cached value to be removed.
|
|
56
|
+
* @return {Promise<boolean>} A promise that resolves to `true` if the value was successfully removed, or
|
|
57
|
+
* `false` otherwise.
|
|
58
|
+
*/
|
|
59
|
+
async removeCachedValue(key) {
|
|
60
|
+
const removed = await this._cache.evict(key);
|
|
61
|
+
if (removed) {
|
|
62
|
+
common_1.logger.debug({ key }, 'Removed value from cache');
|
|
63
|
+
}
|
|
64
|
+
return removed;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Invalidates the cache for the specified model and action type based on the configured cache invalidation strategy.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} modelName - The name of the model whose cache needs to be invalidated.
|
|
70
|
+
* @param {Extract<ActionType, 'create' | 'update' | 'delete' | 'uploadFiles' | 'deleteFiles'>} action - The action type
|
|
71
|
+
* that triggered the cache invalidation (e.g., 'create', 'update', 'delete', 'uploadFiles', or 'fileDelete').
|
|
72
|
+
* @return {Promise<void>} Resolves when the cache invalidation process is completed.
|
|
73
|
+
*/
|
|
74
|
+
async invalidateCache(modelName, action) {
|
|
75
|
+
if (!common_1.config.CACHE_ENABLED) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const strategy = common_1.config.CACHE_INVALIDATION_STRATEGY;
|
|
79
|
+
common_1.logger.debug({ modelName, action, strategy }, `Invalidating resource cache`);
|
|
80
|
+
let invalidation;
|
|
81
|
+
switch (strategy) {
|
|
82
|
+
case common_1.CacheInvalidationStrategy.ExpireRelated:
|
|
83
|
+
invalidation = this._cache.expire(`*!${modelName}!*:inv`);
|
|
84
|
+
break;
|
|
85
|
+
case common_1.CacheInvalidationStrategy.ExpireAll:
|
|
86
|
+
invalidation = this._cache.expire(`*:inv`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (invalidation) {
|
|
90
|
+
if (common_1.config.CACHE_INVALIDATION_DEFERRED) {
|
|
91
|
+
void invalidation.catch((error) => {
|
|
92
|
+
common_1.logger.error(error, 'Cache invalidation error');
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
try {
|
|
97
|
+
await invalidation;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
common_1.logger.error(error, 'Cache invalidation error');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Builds a unique cache key based on the provided input parameters. The key is constructed
|
|
107
|
+
* using properties such as the base key, HTTP method, URL, request body, model name, and any
|
|
108
|
+
* associated relations. It also includes optional user-specific data and cache invalidation settings.
|
|
109
|
+
*
|
|
110
|
+
* @param {Object} data Input data for constructing the cache key.
|
|
111
|
+
* @param {string} data.baseKey The base key that serves as the starting point of the cache key.
|
|
112
|
+
* @param {string} [data.method] The HTTP method (e.g., GET, POST) associated with the key.
|
|
113
|
+
* @param {string} [data.url] The URL associated with the request.
|
|
114
|
+
* @param {string} [data.body] The stringified request body, which will be hashed if present.
|
|
115
|
+
* @param {string} [data.modelName] The name of the model to associate with the cache key.
|
|
116
|
+
* @param {string[]} [data.relations] A list of related model names to associate with the cache key.
|
|
117
|
+
* @param {boolean} [data.skipInvalidation] Whether to skip invalidation logic for this cache key.
|
|
118
|
+
* @param {AuthUser} [data.authUser] The authenticated user information, if applicable, to create user-specific keys.
|
|
119
|
+
* @return {string} The generated cache key string.
|
|
120
|
+
*/
|
|
121
|
+
buildCacheKey(data) {
|
|
122
|
+
const userPrefix = data.authUser ? data.authUser.id : '';
|
|
123
|
+
const bodyHash = data.body ? (0, common_1.makeHash)((0, flatted_1.stringify)(data.body)) : '';
|
|
124
|
+
// Extract model relations based on route cache config so they can be used to
|
|
125
|
+
// evict cache entries when related data changes
|
|
126
|
+
const allRelations = [];
|
|
127
|
+
if (data.relations?.[0] === '*' || data.modelName) {
|
|
128
|
+
if (data.modelName) {
|
|
129
|
+
// Add all relations from a specified model
|
|
130
|
+
const model = (0, context_1.injectModel)(data.modelName, false);
|
|
131
|
+
if (model) {
|
|
132
|
+
allRelations.push(...this.collectAllRelations(model));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Add all defined models as relations
|
|
137
|
+
for (const model of context_1.context.resource.models.values()) {
|
|
138
|
+
for (const relation of Object.values(model.config.relations ?? {})) {
|
|
139
|
+
allRelations.push(relation.model);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (data.relations && data.relations.length > 1) {
|
|
145
|
+
allRelations.push(...data.relations);
|
|
146
|
+
}
|
|
147
|
+
// Create a list of unique relation names
|
|
148
|
+
const relations = [...new Set(allRelations)];
|
|
149
|
+
const relationList = relations.length > 0 ? `!${relations.join('!')}!` : '';
|
|
150
|
+
const invalidateSuffix = data.skipInvalidation ? '' : 'inv';
|
|
151
|
+
return [
|
|
152
|
+
data.baseKey,
|
|
153
|
+
userPrefix,
|
|
154
|
+
data.method,
|
|
155
|
+
data.url,
|
|
156
|
+
bodyHash,
|
|
157
|
+
relationList,
|
|
158
|
+
invalidateSuffix
|
|
159
|
+
]
|
|
160
|
+
.filter((v) => !!v)
|
|
161
|
+
.join(':');
|
|
162
|
+
}
|
|
163
|
+
/** @internal */
|
|
164
|
+
collectAllRelations(model) {
|
|
165
|
+
const relations = [];
|
|
166
|
+
// Add the model itself as a relation
|
|
167
|
+
relations.push(model.name);
|
|
168
|
+
// Traverse top-level relations (using their own output config)
|
|
169
|
+
for (const relation of Object.values(model.config.relations ?? {})) {
|
|
170
|
+
const outputType = relation.output?.type;
|
|
171
|
+
if (outputType === 'none') {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
relations.push(relation.model);
|
|
175
|
+
// Traverse nested includes (if any)
|
|
176
|
+
if (relation.output?.include) {
|
|
177
|
+
this.traverseInclude(relation.output.include, model, relations);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return relations;
|
|
181
|
+
}
|
|
182
|
+
/** @internal */
|
|
183
|
+
traverseInclude(include, model, acc) {
|
|
184
|
+
const relations = model.config.relations ?? {};
|
|
185
|
+
for (const [relKey, includeConfig] of Object.entries(include)) {
|
|
186
|
+
const type = includeConfig.type;
|
|
187
|
+
if (type === 'none') {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const relationDef = relations[relKey];
|
|
191
|
+
if (!relationDef) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
acc.push(relationDef.model);
|
|
195
|
+
// If this include has nested includes, recurse
|
|
196
|
+
if (includeConfig.include) {
|
|
197
|
+
this.traverseInclude(includeConfig.include, model, acc);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.CacheService = CacheService;
|
package/cache/cache.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Cache as CommonCache, Memory } from '@appweaver/common';
|
|
2
|
+
export declare abstract class Cache extends CommonCache {
|
|
3
|
+
private readonly _memory;
|
|
4
|
+
protected constructor(_memory: Memory);
|
|
5
|
+
onInit(): Promise<void>;
|
|
6
|
+
get<T>(key: string): Promise<T | null>;
|
|
7
|
+
set(key: string, value: any, ttl?: number): Promise<boolean>;
|
|
8
|
+
has(key: string): Promise<boolean>;
|
|
9
|
+
evict(key: string): Promise<boolean>;
|
|
10
|
+
expire(pattern?: string): Promise<number>;
|
|
11
|
+
keys(pattern?: string): Promise<string[]>;
|
|
12
|
+
}
|
package/cache/cache.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Cache = void 0;
|
|
4
|
+
const common_1 = require("@appweaver/common");
|
|
5
|
+
const eviction_1 = require("./eviction");
|
|
6
|
+
class Cache extends common_1.Cache {
|
|
7
|
+
_memory;
|
|
8
|
+
/** @internal */
|
|
9
|
+
_entryMeta = new Map();
|
|
10
|
+
/** @internal */
|
|
11
|
+
_evictionIndex = this.createEvictionIndex();
|
|
12
|
+
/** @internal */
|
|
13
|
+
_maxSizeBytes = (0, common_1.textToBytes)(common_1.config.CACHE_MAX_SIZE);
|
|
14
|
+
constructor(_memory) {
|
|
15
|
+
super();
|
|
16
|
+
this._memory = _memory;
|
|
17
|
+
}
|
|
18
|
+
async onInit() {
|
|
19
|
+
if (common_1.config.CACHE_ENABLED && common_1.config.CACHE_CLEAN_START) {
|
|
20
|
+
await this.expire();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async get(key) {
|
|
24
|
+
const prefixedKey = this.addPrefix(key);
|
|
25
|
+
const data = await this._memory.getValue(prefixedKey);
|
|
26
|
+
if (data) {
|
|
27
|
+
const keyMeta = this._entryMeta.get(prefixedKey);
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const count = keyMeta?.usedCount ?? 0;
|
|
30
|
+
const updatedMeta = {
|
|
31
|
+
...(keyMeta || {
|
|
32
|
+
key,
|
|
33
|
+
createdAt: now,
|
|
34
|
+
sizeBytes: await this._memory.valueSizeBytes(prefixedKey)
|
|
35
|
+
}),
|
|
36
|
+
usedCount: count + 1,
|
|
37
|
+
lastUsedAt: now
|
|
38
|
+
};
|
|
39
|
+
this._entryMeta.set(prefixedKey, updatedMeta);
|
|
40
|
+
this._evictionIndex.touch(prefixedKey, updatedMeta);
|
|
41
|
+
}
|
|
42
|
+
else if (prefixedKey in this._entryMeta) {
|
|
43
|
+
// Remove key from meta and index storage if it does not exist memory
|
|
44
|
+
this._entryMeta.delete(prefixedKey);
|
|
45
|
+
this._evictionIndex.remove(prefixedKey);
|
|
46
|
+
}
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
async set(key, value, ttl) {
|
|
50
|
+
const prefixedKey = this.addPrefix(key);
|
|
51
|
+
const expireMs = ttl === 0 ? undefined : (ttl ?? common_1.config.CACHE_DEFAULT_TTL);
|
|
52
|
+
await this.evictExcessEntries();
|
|
53
|
+
const result = await this._memory.putValue(prefixedKey, value, expireMs);
|
|
54
|
+
const sizeBytes = await this._memory.valueSizeBytes(prefixedKey);
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const meta = {
|
|
57
|
+
key,
|
|
58
|
+
sizeBytes,
|
|
59
|
+
usedCount: 0,
|
|
60
|
+
createdAt: now,
|
|
61
|
+
lastUsedAt: now,
|
|
62
|
+
expiresAt: expireMs ? now + expireMs : undefined
|
|
63
|
+
};
|
|
64
|
+
this._entryMeta.set(prefixedKey, meta);
|
|
65
|
+
this._evictionIndex.add(prefixedKey, meta);
|
|
66
|
+
await this.ensureMemoryLimit();
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
async has(key) {
|
|
70
|
+
return this._memory.hasKey(this.addPrefix(key));
|
|
71
|
+
}
|
|
72
|
+
async evict(key) {
|
|
73
|
+
const prefixedKey = this.addPrefix(key);
|
|
74
|
+
const result = await this._memory.removeValue(prefixedKey);
|
|
75
|
+
this._entryMeta.delete(prefixedKey);
|
|
76
|
+
this._evictionIndex.remove(prefixedKey);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
async expire(pattern = '*') {
|
|
80
|
+
const prefixedPattern = this.addPrefix(pattern);
|
|
81
|
+
const prefixedKeys = await this._memory.findKeys(prefixedPattern);
|
|
82
|
+
await this._memory.removeEntries(prefixedPattern);
|
|
83
|
+
for (const prefixedKey of prefixedKeys) {
|
|
84
|
+
this._entryMeta.delete(prefixedKey);
|
|
85
|
+
this._evictionIndex.remove(prefixedKey);
|
|
86
|
+
}
|
|
87
|
+
return prefixedKeys.size;
|
|
88
|
+
}
|
|
89
|
+
async keys(pattern = '*') {
|
|
90
|
+
const prefixedPattern = this.addPrefix(pattern);
|
|
91
|
+
const prefixedKeys = await this._memory.findKeys(prefixedPattern);
|
|
92
|
+
return Array.from(prefixedKeys);
|
|
93
|
+
}
|
|
94
|
+
/** @internal */
|
|
95
|
+
async evictExcessEntries() {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
// Delete expired entries metadata first, underlying memory implementation
|
|
98
|
+
// should have already removed cached values
|
|
99
|
+
for (const [prefixedKey, meta] of this._entryMeta.entries()) {
|
|
100
|
+
if (meta.expiresAt && meta.expiresAt < now) {
|
|
101
|
+
this._entryMeta.delete(prefixedKey);
|
|
102
|
+
this._evictionIndex.remove(prefixedKey);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Calculate the number of entries that should be evicted, plus one is added
|
|
106
|
+
// because this method is called before adding new cache entry
|
|
107
|
+
const excessItemsCount = this._entryMeta.size - common_1.config.CACHE_MAX_ITEMS + 1;
|
|
108
|
+
if (excessItemsCount <= 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const candidateKeys = this._evictionIndex.evictionCandidates(excessItemsCount, now, common_1.config.CACHE_EVICTION_GRACE_PERIOD);
|
|
112
|
+
const evictActions = candidateKeys.map((key) => this.evict(key));
|
|
113
|
+
if (common_1.config.CACHE_EVICTION_DEFERRED) {
|
|
114
|
+
Promise.all(evictActions).catch((error) => {
|
|
115
|
+
common_1.logger.error(error, 'Cache eviction error');
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
await Promise.all(evictActions);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** @internal */
|
|
123
|
+
async ensureMemoryLimit() {
|
|
124
|
+
if (!this._maxSizeBytes) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (common_1.config.CACHE_EVICTION_DEFERRED) {
|
|
128
|
+
this.runEnsureMemoryLimit().catch((error) => {
|
|
129
|
+
common_1.logger.error(error, 'Cache memory limit eviction error');
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await this.runEnsureMemoryLimit();
|
|
134
|
+
}
|
|
135
|
+
/** @internal */
|
|
136
|
+
async runEnsureMemoryLimit() {
|
|
137
|
+
if (!this._maxSizeBytes) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
let currentSizeBytes = [...this._entryMeta.values()]
|
|
142
|
+
.map((e) => e.sizeBytes ?? 0)
|
|
143
|
+
.reduce((a, b) => a + b, 0);
|
|
144
|
+
// Evict candidates incrementally until memory usage falls within the limit
|
|
145
|
+
while (currentSizeBytes > this._maxSizeBytes) {
|
|
146
|
+
const candidateKeys = this._evictionIndex.evictionCandidates(10, now, common_1.config.CACHE_EVICTION_GRACE_PERIOD);
|
|
147
|
+
if (candidateKeys.length === 0) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
const keysToEvict = [];
|
|
151
|
+
for (const candidateKey of candidateKeys) {
|
|
152
|
+
keysToEvict.push(candidateKey);
|
|
153
|
+
currentSizeBytes -= this._entryMeta.get(candidateKey)?.sizeBytes ?? 0;
|
|
154
|
+
// Skip eviction of the rest of candidates when memory usage falls
|
|
155
|
+
// within the configured limit
|
|
156
|
+
if (currentSizeBytes <= this._maxSizeBytes) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const evictActions = keysToEvict.map((key) => this.evict(key));
|
|
161
|
+
await Promise.all(evictActions);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** @internal */
|
|
165
|
+
addPrefix(key) {
|
|
166
|
+
const prefix = common_1.config.CACHE_KEY_PREFIX;
|
|
167
|
+
if (key.startsWith(prefix)) {
|
|
168
|
+
return key;
|
|
169
|
+
}
|
|
170
|
+
return `${prefix}${key}`;
|
|
171
|
+
}
|
|
172
|
+
/** @internal */
|
|
173
|
+
createEvictionIndex() {
|
|
174
|
+
switch (common_1.config.CACHE_EVICTION_STRATEGY) {
|
|
175
|
+
case common_1.CacheEvictionStrategy.LRU:
|
|
176
|
+
return new eviction_1.LruEvictionIndex();
|
|
177
|
+
case common_1.CacheEvictionStrategy.LFU:
|
|
178
|
+
return new eviction_1.LfuEvictionIndex();
|
|
179
|
+
case common_1.CacheEvictionStrategy.FIFO:
|
|
180
|
+
return new eviction_1.FifoEvictionIndex();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.Cache = Cache;
|
package/cache/caching.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
7
|
+
const request_context_1 = require("@fastify/request-context");
|
|
8
|
+
const common_1 = require("@appweaver/common");
|
|
9
|
+
const cache_service_1 = require("./cache-service");
|
|
10
|
+
const context_1 = require("../context");
|
|
11
|
+
exports.default = (0, fastify_plugin_1.default)((server) => {
|
|
12
|
+
const { currentUser } = server;
|
|
13
|
+
const cacheService = (0, context_1.inject)(cache_service_1.CacheService);
|
|
14
|
+
server.addHook('preHandler', async (request, reply) => {
|
|
15
|
+
const config = getRouteConfig(request);
|
|
16
|
+
if (!shouldUseCache(request, config)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const user = config.public ? undefined : currentUser();
|
|
20
|
+
const key = buildCacheKey(request, config, cacheService, user);
|
|
21
|
+
const entry = await cacheService.getCachedValue(key);
|
|
22
|
+
if (entry) {
|
|
23
|
+
request_context_1.requestContext.set('cached', true);
|
|
24
|
+
return reply
|
|
25
|
+
.code(entry.statusCode)
|
|
26
|
+
.headers(entry.headers)
|
|
27
|
+
.send(entry.payload);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
server.addHook('onSend', async (request, reply, payload) => {
|
|
31
|
+
const config = getRouteConfig(request);
|
|
32
|
+
if (!shouldUseCache(request, config)) {
|
|
33
|
+
return payload;
|
|
34
|
+
}
|
|
35
|
+
if (reply.statusCode >= 200 &&
|
|
36
|
+
reply.statusCode < 300 &&
|
|
37
|
+
!request_context_1.requestContext.get('cached')) {
|
|
38
|
+
const user = config.public ? undefined : currentUser();
|
|
39
|
+
const key = buildCacheKey(request, config, cacheService, user);
|
|
40
|
+
await cacheService.addToCache(key, {
|
|
41
|
+
statusCode: reply.statusCode,
|
|
42
|
+
headers: reply.getHeaders(),
|
|
43
|
+
payload
|
|
44
|
+
}, config.cacheTTL);
|
|
45
|
+
}
|
|
46
|
+
return payload;
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
function getRouteConfig(req) {
|
|
50
|
+
return req.routeOptions.config;
|
|
51
|
+
}
|
|
52
|
+
function shouldUseCache(req, config) {
|
|
53
|
+
return !!((req.method === 'GET' || req.method === 'POST') &&
|
|
54
|
+
(config.cacheTTL || config.cacheKey || config.cache) &&
|
|
55
|
+
(config.cacheTTL === undefined || config.cacheTTL > 0) &&
|
|
56
|
+
config.cache !== false);
|
|
57
|
+
}
|
|
58
|
+
function buildCacheKey(req, config, cacheService, user) {
|
|
59
|
+
return (0, common_1.isFunction)(config.cacheKey)
|
|
60
|
+
? config.cacheKey(req, user)
|
|
61
|
+
: config.cacheKey ||
|
|
62
|
+
cacheService.buildCacheKey({
|
|
63
|
+
baseKey: 'req',
|
|
64
|
+
method: req.method,
|
|
65
|
+
url: req.raw.url,
|
|
66
|
+
body: req.body,
|
|
67
|
+
modelName: config.cacheModelName,
|
|
68
|
+
relations: config.cacheRelations,
|
|
69
|
+
skipInvalidation: config.cacheSkipInvalidation,
|
|
70
|
+
authUser: user
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CacheEntryMeta } from '@appweaver/common';
|
|
2
|
+
/**
|
|
3
|
+
* Interface representing operations for managing eviction policies in a cache system.
|
|
4
|
+
*/
|
|
5
|
+
export interface EvictionIndex {
|
|
6
|
+
/**
|
|
7
|
+
* Called when a key-value pair is added to the cache with associated metadata.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} key - The unique identifier for the cache entry.
|
|
10
|
+
* @param {CacheEntryMeta} meta - The metadata associated with the cache entry.
|
|
11
|
+
*/
|
|
12
|
+
add(key: string, meta: CacheEntryMeta): void;
|
|
13
|
+
/**
|
|
14
|
+
* Called when a cache entry is removed from the cache by the specified key.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} key - The unique identifier for the removed entry.
|
|
17
|
+
*/
|
|
18
|
+
remove(key: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Updates the metadata of a cached entry identified by the provided key.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} key - The unique identifier for the accessed cache entry.
|
|
23
|
+
* @param {CacheEntryMeta} meta - The metadata object containing updated information for the cache entry.
|
|
24
|
+
*/
|
|
25
|
+
touch(key: string, meta: CacheEntryMeta): void;
|
|
26
|
+
/**
|
|
27
|
+
* Returns a list of cache keys that meet the eviction criteria.
|
|
28
|
+
*
|
|
29
|
+
* @param {number} count - The maximum number of candidates to evict.
|
|
30
|
+
* @param {number} now - The current timestamp used to determine eligibility for eviction.
|
|
31
|
+
* @param {number} gracePeriod - The allowed time period after which candidates are eligible for eviction.
|
|
32
|
+
* @return {string[]} A list of keys for the cache entries to evict.
|
|
33
|
+
*/
|
|
34
|
+
evictionCandidates(count: number, now: number, gracePeriod: number): string[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CacheEntryMeta } from '@appweaver/common';
|
|
2
|
+
import { EvictionIndex } from './eviction-index';
|
|
3
|
+
export declare class FifoEvictionIndex implements EvictionIndex {
|
|
4
|
+
add(key: string, meta: CacheEntryMeta): void;
|
|
5
|
+
remove(key: string): void;
|
|
6
|
+
touch(_key: string, _meta: CacheEntryMeta): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FifoEvictionIndex = void 0;
|
|
4
|
+
class FifoEvictionIndex {
|
|
5
|
+
/** @internal */
|
|
6
|
+
_keys = new Map();
|
|
7
|
+
add(key, meta) {
|
|
8
|
+
this._keys.set(key, meta);
|
|
9
|
+
}
|
|
10
|
+
remove(key) {
|
|
11
|
+
this._keys.delete(key);
|
|
12
|
+
}
|
|
13
|
+
touch(_key, _meta) {
|
|
14
|
+
// FIFO strategy ignores access recency or frequency
|
|
15
|
+
}
|
|
16
|
+
/** @internal */
|
|
17
|
+
evictionCandidates(count) {
|
|
18
|
+
const candidates = [];
|
|
19
|
+
for (const [key] of this._keys) {
|
|
20
|
+
if (candidates.length >= count) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
candidates.push(key);
|
|
24
|
+
}
|
|
25
|
+
return candidates;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.FifoEvictionIndex = FifoEvictionIndex;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./eviction-index"), exports);
|
|
18
|
+
__exportStar(require("./fifo-eviction-index"), exports);
|
|
19
|
+
__exportStar(require("./lfu-eviction-index"), exports);
|
|
20
|
+
__exportStar(require("./lru-eviction-index"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CacheEntryMeta } from '@appweaver/common';
|
|
2
|
+
import { EvictionIndex } from './eviction-index';
|
|
3
|
+
export declare class LfuEvictionIndex implements EvictionIndex {
|
|
4
|
+
add(key: string, meta: CacheEntryMeta): void;
|
|
5
|
+
remove(key: string): void;
|
|
6
|
+
touch(key: string, meta: CacheEntryMeta): void;
|
|
7
|
+
evictionCandidates(count: number, now: number, gracePeriod: number): string[];
|
|
8
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LfuEvictionIndex = void 0;
|
|
4
|
+
class LfuEvictionIndex {
|
|
5
|
+
/** @internal */
|
|
6
|
+
_entries = new Map();
|
|
7
|
+
add(key, meta) {
|
|
8
|
+
this._entries.set(key, meta);
|
|
9
|
+
}
|
|
10
|
+
remove(key) {
|
|
11
|
+
this._entries.delete(key);
|
|
12
|
+
}
|
|
13
|
+
touch(key, meta) {
|
|
14
|
+
this._entries.set(key, meta);
|
|
15
|
+
}
|
|
16
|
+
evictionCandidates(count, now, gracePeriod) {
|
|
17
|
+
// Build a min-heap of size `count` to find the k entries with the lowest
|
|
18
|
+
// frequency score (usedCount / age), avoiding a full sort.
|
|
19
|
+
// Time complexity: O(n log k) where k = count (typically 1).
|
|
20
|
+
const heap = [];
|
|
21
|
+
for (const [key, meta] of this._entries) {
|
|
22
|
+
if (meta.createdAt + gracePeriod >= now) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const entry = { key, meta };
|
|
26
|
+
if (heap.length < count) {
|
|
27
|
+
heap.push(entry);
|
|
28
|
+
this.siftUp(heap, heap.length - 1, now);
|
|
29
|
+
}
|
|
30
|
+
else if (this.score(meta, now) < this.score(heap[0].meta, now)) {
|
|
31
|
+
heap[0] = entry;
|
|
32
|
+
this.siftDown(heap, 0, now);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Extract candidates — heap contains the `count` lowest-scored entries
|
|
36
|
+
// We use a max-heap so heap[0] is the largest among selected candidates.
|
|
37
|
+
// All entries in the heap are valid eviction candidates.
|
|
38
|
+
return heap.map((e) => e.key);
|
|
39
|
+
}
|
|
40
|
+
/** @internal */
|
|
41
|
+
score(meta, now) {
|
|
42
|
+
const age = now - meta.createdAt;
|
|
43
|
+
return age > 0 ? (meta.usedCount + 1) / age : Infinity;
|
|
44
|
+
}
|
|
45
|
+
// Max-heap by score: the root has the highest score among selected candidates.
|
|
46
|
+
// When a new entry has a lower score than the root, it replaces the root,
|
|
47
|
+
// ensuring the heap always contains the k lowest-scored entries.
|
|
48
|
+
/** @internal */
|
|
49
|
+
siftUp(heap, index, now) {
|
|
50
|
+
while (index > 0) {
|
|
51
|
+
const parent = (index - 1) >> 1;
|
|
52
|
+
if (this.score(heap[index].meta, now) <= this.score(heap[parent].meta, now)) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
[heap[index], heap[parent]] = [heap[parent], heap[index]];
|
|
56
|
+
index = parent;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** @internal */
|
|
60
|
+
siftDown(heap, index, now) {
|
|
61
|
+
const length = heap.length;
|
|
62
|
+
while (true) {
|
|
63
|
+
let largest = index;
|
|
64
|
+
const left = 2 * index + 1;
|
|
65
|
+
const right = 2 * index + 2;
|
|
66
|
+
if (left < length &&
|
|
67
|
+
this.score(heap[left].meta, now) > this.score(heap[largest].meta, now)) {
|
|
68
|
+
largest = left;
|
|
69
|
+
}
|
|
70
|
+
if (right < length &&
|
|
71
|
+
this.score(heap[right].meta, now) > this.score(heap[largest].meta, now)) {
|
|
72
|
+
largest = right;
|
|
73
|
+
}
|
|
74
|
+
if (largest === index) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
[heap[index], heap[largest]] = [heap[largest], heap[index]];
|
|
78
|
+
index = largest;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.LfuEvictionIndex = LfuEvictionIndex;
|