@nuskin/contentstack-lib 2.0.0 → 2.0.1
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/.eslintrc.json +1 -1
- package/README.md +5 -14
- package/__tests__/api.spec.js +125 -14
- package/docs/CHANGELOG.md +1 -1
- package/package.json +2 -2
- package/src/api.js +96 -3
- package/src/index.js +1 -2
package/.eslintrc.json
CHANGED
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ FOR COMMON SPANISH AND MX-es FOR EXAMPLE.
|
|
|
10
10
|
|
|
11
11
|
## Installing
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Using npm:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install @nuskin/contentstack-lib
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Using yarn:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
yarn add @nuskin/contentstack-lib
|
|
@@ -27,8 +27,9 @@ yarn add @nuskin/contentstack-lib
|
|
|
27
27
|
```js
|
|
28
28
|
const { getStack } = require('@nuskin/contentstack-lib')
|
|
29
29
|
|
|
30
|
-
//
|
|
31
|
-
|
|
30
|
+
// Pass an environment explicitly outside the browser.
|
|
31
|
+
// In the browser, getStack() can infer the env from the hostname.
|
|
32
|
+
const Stack = getStack('prod')
|
|
32
33
|
|
|
33
34
|
// get all languages
|
|
34
35
|
const languages = await Stack.ContentType('supported_language').Query().toJSON().find();
|
|
@@ -36,16 +37,6 @@ const languages = await Stack.ContentType('supported_language').Query().toJSON()
|
|
|
36
37
|
// get all markets
|
|
37
38
|
const markets = await Stack.ContentType('market').Query().toJSON().find();
|
|
38
39
|
|
|
39
|
-
// Use the extended getStrings function to get translations
|
|
40
|
-
// ==================== Deprecated - Dont use this function ==========================
|
|
41
|
-
const translations = Stack.getStrings({
|
|
42
|
-
contentTypes: ['checkout_adr_strings', 'checkout_cart_strings'], // List of content types you want to get translations for
|
|
43
|
-
language: 'en',
|
|
44
|
-
country: 'US',
|
|
45
|
-
merge: true // Will combine checkout_adr_strings and checkout_cart_strings object with single list of strings
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Use this function instead to get your translations
|
|
49
40
|
// The entries are returned in the order they were requested so you can destructure the array accordingly
|
|
50
41
|
// To get the result in camelcase instead of snake case you can pass in the option.camelcase: true
|
|
51
42
|
// Contentstack does not merge common english strings with en_US strings. This function allows you to merge the two with mergeWithFallback: true
|
package/__tests__/api.spec.js
CHANGED
|
@@ -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,27 +45,76 @@ function getConfig(env) {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
beforeEach(() => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
find: mockPromiseResult,
|
|
52
|
-
findOne: mockSinglePromiseResult
|
|
53
|
-
})
|
|
54
|
-
})
|
|
48
|
+
const mockQuery = {
|
|
49
|
+
language: jest.fn(() => ({
|
|
50
|
+
includeFallback: () => ({
|
|
51
|
+
toJSON: () => ({
|
|
52
|
+
find: mockPromiseResult,
|
|
53
|
+
findOne: mockSinglePromiseResult
|
|
55
54
|
})
|
|
56
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
|
|
57
70
|
})
|
|
58
71
|
}));
|
|
59
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
|
+
|
|
60
109
|
jest.mock("contentstack", () => ({Stack: mockStack}));
|
|
61
110
|
});
|
|
62
111
|
|
|
63
112
|
afterEach(() => {
|
|
64
113
|
mockSinglePromiseResult.mockReset();
|
|
65
114
|
mockPromiseResult.mockReset();
|
|
115
|
+
mockContainedIn.mockReset();
|
|
116
|
+
mockIncludeReference.mockReset();
|
|
117
|
+
mockFetch.mockReset();
|
|
66
118
|
//jest.resetAllMocks(); don't reset the mockStack function
|
|
67
119
|
});
|
|
68
120
|
|
|
@@ -71,7 +123,7 @@ const mockResults = () => {
|
|
|
71
123
|
mockPromiseResult.mockResolvedValueOnce(checkoutAdrStrings);
|
|
72
124
|
mockSinglePromiseResult.mockResolvedValueOnce(singleCheckoutAdrStrings);
|
|
73
125
|
mockSinglePromiseResult.mockResolvedValueOnce(singleCheckoutCartStrings);
|
|
74
|
-
}
|
|
126
|
+
};
|
|
75
127
|
|
|
76
128
|
describe("ContentstackApi", () => {
|
|
77
129
|
test("getSingletonEntries", async () => {
|
|
@@ -133,8 +185,67 @@ describe("ContentstackApi", () => {
|
|
|
133
185
|
global.window = Object.create(window);
|
|
134
186
|
global.window.location = { hostname };
|
|
135
187
|
const {getStack} = require('../src/api');
|
|
136
|
-
getStack(
|
|
188
|
+
getStack();
|
|
137
189
|
expect(mockStack).toHaveBeenCalledWith({api_key: csCfg.apiKey, delivery_token: csCfg.deliveryToken, environment: csCfg.environment});
|
|
138
|
-
})
|
|
139
|
-
})
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('getSingletonEntries maps language code in to id', async () => {
|
|
194
|
+
mockSinglePromiseResult.mockResolvedValueOnce(singleCheckoutAdrStrings);
|
|
195
|
+
const {getStack} = require('../src/api');
|
|
196
|
+
const Stack = getStack('dev');
|
|
197
|
+
await Stack.getSingletonEntries({contentTypeUIDs: ['checkout_adr_strings'], language: 'in'});
|
|
198
|
+
expect(mockSinglePromiseResult).toHaveBeenCalled();
|
|
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
|
+
});
|
|
140
251
|
});
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
## [2.0.1](https://code.tls.nuskin.io/ns-am/utility/npm/contentstack-lib/compare/v2.0.0...v2.0.1) (2026-05-22)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/contentstack-lib",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
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": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"homepage": "https://code.tls.nuskin.io/ns-am/utility/npm/contentstack-lib/blob/master/README.md",
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"eslint": "
|
|
22
|
+
"eslint": "7.32.0",
|
|
23
23
|
"eslint-config-google": "0.14.0",
|
|
24
24
|
"eslint-config-prettier": "4.1.0",
|
|
25
25
|
"eslint-plugin-json": "2.1.1",
|
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,10 +174,100 @@ 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
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Gets all the specified entries for a multiple content type
|
|
242
|
+
* @param {string} contentType
|
|
243
|
+
* @param {string[]} values
|
|
244
|
+
* @return {Promise<{Object}>} config
|
|
245
|
+
*/
|
|
246
|
+
const getMultiEntries = async (contentType, includeRefs, values) => {
|
|
247
|
+
try {
|
|
248
|
+
const resolvedIncludeRefs = await _resolveIncludeRefs(contentType, includeRefs);
|
|
249
|
+
|
|
250
|
+
let query = Stack
|
|
251
|
+
.ContentType(contentType)
|
|
252
|
+
.Query()
|
|
253
|
+
.containedIn('title', values);
|
|
254
|
+
|
|
255
|
+
if (resolvedIncludeRefs.length) {
|
|
256
|
+
query = query.includeReference(resolvedIncludeRefs);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const [results] = await query.toJSON().find();
|
|
260
|
+
|
|
261
|
+
return results;
|
|
262
|
+
} catch(err) {
|
|
263
|
+
console.error(err);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
176
267
|
/**
|
|
177
268
|
* Returns contentstack Stack object initialized with the api key, deliveryToken, and extended with custom functionality
|
|
178
269
|
* @param {'dev' | 'test' | 'stage' | 'prod'} envrnmnt
|
|
179
|
-
* @return {Object} Stack - contentstack Stack sdk object with additional
|
|
270
|
+
* @return {Object} Stack - contentstack Stack sdk object with additional helper functions
|
|
180
271
|
*/
|
|
181
272
|
function getStack(envrnmnt) {
|
|
182
273
|
if (!envrnmnt) {
|
|
@@ -192,11 +283,13 @@ function getStack(envrnmnt) {
|
|
|
192
283
|
}
|
|
193
284
|
|
|
194
285
|
env = envrnmnt;
|
|
195
|
-
|
|
196
|
-
|
|
286
|
+
referenceFieldCache.clear();
|
|
287
|
+
const {apiKey: api_key, deliveryToken: delivery_token, environment} = getConfig(env);
|
|
288
|
+
Stack = Contentstack.Stack({api_key, delivery_token, environment});
|
|
197
289
|
|
|
198
290
|
// extend the Stack with a custom function to get translations
|
|
199
291
|
Stack.getSingletonEntries = getSingletonEntries;
|
|
292
|
+
Stack.getMultiEntries = getMultiEntries;
|
|
200
293
|
|
|
201
294
|
return Stack;
|
|
202
295
|
}
|