UNPKG

@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.

121 lines (103 loc) 3.48 kB
import { createHash } from "node:crypto"; import { match, P } from "ts-pattern"; import type { CaseType, ObjectWithCodename, ObjectWithName } from "./core.models.js"; export type FilenameResolver<T extends Readonly<ObjectWithCodename>> = undefined | ((item: T, addExtension: boolean) => string); export type NameResolver<T extends Readonly<ObjectWithName>> = undefined | ((item: T) => string); export function mapFilename<T extends ObjectWithCodename>( resolver: NonNullable<FilenameResolver<T>>, options?: { readonly prefix?: string; readonly suffix?: string; }, ): NonNullable<FilenameResolver<T>> { return (item, addExtension) => { return ( (options?.prefix ? options.prefix : "") + addExtensionToFilename( resolveCase( match(resolver) .returnType<string>() .with(P.instanceOf(Function), (resolver) => resolver(item, addExtension)) .otherwise(() => item.codename), "kebabCase", ) + (options?.suffix ? options.suffix : ""), addExtension, ) ); }; } export function mapName<T extends ObjectWithName>( resolver: NameResolver<T>, caseType: CaseType, options?: { readonly prefix?: string; readonly suffix?: string; }, ): NonNullable<NameResolver<T>> { return (item) => (options?.prefix ? options.prefix : "") + resolveCase( match(resolver) .returnType<string>() .with(P.instanceOf(Function), (resolver) => resolver(item)) .otherwise(() => item.name), caseType, ) + (options?.suffix ? options.suffix : ""); } export function resolveCase(text: string, resolverType: CaseType): string { return match(resolverType) .returnType<string>() .with("camelCase", () => toCamelCase(text)) .with("pascalCase", () => toPascalCase(text)) .with("kebabCase", () => toKebabCase(text)) .exhaustive(); } export function resolvePropertyName(value: string): string { const propertyName = toCamelCase(value); if (propertyName.length === 0) { // to prevent empty string being used as property name, use hash return getPropertyStringHash(value); } return prefixWithUnderscoreWhenStartsWithNonAlpha(propertyName); } function addExtensionToFilename(filename: string, addExtension: boolean): string { return `${filename}${addExtension ? ".ts" : ""}`; } function toPascalCase(text: string): string { return prefixWithUnderscoreWhenStartsWithNonAlpha( toSafeStringCode( text .replace(/[_-]+/g, " ") .replace(/(?:^\w|[A-Z]|\b\w|\s+|\d\w)/g, (match) => match.toUpperCase()) .replace(/\s+/g, ""), false, ), ); } function toKebabCase(text: string): string { const matchResult = text.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g); if (!matchResult) { throw new Error(`Failed to convert text to kebab case: ${text}`); } return matchResult.join("-").toLowerCase(); } function toCamelCase(text: string): string { return toPascalCase(text).replace(/^\w/, (s) => s.toLowerCase()); } function getPropertyStringHash(text: string): string { const hash = createHash("sha256"); hash.update(text); return `_${hash.digest("hex")}`.slice(0, 10); } function toSafeStringCode(text: string, allowHyphen: boolean): string { const replaceWith = ""; const pattern = allowHyphen ? /[^a-zA-Z0-9_-]/g : /[^a-zA-Z0-9_]/g; return text.replace(pattern, replaceWith); } function prefixWithUnderscoreWhenStartsWithNonAlpha(text: string): string { if (/^[^a-zA-Z-]/.test(text)) { return `_${text.replace(/^_+/, "")}`; } return text; }