@stackbit/cms-contentstack 0.1.2-develop.2 → 0.1.2-staging.2
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/dist/.tsbuildinfo +1 -1
- package/dist/contentstack-content-poller.js +1 -1
- package/dist/contentstack-content-poller.js.map +1 -1
- package/dist/contentstack-content-source.d.ts +3 -0
- package/dist/contentstack-content-source.d.ts.map +1 -1
- package/dist/contentstack-content-source.js +45 -47
- package/dist/contentstack-content-source.js.map +1 -1
- package/dist/contentstack-operation-converter.d.ts +8 -6
- package/dist/contentstack-operation-converter.d.ts.map +1 -1
- package/dist/contentstack-operation-converter.js.map +1 -1
- package/dist/contentstack-types.d.ts +42 -11
- package/dist/contentstack-types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/contentstack-content-poller.ts +1 -1
- package/src/contentstack-content-source.ts +50 -48
- package/src/contentstack-operation-converter.ts +6 -7
- package/src/contentstack-types.ts +42 -11
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
convertAssets,
|
|
16
16
|
DocumentWithContext
|
|
17
17
|
} from './contentstack-entries-converter';
|
|
18
|
-
import { createEntryFromOperationFields, updateEntryFromUpdateOperations } from './contentstack-operation-converter';
|
|
18
|
+
import { createEntryFromOperationFields, updateEntryFromUpdateOperations, GetLeanDocumentById } from './contentstack-operation-converter';
|
|
19
19
|
import { getContentstackDashboardUrl, resolvePublishEnvironment, downloadFile, createWebhookIfNeeded, userMissingInMap } from './contentstack-utils';
|
|
20
20
|
import { ContentPoller, getLastUpdatedEntityDate, SyncContext, SyncResult } from './contentstack-content-poller';
|
|
21
21
|
import type { User, Asset, Entry, Environment, WebhookPayload } from './contentstack-types';
|
|
@@ -85,6 +85,7 @@ export class ContentstackContentSource
|
|
|
85
85
|
private publishEnvironmentName?: string;
|
|
86
86
|
private publishEnvironment!: Environment;
|
|
87
87
|
private usersById: Record<string, User> = {};
|
|
88
|
+
private newUnsyncedEntryMap: Record<string, string> = {};
|
|
88
89
|
|
|
89
90
|
constructor({ apiKey, managementToken, branch, authtoken, masterLocale, publishEnvironmentName, skipFetchOnStartIfCache }: ContentStackSourceOptions) {
|
|
90
91
|
this.apiKey = apiKey;
|
|
@@ -214,6 +215,11 @@ export class ContentstackContentSource
|
|
|
214
215
|
} else {
|
|
215
216
|
const result = await this.convertSyncResult(syncResult);
|
|
216
217
|
await this.cache.updateContent(result);
|
|
218
|
+
for (const document of result.documents) {
|
|
219
|
+
if (this.newUnsyncedEntryMap[document.id]) {
|
|
220
|
+
delete this.newUnsyncedEntryMap[document.id];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
217
223
|
}
|
|
218
224
|
}
|
|
219
225
|
});
|
|
@@ -336,7 +342,7 @@ export class ContentstackContentSource
|
|
|
336
342
|
let entries: Entry[];
|
|
337
343
|
const cachedEntries = ((await this.cache.get('entries')) as Entry[]) ?? [];
|
|
338
344
|
try {
|
|
339
|
-
if (this.syncContext.lastUpdatedEntryDate && cachedEntries) {
|
|
345
|
+
if (this.syncContext.lastUpdatedEntryDate && !_.isEmpty(cachedEntries)) {
|
|
340
346
|
entries = cachedEntries;
|
|
341
347
|
if (!this.skipFetchOnStartIfCache) {
|
|
342
348
|
this.logger.debug(
|
|
@@ -382,7 +388,7 @@ export class ContentstackContentSource
|
|
|
382
388
|
let assets: Asset[];
|
|
383
389
|
const cachedAssets = ((await this.cache.get('assets')) as Asset[]) ?? [];
|
|
384
390
|
try {
|
|
385
|
-
if (this.syncContext.lastUpdatedAssetDate && cachedAssets) {
|
|
391
|
+
if (this.syncContext.lastUpdatedAssetDate && !_.isEmpty(cachedAssets)) {
|
|
386
392
|
assets = cachedAssets;
|
|
387
393
|
if (!this.skipFetchOnStartIfCache) {
|
|
388
394
|
this.logger.debug(`got ${cachedAssets.length} assets from cache, fetching assets updated after ${this.syncContext.lastUpdatedAssetDate}`);
|
|
@@ -439,11 +445,14 @@ export class ContentstackContentSource
|
|
|
439
445
|
const entry = createEntryFromOperationFields({
|
|
440
446
|
updateOperationFields: options.updateOperationFields,
|
|
441
447
|
model: options.model,
|
|
442
|
-
getDocumentById: this.
|
|
448
|
+
getDocumentById: this.getCachedDocumentById.bind(this),
|
|
443
449
|
getModelByName: this.cache.getModelByName,
|
|
444
450
|
logger: this.logger
|
|
445
451
|
});
|
|
446
452
|
const newEntry = await this.contentStackClient.createEntry(options.model.name, entry);
|
|
453
|
+
// cache the created entry's uid and model name until it is received from Contentstack
|
|
454
|
+
// via onWebhook or contentPoller's notificationCallback methods.
|
|
455
|
+
this.newUnsyncedEntryMap[newEntry.uid] = options.model.name;
|
|
447
456
|
return { documentId: newEntry.uid };
|
|
448
457
|
}
|
|
449
458
|
|
|
@@ -458,13 +467,28 @@ export class ContentstackContentSource
|
|
|
458
467
|
const updatedEntry = updateEntryFromUpdateOperations({
|
|
459
468
|
entry,
|
|
460
469
|
updateOperations: operations,
|
|
461
|
-
getDocumentById: this.
|
|
470
|
+
getDocumentById: this.getCachedDocumentById.bind(this),
|
|
462
471
|
getModelByName: this.cache.getModelByName,
|
|
463
472
|
logger: this.logger
|
|
464
473
|
});
|
|
465
474
|
await this.contentStackClient.updateEntry(updatedEntry);
|
|
466
475
|
}
|
|
467
476
|
|
|
477
|
+
getCachedDocumentById(documentId: string): ReturnType<GetLeanDocumentById> {
|
|
478
|
+
const document = this.cache.getDocumentById(documentId);
|
|
479
|
+
if (document) {
|
|
480
|
+
return document;
|
|
481
|
+
}
|
|
482
|
+
if (this.newUnsyncedEntryMap[documentId]) {
|
|
483
|
+
this.logger.debug(`the created document ${documentId} was not yet fetched from Contentstack, using the cached document`);
|
|
484
|
+
return {
|
|
485
|
+
id: documentId,
|
|
486
|
+
modelName: this.newUnsyncedEntryMap[documentId]!
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
468
492
|
async deleteDocument(options: { document: StackbitTypes.Document<unknown>; userContext?: UserWithContext }): Promise<void> {
|
|
469
493
|
this.logger.debug('deleteDocument');
|
|
470
494
|
await this.contentStackClient.deleteEntry(options.document.modelName, options.document.id);
|
|
@@ -483,7 +507,7 @@ export class ContentstackContentSource
|
|
|
483
507
|
let asset: Asset;
|
|
484
508
|
try {
|
|
485
509
|
if (options.base64) {
|
|
486
|
-
// TODO:
|
|
510
|
+
// TODO: pass the path to .stackbit/cache folder in init() options and use it to write the temp file
|
|
487
511
|
await writeFile(tempName, Buffer.from(options.base64, 'base64'));
|
|
488
512
|
} else {
|
|
489
513
|
await downloadFile(options.url!, tempName);
|
|
@@ -549,41 +573,31 @@ export class ContentstackContentSource
|
|
|
549
573
|
|
|
550
574
|
this.logger.debug(`got webhook for ${webhookData.event} ${webhookData.module}`);
|
|
551
575
|
|
|
576
|
+
let unsyncedEntryUid: string | undefined;
|
|
577
|
+
|
|
552
578
|
if (webhookData.module === 'entry') {
|
|
553
579
|
switch (webhookData.event) {
|
|
554
580
|
case 'create':
|
|
555
|
-
case 'update':
|
|
556
|
-
if (userMissingInMap(this.usersById, webhookData.data.entry as Entry)) {
|
|
557
|
-
await this.fetchUsers();
|
|
558
|
-
}
|
|
559
|
-
const document = convertEntry({
|
|
560
|
-
entry: {
|
|
561
|
-
...webhookData.data.entry,
|
|
562
|
-
content_type_uid: webhookData.data.content_type.uid,
|
|
563
|
-
publish_details: [],
|
|
564
|
-
_branch: webhookData.data.branch.uid
|
|
565
|
-
} as unknown as Entry,
|
|
566
|
-
status: webhookData.event === 'create' ? 'added' : 'modified',
|
|
567
|
-
apiKey: this.apiKey,
|
|
568
|
-
getModelByName: this.cache.getModelByName,
|
|
569
|
-
usersById: this.usersById,
|
|
570
|
-
publishEnvironment: this.publishEnvironment,
|
|
571
|
-
logger: this.logger
|
|
572
|
-
});
|
|
573
|
-
updates.documents!.push(document);
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
581
|
+
case 'update':
|
|
576
582
|
case 'publish': {
|
|
577
|
-
|
|
583
|
+
// Generally, entry publish webhooks created by the content source are already configured to filter
|
|
584
|
+
// only the publishEnvironment. However, still check for the publish event environment in case the
|
|
585
|
+
// webhook was manually changed.
|
|
586
|
+
if (webhookData.event === 'publish' && webhookData.data.environment.uid !== this.publishEnvironment.uid) {
|
|
578
587
|
this.logger.debug(
|
|
579
588
|
`entry was published in an environment '${webhookData.data.environment.name}' ` +
|
|
580
589
|
`different from the publish environment '${this.publishEnvironment.name}', ignoring the webhook'`
|
|
581
590
|
);
|
|
582
591
|
break;
|
|
583
592
|
}
|
|
593
|
+
if (webhookData.event === 'create' && webhookData.data.entry.uid in this.newUnsyncedEntryMap) {
|
|
594
|
+
unsyncedEntryUid = webhookData.data.entry.uid;
|
|
595
|
+
}
|
|
584
596
|
if (userMissingInMap(this.usersById, webhookData.data.entry as Entry)) {
|
|
585
597
|
await this.fetchUsers();
|
|
586
598
|
}
|
|
599
|
+
const status: StackbitTypes.DocumentStatus =
|
|
600
|
+
webhookData.event === 'publish' ? 'published' : webhookData.event === 'create' ? 'added' : 'modified';
|
|
587
601
|
const document = convertEntry({
|
|
588
602
|
entry: {
|
|
589
603
|
...webhookData.data.entry,
|
|
@@ -591,7 +605,7 @@ export class ContentstackContentSource
|
|
|
591
605
|
publish_details: [],
|
|
592
606
|
_branch: webhookData.data.branch.uid
|
|
593
607
|
} as unknown as Entry,
|
|
594
|
-
status:
|
|
608
|
+
status: status,
|
|
595
609
|
apiKey: this.apiKey,
|
|
596
610
|
getModelByName: this.cache.getModelByName,
|
|
597
611
|
usersById: this.usersById,
|
|
@@ -618,25 +632,9 @@ export class ContentstackContentSource
|
|
|
618
632
|
} else if (webhookData.module === 'asset') {
|
|
619
633
|
switch (webhookData.event) {
|
|
620
634
|
case 'create':
|
|
621
|
-
case 'update':
|
|
622
|
-
if (userMissingInMap(this.usersById, webhookData.data.asset as Asset)) {
|
|
623
|
-
await this.fetchUsers();
|
|
624
|
-
}
|
|
625
|
-
const asset = convertAsset({
|
|
626
|
-
asset: {
|
|
627
|
-
...webhookData.data.asset,
|
|
628
|
-
publish_details: [],
|
|
629
|
-
_branch: webhookData.data.branch.uid
|
|
630
|
-
} as unknown as Asset,
|
|
631
|
-
apiKey: this.apiKey,
|
|
632
|
-
usersById: this.usersById,
|
|
633
|
-
publishEnvironment: this.publishEnvironment
|
|
634
|
-
});
|
|
635
|
-
updates.assets!.push(asset);
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
635
|
+
case 'update':
|
|
638
636
|
case 'publish': {
|
|
639
|
-
if (webhookData.data.environment.uid !== this.publishEnvironment.uid) {
|
|
637
|
+
if (webhookData.event === 'publish' && webhookData.data.environment.uid !== this.publishEnvironment.uid) {
|
|
640
638
|
this.logger.debug(
|
|
641
639
|
`asset was published in an environment '${webhookData.data.environment.name}' ` +
|
|
642
640
|
`different from the publish environment '${this.publishEnvironment.name}', ignoring the webhook'`
|
|
@@ -686,6 +684,10 @@ export class ContentstackContentSource
|
|
|
686
684
|
`deleted documents: ${updates.deletedDocumentIds?.length}, ` +
|
|
687
685
|
`deleted assets: ${updates.deletedAssetIds?.length}`
|
|
688
686
|
);
|
|
689
|
-
this.cache.updateContent(updates);
|
|
687
|
+
await this.cache.updateContent(updates);
|
|
688
|
+
|
|
689
|
+
if (unsyncedEntryUid) {
|
|
690
|
+
delete this.newUnsyncedEntryMap[unsyncedEntryUid];
|
|
691
|
+
}
|
|
690
692
|
}
|
|
691
693
|
}
|
|
@@ -4,10 +4,9 @@ import * as StackbitTypes from '@stackbit/types';
|
|
|
4
4
|
import type { EntryData } from '@contentstack/management/types/stack/contentType/entry';
|
|
5
5
|
import type { Entry, Asset } from './contentstack-types';
|
|
6
6
|
import type { ModelWithContext } from './contentstack-schema-converter';
|
|
7
|
-
import type { DocumentWithContext } from './contentstack-entries-converter';
|
|
8
7
|
|
|
9
8
|
type GetModelByName = (modelName: string) => ModelWithContext | undefined;
|
|
10
|
-
type
|
|
9
|
+
export type GetLeanDocumentById = (documentId: string) => { id: string; modelName: string } | undefined;
|
|
11
10
|
|
|
12
11
|
export function createEntryFromOperationFields({
|
|
13
12
|
updateOperationFields,
|
|
@@ -18,7 +17,7 @@ export function createEntryFromOperationFields({
|
|
|
18
17
|
}: {
|
|
19
18
|
updateOperationFields: Record<string, StackbitTypes.UpdateOperationField>;
|
|
20
19
|
model: ModelWithContext;
|
|
21
|
-
getDocumentById:
|
|
20
|
+
getDocumentById: GetLeanDocumentById;
|
|
22
21
|
getModelByName: GetModelByName;
|
|
23
22
|
logger: StackbitTypes.Logger;
|
|
24
23
|
}): EntryData {
|
|
@@ -55,7 +54,7 @@ export function updateEntryFromUpdateOperations({
|
|
|
55
54
|
}: {
|
|
56
55
|
entry: Entry;
|
|
57
56
|
updateOperations: StackbitTypes.UpdateOperation[];
|
|
58
|
-
getDocumentById:
|
|
57
|
+
getDocumentById: GetLeanDocumentById;
|
|
59
58
|
getModelByName: GetModelByName;
|
|
60
59
|
logger: StackbitTypes.Logger;
|
|
61
60
|
}): Entry {
|
|
@@ -117,7 +116,7 @@ export function getUpdatedEntryAtFieldPathWithOperation({
|
|
|
117
116
|
entry: Entry;
|
|
118
117
|
updateOperation: StackbitTypes.UpdateOperation;
|
|
119
118
|
model: ModelWithContext;
|
|
120
|
-
getDocumentById:
|
|
119
|
+
getDocumentById: GetLeanDocumentById;
|
|
121
120
|
getModelByName: GetModelByName;
|
|
122
121
|
logger: StackbitTypes.Logger;
|
|
123
122
|
}): Entry {
|
|
@@ -378,7 +377,7 @@ function getValueForOperation({
|
|
|
378
377
|
rootModel: ModelWithContext;
|
|
379
378
|
entryFieldPath: (string | number)[];
|
|
380
379
|
modelFieldPath: string[];
|
|
381
|
-
getDocumentById:
|
|
380
|
+
getDocumentById: GetLeanDocumentById;
|
|
382
381
|
getModelByName: GetModelByName;
|
|
383
382
|
errorPrefix: string;
|
|
384
383
|
logger: StackbitTypes.Logger;
|
|
@@ -468,7 +467,7 @@ export function convertOperationFieldValue({
|
|
|
468
467
|
isInList: boolean;
|
|
469
468
|
entryFieldPath: (string | number)[];
|
|
470
469
|
modelFieldPath: string[];
|
|
471
|
-
getDocumentById:
|
|
470
|
+
getDocumentById: GetLeanDocumentById;
|
|
472
471
|
getModelByName: GetModelByName;
|
|
473
472
|
errorPrefix: string;
|
|
474
473
|
}): any {
|
|
@@ -284,6 +284,11 @@ export type Entry = ContentStackEntry & {
|
|
|
284
284
|
updated_at: string;
|
|
285
285
|
created_by: string;
|
|
286
286
|
updated_by: string;
|
|
287
|
+
/**
|
|
288
|
+
* The entry's locale.
|
|
289
|
+
* Note, this can be different from the `publish_details.locale` if the
|
|
290
|
+
* entry was not localized for the published locale.
|
|
291
|
+
*/
|
|
287
292
|
locale?: string;
|
|
288
293
|
tags?: string[];
|
|
289
294
|
publish_details?: PublishDetails[];
|
|
@@ -297,23 +302,49 @@ export type PublishDetails = {
|
|
|
297
302
|
environment: string;
|
|
298
303
|
time: string;
|
|
299
304
|
user: string;
|
|
305
|
+
/**
|
|
306
|
+
* The version of the entry that was published.
|
|
307
|
+
* If the entry_locale is provided, then the version is of that entry locale.
|
|
308
|
+
* Otherwise, it is a version of the entry's own locale.
|
|
309
|
+
*/
|
|
300
310
|
version: number;
|
|
301
311
|
/**
|
|
302
|
-
* The
|
|
303
|
-
* Note, the locale may be different from entry's
|
|
312
|
+
* The published locale.
|
|
313
|
+
* Note, the published locale may be different from the entry's locale if a
|
|
314
|
+
* non-localized entry was published.
|
|
315
|
+
*
|
|
316
|
+
* For example, a non-localized "fr-fr" entry can be published to the "fr-fr"
|
|
317
|
+
* locale. In this case, the entry will be published from the values stored
|
|
318
|
+
* in the entry of the fallback locale, e.g., 'en-us'. When this entry is
|
|
319
|
+
* fetched using the content delivery API with `locale=fr-fr` it will return
|
|
320
|
+
* the entry with `locale: 'en-us'`, and `publish_details.locale: 'fr-fr'`.
|
|
321
|
+
*
|
|
322
|
+
* If an entry was not published to a `fr-fr` locale but was published to its
|
|
323
|
+
* fallback locale, then it will be returned from the Content Delivery API
|
|
324
|
+
* for `locale=fr-fr` parameter only if it also has `include_fallback=false`.
|
|
325
|
+
* In other words, non-published locales will be returned only when
|
|
326
|
+
* `include_fallback=true`.
|
|
304
327
|
*/
|
|
305
328
|
locale: string;
|
|
306
329
|
/**
|
|
307
|
-
* The 'entry_locale'
|
|
308
|
-
* entry was published, it may be different from the
|
|
330
|
+
* The 'publish_details.entry_locale' specifies the original entry locale
|
|
331
|
+
* from which the entry was published, and it may be different from the
|
|
332
|
+
* 'publish_details.locale' if the entry was localized after it was published.
|
|
333
|
+
*
|
|
334
|
+
* It also specifies the entry's locale the version field relates to.
|
|
309
335
|
*
|
|
310
|
-
* For example, if an entry with locale "
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* will be
|
|
316
|
-
*
|
|
336
|
+
* For example, if an entry with locale "en-us" has been published to "fr-fr"
|
|
337
|
+
* locale without having a localized entry for "fr-fr". Then, when the entry
|
|
338
|
+
* is fetched with the locale=fr-fr filter, the `publish_details.locale`
|
|
339
|
+
* will be set to "fr-fr", but the entry's locale will remain "en-us" because
|
|
340
|
+
* it was not localized, and the `publish_details.entry_locale` will be undefined.
|
|
341
|
+
* Then, if the entry will be localized to "fr-fr" locale, the locale of
|
|
342
|
+
* the entry will be changed to "fr-fr", the `publish_details.locale` will
|
|
343
|
+
* remain "fr-fr" and the `publish_details.entry_locale` will be set to
|
|
344
|
+
* "en-us" specifying that the published entry's locale doesn't match the
|
|
345
|
+
* current's entry locale. Only after the localized "fr-fr" entry will be
|
|
346
|
+
* published to "fr-fr", then the `publish_details.entry_locale` will be set
|
|
347
|
+
* back to undefined.
|
|
317
348
|
*/
|
|
318
349
|
entry_locale?: string;
|
|
319
350
|
};
|