@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.
171 lines (146 loc) • 6.03 kB
text/typescript
import type { ContentTypeModels, TaxonomyModels } from "@kontent-ai/management-sdk";
import { match } from "ts-pattern";
import { deliveryConfig } from "../../../config.js";
import { wrapComment } from "../../../core/comment.utils.js";
import type { ModuleFileExtension } from "../../../core/core.models.js";
import { getImporter } from "../../../core/importer.js";
import { resolveCase } from "../../../core/resolvers.js";
import type { DeliveryEntityNames } from "../delivery-entity-name.generator.js";
import type { DeliveryEntity, DeliveryEntityType } from "../delivery-entity.generator.js";
export type TypeMappingItem = {
readonly codename: string;
readonly typeName: string;
};
export function deliveryEntityUtils() {
return {
getPluralName,
getTypeMapping,
getTypeMappingItem,
getCodeOfDeliveryEntity,
getTaxonomyTermCodenames,
getCodenameTypeguardFunctionCode,
getCoreContentTypeEntityImports,
getCoreContentTypeCode,
};
}
function getCoreContentTypeCode(
types: readonly Readonly<ContentTypeModels.ContentType>[],
entityNames: DeliveryEntityNames<"Type">,
): string {
return `export type ${deliveryConfig.coreContentTypeName} = ${getCoreContentTypeValue(types, entityNames)}`;
}
function getCoreContentTypeEntityNames(
types: Parameters<typeof getCoreContentTypeCode>[0],
entityNames: Parameters<typeof getCoreContentTypeCode>[1],
): readonly string[] {
return types.map((m) => entityNames.getEntityName(m));
}
function getCoreContentTypeValue(
types: Parameters<typeof getCoreContentTypeEntityNames>[0],
entityNames: Parameters<typeof getCoreContentTypeEntityNames>[1],
): string {
const typeNames = getCoreContentTypeEntityNames(types, entityNames);
if (!typeNames.length) {
return "never";
}
return typeNames.join(" | ");
}
function getCoreContentTypeEntityImports(
types: Parameters<typeof getCoreContentTypeEntityNames>[0],
entityNames: Parameters<typeof getCoreContentTypeEntityNames>[1],
moduleFileExtension: ModuleFileExtension,
): readonly string[] {
const importer = getImporter(moduleFileExtension);
return types.map((m) =>
importer.importType({
filePathOrPackage: `../${entityNames.folderName}/${entityNames.getEntityFilename(m, true)}`,
importValue: entityNames.getEntityName(m),
}),
);
}
function getTypeMappingTypeName(entityType: DeliveryEntityType): string {
return `Codename${resolveCase(entityType, "pascalCase")}Mapping`;
}
function getTypeMapping(entityType: DeliveryEntityType, items: readonly TypeMappingItem[]): string {
return `export type ${getTypeMappingTypeName(entityType)} = {
${items.map((item) => `readonly ${item.codename}: ${item.typeName},`).join("\n")}
};`;
}
function getTypeMappingItem({
codenamesTypeName,
defaultTypeName,
entityType,
}: {
readonly entityType: DeliveryEntityType;
readonly codenamesTypeName: string;
readonly defaultTypeName: string;
}): string {
const codenameGenericName = `T${resolveCase(entityType, "pascalCase")}Codename`;
const typedItemTypeName = `Codename${resolveCase(entityType, "pascalCase")}Mapper`;
return `export type ${typedItemTypeName}<${codenameGenericName} extends ${codenamesTypeName}> = ${codenameGenericName} extends keyof ${getTypeMappingTypeName(entityType)} ? ${getTypeMappingTypeName(entityType)}[${codenameGenericName}] : ${defaultTypeName};`;
}
function getCodenameTypeguardFunctionCode({
codenameTypeName,
entity,
typeGuardName,
}: {
readonly typeGuardName: string;
readonly codenameTypeName: string;
readonly entity: Readonly<DeliveryEntity>;
}): string {
return `export function ${typeGuardName}(value: string | undefined | null): value is ${codenameTypeName} {
return typeof value === 'string' && value === ('${entity.codename}' satisfies ${codenameTypeName});
}`;
}
function getTaxonomyTermCodenames(taxonomyTerms: readonly Readonly<TaxonomyModels.Taxonomy>[]): readonly string[] {
const termCodenames = taxonomyTerms.reduce<readonly string[]>((codenames, taxonomyTerm) => {
return codenames.concat(getTaxonomyTermCodenames(taxonomyTerm.terms), taxonomyTerm.codename);
}, []);
return [...new Set(termCodenames)];
}
function getPluralName(entityType: DeliveryEntityType): string {
return match(entityType)
.returnType<string>()
.with("Taxonomy", () => "Taxonomies")
.otherwise(() => `${entityType}s`);
}
function getCodeOfDeliveryEntity({
extendedType,
codenames,
names,
}: {
readonly names: {
readonly codenamesTypeName: string;
readonly valuesPropertyName: string;
readonly typeGuardFunctionName: string;
};
readonly subtype?: "Term" | "Step";
readonly extendedType: DeliveryEntityType | "Workflow step" | "Taxonomy term";
readonly codenames: readonly string[];
}): string {
const uniqueCodenames: readonly string[] = [...new Set(codenames)];
const getCodenameTypeguardFunctionCode = (): string => {
return `export function ${names.typeGuardFunctionName}(value: string | undefined | null): value is ${names.codenamesTypeName} {
return typeof value === 'string' && (${names.valuesPropertyName} as readonly string[]).includes(value);
}`;
};
const getValuesCode = (): string => {
return `export const ${names.valuesPropertyName} = [${uniqueCodenames.map((m) => `'${m}'`).join(", ")}] as const;`;
};
const getCodenamesTypeCode = (): string => {
return `export type ${names.codenamesTypeName} = typeof ${names.valuesPropertyName}[number];`;
};
const getComment = (title: string): string => {
return wrapComment(title, {
lines: [],
});
};
const getEntityTypeNameForComments = (): string => `${extendedType.toLowerCase()}`;
return `
${getComment(`Array of all ${getEntityTypeNameForComments()} codenames`)}
${getValuesCode()};
${getComment(`Type representing all ${getEntityTypeNameForComments()} codenames`)}
${getCodenamesTypeCode()};
${getComment(`Typeguard for ${getEntityTypeNameForComments()} codename`)}
${getCodenameTypeguardFunctionCode()};`;
}