quickbase-js 0.1.0-beta.10 → 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 +16 -0
- package/README.md +56 -24
- package/dist/esm/quickbase.d.ts +1 -0
- package/dist/esm/quickbase.js +125 -87
- 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 +125 -87
- 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/examples/quicbase-jsCDN.html +2 -2
- package/package.json +1 -1
- package/src/ThrottleBucket.ts +52 -16
- package/src/authorizationStrategy.ts +62 -35
- package/src/generated-unified/QuickbaseClient.ts +1 -1
- package/src/invokeMethod.ts +33 -66
- package/src/quickbaseClient.ts +12 -7
- package/src/rateLimiter.ts +8 -3
- package/src/tokenCache.ts +4 -0
- package/tests/playwright/qb/auth/getAppWithTempTokenConcurrency.test.ts +208 -0
- package/tests/vitest/qb/ThrottleBucket.test.ts +77 -0
- package/tests/vitest/qb/auth/ssoTokenConcurrency.test.ts +68 -0
- package/tests/vitest/qb/auth/userTokenConcurrency.test.ts +63 -0
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +2 -2
- package/tests/vitest/unit/ThrottleBucket.test.ts +16 -10
- package/tests/vitest/unit/apps/getAppConcurrency.test.ts +151 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +20 -2
- package/tests/vitest/unit/auth/userTokenConcurrency.test.ts +62 -0
- package/tests/vitest/unit/index.ts +1 -1
- package/tests/vitest/unit/quickbaseClientHttpMethods.test.ts +3 -3
- package/tools/log-gen.ts +1 -1
- package/tools/log-gen.yaml +130 -78
- package/api-examples.md +0 -49
- package/markdown-examples.md +0 -85
|
@@ -7354,6 +7354,9 @@
|
|
|
7354
7354
|
expiresAt: now + (lifespan || this.tempTokenLifespan),
|
|
7355
7355
|
});
|
|
7356
7356
|
}
|
|
7357
|
+
delete(dbid) {
|
|
7358
|
+
this.cache.delete(dbid);
|
|
7359
|
+
}
|
|
7357
7360
|
clear() {
|
|
7358
7361
|
this.cache.clear();
|
|
7359
7362
|
}
|
|
@@ -7408,14 +7411,13 @@
|
|
|
7408
7411
|
return match ? match[1].toUpperCase() : "GET"; // Fallback to GET if no match
|
|
7409
7412
|
}
|
|
7410
7413
|
|
|
7411
|
-
// src/authorizationStrategy.ts
|
|
7412
|
-
// src/authorizationStrategy.ts (partial update)
|
|
7413
7414
|
class TempTokenStrategy {
|
|
7414
7415
|
tokenCache;
|
|
7415
7416
|
initialTempToken;
|
|
7416
7417
|
fetchApi;
|
|
7417
7418
|
realm;
|
|
7418
7419
|
baseUrl;
|
|
7420
|
+
pendingFetches = new Map();
|
|
7419
7421
|
constructor(tokenCache, initialTempToken, fetchApi, realm, baseUrl = "https://api.quickbase.com/v1") {
|
|
7420
7422
|
this.tokenCache = tokenCache;
|
|
7421
7423
|
this.initialTempToken = initialTempToken;
|
|
@@ -7450,7 +7452,13 @@
|
|
|
7450
7452
|
async getToken(dbid) {
|
|
7451
7453
|
let token = this.tokenCache.get(dbid) || this.initialTempToken;
|
|
7452
7454
|
if (!token && dbid) {
|
|
7453
|
-
|
|
7455
|
+
if (this.pendingFetches.has(dbid)) {
|
|
7456
|
+
console.log(`[getToken] Waiting for existing fetch for dbid: ${dbid}`);
|
|
7457
|
+
return this.pendingFetches.get(dbid);
|
|
7458
|
+
}
|
|
7459
|
+
const fetchPromise = this.fetchTempToken(dbid).finally(() => this.pendingFetches.delete(dbid));
|
|
7460
|
+
this.pendingFetches.set(dbid, fetchPromise);
|
|
7461
|
+
token = await fetchPromise;
|
|
7454
7462
|
}
|
|
7455
7463
|
return token;
|
|
7456
7464
|
}
|
|
@@ -7470,15 +7478,29 @@
|
|
|
7470
7478
|
}
|
|
7471
7479
|
if (debug)
|
|
7472
7480
|
console.log(`Refreshing temp token for dbid: ${dbid}`);
|
|
7473
|
-
|
|
7474
|
-
this.tokenCache.
|
|
7475
|
-
|
|
7481
|
+
// Invalidate the cache for this dbid to force a fresh fetch
|
|
7482
|
+
this.tokenCache.delete(dbid);
|
|
7483
|
+
try {
|
|
7484
|
+
const newToken = await this.getToken(dbid);
|
|
7485
|
+
if (newToken) {
|
|
7486
|
+
this.tokenCache.set(dbid, newToken);
|
|
7487
|
+
return newToken;
|
|
7488
|
+
}
|
|
7489
|
+
return null;
|
|
7490
|
+
}
|
|
7491
|
+
catch (error) {
|
|
7492
|
+
if (debug)
|
|
7493
|
+
console.log(`[${methodName || "method"}] Failed to refresh token:`, error);
|
|
7494
|
+
throw error;
|
|
7495
|
+
}
|
|
7476
7496
|
}
|
|
7477
7497
|
}
|
|
7478
7498
|
class UserTokenStrategy {
|
|
7479
7499
|
userToken;
|
|
7480
|
-
|
|
7500
|
+
baseUrl;
|
|
7501
|
+
constructor(userToken, baseUrl = "https://api.quickbase.com/v1") {
|
|
7481
7502
|
this.userToken = userToken;
|
|
7503
|
+
this.baseUrl = baseUrl;
|
|
7482
7504
|
}
|
|
7483
7505
|
async getToken(_dbid) {
|
|
7484
7506
|
return this.userToken;
|
|
@@ -7499,21 +7521,31 @@
|
|
|
7499
7521
|
realm;
|
|
7500
7522
|
fetchApi;
|
|
7501
7523
|
debug;
|
|
7524
|
+
baseUrl;
|
|
7502
7525
|
currentToken;
|
|
7503
|
-
|
|
7526
|
+
pendingFetches = new Map();
|
|
7527
|
+
constructor(samlToken, realm, fetchApi, debug = false, baseUrl = "https://api.quickbase.com/v1") {
|
|
7504
7528
|
this.samlToken = samlToken;
|
|
7505
7529
|
this.realm = realm;
|
|
7506
7530
|
this.fetchApi = fetchApi;
|
|
7507
7531
|
this.debug = debug;
|
|
7532
|
+
this.baseUrl = baseUrl;
|
|
7508
7533
|
}
|
|
7509
7534
|
async getToken(_dbid) {
|
|
7510
7535
|
if (!this.currentToken) {
|
|
7511
|
-
|
|
7536
|
+
if (this.pendingFetches.has("sso")) {
|
|
7537
|
+
if (this.debug)
|
|
7538
|
+
console.log("[getToken] Waiting for existing SSO fetch");
|
|
7539
|
+
return this.pendingFetches.get("sso");
|
|
7540
|
+
}
|
|
7541
|
+
const fetchPromise = this.fetchSsoToken({
|
|
7512
7542
|
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
7513
7543
|
requested_token_type: "urn:quickbase:params:oauth:token-type:temp_token",
|
|
7514
7544
|
subject_token: this.samlToken,
|
|
7515
7545
|
subject_token_type: "urn:ietf:params:oauth:token-type:saml2",
|
|
7516
|
-
});
|
|
7546
|
+
}).finally(() => this.pendingFetches.delete("sso"));
|
|
7547
|
+
this.pendingFetches.set("sso", fetchPromise);
|
|
7548
|
+
this.currentToken = await fetchPromise;
|
|
7517
7549
|
}
|
|
7518
7550
|
return this.currentToken;
|
|
7519
7551
|
}
|
|
@@ -7540,7 +7572,7 @@
|
|
|
7540
7572
|
subject_token: this.samlToken,
|
|
7541
7573
|
subject_token_type: "urn:ietf:params:oauth:token-type:saml2",
|
|
7542
7574
|
};
|
|
7543
|
-
const response = await this.fetchApi(
|
|
7575
|
+
const response = await this.fetchApi(`${this.baseUrl}/auth/oauth/token`, {
|
|
7544
7576
|
method: "POST",
|
|
7545
7577
|
headers: {
|
|
7546
7578
|
"QB-Realm-Hostname": `${this.realm}.quickbase.com`,
|
|
@@ -7567,7 +7599,7 @@
|
|
|
7567
7599
|
return newToken;
|
|
7568
7600
|
}
|
|
7569
7601
|
async fetchSsoToken(params) {
|
|
7570
|
-
const response = await this.fetchApi(
|
|
7602
|
+
const response = await this.fetchApi(`${this.baseUrl}/auth/oauth/token`, {
|
|
7571
7603
|
method: "POST",
|
|
7572
7604
|
headers: {
|
|
7573
7605
|
"QB-Realm-Hostname": `${this.realm}.quickbase.com`,
|
|
@@ -7615,16 +7647,15 @@
|
|
|
7615
7647
|
}
|
|
7616
7648
|
|
|
7617
7649
|
// src/invokeMethod.ts
|
|
7618
|
-
async function invokeMethod(methodName, params, methodMap, baseHeaders, authStrategy, rateLimiter, transformDates, debug, convertDates) {
|
|
7619
|
-
console.log("[invokeMethod] Starting for method:", methodName);
|
|
7650
|
+
async function invokeMethod(methodName, params, methodMap, baseHeaders, authStrategy, rateLimiter, transformDates, debug, convertDates, attempt = 0, maxAttempts = rateLimiter.maxRetries + 1) {
|
|
7620
7651
|
const methodInfo = methodMap[methodName];
|
|
7621
7652
|
if (!methodInfo)
|
|
7622
7653
|
throw new Error(`Method ${methodName} not found`);
|
|
7623
7654
|
const hasBody = "body" in params && params.body !== undefined;
|
|
7624
7655
|
const body = hasBody ? params.body : undefined;
|
|
7625
7656
|
const restParams = hasBody
|
|
7626
|
-
? Object.fromEntries(Object.entries(params).filter(([key]) => key !== "body"))
|
|
7627
|
-
: { ...params };
|
|
7657
|
+
? Object.fromEntries(Object.entries(params).filter(([key]) => key !== "body" && key !== "startTime"))
|
|
7658
|
+
: { ...params, startTime: undefined };
|
|
7628
7659
|
const requestParameters = {
|
|
7629
7660
|
...restParams,
|
|
7630
7661
|
...(hasBody ? { generated: body } : {}),
|
|
@@ -7634,33 +7665,21 @@
|
|
|
7634
7665
|
method: methodInfo.httpMethod,
|
|
7635
7666
|
};
|
|
7636
7667
|
let dbid = extractDbid(params);
|
|
7637
|
-
console.log("[invokeMethod] Extracted dbid:", dbid);
|
|
7638
7668
|
let token = dbid
|
|
7639
7669
|
? await authStrategy.getToken(dbid)
|
|
7640
7670
|
: await authStrategy.getToken("");
|
|
7641
|
-
console.log("[invokeMethod] Initial token:", token);
|
|
7642
7671
|
if (token) {
|
|
7643
7672
|
authStrategy.applyHeaders(baseHeaders, token);
|
|
7644
7673
|
requestOptions.headers = { ...baseHeaders };
|
|
7645
|
-
console.log("[invokeMethod] Token found, headers set:", requestOptions.headers);
|
|
7646
7674
|
if (methodName === "getTempTokenDBID") {
|
|
7647
|
-
console.log("[invokeMethod] Using cached token:", token);
|
|
7648
7675
|
return { temporaryAuthorization: token };
|
|
7649
7676
|
}
|
|
7650
7677
|
}
|
|
7651
|
-
if (debug) {
|
|
7652
|
-
console.log(`[${methodName}] requestParameters:`, requestParameters);
|
|
7653
|
-
console.log(`[${methodName}] requestOptions:`, requestOptions);
|
|
7654
|
-
}
|
|
7655
7678
|
async function processResponse(rawResponse) {
|
|
7656
|
-
if (debug)
|
|
7657
|
-
console.log(`[${methodName}] rawResponse:`, rawResponse);
|
|
7658
7679
|
if (rawResponse instanceof Response) {
|
|
7659
7680
|
const contentType = rawResponse.headers
|
|
7660
7681
|
.get("Content-Type")
|
|
7661
7682
|
?.toLowerCase();
|
|
7662
|
-
if (debug)
|
|
7663
|
-
console.log(`[${methodName}] contentType:`, contentType);
|
|
7664
7683
|
if (contentType?.includes("application/json")) {
|
|
7665
7684
|
const jsonResponse = await rawResponse.json();
|
|
7666
7685
|
return transformDates(jsonResponse, convertDates);
|
|
@@ -7669,8 +7688,6 @@
|
|
|
7669
7688
|
}
|
|
7670
7689
|
if (rawResponse && typeof rawResponse.value === "function") {
|
|
7671
7690
|
const response = await rawResponse.value();
|
|
7672
|
-
if (debug)
|
|
7673
|
-
console.log(`[${methodName}] Resolved JSONApiResponse:`, response);
|
|
7674
7691
|
return transformDates(response, convertDates);
|
|
7675
7692
|
}
|
|
7676
7693
|
return transformDates(rawResponse, convertDates);
|
|
@@ -7698,28 +7715,26 @@
|
|
|
7698
7715
|
}
|
|
7699
7716
|
}
|
|
7700
7717
|
catch (e) {
|
|
7701
|
-
if (debug)
|
|
7702
|
-
console.log(`[${methodName}] Error parsing response body:`, e);
|
|
7703
7718
|
message = "Failed to parse error response";
|
|
7704
7719
|
}
|
|
7705
|
-
if (debug)
|
|
7706
|
-
console.log("[invokeMethod] Parsed error - status:", status, "message:", message);
|
|
7707
7720
|
return { message, status };
|
|
7708
7721
|
}
|
|
7709
|
-
let attempt = 0;
|
|
7710
|
-
const maxAttempts = rateLimiter.maxRetries + 1;
|
|
7711
7722
|
while (attempt < maxAttempts) {
|
|
7712
|
-
|
|
7723
|
+
let acquired = false;
|
|
7713
7724
|
try {
|
|
7714
7725
|
await rateLimiter.throttle();
|
|
7715
|
-
|
|
7716
|
-
const
|
|
7717
|
-
|
|
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;
|
|
7718
7733
|
return await processResponse(response);
|
|
7719
7734
|
}
|
|
7720
7735
|
catch (error) {
|
|
7721
|
-
if (
|
|
7722
|
-
|
|
7736
|
+
if (acquired)
|
|
7737
|
+
rateLimiter.release();
|
|
7723
7738
|
let status;
|
|
7724
7739
|
let message;
|
|
7725
7740
|
let response;
|
|
@@ -7732,31 +7747,13 @@
|
|
|
7732
7747
|
({ message, status } = await parseErrorResponse(response));
|
|
7733
7748
|
}
|
|
7734
7749
|
else {
|
|
7735
|
-
|
|
7736
|
-
console.log(`[${methodName}] Unexpected error:`, error);
|
|
7737
|
-
// Attempt to handle as a fetch error with a response
|
|
7738
|
-
if (error instanceof Error && "response" in error) {
|
|
7739
|
-
response = error.response;
|
|
7740
|
-
if (response instanceof Response) {
|
|
7741
|
-
({ message, status } = await parseErrorResponse(response));
|
|
7742
|
-
}
|
|
7743
|
-
else {
|
|
7744
|
-
throw error; // Rethrow if no response
|
|
7745
|
-
}
|
|
7746
|
-
}
|
|
7747
|
-
else {
|
|
7748
|
-
throw error; // Rethrow truly unexpected errors
|
|
7749
|
-
}
|
|
7750
|
+
throw error;
|
|
7750
7751
|
}
|
|
7751
|
-
if (debug)
|
|
7752
|
-
console.log("[invokeMethod] Handling error with status:", status);
|
|
7753
7752
|
if (status === 429) {
|
|
7754
7753
|
if (!(error instanceof ResponseError)) {
|
|
7755
7754
|
throw new Error("Expected ResponseError for 429 handling");
|
|
7756
7755
|
}
|
|
7757
7756
|
const delay = await rateLimiter.handle429(error, attempt + 1);
|
|
7758
|
-
if (debug)
|
|
7759
|
-
console.log(`[${methodName}] 429 delay: ${delay}ms`);
|
|
7760
7757
|
if (attempt + 1 === maxAttempts) {
|
|
7761
7758
|
throw new RateLimitError(`API Error: ${message} (Status: ${status})`, status, response?.headers.get("Retry-After")
|
|
7762
7759
|
? parseInt(response.headers.get("Retry-After"), 10)
|
|
@@ -7766,14 +7763,20 @@
|
|
|
7766
7763
|
attempt++;
|
|
7767
7764
|
continue;
|
|
7768
7765
|
}
|
|
7769
|
-
|
|
7770
|
-
|
|
7766
|
+
let newToken;
|
|
7767
|
+
try {
|
|
7768
|
+
newToken = await authStrategy.handleError(status, params, attempt, maxAttempts, debug, methodName);
|
|
7769
|
+
}
|
|
7770
|
+
catch (authError) {
|
|
7771
|
+
throw authError;
|
|
7772
|
+
}
|
|
7773
|
+
if (newToken !== null) {
|
|
7771
7774
|
token = newToken;
|
|
7772
7775
|
authStrategy.applyHeaders(baseHeaders, token);
|
|
7773
7776
|
requestOptions.headers = { ...baseHeaders };
|
|
7774
|
-
attempt++;
|
|
7775
7777
|
if (debug)
|
|
7776
7778
|
console.log(`[${methodName}] Retrying with token: ${token.substring(0, 10)}...`);
|
|
7779
|
+
attempt++;
|
|
7777
7780
|
continue;
|
|
7778
7781
|
}
|
|
7779
7782
|
throw new Error(`API Error: ${message} (Status: ${status})`);
|
|
@@ -7783,41 +7786,71 @@
|
|
|
7783
7786
|
}
|
|
7784
7787
|
|
|
7785
7788
|
// src/ThrottleBucket.ts
|
|
7786
|
-
class
|
|
7789
|
+
class ConcurrentThrottleBucket {
|
|
7787
7790
|
tokens;
|
|
7788
7791
|
maxTokens;
|
|
7789
|
-
refillRate;
|
|
7792
|
+
refillRate;
|
|
7790
7793
|
lastRefill;
|
|
7791
|
-
|
|
7794
|
+
semaphore;
|
|
7792
7795
|
constructor(rate, burst) {
|
|
7793
7796
|
this.tokens = burst;
|
|
7794
7797
|
this.maxTokens = burst;
|
|
7795
7798
|
this.refillRate = rate;
|
|
7796
7799
|
this.lastRefill = Date.now();
|
|
7800
|
+
this.semaphore = new Semaphore(burst);
|
|
7797
7801
|
}
|
|
7798
7802
|
refill() {
|
|
7799
7803
|
const now = Date.now();
|
|
7800
|
-
const elapsed = (now - this.lastRefill) / 1000;
|
|
7804
|
+
const elapsed = (now - this.lastRefill) / 1000;
|
|
7801
7805
|
const newTokens = elapsed * this.refillRate;
|
|
7802
7806
|
this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
|
|
7803
7807
|
this.lastRefill = now;
|
|
7804
7808
|
}
|
|
7805
7809
|
async acquire() {
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
this.pending = (async () => {
|
|
7809
|
-
await previous; // Wait for prior calls to complete
|
|
7810
|
+
await this.semaphore.acquire();
|
|
7811
|
+
try {
|
|
7810
7812
|
this.refill();
|
|
7811
|
-
|
|
7812
|
-
this.tokens
|
|
7813
|
-
|
|
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();
|
|
7814
7817
|
}
|
|
7815
|
-
const waitTime = ((1 - this.tokens) / this.refillRate) * 1000; // ms until next token
|
|
7816
|
-
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
7817
|
-
this.refill();
|
|
7818
7818
|
this.tokens -= 1;
|
|
7819
|
-
}
|
|
7820
|
-
|
|
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;
|
|
7821
7854
|
}
|
|
7822
7855
|
}
|
|
7823
7856
|
|
|
@@ -7825,7 +7858,8 @@
|
|
|
7825
7858
|
throttleBucket;
|
|
7826
7859
|
maxRetries;
|
|
7827
7860
|
retryDelay;
|
|
7828
|
-
constructor(throttleBucket,
|
|
7861
|
+
constructor(throttleBucket, // Updated type
|
|
7862
|
+
maxRetries = 3, retryDelay = 1000) {
|
|
7829
7863
|
this.throttleBucket = throttleBucket;
|
|
7830
7864
|
this.maxRetries = maxRetries;
|
|
7831
7865
|
this.retryDelay = retryDelay;
|
|
@@ -7835,8 +7869,12 @@
|
|
|
7835
7869
|
await this.throttleBucket.acquire();
|
|
7836
7870
|
}
|
|
7837
7871
|
}
|
|
7872
|
+
release() {
|
|
7873
|
+
if (this.throttleBucket) {
|
|
7874
|
+
this.throttleBucket.release();
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7838
7877
|
async handle429(error, attempt) {
|
|
7839
|
-
// Remove the throw here; let invokeMethod handle exhaustion
|
|
7840
7878
|
const retryAfter = error.response.headers.get("Retry-After");
|
|
7841
7879
|
return retryAfter
|
|
7842
7880
|
? parseInt(retryAfter, 10) * 1000
|
|
@@ -7844,12 +7882,12 @@
|
|
|
7844
7882
|
}
|
|
7845
7883
|
}
|
|
7846
7884
|
|
|
7847
|
-
// src/quickbaseClient.ts
|
|
7848
7885
|
function quickbase(config) {
|
|
7849
|
-
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;
|
|
7850
7888
|
const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
|
|
7851
7889
|
const throttleBucket = throttle
|
|
7852
|
-
? new
|
|
7890
|
+
? new ConcurrentThrottleBucket(throttle.rate, throttle.burst)
|
|
7853
7891
|
: null;
|
|
7854
7892
|
const rateLimiter = new RateLimiter(throttleBucket, maxRetries, retryDelay);
|
|
7855
7893
|
const defaultFetch = typeof globalThis.window !== "undefined"
|
|
@@ -7860,10 +7898,10 @@
|
|
|
7860
7898
|
throw new Error("No fetch implementation available");
|
|
7861
7899
|
}
|
|
7862
7900
|
const authStrategy = useSso
|
|
7863
|
-
? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug)
|
|
7901
|
+
? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug, baseUrl)
|
|
7864
7902
|
: useTempTokens
|
|
7865
7903
|
? new TempTokenStrategy(tokenCache, tempToken, effectiveFetch, realm, baseUrl)
|
|
7866
|
-
: new UserTokenStrategy(userToken || "");
|
|
7904
|
+
: new UserTokenStrategy(userToken || "", baseUrl);
|
|
7867
7905
|
const baseHeaders = {
|
|
7868
7906
|
"QB-Realm-Hostname": `${realm}.quickbase.com`,
|
|
7869
7907
|
"Content-Type": "application/json",
|