@stackbit/cms-contentstack 0.1.1-staging.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.
Files changed (72) hide show
  1. package/README.md +1 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/contentstack-api-client.d.ts +63 -0
  4. package/dist/contentstack-api-client.d.ts.map +1 -0
  5. package/dist/contentstack-api-client.js +295 -0
  6. package/dist/contentstack-api-client.js.map +1 -0
  7. package/dist/contentstack-content-poller.d.ts +46 -0
  8. package/dist/contentstack-content-poller.d.ts.map +1 -0
  9. package/dist/contentstack-content-poller.js +111 -0
  10. package/dist/contentstack-content-poller.js.map +1 -0
  11. package/dist/contentstack-content-source.d.ts +138 -0
  12. package/dist/contentstack-content-source.d.ts.map +1 -0
  13. package/dist/contentstack-content-source.js +544 -0
  14. package/dist/contentstack-content-source.js.map +1 -0
  15. package/dist/contentstack-conversion-utils.d.ts +41 -0
  16. package/dist/contentstack-conversion-utils.d.ts.map +1 -0
  17. package/dist/contentstack-conversion-utils.js +504 -0
  18. package/dist/contentstack-conversion-utils.js.map +1 -0
  19. package/dist/contentstack-entries-converter.d.ts +39 -0
  20. package/dist/contentstack-entries-converter.d.ts.map +1 -0
  21. package/dist/contentstack-entries-converter.js +333 -0
  22. package/dist/contentstack-entries-converter.js.map +1 -0
  23. package/dist/contentstack-operation-converter.d.ts +42 -0
  24. package/dist/contentstack-operation-converter.d.ts.map +1 -0
  25. package/dist/contentstack-operation-converter.js +535 -0
  26. package/dist/contentstack-operation-converter.js.map +1 -0
  27. package/dist/contentstack-schema-converter.d.ts +26 -0
  28. package/dist/contentstack-schema-converter.d.ts.map +1 -0
  29. package/dist/contentstack-schema-converter.js +379 -0
  30. package/dist/contentstack-schema-converter.js.map +1 -0
  31. package/dist/contentstack-types.d.ts +429 -0
  32. package/dist/contentstack-types.d.ts.map +1 -0
  33. package/dist/contentstack-types.js +3 -0
  34. package/dist/contentstack-types.js.map +1 -0
  35. package/dist/contentstack-utils.d.ts +31 -0
  36. package/dist/contentstack-utils.d.ts.map +1 -0
  37. package/dist/contentstack-utils.js +144 -0
  38. package/dist/contentstack-utils.js.map +1 -0
  39. package/dist/entries-converter.d.ts +10 -0
  40. package/dist/entries-converter.d.ts.map +1 -0
  41. package/dist/entries-converter.js +245 -0
  42. package/dist/entries-converter.js.map +1 -0
  43. package/dist/file-download.d.ts +2 -0
  44. package/dist/file-download.d.ts.map +1 -0
  45. package/dist/file-download.js +33 -0
  46. package/dist/file-download.js.map +1 -0
  47. package/dist/index.d.ts +4 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +14 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/schema-converter.d.ts +3 -0
  52. package/dist/schema-converter.d.ts.map +1 -0
  53. package/dist/schema-converter.js +169 -0
  54. package/dist/schema-converter.js.map +1 -0
  55. package/dist/transformation-utils.d.ts +41 -0
  56. package/dist/transformation-utils.d.ts.map +1 -0
  57. package/dist/transformation-utils.js +730 -0
  58. package/dist/transformation-utils.js.map +1 -0
  59. package/dist/types.d.ts +120 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +3 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +44 -0
  64. package/src/contentstack-api-client.ts +330 -0
  65. package/src/contentstack-content-poller.ts +157 -0
  66. package/src/contentstack-content-source.ts +687 -0
  67. package/src/contentstack-entries-converter.ts +438 -0
  68. package/src/contentstack-operation-converter.ts +703 -0
  69. package/src/contentstack-schema-converter.ts +486 -0
  70. package/src/contentstack-types.ts +527 -0
  71. package/src/contentstack-utils.ts +174 -0
  72. package/src/index.ts +3 -0
