@checkfirst/nestjs-outlook 8.0.1 → 8.0.3
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/constants.d.ts +2 -0
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/config/outlook-config.interface.d.ts +10 -0
- package/dist/interfaces/state/redis-like.interface.d.ts +13 -0
- package/dist/interfaces/state/redis-like.interface.js +3 -0
- package/dist/interfaces/state/redis-like.interface.js.map +1 -0
- package/dist/microsoft-outlook.module.js +63 -0
- package/dist/microsoft-outlook.module.js.map +1 -1
- package/dist/services/auth/microsoft-auth.service.d.ts +5 -1
- package/dist/services/auth/microsoft-auth.service.js +14 -5
- package/dist/services/auth/microsoft-auth.service.js.map +1 -1
- package/dist/services/calendar/calendar.service.d.ts +1 -1
- package/dist/services/calendar/calendar.service.js +4 -4
- package/dist/services/calendar/calendar.service.js.map +1 -1
- package/dist/services/shared/delta-sync.service.d.ts +6 -1
- package/dist/services/shared/delta-sync.service.js +121 -110
- package/dist/services/shared/delta-sync.service.js.map +1 -1
- package/dist/services/shared/graph-rate-limiter.service.d.ts +15 -17
- package/dist/services/shared/graph-rate-limiter.service.js +104 -148
- package/dist/services/shared/graph-rate-limiter.service.js.map +1 -1
- package/dist/services/shared/outlook-lock.store.d.ts +37 -0
- package/dist/services/shared/outlook-lock.store.js +134 -0
- package/dist/services/shared/outlook-lock.store.js.map +1 -0
- package/dist/services/shared/outlook-rate-limit.store.d.ts +59 -0
- package/dist/services/shared/outlook-rate-limit.store.js +235 -0
- package/dist/services/shared/outlook-rate-limit.store.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/outlook-api-executor.util.js +3 -3
- package/dist/utils/outlook-api-executor.util.js.map +1 -1
- package/package.json +3 -1
|
@@ -8,213 +8,167 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
var GraphRateLimiterService_1;
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.GraphRateLimiterService = void 0;
|
|
14
17
|
const common_1 = require("@nestjs/common");
|
|
15
18
|
const schedule_1 = require("@nestjs/schedule");
|
|
16
19
|
const retry_util_1 = require("../../utils/retry.util");
|
|
20
|
+
const constants_1 = require("../../constants");
|
|
17
21
|
let GraphRateLimiterService = GraphRateLimiterService_1 = class GraphRateLimiterService {
|
|
18
|
-
constructor() {
|
|
22
|
+
constructor(store) {
|
|
23
|
+
this.store = store;
|
|
19
24
|
this.logger = new common_1.Logger(GraphRateLimiterService_1.name);
|
|
20
25
|
this.MAX_REQUESTS_PER_SECOND = 4;
|
|
21
26
|
this.MAX_REQUESTS_PER_10_MINUTES = 10000;
|
|
22
27
|
this.ONE_SECOND_MS = 1000;
|
|
23
28
|
this.TEN_MINUTES_MS = 10 * 60 * 1000;
|
|
24
29
|
this.INACTIVE_USER_THRESHOLD_MS = 30 * 60 * 1000;
|
|
25
|
-
this.userLimiters = new Map();
|
|
26
30
|
this.totalPermitsAcquired = 0;
|
|
27
31
|
this.totalWaitTime = 0;
|
|
28
32
|
this.cooldownCount = 0;
|
|
29
33
|
this.CB_FAILURE_THRESHOLD = 5;
|
|
30
34
|
this.CB_FAILURE_WINDOW_MS = 60000;
|
|
31
35
|
this.CB_COOLDOWN_MS = 60000;
|
|
32
|
-
this.
|
|
33
|
-
this.cbState = 'closed';
|
|
36
|
+
this.CB_PROBE_TTL_MS = 5000;
|
|
34
37
|
this.cbFailureTimestamps = [];
|
|
35
|
-
this.cbOpenedAt = null;
|
|
36
|
-
this.cbHalfOpenInFlight = 0;
|
|
37
38
|
this.cbTotalTrips = 0;
|
|
38
39
|
}
|
|
39
40
|
onModuleInit() {
|
|
40
|
-
this.logger.log(`GraphRateLimiterService initialized - Limits: ${this.MAX_REQUESTS_PER_SECOND} req/sec, ` +
|
|
41
|
+
this.logger.log(`GraphRateLimiterService initialized (backend=${this.store.kind}) - Limits: ${this.MAX_REQUESTS_PER_SECOND} req/sec, ` +
|
|
41
42
|
`${this.MAX_REQUESTS_PER_10_MINUTES} req/10min per user`);
|
|
42
43
|
}
|
|
43
44
|
async acquirePermit(userId) {
|
|
44
45
|
await this.waitForCircuitBreaker();
|
|
45
|
-
if (!this.userLimiters.has(userId)) {
|
|
46
|
-
this.initializeLimiter(userId);
|
|
47
|
-
}
|
|
48
|
-
const limiter = this.userLimiters.get(userId);
|
|
49
|
-
if (!limiter) {
|
|
50
|
-
this.logger.warn(`[acquirePermit] No limiter found for user ${userId}, skipping acquire`);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
46
|
const startTime = Date.now();
|
|
54
47
|
let waitedOnce = false;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
for (;;) {
|
|
49
|
+
const cooldownUntil = await this.store.getCooldown(userId);
|
|
50
|
+
if (cooldownUntil && cooldownUntil > Date.now()) {
|
|
51
|
+
const wait = cooldownUntil - Date.now() + 50;
|
|
52
|
+
if (!waitedOnce) {
|
|
53
|
+
waitedOnce = true;
|
|
54
|
+
this.logger.debug(`[acquirePermit] User ${userId} in cooldown for ${wait}ms`);
|
|
55
|
+
}
|
|
56
|
+
await (0, retry_util_1.delay)(Math.max(wait, 50));
|
|
57
|
+
continue;
|
|
59
58
|
}
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
const secCount = await this.store.getCount(userId, this.ONE_SECOND_MS, 'sec');
|
|
60
|
+
const tenMinCount = await this.store.getCount(userId, this.TEN_MINUTES_MS, 'min10');
|
|
61
|
+
if (secCount >= this.MAX_REQUESTS_PER_SECOND) {
|
|
62
|
+
if (!waitedOnce) {
|
|
63
|
+
waitedOnce = true;
|
|
64
|
+
this.logger.debug(`[acquirePermit] User ${userId} at 1s limit, waiting`);
|
|
65
|
+
}
|
|
66
|
+
await (0, retry_util_1.delay)(100);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (tenMinCount >= this.MAX_REQUESTS_PER_10_MINUTES) {
|
|
70
|
+
this.logger.warn(`[acquirePermit] User ${userId} hit 10-minute limit (${tenMinCount}/${this.MAX_REQUESTS_PER_10_MINUTES})`);
|
|
71
|
+
await (0, retry_util_1.delay)(1000);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
65
75
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
limiter.tenMinuteWindow.push(now);
|
|
69
|
-
limiter.lastActivity = now;
|
|
76
|
+
await this.store.recordRequest(userId, this.ONE_SECOND_MS, 'sec');
|
|
77
|
+
await this.store.recordRequest(userId, this.TEN_MINUTES_MS, 'min10');
|
|
70
78
|
this.totalPermitsAcquired++;
|
|
71
|
-
const totalWait = now - startTime;
|
|
79
|
+
const totalWait = Date.now() - startTime;
|
|
72
80
|
if (totalWait > 50) {
|
|
73
81
|
this.totalWaitTime += totalWait;
|
|
74
|
-
this.logger.debug(`[acquirePermit] User ${userId} waited ${totalWait}ms for permit
|
|
75
|
-
`(recent: ${limiter.recentRequests.length}/${this.MAX_REQUESTS_PER_SECOND}, ` +
|
|
76
|
-
`10min: ${limiter.tenMinuteWindow.length}/${this.MAX_REQUESTS_PER_10_MINUTES})`);
|
|
82
|
+
this.logger.debug(`[acquirePermit] User ${userId} waited ${totalWait}ms for permit`);
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
|
-
releasePermit(
|
|
80
|
-
if (!this.userLimiters.has(userId)) {
|
|
81
|
-
this.logger.warn(`[releasePermit] No limiter found for user ${userId}, skipping release`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
85
|
+
releasePermit(_userId) {
|
|
84
86
|
}
|
|
85
|
-
handleRateLimitResponse(userId, retryAfterSeconds) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
const limiter = this.userLimiters.get(userId);
|
|
90
|
-
if (!limiter) {
|
|
91
|
-
this.logger.warn(`[handleRateLimitResponse] No limiter found for user ${userId}, skipping cooldown`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const cooldownUntil = Date.now() + (retryAfterSeconds * 1000);
|
|
95
|
-
limiter.cooldownUntil = cooldownUntil;
|
|
87
|
+
async handleRateLimitResponse(userId, retryAfterSeconds) {
|
|
88
|
+
const cooldownUntil = Date.now() + retryAfterSeconds * 1000;
|
|
89
|
+
await this.store.setCooldown(userId, cooldownUntil);
|
|
96
90
|
this.cooldownCount++;
|
|
97
91
|
this.logger.warn(`[handleRateLimitResponse] User ${userId} hit 429 - cooldown for ${retryAfterSeconds}s ` +
|
|
98
92
|
`until ${new Date(cooldownUntil).toISOString()}`);
|
|
99
93
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const now = Date.now();
|
|
105
|
-
limiter.recentRequests = limiter.recentRequests.filter(timestamp => now - timestamp < this.ONE_SECOND_MS);
|
|
106
|
-
limiter.tenMinuteWindow = limiter.tenMinuteWindow.filter(timestamp => now - timestamp < this.TEN_MINUTES_MS);
|
|
107
|
-
if (limiter.cooldownUntil && now < limiter.cooldownUntil) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if (limiter.recentRequests.length >= this.MAX_REQUESTS_PER_SECOND) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
if (limiter.tenMinuteWindow.length >= this.MAX_REQUESTS_PER_10_MINUTES) {
|
|
114
|
-
this.logger.warn(`[canMakeRequest] User ${userId} hit 10-minute limit ` +
|
|
115
|
-
`(${limiter.tenMinuteWindow.length}/${this.MAX_REQUESTS_PER_10_MINUTES})`);
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
initializeLimiter(userId) {
|
|
121
|
-
this.userLimiters.set(userId, {
|
|
122
|
-
userId,
|
|
123
|
-
recentRequests: [],
|
|
124
|
-
tenMinuteWindow: [],
|
|
125
|
-
cooldownUntil: null,
|
|
126
|
-
lastActivity: Date.now(),
|
|
127
|
-
});
|
|
128
|
-
this.logger.debug(`[initializeLimiter] Created limiter for user ${userId}`);
|
|
129
|
-
}
|
|
130
|
-
cleanupInactiveUsers() {
|
|
94
|
+
async cleanupInactiveUsers() {
|
|
95
|
+
if (this.store.kind !== 'memory')
|
|
96
|
+
return;
|
|
97
|
+
const removed = await this.store.cleanupInactive(this.INACTIVE_USER_THRESHOLD_MS);
|
|
131
98
|
const now = Date.now();
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (inactiveDuration > this.INACTIVE_USER_THRESHOLD_MS) {
|
|
136
|
-
this.userLimiters.delete(userId);
|
|
137
|
-
cleaned++;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
this.cbFailureTimestamps = this.cbFailureTimestamps.filter(t => now - t < this.CB_FAILURE_WINDOW_MS);
|
|
141
|
-
if (cleaned > 0) {
|
|
142
|
-
this.logger.log(`[cleanupInactiveUsers] Cleaned up ${cleaned} inactive users ` +
|
|
143
|
-
`(${this.userLimiters.size} active limiters remaining)`);
|
|
99
|
+
this.cbFailureTimestamps = this.cbFailureTimestamps.filter((t) => now - t < this.CB_FAILURE_WINDOW_MS);
|
|
100
|
+
if (removed > 0) {
|
|
101
|
+
this.logger.log(`[cleanupInactiveUsers] Cleaned up ${removed} inactive users`);
|
|
144
102
|
}
|
|
145
103
|
}
|
|
146
|
-
record503Failure() {
|
|
104
|
+
async record503Failure() {
|
|
105
|
+
var _a;
|
|
147
106
|
const now = Date.now();
|
|
148
107
|
this.cbFailureTimestamps.push(now);
|
|
149
|
-
this.cbFailureTimestamps = this.cbFailureTimestamps.filter(t => now - t < this.CB_FAILURE_WINDOW_MS);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.
|
|
108
|
+
this.cbFailureTimestamps = this.cbFailureTimestamps.filter((t) => now - t < this.CB_FAILURE_WINDOW_MS);
|
|
109
|
+
const cb = await this.store.getCbState();
|
|
110
|
+
const state = (_a = cb === null || cb === void 0 ? void 0 : cb.state) !== null && _a !== void 0 ? _a : 'closed';
|
|
111
|
+
if (state === 'half-open') {
|
|
112
|
+
await this.store.setCbState({ state: 'open', openedAt: now });
|
|
154
113
|
this.logger.warn(`[CircuitBreaker] Half-open probe failed (503), re-opening circuit for ${this.CB_COOLDOWN_MS / 1000}s`);
|
|
155
114
|
return;
|
|
156
115
|
}
|
|
157
|
-
if (
|
|
116
|
+
if (state === 'closed' &&
|
|
158
117
|
this.cbFailureTimestamps.length >= this.CB_FAILURE_THRESHOLD) {
|
|
159
|
-
this.
|
|
160
|
-
this.cbOpenedAt = now;
|
|
118
|
+
await this.store.setCbState({ state: 'open', openedAt: now });
|
|
161
119
|
this.cbTotalTrips++;
|
|
162
120
|
this.logger.warn(`[CircuitBreaker] OPEN — ${this.cbFailureTimestamps.length} 503 errors ` +
|
|
163
121
|
`within ${this.CB_FAILURE_WINDOW_MS / 1000}s window. ` +
|
|
164
122
|
`Blocking requests for ${this.CB_COOLDOWN_MS / 1000}s`);
|
|
165
123
|
}
|
|
166
124
|
}
|
|
167
|
-
recordSuccess() {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.
|
|
125
|
+
async recordSuccess() {
|
|
126
|
+
const cb = await this.store.getCbState();
|
|
127
|
+
if ((cb === null || cb === void 0 ? void 0 : cb.state) === 'half-open') {
|
|
128
|
+
await this.store.setCbState({ state: 'closed', openedAt: null });
|
|
171
129
|
this.cbFailureTimestamps = [];
|
|
172
|
-
this.
|
|
173
|
-
this.cbOpenedAt = null;
|
|
130
|
+
this.logger.log('[CircuitBreaker] CLOSED — half-open probe succeeded');
|
|
174
131
|
return;
|
|
175
132
|
}
|
|
176
|
-
if (
|
|
133
|
+
if ((cb === null || cb === void 0 ? void 0 : cb.state) === 'closed') {
|
|
177
134
|
this.cbFailureTimestamps = [];
|
|
178
135
|
}
|
|
179
136
|
}
|
|
180
137
|
async waitForCircuitBreaker() {
|
|
181
138
|
var _a;
|
|
182
|
-
|
|
139
|
+
const cb = await this.store.getCbState();
|
|
140
|
+
if (!cb || cb.state === 'closed')
|
|
183
141
|
return;
|
|
184
142
|
const now = Date.now();
|
|
185
|
-
if (
|
|
186
|
-
const elapsed = now - ((_a =
|
|
143
|
+
if (cb.state === 'open') {
|
|
144
|
+
const elapsed = now - ((_a = cb.openedAt) !== null && _a !== void 0 ? _a : now);
|
|
187
145
|
if (elapsed >= this.CB_COOLDOWN_MS) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
else {
|
|
193
|
-
const remaining = this.CB_COOLDOWN_MS - elapsed;
|
|
194
|
-
this.logger.warn(`[CircuitBreaker] Circuit OPEN, waiting ${Math.round(remaining / 1000)}s before retry`);
|
|
195
|
-
await (0, retry_util_1.delay)(remaining);
|
|
196
|
-
this.cbState = 'half-open';
|
|
197
|
-
this.cbHalfOpenInFlight = 0;
|
|
198
|
-
this.logger.log('[CircuitBreaker] HALF-OPEN — cooldown expired after wait');
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const currentState = this.cbState;
|
|
202
|
-
if (currentState === 'half-open') {
|
|
203
|
-
while (this.cbHalfOpenInFlight >= this.CB_HALF_OPEN_MAX) {
|
|
204
|
-
await (0, retry_util_1.delay)(500);
|
|
205
|
-
const stateAfterWait = this.cbState;
|
|
206
|
-
if (stateAfterWait === 'closed')
|
|
146
|
+
const wonProbe = await this.store.tryClaimHalfOpenProbe(this.CB_PROBE_TTL_MS);
|
|
147
|
+
if (wonProbe) {
|
|
148
|
+
await this.store.setCbState({ state: 'half-open', openedAt: null });
|
|
149
|
+
this.logger.log('[CircuitBreaker] HALF-OPEN — cooldown expired, this container is probing');
|
|
207
150
|
return;
|
|
208
|
-
if (stateAfterWait === 'open') {
|
|
209
|
-
return this.waitForCircuitBreaker();
|
|
210
151
|
}
|
|
152
|
+
await (0, retry_util_1.delay)(this.CB_PROBE_TTL_MS);
|
|
153
|
+
return this.waitForCircuitBreaker();
|
|
211
154
|
}
|
|
212
|
-
this.
|
|
155
|
+
const remaining = this.CB_COOLDOWN_MS - elapsed;
|
|
156
|
+
this.logger.warn(`[CircuitBreaker] Circuit OPEN, waiting ${Math.round(remaining / 1000)}s before retry`);
|
|
157
|
+
await (0, retry_util_1.delay)(remaining);
|
|
158
|
+
return this.waitForCircuitBreaker();
|
|
159
|
+
}
|
|
160
|
+
const wonProbe = await this.store.tryClaimHalfOpenProbe(this.CB_PROBE_TTL_MS);
|
|
161
|
+
if (!wonProbe) {
|
|
162
|
+
await (0, retry_util_1.delay)(this.CB_PROBE_TTL_MS);
|
|
163
|
+
return this.waitForCircuitBreaker();
|
|
213
164
|
}
|
|
214
165
|
}
|
|
215
|
-
getStats() {
|
|
166
|
+
async getStats() {
|
|
167
|
+
var _a;
|
|
168
|
+
const cb = await this.store.getCbState();
|
|
216
169
|
return {
|
|
217
|
-
|
|
170
|
+
backend: this.store.kind,
|
|
171
|
+
activeUsers: await this.store.getActiveUserCount(),
|
|
218
172
|
totalPermitsAcquired: this.totalPermitsAcquired,
|
|
219
173
|
totalWaitTimeMs: this.totalWaitTime,
|
|
220
174
|
averageWaitTimeMs: this.totalPermitsAcquired > 0
|
|
@@ -222,25 +176,25 @@ let GraphRateLimiterService = GraphRateLimiterService_1 = class GraphRateLimiter
|
|
|
222
176
|
: 0,
|
|
223
177
|
cooldownCount: this.cooldownCount,
|
|
224
178
|
circuitBreaker: {
|
|
225
|
-
state:
|
|
179
|
+
state: (_a = cb === null || cb === void 0 ? void 0 : cb.state) !== null && _a !== void 0 ? _a : 'closed',
|
|
226
180
|
recentFailures: this.cbFailureTimestamps.length,
|
|
227
181
|
totalTrips: this.cbTotalTrips,
|
|
228
|
-
openedAt:
|
|
182
|
+
openedAt: (cb === null || cb === void 0 ? void 0 : cb.openedAt) ? new Date(cb.openedAt).toISOString() : null,
|
|
229
183
|
},
|
|
230
184
|
};
|
|
231
185
|
}
|
|
232
|
-
getUserStats(userId) {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
186
|
+
async getUserStats(userId) {
|
|
187
|
+
const [secCount, tenMinCount, cooldownUntil] = await Promise.all([
|
|
188
|
+
this.store.getCount(userId, this.ONE_SECOND_MS, 'sec'),
|
|
189
|
+
this.store.getCount(userId, this.TEN_MINUTES_MS, 'min10'),
|
|
190
|
+
this.store.getCooldown(userId),
|
|
191
|
+
]);
|
|
238
192
|
return {
|
|
239
193
|
userId,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
194
|
+
backend: this.store.kind,
|
|
195
|
+
recentRequestCount: secCount,
|
|
196
|
+
tenMinuteRequestCount: tenMinCount,
|
|
197
|
+
cooldownUntil,
|
|
244
198
|
};
|
|
245
199
|
}
|
|
246
200
|
};
|
|
@@ -249,9 +203,11 @@ __decorate([
|
|
|
249
203
|
(0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_MINUTES),
|
|
250
204
|
__metadata("design:type", Function),
|
|
251
205
|
__metadata("design:paramtypes", []),
|
|
252
|
-
__metadata("design:returntype",
|
|
206
|
+
__metadata("design:returntype", Promise)
|
|
253
207
|
], GraphRateLimiterService.prototype, "cleanupInactiveUsers", null);
|
|
254
208
|
exports.GraphRateLimiterService = GraphRateLimiterService = GraphRateLimiterService_1 = __decorate([
|
|
255
|
-
(0, common_1.Injectable)()
|
|
209
|
+
(0, common_1.Injectable)(),
|
|
210
|
+
__param(0, (0, common_1.Inject)(constants_1.OUTLOOK_RATE_LIMIT_STORE)),
|
|
211
|
+
__metadata("design:paramtypes", [Object])
|
|
256
212
|
], GraphRateLimiterService);
|
|
257
213
|
//# sourceMappingURL=graph-rate-limiter.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-rate-limiter.service.js","sourceRoot":"","sources":["../../../src/services/shared/graph-rate-limiter.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graph-rate-limiter.service.js","sourceRoot":"","sources":["../../../src/services/shared/graph-rate-limiter.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA0E;AAC1E,+CAAwD;AACxD,uDAA+C;AAC/C,+CAEyB;AAmBlB,IAAM,uBAAuB,+BAA7B,MAAM,uBAAuB;IA2BlC,YAEE,KAA6C;QAA5B,UAAK,GAAL,KAAK,CAAuB;QA5B9B,WAAM,GAAG,IAAI,eAAM,CAAC,yBAAuB,CAAC,IAAI,CAAC,CAAC;QAGlD,4BAAuB,GAAG,CAAC,CAAC;QAC5B,gCAA2B,GAAG,KAAK,CAAC;QACpC,kBAAa,GAAG,IAAI,CAAC;QACrB,mBAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAGhC,+BAA0B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAGrD,yBAAoB,GAAG,CAAC,CAAC;QACzB,kBAAa,GAAG,CAAC,CAAC;QAClB,kBAAa,GAAG,CAAC,CAAC;QAGT,yBAAoB,GAAG,CAAC,CAAC;QACzB,yBAAoB,GAAG,KAAM,CAAC;QAC9B,mBAAc,GAAG,KAAM,CAAC;QACxB,oBAAe,GAAG,IAAK,CAAC;QAGjC,wBAAmB,GAAa,EAAE,CAAC;QACnC,iBAAY,GAAG,CAAC,CAAC;IAKtB,CAAC;IAEJ,YAAY;QACV,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,gDAAgD,IAAI,CAAC,KAAK,CAAC,IAAI,eAAe,IAAI,CAAC,uBAAuB,YAAY;YACtH,GAAG,IAAI,CAAC,2BAA2B,qBAAqB,CACzD,CAAC;IACJ,CAAC;IAQD,KAAK,CAAC,aAAa,CAAC,MAAc;QAChC,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAEnC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,UAAU,GAAG,KAAK,CAAC;QAEvB,SAAS,CAAC;YACR,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC3D,IAAI,aAAa,IAAI,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAChD,MAAM,IAAI,GAAG,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;gBAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,UAAU,GAAG,IAAI,CAAC;oBAClB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,wBAAwB,MAAM,oBAAoB,IAAI,IAAI,CAC3D,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAA,kBAAK,EAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAChC,SAAS;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CACxC,MAAM,EACN,IAAI,CAAC,aAAa,EAClB,KAAK,CACN,CAAC;YACF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAC3C,MAAM,EACN,IAAI,CAAC,cAAc,EACnB,OAAO,CACR,CAAC;YAEF,IAAI,QAAQ,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,UAAU,GAAG,IAAI,CAAC;oBAClB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,wBAAwB,MAAM,uBAAuB,CACtD,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAA,kBAAK,EAAC,GAAG,CAAC,CAAC;gBACjB,SAAS;YACX,CAAC;YAED,IAAI,WAAW,IAAI,IAAI,CAAC,2BAA2B,EAAE,CAAC;gBACpD,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,wBAAwB,MAAM,yBAAyB,WAAW,IAAI,IAAI,CAAC,2BAA2B,GAAG,CAC1G,CAAC;gBACF,MAAM,IAAA,kBAAK,EAAC,IAAI,CAAC,CAAC;gBAClB,SAAS;YACX,CAAC;YAED,MAAM;QACR,CAAC;QAGD,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAClE,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAErE,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,SAAS,GAAG,EAAE,EAAE,CAAC;YACnB,IAAI,CAAC,aAAa,IAAI,SAAS,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,wBAAwB,MAAM,WAAW,SAAS,eAAe,CAClE,CAAC;QACJ,CAAC;IACH,CAAC;IAMD,aAAa,CAAC,OAAe;IAE7B,CAAC;IAMD,KAAK,CAAC,uBAAuB,CAC3B,MAAc,EACd,iBAAyB;QAEzB,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,GAAG,IAAI,CAAC;QAC5D,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACpD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,kCAAkC,MAAM,2BAA2B,iBAAiB,IAAI;YACxF,SAAS,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,EAAE,CACjD,CAAC;IACJ,CAAC;IAOa,AAAN,KAAK,CAAC,oBAAoB;QAChC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QAEzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,eAAe,CAC9C,IAAI,CAAC,0BAA0B,CAChC,CAAC;QAEF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CACxD,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,oBAAoB,CAC3C,CAAC;QAEF,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,qCAAqC,OAAO,iBAAiB,CAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IAMD,KAAK,CAAC,gBAAgB;;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CACxD,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,oBAAoB,CAC3C,CAAC;QAEF,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,MAAA,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,KAAK,mCAAI,QAAQ,CAAC;QAEpC,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;YAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9D,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,yEAAyE,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,CACvG,CAAC;YACF,OAAO;QACT,CAAC;QAED,IACE,KAAK,KAAK,QAAQ;YAClB,IAAI,CAAC,mBAAmB,CAAC,MAAM,IAAI,IAAI,CAAC,oBAAoB,EAC5D,CAAC;YACD,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9D,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,2BAA2B,IAAI,CAAC,mBAAmB,CAAC,MAAM,cAAc;gBACxE,UAAU,IAAI,CAAC,oBAAoB,GAAG,IAAI,YAAY;gBACtD,yBAAyB,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,CACvD,CAAC;QACJ,CAAC;IACH,CAAC;IAKD,KAAK,CAAC,aAAa;QACjB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,CAAA,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,KAAK,MAAK,WAAW,EAAE,CAAC;YAC9B,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QACD,IAAI,CAAA,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,KAAK,MAAK,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;IAMO,KAAK,CAAC,qBAAqB;;QACjC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO;QAEzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,EAAE,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,MAAA,EAAE,CAAC,QAAQ,mCAAI,GAAG,CAAC,CAAC;YAC3C,IAAI,OAAO,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;gBACnC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,qBAAqB,CACrD,IAAI,CAAC,eAAe,CACrB,CAAC;gBACF,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpE,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,0EAA0E,CAC3E,CAAC;oBACF,OAAO;gBACT,CAAC;gBAED,MAAM,IAAA,kBAAK,EAAC,IAAI,CAAC,eAAe,CAAC,CAAC;gBAClC,OAAO,IAAI,CAAC,qBAAqB,EAAE,CAAC;YACtC,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,0CAA0C,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,CACvF,CAAC;YACF,MAAM,IAAA,kBAAK,EAAC,SAAS,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACtC,CAAC;QAID,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,qBAAqB,CACrD,IAAI,CAAC,eAAe,CACrB,CAAC;QACF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAA,kBAAK,EAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACtC,CAAC;IACH,CAAC;IAKD,KAAK,CAAC,QAAQ;;QACZ,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACzC,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACxB,WAAW,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE;YAClD,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;YAC/C,eAAe,EAAE,IAAI,CAAC,aAAa;YACnC,iBAAiB,EACf,IAAI,CAAC,oBAAoB,GAAG,CAAC;gBAC3B,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,oBAAoB,CAAC;gBAC5D,CAAC,CAAC,CAAC;YACP,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,cAAc,EAAE;gBACd,KAAK,EAAE,MAAA,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,KAAK,mCAAI,QAAQ;gBAC5B,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,MAAM;gBAC/C,UAAU,EAAE,IAAI,CAAC,YAAY;gBAC7B,QAAQ,EAAE,CAAA,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,QAAQ,EAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;aACpE;SACF,CAAC;IACJ,CAAC;IAKD,KAAK,CAAC,YAAY,CAAC,MAAc;QAC/B,MAAM,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC/D,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC;YACtD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;YACzD,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;SAC/B,CAAC,CAAC;QACH,OAAO;YACL,MAAM;YACN,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACxB,kBAAkB,EAAE,QAAQ;YAC5B,qBAAqB,EAAE,WAAW;YAClC,aAAa;SACd,CAAC;IACJ,CAAC;CACF,CAAA;AA7SY,0DAAuB;AA8IpB;IADb,IAAA,eAAI,EAAC,yBAAc,CAAC,eAAe,CAAC;;;;mEAkBpC;kCA/JU,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IA6BR,WAAA,IAAA,eAAM,EAAC,oCAAwB,CAAC,CAAA;;GA5BxB,uBAAuB,CA6SnC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RedisLike } from "../../interfaces/state/redis-like.interface";
|
|
2
|
+
export interface WithLockResult<T> {
|
|
3
|
+
acquired: boolean;
|
|
4
|
+
value?: T;
|
|
5
|
+
}
|
|
6
|
+
export interface OutlookLockStore {
|
|
7
|
+
readonly kind: "memory" | "redis";
|
|
8
|
+
acquireLock(key: string, ttlMs?: number): Promise<string | null>;
|
|
9
|
+
renewLock(key: string, token: string, ttlMs: number): Promise<boolean>;
|
|
10
|
+
releaseLock(key: string, token: string): Promise<void>;
|
|
11
|
+
clearLock(key: string): Promise<void>;
|
|
12
|
+
consumeFlag(key: string): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
export declare function generateLockToken(): string;
|
|
15
|
+
export declare class InMemoryOutlookLockStore implements OutlookLockStore {
|
|
16
|
+
readonly kind: "memory";
|
|
17
|
+
private readonly logger;
|
|
18
|
+
private readonly locks;
|
|
19
|
+
acquireLock(key: string, ttlMs?: number): Promise<string | null>;
|
|
20
|
+
renewLock(key: string, token: string, ttlMs: number): Promise<boolean>;
|
|
21
|
+
releaseLock(key: string, token: string): Promise<void>;
|
|
22
|
+
clearLock(key: string): Promise<void>;
|
|
23
|
+
consumeFlag(key: string): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
export declare class RedisOutlookLockStore implements OutlookLockStore {
|
|
26
|
+
private readonly redis;
|
|
27
|
+
private readonly keyPrefix;
|
|
28
|
+
readonly kind: "redis";
|
|
29
|
+
private readonly logger;
|
|
30
|
+
constructor(redis: RedisLike, keyPrefix: string);
|
|
31
|
+
private k;
|
|
32
|
+
acquireLock(key: string, ttlMs?: number): Promise<string | null>;
|
|
33
|
+
renewLock(key: string, token: string, ttlMs: number): Promise<boolean>;
|
|
34
|
+
releaseLock(key: string, token: string): Promise<void>;
|
|
35
|
+
clearLock(key: string): Promise<void>;
|
|
36
|
+
consumeFlag(key: string): Promise<boolean>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisOutlookLockStore = exports.InMemoryOutlookLockStore = void 0;
|
|
4
|
+
exports.generateLockToken = generateLockToken;
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
function generateLockToken() {
|
|
8
|
+
return (0, crypto_1.randomBytes)(16).toString("hex");
|
|
9
|
+
}
|
|
10
|
+
class InMemoryOutlookLockStore {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.kind = "memory";
|
|
13
|
+
this.logger = new common_1.Logger(InMemoryOutlookLockStore.name);
|
|
14
|
+
this.locks = new Map();
|
|
15
|
+
}
|
|
16
|
+
acquireLock(key, ttlMs) {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const existing = this.locks.get(key);
|
|
19
|
+
if (existing && existing.expiresAt > now) {
|
|
20
|
+
return Promise.resolve(null);
|
|
21
|
+
}
|
|
22
|
+
const token = generateLockToken();
|
|
23
|
+
const expiresAt = ttlMs === undefined ? Infinity : now + ttlMs;
|
|
24
|
+
this.locks.set(key, { token, expiresAt });
|
|
25
|
+
return Promise.resolve(token);
|
|
26
|
+
}
|
|
27
|
+
renewLock(key, token, ttlMs) {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const existing = this.locks.get(key);
|
|
30
|
+
if (!existing || existing.token !== token || existing.expiresAt <= now) {
|
|
31
|
+
return Promise.resolve(false);
|
|
32
|
+
}
|
|
33
|
+
existing.expiresAt = now + ttlMs;
|
|
34
|
+
return Promise.resolve(true);
|
|
35
|
+
}
|
|
36
|
+
releaseLock(key, token) {
|
|
37
|
+
const existing = this.locks.get(key);
|
|
38
|
+
if (existing && existing.token === token) {
|
|
39
|
+
this.locks.delete(key);
|
|
40
|
+
}
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
}
|
|
43
|
+
clearLock(key) {
|
|
44
|
+
this.locks.delete(key);
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
consumeFlag(key) {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const existing = this.locks.get(key);
|
|
50
|
+
const present = !!existing && existing.expiresAt > now;
|
|
51
|
+
this.locks.delete(key);
|
|
52
|
+
return Promise.resolve(present);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.InMemoryOutlookLockStore = InMemoryOutlookLockStore;
|
|
56
|
+
const RELEASE_LUA = `
|
|
57
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
58
|
+
return redis.call('DEL', KEYS[1])
|
|
59
|
+
else
|
|
60
|
+
return 0
|
|
61
|
+
end
|
|
62
|
+
`;
|
|
63
|
+
const RENEW_LUA = `
|
|
64
|
+
if redis.call('GET', KEYS[1]) == ARGV[1] then
|
|
65
|
+
return redis.call('PEXPIRE', KEYS[1], ARGV[2])
|
|
66
|
+
else
|
|
67
|
+
return 0
|
|
68
|
+
end
|
|
69
|
+
`;
|
|
70
|
+
const CONSUME_FLAG_LUA = `
|
|
71
|
+
return redis.call('DEL', KEYS[1])
|
|
72
|
+
`;
|
|
73
|
+
class RedisOutlookLockStore {
|
|
74
|
+
constructor(redis, keyPrefix) {
|
|
75
|
+
this.redis = redis;
|
|
76
|
+
this.keyPrefix = keyPrefix;
|
|
77
|
+
this.kind = "redis";
|
|
78
|
+
this.logger = new common_1.Logger(RedisOutlookLockStore.name);
|
|
79
|
+
}
|
|
80
|
+
k(key) {
|
|
81
|
+
return `${this.keyPrefix}lock:${key}`;
|
|
82
|
+
}
|
|
83
|
+
async acquireLock(key, ttlMs) {
|
|
84
|
+
const token = generateLockToken();
|
|
85
|
+
try {
|
|
86
|
+
const result = ttlMs === undefined
|
|
87
|
+
? await this.redis.set(this.k(key), token, "NX")
|
|
88
|
+
: await this.redis.set(this.k(key), token, "PX", ttlMs, "NX");
|
|
89
|
+
return result === "OK" ? token : null;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
this.logger.error(`[acquireLock] Redis error for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async renewLock(key, token, ttlMs) {
|
|
97
|
+
try {
|
|
98
|
+
const result = await this.redis.eval(RENEW_LUA, 1, this.k(key), token, ttlMs);
|
|
99
|
+
return result === 1;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
this.logger.error(`[renewLock] Redis error for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async releaseLock(key, token) {
|
|
107
|
+
try {
|
|
108
|
+
await this.redis.eval(RELEASE_LUA, 1, this.k(key), token);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
this.logger.error(`[releaseLock] Redis error for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async clearLock(key) {
|
|
115
|
+
try {
|
|
116
|
+
await this.redis.del(this.k(key));
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
this.logger.error(`[clearLock] Redis error for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async consumeFlag(key) {
|
|
123
|
+
try {
|
|
124
|
+
const removed = await this.redis.eval(CONSUME_FLAG_LUA, 1, this.k(key));
|
|
125
|
+
return removed === 1;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
this.logger.error(`[consumeFlag] Redis error for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.RedisOutlookLockStore = RedisOutlookLockStore;
|
|
134
|
+
//# sourceMappingURL=outlook-lock.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outlook-lock.store.js","sourceRoot":"","sources":["../../../src/services/shared/outlook-lock.store.ts"],"names":[],"mappings":"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"}
|