From 89afb72ba5585e50177e826d9d44b37ff5e5b2c6 Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Wed, 17 Sep 2025 14:16:13 +0530 Subject: [PATCH 1/2] feat: enhance caching and retry logic with content type and entry UID support --- .talismanrc | 12 ++ CHANGELOG.md | 5 + package.json | 2 +- src/lib/base-query.ts | 20 +- src/lib/cache.ts | 57 +++++- src/lib/entries.ts | 35 ++++ src/lib/entry.ts | 7 +- src/lib/query.ts | 38 +++- test/api/retry-integration.spec.ts | 137 +++++++++++++ test/unit/cache.spec.ts | 42 ++-- test/unit/retry-configuration.spec.ts | 277 ++++++++++++++++++++++++++ 11 files changed, 608 insertions(+), 24 deletions(-) create mode 100644 test/api/retry-integration.spec.ts create mode 100644 test/unit/retry-configuration.spec.ts diff --git a/.talismanrc b/.talismanrc index 65ba163..ce5086a 100644 --- a/.talismanrc +++ b/.talismanrc @@ -3,4 +3,16 @@ fileignoreconfig: checksum: dffbcb14c5761976a9b6ab90b96c7e4fc5784eebe381cf48014618cc93355dbc - filename: test/unit/query-optimization-comprehensive.spec.ts checksum: f5aaf6c784d7c101a05ca513c584bbd6e95f963d1e42779f2596050d9bcbac96 +- filename: src/lib/entries.ts + checksum: f6a19da15baed75062ad0cc599573ed08926e28fffe3f6e4890a0efb4d58c910 +- filename: src/lib/cache.ts + checksum: d8d32089b8a4b247e4ba71c22b56cbb0a54440ebf35b102af222eb8032919f02 +- filename: test/unit/cache.spec.ts + checksum: e96f913a466a1f4d55a422e7032fc2c06eeed5fea86cdcc86a05fbe3eba29b7a +- filename: src/lib/query.ts + checksum: 073c47e46755eb79d1d7e9fcaf2864296a218bf650888dd37c42480cce7df379 +- filename: test/api/retry-integration.spec.ts + checksum: dc07b0a8111fd8e155b99f56c31ccdddd4f46c86f1b162b17d73e15dfed8e3c8 +- filename: test/unit/retry-configuration.spec.ts + checksum: 359c8601c6205a65f3395cc209a93b278dfe7f5bb547c91b2eeab250b2c85aa3 version: "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 971f5b7..ac5010d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### Version: 4.9.1 +#### Date: Aug-25-2025 +Fix: Enhance retry logic to use configured retryDelay +Enhancement: Caching logic to use combination of content type uid and entry uid + ### Version: 4.9.0 #### Date: Aug-25-2025 Fix: Fixed Timeout parameter which is being ignored & Removed custom error object diff --git a/package.json b/package.json index 7672c84..1fef992 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/delivery-sdk", - "version": "4.9.0", + "version": "4.9.1", "type": "module", "license": "MIT", "main": "./dist/legacy/index.cjs", diff --git a/src/lib/base-query.ts b/src/lib/base-query.ts index ab7a855..ed75ce6 100644 --- a/src/lib/base-query.ts +++ b/src/lib/base-query.ts @@ -215,7 +215,11 @@ export class BaseQuery extends Pagination { requestParams = { ...this._queryParams, query: queryParams }; } - const getRequestOptions: any = { params: requestParams }; + const getRequestOptions: any = { + params: requestParams, + // Add contentTypeUid to config for improved caching (extract from URL if possible) + contentTypeUid: this.extractContentTypeUidFromUrl() + }; if (this._variants) { getRequestOptions.headers = { @@ -227,4 +231,18 @@ export class BaseQuery extends Pagination { return response as FindResponse; } + + /** + * Extracts content type UID from the URL path + * @returns content type UID if found, null otherwise + */ + private extractContentTypeUidFromUrl(): string | null { + if (!this._urlPath) return null; + + // Match patterns like: /content_types/{content_type_uid}/entries + const contentTypePattern = /\/content_types\/([^\/]+)/; + const match = this._urlPath.match(contentTypePattern); + + return match ? match[1] : null; + } } diff --git a/src/lib/cache.ts b/src/lib/cache.ts index 228d656..0d28f7b 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -3,6 +3,42 @@ import { PersistanceStore } from '../persistance'; import { CacheOptions, Policy } from './types'; +/** + * Extracts entry UID from request URL if available + * @param config - Request config object + * @returns entry UID if found, null otherwise + */ +function extractEntryUidFromUrl(config: any): string | null { + if (!config.url) return null; + + // Match patterns like: /content_types/{content_type_uid}/entries/{entry_uid} + const entryUrlPattern = /\/content_types\/[^\/]+\/entries\/([^\/\?]+)/; + const match = config.url.match(entryUrlPattern); + + return match ? match[1] : null; +} + +/** + * Generates an improved cache key using content type UID and entry UID + * @param originalKey - Original cache key (apiKey) + * @param contentTypeUid - Content type UID + * @param entryUid - Entry UID (optional) + * @returns Enhanced cache key + */ +function generateEnhancedCacheKey(originalKey: string, contentTypeUid?: string, entryUid?: string): string { + let cacheKey = originalKey; + + if (contentTypeUid) { + cacheKey = `${contentTypeUid}_${cacheKey}`; + } + + if (entryUid) { + cacheKey = `${cacheKey}_entry_${entryUid}`; + } + + return cacheKey; +} + export async function handleRequest( cacheOptions: CacheOptions, apiKey: string, @@ -12,16 +48,23 @@ export async function handleRequest( config: any ) { const cacheStore = new PersistanceStore(cacheOptions); + + // Extract entry UID from URL or config + const entryUid = config.entryUid || extractEntryUidFromUrl(config); + + // Generate enhanced cache key using content type UID and entry UID + const enhancedCacheKey = generateEnhancedCacheKey(apiKey, config.contentTypeUid, entryUid); + switch (cacheOptions.policy) { case Policy.NETWORK_ELSE_CACHE: { const apiResponse = await defaultAdapter(config); if (apiResponse.data) { - cacheStore.setItem(apiKey, JSON.parse(apiResponse.data), config.contentTypeUid, cacheOptions.maxAge); + cacheStore.setItem(enhancedCacheKey, JSON.parse(apiResponse.data), config.contentTypeUid, cacheOptions.maxAge); return resolve({data: JSON.parse(apiResponse.data)}); } else { - const cacheResponse = cacheStore.getItem(apiKey, config.contentTypeUid); + const cacheResponse = cacheStore.getItem(enhancedCacheKey, config.contentTypeUid); if (cacheResponse) return resolve({ data: cacheResponse, @@ -35,7 +78,7 @@ export async function handleRequest( return reject(apiResponse); } case Policy.CACHE_THEN_NETWORK: { - const cacheResponse = cacheStore.getItem(apiKey, config.contentTypeUid); + const cacheResponse = cacheStore.getItem(enhancedCacheKey, config.contentTypeUid); if (cacheResponse) return resolve({ data: cacheResponse, @@ -48,7 +91,7 @@ export async function handleRequest( const apiResponse = await defaultAdapter(config); if (apiResponse.data) { - cacheStore.setItem(apiKey, JSON.parse(apiResponse.data), config.contentTypeUid, cacheOptions.maxAge); + cacheStore.setItem(enhancedCacheKey, JSON.parse(apiResponse.data), config.contentTypeUid, cacheOptions.maxAge); return resolve({data: JSON.parse(apiResponse.data)}); } else { @@ -56,7 +99,7 @@ export async function handleRequest( } } case Policy.CACHE_ELSE_NETWORK: { - const cacheResponse = cacheStore.getItem(apiKey, config.contentTypeUid); + const cacheResponse = cacheStore.getItem(enhancedCacheKey, config.contentTypeUid); if (cacheResponse) return resolve({ @@ -70,7 +113,7 @@ export async function handleRequest( const apiResponse = await defaultAdapter(config); if (apiResponse.data) { - cacheStore.setItem(apiKey, JSON.parse(apiResponse.data), config.contentTypeUid, cacheOptions.maxAge); + cacheStore.setItem(enhancedCacheKey, JSON.parse(apiResponse.data), config.contentTypeUid, cacheOptions.maxAge); return resolve({data: JSON.parse(apiResponse.data)}); } else { @@ -79,4 +122,4 @@ export async function handleRequest( } } } -} +} \ No newline at end of file diff --git a/src/lib/entries.ts b/src/lib/entries.ts index ac47c09..c7727d3 100644 --- a/src/lib/entries.ts +++ b/src/lib/entries.ts @@ -1,6 +1,8 @@ import { AxiosInstance, getData } from '@contentstack/core'; import { Query } from './query'; import { BaseQuery } from './base-query'; +import { FindResponse } from './types'; +import { encodeQueryParams } from './utils'; export class Entries extends BaseQuery { private _contentTypeUid: string; @@ -266,4 +268,37 @@ export class Entries extends BaseQuery { } return this; } + + /** + * Override find method to include content type UID directly for better caching + */ + override async find(encode: boolean = false): Promise> { + let requestParams: { [key: string]: any } = this._queryParams; + + if (Object.keys(this._parameters).length > 0) { + let queryParams = { ...this._parameters }; + + if (encode) { + queryParams = encodeQueryParams(queryParams); + } + + requestParams = { ...this._queryParams, query: queryParams }; + } + + const getRequestOptions: any = { + params: requestParams, + // Add contentTypeUid directly for improved caching + contentTypeUid: this._contentTypeUid + }; + + if (this._variants) { + getRequestOptions.headers = { + ...getRequestOptions.headers, + 'x-cs-variant-uid': this._variants + }; + } + const response = await getData(this._client, this._urlPath, getRequestOptions); + + return response as FindResponse; + } } diff --git a/src/lib/entry.ts b/src/lib/entry.ts index b8aff0b..077c8cd 100644 --- a/src/lib/entry.ts +++ b/src/lib/entry.ts @@ -180,7 +180,12 @@ export class Entry { * const result = await stack.contentType(contentType_uid).entry(entry_uid).fetch(); */ async fetch(): Promise { - const getRequestOptions: any = { params: this._queryParams}; + const getRequestOptions: any = { + params: this._queryParams, + // Add contentTypeUid and entryUid to config for improved caching + contentTypeUid: this._contentTypeUid, + entryUid: this._entryUid + }; if (this._variants) { getRequestOptions.headers = { ...getRequestOptions.headers, diff --git a/src/lib/query.ts b/src/lib/query.ts index 07e80e6..96ecc1d 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -1,6 +1,7 @@ -import { AxiosInstance } from '@contentstack/core'; +import { AxiosInstance, getData } from '@contentstack/core'; import { BaseQuery } from './base-query'; -import { BaseQueryParameters, QueryOperation, QueryOperator, TaxonomyQueryOperation, params, queryParams } from './types'; +import { BaseQueryParameters, QueryOperation, QueryOperator, TaxonomyQueryOperation, params, queryParams, FindResponse } from './types'; +import { encodeQueryParams } from './utils'; export class Query extends BaseQuery { private _contentTypeUid?: string; @@ -594,4 +595,37 @@ export class Query extends BaseQuery { this._parameters[key] = { '$gte': value }; return this; } + + /** + * Override find method to include content type UID directly for better caching + */ + override async find(encode: boolean = false): Promise> { + let requestParams: { [key: string]: any } = this._queryParams; + + if (Object.keys(this._parameters).length > 0) { + let queryParams = { ...this._parameters }; + + if (encode) { + queryParams = encodeQueryParams(queryParams); + } + + requestParams = { ...this._queryParams, query: queryParams }; + } + + const getRequestOptions: any = { + params: requestParams, + // Add contentTypeUid directly for improved caching + contentTypeUid: this._contentTypeUid + }; + + if (this._variants) { + getRequestOptions.headers = { + ...getRequestOptions.headers, + 'x-cs-variant-uid': this._variants + }; + } + const response = await getData(this._client, this._urlPath, getRequestOptions); + + return response as FindResponse; + } } diff --git a/test/api/retry-integration.spec.ts b/test/api/retry-integration.spec.ts new file mode 100644 index 0000000..608936d --- /dev/null +++ b/test/api/retry-integration.spec.ts @@ -0,0 +1,137 @@ +import * as Contentstack from '../../src/lib/contentstack'; +import { StackConfig } from '../../src/lib/types'; + +describe('Retry Integration Tests', () => { + const baseConfig: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment' + }; + + describe('Retry Configuration Integration', () => { + it('should properly integrate retry configuration with content type queries', () => { + const config: StackConfig = { + ...baseConfig, + retryLimit: 3, + retryDelay: 100 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify the configuration is properly set + expect(client.defaults.retryLimit).toBe(3); + expect(client.defaults.retryDelay).toBe(100); + + // Verify retry interceptors are configured + expect(client.interceptors.request.handlers.length).toBeGreaterThan(0); + expect(client.interceptors.response.handlers.length).toBeGreaterThan(0); + }); + + it('should maintain retry configuration across different query types', () => { + const config: StackConfig = { + ...baseConfig, + retryLimit: 2, + retryDelay: 50 + }; + + const stack = Contentstack.stack(config); + + // Test different query types use the same client with retry config + const contentTypeQuery = stack.contentType(); + const assetQuery = stack.asset(); + const taxonomyQuery = stack.taxonomy(); + + expect(contentTypeQuery).toBeDefined(); + expect(assetQuery).toBeDefined(); + expect(taxonomyQuery).toBeDefined(); + + // All should use the same client with retry configuration + const client = stack.getClient(); + expect(client.defaults.retryLimit).toBe(2); + expect(client.defaults.retryDelay).toBe(50); + }); + }); + + describe('Advanced Retry Configuration', () => { + it('should support custom retry conditions', () => { + const customRetryCondition = (error: any) => { + return error.response?.status === 500; + }; + + const config: StackConfig = { + ...baseConfig, + retryLimit: 2, + retryDelay: 100, + retryCondition: customRetryCondition + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryCondition).toBe(customRetryCondition); + expect(typeof client.defaults.retryCondition).toBe('function'); + }); + + it('should support timeout configuration along with retry settings', () => { + const config: StackConfig = { + ...baseConfig, + retryLimit: 2, + retryDelay: 100, + timeout: 5000 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryLimit).toBe(2); + expect(client.defaults.retryDelay).toBe(100); + expect(client.defaults.timeout).toBe(5000); + }); + + it('should maintain consistency between stack config and client defaults', () => { + const config: StackConfig = { + ...baseConfig, + retryLimit: 5, + retryDelay: 300, + retryOnError: true + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify consistency between stack config and client defaults + expect(stack.config.retryLimit).toBe(client.defaults.retryLimit); + expect(stack.config.retryDelay).toBe(client.defaults.retryDelay); + expect(stack.config.retryOnError).toBe(client.defaults.retryOnError); + + // Also verify in stackConfig + expect(client.stackConfig.retryLimit).toBe(client.defaults.retryLimit); + expect(client.stackConfig.retryDelay).toBe(client.defaults.retryDelay); + expect(client.stackConfig.retryOnError).toBe(client.defaults.retryOnError); + }); + + it('should verify that retry delay is properly applied in real scenarios', () => { + const config: StackConfig = { + ...baseConfig, + retryLimit: 2, + retryDelay: 500, + retryOnError: true + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify that the retry configuration will be used by the error handler + expect(client.defaults.retryLimit).toBe(2); + expect(client.defaults.retryDelay).toBe(500); + expect(client.defaults.retryOnError).toBe(true); + + // Verify interceptors are configured to handle retries + expect(client.interceptors.response.handlers.length).toBeGreaterThan(0); + + // The actual retry behavior with delay is tested in the core module's unit tests + // This integration test ensures the configuration is properly passed through + }); + }); +}); diff --git a/test/unit/cache.spec.ts b/test/unit/cache.spec.ts index 7d6db94..3e42f9c 100644 --- a/test/unit/cache.spec.ts +++ b/test/unit/cache.spec.ts @@ -73,7 +73,9 @@ describe("Cache handleRequest function", () => { expect(resolve).toBeCalledWith({ data: "foo" }); expect(reject).not.toBeCalled(); - cacheStore.removeItem(apiKey, config.contentTypeUid); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); it("should return cache data when proper network response is not received", async () => { @@ -84,8 +86,10 @@ describe("Cache handleRequest function", () => { }); const cacheStore = new PersistanceStore(cacheOptions); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; cacheStore.setItem( - apiKey, + enhancedCacheKey, "cacheData", config.contentTypeUid, cacheOptions.maxAge @@ -109,7 +113,7 @@ describe("Cache handleRequest function", () => { }); expect(reject).not.toBeCalled(); - cacheStore.removeItem(apiKey, config.contentTypeUid); + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); it("should return error data when network response has error", async () => { @@ -136,7 +140,9 @@ describe("Cache handleRequest function", () => { baz: "quux", }); - cacheStore.removeItem(apiKey, config.contentTypeUid); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); }); @@ -146,8 +152,10 @@ describe("Cache handleRequest function", () => { const defaultAdapter = jest.fn((_config) => ({ data: "foo" })); const cacheStore = new PersistanceStore(cacheOptions); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; cacheStore.setItem( - apiKey, + enhancedCacheKey, "cacheData", config.contentTypeUid, cacheOptions.maxAge @@ -172,7 +180,7 @@ describe("Cache handleRequest function", () => { }); expect(reject).not.toBeCalled(); - cacheStore.removeItem(apiKey, config.contentTypeUid); + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); it("should return api response when proper cache is not available", async () => { const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 }; @@ -195,7 +203,9 @@ describe("Cache handleRequest function", () => { expect(resolve).toBeCalledWith({ data: "foo" }); expect(reject).not.toBeCalled(); - cacheStore.removeItem(apiKey, config.contentTypeUid); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); it("should return error api response when data is not available in network or cache", async () => { const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 }; @@ -222,7 +232,9 @@ describe("Cache handleRequest function", () => { baz: "quux", }); - cacheStore.removeItem(apiKey, config.contentTypeUid); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); }); @@ -232,8 +244,10 @@ describe("Cache handleRequest function", () => { const defaultAdapter = jest.fn((_config) => ({ data: "foo" })); const cacheStore = new PersistanceStore(cacheOptions); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; cacheStore.setItem( - apiKey, + enhancedCacheKey, "cacheData", config.contentTypeUid, cacheOptions.maxAge @@ -258,7 +272,7 @@ describe("Cache handleRequest function", () => { }); expect(reject).not.toBeCalled(); - cacheStore.removeItem(apiKey, config.contentTypeUid); + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); it("should return network response data when cache is not available", async () => { @@ -281,7 +295,9 @@ describe("Cache handleRequest function", () => { expect(resolve).toBeCalledWith({ data: "foo" }); expect(reject).not.toBeCalled(); - cacheStore.removeItem(apiKey, config.contentTypeUid); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); it("should return error data when network response has error", async () => { @@ -308,7 +324,9 @@ describe("Cache handleRequest function", () => { baz: "quux", }); - cacheStore.removeItem(apiKey, config.contentTypeUid); + // Use enhanced cache key format: contentTypeUid + apiKey + const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`; + cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid); }); }); }); diff --git a/test/unit/retry-configuration.spec.ts b/test/unit/retry-configuration.spec.ts new file mode 100644 index 0000000..9fd56cf --- /dev/null +++ b/test/unit/retry-configuration.spec.ts @@ -0,0 +1,277 @@ +import { httpClient, AxiosInstance } from '@contentstack/core'; +import * as Contentstack from '../../src/lib/contentstack'; +import { Stack } from '../../src/lib/stack'; +import { StackConfig } from '../../src/lib/types'; +import MockAdapter from 'axios-mock-adapter'; + +describe('Retry Configuration', () => { + let mockClient: MockAdapter | undefined; + + afterEach(() => { + if (mockClient) { + mockClient.reset(); + mockClient = undefined; + } + }); + + describe('Default Retry Configuration', () => { + it('should use default retry settings when not specified', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment' + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Default values should come from core module + expect(client.defaults.retryOnError).toBe(true); + // retryLimit and retryDelay should be undefined here and use core defaults (5 and 300) + expect(client.defaults.retryLimit).toBeUndefined(); + expect(client.defaults.retryDelay).toBeUndefined(); + }); + + it('should have retry interceptors configured', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment' + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Should have retry request and response interceptors + expect(client.interceptors.request.handlers.length).toBeGreaterThan(0); + expect(client.interceptors.response.handlers.length).toBeGreaterThan(0); + }); + }); + + describe('Custom Retry Configuration', () => { + it('should accept custom retryLimit configuration', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 3 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryLimit).toBe(3); + expect(stack.config.retryLimit).toBe(3); + }); + + it('should accept custom retryDelay configuration', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryDelay: 1000 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryDelay).toBe(1000); + expect(stack.config.retryDelay).toBe(1000); + }); + + it('should accept both retryLimit and retryDelay configuration', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 2, + retryDelay: 500 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryLimit).toBe(2); + expect(client.defaults.retryDelay).toBe(500); + expect(stack.config.retryLimit).toBe(2); + expect(stack.config.retryDelay).toBe(500); + }); + + it('should accept retryOnError configuration', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryOnError: false + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryOnError).toBe(false); + expect(stack.config.retryOnError).toBe(false); + }); + + it('should accept custom retryCondition function', () => { + const customRetryCondition = (error: any) => error.response?.status === 500; + + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryCondition: customRetryCondition + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryCondition).toBe(customRetryCondition); + expect(stack.config.retryCondition).toBe(customRetryCondition); + }); + }); + + describe('Retry Configuration Integration', () => { + it('should pass retry configuration to the http client', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 4, + retryDelay: 750, + retryOnError: true + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify configuration is passed to the underlying HTTP client + expect(client.defaults.retryLimit).toBe(4); + expect(client.defaults.retryDelay).toBe(750); + expect(client.defaults.retryOnError).toBe(true); + }); + + it('should maintain retry configuration in stackConfig property', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 6, + retryDelay: 200 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // The client should have a stackConfig property with retry settings + expect(client.stackConfig).toBeDefined(); + expect(client.stackConfig.retryLimit).toBe(6); + expect(client.stackConfig.retryDelay).toBe(200); + }); + }); + + describe('Retry Configuration Verification', () => { + it('should verify retry interceptors are configured when retryLimit is set', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 2, + retryDelay: 100 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify retry interceptors are present + expect(client.interceptors.request.handlers.length).toBeGreaterThan(0); + expect(client.interceptors.response.handlers.length).toBeGreaterThan(0); + + // Verify configuration is properly set + expect(client.defaults.retryLimit).toBe(2); + expect(client.defaults.retryDelay).toBe(100); + }); + + it('should verify retry configuration is accessible through stackConfig', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 3, + retryDelay: 200 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Verify stackConfig contains retry settings + expect(client.stackConfig).toBeDefined(); + expect(client.stackConfig.retryLimit).toBe(3); + expect(client.stackConfig.retryDelay).toBe(200); + }); + + it('should verify retry settings are passed to httpClient configuration', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 4, + retryDelay: 500, + retryOnError: false + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + // Check that all retry settings are correctly configured + expect(client.defaults.retryLimit).toBe(4); + expect(client.defaults.retryDelay).toBe(500); + expect(client.defaults.retryOnError).toBe(false); + }); + }); + + describe('Edge Cases', () => { + it('should handle zero retryLimit', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 0 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryLimit).toBe(0); + }); + + it('should handle zero retryDelay', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryDelay: 0 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryDelay).toBe(0); + }); + + it('should handle large retry values', () => { + const config: StackConfig = { + apiKey: 'test-api-key', + deliveryToken: 'test-delivery-token', + environment: 'test-environment', + retryLimit: 100, + retryDelay: 5000 + }; + + const stack = Contentstack.stack(config); + const client = stack.getClient(); + + expect(client.defaults.retryLimit).toBe(100); + expect(client.defaults.retryDelay).toBe(5000); + }); + }); +}); From c8327b1f4f253e8b4777fa50e3ed598fe8352273 Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Wed, 17 Sep 2025 14:21:31 +0530 Subject: [PATCH 2/2] chore: update axios version to 1.12.2 in package.json and package-lock.json --- .talismanrc | 2 +- package-lock.json | 12 ++++++------ package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.talismanrc b/.talismanrc index ce5086a..9d03945 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,6 +1,6 @@ fileignoreconfig: - filename: package-lock.json - checksum: dffbcb14c5761976a9b6ab90b96c7e4fc5784eebe381cf48014618cc93355dbc + checksum: 17c3944c70209ee40840cee33e891b57cf1c672ecb03b410b71ad6fb0242a86e - filename: test/unit/query-optimization-comprehensive.spec.ts checksum: f5aaf6c784d7c101a05ca513c584bbd6e95f963d1e42779f2596050d9bcbac96 - filename: src/lib/entries.ts diff --git a/package-lock.json b/package-lock.json index b07ed71..81b4ea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@contentstack/delivery-sdk", - "version": "4.9.0", + "version": "4.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/delivery-sdk", - "version": "4.9.0", + "version": "4.9.1", "license": "MIT", "dependencies": { "@contentstack/core": "^1.3.0", "@contentstack/utils": "^1.4.1", - "axios": "^1.11.0", + "axios": "^1.12.2", "humps": "^2.0.1" }, "devDependencies": { @@ -5000,9 +5000,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/package.json b/package.json index 1fef992..ffd01bf 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "dependencies": { "@contentstack/core": "^1.3.0", "@contentstack/utils": "^1.4.1", - "axios": "^1.11.0", + "axios": "^1.12.2", "humps": "^2.0.1" }, "files": [