UNPKG

@stackbit/cms-contentful

Version:

Stackbit Contentful CMS Interface

121 lines (111 loc) 4.43 kB
import _ from 'lodash'; import { createClient, ContentfulClientApi, Entry, Asset } from 'contentful'; import { AssetProps, EntryProps, KeyValueMap, PlainClientAPI } from 'contentful-management'; import { LazyPoller, LazyPollerOptions } from './lazy-poller'; import { createPlainApiClient } from './contentful-api-client'; export interface SyncPollerOptions extends LazyPollerOptions<SyncCallback> { spaceId: string; environment: string; accessToken: string; managementToken: string; } export interface SyncResult { entries: EntryProps<any>[]; assets: AssetProps[]; deletedEntries: Entry<any>[]; deletedAssets: Asset[]; } export type SyncCallback = (result: SyncResult) => void; export class SyncPoller extends LazyPoller<SyncCallback> { private client: ContentfulClientApi; private managementClient: PlainClientAPI; private nextSyncToken: null | string; constructor({ spaceId, environment, accessToken, managementToken, ...options }: SyncPollerOptions) { options.logger.debug('creating SyncPoller', { spaceId, environment }); super(options); this.nextSyncToken = null; this.client = createClient({ space: spaceId, environment: environment, accessToken: accessToken, host: 'preview.contentful.com' }); this.managementClient = createPlainApiClient({ accessToken: managementToken, spaceId, environment }); } async poll(notificationCallback: SyncCallback): Promise<any> { const initial = this.nextSyncToken === null; let hasMoreItems = true; let hasItems = false; const result: SyncResult = { entries: [], assets: [], deletedEntries: [], deletedAssets: [] }; while (hasMoreItems) { const response = await this.client.sync({ initial: this.nextSyncToken === null, nextSyncToken: this.nextSyncToken, resolveLinks: false }); const isEmptyResponse = _.every(_.pick(response, ['entries', 'assets', 'deletedEntries', 'deletedAssets']), _.isEmpty); if (this.nextSyncToken === response.nextSyncToken || isEmptyResponse) { hasMoreItems = false; } else { if (!initial) { const { entries, assets } = await this.batchRefetchData({ entryIds: response.entries.map((entry) => entry.sys.id), assetIds: response.assets.map((entry) => entry.sys.id) }); hasItems = true; result.entries = result.entries.concat(entries); result.assets = result.assets.concat(assets); result.deletedEntries = result.deletedEntries.concat(response.deletedEntries); result.deletedAssets = result.deletedAssets.concat(response.deletedAssets); } } this.nextSyncToken = response.nextSyncToken; } if (!initial && hasItems) { notificationCallback(result); } } private async batchRefetchData({ entryIds, assetIds }: { entryIds: string[]; assetIds: string[]; }): Promise<{ entries: EntryProps<KeyValueMap>[]; assets: AssetProps[] }> { const limit = 300; const entryChunks = _.chunk(entryIds, limit); const entries = _.flatMap( await Promise.all( entryChunks.map((chunk) => this.managementClient.entry.getMany({ query: { limit: limit, 'sys.id[in]': chunk.join(',') } }) ) ), (result) => result.items ); const assetChunks = _.chunk(assetIds, limit); const assets = _.flatMap( await Promise.all( assetChunks.map((chunk) => this.managementClient.asset.getMany({ query: { limit: limit, 'sys.id[in]': chunk.join(',') } }) ) ), (result) => result.items ); return { entries, assets }; } }