feat: add CW callback route and optimize cache refresh workflows
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* CW API Concurrency Limiter
|
||||
*
|
||||
* Limits the number of simultaneous in-flight requests to the ConnectWise
|
||||
* API. CW responds significantly slower under high concurrency (observed
|
||||
* ~3× slower at 9 concurrent vs 5–6 concurrent), so bounding the
|
||||
* parallelism actually reduces total wall-clock time.
|
||||
*
|
||||
* Implemented as an Axios request interceptor that gates on a simple
|
||||
* counting semaphore. When the limit is reached, new requests queue and
|
||||
* resolve in FIFO order as earlier requests complete.
|
||||
*/
|
||||
|
||||
import type { AxiosInstance, InternalAxiosRequestConfig } from "axios";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Semaphore
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class Semaphore {
|
||||
private _current = 0;
|
||||
private _queue: (() => void)[] = [];
|
||||
|
||||
constructor(private _max: number) {}
|
||||
|
||||
/** Acquire a slot — resolves immediately if under the limit, else waits. */
|
||||
acquire(): Promise<void> {
|
||||
if (this._current < this._max) {
|
||||
this._current++;
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise<void>((resolve) => {
|
||||
this._queue.push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/** Release a slot — wakes the next queued caller, if any. */
|
||||
release(): void {
|
||||
const next = this._queue.shift();
|
||||
if (next) {
|
||||
// Hand the slot directly to the next waiter (don't decrement)
|
||||
next();
|
||||
} else {
|
||||
this._current--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interceptor attachment
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Attach a concurrency-limiting interceptor to an Axios instance.
|
||||
*
|
||||
* @param api - The Axios instance to limit.
|
||||
* @param max - Maximum concurrent in-flight requests (default: 6).
|
||||
*/
|
||||
export function attachCwConcurrencyLimiter(api: AxiosInstance, max = 6): void {
|
||||
const sem = new Semaphore(max);
|
||||
|
||||
// Request interceptor: wait for a slot before the request fires
|
||||
api.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
|
||||
await sem.acquire();
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor: release the slot on success or failure
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
sem.release();
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
sem.release();
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user