quickbase-js 0.1.0-beta.11 → 0.1.0-beta.12
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/CHANGELOG.md +7 -0
- package/README.md +10 -18
- package/dist/esm/quickbase.js +70 -28
- package/dist/esm/quickbase.js.map +1 -1
- package/dist/esm/quickbase.min.js +1 -1
- package/dist/esm/quickbase.min.js.map +1 -1
- package/dist/umd/quickbase.umd.js +70 -28
- package/dist/umd/quickbase.umd.js.map +1 -1
- package/dist/umd/quickbase.umd.min.js +1 -1
- package/dist/umd/quickbase.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/ThrottleBucket.ts +52 -16
- package/src/generated-unified/QuickbaseClient.ts +1 -1
- package/src/invokeMethod.ts +15 -8
- package/src/quickbaseClient.ts +3 -3
- package/src/rateLimiter.ts +8 -3
- package/tests/vitest/qb/ThrottleBucket.test.ts +77 -0
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +2 -2
- package/tests/vitest/unit/ThrottleBucket.test.ts +16 -10
|
@@ -7654,8 +7654,8 @@
|
|
|
7654
7654
|
const hasBody = "body" in params && params.body !== undefined;
|
|
7655
7655
|
const body = hasBody ? params.body : undefined;
|
|
7656
7656
|
const restParams = hasBody
|
|
7657
|
-
? Object.fromEntries(Object.entries(params).filter(([key]) => key !== "body"))
|
|
7658
|
-
: { ...params };
|
|
7657
|
+
? Object.fromEntries(Object.entries(params).filter(([key]) => key !== "body" && key !== "startTime"))
|
|
7658
|
+
: { ...params, startTime: undefined };
|
|
7659
7659
|
const requestParameters = {
|
|
7660
7660
|
...restParams,
|
|
7661
7661
|
...(hasBody ? { generated: body } : {}),
|
|
@@ -7720,12 +7720,21 @@
|
|
|
7720
7720
|
return { message, status };
|
|
7721
7721
|
}
|
|
7722
7722
|
while (attempt < maxAttempts) {
|
|
7723
|
+
let acquired = false;
|
|
7723
7724
|
try {
|
|
7724
7725
|
await rateLimiter.throttle();
|
|
7725
|
-
|
|
7726
|
+
acquired = true;
|
|
7727
|
+
const postThrottleTime = Date.now(); // Capture post-throttle time
|
|
7728
|
+
if (params.startTime !== undefined)
|
|
7729
|
+
params.startTime = postThrottleTime; // Update startTime for testing
|
|
7730
|
+
const responsePromise = methodInfo.method(requestParameters, requestOptions);
|
|
7731
|
+
rateLimiter.release(); // Release slot immediately after starting fetch
|
|
7732
|
+
const response = await responsePromise;
|
|
7726
7733
|
return await processResponse(response);
|
|
7727
7734
|
}
|
|
7728
7735
|
catch (error) {
|
|
7736
|
+
if (acquired)
|
|
7737
|
+
rateLimiter.release();
|
|
7729
7738
|
let status;
|
|
7730
7739
|
let message;
|
|
7731
7740
|
let response;
|
|
@@ -7738,7 +7747,7 @@
|
|
|
7738
7747
|
({ message, status } = await parseErrorResponse(response));
|
|
7739
7748
|
}
|
|
7740
7749
|
else {
|
|
7741
|
-
throw error;
|
|
7750
|
+
throw error;
|
|
7742
7751
|
}
|
|
7743
7752
|
if (status === 429) {
|
|
7744
7753
|
if (!(error instanceof ResponseError)) {
|
|
@@ -7754,15 +7763,13 @@
|
|
|
7754
7763
|
attempt++;
|
|
7755
7764
|
continue;
|
|
7756
7765
|
}
|
|
7757
|
-
// Handle authentication errors
|
|
7758
7766
|
let newToken;
|
|
7759
7767
|
try {
|
|
7760
7768
|
newToken = await authStrategy.handleError(status, params, attempt, maxAttempts, debug, methodName);
|
|
7761
7769
|
}
|
|
7762
7770
|
catch (authError) {
|
|
7763
|
-
throw authError;
|
|
7771
|
+
throw authError;
|
|
7764
7772
|
}
|
|
7765
|
-
// Only proceed with retry if we have a valid new token
|
|
7766
7773
|
if (newToken !== null) {
|
|
7767
7774
|
token = newToken;
|
|
7768
7775
|
authStrategy.applyHeaders(baseHeaders, token);
|
|
@@ -7772,7 +7779,6 @@
|
|
|
7772
7779
|
attempt++;
|
|
7773
7780
|
continue;
|
|
7774
7781
|
}
|
|
7775
|
-
// If no new token was provided, throw the original error
|
|
7776
7782
|
throw new Error(`API Error: ${message} (Status: ${status})`);
|
|
7777
7783
|
}
|
|
7778
7784
|
}
|
|
@@ -7780,41 +7786,71 @@
|
|
|
7780
7786
|
}
|
|
7781
7787
|
|
|
7782
7788
|
// src/ThrottleBucket.ts
|
|
7783
|
-
class
|
|
7789
|
+
class ConcurrentThrottleBucket {
|
|
7784
7790
|
tokens;
|
|
7785
7791
|
maxTokens;
|
|
7786
|
-
refillRate;
|
|
7792
|
+
refillRate;
|
|
7787
7793
|
lastRefill;
|
|
7788
|
-
|
|
7794
|
+
semaphore;
|
|
7789
7795
|
constructor(rate, burst) {
|
|
7790
7796
|
this.tokens = burst;
|
|
7791
7797
|
this.maxTokens = burst;
|
|
7792
7798
|
this.refillRate = rate;
|
|
7793
7799
|
this.lastRefill = Date.now();
|
|
7800
|
+
this.semaphore = new Semaphore(burst);
|
|
7794
7801
|
}
|
|
7795
7802
|
refill() {
|
|
7796
7803
|
const now = Date.now();
|
|
7797
|
-
const elapsed = (now - this.lastRefill) / 1000;
|
|
7804
|
+
const elapsed = (now - this.lastRefill) / 1000;
|
|
7798
7805
|
const newTokens = elapsed * this.refillRate;
|
|
7799
7806
|
this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
|
|
7800
7807
|
this.lastRefill = now;
|
|
7801
7808
|
}
|
|
7802
7809
|
async acquire() {
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
this.pending = (async () => {
|
|
7806
|
-
await previous; // Wait for prior calls to complete
|
|
7810
|
+
await this.semaphore.acquire();
|
|
7811
|
+
try {
|
|
7807
7812
|
this.refill();
|
|
7808
|
-
|
|
7809
|
-
this.tokens
|
|
7810
|
-
|
|
7813
|
+
while (this.tokens < 1) {
|
|
7814
|
+
const waitTime = ((1 - this.tokens) / this.refillRate) * 1000;
|
|
7815
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
7816
|
+
this.refill();
|
|
7811
7817
|
}
|
|
7812
|
-
const waitTime = ((1 - this.tokens) / this.refillRate) * 1000; // ms until next token
|
|
7813
|
-
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
7814
|
-
this.refill();
|
|
7815
7818
|
this.tokens -= 1;
|
|
7816
|
-
}
|
|
7817
|
-
|
|
7819
|
+
}
|
|
7820
|
+
catch (error) {
|
|
7821
|
+
this.semaphore.release();
|
|
7822
|
+
throw error;
|
|
7823
|
+
}
|
|
7824
|
+
}
|
|
7825
|
+
release() {
|
|
7826
|
+
this.semaphore.release();
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7829
|
+
class Semaphore {
|
|
7830
|
+
permits;
|
|
7831
|
+
waiting = [];
|
|
7832
|
+
constructor(maxPermits) {
|
|
7833
|
+
this.permits = maxPermits;
|
|
7834
|
+
}
|
|
7835
|
+
async acquire() {
|
|
7836
|
+
if (this.permits > 0) {
|
|
7837
|
+
this.permits -= 1;
|
|
7838
|
+
return;
|
|
7839
|
+
}
|
|
7840
|
+
return new Promise((resolve, reject) => {
|
|
7841
|
+
this.waiting.push({ resolve, reject });
|
|
7842
|
+
});
|
|
7843
|
+
}
|
|
7844
|
+
release() {
|
|
7845
|
+
this.permits += 1;
|
|
7846
|
+
if (this.waiting.length > 0 && this.permits > 0) {
|
|
7847
|
+
const { resolve } = this.waiting.shift();
|
|
7848
|
+
this.permits -= 1;
|
|
7849
|
+
resolve();
|
|
7850
|
+
}
|
|
7851
|
+
}
|
|
7852
|
+
available() {
|
|
7853
|
+
return this.permits;
|
|
7818
7854
|
}
|
|
7819
7855
|
}
|
|
7820
7856
|
|
|
@@ -7822,7 +7858,8 @@
|
|
|
7822
7858
|
throttleBucket;
|
|
7823
7859
|
maxRetries;
|
|
7824
7860
|
retryDelay;
|
|
7825
|
-
constructor(throttleBucket,
|
|
7861
|
+
constructor(throttleBucket, // Updated type
|
|
7862
|
+
maxRetries = 3, retryDelay = 1000) {
|
|
7826
7863
|
this.throttleBucket = throttleBucket;
|
|
7827
7864
|
this.maxRetries = maxRetries;
|
|
7828
7865
|
this.retryDelay = retryDelay;
|
|
@@ -7832,8 +7869,12 @@
|
|
|
7832
7869
|
await this.throttleBucket.acquire();
|
|
7833
7870
|
}
|
|
7834
7871
|
}
|
|
7872
|
+
release() {
|
|
7873
|
+
if (this.throttleBucket) {
|
|
7874
|
+
this.throttleBucket.release();
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7835
7877
|
async handle429(error, attempt) {
|
|
7836
|
-
// Remove the throw here; let invokeMethod handle exhaustion
|
|
7837
7878
|
const retryAfter = error.response.headers.get("Retry-After");
|
|
7838
7879
|
return retryAfter
|
|
7839
7880
|
? parseInt(retryAfter, 10) * 1000
|
|
@@ -7842,10 +7883,11 @@
|
|
|
7842
7883
|
}
|
|
7843
7884
|
|
|
7844
7885
|
function quickbase(config) {
|
|
7845
|
-
const { realm, userToken, tempToken, useTempTokens, useSso, samlToken, fetchApi, debug, convertDates = true, tempTokenLifespan = 290000, throttle = { rate:
|
|
7886
|
+
const { realm, userToken, tempToken, useTempTokens, useSso, samlToken, fetchApi, debug, convertDates = true, tempTokenLifespan = 290000, throttle = { rate: 5, burst: 3 }, // Updated default to match passing test
|
|
7887
|
+
maxRetries = 3, retryDelay = 1000, tokenCache: providedTokenCache, baseUrl = "https://api.quickbase.com/v1", } = config;
|
|
7846
7888
|
const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
|
|
7847
7889
|
const throttleBucket = throttle
|
|
7848
|
-
? new
|
|
7890
|
+
? new ConcurrentThrottleBucket(throttle.rate, throttle.burst)
|
|
7849
7891
|
: null;
|
|
7850
7892
|
const rateLimiter = new RateLimiter(throttleBucket, maxRetries, retryDelay);
|
|
7851
7893
|
const defaultFetch = typeof globalThis.window !== "undefined"
|