import _ from 'lodash'; import * as CSITypes from '@stackbit/types'; import { getLocalizedFieldForLocale } from '@stackbit/types'; import * as ContentStoreTypes from '../types'; import { getContentSourceId, findContentSourcesDataForTypeOrId } from '../content-store-utils'; import { mapStoreDocumentsToCSIDocumentsWithSource, mapStoreDocumentToCSIDocumentWithSource } from './store-to-csi-docs-converter'; export function getCreateConfigDelegateThunk({ getContentSourceDataById, logger }: { getContentSourceDataById: () => Record; logger: CSITypes.Logger; }): () => CSITypes.ConfigDelegate { return () => createConfigDelegate({ contentSourceDataById: getContentSourceDataById(), logger: logger }); } export function createConfigDelegate({ contentSourceDataById, logger }: { contentSourceDataById: Record; logger: CSITypes.Logger; }): CSITypes.ConfigDelegate { return { getLogger: () => logger, getDocumentById: ({ id, srcType, srcProjectId }): CSITypes.DocumentWithSource | undefined => { const document = findDocumentByIdAndSourceTypeOrId({ contentSourceDataById, documentId: id, srcType, srcProjectId, logger }); if (document) { const contentSourceId = getContentSourceId(document.srcType, document.srcProjectId); const csiDocument = contentSourceDataById[contentSourceId]!.csiDocumentMap[document.srcObjectId]; if (!csiDocument) { return undefined; } return mapStoreDocumentToCSIDocumentWithSource({ document, csiDocument }); } return undefined; }, getDocuments: ({ srcType, srcProjectId } = {}): CSITypes.DocumentWithSource[] => { const contentSourcesData = findContentSourcesDataForTypeOrId({ contentSourceDataById, srcType, srcProjectId }); return contentSourcesData.reduce((matchingDocuments: CSITypes.DocumentWithSource[], contentSourceData) => { return matchingDocuments.concat( mapStoreDocumentsToCSIDocumentsWithSource({ documents: contentSourceData.documents, csiDocumentMap: contentSourceData.csiDocumentMap }) ); }, []); }, getModelByName: ({ modelName, srcType, srcProjectId }): CSITypes.ModelWithSource | undefined => { return findModelByNameAndSourceTypeOrId({ contentSourceDataById, modelName, srcType, srcProjectId, logger }); }, getSchemas: ({ srcType, srcProjectId } = {}): CSITypes.SchemaWithSource[] => { const contentSourcesData = findContentSourcesDataForTypeOrId({ contentSourceDataById, srcType, srcProjectId }); return contentSourcesData.reduce((schemas: CSITypes.SchemaWithSource[], contentSourceData) => { return schemas.concat({ srcType: contentSourceData.srcType, srcProjectId: contentSourceData.srcProjectId, models: contentSourceData.models.map((model) => ({ ...model, srcType: contentSourceData.srcType, srcProjectId: contentSourceData.srcProjectId })), locales: contentSourceData.csiSchema.locales, context: contentSourceData.csiSchema.context }); }, []); }, getDefaultLocaleBySource: ({ srcType, srcProjectId }): string | undefined => { // if srcType and srcProjectId are provided, use them to get the specific contentSourceData without trying to infer the right model if (srcType && srcProjectId) { const contentSourceId = getContentSourceId(srcType, srcProjectId); return contentSourceDataById[contentSourceId]?.defaultLocaleCode; } const contentSourcesData = findContentSourcesDataForTypeOrId({ contentSourceDataById, srcType, srcProjectId }); if (contentSourcesData.length === 1) { return contentSourcesData[0]!.defaultLocaleCode; } else if (contentSourcesData.length > 1) { logger.warn( `The getDefaultLocaleBySource() found more than one content sources for '${srcType}'. ` + `Please specify 'srcType' and 'srcProjectId' to narrow down the search.` ); } return undefined; }, getDocumentFieldForFieldPath: ({ document, fromField, fieldPath, locale }: { document: CSITypes.DocumentWithSource; fromField?: CSITypes.DocumentModelField | CSITypes.DocumentObjectField; fieldPath: string; locale?: string; }): CSITypes.DocumentFieldNonLocalizedForType | undefined => { const fieldPathArr = _.toPath(fieldPath); const contentSourceId = getContentSourceId(document.srcType, document.srcProjectId); const contentSource = contentSourceDataById[contentSourceId]; if (!contentSource) { return undefined; } function getNestedFieldFromFieldsForPath( fields: Record, fieldPathArr: string[], currentContentSource: ContentStoreTypes.ContentSourceData ): CSITypes.DocumentFieldNonLocalized | undefined { const fieldName = fieldPathArr[0]; fieldPathArr = fieldPathArr.slice(1); if (!fieldName) { return undefined; } const field = fields[fieldName]; if (!field) { return undefined; } const resolvedLocale = locale ?? currentContentSource.defaultLocaleCode; const nonLocalizedField = getLocalizedFieldForLocale(field, resolvedLocale); if (!nonLocalizedField) { return undefined; } if (fieldPathArr.length === 0) { return nonLocalizedField; } return getNestedFieldFromLocalizedFieldForPath(nonLocalizedField, fieldPathArr, currentContentSource); } function getNestedFieldFromLocalizedFieldForPath( nonLocalizedField: CSITypes.DocumentFieldNonLocalized, fieldPathArr: string[], currentContentSource: ContentStoreTypes.ContentSourceData ): CSITypes.DocumentFieldNonLocalized | undefined { if (nonLocalizedField.type === 'object' || nonLocalizedField.type === 'model') { return getNestedFieldFromFieldsForPath(nonLocalizedField.fields, fieldPathArr, currentContentSource); } else if (nonLocalizedField.type === 'reference') { const refDocument = currentContentSource.documentMap[nonLocalizedField.refId]; const refCSIDocument = currentContentSource.csiDocumentMap[nonLocalizedField.refId]; if (!refDocument || !refCSIDocument) { return undefined; } const fields = mapStoreDocumentToCSIDocumentWithSource({ document: refDocument, csiDocument: refCSIDocument }).fields; return getNestedFieldFromFieldsForPath(fields, fieldPathArr, currentContentSource); } else if (nonLocalizedField.type === 'cross-reference') { const contentSourceId = getContentSourceId(nonLocalizedField.refSrcType, nonLocalizedField.refProjectId); const contentSource = contentSourceDataById[contentSourceId]; if (!contentSource) { return undefined; } const refDocument = contentSource.documentMap[nonLocalizedField.refId]; const refCSIDocument = contentSource.csiDocumentMap[nonLocalizedField.refId]; if (!refDocument || !refCSIDocument) { return undefined; } const fields = mapStoreDocumentToCSIDocumentWithSource({ document: refDocument, csiDocument: refCSIDocument }).fields; return getNestedFieldFromFieldsForPath(fields, fieldPathArr, contentSource); } else if (nonLocalizedField.type === 'list') { const index = _.toNumber(fieldPathArr[0]); fieldPathArr = fieldPathArr.slice(1); if (_.isNaN(index)) { return undefined; } const localizedItem = nonLocalizedField.items[index]; if (!localizedItem) { return undefined; } if (fieldPathArr.length === 0) { return localizedItem; } return getNestedFieldFromLocalizedFieldForPath(localizedItem, fieldPathArr, currentContentSource); } return undefined; } if (fromField) { const resolvedLocale = locale ?? contentSource.defaultLocaleCode; const nonLocalizedField = getLocalizedFieldForLocale(fromField, resolvedLocale); if (!nonLocalizedField) { return undefined; } return getNestedFieldFromLocalizedFieldForPath(nonLocalizedField, fieldPathArr, contentSource) as | CSITypes.DocumentFieldNonLocalizedForType | undefined; } else { return getNestedFieldFromFieldsForPath(document.fields, fieldPathArr, contentSource) as | CSITypes.DocumentFieldNonLocalizedForType | undefined; } } }; } function findDocumentByIdAndSourceTypeOrId({ contentSourceDataById, documentId, srcType, srcProjectId, logger }: { contentSourceDataById: Record; documentId: string; srcType?: string; srcProjectId?: string; logger: CSITypes.Logger; }): ContentStoreTypes.Document | undefined { // if srcType and srcProjectId are provided, use them to get the specific contentSourceData without trying to infer the right document if (srcType && srcProjectId) { const contentSourceId = getContentSourceId(srcType, srcProjectId); return contentSourceDataById[contentSourceId]?.documentMap[documentId]; } const contentSourcesData = findContentSourcesDataForTypeOrId({ contentSourceDataById, srcType, srcProjectId }); const matchingDocuments = contentSourcesData.reduce((matchingDocuments: ContentStoreTypes.Document[], contentSourceData) => { const document = contentSourceData.documentMap[documentId]; if (document) { matchingDocuments.push(document); } return matchingDocuments; }, []); if (matchingDocuments.length === 1) { return matchingDocuments[0]; } else if (matchingDocuments.length > 1) { const matchedContentSources = matchingDocuments.map((document) => `srcType: ${document.srcType}, srcProjectId: ${document.srcProjectId}`).join('; '); logger.warn( `The getDocumentById() found more than one documents with ID '${documentId}' ` + `in the following content sources ${matchedContentSources}. ` + `Please specify 'srcType' and/or 'srcProjectId' to narrow down the search.` ); } return; } function findModelByNameAndSourceTypeOrId({ contentSourceDataById, modelName, srcType, srcProjectId, logger }: { contentSourceDataById: Record; modelName: string; srcType?: string; srcProjectId?: string; logger: CSITypes.Logger; }): CSITypes.ModelWithSource | undefined { // if srcType and srcProjectId are provided, use them to get the specific contentSourceData without trying to infer the right model if (srcType && srcProjectId) { const contentSourceId = getContentSourceId(srcType, srcProjectId); const contentSourceData = contentSourceDataById[contentSourceId]; const model = contentSourceData?.modelMap[modelName]; if (model) { return { ...model, srcType: contentSourceData.srcType, srcProjectId: contentSourceData.srcProjectId }; } return undefined; } const contentSourcesData = findContentSourcesDataForTypeOrId({ contentSourceDataById, srcType, srcProjectId }); const matchingModels = contentSourcesData.reduce((matchingModels: CSITypes.ModelWithSource[], contentSourceData) => { const model = contentSourceData.modelMap[modelName]; if (model) { matchingModels.push({ ...model, srcType: contentSourceData.srcType, srcProjectId: contentSourceData.srcProjectId }); } return matchingModels; }, []); if (matchingModels.length === 1) { return matchingModels[0]!; } else if (matchingModels.length > 1) { const matchedContentSources = matchingModels.map((model) => `srcType: ${model.srcType}, srcProjectId: ${model.srcProjectId}`).join('; '); logger.warn( `The getModelByName() found more than one model with name '${modelName}' ` + `in the following content sources ${matchedContentSources}. ` + `Please specify 'srcType' and/or 'srcProjectId' to narrow down the search.` ); } return; }