Skip to content

Commit 163c327

Browse files
fix: resolve Sync API non-Axios response issue and update types for synchronization
1 parent f1fd787 commit 163c327

8 files changed

Lines changed: 183 additions & 161 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#### Date: Feb-16-2026
33
Breaking: Cache persistence is now a separate plugin. When using a cache policy other than `IGNORE_CACHE`, you must pass `cacheOptions.persistenceStore`. Install `@contentstack/persistence-plugin` and use `new PersistenceStore({ ... })` as the store. The SDK no longer bundles persistence code or accepts `storeType` in `cacheOptions`.
44
Enhancement: SDK defines only the `PersistenceStore` interface (getItem/setItem); full implementation lives in the plugin for a lighter core package.
5+
Fix: Sync API returns non-Axios response causing undefined data and recursive sync failure
56

67
### Version: 4.11.2
78
#### Date: feb-11-2026

package-lock.json

Lines changed: 105 additions & 105 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/delivery-sdk",
3-
"version": "5.0.0",
3+
"version": "5.0.1",
44
"type": "module",
55
"license": "MIT",
66
"engines": {

src/common/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,22 @@ export interface SyncType {
127127
locale?: string;
128128
startDate?: string;
129129
}
130+
131+
export interface SyncItem {
132+
type: string;
133+
event_at: string;
134+
content_type_uid: string;
135+
data: any;
136+
}
137+
138+
export interface SyncResponse {
139+
items: SyncItem[];
140+
skip?: number;
141+
limit?: number;
142+
total_count?: number;
143+
sync_token?: string;
144+
pagination_token?: string;
145+
}
130146
export type TransformData = { [key: string]: string | string[] };
131147

132148
export enum Format {

src/sync/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { synchronization } from './synchronization';
2+
export type { SyncResponse, SyncItem } from '../common/types';

src/sync/synchronization.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { AxiosInstance, getData } from '@contentstack/core';
2-
import { SyncStack, SyncType, PublishType } from '../common/types';
3-
import { AxiosRequestConfig, AxiosResponse } from 'axios';
2+
import { SyncStack, SyncType, PublishType, SyncResponse } from '../common/types';
3+
import { AxiosRequestConfig } from 'axios';
44
import humps from 'humps';
55

6-
export async function synchronization(client: AxiosInstance, params: SyncStack | SyncType = {}, recursive = false) {
6+
export async function synchronization(client: AxiosInstance, params: SyncStack | SyncType = {}, recursive = false): Promise<SyncResponse> {
77
const config: AxiosRequestConfig = { params };
88
const SYNC_URL = '/stacks/sync';
99

@@ -17,18 +17,19 @@ export async function synchronization(client: AxiosInstance, params: SyncStack |
1717
config.params = { ...config.params, type: type.join(',') };
1818
}
1919

20-
let response: AxiosResponse = await getData(client, SYNC_URL, { params: humps.decamelizeKeys(config.params) });
21-
const data = response.data;
20+
// getData returns response.data directly, not the full AxiosResponse
21+
let response: SyncResponse = await getData(client, SYNC_URL, { params: humps.decamelizeKeys(config.params) });
2222

23-
while (recursive && 'pagination_token' in response.data) {
24-
const recResponse: AxiosResponse = await getData(
23+
while (recursive && 'pagination_token' in response) {
24+
const recResponse: SyncResponse = await getData(
2525
client,
2626
SYNC_URL,
27-
humps.decamelizeKeys({ paginationToken: data.pagination_token })
27+
{ params: humps.decamelizeKeys({ paginationToken: response.pagination_token }) }
2828
);
29-
recResponse.data.items = { ...response.data.items, ...recResponse.data.items };
30-
response = { ...recResponse };
29+
// Merge items from all paginated responses
30+
recResponse.items = [...response.items, ...recResponse.items];
31+
response = recResponse;
3132
}
3233

33-
return response.data;
34+
return response;
3435
}

test/unit/sync-operations-comprehensive.spec.ts

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@ import { axiosGetMock } from '../utils/mocks';
55
import { httpClient } from '@contentstack/core';
66

77
jest.mock('@contentstack/core');
8-
const getDataMock = <jest.Mock<typeof core.getData>>(<unknown>core.getData);
8+
const getDataMock = core.getData as jest.MockedFunction<typeof core.getData>;
99

1010
describe('Comprehensive Sync Operations Tests', () => {
1111
const SYNC_URL = '/stacks/sync';
1212

1313
beforeEach(() => {
14-
getDataMock.mockImplementation((_client, _url, params) => {
15-
const resp: any = { ...axiosGetMock };
14+
getDataMock.mockImplementation(async (_client, _url, params) => {
15+
// getData returns response.data directly, not the full AxiosResponse
16+
const data: any = { ...axiosGetMock.data };
1617
if ('pagination_token' in params.params) {
17-
delete resp.data.pagination_token;
18-
resp.data.sync_token = '<sync_token>';
18+
delete data.pagination_token;
19+
data.sync_token = '<sync_token>';
1920
} else {
20-
resp.data.pagination_token = '<pagination_token>';
21+
data.pagination_token = '<pagination_token>';
2122
}
22-
return resp;
23+
return data;
2324
});
2425
});
2526

@@ -59,18 +60,18 @@ describe('Comprehensive Sync Operations Tests', () => {
5960

6061
describe('Delta Sync Operations', () => {
6162
it('should perform delta sync with sync token', async () => {
62-
getDataMock.mockImplementation((_client, _url, params) => {
63-
const resp: any = { ...axiosGetMock };
64-
resp.data.items = [
63+
getDataMock.mockImplementation(async (_client, _url, params) => {
64+
const data: any = { ...axiosGetMock.data };
65+
data.items = [
6566
{
6667
type: 'entry_published',
6768
event_at: new Date().toISOString(),
6869
content_type_uid: 'blog',
6970
data: { uid: 'entry_1', title: 'Updated Entry' }
7071
}
7172
];
72-
resp.data.sync_token = 'delta_sync_token';
73-
return resp;
73+
data.sync_token = 'delta_sync_token';
74+
return data;
7475
});
7576

7677
const result = await syncCall({ syncToken: 'previous_token' });
@@ -80,11 +81,11 @@ describe('Comprehensive Sync Operations Tests', () => {
8081
});
8182

8283
it('should handle empty delta sync response', async () => {
83-
getDataMock.mockImplementation((_client, _url, params) => {
84-
const resp: any = { ...axiosGetMock };
85-
resp.data.items = [];
86-
resp.data.sync_token = 'empty_sync_token';
87-
return resp;
84+
getDataMock.mockImplementation(async (_client, _url, params) => {
85+
const data: any = { ...axiosGetMock.data };
86+
data.items = [];
87+
data.sync_token = 'empty_sync_token';
88+
return data;
8889
});
8990

9091
const result = await syncCall({ syncToken: 'previous_token' });
@@ -93,9 +94,9 @@ describe('Comprehensive Sync Operations Tests', () => {
9394
});
9495

9596
it('should handle mixed entry types in delta sync', async () => {
96-
getDataMock.mockImplementation((_client, _url, params) => {
97-
const resp: any = { ...axiosGetMock };
98-
resp.data.items = [
97+
getDataMock.mockImplementation(async (_client, _url, params) => {
98+
const data: any = { ...axiosGetMock.data };
99+
data.items = [
99100
{
100101
type: 'entry_published',
101102
content_type_uid: 'blog',
@@ -112,8 +113,8 @@ describe('Comprehensive Sync Operations Tests', () => {
112113
data: { uid: 'asset_1', filename: 'image.jpg' }
113114
}
114115
];
115-
resp.data.sync_token = 'mixed_sync_token';
116-
return resp;
116+
data.sync_token = 'mixed_sync_token';
117+
return data;
117118
});
118119

119120
const result = await syncCall({ syncToken: 'previous_token' });
@@ -182,16 +183,16 @@ describe('Comprehensive Sync Operations Tests', () => {
182183

183184
describe('Sync Performance and Optimization', () => {
184185
it('should handle large dataset efficiently', async () => {
185-
getDataMock.mockImplementation((_client, _url, params) => {
186-
const resp: any = { ...axiosGetMock };
187-
resp.data.items = Array(1000).fill(null).map((_, i) => ({
186+
getDataMock.mockImplementation(async (_client, _url, params) => {
187+
const data: any = { ...axiosGetMock.data };
188+
data.items = Array(1000).fill(null).map((_, i) => ({
188189
type: 'entry_published',
189190
event_at: new Date().toISOString(),
190191
content_type_uid: 'blog',
191192
data: { uid: `entry_${i}`, title: `Entry ${i}` }
192193
}));
193-
resp.data.sync_token = 'large_dataset_token';
194-
return resp;
194+
data.sync_token = 'large_dataset_token';
195+
return data;
195196
});
196197

197198
const startTime = performance.now();
@@ -211,9 +212,9 @@ describe('Comprehensive Sync Operations Tests', () => {
211212

212213
describe('Sync Data Consistency', () => {
213214
it('should maintain data consistency', async () => {
214-
getDataMock.mockImplementation((_client, _url, params) => {
215-
const resp: any = { ...axiosGetMock };
216-
resp.data.items = [
215+
getDataMock.mockImplementation(async (_client, _url, params) => {
216+
const data: any = { ...axiosGetMock.data };
217+
data.items = [
217218
{
218219
type: 'entry_published',
219220
event_at: new Date().toISOString(),
@@ -226,8 +227,8 @@ describe('Comprehensive Sync Operations Tests', () => {
226227
}
227228
}
228229
];
229-
resp.data.sync_token = 'consistent_token';
230-
return resp;
230+
data.sync_token = 'consistent_token';
231+
return data;
231232
});
232233

233234
const result = await syncCall({ syncToken: 'previous_token' });
@@ -242,10 +243,9 @@ describe('Comprehensive Sync Operations Tests', () => {
242243
});
243244

244245
it('should handle malformed responses gracefully', async () => {
245-
getDataMock.mockImplementation((_client, _url, params) => {
246-
const resp: any = { ...axiosGetMock };
247-
resp.data = { malformed: true };
248-
return resp;
246+
getDataMock.mockImplementation(async (_client, _url, params) => {
247+
const data: any = { malformed: true };
248+
return data;
249249
});
250250

251251
const result = await syncCall();

test/unit/synchronization.spec.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ import { axiosGetMock } from '../utils/mocks';
66
import { httpClient } from '@contentstack/core';
77

88
jest.mock('@contentstack/core');
9-
const getDataMock = <jest.Mock<typeof core.getData>>(<unknown>core.getData);
9+
const getDataMock = core.getData as jest.MockedFunction<typeof core.getData>;
1010

1111
describe('Synchronization function', () => {
1212
const SYNC_URL = '/stacks/sync';
1313
beforeEach(() => {
14-
getDataMock.mockImplementation((_client, _url, params) => {
15-
const resp: any = axiosGetMock;
16-
if ('pagination_token' in params) {
17-
delete resp.data.pagination_token;
18-
resp.data.sync_token = '<sync_token>';
19-
} else resp.data.pagination_token = '<pagination_token>';
14+
getDataMock.mockImplementation(async (_client, _url, params) => {
15+
// getData returns response.data directly, not the full AxiosResponse
16+
const data: any = { ...axiosGetMock.data };
17+
if ('pagination_token' in params.params) {
18+
delete data.pagination_token;
19+
data.sync_token = '<sync_token>';
20+
} else {
21+
data.pagination_token = '<pagination_token>';
22+
}
2023

21-
return resp;
24+
return data;
2225
});
2326
});
2427
afterEach(() => {

0 commit comments

Comments
 (0)