homebridge-myleviton 3.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.
Files changed (72) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +112 -0
  3. package/config.schema.json +136 -0
  4. package/dist/api/cache.d.ts +108 -0
  5. package/dist/api/cache.d.ts.map +1 -0
  6. package/dist/api/cache.js +206 -0
  7. package/dist/api/cache.js.map +1 -0
  8. package/dist/api/circuit-breaker.d.ts +118 -0
  9. package/dist/api/circuit-breaker.d.ts.map +1 -0
  10. package/dist/api/circuit-breaker.js +223 -0
  11. package/dist/api/circuit-breaker.js.map +1 -0
  12. package/dist/api/client.d.ts +116 -0
  13. package/dist/api/client.d.ts.map +1 -0
  14. package/dist/api/client.js +358 -0
  15. package/dist/api/client.js.map +1 -0
  16. package/dist/api/index.d.ts +23 -0
  17. package/dist/api/index.d.ts.map +1 -0
  18. package/dist/api/index.js +47 -0
  19. package/dist/api/index.js.map +1 -0
  20. package/dist/api/persistence.d.ts +107 -0
  21. package/dist/api/persistence.d.ts.map +1 -0
  22. package/dist/api/persistence.js +285 -0
  23. package/dist/api/persistence.js.map +1 -0
  24. package/dist/api/rate-limiter.d.ts +102 -0
  25. package/dist/api/rate-limiter.d.ts.map +1 -0
  26. package/dist/api/rate-limiter.js +173 -0
  27. package/dist/api/rate-limiter.js.map +1 -0
  28. package/dist/api/request-queue.d.ts +104 -0
  29. package/dist/api/request-queue.d.ts.map +1 -0
  30. package/dist/api/request-queue.js +223 -0
  31. package/dist/api/request-queue.js.map +1 -0
  32. package/dist/api/websocket.d.ts +116 -0
  33. package/dist/api/websocket.d.ts.map +1 -0
  34. package/dist/api/websocket.js +319 -0
  35. package/dist/api/websocket.js.map +1 -0
  36. package/dist/errors/index.d.ts +182 -0
  37. package/dist/errors/index.d.ts.map +1 -0
  38. package/dist/errors/index.js +273 -0
  39. package/dist/errors/index.js.map +1 -0
  40. package/dist/index.d.ts +16 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +42 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/platform.d.ts +139 -0
  45. package/dist/platform.d.ts.map +1 -0
  46. package/dist/platform.js +664 -0
  47. package/dist/platform.js.map +1 -0
  48. package/dist/types/index.d.ts +225 -0
  49. package/dist/types/index.d.ts.map +1 -0
  50. package/dist/types/index.js +34 -0
  51. package/dist/types/index.js.map +1 -0
  52. package/dist/utils/index.d.ts +15 -0
  53. package/dist/utils/index.d.ts.map +1 -0
  54. package/dist/utils/index.js +52 -0
  55. package/dist/utils/index.js.map +1 -0
  56. package/dist/utils/logger.d.ts +103 -0
  57. package/dist/utils/logger.d.ts.map +1 -0
  58. package/dist/utils/logger.js +184 -0
  59. package/dist/utils/logger.js.map +1 -0
  60. package/dist/utils/retry.d.ts +56 -0
  61. package/dist/utils/retry.d.ts.map +1 -0
  62. package/dist/utils/retry.js +141 -0
  63. package/dist/utils/retry.js.map +1 -0
  64. package/dist/utils/sanitizers.d.ts +37 -0
  65. package/dist/utils/sanitizers.d.ts.map +1 -0
  66. package/dist/utils/sanitizers.js +128 -0
  67. package/dist/utils/sanitizers.js.map +1 -0
  68. package/dist/utils/validators.d.ts +51 -0
  69. package/dist/utils/validators.d.ts.map +1 -0
  70. package/dist/utils/validators.js +243 -0
  71. package/dist/utils/validators.js.map +1 -0
  72. package/package.json +69 -0
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 tbaur
4
+ *
5
+ * Licensed under the Apache License, Version 2.0
6
+ * See LICENSE file for full license text
7
+ *
8
+ * @fileoverview Device state persistence for faster startup and offline resilience
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.DevicePersistence = exports.PERSISTENCE_FILE_NAME = exports.DEFAULT_PERSISTENCE_CONFIG = void 0;
45
+ exports.getDevicePersistence = getDevicePersistence;
46
+ exports.resetGlobalPersistence = resetGlobalPersistence;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ /**
50
+ * Default persistence configuration
51
+ */
52
+ exports.DEFAULT_PERSISTENCE_CONFIG = {
53
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
54
+ maxDevices: 200,
55
+ };
56
+ /**
57
+ * Default persistence file name
58
+ */
59
+ exports.PERSISTENCE_FILE_NAME = '.homebridge-myleviton-state.json';
60
+ /**
61
+ * Device state persistence manager
62
+ * Stores device states for faster startup and offline resilience
63
+ */
64
+ class DevicePersistence {
65
+ storagePath;
66
+ maxAge;
67
+ maxDevices;
68
+ deviceStates = new Map();
69
+ loaded = false;
70
+ dirty = false;
71
+ constructor(storagePath, config = {}) {
72
+ const merged = { ...exports.DEFAULT_PERSISTENCE_CONFIG, ...config };
73
+ this.storagePath = storagePath || path.join(process.env.HOME || '/tmp', exports.PERSISTENCE_FILE_NAME);
74
+ this.maxAge = merged.maxAge ?? 24 * 60 * 60 * 1000;
75
+ this.maxDevices = merged.maxDevices ?? 200;
76
+ }
77
+ /**
78
+ * Load persisted device states from disk
79
+ */
80
+ load() {
81
+ if (this.loaded) {
82
+ return this.deviceStates;
83
+ }
84
+ try {
85
+ if (fs.existsSync(this.storagePath)) {
86
+ const data = fs.readFileSync(this.storagePath, 'utf8');
87
+ const parsed = JSON.parse(data);
88
+ // Validate structure
89
+ if (parsed && typeof parsed === 'object' && parsed.devices) {
90
+ const now = Date.now();
91
+ const maxAge = this.maxAge;
92
+ for (const [id, state] of Object.entries(parsed.devices)) {
93
+ if (state && typeof state === 'object') {
94
+ // Check if data is too old
95
+ const cachedAt = parsed.timestamp || 0;
96
+ if (now - cachedAt < maxAge) {
97
+ this.deviceStates.set(id, {
98
+ ...state,
99
+ _cached: true,
100
+ _cachedAt: cachedAt,
101
+ });
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ catch {
109
+ // Ignore load errors - start fresh
110
+ this.deviceStates.clear();
111
+ }
112
+ this.loaded = true;
113
+ return this.deviceStates;
114
+ }
115
+ /**
116
+ * Save device states to disk
117
+ */
118
+ save() {
119
+ if (!this.dirty && this.loaded) {
120
+ return true; // Nothing to save
121
+ }
122
+ try {
123
+ // Clean up internal properties before saving
124
+ const devices = {};
125
+ // Limit size
126
+ let count = 0;
127
+ for (const [id, state] of this.deviceStates.entries()) {
128
+ if (count >= this.maxDevices) {
129
+ break;
130
+ }
131
+ const { _cached, _cachedAt, _updatedAt, ...cleanState } = state;
132
+ devices[id] = cleanState;
133
+ count++;
134
+ }
135
+ const data = JSON.stringify({
136
+ version: 1,
137
+ timestamp: Date.now(),
138
+ devices,
139
+ }, null, 2);
140
+ // Write atomically using temp file
141
+ const tempPath = `${this.storagePath}.tmp`;
142
+ fs.writeFileSync(tempPath, data, 'utf8');
143
+ fs.renameSync(tempPath, this.storagePath);
144
+ this.dirty = false;
145
+ return true;
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ }
151
+ /**
152
+ * Update state for a device
153
+ */
154
+ updateDevice(deviceId, state) {
155
+ if (!deviceId) {
156
+ return;
157
+ }
158
+ const existing = this.deviceStates.get(deviceId) || { id: deviceId };
159
+ this.deviceStates.set(deviceId, {
160
+ ...existing,
161
+ ...state,
162
+ id: deviceId,
163
+ _cached: false,
164
+ _updatedAt: Date.now(),
165
+ });
166
+ this.dirty = true;
167
+ }
168
+ /**
169
+ * Update device from API status
170
+ */
171
+ updateFromStatus(deviceId, status) {
172
+ this.updateDevice(deviceId, {
173
+ power: status.power,
174
+ brightness: status.brightness,
175
+ });
176
+ }
177
+ /**
178
+ * Get cached state for a device
179
+ */
180
+ getDevice(deviceId) {
181
+ this.load(); // Ensure loaded
182
+ return this.deviceStates.get(deviceId) || null;
183
+ }
184
+ /**
185
+ * Check if device has fresh cached data
186
+ */
187
+ hasFreshCache(deviceId, maxAge = this.maxAge) {
188
+ const state = this.getDevice(deviceId);
189
+ if (!state) {
190
+ return false;
191
+ }
192
+ const updatedAt = state._updatedAt || state._cachedAt || 0;
193
+ return Date.now() - updatedAt < maxAge;
194
+ }
195
+ /**
196
+ * Get device status from cache (for fallback)
197
+ */
198
+ getCachedStatus(deviceId) {
199
+ const state = this.getDevice(deviceId);
200
+ if (!state || !state.power) {
201
+ return null;
202
+ }
203
+ return {
204
+ id: state.id,
205
+ power: state.power,
206
+ brightness: state.brightness,
207
+ };
208
+ }
209
+ /**
210
+ * Remove a device from persistence
211
+ */
212
+ removeDevice(deviceId) {
213
+ const deleted = this.deviceStates.delete(deviceId);
214
+ if (deleted) {
215
+ this.dirty = true;
216
+ }
217
+ return deleted;
218
+ }
219
+ /**
220
+ * Clear all persisted states
221
+ */
222
+ clear() {
223
+ this.deviceStates.clear();
224
+ this.dirty = true;
225
+ try {
226
+ if (fs.existsSync(this.storagePath)) {
227
+ fs.unlinkSync(this.storagePath);
228
+ }
229
+ }
230
+ catch {
231
+ // Ignore delete errors
232
+ }
233
+ }
234
+ /**
235
+ * Get all cached device states
236
+ */
237
+ getAllDevices() {
238
+ this.load();
239
+ return new Map(this.deviceStates);
240
+ }
241
+ /**
242
+ * Get device count
243
+ */
244
+ get size() {
245
+ return this.deviceStates.size;
246
+ }
247
+ /**
248
+ * Check if persistence has been modified
249
+ */
250
+ get isDirty() {
251
+ return this.dirty;
252
+ }
253
+ /**
254
+ * Get persistence statistics
255
+ */
256
+ getStats() {
257
+ return {
258
+ deviceCount: this.size,
259
+ loaded: this.loaded,
260
+ dirty: this.dirty,
261
+ storagePath: this.storagePath,
262
+ };
263
+ }
264
+ }
265
+ exports.DevicePersistence = DevicePersistence;
266
+ /**
267
+ * Global persistence instance
268
+ */
269
+ let globalPersistence = null;
270
+ /**
271
+ * Get or create the global persistence instance
272
+ */
273
+ function getDevicePersistence(storagePath) {
274
+ if (!globalPersistence) {
275
+ globalPersistence = new DevicePersistence(storagePath);
276
+ }
277
+ return globalPersistence;
278
+ }
279
+ /**
280
+ * Reset the global persistence (for testing)
281
+ */
282
+ function resetGlobalPersistence() {
283
+ globalPersistence = null;
284
+ }
285
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/api/persistence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Copyright (c) 2026 tbaur
3
+ *
4
+ * Licensed under the Apache License, Version 2.0
5
+ * See LICENSE file for full license text
6
+ *
7
+ * @fileoverview Token bucket rate limiter for API requests
8
+ */
9
+ /**
10
+ * Rate limiter configuration
11
+ */
12
+ export interface RateLimiterConfig {
13
+ /** Maximum requests per window */
14
+ maxRequests: number;
15
+ /** Window duration in milliseconds */
16
+ windowMs: number;
17
+ /** Whether to wait when rate limited (vs throwing immediately) */
18
+ waitOnLimit?: boolean;
19
+ /** Maximum wait time when waitOnLimit is true */
20
+ maxWaitMs?: number;
21
+ }
22
+ /**
23
+ * Default rate limiter configuration
24
+ * Leviton allows max 99 devices per residence, 20 residences per account
25
+ * At startup: 99 devices × 2 queries (power + brightness) = 198 requests
26
+ * Rate limiter only applies to WRITE operations
27
+ */
28
+ export declare const DEFAULT_RATE_LIMITER_CONFIG: RateLimiterConfig;
29
+ /**
30
+ * Token bucket rate limiter
31
+ * Prevents overwhelming the Leviton API with too many requests
32
+ */
33
+ export declare class RateLimiter {
34
+ private readonly maxRequests;
35
+ private readonly windowMs;
36
+ private readonly waitOnLimit;
37
+ private readonly maxWaitMs;
38
+ private requests;
39
+ constructor(config?: Partial<RateLimiterConfig>);
40
+ /**
41
+ * Clean up expired request timestamps
42
+ */
43
+ private cleanup;
44
+ /**
45
+ * Get current request count in window
46
+ */
47
+ get currentCount(): number;
48
+ /**
49
+ * Get remaining requests in window
50
+ */
51
+ get remaining(): number;
52
+ /**
53
+ * Get time until window resets (oldest request expires)
54
+ */
55
+ get resetTime(): number;
56
+ /**
57
+ * Check if a request can be made without blocking
58
+ */
59
+ canAcquire(): boolean;
60
+ /**
61
+ * Try to acquire a request token
62
+ * @returns true if acquired, false if rate limited
63
+ */
64
+ tryAcquire(): boolean;
65
+ /**
66
+ * Acquire a request token, throwing if rate limited
67
+ * @throws RateLimitError if rate limited and waitOnLimit is false
68
+ */
69
+ acquire(): void;
70
+ /**
71
+ * Acquire a request token, waiting if necessary
72
+ * @throws RateLimitError if wait exceeds maxWaitMs
73
+ */
74
+ acquireAsync(): Promise<void>;
75
+ /**
76
+ * Execute a function with rate limiting
77
+ */
78
+ execute<T>(fn: () => Promise<T>): Promise<T>;
79
+ /**
80
+ * Reset the rate limiter
81
+ */
82
+ reset(): void;
83
+ /**
84
+ * Get rate limiter status for monitoring
85
+ */
86
+ getStatus(): {
87
+ currentCount: number;
88
+ maxRequests: number;
89
+ remaining: number;
90
+ resetTime: number;
91
+ windowMs: number;
92
+ };
93
+ }
94
+ /**
95
+ * Get or create the global rate limiter
96
+ */
97
+ export declare function getRateLimiter(config?: Partial<RateLimiterConfig>): RateLimiter;
98
+ /**
99
+ * Reset the global rate limiter (for testing)
100
+ */
101
+ export declare function resetGlobalRateLimiter(): void;
102
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/api/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,iBAKzC,CAAA;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAe;gBAEnB,MAAM,GAAE,OAAO,CAAC,iBAAiB,CAAM;IAQnD;;OAEG;IACH,OAAO,CAAC,OAAO;IAKf;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAGzB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAItB;IAED;;OAEG;IACH,UAAU,IAAI,OAAO;IAKrB;;;OAGG;IACH,UAAU,IAAI,OAAO;IAWrB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAUf;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBnC;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IASlD;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,SAAS,IAAI;QACX,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;KACjB;CASF;AAOD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,WAAW,CAK/E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2026 tbaur
4
+ *
5
+ * Licensed under the Apache License, Version 2.0
6
+ * See LICENSE file for full license text
7
+ *
8
+ * @fileoverview Token bucket rate limiter for API requests
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RateLimiter = exports.DEFAULT_RATE_LIMITER_CONFIG = void 0;
12
+ exports.getRateLimiter = getRateLimiter;
13
+ exports.resetGlobalRateLimiter = resetGlobalRateLimiter;
14
+ const errors_1 = require("../errors");
15
+ const retry_1 = require("../utils/retry");
16
+ /**
17
+ * Default rate limiter configuration
18
+ * Leviton allows max 99 devices per residence, 20 residences per account
19
+ * At startup: 99 devices × 2 queries (power + brightness) = 198 requests
20
+ * Rate limiter only applies to WRITE operations
21
+ */
22
+ exports.DEFAULT_RATE_LIMITER_CONFIG = {
23
+ maxRequests: 300,
24
+ windowMs: 60000, // 1 minute
25
+ waitOnLimit: false,
26
+ maxWaitMs: 30000,
27
+ };
28
+ /**
29
+ * Token bucket rate limiter
30
+ * Prevents overwhelming the Leviton API with too many requests
31
+ */
32
+ class RateLimiter {
33
+ maxRequests;
34
+ windowMs;
35
+ waitOnLimit;
36
+ maxWaitMs;
37
+ requests = [];
38
+ constructor(config = {}) {
39
+ const merged = { ...exports.DEFAULT_RATE_LIMITER_CONFIG, ...config };
40
+ this.maxRequests = merged.maxRequests;
41
+ this.windowMs = merged.windowMs;
42
+ this.waitOnLimit = merged.waitOnLimit ?? false;
43
+ this.maxWaitMs = merged.maxWaitMs ?? 30000;
44
+ }
45
+ /**
46
+ * Clean up expired request timestamps
47
+ */
48
+ cleanup() {
49
+ const now = Date.now();
50
+ this.requests = this.requests.filter(timestamp => now - timestamp < this.windowMs);
51
+ }
52
+ /**
53
+ * Get current request count in window
54
+ */
55
+ get currentCount() {
56
+ this.cleanup();
57
+ return this.requests.length;
58
+ }
59
+ /**
60
+ * Get remaining requests in window
61
+ */
62
+ get remaining() {
63
+ return Math.max(0, this.maxRequests - this.currentCount);
64
+ }
65
+ /**
66
+ * Get time until window resets (oldest request expires)
67
+ */
68
+ get resetTime() {
69
+ if (this.requests.length === 0) {
70
+ return 0;
71
+ }
72
+ const oldest = Math.min(...this.requests);
73
+ return Math.max(0, this.windowMs - (Date.now() - oldest));
74
+ }
75
+ /**
76
+ * Check if a request can be made without blocking
77
+ */
78
+ canAcquire() {
79
+ this.cleanup();
80
+ return this.requests.length < this.maxRequests;
81
+ }
82
+ /**
83
+ * Try to acquire a request token
84
+ * @returns true if acquired, false if rate limited
85
+ */
86
+ tryAcquire() {
87
+ this.cleanup();
88
+ if (this.requests.length >= this.maxRequests) {
89
+ return false;
90
+ }
91
+ this.requests.push(Date.now());
92
+ return true;
93
+ }
94
+ /**
95
+ * Acquire a request token, throwing if rate limited
96
+ * @throws RateLimitError if rate limited and waitOnLimit is false
97
+ */
98
+ acquire() {
99
+ if (!this.tryAcquire()) {
100
+ const retryAfter = Math.ceil(this.resetTime / 1000);
101
+ throw new errors_1.RateLimitError(`Rate limit exceeded. ${this.maxRequests} requests per ${this.windowMs / 1000}s`, retryAfter);
102
+ }
103
+ }
104
+ /**
105
+ * Acquire a request token, waiting if necessary
106
+ * @throws RateLimitError if wait exceeds maxWaitMs
107
+ */
108
+ async acquireAsync() {
109
+ const startTime = Date.now();
110
+ while (!this.tryAcquire()) {
111
+ const elapsed = Date.now() - startTime;
112
+ if (elapsed >= this.maxWaitMs) {
113
+ throw new errors_1.RateLimitError(`Rate limit wait exceeded ${this.maxWaitMs}ms`, Math.ceil((this.maxWaitMs - elapsed) / 1000));
114
+ }
115
+ // Wait for the oldest request to expire
116
+ const waitTime = Math.min(this.resetTime, this.maxWaitMs - elapsed, 1000);
117
+ if (waitTime > 0) {
118
+ await (0, retry_1.sleep)(waitTime);
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Execute a function with rate limiting
124
+ */
125
+ async execute(fn) {
126
+ if (this.waitOnLimit) {
127
+ await this.acquireAsync();
128
+ }
129
+ else {
130
+ this.acquire();
131
+ }
132
+ return fn();
133
+ }
134
+ /**
135
+ * Reset the rate limiter
136
+ */
137
+ reset() {
138
+ this.requests = [];
139
+ }
140
+ /**
141
+ * Get rate limiter status for monitoring
142
+ */
143
+ getStatus() {
144
+ return {
145
+ currentCount: this.currentCount,
146
+ maxRequests: this.maxRequests,
147
+ remaining: this.remaining,
148
+ resetTime: this.resetTime,
149
+ windowMs: this.windowMs,
150
+ };
151
+ }
152
+ }
153
+ exports.RateLimiter = RateLimiter;
154
+ /**
155
+ * Global rate limiter instance
156
+ */
157
+ let globalRateLimiter = null;
158
+ /**
159
+ * Get or create the global rate limiter
160
+ */
161
+ function getRateLimiter(config) {
162
+ if (!globalRateLimiter) {
163
+ globalRateLimiter = new RateLimiter(config);
164
+ }
165
+ return globalRateLimiter;
166
+ }
167
+ /**
168
+ * Reset the global rate limiter (for testing)
169
+ */
170
+ function resetGlobalRateLimiter() {
171
+ globalRateLimiter = null;
172
+ }
173
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/api/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Copyright (c) 2026 tbaur
3
+ *
4
+ * Licensed under the Apache License, Version 2.0
5
+ * See LICENSE file for full license text
6
+ *
7
+ * @fileoverview Request queue with priority and deduplication
8
+ */
9
+ /**
10
+ * Request queue configuration
11
+ */
12
+ export interface RequestQueueConfig {
13
+ /** Maximum concurrent requests */
14
+ maxConcurrent: number;
15
+ /** Maximum queue size */
16
+ maxQueueSize: number;
17
+ /** Request timeout in ms */
18
+ requestTimeout: number;
19
+ }
20
+ /**
21
+ * Default request queue configuration
22
+ */
23
+ export declare const DEFAULT_REQUEST_QUEUE_CONFIG: RequestQueueConfig;
24
+ /**
25
+ * Request queue with priority ordering and deduplication
26
+ */
27
+ export declare class RequestQueue {
28
+ private readonly maxConcurrent;
29
+ private readonly maxQueueSize;
30
+ private readonly requestTimeout;
31
+ private queue;
32
+ private inFlight;
33
+ private processing;
34
+ constructor(config?: Partial<RequestQueueConfig>);
35
+ /**
36
+ * Get queue length
37
+ */
38
+ get length(): number;
39
+ /**
40
+ * Get number of in-flight requests
41
+ */
42
+ get inFlightCount(): number;
43
+ /**
44
+ * Check if queue is full
45
+ */
46
+ get isFull(): boolean;
47
+ /**
48
+ * Add a request to the queue
49
+ */
50
+ add<T>(execute: () => Promise<T>, options?: {
51
+ priority?: 'high' | 'normal' | 'low';
52
+ dedupeKey?: string;
53
+ }): Promise<T>;
54
+ /**
55
+ * Insert request into queue by priority
56
+ */
57
+ private enqueue;
58
+ /**
59
+ * Process queued requests
60
+ */
61
+ private processQueue;
62
+ /**
63
+ * Execute a single request with timeout
64
+ */
65
+ private executeRequest;
66
+ /**
67
+ * Clear the queue
68
+ */
69
+ clear(): void;
70
+ /**
71
+ * Get queue statistics
72
+ */
73
+ getStats(): {
74
+ queueLength: number;
75
+ inFlight: number;
76
+ maxConcurrent: number;
77
+ maxQueueSize: number;
78
+ };
79
+ /**
80
+ * Wait for all in-flight requests to complete
81
+ */
82
+ drain(): Promise<void>;
83
+ }
84
+ /**
85
+ * In-flight request deduplication map
86
+ */
87
+ export declare class RequestDeduplicator {
88
+ private readonly inFlight;
89
+ private readonly maxSize;
90
+ constructor(maxSize?: number);
91
+ /**
92
+ * Execute a request with deduplication
93
+ */
94
+ execute<T>(key: string, fn: () => Promise<T>): Promise<T>;
95
+ /**
96
+ * Get number of in-flight requests
97
+ */
98
+ get size(): number;
99
+ /**
100
+ * Clear all tracked requests
101
+ */
102
+ clear(): void;
103
+ }
104
+ //# sourceMappingURL=request-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-queue.d.ts","sourceRoot":"","sources":["../../src/api/request-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAA;IACrB,yBAAyB;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,4BAA4B;IAC5B,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,4BAA4B,EAAE,kBAI1C,CAAA;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IAEvC,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,UAAU,CAAQ;gBAEd,MAAM,GAAE,OAAO,CAAC,kBAAkB,CAAM;IAOpD;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,GAAG,CAAC,CAAC,EACH,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE;QACP,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;QACpC,SAAS,CAAC,EAAE,MAAM,CAAA;KACd,GACL,OAAO,CAAC,CAAC,CAAC;IA4Bb;;OAEG;IACH,OAAO,CAAC,OAAO;IAiBf;;OAEG;YACW,YAAY;IAwB1B;;OAEG;YACW,cAAc;IAsB5B;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,QAAQ,IAAI;QACV,WAAW,EAAE,MAAM,CAAA;QACnB,QAAQ,EAAE,MAAM,CAAA;QAChB,aAAa,EAAE,MAAM,CAAA;QACrB,YAAY,EAAE,MAAM,CAAA;KACrB;IASD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IACpE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBAEpB,OAAO,SAAM;IAIzB;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAwB/D;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}