@nuskin/contentstack-lib 2.1.0-pa-1117.10 → 2.1.0-pa-1117.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.
@@ -15,6 +15,9 @@ const {contentstack: prodEnvConfig} = require(`../config/prod.json`);
15
15
  const mockPromiseResult = jest.fn();
16
16
  const mockSinglePromiseResult = jest.fn();
17
17
  const mockStack = jest.fn();
18
+ const mockContainedIn = jest.fn();
19
+ const mockIncludeReference = jest.fn();
20
+ const mockFetch = jest.fn();
18
21
 
19
22
  function getConfig(env) {
20
23
  let config = {};
@@ -42,34 +45,76 @@ function getConfig(env) {
42
45
  }
43
46
 
44
47
  beforeEach(() => {
45
- mockStack.mockImplementation(() => ({
46
- ContentType: () => ({
47
- Query: () => ({
48
- language: () => ({
49
- includeFallback: () => ({
50
- toJSON: () => ({
51
- find: mockPromiseResult,
52
- findOne: mockSinglePromiseResult
53
- })
54
- })
55
- }),
56
- containedIn: () => ({
57
- includeReference: () => ({
58
- toJSON: () => ({
59
- find: mockPromiseResult
60
- })
61
- })
48
+ const mockQuery = {
49
+ language: jest.fn(() => ({
50
+ includeFallback: () => ({
51
+ toJSON: () => ({
52
+ find: mockPromiseResult,
53
+ findOne: mockSinglePromiseResult
62
54
  })
63
55
  })
56
+ })),
57
+ containedIn: mockContainedIn,
58
+ includeReference: mockIncludeReference,
59
+ toJSON: jest.fn(() => ({
60
+ find: mockPromiseResult
61
+ }))
62
+ };
63
+
64
+ mockContainedIn.mockImplementation(() => mockQuery);
65
+ mockIncludeReference.mockImplementation(() => mockQuery);
66
+ mockStack.mockImplementation(() => ({
67
+ ContentType: () => ({
68
+ Query: () => mockQuery,
69
+ fetch: mockFetch
64
70
  })
65
71
  }));
66
72
 
73
+ mockFetch.mockResolvedValue({
74
+ content_type: {
75
+ schema: [
76
+ {
77
+ uid: 'countries',
78
+ data_type: 'reference'
79
+ },
80
+ {
81
+ uid: 'details',
82
+ data_type: 'group',
83
+ schema: [
84
+ {
85
+ uid: 'address_form',
86
+ data_type: 'reference'
87
+ }
88
+ ]
89
+ },
90
+ {
91
+ uid: 'promo_blocks',
92
+ data_type: 'blocks',
93
+ blocks: [
94
+ {
95
+ uid: 'hero',
96
+ schema: [
97
+ {
98
+ uid: 'banner',
99
+ data_type: 'reference'
100
+ }
101
+ ]
102
+ }
103
+ ]
104
+ }
105
+ ]
106
+ }
107
+ });
108
+
67
109
  jest.mock("contentstack", () => ({Stack: mockStack}));
68
110
  });
69
111
 
70
112
  afterEach(() => {
71
113
  mockSinglePromiseResult.mockReset();
72
114
  mockPromiseResult.mockReset();
115
+ mockContainedIn.mockReset();
116
+ mockIncludeReference.mockReset();
117
+ mockFetch.mockReset();
73
118
  //jest.resetAllMocks(); don't reset the mockStack function
74
119
  });
75
120
 
@@ -78,7 +123,7 @@ const mockResults = () => {
78
123
  mockPromiseResult.mockResolvedValueOnce(checkoutAdrStrings);
79
124
  mockSinglePromiseResult.mockResolvedValueOnce(singleCheckoutAdrStrings);
80
125
  mockSinglePromiseResult.mockResolvedValueOnce(singleCheckoutCartStrings);
81
- }
126
+ };
82
127
 
83
128
  describe("ContentstackApi", () => {
84
129
  test("getSingletonEntries", async () => {
@@ -152,4 +197,55 @@ describe("ContentstackApi", () => {
152
197
  await Stack.getSingletonEntries({contentTypeUIDs: ['checkout_adr_strings'], language: 'in'});
153
198
  expect(mockSinglePromiseResult).toHaveBeenCalled();
154
199
  });
200
+
201
+ test('getMultiEntries does not include references when includeRefs is false', async () => {
202
+ mockPromiseResult.mockResolvedValueOnce([[{title: 'United States'}]]);
203
+ const {getStack} = require('../src/api');
204
+ const Stack = getStack('dev');
205
+ await Stack.getMultiEntries('market', false, ['United States']);
206
+ expect(mockContainedIn).toHaveBeenCalledWith('title', ['United States']);
207
+ expect(mockIncludeReference).not.toHaveBeenCalled();
208
+ expect(mockFetch).not.toHaveBeenCalled();
209
+ });
210
+
211
+ test('getMultiEntries includes explicit reference paths', async () => {
212
+ mockPromiseResult.mockResolvedValueOnce([[{title: 'United States'}]]);
213
+ const {getStack} = require('../src/api');
214
+ const Stack = getStack('dev');
215
+ await Stack.getMultiEntries('market', ['countries', 'address_form.address_elements'], ['United States']);
216
+ expect(mockIncludeReference).toHaveBeenCalledWith(['countries', 'address_form.address_elements']);
217
+ expect(mockFetch).not.toHaveBeenCalled();
218
+ });
219
+
220
+ test('getMultiEntries discovers reference paths from schema when includeRefs is true', async () => {
221
+ mockPromiseResult.mockResolvedValueOnce([[{title: 'United States'}]]);
222
+ const {getStack} = require('../src/api');
223
+ const Stack = getStack('dev');
224
+ await Stack.getMultiEntries('shipping_address_form', true, ['United States']);
225
+ expect(mockFetch).toHaveBeenCalledTimes(1);
226
+ expect(mockIncludeReference).toHaveBeenCalledWith([
227
+ 'countries',
228
+ 'details.address_form',
229
+ 'promo_blocks.hero.banner'
230
+ ]);
231
+ });
232
+
233
+ test('getMultiEntries caches discovered reference paths per content type', async () => {
234
+ mockPromiseResult.mockResolvedValue([[{title: 'United States'}]]);
235
+ const {getStack} = require('../src/api');
236
+ const Stack = getStack('test');
237
+ await Stack.getMultiEntries('shipping_address_form', true, ['United States']);
238
+ await Stack.getMultiEntries('shipping_address_form', true, ['Canada']);
239
+ expect(mockFetch).toHaveBeenCalledTimes(1);
240
+ expect(mockIncludeReference).toHaveBeenNthCalledWith(1, [
241
+ 'countries',
242
+ 'details.address_form',
243
+ 'promo_blocks.hero.banner'
244
+ ]);
245
+ expect(mockIncludeReference).toHaveBeenNthCalledWith(2, [
246
+ 'countries',
247
+ 'details.address_form',
248
+ 'promo_blocks.hero.banner'
249
+ ]);
250
+ });
155
251
  });
package/docs/CHANGELOG.md CHANGED
@@ -1 +1 @@
1
- # [2.1.0-pa-1117.10](https://code.tls.nuskin.io/ns-am/utility/npm/contentstack-lib/compare/v2.1.0-pa-1117.9...v2.1.0-pa-1117.10) (2026-05-20)
1
+ # [2.1.0-pa-1117.11](https://code.tls.nuskin.io/ns-am/utility/npm/contentstack-lib/compare/v2.1.0-pa-1117.10...v2.1.0-pa-1117.11) (2026-05-21)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/contentstack-lib",
3
- "version": "2.1.0-pa-1117.10",
3
+ "version": "2.1.0-pa-1117.11",
4
4
  "description": "This project contains configuration and api code to access Contentstack, to be shared between the backend (AWS Lambda) and frontend (Vue, etc).",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/api.js CHANGED
@@ -8,6 +8,7 @@ const {contentstack: prodEnvConfig} = require(`../config/prod.json`);
8
8
 
9
9
  let env = null;
10
10
  let Stack = null;
11
+ const referenceFieldCache = new Map();
11
12
 
12
13
  const COMMON_LANGUAGES = {
13
14
  'da': 'da-dk',
@@ -173,6 +174,69 @@ async function getSingletonEntries(options) {
173
174
  }
174
175
  }
175
176
 
177
+ /**
178
+ * Collect reference field paths recursively from a content type schema.
179
+ * @param {Object[]} schema
180
+ * @param {string=} parentPath
181
+ * @return {string[]}
182
+ * @private
183
+ */
184
+ function _collectReferenceFieldPaths(schema = [], parentPath = '') {
185
+ return schema.flatMap((field) => {
186
+ const currentPath = parentPath ? `${parentPath}.${field.uid}` : field.uid;
187
+ const nestedSchemaPaths = Array.isArray(field.schema) ?
188
+ _collectReferenceFieldPaths(field.schema, currentPath) :
189
+ [];
190
+ const blockPaths = field.data_type === 'blocks' && Array.isArray(field.blocks) ?
191
+ field.blocks.flatMap((block) => {
192
+ return Array.isArray(block.schema) ?
193
+ _collectReferenceFieldPaths(block.schema, `${currentPath}.${block.uid}`) :
194
+ [];
195
+ }) :
196
+ [];
197
+
198
+ return [
199
+ ...(field.data_type === 'reference' ? [currentPath] : []),
200
+ ...nestedSchemaPaths,
201
+ ...blockPaths
202
+ ];
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Resolve reference include paths from a boolean, string, or array input.
208
+ * @param {string} contentType
209
+ * @param {boolean|string|string[]} includeRefs
210
+ * @return {Promise<string[]>}
211
+ * @private
212
+ */
213
+ async function _resolveIncludeRefs(contentType, includeRefs) {
214
+ if (!includeRefs) {
215
+ return [];
216
+ }
217
+
218
+ if (typeof includeRefs === 'string') {
219
+ return [includeRefs];
220
+ }
221
+
222
+ if (Array.isArray(includeRefs)) {
223
+ return includeRefs;
224
+ }
225
+
226
+ if (includeRefs !== true) {
227
+ return [];
228
+ }
229
+
230
+ if (referenceFieldCache.has(contentType)) {
231
+ return referenceFieldCache.get(contentType);
232
+ }
233
+
234
+ const contentTypeSchema = await Stack.ContentType(contentType).fetch();
235
+ const referenceFields = _collectReferenceFieldPaths(contentTypeSchema.content_type.schema);
236
+ referenceFieldCache.set(contentType, referenceFields);
237
+ return referenceFields;
238
+ }
239
+
176
240
  /**
177
241
  * Gets all the specified entries for a multiple content type
178
242
  * @param {string} contentType
@@ -181,19 +245,24 @@ async function getSingletonEntries(options) {
181
245
  */
182
246
  const getMultiEntries = async (contentType, includeRefs, values) => {
183
247
  try {
184
- const [results] = await Stack
248
+ const resolvedIncludeRefs = await _resolveIncludeRefs(contentType, includeRefs);
249
+
250
+ let query = Stack
185
251
  .ContentType(contentType)
186
252
  .Query()
187
- .containedIn('title', values)
188
- .includeReference(includeRefs)
189
- .toJSON()
190
- .find();
253
+ .containedIn('title', values);
254
+
255
+ if (resolvedIncludeRefs.length) {
256
+ query = query.includeReference(resolvedIncludeRefs);
257
+ }
258
+
259
+ const [results] = await query.toJSON().find();
191
260
 
192
261
  return results;
193
262
  } catch(err) {
194
263
  console.error(err);
195
264
  }
196
- }
265
+ };
197
266
 
198
267
  /**
199
268
  * Returns contentstack Stack object initialized with the api key, deliveryToken, and extended with custom functionality
@@ -214,8 +283,9 @@ function getStack(envrnmnt) {
214
283
  }
215
284
 
216
285
  env = envrnmnt;
217
- const {apiKey: api_key, deliveryToken: delivery_token, environment} = getConfig(env)
218
- Stack = Contentstack.Stack({api_key, delivery_token, environment})
286
+ referenceFieldCache.clear();
287
+ const {apiKey: api_key, deliveryToken: delivery_token, environment} = getConfig(env);
288
+ Stack = Contentstack.Stack({api_key, delivery_token, environment});
219
289
 
220
290
  // extend the Stack with a custom function to get translations
221
291
  Stack.getSingletonEntries = getSingletonEntries;