@stackbit/cms-contentful
Version:
Stackbit Contentful CMS Interface
121 lines (111 loc) • 4.43 kB
text/typescript
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 };
}
}