UNPKG

@nacelle/compatibility-connector

Version:

Connect @nacelle/client-js-sdk to Nacelle's v2 back end with minimal code changes

195 lines (177 loc) 5.88 kB
import { destructureNacelleEntryId, reconstructContentfulEntry, toUnixTimestamp, transformArticleLists, transformAuthor, transformMedia, transformRelatedArticles, transformSanityContent, transformSourceEntryId } from '.'; import type { NacelleContent } from '@nacelle/types'; import type { Content } from 'storefrontSdkV1'; import type { SourceName } from './destructureNacelleEntryId'; export interface AnyObject { [key: string]: AnyObject | string | unknown; } export interface NacelleContentEntry { fields?: AnyObject; type?: string; nacelleEntryId?: string; tags?: string[]; } export const isObject = (x: unknown): x is AnyObject => Boolean(x) && typeof x === 'object' && !Array.isArray(x); /** * Determine if an object is a Nacelle v2 `Content` entry. */ export const isNacelleContentEntry = (entry: { nacelleEntryId?: string; type?: string; fields?: object; }): entry is NacelleContentEntry => isObject(entry) && typeof entry.nacelleEntryId === 'string' && typeof entry.type === 'string' && typeof entry.fields === 'object'; interface ContentTransformers<CmsContent> { /** A transformer for Nacelle v2 `NacelleReference`s to top-level content */ entries?: (x: NacelleContentEntry) => CmsContent; /** A transformer for arbitrary content */ arbitraryContent?: (x: AnyObject) => CmsContent; } /** * Apply content transformation functions to content. This takes care of the boilerplate * associated with applying transformations to arrays and objects, which allows the * CMS-specific transformer utilities to be less complex. * @param content A single content object or array of arbitrary content objects. * @param transformers Functions that transform top-level content entries and arbitrary content. * @returns Content that's been transformed into a Nacelle v1 shape. */ export function applyContentTransformation<CmsContent>( content: AnyObject | AnyObject[], transformers: ContentTransformers<CmsContent> ): NacelleContentEntry | NacelleContentEntry[] | CmsContent { if (Array.isArray(content)) { return content.map( (entry) => applyContentTransformation(entry, transformers) as NacelleContentEntry ); } if (isObject(content)) { for (const property of Object.keys(content)) { const key = property as keyof NacelleContentEntry; if (isObject(content[key]) || Array.isArray(content[key])) { content[key] = applyContentTransformation( content[key] as AnyObject, transformers ); } } if (isNacelleContentEntry(content) && transformers.entries) { // `content` is a reference to a top-level content entry return transformers.entries(content); } else if (transformers.arbitraryContent) { // `content` is arbitrary content return transformers.arbitraryContent(content); } } return content; } /** * Apply CMS-specific transformations to content. */ function applyCmsTransforms(data: AnyObject | AnyObject[], cms: SourceName) { switch (cms) { case 'CONTENTFUL': { return applyContentTransformation(data, { // With Contentful content, we only want to reconstruct top-level // content entries (from `NacelleReference`s) with `sys` metadata. // Any other flattening and transformation is handled by other // (more specialized) transformers like `transformAuthor`. entries: reconstructContentfulEntry }); } case 'SANITY': { return applyContentTransformation(data, { // With Sanity content, we can apply the same transformation to // top-level content entries (from `NacelleReference`s) and arbitrary // content data. arbitraryContent: transformSanityContent, entries: transformSanityContent }); } default: { return data; } } } export function transformContent( entries: Content[], locale: string ): NacelleContent[] { return entries .filter((entry) => entry.handle) .map((entry) => { const { title, handle, createdAt, updatedAt, indexedAt, type, sourceEntryId, nacelleEntryId } = entry; const { sourceName: cms } = destructureNacelleEntryId(nacelleEntryId); const { articles = null, author = null, blogHandle = null, collectionHandle = null, content = null, contentHtml = null, description = null, excerpt = null, featuredMedia = null, publishDate = null, relatedArticles = null, sections = null, tags = null, ...fields } = entry.fields || {}; const contentIsString = typeof content === 'string'; if (content && !contentIsString && typeof content === 'object') { fields.content = content; } return { articleLists: transformArticleLists(articles, blogHandle, locale), author: transformAuthor(author, cms), blogHandle, cmsSyncSource: cms.toLowerCase(), cmsSyncSourceDomain: '', cmsSyncSourceContentId: transformSourceEntryId(sourceEntryId), collectionHandle, content: contentIsString ? content : null, contentHtml, createdAt, description, excerpt, featuredMedia: transformMedia(featuredMedia, cms), fields: applyCmsTransforms(fields, cms), globalHandle: `${handle}::${locale}`, handle: handle as string, id: nacelleEntryId, indexedAt: indexedAt || 0, locale, publishDate: toUnixTimestamp(publishDate) as number, relatedArticles: transformRelatedArticles(relatedArticles, cms), sections: applyCmsTransforms(sections, cms), tags, title, type: type || '', updatedAt }; }); }