@@ -0,0 +1,438 @@
1
+ import _ from 'lodash';
2
+ import * as StackbitTypes from '@stackbit/types';
3
+ import { getAssetUrl, getEntryUrl } from './contentstack-utils';
4
+ import type { Entry, Asset, Environment, User } from './contentstack-types';
5
+ import type { ModelWithContext } from './contentstack-schema-converter';
6
+
7
+ export type DocumentWithContext = StackbitTypes.Document<DocumentContext>;
8
+ export type DocumentContext = unknown;
9
+ export type AssetWithContext = StackbitTypes.Asset<AssetContext>;
10
+ export type AssetContext = unknown;
11
+
12
+ type GetModelByName = (modelName: string) => ModelWithContext | undefined;
13
+
14
+ export function convertEntries(options: {
15
+ entries: Entry[];
16
+ apiKey: string;
17
+ getModelByName: GetModelByName;
18
+ usersById: Record<string, User>;
19
+ publishEnvironment: Environment;
20
+ logger: StackbitTypes.Logger;
21
+ }): DocumentWithContext[] {
22
+ const { entries, ...rest } = options;
23
+ return entries.map((entry) =>
24
+ convertEntry({
25
+ entry,
26
+ ...rest
27
+ })
28
+ );
29
+ }
30
+
31
+ export function convertEntry({
32
+ entry,
33
+ apiKey,
34
+ getModelByName,
35
+ usersById,
36
+ publishEnvironment,
37
+ status,
38
+ logger
39
+ }: {
40
+ entry: Entry;
41
+ apiKey: string;
42
+ getModelByName: GetModelByName;
43
+ usersById: Record<string, User>;
44
+ publishEnvironment: Environment;
45
+ status?: StackbitTypes.DocumentStatus;
46
+ logger: StackbitTypes.Logger;
47
+ }): DocumentWithContext {
48
+ const model = getModelByName(entry.content_type_uid);
49
+ if (!model) {
50
+ throw new Error(`error converting an entry, model '${entry.content_type_uid}' not found`);
51
+ }
52
+ return {
53
+ type: 'document',
54
+ id: entry.uid,
55
+ manageUrl: getEntryUrl({ apiKey, entry }),
56
+ modelName: entry.content_type_uid,
57
+ status: status ?? getEntryPublishStatus(entry, publishEnvironment),
58
+ createdAt: entry.created_at,
59
+ updatedAt: entry.updated_at,
60
+ ...getEntityCreatedAndUpdatedBy(usersById, entry),
61
+ locale: entry.locale,
62
+ context: {},
63
+ fields: convertFields({
64
+ entry,
65
+ modelFields: model.fields ?? [],
66
+ rootEntry: entry,
67
+ rootModel: model,
68
+ entryFieldPath: [],
69
+ modelFieldPath: [],
70
+ getModelByName,
71
+ logger
72
+ })
73
+ };
74
+ }
75
+
76
+ function convertFields({
77
+ entry,
78
+ modelFields,
79
+ rootEntry,
80
+ rootModel,
81
+ entryFieldPath,
82
+ modelFieldPath,
83
+ getModelByName,
84
+ logger
85
+ }: {
86
+ entry: Record<string, unknown>;
87
+ modelFields: StackbitTypes.Field[];
88
+ rootEntry: Entry;
89
+ rootModel: ModelWithContext;
90
+ entryFieldPath: (string | number)[];
91
+ modelFieldPath: string[];
92
+ getModelByName: GetModelByName;
93
+ logger: StackbitTypes.Logger;
94
+ }): Record<string, StackbitTypes.DocumentField> {
95
+ const modelFieldsByName = _.keyBy(modelFields, 'name');
96
+ return _.entries(entry).reduce((documentFields: Record<string, StackbitTypes.DocumentField>, [fieldName, fieldValue]) => {
97
+ const modelField = modelFieldsByName[fieldName];
98
+ if (!modelField) {
99
+ return documentFields;
100
+ }
101
+ const documentField = convertField({
102
+ fieldValue: fieldValue,
103
+ modelField: modelField,
104
+ rootEntry: rootEntry,
105
+ rootModel: rootModel,
106
+ entryFieldPath: entryFieldPath.concat(fieldName),
107
+ modelFieldPath: modelFieldPath.concat(fieldName),
108
+ getModelByName,
109
+ logger
110
+ });
111
+ if (typeof documentField !== 'undefined') {
112
+ documentFields[fieldName] = documentField;
113
+ }
114
+ return documentFields;
115
+ }, {});
116
+ }
117
+
118
+ type ConvertFieldOptions<ModelField extends StackbitTypes.Field | StackbitTypes.FieldListItems> = {
119
+ fieldValue: unknown;
120
+ modelField: ModelField;
121
+ rootEntry: Entry;
122
+ rootModel: ModelWithContext;
123
+ entryFieldPath: (string | number)[];
124
+ modelFieldPath: string[];
125
+ getModelByName: GetModelByName;
126
+ logger: StackbitTypes.Logger;
127
+ };
128
+ function convertField(options: ConvertFieldOptions<StackbitTypes.Field>): StackbitTypes.DocumentField | undefined;
129
+ function convertField(options: ConvertFieldOptions<StackbitTypes.FieldListItems>): StackbitTypes.DocumentListFieldItems | undefined;
130
+ function convertField<ModelField extends StackbitTypes.Field | StackbitTypes.FieldListItems>({
131
+ fieldValue,
132
+ modelField,
133
+ rootEntry,
134
+ rootModel,
135
+ entryFieldPath,
136
+ modelFieldPath,
137
+ getModelByName,
138
+ logger
139
+ }: {
140
+ fieldValue: unknown;
141
+ modelField: ModelField;
142
+ rootEntry: Entry;
143
+ rootModel: ModelWithContext;
144
+ entryFieldPath: (string | number)[];
145
+ modelFieldPath: string[];
146
+ getModelByName: GetModelByName;
147
+ logger: StackbitTypes.Logger;
148
+ }): StackbitTypes.DocumentField | StackbitTypes.DocumentListFieldItems | undefined {
149
+ if (_.isNil(fieldValue) || fieldValue === '') {
150
+ return;
151
+ }
152
+
153
+ const errorPrefix = `Error occurred while converting entry '${rootEntry.uid}'.`;
154
+
155
+ switch (modelField.type) {
156
+ case 'string':
157
+ case 'url':
158
+ case 'slug':
159
+ case 'text':
160
+ case 'markdown':
161
+ case 'html':
162
+ case 'date':
163
+ case 'datetime':
164
+ case 'color':
165
+ case 'enum':
166
+ return {
167
+ type: modelField.type,
168
+ value: fieldValue as string
169
+ };
170
+ case 'number':
171
+ case 'boolean':
172
+ case 'json':
173
+ return {
174
+ type: modelField.type,
175
+ value: fieldValue as any
176
+ };
177
+ case 'richText':
178
+ return {
179
+ type: modelField.type,
180
+ value: fieldValue
181
+ };
182
+ case 'image':
183
+ if (!isObject(fieldValue) || typeof fieldValue.uid !== 'string') {
184
+ return;
185
+ }
186
+ return {
187
+ type: 'reference',
188
+ refType: 'asset',
189
+ refId: fieldValue.uid
190
+ };
191
+ case 'file':
192
+ if (!isObject(fieldValue) || typeof fieldValue.uid !== 'string') {
193
+ return;
194
+ }
195
+ return {
196
+ type: 'file',
197
+ value: fieldValue.uid
198
+ };
199
+ case 'reference':
200
+ // references fields always returned as arrays, even if they are not marked as multiple
201
+ if (Array.isArray(fieldValue) && fieldValue.length === 1) {
202
+ fieldValue = fieldValue[0];
203
+ }
204
+ if (!isObject(fieldValue) || typeof fieldValue.uid !== 'string') {
205
+ return;
206
+ }
207
+ return {
208
+ type: 'reference',
209
+ refType: 'document',
210
+ refId: fieldValue.uid
211
+ };
212
+ case 'object': {
213
+ if (!isObject(fieldValue)) {
214
+ logger.warn(`${errorPrefix} The value of a group field at field path ${entryFieldPath.join('.')} is not an object.`);
215
+ return;
216
+ }
217
+ return {
218
+ type: 'object',
219
+ fields: convertFields({
220
+ entry: fieldValue,
221
+ modelFields: modelField.fields,
222
+ rootEntry,
223
+ rootModel,
224
+ modelFieldPath,
225
+ entryFieldPath,
226
+ getModelByName,
227
+ logger
228
+ })
229
+ };
230
+ }
231
+ case 'model': {
232
+ const fieldPathStr = modelFieldPath.join('.');
233
+ const blockMap = rootModel.context?.blockMap?.[fieldPathStr];
234
+ if (blockMap) {
235
+ // this is a modular blocks field with keys mapping to inline (object) or global (model) fields
236
+ // mapped by context.blockMap[blockUID]
237
+ if (!isObject(fieldValue)) {
238
+ logger.warn(`${errorPrefix} The value of a modular block at field path ${entryFieldPath.join('.')} is not an object.`);
239
+ return;
240
+ }
241
+ // The fieldValue must be an object with a single property matching the block uid
242
+ const blockUIDs = _.keys(fieldValue);
243
+ const blockUID = blockUIDs[0];
244
+ if (blockUIDs.length !== 1 || !blockUID) {
245
+ logger.warn(
246
+ `${errorPrefix} The object wrapping a modular block at field path ${entryFieldPath.join('.')} ` +
247
+ `does not have a modular block uid property.`
248
+ );
249
+ return;
250
+ }
251
+ const nestedEntry = fieldValue[blockUID];
252
+ if (!isObject(nestedEntry)) {
253
+ return;
254
+ }
255
+ const modelName = blockMap.blockIdToModelName[blockUID];
256
+ if (!modelName) {
257
+ logger.warn(`${errorPrefix} Could not match model name for modular block '${blockUID}' at field path ${entryFieldPath.join('.')}.`);
258
+ return;
259
+ }
260
+ const nestedModel = getModelByName(modelName);
261
+ if (!nestedModel) {
262
+ logger.warn(
263
+ `${errorPrefix} Could not find model named '${modelName}' for modular block '${blockUID}' at field path ${entryFieldPath.join('.')}.`
264
+ );
265
+ return;
266
+ }
267
+ return {
268
+ type: 'model',
269
+ modelName: modelName,
270
+ fields: convertFields({
271
+ entry: nestedEntry,
272
+ modelFields: nestedModel.fields ?? [],
273
+ rootEntry,
274
+ rootModel: nestedModel,
275
+ entryFieldPath: entryFieldPath.concat(blockUID),
276
+ modelFieldPath: [],
277
+ getModelByName,
278
+ logger
279
+ })
280
+ };
281
+ } else {
282
+ // this is as a regular global field (model) with a single model or as a CNTSTK_LINK_MODEL
283
+ if (modelField.models.length !== 1) {
284
+ logger.warn(`${errorPrefix} The global field at field path ${entryFieldPath.join('.')} has more than one model name.`);
285
+ return;
286
+ }
287
+ const modelName = modelField.models[0]!;
288
+ const nestedModel = getModelByName(modelName);
289
+ if (!nestedModel) {
290
+ logger.warn(`${errorPrefix} Could not find model named '${modelName}' for global field at field path ${entryFieldPath.join('.')}.`);
291
+ return;
292
+ }
293
+ if (!isObject(fieldValue)) {
294
+ logger.warn(`${errorPrefix} The value of a global field at field path ${entryFieldPath.join('.')} is not an object.`);
295
+ return;
296
+ }
297
+ return {
298
+ type: 'model',
299
+ modelName: modelName,
300
+ fields: convertFields({
301
+ entry: fieldValue,
302
+ modelFields: nestedModel.fields ?? [],
303
+ rootEntry,
304
+ rootModel: nestedModel,
305
+ entryFieldPath: entryFieldPath,
306
+ modelFieldPath: [],
307
+ getModelByName,
308
+ logger
309
+ })
310
+ };
311
+ }
312
+ }
313
+ case 'list': {
314
+ if (!Array.isArray(fieldValue)) {
315
+ logger.warn(`${errorPrefix} The value of a list field at field path ${entryFieldPath.join('.')} is not an array.`);
316
+ return;
317
+ }
318
+ return {
319
+ type: 'list',
320
+ items: _.reduce(
321
+ fieldValue,
322
+ (documentListFieldItems: StackbitTypes.DocumentListFieldItems[], value, index) => {
323
+ const documentListFieldItem = convertField({
324
+ fieldValue: value,
325
+ modelField: modelField.items,
326
+ rootEntry,
327
+ rootModel,
328
+ entryFieldPath: entryFieldPath.concat(index),
329
+ modelFieldPath,
330
+ getModelByName,
331
+ logger
332
+ });
333
+ if (!documentListFieldItem) {
334
+ logger.debug(
335
+ `${errorPrefix} One of the items of a list at field path ${entryFieldPath.join('.')} ` +
336
+ `could not be converted, skipping the whole list field.`
337
+ );
338
+ return documentListFieldItems;
339
+ }
340
+ return documentListFieldItems.concat(documentListFieldItem);
341
+ },
342
+ []
343
+ )
344
+ };
345
+ }
346
+ case 'cross-reference':
347
+ case 'style':
348
+ logger.warn(`${errorPrefix} Field type ${modelField.type} at field path ${entryFieldPath.join('.')}.`);
349
+ return;
350
+ default: {
351
+ const _exhaustiveCheck: never = modelField;
352
+ return _exhaustiveCheck;
353
+ }
354
+ }
355
+ }
356
+
357
+ export function convertAssets(options: {
358
+ assets: Asset[];
359
+ apiKey: string;
360
+ usersById: Record<string, User>;
361
+ publishEnvironment: Environment;
362
+ }): AssetWithContext[] {
363
+ const { assets, ...rest } = options;
364
+ return assets.map((asset) => convertAsset({ asset, ...rest }));
365
+ }
366
+
367
+ export function convertAsset({
368
+ asset,
369
+ apiKey,
370
+ usersById,
371
+ publishEnvironment
372
+ }: {
373
+ asset: Asset;
374
+ apiKey: string;
375
+ usersById: Record<string, User>;
376
+ publishEnvironment: Environment;
377
+ }): AssetWithContext {
378
+ return {
379
+ type: 'asset',
380
+ id: asset.uid,
381
+ manageUrl: getAssetUrl({ apiKey, asset }),
382
+ status: getAssetPublishStatus(asset, publishEnvironment),
383
+ createdAt: asset.created_at,
384
+ updatedAt: asset.updated_at,
385
+ ...getEntityCreatedAndUpdatedBy(usersById, asset),
386
+ context: {},
387
+ fields: {
388
+ title: {
389
+ type: 'string',
390
+ value: asset.title ?? asset.filename ?? ''
391
+ },
392
+ file: {
393
+ type: 'assetFile',
394
+ url: asset.url!,
395
+ fileName: asset.filename,
396
+ contentType: asset.content_type,
397
+ size: Number(asset.file_size)
398
+ }
399
+ }
400
+ };
401
+ }
402
+
403
+ function getEntryPublishStatus(entry: Entry, publishEnvironment: Environment): StackbitTypes.DocumentStatus {
404
+ // Get the publish that matches the publish environment and the entry's locale
405
+ const publishDetailsForEnv = _.filter(entry.publish_details, _.matchesProperty('environment', publishEnvironment.uid));
406
+ const publishForLocale = _.find(publishDetailsForEnv, _.matchesProperty('locale', entry.locale));
407
+ if (publishForLocale) {
408
+ return entry._version === publishForLocale.version ? 'published' : 'modified';
409
+ }
410
+ return 'added';
411
+ }
412
+
413
+ function getAssetPublishStatus(asset: Asset, publishEnvironment: Environment): StackbitTypes.DocumentStatus {
414
+ // Get the publish that matches the publish environment and the asset's locale
415
+ const publishDetailsForEnv = _.filter(asset.publish_details, _.matchesProperty('environment', publishEnvironment.uid));
416
+ if (publishDetailsForEnv?.length === 1) {
417
+ const publish = publishDetailsForEnv[0]!;
418
+ return asset._version === publish.version ? 'published' : 'modified';
419
+ }
420
+ return 'added';
421
+ }
422
+
423
+ function getEntityCreatedAndUpdatedBy(usersById: Record<string, User>, entity: Entry | Asset) {
424
+ const result: { createdBy?: string; updatedBy?: string[] } = {};
425
+ const createdBy = usersById[entity.created_by];
426
+ if (createdBy && createdBy.email) {
427
+ result.createdBy = createdBy.email;
428
+ }
429
+ const updatedBy = usersById[entity.updated_by];
430
+ if (updatedBy && updatedBy.email) {
431
+ result.updatedBy = [updatedBy.email];
432
+ }
433
+ return result;
434
+ }
435
+
436
+ function isObject(value: unknown): value is Record<string, unknown> {
437
+ return _.isPlainObject(value);
438
+ }