@nuskin/contentstack-lib 2.0.1 → 2.0.2-pa-1190.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/.releaserc +1 -1
- package/__tests__/api.spec.js +130 -28
- package/docs/CHANGELOG.md +1 -1
- package/package.json +1 -1
- package/src/api.js +77 -28
package/.releaserc
CHANGED
package/__tests__/api.spec.js
CHANGED
|
@@ -64,46 +64,120 @@ beforeEach(() => {
|
|
|
64
64
|
mockContainedIn.mockImplementation(() => mockQuery);
|
|
65
65
|
mockIncludeReference.mockImplementation(() => mockQuery);
|
|
66
66
|
mockStack.mockImplementation(() => ({
|
|
67
|
-
ContentType: () => ({
|
|
67
|
+
ContentType: (contentType) => ({
|
|
68
68
|
Query: () => mockQuery,
|
|
69
|
-
fetch: mockFetch
|
|
69
|
+
fetch: () => mockFetch(contentType)
|
|
70
70
|
})
|
|
71
71
|
}));
|
|
72
72
|
|
|
73
|
-
mockFetch.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
uid: 'countries',
|
|
78
|
-
data_type: 'reference'
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
uid: 'details',
|
|
82
|
-
data_type: 'group',
|
|
73
|
+
mockFetch.mockImplementation((contentType) => {
|
|
74
|
+
const schemas = {
|
|
75
|
+
shipping_address_form: {
|
|
76
|
+
content_type: {
|
|
83
77
|
schema: [
|
|
84
78
|
{
|
|
85
|
-
uid: '
|
|
86
|
-
data_type: 'reference'
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
uid: 'promo_blocks',
|
|
92
|
-
data_type: 'blocks',
|
|
93
|
-
blocks: [
|
|
79
|
+
uid: 'countries',
|
|
80
|
+
data_type: 'reference',
|
|
81
|
+
reference_to: 'market'
|
|
82
|
+
},
|
|
94
83
|
{
|
|
95
|
-
uid: '
|
|
84
|
+
uid: 'details',
|
|
85
|
+
data_type: 'group',
|
|
96
86
|
schema: [
|
|
97
87
|
{
|
|
98
|
-
uid: '
|
|
99
|
-
data_type: 'reference'
|
|
88
|
+
uid: 'address_form',
|
|
89
|
+
data_type: 'reference',
|
|
90
|
+
reference_to: 'address_form'
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
uid: 'promo_blocks',
|
|
96
|
+
data_type: 'blocks',
|
|
97
|
+
blocks: [
|
|
98
|
+
{
|
|
99
|
+
uid: 'hero',
|
|
100
|
+
schema: [
|
|
101
|
+
{
|
|
102
|
+
uid: 'banner',
|
|
103
|
+
data_type: 'reference',
|
|
104
|
+
reference_to: 'banner'
|
|
105
|
+
}
|
|
106
|
+
]
|
|
100
107
|
}
|
|
101
108
|
]
|
|
102
109
|
}
|
|
103
110
|
]
|
|
104
111
|
}
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
},
|
|
113
|
+
address_form: {
|
|
114
|
+
content_type: {
|
|
115
|
+
schema: [
|
|
116
|
+
{
|
|
117
|
+
uid: 'address_elements',
|
|
118
|
+
data_type: 'reference',
|
|
119
|
+
reference_to: 'address_form_field'
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
market: {
|
|
125
|
+
content_type: {
|
|
126
|
+
schema: []
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
banner: {
|
|
130
|
+
content_type: {
|
|
131
|
+
schema: []
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
address_form_field: {
|
|
135
|
+
content_type: {
|
|
136
|
+
schema: [
|
|
137
|
+
{
|
|
138
|
+
uid: 'field_config',
|
|
139
|
+
data_type: 'reference',
|
|
140
|
+
reference_to: 'field_config'
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
field_config: {
|
|
146
|
+
content_type: {
|
|
147
|
+
schema: [
|
|
148
|
+
{
|
|
149
|
+
uid: 'validation_rule',
|
|
150
|
+
data_type: 'reference',
|
|
151
|
+
reference_to: 'validation_rule'
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
validation_rule: {
|
|
157
|
+
content_type: {
|
|
158
|
+
schema: [
|
|
159
|
+
{
|
|
160
|
+
uid: 'too_deep_reference',
|
|
161
|
+
data_type: 'reference',
|
|
162
|
+
reference_to: 'too_deep_reference'
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
too_deep_reference: {
|
|
168
|
+
content_type: {
|
|
169
|
+
schema: [
|
|
170
|
+
{
|
|
171
|
+
uid: 'should_not_be_included',
|
|
172
|
+
data_type: 'reference',
|
|
173
|
+
reference_to: 'market'
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return Promise.resolve(schemas[contentType] || {content_type: {schema: []}});
|
|
107
181
|
});
|
|
108
182
|
|
|
109
183
|
jest.mock("contentstack", () => ({Stack: mockStack}));
|
|
@@ -222,10 +296,13 @@ describe("ContentstackApi", () => {
|
|
|
222
296
|
const {getStack} = require('../src/api');
|
|
223
297
|
const Stack = getStack('dev');
|
|
224
298
|
await Stack.getMultiEntries('shipping_address_form', true, ['United States']);
|
|
225
|
-
expect(mockFetch).toHaveBeenCalledTimes(
|
|
299
|
+
expect(mockFetch).toHaveBeenCalledTimes(6);
|
|
226
300
|
expect(mockIncludeReference).toHaveBeenCalledWith([
|
|
227
301
|
'countries',
|
|
228
302
|
'details.address_form',
|
|
303
|
+
'details.address_form.address_elements',
|
|
304
|
+
'details.address_form.address_elements.field_config',
|
|
305
|
+
'details.address_form.address_elements.field_config.validation_rule',
|
|
229
306
|
'promo_blocks.hero.banner'
|
|
230
307
|
]);
|
|
231
308
|
});
|
|
@@ -236,16 +313,41 @@ describe("ContentstackApi", () => {
|
|
|
236
313
|
const Stack = getStack('test');
|
|
237
314
|
await Stack.getMultiEntries('shipping_address_form', true, ['United States']);
|
|
238
315
|
await Stack.getMultiEntries('shipping_address_form', true, ['Canada']);
|
|
239
|
-
expect(mockFetch).toHaveBeenCalledTimes(
|
|
316
|
+
expect(mockFetch).toHaveBeenCalledTimes(6);
|
|
240
317
|
expect(mockIncludeReference).toHaveBeenNthCalledWith(1, [
|
|
241
318
|
'countries',
|
|
242
319
|
'details.address_form',
|
|
320
|
+
'details.address_form.address_elements',
|
|
321
|
+
'details.address_form.address_elements.field_config',
|
|
322
|
+
'details.address_form.address_elements.field_config.validation_rule',
|
|
243
323
|
'promo_blocks.hero.banner'
|
|
244
324
|
]);
|
|
245
325
|
expect(mockIncludeReference).toHaveBeenNthCalledWith(2, [
|
|
246
326
|
'countries',
|
|
247
327
|
'details.address_form',
|
|
328
|
+
'details.address_form.address_elements',
|
|
329
|
+
'details.address_form.address_elements.field_config',
|
|
330
|
+
'details.address_form.address_elements.field_config.validation_rule',
|
|
331
|
+
'promo_blocks.hero.banner'
|
|
332
|
+
]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('getMultiEntries limits automatic reference traversal to four levels deep', async () => {
|
|
336
|
+
mockPromiseResult.mockResolvedValueOnce([[{title: 'United States'}]]);
|
|
337
|
+
const {getStack} = require('../src/api');
|
|
338
|
+
const Stack = getStack('stage');
|
|
339
|
+
await Stack.getMultiEntries('shipping_address_form', true, ['United States']);
|
|
340
|
+
expect(mockIncludeReference).toHaveBeenCalledWith([
|
|
341
|
+
'countries',
|
|
342
|
+
'details.address_form',
|
|
343
|
+
'details.address_form.address_elements',
|
|
344
|
+
'details.address_form.address_elements.field_config',
|
|
345
|
+
'details.address_form.address_elements.field_config.validation_rule',
|
|
248
346
|
'promo_blocks.hero.banner'
|
|
249
347
|
]);
|
|
348
|
+
expect(mockIncludeReference).not.toHaveBeenCalledWith(expect.arrayContaining([
|
|
349
|
+
'details.address_form.address_elements.field_config.validation_rule.too_deep_reference',
|
|
350
|
+
'details.address_form.address_elements.field_config.validation_rule.too_deep_reference.should_not_be_included'
|
|
351
|
+
]));
|
|
250
352
|
});
|
|
251
353
|
});
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
## [2.0.1](https://code.tls.nuskin.io/ns-am/utility/npm/contentstack-lib/compare/v2.0.
|
|
1
|
+
## [2.0.2-pa-1190.1](https://code.tls.nuskin.io/ns-am/utility/npm/contentstack-lib/compare/v2.0.1...v2.0.2-pa-1190.1) (2026-06-02)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/contentstack-lib",
|
|
3
|
-
"version": "2.0.1",
|
|
3
|
+
"version": "2.0.2-pa-1190.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": {
|
package/src/api.js
CHANGED
|
@@ -9,6 +9,8 @@ const {contentstack: prodEnvConfig} = require(`../config/prod.json`);
|
|
|
9
9
|
let env = null;
|
|
10
10
|
let Stack = null;
|
|
11
11
|
const referenceFieldCache = new Map();
|
|
12
|
+
const contentTypeSchemaCache = new Map();
|
|
13
|
+
const MAX_REFERENCE_DEPTH = 4;
|
|
12
14
|
|
|
13
15
|
const COMMON_LANGUAGES = {
|
|
14
16
|
'da': 'da-dk',
|
|
@@ -174,33 +176,80 @@ async function getSingletonEntries(options) {
|
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return schema
|
|
179
|
+
async function _getContentTypeSchema(contentType) {
|
|
180
|
+
if (contentTypeSchemaCache.has(contentType)) {
|
|
181
|
+
return contentTypeSchemaCache.get(contentType);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const contentTypeSchema = await Stack.ContentType(contentType).fetch();
|
|
185
|
+
const schema = contentTypeSchema.content_type.schema || [];
|
|
186
|
+
contentTypeSchemaCache.set(contentType, schema);
|
|
187
|
+
return schema;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function _collectReferenceFieldPathsForSchema(schema = [], parentPath = '', visited = new Set(), depth = 0) {
|
|
191
|
+
const allPaths = [];
|
|
192
|
+
|
|
193
|
+
for (const field of schema) {
|
|
186
194
|
const currentPath = parentPath ? `${parentPath}.${field.uid}` : field.uid;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
195
|
+
|
|
196
|
+
if (Array.isArray(field.schema)) {
|
|
197
|
+
const nestedSchemaPaths = await _collectReferenceFieldPathsForSchema(field.schema, currentPath, visited, depth);
|
|
198
|
+
allPaths.push(...nestedSchemaPaths);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (field.data_type === 'blocks' && Array.isArray(field.blocks)) {
|
|
202
|
+
for (const block of field.blocks) {
|
|
203
|
+
if (Array.isArray(block.schema)) {
|
|
204
|
+
const blockPaths = await _collectReferenceFieldPathsForSchema(
|
|
205
|
+
block.schema,
|
|
206
|
+
`${currentPath}.${block.uid}`,
|
|
207
|
+
visited,
|
|
208
|
+
depth
|
|
209
|
+
);
|
|
210
|
+
allPaths.push(...blockPaths);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (field.data_type === 'reference') {
|
|
216
|
+
const currentDepth = depth + 1;
|
|
217
|
+
if (currentDepth > MAX_REFERENCE_DEPTH) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
allPaths.push(currentPath);
|
|
221
|
+
const referenceTargets = Array.isArray(field.reference_to) ?
|
|
222
|
+
field.reference_to :
|
|
223
|
+
field.reference_to ? [field.reference_to] : [];
|
|
224
|
+
|
|
225
|
+
if (currentDepth >= MAX_REFERENCE_DEPTH) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const referencedContentType of referenceTargets) {
|
|
230
|
+
const nestedReferencePaths = await _collectReferenceFieldPathsForContentType(
|
|
231
|
+
referencedContentType,
|
|
232
|
+
currentPath,
|
|
233
|
+
visited,
|
|
234
|
+
currentDepth
|
|
235
|
+
);
|
|
236
|
+
allPaths.push(...nestedReferencePaths);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return [...new Set(allPaths)];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function _collectReferenceFieldPathsForContentType(contentType, parentPath = '', visited = new Set(), depth = 0) {
|
|
245
|
+
const visitKey = `${contentType}|${parentPath}|${depth}`;
|
|
246
|
+
if (visited.has(visitKey)) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
visited.add(visitKey);
|
|
250
|
+
|
|
251
|
+
const schema = await _getContentTypeSchema(contentType);
|
|
252
|
+
return _collectReferenceFieldPathsForSchema(schema, parentPath, visited, depth);
|
|
204
253
|
}
|
|
205
254
|
|
|
206
255
|
/**
|
|
@@ -231,8 +280,7 @@ async function _resolveIncludeRefs(contentType, includeRefs) {
|
|
|
231
280
|
return referenceFieldCache.get(contentType);
|
|
232
281
|
}
|
|
233
282
|
|
|
234
|
-
const
|
|
235
|
-
const referenceFields = _collectReferenceFieldPaths(contentTypeSchema.content_type.schema);
|
|
283
|
+
const referenceFields = await _collectReferenceFieldPathsForContentType(contentType);
|
|
236
284
|
referenceFieldCache.set(contentType, referenceFields);
|
|
237
285
|
return referenceFields;
|
|
238
286
|
}
|
|
@@ -284,6 +332,7 @@ function getStack(envrnmnt) {
|
|
|
284
332
|
|
|
285
333
|
env = envrnmnt;
|
|
286
334
|
referenceFieldCache.clear();
|
|
335
|
+
contentTypeSchemaCache.clear();
|
|
287
336
|
const {apiKey: api_key, deliveryToken: delivery_token, environment} = getConfig(env);
|
|
288
337
|
Stack = Contentstack.Stack({api_key, delivery_token, environment});
|
|
289
338
|
|