@stigg/node-server-sdk 4.21.1 → 4.22.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/dist/client.js +8 -8
- package/dist/clientInitialization.js +6 -6
- package/dist/configuration.d.ts +2 -2
- package/dist/models.d.ts +1 -1
- package/dist/services/LegacyEventPayloadMapper.d.ts +1 -2
- package/dist/services/LegacyEventPayloadMapper.js +1 -1
- package/dist/services/entitlementDecisionService.d.ts +1 -2
- package/dist/services/entitlementDecisionService.js +5 -5
- package/dist/services/entitlementsService.d.ts +2 -4
- package/dist/services/entitlementsService.js +29 -31
- package/dist/services/eventEmitter.d.ts +1 -1
- package/dist/services/eventEmitterCacheInstrumentation.d.ts +1 -1
- package/dist/services/inMemoryEntitlementsService.d.ts +2 -3
- package/dist/services/inMemoryEntitlementsService.js +4 -5
- package/dist/services/redisEntitlementsService.d.ts +3 -3
- package/dist/services/redisEntitlementsService.js +3 -3
- package/dist/utils/ModelMapper.d.ts +1 -1
- package/dist/utils/ModelMapper.js +22 -24
- package/dist/utils/entitlementFallbackUtils.d.ts +1 -1
- package/dist/utils/entitlementFallbackUtils.js +3 -3
- package/package.json +3 -1
- package/dist/services/ApiCacheMapper.d.ts +0 -15
- package/dist/services/ApiCacheMapper.js +0 -100
- package/dist/services/cache/CacheMapper.d.ts +0 -16
- package/dist/services/cache/CacheMapper.js +0 -3
- package/dist/services/cache/RedisSingleExecutionService/RedisSingleExecution.d.ts +0 -28
- package/dist/services/cache/RedisSingleExecutionService/RedisSingleExecution.js +0 -184
- package/dist/services/cache/RedisSingleExecutionService/RedisSingleExecution.utils.d.ts +0 -22
- package/dist/services/cache/RedisSingleExecutionService/RedisSingleExecution.utils.js +0 -16
- package/dist/services/cache/RedisSingleExecutionService/index.d.ts +0 -1
- package/dist/services/cache/RedisSingleExecutionService/index.js +0 -6
- package/dist/services/cache/accessDeniedReasonMapper.d.ts +0 -3
- package/dist/services/cache/accessDeniedReasonMapper.js +0 -15
- package/dist/services/cache/cacheKeysHelpers.d.ts +0 -9
- package/dist/services/cache/cacheKeysHelpers.js +0 -55
- package/dist/services/cache/cacheService.d.ts +0 -13
- package/dist/services/cache/cacheService.js +0 -3
- package/dist/services/cache/entities/cachedEntitlement.d.ts +0 -20
- package/dist/services/cache/entities/cachedEntitlement.js +0 -53
- package/dist/services/cache/entities/calculatedEntitlement.d.ts +0 -39
- package/dist/services/cache/entities/calculatedEntitlement.js +0 -3
- package/dist/services/cache/entities/entitlementQuery.d.ts +0 -8
- package/dist/services/cache/entities/entitlementQuery.js +0 -3
- package/dist/services/cache/entities/entitlementsMap.d.ts +0 -33
- package/dist/services/cache/entities/entitlementsMap.js +0 -92
- package/dist/services/cache/entities/index.d.ts +0 -6
- package/dist/services/cache/entities/index.js +0 -12
- package/dist/services/cache/entities/usageData.d.ts +0 -11
- package/dist/services/cache/entities/usageData.js +0 -9
- package/dist/services/cache/extractWithDependencies.d.ts +0 -12
- package/dist/services/cache/extractWithDependencies.js +0 -37
- package/dist/services/cache/freshness/EntitlementsFreshener.d.ts +0 -11
- package/dist/services/cache/freshness/EntitlementsFreshener.js +0 -27
- package/dist/services/cache/freshness/transformers/CreditAccessPropagator.d.ts +0 -15
- package/dist/services/cache/freshness/transformers/CreditAccessPropagator.js +0 -67
- package/dist/services/cache/freshness/transformers/UsagePeriodReset.d.ts +0 -11
- package/dist/services/cache/freshness/transformers/UsagePeriodReset.js +0 -45
- package/dist/services/cache/inMemoryCacheService.d.ts +0 -28
- package/dist/services/cache/inMemoryCacheService.js +0 -80
- package/dist/services/cache/index.d.ts +0 -13
- package/dist/services/cache/index.js +0 -38
- package/dist/services/cache/redis/distributedLocks.d.ts +0 -15
- package/dist/services/cache/redis/distributedLocks.js +0 -90
- package/dist/services/cache/redisCacheService.constants.d.ts +0 -6
- package/dist/services/cache/redisCacheService.constants.js +0 -10
- package/dist/services/cache/redisCacheService.d.ts +0 -78
- package/dist/services/cache/redisCacheService.js +0 -519
- package/dist/services/cache/types/cacheInstrumentation.d.ts +0 -37
- package/dist/services/cache/types/cacheInstrumentation.js +0 -3
- package/dist/services/cache/types/cacheResponse.d.ts +0 -26
- package/dist/services/cache/types/cacheResponse.js +0 -19
- package/dist/services/cache/types/cacheServiceParams.d.ts +0 -20
- package/dist/services/cache/types/cacheServiceParams.js +0 -3
- package/dist/services/cache/types/entitlementFeature.d.ts +0 -19
- package/dist/services/cache/types/entitlementFeature.js +0 -3
- package/dist/services/cache/types/entitlementsMapTransformer.d.ts +0 -4
- package/dist/services/cache/types/entitlementsMapTransformer.js +0 -3
- package/dist/services/cache/types/logger.d.ts +0 -6
- package/dist/services/cache/types/logger.js +0 -3
- package/dist/services/cache/types/redisConfiguration.d.ts +0 -39
- package/dist/services/cache/types/redisConfiguration.js +0 -14
- package/dist/types.d.ts +0 -3
- package/dist/types.js +0 -3
- package/dist/utils/calculateUsagePeriod.d.ts +0 -5
- package/dist/utils/calculateUsagePeriod.js +0 -37
- package/dist/utils/dateUtils.d.ts +0 -2
- package/dist/utils/dateUtils.js +0 -13
- package/dist/utils/decorators/ReuseOngoingExecution.d.ts +0 -2
- package/dist/utils/decorators/ReuseOngoingExecution.js +0 -38
- package/dist/utils/featureTypes.d.ts +0 -10
- package/dist/utils/featureTypes.js +0 -24
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { CacheService } from './cacheService';
|
|
2
|
-
import { CacheInstrumentation } from './types/cacheInstrumentation';
|
|
3
|
-
import { CustomerIdentity, SetCustomerParams, GetCustomerEntitlementParams, UpdateUsageParams } from './types/cacheServiceParams';
|
|
4
|
-
import { EntitlementType, EntitlementsMap } from './entities';
|
|
5
|
-
import { CacheMapper } from './CacheMapper';
|
|
6
|
-
import { SlimLogger } from './types/logger';
|
|
7
|
-
import { RedisCacheConfiguration } from './types/redisConfiguration';
|
|
8
|
-
import { RedisSingleExecutionService } from './RedisSingleExecutionService';
|
|
9
|
-
import { EntitlementResponse, EntitlementsResponse } from './types/cacheResponse';
|
|
10
|
-
import { OneOrMany } from '../../types';
|
|
11
|
-
import { AccessDeniedReason } from '@stigg/api-client-js/src/generated/sdk';
|
|
12
|
-
export type CustomerMetadata = {
|
|
13
|
-
accessDeniedReason: AccessDeniedReason | null;
|
|
14
|
-
};
|
|
15
|
-
export declare class RedisCacheService<Input> implements CacheService<Input> {
|
|
16
|
-
private readonly loggerService;
|
|
17
|
-
private readonly cacheInstrumentation;
|
|
18
|
-
private readonly cacheMapper;
|
|
19
|
-
private static readonly DELETE_CHUNK_SIZE;
|
|
20
|
-
private readonly environmentPrefix;
|
|
21
|
-
private readonly ttl;
|
|
22
|
-
private readonly redisClient;
|
|
23
|
-
private readonly distributedLocks;
|
|
24
|
-
private readonly cacheUpdatePolicy;
|
|
25
|
-
private readonly directExecution;
|
|
26
|
-
private readonly freshen;
|
|
27
|
-
readonly distributedRefetchEntitlementsService: RedisSingleExecutionService | undefined;
|
|
28
|
-
constructor(options: RedisCacheConfiguration, loggerService: SlimLogger, cacheInstrumentation: CacheInstrumentation, cacheMapper: CacheMapper<Input>);
|
|
29
|
-
/**
|
|
30
|
-
* Waits for Redis client to be ready for use.
|
|
31
|
-
* Returns immediately if already connected, otherwise waits for 'ready' or 'error' events
|
|
32
|
-
* with a timeout fallback to prevent indefinite waiting.
|
|
33
|
-
*/
|
|
34
|
-
waitForInitialization(): Promise<void>;
|
|
35
|
-
isClientConnected(): boolean;
|
|
36
|
-
updateUsage<T extends EntitlementType>({ customerId, resourceId, entitlementReference, usage, usageTimestamp, }: UpdateUsageParams<T>): Promise<void>;
|
|
37
|
-
private keyExists;
|
|
38
|
-
private getUsageItemToUpdate;
|
|
39
|
-
setCustomer({ customerId, resourceId, entitlements: entitlementsInput, accessDeniedReason, entitlementsTimestamp, }: SetCustomerParams<Input>): Promise<EntitlementsMap>;
|
|
40
|
-
private extractUsagesToUpdate;
|
|
41
|
-
private getUsageTimestamp;
|
|
42
|
-
private getCustomerEntitlementsWithoutUsage;
|
|
43
|
-
private isGlobalCustomerMissingInCache;
|
|
44
|
-
getCustomerEntitlements(params: CustomerIdentity): Promise<EntitlementsResponse>;
|
|
45
|
-
private getCached;
|
|
46
|
-
/**
|
|
47
|
-
* Fetches usage for all trackable entitlements and merges it into the map.
|
|
48
|
-
* Returns null if any trackable entitlement is missing usage (cache miss).
|
|
49
|
-
*/
|
|
50
|
-
private fetchAndMergeUsage;
|
|
51
|
-
private getUsages;
|
|
52
|
-
clearCache(): void | Promise<void>;
|
|
53
|
-
private updateCacheItems;
|
|
54
|
-
private shouldSkipWriting;
|
|
55
|
-
private getKeysLatestTimestamp;
|
|
56
|
-
private parseTimestamp;
|
|
57
|
-
cleanup(): Promise<void>;
|
|
58
|
-
purge(customers: OneOrMany<CustomerIdentity>): Promise<void>;
|
|
59
|
-
getCustomerEntitlement<T extends EntitlementType>(params: GetCustomerEntitlementParams<T>): Promise<EntitlementResponse<T>>;
|
|
60
|
-
private mergeEntitlementWithUsage;
|
|
61
|
-
/**
|
|
62
|
-
* Returns Redis server info for the specified section (e.g., 'memory', 'stats').
|
|
63
|
-
* Throws if the client is not connected.
|
|
64
|
-
*/
|
|
65
|
-
getServerInfo(section: string): Promise<string>;
|
|
66
|
-
/**
|
|
67
|
-
* Migrates old cache format to new format.
|
|
68
|
-
* Handles: featureUsage→usageData rename, adds type discriminator, moves fields,
|
|
69
|
-
* adds updatedAt, adds displayName fallback, nextResetDate→usagePeriodEnd.
|
|
70
|
-
*/
|
|
71
|
-
private migrateOldCacheFormat;
|
|
72
|
-
/**
|
|
73
|
-
* Migrates old usage data format.
|
|
74
|
-
* Handles backward compatibility for old items that have nextResetDate instead of usagePeriodEnd.
|
|
75
|
-
*/
|
|
76
|
-
private migrateUsageData;
|
|
77
|
-
private executeSafely;
|
|
78
|
-
}
|
|
@@ -1,519 +0,0 @@
|
|
|
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
|
-
exports.RedisCacheService = void 0;
|
|
7
|
-
const entities_1 = require("./entities");
|
|
8
|
-
const ioredis_1 = __importDefault(require("ioredis"));
|
|
9
|
-
const lodash_1 = require("lodash");
|
|
10
|
-
const redisConfiguration_1 = require("./types/redisConfiguration");
|
|
11
|
-
const cacheKeysHelpers_1 = require("./cacheKeysHelpers");
|
|
12
|
-
const RedisSingleExecutionService_1 = require("./RedisSingleExecutionService");
|
|
13
|
-
const cacheResponse_1 = require("./types/cacheResponse");
|
|
14
|
-
const extractWithDependencies_1 = require("./extractWithDependencies");
|
|
15
|
-
const EntitlementsFreshener_1 = require("./freshness/EntitlementsFreshener");
|
|
16
|
-
const redisCacheService_constants_1 = require("./redisCacheService.constants");
|
|
17
|
-
const distributedLocks_1 = require("./redis/distributedLocks");
|
|
18
|
-
const accessDeniedReasonMapper_1 = require("./accessDeniedReasonMapper");
|
|
19
|
-
const GRACE_CONNECT_TIMEOUT_MS = 250;
|
|
20
|
-
const READY_STATUSES = ['connect', 'ready'];
|
|
21
|
-
class RedisCacheService {
|
|
22
|
-
constructor(options, loggerService, cacheInstrumentation, cacheMapper) {
|
|
23
|
-
this.loggerService = loggerService;
|
|
24
|
-
this.cacheInstrumentation = cacheInstrumentation;
|
|
25
|
-
this.cacheMapper = cacheMapper;
|
|
26
|
-
this.freshen = new EntitlementsFreshener_1.EntitlementsFreshener();
|
|
27
|
-
const { redis: redisOptions, entitlementsTimeout = redisCacheService_constants_1.DEFAULT_ENTITLEMENTS_TIMEOUT_MS, cacheUpdatePolicy = redisConfiguration_1.CacheUpdatePolicy.UPSERT, lockSettings, directExecution = false, } = options;
|
|
28
|
-
const { environmentPrefix, ttl = redisCacheService_constants_1.DEFAULT_TTL_SECS, distributedEntitlementsFetching = {} } = redisOptions;
|
|
29
|
-
this.redisClient = new ioredis_1.default(redisOptions);
|
|
30
|
-
this.distributedLocks = new distributedLocks_1.DistributedLocks(this.redisClient, loggerService, cacheInstrumentation, lockSettings);
|
|
31
|
-
this.directExecution = directExecution;
|
|
32
|
-
this.cacheUpdatePolicy = cacheUpdatePolicy;
|
|
33
|
-
this.environmentPrefix = environmentPrefix;
|
|
34
|
-
this.ttl = ttl;
|
|
35
|
-
this.redisClient.on('error', (err) => {
|
|
36
|
-
var _a, _b;
|
|
37
|
-
this.loggerService.error('Redis client error: ', err);
|
|
38
|
-
(_b = (_a = this.cacheInstrumentation).trackRedisClientError) === null || _b === void 0 ? void 0 : _b.call(_a, { error: err, clientName: 'redis' });
|
|
39
|
-
});
|
|
40
|
-
this.redisClient.on('connect', () => {
|
|
41
|
-
this.loggerService.log('Redis client connected!');
|
|
42
|
-
});
|
|
43
|
-
this.redisClient.on('close', () => {
|
|
44
|
-
this.loggerService.log('Redis client disconnected!');
|
|
45
|
-
});
|
|
46
|
-
if (!distributedEntitlementsFetching.disabled) {
|
|
47
|
-
const { notificationBufferMs = redisCacheService_constants_1.REFETCH_NOTIFICATION_BUFFER_MS } = distributedEntitlementsFetching;
|
|
48
|
-
const notificationTimeoutMs = entitlementsTimeout + notificationBufferMs;
|
|
49
|
-
this.distributedRefetchEntitlementsService = new RedisSingleExecutionService_1.RedisSingleExecutionService(redisCacheService_constants_1.REFETCH_OPERATION_NAME, this.environmentPrefix, notificationTimeoutMs, this.redisClient, this.distributedLocks, this.loggerService, this.cacheInstrumentation);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Waits for Redis client to be ready for use.
|
|
54
|
-
* Returns immediately if already connected, otherwise waits for 'ready' or 'error' events
|
|
55
|
-
* with a timeout fallback to prevent indefinite waiting.
|
|
56
|
-
*/
|
|
57
|
-
waitForInitialization() {
|
|
58
|
-
return new Promise((resolve) => {
|
|
59
|
-
// for trigger connection for lazyConnect option
|
|
60
|
-
if (this.redisClient.status === 'wait') {
|
|
61
|
-
this.redisClient.connect().catch(() => {
|
|
62
|
-
/* no-op: errors are handled in the 'error' event listener */
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (this.isClientConnected()) {
|
|
66
|
-
resolve();
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
let isResolved = false;
|
|
70
|
-
let timeoutId = undefined;
|
|
71
|
-
const cleanup = () => {
|
|
72
|
-
if (timeoutId) {
|
|
73
|
-
clearTimeout(timeoutId);
|
|
74
|
-
}
|
|
75
|
-
this.redisClient.removeListener('ready', resolvePromise);
|
|
76
|
-
this.redisClient.removeListener('error', resolvePromise);
|
|
77
|
-
};
|
|
78
|
-
const resolvePromise = () => {
|
|
79
|
-
if (!isResolved) {
|
|
80
|
-
isResolved = true;
|
|
81
|
-
cleanup();
|
|
82
|
-
resolve();
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
// Set up timeout for graceful fallback
|
|
86
|
-
timeoutId = setTimeout(() => {
|
|
87
|
-
this.loggerService.log('Redis initialization timeout reached, proceeding anyway');
|
|
88
|
-
resolvePromise();
|
|
89
|
-
}, GRACE_CONNECT_TIMEOUT_MS);
|
|
90
|
-
// Listen for connection events
|
|
91
|
-
this.redisClient.once('ready', resolvePromise);
|
|
92
|
-
this.redisClient.once('error', resolvePromise);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
isClientConnected() {
|
|
96
|
-
return READY_STATUSES.includes(this.redisClient.status);
|
|
97
|
-
}
|
|
98
|
-
async updateUsage({ customerId, resourceId, entitlementReference, usage, usageTimestamp, }) {
|
|
99
|
-
return this.executeSafely('updateUsage', undefined, async () => {
|
|
100
|
-
// Only update usage if the customer's entitlements exist in cache
|
|
101
|
-
const { customerKey } = (0, cacheKeysHelpers_1.buildRedisCustomerKey)(this.environmentPrefix, customerId, resourceId);
|
|
102
|
-
const customerExists = await this.keyExists(customerKey);
|
|
103
|
-
if (!customerExists) {
|
|
104
|
-
this.loggerService.debug(`Skipping usage update - customer entitlements not found in cache`, {
|
|
105
|
-
customerId,
|
|
106
|
-
resourceId,
|
|
107
|
-
entitlementReference,
|
|
108
|
-
});
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const item = this.getUsageItemToUpdate({
|
|
112
|
-
customerId,
|
|
113
|
-
resourceId,
|
|
114
|
-
entitlementReference,
|
|
115
|
-
usage,
|
|
116
|
-
usageTimestamp,
|
|
117
|
-
});
|
|
118
|
-
await this.updateCacheItems([item]);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
async keyExists(key) {
|
|
122
|
-
const result = await this.redisClient.exists(`${key}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`);
|
|
123
|
-
return result === 1;
|
|
124
|
-
}
|
|
125
|
-
getUsageItemToUpdate({ customerId, resourceId, entitlementReference, usage, usageTimestamp, }) {
|
|
126
|
-
const { currentUsage, usagePeriodStart, usagePeriodEnd } = usage;
|
|
127
|
-
const value = {
|
|
128
|
-
currentUsage,
|
|
129
|
-
usagePeriodStart,
|
|
130
|
-
usagePeriodEnd,
|
|
131
|
-
};
|
|
132
|
-
return {
|
|
133
|
-
messageTimestamp: usageTimestamp,
|
|
134
|
-
key: (0, cacheKeysHelpers_1.buildRedisUsageKey)(this.environmentPrefix, customerId, entitlementReference, resourceId),
|
|
135
|
-
value,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
async setCustomer({ customerId, resourceId, entitlements: entitlementsInput, accessDeniedReason, entitlementsTimestamp, }) {
|
|
139
|
-
const { entitlements, usageUpdatedAtMap } = entities_1.EntitlementsMap.fromInput(this.cacheMapper, entitlementsInput);
|
|
140
|
-
const freshenedEntitlements = this.freshen.transform(entitlements);
|
|
141
|
-
return this.executeSafely('setCustomer', freshenedEntitlements, async () => {
|
|
142
|
-
const { customerKey, metadataKey } = (0, cacheKeysHelpers_1.buildRedisCustomerKey)(this.environmentPrefix, customerId, resourceId);
|
|
143
|
-
const lockKey = (0, cacheKeysHelpers_1.buildLockKey)(this.environmentPrefix, customerId, resourceId);
|
|
144
|
-
await this.distributedLocks.using(lockKey, async () => {
|
|
145
|
-
const entitlementsItem = {
|
|
146
|
-
messageTimestamp: new Date(entitlementsTimestamp),
|
|
147
|
-
key: customerKey,
|
|
148
|
-
value: entitlements.toJSON(),
|
|
149
|
-
};
|
|
150
|
-
const metadata = {
|
|
151
|
-
accessDeniedReason: (0, accessDeniedReasonMapper_1.mapAccessDeniedReason)(accessDeniedReason),
|
|
152
|
-
};
|
|
153
|
-
const metadataItem = {
|
|
154
|
-
messageTimestamp: new Date(entitlementsTimestamp),
|
|
155
|
-
key: metadataKey,
|
|
156
|
-
value: metadata,
|
|
157
|
-
};
|
|
158
|
-
const usagesItems = this.extractUsagesToUpdate({
|
|
159
|
-
customerId,
|
|
160
|
-
resourceId,
|
|
161
|
-
entitlements,
|
|
162
|
-
usageUpdatedAtMap,
|
|
163
|
-
});
|
|
164
|
-
await this.updateCacheItems([entitlementsItem, metadataItem, ...usagesItems]);
|
|
165
|
-
});
|
|
166
|
-
return freshenedEntitlements;
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
extractUsagesToUpdate({ customerId, resourceId, entitlements, usageUpdatedAtMap, }) {
|
|
170
|
-
return (0, lodash_1.compact)(entitlements
|
|
171
|
-
.values()
|
|
172
|
-
.filter((entitlement) => entitlement.isUsageTrackable())
|
|
173
|
-
.map((entitlement) => {
|
|
174
|
-
const entitlementReference = entitlement.getEntitlementQuery();
|
|
175
|
-
const usageTimestamp = this.getUsageTimestamp(usageUpdatedAtMap, entitlementReference);
|
|
176
|
-
return this.getUsageItemToUpdate({
|
|
177
|
-
customerId,
|
|
178
|
-
resourceId,
|
|
179
|
-
entitlementReference,
|
|
180
|
-
usage: entitlement.usageData,
|
|
181
|
-
usageTimestamp,
|
|
182
|
-
});
|
|
183
|
-
}));
|
|
184
|
-
}
|
|
185
|
-
getUsageTimestamp(usageUpdatedAtMap, entitlementReference) {
|
|
186
|
-
const key = entities_1.EntitlementsMap.getUniqueKey(entitlementReference);
|
|
187
|
-
const usageTimestamp = usageUpdatedAtMap.get(key);
|
|
188
|
-
if (!usageTimestamp) {
|
|
189
|
-
this.loggerService.error('Usage timestamp not found for entitlement, using current time', {
|
|
190
|
-
entitlementReference,
|
|
191
|
-
});
|
|
192
|
-
return new Date();
|
|
193
|
-
}
|
|
194
|
-
return usageTimestamp;
|
|
195
|
-
}
|
|
196
|
-
async getCustomerEntitlementsWithoutUsage(customerId, resourceId) {
|
|
197
|
-
var _a, _b, _c, _d;
|
|
198
|
-
const { customerKey, metadataKey } = (0, cacheKeysHelpers_1.buildRedisCustomerKey)(this.environmentPrefix, customerId, resourceId);
|
|
199
|
-
const keysToFetch = [customerKey, metadataKey];
|
|
200
|
-
if (resourceId) {
|
|
201
|
-
const globalKey = (0, cacheKeysHelpers_1.buildRedisCustomerKey)(this.environmentPrefix, customerId, undefined);
|
|
202
|
-
keysToFetch.push(`${customerKey}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`);
|
|
203
|
-
keysToFetch.push(`${globalKey.customerKey}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`);
|
|
204
|
-
}
|
|
205
|
-
const [entitlementsRaw, metadataValue, entitlementsTimestampValue, globalEntitlementsTimestampValue] = await this.redisClient.mget(keysToFetch);
|
|
206
|
-
const entitlements = !(0, lodash_1.isNil)(entitlementsRaw) && !(0, lodash_1.isEmpty)(entitlementsRaw)
|
|
207
|
-
? entities_1.EntitlementsMap.fromJSON(this.migrateOldCacheFormat(JSON.parse(entitlementsRaw)))
|
|
208
|
-
: null;
|
|
209
|
-
let accessDeniedReason = null;
|
|
210
|
-
if (!(0, lodash_1.isNil)(metadataValue) && !(0, lodash_1.isEmpty)(metadataValue)) {
|
|
211
|
-
const metadata = JSON.parse(metadataValue);
|
|
212
|
-
accessDeniedReason = metadata.accessDeniedReason;
|
|
213
|
-
}
|
|
214
|
-
const entitlementsTimestamp = this.parseTimestamp(entitlementsTimestampValue);
|
|
215
|
-
const globalEntitlementsTimestamp = this.parseTimestamp(globalEntitlementsTimestampValue);
|
|
216
|
-
const globalCustomerMissing = this.isGlobalCustomerMissingInCache(customerId, resourceId, entitlements, entitlementsTimestamp, globalEntitlementsTimestamp);
|
|
217
|
-
if (!entitlements || globalCustomerMissing) {
|
|
218
|
-
(_b = (_a = this.cacheInstrumentation).trackMiss) === null || _b === void 0 ? void 0 : _b.call(_a, { customerId, resourceId, globalCustomerMissing });
|
|
219
|
-
return cacheResponse_1.entitlementsResponseMapper.cacheMiss(globalCustomerMissing);
|
|
220
|
-
}
|
|
221
|
-
this.loggerService.debug(`Found entitlements in persisted cache for customer`, { customerId, resourceId });
|
|
222
|
-
(_d = (_c = this.cacheInstrumentation).trackHit) === null || _d === void 0 ? void 0 : _d.call(_c, { customerId, resourceId });
|
|
223
|
-
return cacheResponse_1.entitlementsResponseMapper.cacheHit(entitlements, accessDeniedReason);
|
|
224
|
-
}
|
|
225
|
-
// resource entitlements are affected by global entitlements, so if global
|
|
226
|
-
// entitlements are missing or newer, we should refetch since the cache is stale
|
|
227
|
-
isGlobalCustomerMissingInCache(customerId, resourceId, entitlements, entitlementsTimestamp, globalEntitlementsTimestamp) {
|
|
228
|
-
if ((0, lodash_1.isNil)(resourceId)) {
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
if (!entitlements) {
|
|
232
|
-
// in case no entitlements are found, it's a cache miss anyway for the resource
|
|
233
|
-
// so we just need to check if the global entitlements are missing
|
|
234
|
-
return !globalEntitlementsTimestamp;
|
|
235
|
-
}
|
|
236
|
-
const isResourceTimestampAfterGlobalTimestamp = entitlementsTimestamp && globalEntitlementsTimestamp && entitlementsTimestamp >= globalEntitlementsTimestamp;
|
|
237
|
-
if (isResourceTimestampAfterGlobalTimestamp) {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
this.loggerService.log(`cache miss for resource due to global customer`, {
|
|
241
|
-
customerId,
|
|
242
|
-
resourceId,
|
|
243
|
-
entitlementsTimestamp,
|
|
244
|
-
globalEntitlementsTimestamp,
|
|
245
|
-
});
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
async getCustomerEntitlements(params) {
|
|
249
|
-
return this.getCached('getCustomerEntitlements', params);
|
|
250
|
-
}
|
|
251
|
-
async getCached(operationName, { customerId, resourceId }, extract) {
|
|
252
|
-
return this.executeSafely(operationName, cacheResponse_1.entitlementsResponseMapper.cacheMiss(), async () => {
|
|
253
|
-
var _a, _b, _c, _d;
|
|
254
|
-
const response = await this.getCustomerEntitlementsWithoutUsage(customerId, resourceId);
|
|
255
|
-
if (response.cacheMiss) {
|
|
256
|
-
return response;
|
|
257
|
-
}
|
|
258
|
-
const { entitlements: entitlementsWithoutUsage, accessDeniedReason } = response;
|
|
259
|
-
const extracted = extract ? extract.transform(entitlementsWithoutUsage) : entitlementsWithoutUsage;
|
|
260
|
-
const withUsage = await this.fetchAndMergeUsage(customerId, resourceId, extracted);
|
|
261
|
-
if (!withUsage) {
|
|
262
|
-
(_b = (_a = this.cacheInstrumentation).trackMiss) === null || _b === void 0 ? void 0 : _b.call(_a, { customerId, resourceId });
|
|
263
|
-
return cacheResponse_1.entitlementsResponseMapper.cacheMiss();
|
|
264
|
-
}
|
|
265
|
-
const entitlements = this.freshen.transform(withUsage);
|
|
266
|
-
(_d = (_c = this.cacheInstrumentation).trackHit) === null || _d === void 0 ? void 0 : _d.call(_c, { customerId, resourceId });
|
|
267
|
-
return cacheResponse_1.entitlementsResponseMapper.cacheHit(entitlements, accessDeniedReason);
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Fetches usage for all trackable entitlements and merges it into the map.
|
|
272
|
-
* Returns null if any trackable entitlement is missing usage (cache miss).
|
|
273
|
-
*/
|
|
274
|
-
async fetchAndMergeUsage(customerId, resourceId, entitlements) {
|
|
275
|
-
const usageTrackableRefs = entitlements
|
|
276
|
-
.values()
|
|
277
|
-
.filter((ent) => ent.isUsageTrackable())
|
|
278
|
-
.map((ent) => ent.getEntitlementQuery());
|
|
279
|
-
if (!(0, lodash_1.isEmpty)(usageTrackableRefs)) {
|
|
280
|
-
const usageByKey = await this.getUsages(this.environmentPrefix, customerId, resourceId, usageTrackableRefs);
|
|
281
|
-
const missingRefs = usageTrackableRefs.filter((ref) => !usageByKey.has(entities_1.EntitlementsMap.getUniqueKey(ref)));
|
|
282
|
-
if (!(0, lodash_1.isEmpty)(missingRefs)) {
|
|
283
|
-
this.loggerService.error(`Failed to find usage - considering it as cache miss`, {
|
|
284
|
-
customerId,
|
|
285
|
-
resourceId,
|
|
286
|
-
missingRefs,
|
|
287
|
-
});
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
usageTrackableRefs.forEach((entitlementRef) => {
|
|
291
|
-
const usageValue = usageByKey.get(entities_1.EntitlementsMap.getUniqueKey(entitlementRef));
|
|
292
|
-
if (!usageValue) {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const cachedEntitlement = entitlements.get(entitlementRef);
|
|
296
|
-
if (cachedEntitlement) {
|
|
297
|
-
entitlements.set(this.mergeEntitlementWithUsage(cachedEntitlement, usageValue));
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
this.loggerService.log(`Found usage for an entitlement the customer is not entitled to.`, {
|
|
301
|
-
customerId,
|
|
302
|
-
entitlementRef,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
return entitlements;
|
|
308
|
-
}
|
|
309
|
-
async getUsages(environmentPrefix, customerId, resourceId, entitlementReferences) {
|
|
310
|
-
const keysToFetch = entitlementReferences.map((ref) => (0, cacheKeysHelpers_1.buildRedisUsageKey)(environmentPrefix, customerId, ref, resourceId));
|
|
311
|
-
const usageValues = await this.redisClient.mget(keysToFetch);
|
|
312
|
-
const usageByEntitlementKey = new Map();
|
|
313
|
-
// Redis guarantees returning values in the same order of the keys so this is legit!
|
|
314
|
-
entitlementReferences.forEach((ref, index) => {
|
|
315
|
-
const usageValue = usageValues[index];
|
|
316
|
-
if (usageValue === null) {
|
|
317
|
-
this.loggerService.log(`Failed to find usage for entitlement: ${ref.type}:${ref.id}`);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
usageByEntitlementKey.set(entities_1.EntitlementsMap.getUniqueKey(ref), this.migrateUsageData(JSON.parse(usageValue)));
|
|
321
|
-
});
|
|
322
|
-
return usageByEntitlementKey;
|
|
323
|
-
}
|
|
324
|
-
clearCache() {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
async updateCacheItems(items) {
|
|
328
|
-
var _a, _b, _c, _d;
|
|
329
|
-
if ((0, lodash_1.isEmpty)(items)) {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const startTime = Date.now();
|
|
333
|
-
try {
|
|
334
|
-
const latestTimestampByKey = await this.getKeysLatestTimestamp(items.map((item) => item.key));
|
|
335
|
-
// Check if we should skip writing based on cache policy
|
|
336
|
-
if (this.shouldSkipWriting(items, latestTimestampByKey)) {
|
|
337
|
-
this.loggerService.log(`Cache data does not exist - skipping according to cache policy (${this.cacheUpdatePolicy})`, {
|
|
338
|
-
keys: items.map((item) => item.key),
|
|
339
|
-
});
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
const itemsToUpdate = [];
|
|
343
|
-
items.forEach(({ messageTimestamp, key, value }) => {
|
|
344
|
-
const latestTimestamp = latestTimestampByKey.get(key);
|
|
345
|
-
if (!latestTimestamp ||
|
|
346
|
-
messageTimestamp.getTime() === cacheResponse_1.DATE_IN_FAR_PAST.getTime() ||
|
|
347
|
-
latestTimestamp.getTime() <= messageTimestamp.getTime()) {
|
|
348
|
-
const writeableValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
349
|
-
itemsToUpdate.push({ key, value: writeableValue });
|
|
350
|
-
itemsToUpdate.push({ key: `${key}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`, value: messageTimestamp.getTime() });
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
this.loggerService.log('Cache data timestamp is after message timestamp, skipping key update', {
|
|
354
|
-
messageTimestamp,
|
|
355
|
-
latestTimestamp,
|
|
356
|
-
key,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
if ((0, lodash_1.isEmpty)(itemsToUpdate)) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
const batch = this.redisClient.multi();
|
|
364
|
-
itemsToUpdate.forEach(({ key, value }) => {
|
|
365
|
-
batch.set(key, value, 'EX', this.ttl);
|
|
366
|
-
});
|
|
367
|
-
await batch.exec();
|
|
368
|
-
// Track write duration
|
|
369
|
-
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
370
|
-
(_b = (_a = this.cacheInstrumentation).trackWriteDuration) === null || _b === void 0 ? void 0 : _b.call(_a, 'updateCacheItems', durationSeconds);
|
|
371
|
-
}
|
|
372
|
-
catch (error) {
|
|
373
|
-
(_d = (_c = this.cacheInstrumentation).trackWriteError) === null || _d === void 0 ? void 0 : _d.call(_c, 'updateCacheItems');
|
|
374
|
-
throw error;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
shouldSkipWriting(items, latestTimestampByKey) {
|
|
378
|
-
if (this.cacheUpdatePolicy === redisConfiguration_1.CacheUpdatePolicy.UPSERT) {
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
return !items.some(({ key }) => latestTimestampByKey.get(key) !== undefined);
|
|
382
|
-
}
|
|
383
|
-
async getKeysLatestTimestamp(keys) {
|
|
384
|
-
const timestampKeys = keys.map((key) => `${key}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`);
|
|
385
|
-
const value = await this.redisClient.mget(timestampKeys);
|
|
386
|
-
const result = new Map();
|
|
387
|
-
keys.forEach((key, index) => {
|
|
388
|
-
result.set(key, this.parseTimestamp(value[index]));
|
|
389
|
-
});
|
|
390
|
-
return result;
|
|
391
|
-
}
|
|
392
|
-
parseTimestamp(value) {
|
|
393
|
-
if ((0, lodash_1.isNil)(value)) {
|
|
394
|
-
return undefined;
|
|
395
|
-
}
|
|
396
|
-
const number = (0, lodash_1.parseInt)(value, 10);
|
|
397
|
-
if (Number.isNaN(number)) {
|
|
398
|
-
return undefined;
|
|
399
|
-
}
|
|
400
|
-
return new Date(number);
|
|
401
|
-
}
|
|
402
|
-
async cleanup() {
|
|
403
|
-
var _a;
|
|
404
|
-
await this.distributedLocks.cleanup();
|
|
405
|
-
await ((_a = this.distributedRefetchEntitlementsService) === null || _a === void 0 ? void 0 : _a.cleanup());
|
|
406
|
-
}
|
|
407
|
-
async purge(customers) {
|
|
408
|
-
const customersArray = Array.isArray(customers) ? customers : [customers];
|
|
409
|
-
return this.executeSafely('purge', undefined, async () => {
|
|
410
|
-
for (const customerChunk of (0, lodash_1.chunk)(customersArray, RedisCacheService.DELETE_CHUNK_SIZE)) {
|
|
411
|
-
const keysToDelete = customerChunk.flatMap(({ customerId, resourceId }) => {
|
|
412
|
-
this.loggerService.log(`Invalidating entitlements cache`, { customerId, resourceId });
|
|
413
|
-
const { customerKey, metadataKey } = (0, cacheKeysHelpers_1.buildRedisCustomerKey)(this.environmentPrefix, customerId, resourceId);
|
|
414
|
-
return [customerKey, `${customerKey}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`, metadataKey, `${metadataKey}#${redisCacheService_constants_1.TIMESTAMP_SUFFIX}`];
|
|
415
|
-
});
|
|
416
|
-
await this.redisClient.del(keysToDelete);
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
async getCustomerEntitlement(params) {
|
|
421
|
-
const { query } = params;
|
|
422
|
-
const extractor = new extractWithDependencies_1.ExtractWithDependencies(query);
|
|
423
|
-
const { entitlements, accessDeniedReason, cacheMiss, globalCustomerMissing } = await this.getCached('getCustomerEntitlement', params, extractor);
|
|
424
|
-
const entitlement = !cacheMiss ? (entitlements === null || entitlements === void 0 ? void 0 : entitlements.get(query)) || null : null;
|
|
425
|
-
return { cacheMiss, accessDeniedReason, entitlement, globalCustomerMissing };
|
|
426
|
-
}
|
|
427
|
-
mergeEntitlementWithUsage(entitlement, cachedUsage) {
|
|
428
|
-
return new entities_1.CachedEntitlement(entitlement.calculatedEntitlement, cachedUsage);
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Returns Redis server info for the specified section (e.g., 'memory', 'stats').
|
|
432
|
-
* Throws if the client is not connected.
|
|
433
|
-
*/
|
|
434
|
-
async getServerInfo(section) {
|
|
435
|
-
if (!this.isClientConnected()) {
|
|
436
|
-
throw new Error('Redis client is not connected');
|
|
437
|
-
}
|
|
438
|
-
return this.redisClient.info(section);
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Migrates old cache format to new format.
|
|
442
|
-
* Handles: featureUsage→usageData rename, adds type discriminator, moves fields,
|
|
443
|
-
* adds updatedAt, adds displayName fallback, nextResetDate→usagePeriodEnd.
|
|
444
|
-
*/
|
|
445
|
-
migrateOldCacheFormat(cacheData) {
|
|
446
|
-
for (const [key, value] of Object.entries(cacheData)) {
|
|
447
|
-
const entry = value;
|
|
448
|
-
if (!(entry === null || entry === void 0 ? void 0 : entry.calculatedEntitlement))
|
|
449
|
-
continue;
|
|
450
|
-
// 1. Rename featureUsage → usageData
|
|
451
|
-
if (entry.featureUsage && !entry.usageData) {
|
|
452
|
-
entry.usageData = entry.featureUsage;
|
|
453
|
-
delete entry.featureUsage;
|
|
454
|
-
}
|
|
455
|
-
// 2. Add type discriminator if missing (old cache only had Feature entitlements)
|
|
456
|
-
if (!entry.calculatedEntitlement.type) {
|
|
457
|
-
// Skip malformed entries that don't have a feature object - they can't be valid Feature entitlements
|
|
458
|
-
if (!entry.calculatedEntitlement.feature) {
|
|
459
|
-
delete cacheData[key];
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
entry.calculatedEntitlement.type = entities_1.EntitlementType.Feature;
|
|
463
|
-
}
|
|
464
|
-
// 3. Move fields from usageData to calculatedEntitlement (for Feature type)
|
|
465
|
-
if (entry.usageData && entry.calculatedEntitlement.type === entities_1.EntitlementType.Feature) {
|
|
466
|
-
const fieldsToMove = ['usagePeriodAnchor', 'resetPeriod', 'resetPeriodConfiguration'];
|
|
467
|
-
for (const field of fieldsToMove) {
|
|
468
|
-
if (entry.usageData[field] !== undefined && entry.calculatedEntitlement[field] === undefined) {
|
|
469
|
-
entry.calculatedEntitlement[field] = entry.usageData[field];
|
|
470
|
-
delete entry.usageData[field];
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
// 4. Add updatedAt if missing
|
|
475
|
-
if (entry.usageData && !entry.usageData.updatedAt) {
|
|
476
|
-
entry.usageData.updatedAt = Date.now();
|
|
477
|
-
}
|
|
478
|
-
// 5. Add displayName fallback if missing
|
|
479
|
-
if (entry.calculatedEntitlement.feature && !entry.calculatedEntitlement.feature.displayName) {
|
|
480
|
-
entry.calculatedEntitlement.feature.displayName = entry.calculatedEntitlement.feature.id;
|
|
481
|
-
}
|
|
482
|
-
// 6. Migrate usageData (nextResetDate → usagePeriodEnd)
|
|
483
|
-
if (entry.usageData) {
|
|
484
|
-
entry.usageData = this.migrateUsageData(entry.usageData);
|
|
485
|
-
}
|
|
486
|
-
cacheData[key] = entry;
|
|
487
|
-
}
|
|
488
|
-
return cacheData;
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Migrates old usage data format.
|
|
492
|
-
* Handles backward compatibility for old items that have nextResetDate instead of usagePeriodEnd.
|
|
493
|
-
*/
|
|
494
|
-
migrateUsageData(usageData) {
|
|
495
|
-
if (!(0, lodash_1.isNil)(usageData.nextResetDate) && (0, lodash_1.isNil)(usageData.usagePeriodEnd)) {
|
|
496
|
-
return Object.assign(Object.assign({}, (0, lodash_1.omit)(usageData, 'nextResetDate')), { usagePeriodEnd: usageData.nextResetDate });
|
|
497
|
-
}
|
|
498
|
-
return usageData;
|
|
499
|
-
}
|
|
500
|
-
async executeSafely(operationName, defaultValue, operation) {
|
|
501
|
-
// When directExecution is enabled, run operations directly without safety wrapper
|
|
502
|
-
if (this.directExecution) {
|
|
503
|
-
return operation();
|
|
504
|
-
}
|
|
505
|
-
if (!this.isClientConnected()) {
|
|
506
|
-
return defaultValue;
|
|
507
|
-
}
|
|
508
|
-
try {
|
|
509
|
-
return await operation();
|
|
510
|
-
}
|
|
511
|
-
catch (error) {
|
|
512
|
-
this.loggerService.error(`Redis cache service: got error during ${operationName} (silent ignore it)`, error.stack);
|
|
513
|
-
return defaultValue;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
exports.RedisCacheService = RedisCacheService;
|
|
518
|
-
RedisCacheService.DELETE_CHUNK_SIZE = 1000;
|
|
519
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVkaXNDYWNoZVNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmljZXMvY2FjaGUvcmVkaXNDYWNoZVNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBUUEseUNBQThHO0FBRTlHLHNEQUE0QjtBQUM1QixtQ0FBd0U7QUFFeEUsbUVBQXdGO0FBQ3hGLHlEQUE2RjtBQUM3RiwrRUFBNEU7QUFDNUUseURBSytCO0FBQy9CLHVFQUFvRTtBQUVwRSw2RUFBMEU7QUFDMUUsK0VBTXVDO0FBQ3ZDLCtEQUE0RDtBQUc1RCx5RUFBbUU7QUFpQm5FLE1BQU0sd0JBQXdCLEdBQUcsR0FBRyxDQUFDO0FBRXJDLE1BQU0sY0FBYyxHQUFhLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRXRELE1BQWEsaUJBQWlCO0lBYTVCLFlBQ0UsT0FBZ0MsRUFDZixhQUF5QixFQUN6QixvQkFBMEMsRUFDMUMsV0FBK0I7UUFGL0Isa0JBQWEsR0FBYixhQUFhLENBQVk7UUFDekIseUJBQW9CLEdBQXBCLG9CQUFvQixDQUFzQjtRQUMxQyxnQkFBVyxHQUFYLFdBQVcsQ0FBb0I7UUFSakMsWUFBTyxHQUFHLElBQUksNkNBQXFCLEVBQUUsQ0FBQztRQVVyRCxNQUFNLEVBQ0osS0FBSyxFQUFFLFlBQVksRUFDbkIsbUJBQW1CLEdBQUcsNkRBQStCLEVBQ3JELGlCQUFpQixHQUFHLHNDQUFpQixDQUFDLE1BQU0sRUFDNUMsWUFBWSxFQUNaLGVBQWUsR0FBRyxLQUFLLEdBQ3hCLEdBQUcsT0FBTyxDQUFDO1FBQ1osTUFBTSxFQUFFLGlCQUFpQixFQUFFLEdBQUcsR0FBRyw4Q0FBZ0IsRUFBRSwrQkFBK0IsR0FBRyxFQUFFLEVBQUUsR0FBRyxZQUFZLENBQUM7UUFFekcsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLGlCQUFLLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLGdCQUFnQixHQUFH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
|