quickbase-js 0.1.0-beta.10 → 0.1.0-beta.11

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.
@@ -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
- token = await this.fetchTempToken(dbid);
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
- const newToken = await this.fetchTempToken(dbid);
7474
- this.tokenCache.set(dbid, newToken);
7475
- return newToken;
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
- constructor(userToken) {
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
- constructor(samlToken, realm, fetchApi, debug = false) {
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
- this.currentToken = await this.fetchSsoToken({
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(`https://api.quickbase.com/v1/auth/oauth/token`, {
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(`https://api.quickbase.com/v1/auth/oauth/token`, {
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,8 +7647,7 @@
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`);
@@ -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,17 @@
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
- console.log("[invokeMethod] Attempt:", attempt + 1, "of", maxAttempts);
7713
7723
  try {
7714
7724
  await rateLimiter.throttle();
7715
- console.log("[invokeMethod] About to call API for method:", methodName);
7716
7725
  const response = await methodInfo.method(requestParameters, requestOptions);
7717
- console.log("[invokeMethod] API call completed for method:", methodName);
7718
7726
  return await processResponse(response);
7719
7727
  }
7720
7728
  catch (error) {
7721
- if (debug)
7722
- console.log("[invokeMethod] Caught error:", error);
7723
7729
  let status;
7724
7730
  let message;
7725
7731
  let response;
@@ -7732,31 +7738,13 @@
7732
7738
  ({ message, status } = await parseErrorResponse(response));
7733
7739
  }
7734
7740
  else {
7735
- if (debug)
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
- }
7741
+ throw error; // Rethrow truly unexpected errors
7750
7742
  }
7751
- if (debug)
7752
- console.log("[invokeMethod] Handling error with status:", status);
7753
7743
  if (status === 429) {
7754
7744
  if (!(error instanceof ResponseError)) {
7755
7745
  throw new Error("Expected ResponseError for 429 handling");
7756
7746
  }
7757
7747
  const delay = await rateLimiter.handle429(error, attempt + 1);
7758
- if (debug)
7759
- console.log(`[${methodName}] 429 delay: ${delay}ms`);
7760
7748
  if (attempt + 1 === maxAttempts) {
7761
7749
  throw new RateLimitError(`API Error: ${message} (Status: ${status})`, status, response?.headers.get("Retry-After")
7762
7750
  ? parseInt(response.headers.get("Retry-After"), 10)
@@ -7766,16 +7754,25 @@
7766
7754
  attempt++;
7767
7755
  continue;
7768
7756
  }
7769
- const newToken = await authStrategy.handleError(status, params, attempt, maxAttempts, debug, methodName);
7770
- if (newToken) {
7757
+ // Handle authentication errors
7758
+ let newToken;
7759
+ try {
7760
+ newToken = await authStrategy.handleError(status, params, attempt, maxAttempts, debug, methodName);
7761
+ }
7762
+ catch (authError) {
7763
+ throw authError; // Propagate the fetchTempToken error and exit immediately
7764
+ }
7765
+ // Only proceed with retry if we have a valid new token
7766
+ if (newToken !== null) {
7771
7767
  token = newToken;
7772
7768
  authStrategy.applyHeaders(baseHeaders, token);
7773
7769
  requestOptions.headers = { ...baseHeaders };
7774
- attempt++;
7775
7770
  if (debug)
7776
7771
  console.log(`[${methodName}] Retrying with token: ${token.substring(0, 10)}...`);
7772
+ attempt++;
7777
7773
  continue;
7778
7774
  }
7775
+ // If no new token was provided, throw the original error
7779
7776
  throw new Error(`API Error: ${message} (Status: ${status})`);
7780
7777
  }
7781
7778
  }
@@ -7844,7 +7841,6 @@
7844
7841
  }
7845
7842
  }
7846
7843
 
7847
- // src/quickbaseClient.ts
7848
7844
  function quickbase(config) {
7849
7845
  const { realm, userToken, tempToken, useTempTokens, useSso, samlToken, fetchApi, debug, convertDates = true, tempTokenLifespan = 290000, throttle = { rate: 10, burst: 10 }, maxRetries = 3, retryDelay = 1000, tokenCache: providedTokenCache, baseUrl = "https://api.quickbase.com/v1", } = config;
7850
7846
  const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
@@ -7860,10 +7856,10 @@
7860
7856
  throw new Error("No fetch implementation available");
7861
7857
  }
7862
7858
  const authStrategy = useSso
7863
- ? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug)
7859
+ ? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug, baseUrl)
7864
7860
  : useTempTokens
7865
7861
  ? new TempTokenStrategy(tokenCache, tempToken, effectiveFetch, realm, baseUrl)
7866
- : new UserTokenStrategy(userToken || "");
7862
+ : new UserTokenStrategy(userToken || "", baseUrl);
7867
7863
  const baseHeaders = {
7868
7864
  "QB-Realm-Hostname": `${realm}.quickbase.com`,
7869
7865
  "Content-Type": "application/json",