UNPKG

frontitygit

Version:

A Frontity source package for the REST API of self-hosted and WordPress.com sites

630 lines (583 loc) 17 kB
import { Action, State, Derived, Package, Frontity } from "frontity/types"; import Source, { DataEntity } from "@frontity/source/types"; import { Api } from "./src/libraries"; /** * A Frontity source package for the REST API of self-hosted WordPress and * WordPress.com sites. */ interface WpSource extends Source<Packages> { /** * The name of this package. */ name: "@frontity/wp-source"; /** * The state exposed by this package. */ state: { /** * Source namespace. * * It also contains the state defined in the main source package. */ source: Source<Packages>["state"]["source"] & { /** * True when the REST API belongs to a WordPress.com site. * * @deprecated Use `state.wpSource.isWpCom` instead. */ isWpCom: Derived<Packages, boolean>; /** * The name or path indicating the subdirectory of the domain where the * Frontity site lives. * * For example, if your site is at https://mysite.com/blog, you have to * use it with the value of /blog. * * It is also used to transform links of the entities that come from the * REST API by prefixing them with this value. * * @example "/blog" */ subdirectory: string; /** * Set a specific page as the homepage of the site. * * For example, if you set this value to `/about-us` then that page will * be shown when you access `/`. * * @remarks * - This option overrides the `/` route so it should be used in * combination with `state.source.postsPage` to be able to access the * posts archive with a different route. * - You will need to configure WordPress with the same setting. * * @example "/about-us" */ homepage: string; /** * Show the posts archive when accessing a specific URL of your site, * instead of the homepage. * * For example, if you set this value to `/blog`, then the posts archive * will be shown if you access `/blog` instead of `/`. * * @remarks * - It is useful when used in combination with `state.source.homepage`. * - You will need to configure WordPress with the same setting. * * @example "/blog" */ postsPage: string; /** * Change the base prefix of the URLs for category pages. * * @remarks * Configure WordPress with the same setting. * * @example "categorias" */ categoryBase: string; /** * Change the base prefix of the URLs for tag pages. * * @remarks * Configure WordPress with the same setting. * * @example "etiquetas" */ tagBase: string; /** * Change the base prefix of the URLs for author pages. * * @remarks * Configure WordPress with the same setting. * * @example "autores" */ authorBase: string; /** * Set the endpoint against which calls to the REST API are made when * posts are requested when fetching a single post, the post archive, date * archives, categories, tags, authors, and so on. This is useful when you * want to use another post type as your default. * * @example "products" * * @defaultValue "posts" */ postEndpoint: string; /** * An object that will be used in each call to the REST API when using * `actions.source.fetch` with the default handlers. * * This is useful to filter fields from the REST API, change the default * `per_page` value and so on. * * @example * ```js * { * per_page: 5, * _fields: ["title", "id", "content", "link"] * } * ``` */ params: Record<string, any>; /** * The value that should be used to authenticate with the server. * * Typically, this would be used to store the JWT needed for WordPress * preview functionality or the password used for Basic Buthentication. */ auth?: string; /** * An array of objects that define the custom post types present in the * site. * * @example * ```js * [ * { * type: "movie", * endpoint: "movies", * archive: "/movies-archive" * } * ] * ``` */ postTypes: { /** * The slug of the custom post type as configured in WordPress. * * @example "movie" */ type: string; /** * The REST API endpoint from where this post type can be fetched. * * @example "movies" */ endpoint: string; /** * The URL of the archive for the custom post type. * * @example "/movies-archive" */ archive?: string; }[]; /** * An array of objects that define the custom taxonomies present in the * site. * * @example * ```js * [ * { * taxonomy: "actors", * endpoint: "actor", * postTypeEndpoint: "movies", * params: { * per_page: 5, * _embed: true * } * } * ] * ``` */ taxonomies: { /** * The slug of the custom taxonomy as configured in WordPress. * * @example "actors" */ taxonomy: string; /** * The REST API endpoint from where this taxonomy can be fetched. * * @example "actor" */ endpoint: string; /** * The REST API endpoint of the custom post type that should be fetched * for this taxonomy. * * @example "movies" */ postTypeEndpoint?: string; /** * The params that should be used in the REST API when fetching this * taxonomy. * * @example * ```js * { * per_page: 5, * _embed: true * } * ``` * * @defaultValue * ```js * { * _embed: true * } * ``` */ params?: Record<string, any>; }[]; /** * The URL of the REST API. * * It can be from a self-hosted WordPress or from a WordPress.com site. * * @example "https://your-site.com/wp-json" * @example "https://public-api.wordpress.com/wp/v2/sites/your-site.wordpress.com" * * @deprecated Use `state.wpSource.api` instead. */ api: Derived<Package, string> | string; }; /** * The WP Source namespace. */ wpSource: { /** * The URL of the REST API. * * It can be from a self-hosted WordPress or from a WordPress.com site. * * @example "https://your-site.com/wp-json" * @example "https://public-api.wordpress.com/wp/v2/sites/your-site.wordpress.com" */ api: Derived<Packages, string>; /** * True when the REST API belongs to a WordPress.com site. */ isWpCom: Derived<Packages, boolean>; /** * The prefix of the API. */ prefix?: string; }; }; /** * The actions exposed by this package. */ actions: { /** * Source namespace. */ source: Source<Packages>["actions"]["source"] & { /** * An internal action that bootstraps the initialization. * * @remarks * This action is not meant to be run by the user, but by the Frontity * framework. */ init: Action<Packages>; /** * An internal frontity action that runs after server-side rendering * completes. * * @remarks * This action is not meant to be run by the user, but by the Frontity * framework. */ afterSSR: Action<Packages>; }; }; /** * The libraries exposed by this package. */ libraries: { /** * Source namespace. */ source: Source["libraries"]["source"] & { /** * A library helper to fetch the REST API. * * It has two methods, `api.init` and `api.get`: * - `api.init`: Used internally to initialize the class. * - `api.get`: Used to fetch the entities from the REST API. * * Types defined in {@link Api}. * * @example * `api.get({ endpoint: "pages", params: { _embed: true, include: '14' } });` * @example * `api.get({ endpoint: "posts", params: { _embed: true, categories: '2,3,4' } });` */ api: Api; /** * A library helper to add entities to the Frontity state. * * @remarks * Entities are not overwritten. If an entity already exists in the state * and a new one is fetched, the one in the state will prevail. If you * want to overwrite them, use the `force` option. * * @example * ```js * const response = await libraries.source.api.get({ endpoint: "posts" }); * const entities = await libraries.source.populate({ response, state }); * ``` */ populate: (args: { /** * The Frontity state. */ state: State<Packages>; /** * The Response object. * * Usually returned from `api.get`, but can also be the one returned by * `window.fetch`. */ response: Response; /** * The subdirectory of the Frontity site. When this options is passed, * this subdirectory is added to the entities' links. * * @example "/blog" * * @defaultValue `state.source.subdirectory` */ subdirectory?: string; /** * A boolean that indicates if the entities should be overwritten. * * @defaultValue false */ force?: boolean; }) => /** * Returns a promise that resolves with an array of objects with * attributes `type`, `id` and `link` representing the added entities. * * @example * ```js * const entities = await libraries.source.populate({ response, state }); * data.entities = entities.map(entity => ({ * type: entity.type, * id: entity.id, * link: entity.link, * })) * ``` */ Promise<DataEntity[]>; /** * Handlers are objects that associate a link pattern with a function that * fetches all the entities that belong to that WordPress link. * * Types defined in {@link Pattern}. */ handlers: Pattern<Handler>[]; /** * Redirections are objects that associate a link pattern with a function * that returns a new link. * * These redirections are used when `actions.source.fetch` is executed, * before the handlers. */ redirections: Pattern<Redirection>[]; /** * Extracts the total number of entities of an archive from its REST API * Response object. * * @param response - The Response object. Usually returned from `api.get`, * but can also be the one returned by `window.fetch`. * @param valueIfHeaderMissing - The value that must be returned if the * header containing the information is not found in the Response object. * * @returns - The total number of entities for that archive. * * @defaultValue 0 */ getTotal: (response: Response, valueIfHeaderMissing: number) => number; /** * Extracts the total number of pages of an archive from its REST API * Response object. * * @param response - The Response object. Usually returned from `api.get`, * but can also be the one returned by `window.fetch`. * @param valueIfHeaderMissing - The value that must be returned if the * header containing the information is not found in the Response object. * * @returns - The total number of pages for that archive. * * @defaultValue 0 */ getTotalPages: ( response: Response, valueIfHeaderMissing: number ) => number; }; }; } /** * Packages used internally by wp-source. */ export type Packages = WpSource & Frontity; export default WpSource; /** * Handlers are objects that associate a link pattern with a function that * fetches all the entities that belong to that WordPress link. * * Handlers are used when `actions.source.fetch` is executed. */ export interface Pattern<F extends (...args: any) => any> { /** * A unique name to identify this handler. * * @example "product" */ name: string; /** * The priority of this handler. * * Lower numbers have higher priority. * * @defaultValue 10 */ priority: number; /** * The pattern used to compare against the link that is being fetched. * * - To match a URL that doesn't include a query, like "/category/nature", * you have to use a `path-to-regexp` pattern. Please check its documentation * to learn how to use them: https://github.com/pillarjs/path-to-regexp. * * - To match a URL that includes a query, like "/?p=12", you have to use a * regular expression. Start the string with "RegExp:" to differenciate it * from the `path-to-regexp` patterns. Named capture groups will be * added to the params object. * * @example "/product/:slug" * @example "RegExp:\\?p=(?<id>\\d+)" */ pattern: string; /** * The function that does the actual fetching. * * Types defined in {@link Handler}. */ func: F; } /** * The handler function is in charge of fetching all the entities that belong to * a link. * * For example, when we fetch the link of a page, the handler must retrieve the * entitiy for that page, but also its author. When we fetch the link of a post, * the handler should retrieve the entity of the post, its author, its * categories, its tags, its featured image and so on. * * @example * ```js * libraries.source.handlers.push({ * name: "product", * priority: 10, * pattern: "/product/:slug", * func: async ({ link, params, state, libraries, force }) => { * // 1. Get the product entities from the REST API. * const response = await libraries.source.api.get({ * endpoint: "products", * params: { slug: params.slug } * }); * * // 2. Add the product to the state. * const [product] = await libraries.source.populate({ response, state, force }); * * // 3. Add the data object for this link. * Object.assign(state.source.data[link], { * id: product.id, * type: product.type, * isPostType: true, * isProduct: true * }); * } * }); * ``` * * @param args - The arguments object. * * @returns - Returns a promise that is resolved when the handler has finished. */ export interface Handler<Pkg extends Source = WpSource> { (args: { /** * The link that is being fetched. * * @example "/some-post" */ link: string; /** * The link that is being fetched. * * @deprecated * Use `link` instead. */ route: string; /** * The values extracted from the pattern. * * For example, if the pattern is `"/category/:slug"` and it is called for * the link `"/category/nature"`, it will receive `{ slug: "nature" }`. * * @example * ```js * { * slug: "nature" * } * ``` */ params: { [param: string]: any }; /** * The Frontity state. */ state: State<Pkg>; /** * The Frontity libraries. */ libraries: Pkg["libraries"]; /** * Whether `actions.source.fetch` was called with the `force` option. */ force?: boolean; }): Promise<void>; } /** * Redirections are objects that associate a link pattern with a function that * returns a new link. * * These redirections are used when `actions.source.fetch` is executed, before * the handlers. * * @example * ```js * libraries.source.redirections.push({ * name: "tags-to-labels", * priority: 5, * pattern: "/tag/:slug/", * func: ({ slug }) => "/label/" + slug * }); * ``` * * @param params - An object that contains the values extracted from the * pattern. * * For example, if the pattern is `"/category/:slug"` and it is called for the * link `"/category/nature"`, it will receive `{ slug: "nature" }`. * * @example * ```js * { * slug: "nature" * } * ``` * * @returns The new link. */ export interface Redirection { (params?: Record<string, string>): string; }