@kontent-ai/model-generator
Version:
This utility generates strongly-typed models for Delivery JS SDK, Migration toolkit or just general scripting to improve the experience when referencing Kontent.ai related objects.
155 lines (139 loc) • 5.49 kB
text/typescript
import type { ContentTypeElements, ContentTypeModels, ContentTypeSnippetModels, TaxonomyModels } from "@kontent-ai/management-sdk";
import { P, match } from "ts-pattern";
import type { FlattenedElement, MultipleChoiceOption } from "./core.models.js";
import { isNotUndefined } from "./core.utils.js";
interface ElementWrapper {
readonly element: Readonly<ContentTypeElements.ContentTypeElementModel>;
readonly fromSnippet: Readonly<ContentTypeSnippetModels.ContentTypeSnippet> | undefined;
}
export function getFlattenedElements(data: {
readonly elements: readonly Readonly<ContentTypeElements.ContentTypeElementModel>[];
readonly snippets: readonly Readonly<ContentTypeSnippetModels.ContentTypeSnippet>[];
readonly taxonomies: readonly Readonly<TaxonomyModels.Taxonomy>[];
readonly types: readonly Readonly<ContentTypeModels.ContentType>[];
}): readonly FlattenedElement[] {
return data.elements
.flatMap<ElementWrapper>((element) => {
return match(element)
.returnType<readonly ElementWrapper[] | ElementWrapper>()
.with({ type: "snippet" }, (snippetElement) => {
const snippet = data.snippets.find((snippet) => snippet.id === snippetElement.snippet.id);
if (!snippet) {
throw new Error(`Could not find snippet with id '${snippetElement.snippet.id}'`);
}
return snippet.elements.map((snippetElement) => {
return {
element: snippetElement,
fromSnippet: snippet,
};
});
})
.otherwise(() => {
return {
element: element,
fromSnippet: undefined,
};
});
})
.map((element) => {
return getFlattenedElement(element, data.taxonomies, data.types);
})
.filter(isNotUndefined)
.filter((element) => {
return element.type !== "guidelines";
});
}
function getFlattenedElement(
wrapper: ElementWrapper,
taxonomies: readonly Readonly<TaxonomyModels.Taxonomy>[],
types: readonly Readonly<ContentTypeModels.ContentType>[],
): Readonly<FlattenedElement> | undefined {
if (!(wrapper.element.codename && wrapper.element.id)) {
return undefined;
}
return {
title: getElementTitle(wrapper.element, taxonomies),
codename: wrapper.element.codename,
id: wrapper.element.id,
type: wrapper.element.type,
isRequired: isElementRequired(wrapper.element),
guidelines: getElementGuidelines(wrapper.element),
externalId: wrapper.element.external_id,
originalElement: wrapper.element,
allowedContentTypes: extractLinkedItemsAllowedTypes(wrapper.element, types),
assignedTaxonomy: extractTaxonomy(wrapper.element, taxonomies),
fromSnippet: wrapper.fromSnippet,
multipleChoiceOptions: extractMultipleChoiceOptions(wrapper.element),
isElementWithProperty: match(wrapper.element.type)
.returnType<boolean>()
.with("guidelines", () => false)
.otherwise(() => true),
};
}
function isElementRequired(element: Readonly<ContentTypeElements.ContentTypeElementModel>): boolean {
return match(element)
.returnType<boolean>()
.with({ is_required: true }, () => true)
.otherwise(() => false);
}
function getElementGuidelines(element: Readonly<ContentTypeElements.ContentTypeElementModel>): string | undefined {
return match(element)
.returnType<string | undefined>()
.with({ guidelines: P.string }, (element) => element.guidelines)
.otherwise(() => undefined);
}
function getElementTitle(
element: Readonly<ContentTypeElements.ContentTypeElementModel>,
taxonomies: readonly Readonly<TaxonomyModels.Taxonomy>[],
): string {
return match(element)
.returnType<string>()
.with({ type: "taxonomy" }, (taxonomyElement) => {
const taxonomyGroupId = taxonomyElement.taxonomy_group.id;
if (!taxonomyGroupId) {
return element.type;
}
const taxonomy = taxonomies.find((m) => m.id === taxonomyGroupId);
return taxonomy?.name ?? element.type;
})
.with({ name: P.string }, (element) => element.name)
.otherwise(() => "invalidTitle");
}
function extractLinkedItemsAllowedTypes(
element: Readonly<ContentTypeElements.ContentTypeElementModel>,
types: readonly Readonly<ContentTypeModels.ContentType>[],
): readonly Readonly<ContentTypeModels.ContentType>[] {
const allowedTypeIds = match(element)
.returnType<readonly string[]>()
.with({ type: P.union("modular_content", "subpages", "rich_text") }, (elementWithAllowedContentTypes) => {
return elementWithAllowedContentTypes.allowed_content_types?.map((m) => m.id).filter(isNotUndefined) ?? [];
})
.otherwise(() => []);
return allowedTypeIds.map((id) => types.find((m) => m.id === id)).filter(isNotUndefined);
}
function extractMultipleChoiceOptions(
element: Readonly<ContentTypeElements.ContentTypeElementModel>,
): readonly MultipleChoiceOption[] | undefined {
return match(element)
.returnType<readonly MultipleChoiceOption[] | undefined>()
.with({ type: "multiple_choice" }, (multipleChoiceElement) => {
return multipleChoiceElement.options.map((option) => {
return {
codename: option.codename ?? "",
name: option.name,
};
});
})
.otherwise(() => undefined);
}
function extractTaxonomy(
element: Readonly<ContentTypeElements.ContentTypeElementModel>,
taxonomies: readonly Readonly<TaxonomyModels.Taxonomy>[],
): Readonly<TaxonomyModels.Taxonomy> | undefined {
return match(element)
.returnType<Readonly<TaxonomyModels.Taxonomy> | undefined>()
.with({ type: "taxonomy" }, (taxonomyElement) => {
return taxonomies.find((m) => m.id === taxonomyElement.taxonomy_group.id);
})
.otherwise(() => undefined);
}