@agility/content-sync 1.0.2 → 1.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agility/content-sync",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "JavaScript SDK for synchronizing content from Agility CMS",
5
5
  "main": "dist/agility-sync-sdk.node.js",
6
6
  "scripts": {
@@ -12,21 +12,23 @@ export default async function (languageCode, token) {
12
12
  let itemCount = 0
13
13
  let busy = false
14
14
  let waitMS = 0
15
- const waitMaxMS = 30000
16
- const waitIntervalMS = 500
15
+ const waitMaxMS = 60000
16
+ const waitIntervalMS = 1000
17
17
 
18
18
  do {
19
+ let syncRet = null
20
+
19
21
 
20
22
  //sync content items...
21
- const syncRet = await this.agilityClient.getSyncContent({
23
+ syncRet = await this.agilityClient.getSyncContent({
22
24
  syncToken: token,
23
25
  pageSize: 100,
24
26
  languageCode: languageCode,
25
27
 
26
28
  });
27
29
 
28
- if (syncRet.busy !== undefined
29
- && syncRet.busy === true) {
30
+
31
+ if (syncRet === undefined || (syncRet.busy !== undefined && syncRet.busy === true)) {
30
32
  //if the api is being updated, wait a few ms and try again...
31
33
  waitMS += waitIntervalMS
32
34
  if (waitMS > waitMaxMS) {
@@ -53,15 +55,19 @@ export default async function (languageCode, token) {
53
55
  const syncItems = syncRet.items;
54
56
 
55
57
  //if we don't get anything back, kick out
56
- if (syncItems.length === 0) {
57
- break;
58
+ if (syncItems.length > 0 ) {
59
+
60
+ for (let index = 0; index < syncItems.length; index++) {
61
+ await storeInterface.saveContentItem({ contentItem: syncItems[index], languageCode });
62
+ }
58
63
  }
59
64
 
60
- for (let index = 0; index < syncItems.length; index++) {
61
- await storeInterface.saveContentItem({ contentItem: syncItems[index], languageCode });
65
+ if (syncRet.syncToken > token) {
66
+ token = syncRet.syncToken;
67
+ } else {
68
+ break;
62
69
  }
63
70
 
64
- token = syncRet.syncToken;
65
71
  itemCount += syncItems.length;
66
72
 
67
73
  } while (token > 0 || busy === true)
@@ -1,7 +1,7 @@
1
1
  import { logInfo, logWarning, sleep } from '../util'
2
2
 
3
3
  export default async function (languageCode, token) {
4
- const storeInterface = this.store;
4
+ const storeInterface = this.store;
5
5
  if (!token) token = 0;
6
6
 
7
7
  let itemCount = 0;
@@ -29,7 +29,7 @@ export default async function (languageCode, token) {
29
29
  break
30
30
  }
31
31
 
32
- if (! busy) {
32
+ if (!busy) {
33
33
  busy = true
34
34
  logInfo("Sync API is busy. Waiting...")
35
35
  }
@@ -46,16 +46,21 @@ export default async function (languageCode, token) {
46
46
 
47
47
  const syncItems = syncRet.items;
48
48
 
49
- //if we don't get anything back, kick out
50
- if (syncItems.length === 0) {
51
- break;
49
+ //if we have something...
50
+ if (syncItems.length > 0) {
51
+
52
+
53
+ for (let index = 0; index < syncItems.length; index++) {
54
+ await storeInterface.savePageItem({ pageItem: syncItems[index], languageCode });
55
+ }
52
56
  }
53
57
 
54
- for (let index = 0; index < syncItems.length; index++) {
55
- await storeInterface.savePageItem({ pageItem: syncItems[index], languageCode });
58
+ if (syncRet.syncToken > token) {
59
+ token = syncRet.syncToken;
60
+ } else {
61
+ break;
56
62
  }
57
63
 
58
- token = syncRet.syncToken;
59
64
  itemCount += syncItems.length;
60
65
 
61
66
 
@@ -1,23 +1,64 @@
1
+ /**
2
+ * The function to handle saving/updating an item to your storage. This could be a Content Item, Page, Url Redirections, Sync State (state), or Sitemap.
3
+ * @param {Object} params - The parameters object
4
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
5
+ * @param {Object} params.item - The object representing the Content Item, Page, Url Redirections, Sync State (state), or Sitemap that needs to be saved/updated
6
+ * @param {String} params.itemType - The type of item being saved/updated, expected values are `item`, `page`, `sitemap`, `nestedsitemap`, `state`, `urlredirections`
7
+ * @param {String} params.languageCode - The locale code associated to the item being saved/updated
8
+ * @param {(String|Number)} params.itemID - The ID of the item being saved/updated - this could be a string or number depending on the itemType
9
+ * @returns {Void}
10
+ */
1
11
  const saveItem = async ({ options, item, itemType, languageCode, itemID }) => {
2
12
  console.log(`Console Interface: saveItem has been called`);
3
13
  return null;
4
14
  }
5
-
15
+ /**
16
+ * The function to handle deleting an item to your storage. This could be a Content Item, Page, Url Redirections, Sync State (state), or Sitemap.
17
+ * @param {Object} params - The parameters object
18
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
19
+ * @param {String} params.itemType - The type of item being deleted, expected values are `item`, `page`, `sitemap`, `nestedsitemap`, `state`, `urlredirections`
20
+ * @param {String} params.languageCode - The locale code associated to the item being saved/updated
21
+ * @param {(String|Number)} params.itemID - The ID of the item being deleted - this could be a string or number depending on the itemType
22
+ * @returns {Void}
23
+ */
6
24
  const deleteItem = async ({ options, itemType, languageCode, itemID }) => {
7
25
  console.log(`Console Interface: deleteItem has been called`);
8
26
  return null;
9
27
  }
10
-
28
+ /**
29
+ * The function to handle updating and placing a Content Item into a "list" so that you can handle querying a collection of items.
30
+ * @param {Object} params - The parameters object
31
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
32
+ * @param {Object} params.item - The object representing the Content Item
33
+ * @param {String} params.languageCode - The locale code associated to the item being saved/updated
34
+ * @param {(String|Number)} params.itemID - The ID of the item being updated - this could be a string or number depending on the itemType
35
+ * @param {String} params.referenceName - The reference name of the Content List that this Content Item should be added to
36
+ * @param {String} params.definitionName - The Model name that the Content Item is based on
37
+ * @returns {Void}
38
+ */
11
39
  const mergeItemToList = async ({ options, item, languageCode, itemID, referenceName, definitionName }) => {
12
40
  console.log(`Console Interface: mergeItemToList has been called`);
13
41
  return null;
14
42
  }
15
-
43
+ /**
44
+ * The function to handle retrieving a Content Item, Page, Url Redirections, Sync State (state), or Sitemap
45
+ * @param {Object} params - The parameters object
46
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
47
+ * @param {String} params.itemType - The type of item being accessed, expected values are `item`, `list`, `page`, `sitemap`, `nestedsitemap`, `state`, `urlredirections`
48
+ * @param {String} params.languageCode - The locale code associated to the item being accessed
49
+ * @param {(String|Number)} params.itemID - The ID of the item being accessed - this could be a string or number depending on the itemType
50
+ * @returns {Object}
51
+ */
16
52
  const getItem = async ({ options, itemType, languageCode, itemID }) => {
17
53
  console.log(`Console Interface: getItem has been called`)
18
54
  return null;
19
55
  }
20
-
56
+ /**
57
+ * The function to handle clearing the cache of synchronized data from the CMS
58
+ * @param {Object} params - The parameters object
59
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
60
+ * @returns {Void}
61
+ */
21
62
  const clearItems = async ({ options }) => {
22
63
  console.log(`Console Interface: clearItem has been called`)
23
64
  return null;
@@ -9,13 +9,17 @@ require("dotenv").config({
9
9
  path: `.env.${process.env.NODE_ENV}`,
10
10
  })
11
11
 
12
-
13
- const getFilePath = ({ options, itemType, languageCode, itemID }) => {
14
- const fileName = `${itemID}.json`;
15
-
16
- return path.join(options.rootPath, languageCode, itemType, fileName);
17
- }
18
-
12
+ /**
13
+ * The function to handle saving/updating an item to your storage. This could be a Content Item, Page, Url Redirections, Sync State (state), or Sitemap.
14
+ * @param {Object} params - The parameters object
15
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
16
+ * @param {String} params.options.rootPath - The path to store/access the content as JSON
17
+ * @param {Object} params.item - The object representing the Content Item, Page, Url Redirections, Sync State (state), or Sitemap that needs to be saved/updated
18
+ * @param {String} params.itemType - The type of item being saved/updated, expected values are `item`, `page`, `sitemap`, `nestedsitemap`, `state`, `urlredirections`
19
+ * @param {String} params.languageCode - The locale code associated to the item being saved/updated
20
+ * @param {(String|Number)} params.itemID - The ID of the item being saved/updated - this could be a string or number depending on the itemType
21
+ * @returns {Void}
22
+ */
19
23
  const saveItem = async ({ options, item, itemType, languageCode, itemID }) => {
20
24
 
21
25
  let filePath = getFilePath({ options, itemType, languageCode, itemID });
@@ -30,7 +34,16 @@ const saveItem = async ({ options, item, itemType, languageCode, itemID }) => {
30
34
  let json = JSON.stringify(item);
31
35
  fs.writeFileSync(filePath, json);
32
36
  }
33
-
37
+ /**
38
+ * The function to handle deleting an item to your storage. This could be a Content Item, Page, Url Redirections, Sync State (state), or Sitemap.
39
+ * @param {Object} params - The parameters object
40
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
41
+ * @param {String} params.options.rootPath - The path to store/access the content as JSON
42
+ * @param {String} params.itemType - The type of item being deleted, expected values are `item`, `page`, `sitemap`, `nestedsitemap`, `state`, `urlredirections`
43
+ * @param {String} params.languageCode - The locale code associated to the item being saved/updated
44
+ * @param {(String|Number)} params.itemID - The ID of the item being deleted - this could be a string or number depending on the itemType
45
+ * @returns {Void}
46
+ */
34
47
  const deleteItem = async ({ options, itemType, languageCode, itemID }) => {
35
48
 
36
49
  let filePath = getFilePath({ options, itemType, languageCode, itemID });
@@ -40,7 +53,18 @@ const deleteItem = async ({ options, itemType, languageCode, itemID }) => {
40
53
  }
41
54
 
42
55
  }
43
-
56
+ /**
57
+ * The function to handle updating and placing a Content Item into a "list" so that you can handle querying a collection of items.
58
+ * @param {Object} params - The parameters object
59
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
60
+ * @param {String} params.options.rootPath - The path to store/access the content as JSON
61
+ * @param {Object} params.item - The object representing the Content Item
62
+ * @param {String} params.languageCode - The locale code associated to the item being saved/updated
63
+ * @param {(String|Number)} params.itemID - The ID of the item being updated - this could be a string or number depending on the itemType
64
+ * @param {String} params.referenceName - The reference name of the Content List that this Content Item should be added to
65
+ * @param {String} params.definitionName - The Model name that the Content Item is based on
66
+ * @returns {Void}
67
+ */
44
68
  const mergeItemToList = async ({ options, item, languageCode, itemID, referenceName, definitionName }) => {
45
69
 
46
70
  let contentList = await getItem({ options, itemType: "list", languageCode, itemID: referenceName });
@@ -75,7 +99,16 @@ const mergeItemToList = async ({ options, item, languageCode, itemID, referenceN
75
99
 
76
100
  await saveItem({ options, item: contentList, itemType: "list", languageCode, itemID: referenceName });
77
101
  }
78
-
102
+ /**
103
+ * The function to handle retrieving a Content Item, Page, Url Redirections, Sync State (state), or Sitemap
104
+ * @param {Object} params - The parameters object
105
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
106
+ * @param {String} params.options.rootPath - The path to store/access the content as JSON
107
+ * @param {String} params.itemType - The type of item being accessed, expected values are `item`, `list`, `page`, `sitemap`, `nestedsitemap`, `state`, `urlredirections`
108
+ * @param {String} params.languageCode - The locale code associated to the item being accessed
109
+ * @param {(String|Number)} params.itemID - The ID of the item being accessed - this could be a string or number depending on the itemType
110
+ * @returns {Object}
111
+ */
79
112
  const getItem = async ({ options, itemType, languageCode, itemID }) => {
80
113
  let filePath = getFilePath({ options, itemType, languageCode, itemID });
81
114
 
@@ -85,18 +118,23 @@ const getItem = async ({ options, itemType, languageCode, itemID }) => {
85
118
  return JSON.parse(json);
86
119
  }
87
120
 
121
+ /**
122
+ * The function to handle clearing the cache of synchronized data from the CMS
123
+ * @param {Object} params - The parameters object
124
+ * @param {Object} params.options - A flexible object that can contain any properties specifically related to this interface
125
+ * @param {String} params.options.rootPath - The path to store/access the content as JSON
126
+ * @returns {Void}
127
+ */
88
128
  const clearItems = async ({ options }) => {
89
129
  fs.rmdirSync(options.rootPath, { recursive: true })
90
130
  }
91
131
 
92
- const waitOnLock = async (lockFile) => {
93
132
 
94
- while (await check(lockFile)) {
95
- await sleep(100)
96
- }
97
-
98
- }
99
133
 
134
+ /**
135
+ * The function to handle multi-threaded Syncs that may be happening at the same time. If you need to prevent a sync from happening and let it wait until another sync has finished use this.
136
+ * @returns {Promise}
137
+ */
100
138
  const mutexLock = async () => {
101
139
 
102
140
 
@@ -137,7 +175,18 @@ const mutexLock = async () => {
137
175
  }
138
176
 
139
177
 
178
+ //private function to get a wait on a lock file
179
+ const waitOnLock = async (lockFile) => {
180
+ while (await check(lockFile)) {
181
+ await sleep(100)
182
+ }
183
+ }
140
184
 
185
+ //private function to get path of an item
186
+ const getFilePath = ({ options, itemType, languageCode, itemID }) => {
187
+ const fileName = `${itemID}.json`;
188
+ return path.join(options.rootPath, languageCode, itemType, fileName);
189
+ }
141
190
 
142
191
 
143
192
  module.exports = {
@@ -82,20 +82,19 @@ const saveContentItem = async ({ contentItem, languageCode }) => {
82
82
  languageCode,
83
83
  itemID: contentItem.contentID,
84
84
  });
85
-
86
85
  if (currentItem) {
86
+
87
87
  //if the item is deleted, we need to grab the def and ref name from the current
88
88
  definitionName = sanitizeName(currentItem.properties.definitionName)
89
89
  referenceName = currentItem.properties.referenceName
90
- }
91
-
92
- await store.deleteItem({
93
- options,
94
- itemType: "item",
95
- languageCode,
96
- itemID: contentItem.contentID,
97
- });
98
90
 
91
+ await store.deleteItem({
92
+ options,
93
+ itemType: "item",
94
+ languageCode,
95
+ itemID: contentItem.contentID,
96
+ });
97
+ }
99
98
  } else {
100
99
  //regular item
101
100
  if (!contentItem.properties.definitionName
@@ -221,7 +220,14 @@ const getSyncState = async (languageCode) => {
221
220
  * @param {boolean} [requestParams.expandAllContentLinks] - Whether or not to expand entire linked content references, includings lists and items that are rendered in the CMS as Grid or Link. Default is **false**
222
221
  * @returns {Promise<Object>} - Returns a content item object.
223
222
  */
224
- const getContentItem = async ({ contentID, languageCode, depth = 2, expandAllContentLinks = false }) => {
223
+ const getContentItem = async ({ contentID, languageCode, depth, contentLinkDepth, expandAllContentLinks = false }) => {
224
+
225
+ if (depth === undefined && contentLinkDepth !== undefined) {
226
+ depth = contentLinkDepth
227
+ } else if (depth === undefined && contentLinkDepth === undefined) {
228
+ depth = 2
229
+ }
230
+
225
231
  const contentItem = await store.getItem({
226
232
  options,
227
233
  itemType: "item",
@@ -251,33 +257,92 @@ const expandContentItem = async ({ contentItem, languageCode, depth, expandAllCo
251
257
  depth: depth - 1,
252
258
  });
253
259
  if (childItem != null) fields[fieldName] = childItem;
254
- } else if (fieldValue.sortids && fieldValue.sortids.split) {
255
- //multi linked item
256
- const sortIDAry = fieldValue.sortids.split(",");
260
+ } else if (fieldValue.fulllist === true
261
+ && fieldValue.referencename
262
+ && expandAllContentLinks === true) {
263
+
264
+ //LINK TO THE FULL LIST
265
+ const referenceName = fieldValue.referencename
266
+
267
+ const skip = 0
268
+ const take = 50
269
+
270
+ const list = await getContentList({
271
+ referenceName,
272
+ languageCode,
273
+ depth: depth - 1,
274
+ expandAllContentLinks,
275
+ skip,
276
+ take
277
+ })
278
+
279
+ let sortIDAry = []
280
+
281
+ if (fieldValue.sortids && fieldValue.sortids.split) {
282
+ sortIDAry = fieldValue.sortids.split(",");
283
+ }
284
+
285
+ let itemCount = 0
257
286
  const childItems = [];
258
287
  for (const childItemID of sortIDAry) {
288
+ itemCount++
259
289
  const childItem = await getContentItem({
260
290
  contentID: childItemID,
261
291
  languageCode,
262
292
  depth: depth - 1,
293
+ expandAllContentLinks,
294
+ skip,
295
+ take
296
+ });
297
+
298
+ if (childItem != null) {
299
+ childItems.push(childItem);
300
+ }
301
+ }
302
+
303
+ for (const listItem of list) {
304
+ itemCount++
305
+ if (itemCount > 50) break;
306
+
307
+ const listItemContentID = listItem.contentID
308
+ if (sortIDAry.includes(`${listItemContentID}`)) {
309
+ continue;
310
+ }
311
+
312
+ const childItem = await getContentItem({
313
+ contentID: listItemContentID,
314
+ languageCode,
315
+ depth: depth - 1,
263
316
  expandAllContentLinks
264
317
  });
265
318
  if (childItem != null) childItems.push(childItem);
266
319
  }
320
+
267
321
  fields[fieldName] = childItems;
268
- } else if (fieldValue.referencename
269
- && expandAllContentLinks === true) {
270
- //if we are expanding ALL content links...
271
322
 
272
- const childList = await getContentList({
273
- referenceName: fieldValue.referencename,
323
+ } else if (fieldValue.sortids && fieldValue.sortids.split && fieldValue.fulllist !== true) {
324
+ //MULTI LINKED ITEM
325
+ let sortIDAry = []
326
+ if (fieldValue.sortids && fieldValue.sortids.split) {
327
+ sortIDAry = fieldValue.sortids.split(",");
328
+ }
329
+
330
+ const skip = expandAllContentLinks ? 0 : undefined
331
+ const take = expandAllContentLinks ? 50 : undefined
332
+
333
+ const childItems = [];
334
+ for (const childItemID of sortIDAry) {
335
+ const childItem = await getContentItem({
336
+ contentID: childItemID,
274
337
  languageCode,
275
338
  depth: depth - 1,
276
339
  expandAllContentLinks,
277
- take: 50
278
- })
279
-
280
- fields[fieldName] = childList
340
+ skip,
341
+ take
342
+ });
343
+ if (childItem != null) childItems.push(childItem);
344
+ }
345
+ fields[fieldName] = childItems;
281
346
 
282
347
  }
283
348
  }
@@ -358,7 +423,14 @@ const getContentList = async ({ referenceName, languageCode, depth = 0, expandAl
358
423
  * @param {*} { pageID, languageCode, depth = 3 }
359
424
  * @returns
360
425
  */
361
- const getPage = async ({ pageID, languageCode, depth = 3 }) => {
426
+ const getPage = async ({ pageID, languageCode, depth, contentLinkDepth, expandAllContentLinks = false }) => {
427
+
428
+ if (depth === undefined && contentLinkDepth !== undefined) {
429
+ depth = contentLinkDepth
430
+ } else if (depth === undefined && contentLinkDepth === undefined) {
431
+ depth = 2
432
+ }
433
+
362
434
  let pageItem = await store.getItem({
363
435
  options,
364
436
  itemType: "page",
@@ -377,6 +449,7 @@ const getPage = async ({ pageID, languageCode, depth = 3 }) => {
377
449
  contentID: mod.item.contentid,
378
450
  languageCode,
379
451
  depth: depth - 1,
452
+ expandAllContentLinks
380
453
  });
381
454
  mod.item = moduleItem;
382
455
  }
@@ -9,7 +9,7 @@ import storeInterfaceFileSystem from './store-interface-filesystem'
9
9
 
10
10
  function getSyncClient (userConfig) {
11
11
  validateConfigParams(userConfig);
12
- return createSyncCient(userConfig);
12
+ return createSyncClient(userConfig);
13
13
  }
14
14
 
15
15
  function validateConfigParams(configParams) {
@@ -39,11 +39,11 @@ const defaultConfig = {
39
39
  }
40
40
  };
41
41
 
42
- function createSyncCient(userConfig) {
42
+ function createSyncClient(userConfig) {
43
43
  let config = {
44
44
  ...defaultConfig,
45
45
  ...userConfig
46
-
46
+
47
47
  }
48
48
 
49
49
  const agilityClient = agility.getApi({
@@ -60,7 +60,7 @@ function createSyncCient(userConfig) {
60
60
 
61
61
  //set the sync storage interface provider, it will also validate it
62
62
  storeInterface.setStore(store, config.store.options);
63
-
63
+
64
64
  return {
65
65
  config,
66
66
  agilityClient,