import _ from 'lodash'; import path from 'path'; import { Config, Model } from '@stackbit/sdk'; import { append } from '@stackbit/utils'; import { FieldType, UpdateOperationCrossReferenceField, UpdateOperationValueField, isOneOfFieldTypes } from '@stackbit/types'; import * as CSITypes from '@stackbit/types'; import * as ContentStoreTypes from './types'; import { BackCompatContentSourceInterface } from './utils/backward-compatibility'; export function getContentSourceIdForContentSource(contentSource: BackCompatContentSourceInterface): string { return getContentSourceId(contentSource.getContentSourceType(), contentSource.getProjectId()); } export function getContentSourceId(contentSourceType: string, srcProjectId: string) { return contentSourceType + ':' + srcProjectId; } export function getObjectId(srcType: string, srcProjectId: string, srcObjectId: string) { return `${srcType}:${srcProjectId}:${srcObjectId}`; } export function getContentSourceDataByTypeAndProjectIdOrThrow( srcType: string, srcProjectId: string, contentSourceDataById: Record ) { const contentSourceId = getContentSourceId(srcType, srcProjectId); return getContentSourceDataByIdOrThrow(contentSourceId, contentSourceDataById); } export function getContentSourceDataByIdOrThrow(contentSourceId: string, contentSourceDataById: Record) { const contentSourceData = contentSourceDataById[contentSourceId]; if (!contentSourceData) { throw new Error(`Content source not found: '${contentSourceId}'.`); } return contentSourceData; } export function findContentSourcesDataForTypeOrId({ contentSourceDataById, srcType, srcProjectId }: { contentSourceDataById: Record; srcType?: string; srcProjectId?: string; }): ContentStoreTypes.ContentSourceData[] { return _.filter(contentSourceDataById, (contentSourceData) => { const srcTypeMatch = !srcType || contentSourceData.srcType === srcType; const srcProjectIdMatch = !srcProjectId || contentSourceData.srcProjectId === srcProjectId; return srcTypeMatch && srcProjectIdMatch; }); } export function getUserContextForSrcTypeThunk(user?: ContentStoreTypes.User) { return (srcType: string) => getUserContextForSrcType(srcType, user); } export function getUserContextForSrcType(srcType: string, user?: ContentStoreTypes.User): CSITypes.User | undefined { if (!user) { return undefined; } const connection = user?.connections?.find((connection) => connection.type === srcType); return { email: user.email, name: user.name, sso: user.sso, role: user.role, ...connection }; } export function isDocumentFieldOneOfFieldTypes( documentField: ContentStoreTypes.DocumentField, fieldTypes: ReadonlyArray ): documentField is ContentStoreTypes.DocumentFieldForType { return fieldTypes.includes(documentField.type as T); } export function getDocumentFieldForLocale( docField: ContentStoreTypes.DocumentFieldForType, locale?: string ): ContentStoreTypes.DocumentFieldNonLocalizedForType | null { if (docField && docField.localized) { if (!locale) { return null; } const { localized, locales, ...base } = docField; const localizedField = locales?.[locale]; if (!localizedField) { return null; } return { ...base, ...localizedField } as unknown as ContentStoreTypes.DocumentFieldNonLocalizedForType; } else { return docField; } } export function groupModelsByContentSource({ models }: { models: CSITypes.ModelWithSource[] }): Record>> { const modelMapByContentSource: Record>> = {}; for (const model of models) { const { srcType, srcProjectId, ...rest } = model; _.set(modelMapByContentSource, [srcType, srcProjectId, model.name], rest); } return modelMapByContentSource; } export function groupDocumentsByContentSource({ documents }: { documents: CSITypes.DocumentWithSource[]; }): Record> { const documentMapByContentSource: Record> = {}; for (const document of documents) { const { srcType, srcProjectId, ...rest } = document; append(documentMapByContentSource, [srcType, srcProjectId], rest); } return documentMapByContentSource; } export function getCSIDocumentsAndAssetsFromContentSourceDataByIds( contentSourceData: ContentStoreTypes.ContentSourceData, objects: { srcObjectId: string }[] ): { documents: CSITypes.Document[]; assets: CSITypes.Asset[]; } { const documents: CSITypes.Document[] = []; const assets: CSITypes.Asset[] = []; for (const object of objects) { if (object.srcObjectId in contentSourceData.csiDocumentMap) { documents.push(contentSourceData.csiDocumentMap[object.srcObjectId]!); } else if (object.srcObjectId in contentSourceData.csiAssetMap) { assets.push(contentSourceData.csiAssetMap[object.srcObjectId]!); } } return { documents, assets }; } export function updateOperationValueFieldWithCrossReference( type: 'string' | 'text' | 'json' | 'cross-reference', refObject: ContentStoreTypes.CrossReferenceData ): UpdateOperationValueField | UpdateOperationCrossReferenceField { if (isOneOfFieldTypes(type, ['json', 'cross-reference'])) { return { type, value: refObject }; } return { type, value: JSON.stringify(refObject) }; } export function isContentChangesEmpty(contentChanges?: CSITypes.ContentChanges): contentChanges is undefined { return ( !contentChanges || (!contentChanges.documents?.length && !contentChanges.assets?.length && !contentChanges.scheduledActions?.length && !contentChanges.deletedDocumentIds?.length && !contentChanges.deletedAssetIds?.length && !contentChanges.deletedScheduledActionIds?.length) ); } export function isContentChangeResultEmpty(contentChangeResult: ContentStoreTypes.ContentChangeResult) { return ( contentChangeResult.createdDocuments.length === 0 && contentChangeResult.createdAssets.length === 0 && contentChangeResult.createdScheduledActions.length === 0 && contentChangeResult.updatedDocuments.length === 0 && contentChangeResult.updatedAssets.length === 0 && contentChangeResult.updatedScheduledActions.length === 0 && contentChangeResult.deletedDocuments.length === 0 && contentChangeResult.deletedAssets.length === 0 && contentChangeResult.deletedScheduledActions.length === 0 ); } export function contentChangeResultCounts(contentChangeResult: ContentStoreTypes.ContentChangeResult) { return { createdDocumentsCount: contentChangeResult.createdDocuments.length, createdAssetsCount: contentChangeResult.createdAssets.length, createdScheduledActionsCount: contentChangeResult.createdScheduledActions.length, updatedDocumentsCount: contentChangeResult.updatedDocuments.length, updatedAssetsCount: contentChangeResult.updatedAssets.length, updatedScheduledActionsCount: contentChangeResult.updatedScheduledActions.length, deletedDocumentsCount: contentChangeResult.deletedDocuments.length, deletedAssetsCount: contentChangeResult.deletedAssets.length, deletedScheduledActionsCount: contentChangeResult.deletedScheduledActions.length }; } // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type export function getErrorAtLine(dropLastCallStackEntries: number, dropCallStackFunc?: Function) { const tempErr = new Error(); Error.captureStackTrace(tempErr, dropCallStackFunc ?? getErrorAtLine); const atLine = tempErr.stack?.split('\n')[1 + dropLastCallStackEntries]?.trim(); return atLine ? ` ${atLine}` : ''; } export function getCacheDir(stackbitConfig: Config) { return stackbitConfig.cacheDir ?? path.resolve(stackbitConfig.dirPath, '.stackbit/cache'); }