//////////////////////////// // Internal utility types // //////////////////////////// type FlatValue = Key extends `${infer I}.${infer J}` ? I extends keyof Type ? FlatValue, J> : never : Key extends keyof Type ? Type[Key] : never; type FlatKeys = Type extends object ? { [Key in keyof Type]: FlatKeys< Type[Key], `${P}${P extends "" ? "" : "."}${Key & string}` >; }[keyof Type] : P; // Use to flatten types and prefix with "fields." so they can be filtered type WithFieldsPrefix = { [Key in FlatKeys as Key extends string ? `fields.${Key}` : never]?: FlatValue, Key>; }; // Creates a type where the ContentModel keys are also prefixed with "-" for a decreasing order type OrderParam = { [Key in keyof ContentModel as Key extends string ? `${"-" | ""}${Key}` : never]: ContentModel[Key]; }; type ContentArrays = { [Key in keyof ContentModels as Key extends string ? Key : never]: Array< ContentModels[Key] & { meta: { id: number } } >; }; type ContentModelTopLevelValues = T[keyof T] extends object ? T[keyof T] : never; type FlattenContentModels = ( U extends any ? (k: U) => void : never ) extends (k: infer I extends object) => void ? I : never; export namespace Butter { /////////////////// // Utility types // /////////////////// interface Meta { next_page: number | null; previous_page: number | null; count: number; } interface Methods { /** * Cancels the ongoing fetch request by aborting the associated AbortController */ cancelRequest(): void; } interface Response { data?: Data; status?: number; statusText?: string; headers?: Record; config?: Record; } ////////// // Post // ////////// interface PostRetrieveParams { preview?: 1 | 0; } interface PostListParams { author_slug?: AuthorSlug; category_slug?: string; exclude_body?: boolean; limit?: number; offset?: number; page?: number; page_size?: number; preview?: 1 | 0; tag_slug?: string; } interface PostSearchParams { page?: number; page_size?: number; } interface Post< AuthorSlug extends string = string, PostSlug extends string = string > { author: Omit, "recent_posts">; body?: string; categories: Category[]; created: string; featured_image: string | null; featured_image_alt: string; meta_description: string; published: string | null; scheduled: string | null; seo_title: string; slug: PostSlug; status: "published" | "draft" | "scheduled"; summary: string; tags: Tag[]; title: string; updated: string | null; url: string; } interface PostMeta { next_post: object | null; previous_post: object | null; } interface PostRetrieveResponse< AuthorSlug extends string = string, PostSlug extends string = string > { meta: PostMeta; data: Post; } interface PostListResponse< AuthorSlug extends string = string, PostSlug extends string = string > { meta: Meta; data: Post[]; } interface PostSearchResponse { meta: Meta; data: Post[]; } interface PostMethods extends Methods { /** * Retrieve a post * @param slug The post's slug * @param params Optional params * @example * retrieve<'how-to-use-butter'>('how-to-use-butter') */ retrieve( slug: PostSlug, params?: PostRetrieveParams ): Promise>>; /** * Get a list of posts * @param params Optional params * @example * list() */ list( params?: PostListParams ): Promise>>; /** * Search posts based on a query * @param query The query * @param params Optional params * @example * search('my query') */ search( query: string, params?: PostSearchParams ): Promise>; } ////////////// // Category // ////////////// interface CategoryParams { /** * Get 10 most recent posts of this category */ include?: "recent_posts"; } interface Category { name: string; slug: CategorySlug; recent_posts?: Post[]; } interface CategoryRetrieveResponse { data: Category; } interface CategoryListResponse { data: Category[]; } interface CategoryMethods extends Methods { /** * Retrieve a category * @param slug The category's slug * @param params Optional params * @example * retrieve<'some-category'>('some-category') */ retrieve( slug: CategorySlug, params?: CategoryParams ): Promise>>; /** * Get all categories * @param params Optional params * @example * list() */ list(params?: CategoryParams): Promise>; } ///////// // Tag // ///////// interface TagParams { /** * Get 10 most recent posts of this tag */ include?: "recent_posts"; } interface Tag { name: string; slug: TagSlug; recent_posts?: Post[]; } interface TagRetrieveResponse { data: Tag; } interface TagListResponse { data: Tag[]; } interface TagMethods extends Methods { /** * Retrieve a tag * @param slug The tag's slug * @param params Optional params * @example * retrieve<'some-tag'>('some-tag') */ retrieve( slug: TagSlug, params?: TagParams ): Promise>>; /** * Get all tags * @param params Optional params * @example * list() */ list(params?: TagParams): Promise>; } //////////// // Author // //////////// interface AuthorParams { /** * Get 10 most recent posts by the author */ include?: "recent_posts"; } interface Author { first_name: string; last_name: string; email: string; slug: AuthorSlug; bio: string; title: string; linkedin_url: string; facebook_url: string; pinterest_url: string; instagram_url: string; twitter_handle: string; profile_image: string; recent_posts?: Post[]; } interface AuthorRetrieveResponse { data: Author; } interface AuthorListResponse { data: Author[]; } interface AuthorMethods extends Methods { /** * Retrieve an author * @param slug The author's slug * @param params Optional params * @example * retrieve<'joe-bloggs'>('joe-bloggs') */ retrieve( slug: string, params?: AuthorParams ): Promise>>; /** * Get a list of authors * @param params Optional params * @example * list() */ list(params?: AuthorParams): Promise>; } ////////// // Feed // ////////// type FeedTypes = "atom" | "rss" | "sitemap"; interface FeedParams { category_slug?: string; tag_slug?: string; } interface FeedMethods extends Methods { /** * Get a feed * @param feedType The type of feed * @param params Optional params * @example * retrieve('atom') * retrieve('rss') * retrieve('sitemap') */ retrieve( feedType: FeedTypes, params?: FeedParams ): Promise>; } ////////// // Page // ////////// interface PageRetrieveParams { alt_media_text?: 0 | 1; levels?: number; locale?: string; preview?: 0 | 1; } type PageListParams = WithFieldsPrefix & { alt_media_text?: 0 | 1; levels?: number; limit?: number; locale?: string; offset?: number; order?: string; page?: number; page_size?: number; preview?: 0 | 1; }; interface PageSearchParams { levels?: number; limit?: number; locale?: string; offset?: number; page?: number; page_size?: number; page_type?: PageType; } interface Page< PageModel extends object = object, PageType extends string = string, PageSlug extends string = string > { fields: PageModel; name: string; page_type: PageType; published: string | null; slug: PageSlug; status: "published" | "draft" | "scheduled"; updated: string | null; } interface PageRetrieveResponse< PageModel extends object = object, PageType extends string = string, PageSlug extends string = string > { data: Page; } interface PageListResponse< PageModel extends object = object, PageType extends string = string > { meta: Meta; data: Page[]; } interface PageSearchResponse< PageModel extends object = object, PageType extends string = string > { meta: Meta; data: Page[]; } interface PageMethods extends Methods { /** * Retrieve a single page * @param page_type The page type * @param page_slug The page slug * @param params Optional params * @example * interface LandingPage { ... } * retrieve('landing_page', 'our_services') */ retrieve< PageModel extends object = object, PageType extends string = string, PageSlug extends string = string >( page_type: PageType, page_slug: PageSlug, params?: PageRetrieveParams ): Promise>>; /** * Get multiple pages of the same page type * @param page_type The page type * @param params Optional params * @example * interface LandingPage { ... } * list('landing_page') */ list( page_type: PageType, params?: PageListParams ): Promise>>; /** * Search pages based on a query * @param query The query * @param params Optional params * @example * // Generic search * search('my query') * * // Search all pages of the same page type * interface LandingPage { ... } * search('my query', { page_type: 'landing_type' }) */ search( query: string, params?: PageSearchParams ): Promise>>; } ///////////// // Content // ///////////// type ContentParams = WithFieldsPrefix & { levels?: number; locale?: string; order?: keyof OrderParam; page?: number; page_size?: number; preview?: 0 | 1; }; interface ContentResponse { meta: Meta; data: ContentArrays; } interface ContentMethods extends Methods { /** * Retrieve content * @param keys An array of the keys of the content to retrieve * @param params Optional params * @example * interface Content1 { ... } * interface Content2 { ... } * retrieve<{ key1: Content1, key2: Content2 }>(['key1', 'key2']) */ retrieve( keys: Array, params?: ContentParams< FlattenContentModels> > ): Promise>>; } } export class ButterStatic { author: Butter.AuthorMethods; category: Butter.CategoryMethods; content: Butter.ContentMethods; feed: Butter.FeedMethods; page: Butter.PageMethods; post: Butter.PostMethods; tag: Butter.TagMethods; constructor( apiToken: string, options: { cache?: string onError?: ((errors: any[], params?: object) => void) | null onRequest?: ((apiEndpoint: string, params?: object) => void) | null onResponse?: ((response: Response, params?: object) => void) | null testMode?: boolean timeout?: number } ); } export const Butter: ( apiToken: string, options?: { cache?: string onError?: ((errors: any[], params?: object) => void) | null onRequest?: ((apiEndpoint: string, params?: object) => void) | null onResponse?: ((response: Response, params?: object) => void) | null testMode?: boolean timeout?: number } ) => ButterStatic; export default Butter;