From 87774042eb5ed6497fa6431e9cbd49e70ffc399d Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Tue, 21 Jan 2025 15:03:28 -0600 Subject: [PATCH 01/35] feat: move API docs outside of Next site Standing this up for feeback. While it does have pretty obvious UX disadvantages, it draws a clearer distinction and provides more detail in the docs. * Promote API link to header menu * Serve HTML API docs out of public dir - if we stick to this we'll want to clean up the URLs with Next redirects or something similar. --- www/.gitignore | 2 +- www/config/docs.ts | 9 --------- www/config/site.ts | 4 ++++ www/typedoc.json | 8 ++++---- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/www/.gitignore b/www/.gitignore index 27c0264e..6c9a7e5e 100644 --- a/www/.gitignore +++ b/www/.gitignore @@ -13,7 +13,7 @@ /cypress/videos # Files generated automatically by typedoc -/content/docs/api +/public/api # next.js /.next/ diff --git a/www/config/docs.ts b/www/config/docs.ts index edc91e2b..a6abaa81 100644 --- a/www/config/docs.ts +++ b/www/config/docs.ts @@ -121,15 +121,6 @@ export const docsConfig: DocsConfig = { }, ], }, - { - title: "API", - items: [ - { - title: "API Reference", - href: "/docs/api/globals", - }, - ], - }, { title: "Drupal", items: [ diff --git a/www/config/site.ts b/www/config/site.ts index 83effd88..0c060ba9 100644 --- a/www/config/site.ts +++ b/www/config/site.ts @@ -26,6 +26,10 @@ export const site: SiteConfig = { href: "/guides", activePathNames: ["/guides/[...slug]"], }, + { + title: "API", + href: "/api/modules.html", + }, { title: "Blog", href: "/blog", diff --git a/www/typedoc.json b/www/typedoc.json index 05db124c..2fb9101c 100644 --- a/www/typedoc.json +++ b/www/typedoc.json @@ -1,7 +1,7 @@ { - "plugin": ["typedoc-plugin-markdown"], "entryPoints": ["../packages/next-drupal/src/index.ts"], - "out": "content/docs/api", - "fileExtension": ".mdx", - "hidePageHeader": true + "out": "public/api", + "navigationLinks": { + "Back to Docs": "/docs" + } } From 982e9c6ad0089f71163c068072e7292571a34a64 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Tue, 21 Jan 2025 15:28:22 -0600 Subject: [PATCH 02/35] feat: port all examples from getResource docs to Typedoc version See /api/classes/NextDrupal.html#getresource for output --- packages/next-drupal/src/next-drupal.ts | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 51b20d53..a38d4bc5 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -294,6 +294,62 @@ export class NextDrupal extends NextDrupalBase { * @param {string} uuid The UUID of the resource. * @param {JsonApiOptions & JsonApiWithCacheOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} The fetched resource. + * @examples + * Get a page by uuid. + * ```ts + * const node = await drupal.getResource( + * "node--page", + * "07464e9f-9221-4a4f-b7f2-01389408e6c8" + * ) + * ``` + * Get the es translation for a page by uuid. + * ```ts + * const node = await drupal.getResource( + * "node--page", + * "07464e9f-9221-4a4f-b7f2-01389408e6c8", + * { + * locale: "es", + * defaultLocale: "en", + * } + * ) + * ``` + * Get the raw JSON:API response. + * ```ts + * const { data, meta, links } = await drupal.getResource( + * "node--page", + * "07464e9f-9221-4a4f-b7f2-01389408e6c8", + * { + * deserialize: false, + * } + * ) + * ``` + * Get a node--article resource using cache. + * ```ts + * const id = "07464e9f-9221-4a4f-b7f2-01389408e6c8" + * + * const article = await drupal.getResource("node--article", id, { + * withCache: true, + * cacheKey: `node--article:${id}`, + * }) + * ``` + * Using DrupalNode for a node entity type. + * ```ts + * import { DrupalNode } from "next-drupal" + * + * const node = await drupal.getResource( + * "node--page", + * "07464e9f-9221-4a4f-b7f2-01389408e6c8" + * ) + * ``` + * Using DrupalTaxonomyTerm for a taxonomy term entity type. + * ```ts + * import { DrupalTaxonomyTerm } from "next-drupal" + * + * const term = await drupal.getResource( + * "taxonomy_term--tags", + * "7b47d7cc-9b1b-4867-a909-75dc1d61dfd3" + * ) + * ``` */ async getResource( type: string, From 02c22f9102b599d40b62fb731c5491fa6dc7da81 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Tue, 21 Jan 2025 15:32:04 -0600 Subject: [PATCH 03/35] feat: example of ignoring something from tsdoc docs Not positive if we want to ignore this, or just find a way to re-order this in menus. --- packages/next-drupal/src/jsonapi-errors.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-drupal/src/jsonapi-errors.ts b/packages/next-drupal/src/jsonapi-errors.ts index 41017f16..93ea07e6 100644 --- a/packages/next-drupal/src/jsonapi-errors.ts +++ b/packages/next-drupal/src/jsonapi-errors.ts @@ -13,6 +13,7 @@ export interface JsonApiLinks { [key: string]: string | Record } +/** @hidden */ export class JsonApiErrors extends Error { errors: JsonApiError[] | string statusCode: number From 16c6e0b061399a8789b7690ff220c529eb4d4526 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Tue, 21 Jan 2025 15:38:39 -0600 Subject: [PATCH 04/35] feat: example of marking something as deprecated --- packages/next-drupal/src/deprecated/get-access-token.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-drupal/src/deprecated/get-access-token.ts b/packages/next-drupal/src/deprecated/get-access-token.ts index 017a6660..dfc243cd 100644 --- a/packages/next-drupal/src/deprecated/get-access-token.ts +++ b/packages/next-drupal/src/deprecated/get-access-token.ts @@ -3,6 +3,7 @@ import type { AccessToken } from "../types" const CACHE_KEY = "NEXT_DRUPAL_ACCESS_TOKEN" +/** @deprecated */ export async function getAccessToken(): Promise { if (!process.env.DRUPAL_CLIENT_ID || !process.env.DRUPAL_CLIENT_SECRET) { return null From 61488cd6aa72f0451916b1a065d1911fdd4f44a0 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 22 Jan 2025 09:26:57 -0600 Subject: [PATCH 05/35] docs: add additional detail to getResource entry --- packages/next-drupal/src/next-drupal.ts | 25 +++++++++++++++++++++-- packages/next-drupal/src/types/options.ts | 17 +++++++++++++++ www/package.json | 3 ++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index a38d4bc5..3227443c 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -290,8 +290,8 @@ export class NextDrupal extends NextDrupalBase { /** * Fetches a resource of the specified type by its UUID. * - * @param {string} type The type of the resource. - * @param {string} uuid The UUID of the resource. + * @param {string} type The resource type. Example: `node--article`, `taxonomy_term--tags`, or `block_content--basic`. + * @param {string} uuid The id of the resource. Example: `15486935-24bf-4be7-b858-a5b2de78d09d`. * @param {JsonApiOptions & JsonApiWithCacheOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} The fetched resource. * @examples @@ -332,6 +332,27 @@ export class NextDrupal extends NextDrupalBase { * cacheKey: `node--article:${id}`, * }) * ``` + * Get a page resource with time-based revalidation. + * ```ts + * const node = await drupal.getResource( + * "node--page", + * "07464e9f-9221-4a4f-b7f2-01389408e6c8", + * { next: { revalidate: 3600 } } + * ) + * ``` + * Get a page resource with tag-based revalidation. + * ```ts + * const {slug} = params; + * const path = drupal.translatePath(slug) + * + * const type = path.jsonapi.resourceName + * const tag = `${path.entity.type}:${path.entity.id}` + * + * const node = await drupal.getResource(path, path.entity.uuid, { + * params: params.getQueryObject(), + * tags: [tag] + * }) + * ``` * Using DrupalNode for a node entity type. * ```ts * import { DrupalNode } from "next-drupal" diff --git a/packages/next-drupal/src/types/options.ts b/packages/next-drupal/src/types/options.ts index eb09e6c3..417788db 100644 --- a/packages/next-drupal/src/types/options.ts +++ b/packages/next-drupal/src/types/options.ts @@ -10,13 +10,24 @@ export interface FetchOptions extends RequestInit { withAuth?: boolean | NextDrupalAuth } +/** + * JSON:API Related Options + */ export type JsonApiOptions = { + /** + * Set to false to return the raw JSON:API response. + * */ deserialize?: boolean + /** + * JSON:API params such as `filter`, `fields`, `include` or `sort`. + */ params?: JsonApiParams } & JsonApiWithAuthOption & ( | { + /** The locale to fetch the resource in. */ locale: Locale + /** The default locale of the site. */ defaultLocale: Locale } | { @@ -26,11 +37,14 @@ export type JsonApiOptions = { ) export type JsonApiWithAuthOption = { + /** Set to true to use the authentication method configured on the client. */ withAuth?: boolean | NextDrupalAuth } export type JsonApiWithCacheOptions = { + /** Set `withCache` if you want to store and retrieve the resource from cache. */ withCache?: boolean + /** The cache key to use. */ cacheKey?: string } @@ -39,5 +53,8 @@ export type JsonApiWithNextFetchOptions = { cache?: RequestCache } // TODO: Properly type this. +/** + * JSON:API params such as filter, fields, include or sort. + */ /* eslint-disable @typescript-eslint/no-explicit-any */ export type JsonApiParams = Record diff --git a/www/package.json b/www/package.json index 1b11886a..2526a6ed 100644 --- a/www/package.json +++ b/www/package.json @@ -9,7 +9,8 @@ "dev": "typedoc --tsconfig tsconfig.docs.json && next dev -p 4444", "build": "typedoc --tsconfig tsconfig.docs.json && next build", "preview": "typedoc --tsconfig tsconfig.docs.json && next build && next start -p 4444", - "generate:api-docs": "typedoc --tsconfig tsconfig.docs.json" + "generate:api-docs": "typedoc --tsconfig tsconfig.docs.json", + "watch:api-docs": "typedoc --tsconfig tsconfig.docs.json --watch" }, "dependencies": { "@docsearch/react": "^3.6.0", From 27cd1b7cb68a3949e821042b743011de07f01aa0 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Thu, 23 Jan 2025 09:25:55 -0600 Subject: [PATCH 06/35] docs: initial revisions to getResourceFromContext typedoc --- packages/next-drupal/src/next-drupal-pages.ts | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index e37d4916..381edef9 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -88,10 +88,64 @@ export class NextDrupalPages extends NextDrupal { /** * Gets a resource from the context. * - * @param {string | DrupalTranslatedPath} input The input path or translated path. - * @param {GetStaticPropsContext} context The static props context. + * @param {string | DrupalTranslatedPath} input Either a resource type (e.g. "node--article") or a translated path from translatePath(). + * @param {GetStaticPropsContext} context The Next.js context from getStaticProps. * @param {Object} options Options for the request. + * @param {PathPrefix} [options.pathPrefix] The path prefix to use for the request (defaults to "/"). + * @param {boolean} [options.isVersionable] Whether the resource is versionable (defaults to false for all entity types except nodes). * @returns {Promise} The fetched resource. + * @remarks + * The localized resource will be fetched based on the `locale` and `defaultLocale` values from `context`. + * + * If you pass in a `DrupalTranslatedPath` for input, `getResourceFromContext` will take the `type` and `id` from the path and make a `getResource` call to Drupal: + * ```ts + * export async function getStaticProps(context) { + * const path = await drupal.translatePathFromContext(context) + * + * const node = await drupal.getResourceFromContext(path, context) + * + * return { + * props: { + * node, + * }, + * } + * } + * ``` + * + * If you pass in a `string` input, such as `node--article`, `getResourceFromContext` will make a subrequest call to Drupal to translate the path and then fetch the resource. + * You will need both the [Subrequests](https://drupal.org/project/subrequests) and [Decoupled Router](https://drupal.org/project/decoupled_router) modules: + * ```ts + * export async function getStaticProps(context) { + * const node = await drupal.getResourceFromContext("node--article", context) + * + * return { + * props: { + * node, + * }, + * } + * } + * ``` + * @examples + * + * Using DrupalNode type: + * ```ts + * import { DrupalNode } from "next-drupal" + * + * const node = await drupal.getResourceFromContext( + * "node--page", + * context + * ) + * ``` + * Using DrupalTaxonomyTerm type: + * ```ts + * import { DrupalTaxonomyTerm } from "next-drupal" + * + * const term = await drupal.getResourceFromContext( + * "taxonomy_term--tags", + * context + * ) + * ``` + * @see {@link https://next-drupal.org/docs/typescript} for more built-in types. */ async getResourceFromContext( input: string | DrupalTranslatedPath, From 1509e09933db9d7d50685cf0f72ad6e7fdfcd848 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Thu, 23 Jan 2025 09:30:01 -0600 Subject: [PATCH 07/35] docs: add remaining examples to getResourceFromContext typedoc --- packages/next-drupal/src/next-drupal-pages.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index 381edef9..821d4b8e 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -126,7 +126,32 @@ export class NextDrupalPages extends NextDrupal { * } * ``` * @examples + * Fetch a resource from context. + * ```ts title=pages/[[...slug]].tsx + * export async function getStaticProps(context) { + * const node = await drupal.getResourceFromContext("node--page", context) * + * return { + * props: { + * node, + * }, + * } + * } + * ``` + * Fetch a resource from context in a sub directory. + * ```ts title=pages/articles/[[...slug]].tsx + * export async function getStaticProps(context) { + * const node = await drupal.getResourceFromContext("node--page", context, { + * pathPrefix: "/articles", + * }) + * + * return { + * props: { + * node, + * }, + * } + * } + * ``` * Using DrupalNode type: * ```ts * import { DrupalNode } from "next-drupal" From 32d40b1ed1696260788ba1685d74ed477a76fd84 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Fri, 24 Jan 2025 08:46:19 -0600 Subject: [PATCH 08/35] docs: add examples for getResourceByPath --- packages/next-drupal/src/next-drupal.ts | 27 +++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 3227443c..c7df68e8 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -431,9 +431,32 @@ export class NextDrupal extends NextDrupalBase { /** * Fetches a resource of the specified type by its path. * - * @param {string} path The path of the resource. - * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. + * @param {string} path The path of the resource. Example: `/blog/slug-for-article`. + * @param { { isVersionable?: boolean } & JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. + * - isVersionable: Set to true if you're fetching the revision for a resource. Automatically set to true for node entity types * @returns {Promise} The fetched resource. + * @requires Decoupled Router module + * @example + * Get a page by path + * ``` + * const node = await drupal.getResourceByPath("/blog/slug-for-article") + * ``` + * Get the raw JSON:API response + * ``` + * const { data, meta, links } = await drupal.getResourceByPath( + * "/blog/slug-for-article", + * { + * deserialize: false, + * } + * ) + *``` + * Using DrupalNode for a node entity type + * ``` + * import { DrupalNode } from "next-drupal" + * const node = await drupal.getResourceByPath( + * "/blog/slug-for-article" + * ) + * ``` */ async getResourceByPath( path: string, From d666e5f850dc6a9afec9f22b79c44dc9eea0767c Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Mon, 27 Jan 2025 15:06:17 -0600 Subject: [PATCH 09/35] docs: add reference examples to getResourceCollection --- .gitignore | 1 + packages/next-drupal/src/next-drupal.ts | 27 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 16411a16..2c64cc75 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ yarn-error.log* /drupal/web/phpunit.xml /drupal/web/sites/default/settings.local.php /drupal-* +/local-next-drupal diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index c7df68e8..14b12d4d 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -571,9 +571,34 @@ export class NextDrupal extends NextDrupalBase { /** * Fetches a collection of resources of the specified type. * - * @param {string} type The type of the resources. + * @param {string} type The type of the resources. Example: `node--article` or `user--user`. * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. + * - deserialize: Set to false to return the raw JSON:API response * @returns {Promise} The fetched collection of resources. + * @example + * Get all articles + * ``` + * const articles = await drupal.getResourceCollection("node--article") + * ``` + * Using filters + * ``` + * const publishedArticles = await drupal.getResourceCollection("node--article", { + * params: { + * "filter[status]": "1", + * }, + * }) + * ``` + * Get the raw JSON:API response + * ``` + * const { data, meta, links } = await drupal.getResourceCollection("node--page", { + * deserialize: false, + * }) + * ``` + * Using TypeScript with DrupalNode for a node entity type + * ``` + * import { DrupalNode } from "next-drupal" + * const nodes = await drupal.getResourceCollection("node--article") + * ``` */ async getResourceCollection( type: string, From b06eb648999b90688ac77a43a15b4f0e59f19e89 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Mon, 27 Jan 2025 15:19:11 -0600 Subject: [PATCH 10/35] docs: add reference example for getResourceCollectionFromContext --- packages/next-drupal/src/next-drupal-pages.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index 821d4b8e..e317b9b1 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -258,10 +258,37 @@ export class NextDrupalPages extends NextDrupal { /** * Gets a collection of resources from the context. * - * @param {string} type The type of the resources. - * @param {GetStaticPropsContext} context The static props context. + * @param {string} type The type of the resources. Example: `node--article` or `user--user`. + * @param {GetStaticPropsContext} context The static props context from getStaticProps or getServerSideProps. * @param {Object} options Options for the request. + * - deserialize: Set to false to return the raw JSON:API response * @returns {Promise} The fetched collection of resources. + * @remarks + * The localized resources will be fetched based on the `locale` and `defaultLocale` values from `context`. + * @example + * Get all articles from context + * ``` + * export async function getStaticProps(context) { + * const articles = await drupal.getResourceCollectionFromContext( + * "node--article", + * context + * ) + * + * return { + * props: { + * articles, + * }, + * } + * } + * ``` + * Using TypeScript with DrupalNode for a node entity type + * ``` + * import { DrupalNode } from "next-drupal" + * const nodes = await drupal.getResourceCollectionFromContext( + * "node--article", + * context + * ) + * ``` */ async getResourceCollectionFromContext( type: string, From af6086a666d52ae8e25213ef78d40782e366f02f Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Mon, 27 Jan 2025 15:56:28 -0600 Subject: [PATCH 11/35] docs: add reference examples to next-drupal createResource --- packages/next-drupal/src/next-drupal.ts | 80 ++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 14b12d4d..7ff87314 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -93,10 +93,84 @@ export class NextDrupal extends NextDrupalBase { /** * Creates a new resource of the specified type. * - * @param {string} type The type of the resource. - * @param {JsonApiCreateResourceBody} body The body of the resource. - * @param {JsonApiOptions} options Options for the request. + * @param {string} type The type of the resource. Example: `node--article`, `taxonomy_term--tags`, or `block_content--basic`. + * @param {JsonApiCreateResourceBody} body The body payload with data. + * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} The created resource. + * @example + * Create a node--page resource + * ``` + * const page = await drupal.createResource("node--page", { + * data: { + * attributes: { + * title: "Page Title", + * body: { + * value: "

Content of body field

", + * format: "full_html", + * }, + * }, + * }, + * }) + * ``` + * Create a node--article with a taxonomy term + * ``` + * const article = await drupal.createResource("node--article", { + * data: { + * attributes: { + * title: "Title of Article", + * body: { + * value: "

Content of body field

", + * format: "full_html", + * }, + * }, + * relationships: { + * field_category: { + * data: { + * type: "taxonomy_term--category", + * id: "28ab9f26-927d-4e33-9510-b59a7ccdafe6", + * }, + * }, + * }, + * }, + * }) + * ``` + * Using filters + * ``` + * const page = await drupal.createResource( + * "node--page", + * { + * data: { + * attributes: { + * title: "Page Title", + * body: { + * value: "

Content of body field

", + * format: "full_html", + * }, + * }, + * }, + * }, + * { + * params: { + * "fields[node--page]": "title,path", + * }, + * } + * ) + * ``` + * Using TypeScript with DrupalNode + * ``` + * import { DrupalNode } from "next-drupal" + * const page = await drupal.createResource("node--page", { + * data: { + * attributes: { + * title: "Page Title", + * body: { + * value: "

Content of body field

", + * format: "full_html", + * }, + * }, + * }, + * }) + * ``` */ async createResource( type: string, From 9000395e66ef4fc0f8c5d3dc131306bba1e1def4 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 08:08:11 -0600 Subject: [PATCH 12/35] docs: add reference examples to createFileResource --- packages/next-drupal/src/next-drupal.ts | 41 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 7ff87314..3c684118 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -216,10 +216,47 @@ export class NextDrupal extends NextDrupalBase { /** * Creates a new file resource for the specified media type. * - * @param {string} type The type of the media. - * @param {JsonApiCreateFileResourceBody} body The body of the file resource. + * @param {string} type The type of the resource. In most cases this is `file--file`. + * @param {JsonApiCreateFileResourceBody} body The body payload with data. + * - type: The resource type of the host entity. Example: `media--image` + * - field: The name of the file field on the host entity. Example: `field_media_image` + * - filename: The name of the file with extension. Example: `avatar.jpg` + * - file: The file as a Buffer * @param {JsonApiOptions} options Options for the request. * @returns {Promise} The created file resource. + * @example + * Create a file resource for a media--image entity + * ```ts + * const file = await drupal.createFileResource("file--file", { + * data: { + * attributes: { + * type: "media--image", // The type of the parent resource + * field: "field_media_image", // The name of the field on the parent resource + * filename: "filename.jpg", + * file: await fs.readFile("/path/to/file.jpg"), + * }, + * }, + * }) + * ``` + * + * You can then use this to create a new media--image with a relationship to the file: + * ```ts + * const media = await drupal.createResource("media--image", { + * data: { + * attributes: { + * name: "Name for the media", + * }, + * relationships: { + * field_media_image: { + * data: { + * type: "file--file", + * id: file.id, + * }, + * }, + * }, + * }, + * }) + * ``` */ async createFileResource( type: string, From a01008b4c821b244c63cb672aff0daa0291bcc02 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 08:17:51 -0600 Subject: [PATCH 13/35] docs: add reference examples to updateResource --- packages/next-drupal/src/next-drupal.ts | 40 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 3c684118..39a700e0 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -306,11 +306,43 @@ export class NextDrupal extends NextDrupalBase { /** * Updates an existing resource of the specified type. * - * @param {string} type The type of the resource. - * @param {string} uuid The UUID of the resource. - * @param {JsonApiUpdateResourceBody} body The body of the resource. - * @param {JsonApiOptions} options Options for the request. + * @param {string} type The type of the resource. Example: `node--article`, `taxonomy_term--tags`, or `block_content--basic`. + * @param {string} uuid The resource id. Example: `a50ffee7-ba94-46c9-9705-f9f8f440db94`. + * @param {JsonApiUpdateResourceBody} body The body payload with data. + * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} The updated resource. + * @example + * Update a node--page resource + * ```ts + * const page = await drupal.updateResource( + * "node--page", + * "a50ffee7-ba94-46c9-9705-f9f8f440db94", + * { + * data: { + * attributes: { + * title: "Updated Title", + * }, + * }, + * } + * ) + * ``` + * + * Using TypeScript with DrupalNode for a node entity type + * ```ts + * import { DrupalNode } from "next-drupal" + * + * const page = await drupal.updateResource( + * "node--page", + * "a50ffee7-ba94-46c9-9705-f9f8f440db94", + * { + * data: { + * attributes: { + * title: "Updated Title", + * }, + * }, + * } + * ) + * ``` */ async updateResource( type: string, From f763ddc8643342e1c4707bd9ff9834f8f4c0e168 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 08:21:50 -0600 Subject: [PATCH 14/35] docs: add reference examples to deleteResource --- packages/next-drupal/src/next-drupal.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 39a700e0..b4920ec6 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -391,10 +391,18 @@ export class NextDrupal extends NextDrupalBase { /** * Deletes an existing resource of the specified type. * - * @param {string} type The type of the resource. - * @param {string} uuid The UUID of the resource. - * @param {JsonApiOptions} options Options for the request. + * @param {string} type The type of the resource. Example: `node--article`, `taxonomy_term--tags`, or `block_content--basic`. + * @param {string} uuid The resource id. Example: `a50ffee7-ba94-46c9-9705-f9f8f440db94`. + * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} True if the resource was deleted, false otherwise. + * @example + * Delete a node--page resource + * ```ts + * const isDeleted = await drupal.deleteResource( + * "node--page", + * "a50ffee7-ba94-46c9-9705-f9f8f440db94" + * ) + * ``` */ async deleteResource( type: string, From 2e8d778e68a063f61e414dd21c58c5f412854fae Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 08:27:25 -0600 Subject: [PATCH 15/35] docs: add reference examples to getStaticPathsFromContext --- packages/next-drupal/src/next-drupal-pages.ts | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index e317b9b1..407eacce 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -381,10 +381,33 @@ export class NextDrupalPages extends NextDrupal { /** * Gets static paths from the context. * - * @param {string | string[]} types The types of the resources. - * @param {GetStaticPathsContext} context The static paths context. - * @param {Object} options Options for the request. - * @returns {Promise["paths"]>} The fetched static paths. + * @param {string | string[]} types The resource types. Example: `node--article` or `["taxonomy_term--tags", "user--user"]`. + * @param {GetStaticPathsContext} context The context from `getStaticPaths`. + * @param {object} options Options for the request. + * @returns {Promise["paths"]>} The static paths. + * @example + * Return static paths for `node--page` resources + * ```ts + * export async function getStaticPaths(context) { + * return { + * paths: await drupal.getStaticPathsFromContext("node--page", context), + * fallback: "blocking", + * } + * } + * ``` + * + * Return static paths for `node--page` and `node--article` resources + * ```ts + * export async function getStaticPaths(context) { + * return { + * paths: await drupal.getStaticPathsFromContext( + * ["node--page", "node--article"], + * context + * ), + * fallback: "blocking", + * } + * } + * ``` */ async getStaticPathsFromContext( types: string | string[], From 4c5f95ab32ed296f0d7fca4941be15e2f26d8690 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:02:23 -0600 Subject: [PATCH 16/35] docs: add reference examples to translatePath --- packages/next-drupal/src/next-drupal.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index b4920ec6..a533daf9 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -908,9 +908,15 @@ export class NextDrupal extends NextDrupalBase { /** * Translates a path to a DrupalTranslatedPath object. * - * @param {string} path The path to translate. + * @param {string} path The resource path. Example: `/blog/slug-for-article`. * @param {JsonApiWithAuthOption & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} The translated path. + * @requires Decoupled Router module + * @example + * Get info about a `/blog/slug-for-article` path + * ```ts + * const path = await drupal.translatePath("/blog/slug-for-article") + * ``` */ async translatePath( path: string, From cbe0eab53ec734f3ad00a3d3c93b6ce78cee8680 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:09:22 -0600 Subject: [PATCH 17/35] docs: add reference examples to translatePathFromContext --- packages/next-drupal/src/next-drupal-pages.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index 407eacce..71ec3951 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -333,9 +333,17 @@ export class NextDrupalPages extends NextDrupal { /** * Translates a path from the context. * - * @param {GetStaticPropsContext} context The static props context. + * @param {GetStaticPropsContext} context The context from `getStaticProps` or `getServerSideProps`. * @param {Object} options Options for the request. * @returns {Promise} The translated path. + * @requires Decoupled Router module + * @example + * Get info about a path from `getStaticProps` context + * ```ts + * export async function getStaticProps(context) { + * const path = await drupal.translatePathFromContext(context) + * } + * ``` */ async translatePathFromContext( context: GetStaticPropsContext, From 616214389406a4416d7f858a7a1fa6de6a6b3428 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:13:04 -0600 Subject: [PATCH 18/35] docs: add reference examples to getPathFromContext --- packages/next-drupal/src/next-drupal-pages.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index 71ec3951..8d1d6e12 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -365,11 +365,18 @@ export class NextDrupalPages extends NextDrupal { } /** - * Gets the path from the context. + * Return the path (slug) from getStaticProps or getServerSideProps context. * - * @param {GetStaticPropsContext} context The static props context. + * @param {GetStaticPropsContext} context The context from `getStaticProps` or `getServerSideProps`. * @param {Object} options Options for the request. * @returns {string} The constructed path. + * @example + * Get the path (slug) from `getStaticProps` context + * ```ts + * export async function getStaticProps(context) { + * const slug = await drupal.getPathFromContext(context) + * } + * ``` */ getPathFromContext( context: GetStaticPropsContext, From 92d936e5a69ca9d03dba94171b7fc503395540a9 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:15:04 -0600 Subject: [PATCH 19/35] docs: add reference examples for getEntryForResourceType --- packages/next-drupal/src/next-drupal-pages.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index 8d1d6e12..e8b27450 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -64,11 +64,29 @@ export class NextDrupalPages extends NextDrupal { } /** - * Gets the entry point for a given resource type. + * Get the JSON:API entry for a resource type. * - * @param {string} resourceType The resource type. - * @param {Locale} locale The locale. + * @param {string} resourceType The resource type. Example: `node--article`. + * @param {Locale} locale Optional. The locale to fetch the index. Example: `es` or `fr`. * @returns {Promise} The entry point URL. + * @remarks + * By default, when retrieving resources in `getResource` or `getResourceCollection`, + * the `DrupalClient` make a request to Drupal to fetch the JSON:API resource entry. + * + * Example: if you provide `node--article`, `DrupalClient` will make a request to + * `http://example.com/jsonapi/node/article`. + * + * If you would like to infer the entry from the resource type, use the useDefaultResourceTypeEntry option: + * ```ts + * const drupal = new DrupalClient(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL, { + * useDefaultResourceTypeEntry: true, + * }) + * ``` + * @example + * ```ts + * // https://example.com/jsonapi/node/article + * const url = await drupal.getEntryForResourceType(`node--article`) + * ``` */ async getEntryForResourceType( resourceType: string, From e16c9761669f27245113c05528315e93ecb85b2d Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:18:43 -0600 Subject: [PATCH 20/35] docs: add reference examples for preview method --- packages/next-drupal/src/next-drupal-pages.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-pages.ts b/packages/next-drupal/src/next-drupal-pages.ts index e8b27450..03f581c5 100644 --- a/packages/next-drupal/src/next-drupal-pages.ts +++ b/packages/next-drupal/src/next-drupal-pages.ts @@ -576,11 +576,33 @@ export class NextDrupalPages extends NextDrupal { } /** - * Handles preview mode. + * Handle preview mode for resources. * - * @param {NextApiRequest} request The API request. - * @param {NextApiResponse} response The API response. + * @param {NextApiRequest} request The `request` from an API route. + * @param {NextApiResponse} response The `response` from an API route. * @param {Object} options Options for the request. + * @returns {Promise} + * @remarks + * The `preview` method should be called in an API route. + * Remember to set a `previewSecret` on the client. + * ```ts + * // lib/drupal.ts + * export const drupal = new DrupalClient( + * process.env.NEXT_PUBLIC_DRUPAL_BASE_URL, + * { + * previewSecret: process.env.DRUPAL_PREVIEW_SECRET, + * } + * ) + * ``` + * @example + * ```ts + * // pages/api/preview.ts + * import { drupal } from "lib/drupal" + * + * export default async function handler(req, res) { + * return await drupal.preview(req, res) + * } + * ``` */ async preview( request: NextApiRequest, From 26582a5a42026dd7af451f5de3a7e296904546b7 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:37:25 -0600 Subject: [PATCH 21/35] docs: add reference examples to getAccessToken --- packages/next-drupal/src/next-drupal-base.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/next-drupal/src/next-drupal-base.ts b/packages/next-drupal/src/next-drupal-base.ts index 5d9db158..300f6e87 100644 --- a/packages/next-drupal/src/next-drupal-base.ts +++ b/packages/next-drupal/src/next-drupal-base.ts @@ -407,10 +407,19 @@ export class NextDrupalBase { } /** - * Gets an access token using the provided client ID and secret. + * Retrieve an access token. * * @param {NextDrupalAuthClientIdSecret} clientIdSecret The client ID and secret. * @returns {Promise} The access token. + * @remarks + * If options is not provided, `DrupalClient` will use the `clientId` and `clientSecret` configured in `auth`. + * @example + * ```ts + * const accessToken = await drupal.getAccessToken({ + * clientId: "7034f4db-7151-466f-a711-8384bddb9e60", + * clientSecret: "d92Fm^ds", + * }) + * ``` */ async getAccessToken( clientIdSecret?: NextDrupalAuthClientIdSecret From a7c852dbc964f2c4197f58dc187d39bf3e798c16 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:41:46 -0600 Subject: [PATCH 22/35] docs: add reference examples to getMenu --- packages/next-drupal/src/next-drupal.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index a533daf9..929ef1f9 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -1058,9 +1058,25 @@ export class NextDrupal extends NextDrupalBase { /** * Fetches a menu by its name. * - * @param {string} menuName The name of the menu. + * @param {string} menuName The name of the menu. Example: `main` or `footer`. * @param {JsonApiOptions & JsonApiWithCacheOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise<{ items: T[], tree: T[] }>} The fetched menu. + * - items: An array of `DrupalMenuLinkContent` + * - tree: An array of `DrupalMenuLinkContent` with children nested to match the hierarchy from Drupal + * @requires JSON:API Menu Items module + * @example + * Get the `main` menu + * ```ts + * const { menu, items } = await drupal.getMenu("main") + * ``` + * + * Get the `main` menu using cache + * ```ts + * const menu = await drupal.getMenu("main", { + * withCache: true, + * cacheKey: "menu--main", + * }) + * ``` */ async getMenu( menuName: string, From 7a615755dd53b028af5dce815b6c01ab0091aaec Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:43:33 -0600 Subject: [PATCH 23/35] docs: add reference examples to getView --- packages/next-drupal/src/next-drupal.ts | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index 929ef1f9..e2a14950 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -1147,9 +1147,33 @@ export class NextDrupal extends NextDrupalBase { /** * Fetches a view by its name. * - * @param {string} name The name of the view. + * @param {string} name The name of the view and the display id. Example: `articles--promoted`. * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise>} The fetched view. + * @requires JSON:API Views module + * @example + * Get a view named `articles` and display id `promoted` + * ```ts + * const view = await drupal.getView("articles--promoted") + * ``` + * + * Using sparse fieldsets to only fetch the title and body fields + * ```ts + * const view = await drupal.getView("articles--promoted", { + * params: { + * fields: { + * "node--article": "title,body", + * }, + * }, + * }) + * ``` + * + * Using TypeScript with DrupalNode for a node entity type + * ```ts + * import { DrupalNode } from "next-drupal" + * + * const view = await drupal.getView("articles--promoted") + * ``` */ async getView( name: string, From 20159205299eee27e94fdc2689963a867df46bc1 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:47:49 -0600 Subject: [PATCH 24/35] docs: add reference examples to getSearchIndex --- packages/next-drupal/src/next-drupal.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index e2a14950..b370e7c5 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -1223,6 +1223,19 @@ export class NextDrupal extends NextDrupalBase { * @param {string} name The name of the search index. * @param {JsonApiOptions & JsonApiWithNextFetchOptions} options Options for the request. * @returns {Promise} The fetched search index. + * @requires JSON:API Search API module + * @example + * Get search results from an index named `articles` + * ```ts + * const results = await drupal.getSearchIndex("articles") + * ``` + * + * Using TypeScript with DrupalNode for a node entity type + * ```ts + * import { DrupalNode } from "next-drupal" + * + * const results = await drupal.getSearchIndex("articles") + * ``` */ async getSearchIndex( name: string, From 63d1e3b74862fe95a9228a64c6e5e5c440aed152 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:53:39 -0600 Subject: [PATCH 25/35] docs: add reference examples to buildUrl --- packages/next-drupal/src/next-drupal-base.ts | 30 ++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-base.ts b/packages/next-drupal/src/next-drupal-base.ts index 300f6e87..74613694 100644 --- a/packages/next-drupal/src/next-drupal-base.ts +++ b/packages/next-drupal/src/next-drupal-base.ts @@ -269,9 +269,35 @@ export class NextDrupalBase { /** * Builds a URL with the given path and search parameters. * - * @param {string} path The URL path. - * @param {EndpointSearchParams} searchParams The search parameters. + * @param {string} path The path for the url. Example: "/example" + * @param {string | Record | URLSearchParams | JsonApiParams} searchParams Optional query parameters. * @returns {URL} The constructed URL. + * @example + * ```ts + * const drupal = new DrupalClient("https://example.com") + * + * // https://drupal.org + * drupal.buildUrl("https://drupal.org").toString() + * + * // https://example.com/foo + * drupal.buildUrl("/foo").toString() + * + * // https://example.com/foo?bar=baz + * client.buildUrl("/foo", { bar: "baz" }).toString() + * ``` + * + * Build a URL from `DrupalJsonApiParams` + * ```ts + * const params = { + * getQueryObject: () => ({ + * sort: "-created", + * "fields[node--article]": "title,path", + * }), + * } + * + * // https://example.com/jsonapi/node/article?sort=-created&fields%5Bnode--article%5D=title%2Cpath + * drupal.buildUrl("/jsonapi/node/article", params).toString() + * ``` */ buildUrl(path: string, searchParams?: EndpointSearchParams): URL { const url = new URL(path, this.baseUrl) From ace7dac88a912ba18fd2b377cc0fe3db5e7fb3cf Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:56:56 -0600 Subject: [PATCH 26/35] docs: add reference examples to fetch --- packages/next-drupal/src/next-drupal-base.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/next-drupal/src/next-drupal-base.ts b/packages/next-drupal/src/next-drupal-base.ts index 74613694..401929ef 100644 --- a/packages/next-drupal/src/next-drupal-base.ts +++ b/packages/next-drupal/src/next-drupal-base.ts @@ -179,9 +179,21 @@ export class NextDrupalBase { /** * Fetches a resource from the given input URL or path. * - * @param {RequestInfo} input The input URL or path. - * @param {FetchOptions} init The fetch options. + * @param {RequestInfo} input The url to fetch from. + * @param {FetchOptions} init The fetch options with `withAuth`. + * If `withAuth` is set, `fetch` will fetch an `Authorization` header before making the request. * @returns {Promise} The fetch response. + * @remarks + * To provide your own custom fetcher, see the fetcher docs. + * @example + * ```ts + * const url = drupal.buildUrl("/jsonapi/node/article", { + * sort: "-created", + * "fields[node--article]": "title,path", + * }) + * + * const response = await drupal.fetch(url.toString()) + * ``` */ async fetch( input: RequestInfo, From 4d27b1510fd3b3ec56402429034a5b6a5ce06647 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 29 Jan 2025 09:59:31 -0600 Subject: [PATCH 27/35] docs: add reference examples for deserialize --- packages/next-drupal/src/next-drupal.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/next-drupal/src/next-drupal.ts b/packages/next-drupal/src/next-drupal.ts index b370e7c5..6bb205ae 100644 --- a/packages/next-drupal/src/next-drupal.ts +++ b/packages/next-drupal/src/next-drupal.ts @@ -1278,6 +1278,20 @@ export class NextDrupal extends NextDrupalBase { * @param {any} body The response body. * @param {any} options Options for deserialization. * @returns {any} The deserialized response body. + * @remarks + * To provide your own custom deserializer, see the serializer docs. + * @example + * ```ts + * const url = drupal.buildUrl("/jsonapi/node/article", { + * sort: "-created", + * "fields[node--article]": "title,path", + * }) + * + * const response = await drupal.fetch(url.toString()) + * const json = await response.json() + * + * const resource = drupal.deserialize(json) + * ``` */ deserialize(body, options?) { if (!body) return null From 8a8d7620363a86f4b44cbfe8d4963d6bcd6a5016 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Thu, 30 Jan 2025 09:59:11 -0600 Subject: [PATCH 28/35] docs: update 2.0 announcement, including contributors --- www/content/blog/next-drupal-2-0.mdx | 42 ++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/www/content/blog/next-drupal-2-0.mdx b/www/content/blog/next-drupal-2-0.mdx index d84fda52..17e18f59 100644 --- a/www/content/blog/next-drupal-2-0.mdx +++ b/www/content/blog/next-drupal-2-0.mdx @@ -1,8 +1,8 @@ --- title: Next-Drupal 2.0 author: brianperry -date: January 10, 2025 -created: "2025-01-25" +date: February 11, 2025 +created: "2025-02-11" excerpt: Next.js 15, Drupal 11 and App Router Support. published: true --- @@ -15,7 +15,7 @@ As part of our App Router support we've also added **tag-based invalidation** wh ## App Router -Next-Drupal 2.0 uses the App Router by default to match Next.js, but we will continue to provide support for the Pages Router. +Next-Drupal 2.0 uses the App Router by default to match Next.js defaults, but we will continue to provide support for the Pages Router. ### Starters @@ -57,12 +57,14 @@ import { NextDrupalPages } from "next-drupal" const drupal = new NextDrupalPages("https://example.com") ``` -Backwards compatibility is also provided for instances of `DrupalClient` which will continue to work with the Pages Router. +Backwards compatibility is also provided for instances of `DrupalClient`, which will continue to work with the Pages Router. ### Documentation Our documentation has been updated to reflect these changes and new features. All examples in our documentation now assume the App Router by default, but call out any Page Router specific differences where applicable. +We have also begun publishing the `canary` version of our documentation at [https://next.next-drupal.org/](https://next.next-drupal.org/). This will allways represent the pre-release version of our documentation and can be used by anyone testing upcoming features. + --- ## Tag-based Invalidation @@ -99,7 +101,7 @@ This configuration can provide much more targeted cache invalidation than path b We have updated all of the next-drupal starters (JSON:API and GraphQL) to Next.js 15. -The next module has been updated to Drupal 11. We've removed all deprecated code and APIs. +The Next Drupal module has been updated to support Drupal 11. We've removed all deprecated code and APIs. Following end-of-life for the projects themselves, we no longer officially support Next.js 13 or Drupal 9. @@ -108,3 +110,33 @@ Following end-of-life for the projects themselves, we no longer officially suppo ## Upgrading You can upgrade to `2.0` by following our upgrade guide [here](/docs/upgrade-guide). + +--- + +## Thanks to Our Contributors + +A special shout out goes to [John Albin](https://www.drupal.org/u/johnalbin) who lead the initial efforts for our 2.x release and App Router support. + +We'd also like to welcome [Mario Vercellotti](https://www.drupal.org/u/vermario) ([Wunder](https://wunder.io/)) and [Ankit Pathak](https://www.drupal.org/u/ankitv18) ([Acquia](https://www.acquia.com/)) who have joined as additional Next.js for Drupal maintainers. + +Finally, a huge thanks to all who have contributed to this release (yes, even you dependabot)! + +| [shadcn](https://github.com/shadcn) | [JohnAlbin](https://github.com/JohnAlbin) | [dependabot[bot]](https://github.com/apps/dependabot) | [robdecker](https://github.com/robdecker) | [backlineint](https://github.com/backlineint) | [theRuslan](https://github.com/theRuslan) | +| :--------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | +| [shadcn](https://github.com/shadcn) | [JohnAlbin](https://github.com/JohnAlbin) | [dependabot[bot]](https://github.com/apps/dependabot) | [robdecker](https://github.com/robdecker) | [backlineint](https://github.com/backlineint) | [theRuslan](https://github.com/theRuslan) | + +| [mglaman](https://github.com/mglaman) | [apathak18](https://github.com/apathak18) | [cruno91](https://github.com/cruno91) | [eiriksm](https://github.com/eiriksm) | [MontiMarco92](https://github.com/MontiMarco92) | [blyme](https://github.com/blyme) | +| :-----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | +| [mglaman](https://github.com/mglaman) | [apathak18](https://github.com/apathak18) | [cruno91](https://github.com/cruno91) | [eiriksm](https://github.com/eiriksm) | [MontiMarco92](https://github.com/MontiMarco92) | [blyme](https://github.com/blyme) | + +| [vattenapa](https://github.com/vattenapa) | [fiasco](https://github.com/fiasco) | [Elendev](https://github.com/Elendev) | [joshirohit100](https://github.com/joshirohit100) | [devtim123](https://github.com/devtim123) | [rajeshreeputra](https://github.com/rajeshreeputra) | +| :----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | +| [vattenapa](https://github.com/vattenapa) | [fiasco](https://github.com/fiasco) | [Elendev](https://github.com/Elendev) | [joshirohit100](https://github.com/joshirohit100) | [devtim123](https://github.com/devtim123) | [rajeshreeputra](https://github.com/rajeshreeputra) | + +| [yobottehg](https://github.com/yobottehg) | [PierreRe](https://github.com/PierreRe) | [minnur](https://github.com/minnur) | [lauriii](https://github.com/lauriii) | [eojthebrave](https://github.com/eojthebrave) | [Cryt1c](https://github.com/Cryt1c) | +| :----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | +| [yobottehg](https://github.com/yobottehg) | [PierreRe](https://github.com/PierreRe) | [minnur](https://github.com/minnur) | [lauriii](https://github.com/lauriii) | [eojthebrave](https://github.com/eojthebrave) | [Cryt1c](https://github.com/Cryt1c) | + +| [2dareis2do](https://github.com/2dareis2do) | [brentrobbins](https://github.com/brentrobbins) | [bojanbogdanovic](https://github.com/bojanbogdanovic) | [webchick](https://github.com/webchick) | [apmsooner](https://github.com/apmsooner) | [alex-ahumada](https://github.com/alex-ahumada) | +| :-----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: | +| [2dareis2do](https://github.com/2dareis2do) | [brentrobbins](https://github.com/brentrobbins) | [bojanbogdanovic](https://github.com/bojanbogdanovic) | [webchick](https://github.com/webchick) | [apmsooner](https://github.com/apmsooner) | [alex-ahumada](https://github.com/alex-ahumada) | From b0633e4ce244a882066badc978b97aac2b352e51 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Thu, 30 Jan 2025 10:10:33 -0600 Subject: [PATCH 29/35] docs: correctly filter list of contributors in 2.x announcement --- www/content/blog/next-drupal-2-0.mdx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/www/content/blog/next-drupal-2-0.mdx b/www/content/blog/next-drupal-2-0.mdx index 17e18f59..8a9f2009 100644 --- a/www/content/blog/next-drupal-2-0.mdx +++ b/www/content/blog/next-drupal-2-0.mdx @@ -121,6 +121,12 @@ We'd also like to welcome [Mario Vercellotti](https://www.drupal.org/u/vermario) Finally, a huge thanks to all who have contributed to this release (yes, even you dependabot)! + + | [shadcn](https://github.com/shadcn) | [JohnAlbin](https://github.com/JohnAlbin) | [dependabot[bot]](https://github.com/apps/dependabot) | [robdecker](https://github.com/robdecker) | [backlineint](https://github.com/backlineint) | [theRuslan](https://github.com/theRuslan) | | :--------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | | [shadcn](https://github.com/shadcn) | [JohnAlbin](https://github.com/JohnAlbin) | [dependabot[bot]](https://github.com/apps/dependabot) | [robdecker](https://github.com/robdecker) | [backlineint](https://github.com/backlineint) | [theRuslan](https://github.com/theRuslan) | @@ -129,14 +135,10 @@ Finally, a huge thanks to all who have contributed to this release (yes, even yo | :-----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | | [mglaman](https://github.com/mglaman) | [apathak18](https://github.com/apathak18) | [cruno91](https://github.com/cruno91) | [eiriksm](https://github.com/eiriksm) | [MontiMarco92](https://github.com/MontiMarco92) | [blyme](https://github.com/blyme) | -| [vattenapa](https://github.com/vattenapa) | [fiasco](https://github.com/fiasco) | [Elendev](https://github.com/Elendev) | [joshirohit100](https://github.com/joshirohit100) | [devtim123](https://github.com/devtim123) | [rajeshreeputra](https://github.com/rajeshreeputra) | -| :----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | -| [vattenapa](https://github.com/vattenapa) | [fiasco](https://github.com/fiasco) | [Elendev](https://github.com/Elendev) | [joshirohit100](https://github.com/joshirohit100) | [devtim123](https://github.com/devtim123) | [rajeshreeputra](https://github.com/rajeshreeputra) | - -| [yobottehg](https://github.com/yobottehg) | [PierreRe](https://github.com/PierreRe) | [minnur](https://github.com/minnur) | [lauriii](https://github.com/lauriii) | [eojthebrave](https://github.com/eojthebrave) | [Cryt1c](https://github.com/Cryt1c) | -| :----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | -| [yobottehg](https://github.com/yobottehg) | [PierreRe](https://github.com/PierreRe) | [minnur](https://github.com/minnur) | [lauriii](https://github.com/lauriii) | [eojthebrave](https://github.com/eojthebrave) | [Cryt1c](https://github.com/Cryt1c) | +| [fiasco](https://github.com/fiasco) | [rajeshreeputra](https://github.com/rajeshreeputra) | [yobottehg](https://github.com/yobottehg) | [PierreRe](https://github.com/PierreRe) | [minnur](https://github.com/minnur) | [bojanbogdanovic](https://github.com/bojanbogdanovic) | +| :--------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------: | +| [fiasco](https://github.com/fiasco) | [rajeshreeputra](https://github.com/rajeshreeputra) | [yobottehg](https://github.com/yobottehg) | [PierreRe](https://github.com/PierreRe) | [minnur](https://github.com/minnur) | [bojanbogdanovic](https://github.com/bojanbogdanovic) | -| [2dareis2do](https://github.com/2dareis2do) | [brentrobbins](https://github.com/brentrobbins) | [bojanbogdanovic](https://github.com/bojanbogdanovic) | [webchick](https://github.com/webchick) | [apmsooner](https://github.com/apmsooner) | [alex-ahumada](https://github.com/alex-ahumada) | -| :-----------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: | -| [2dareis2do](https://github.com/2dareis2do) | [brentrobbins](https://github.com/brentrobbins) | [bojanbogdanovic](https://github.com/bojanbogdanovic) | [webchick](https://github.com/webchick) | [apmsooner](https://github.com/apmsooner) | [alex-ahumada](https://github.com/alex-ahumada) | +| [apmsooner](https://github.com/apmsooner) | +| :---------------------------------------------------------------------------------------------------------------------------------: | +| [apmsooner](https://github.com/apmsooner) | From 0e3158f792769e32b84c24564b882024a2f15114 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Fri, 31 Jan 2025 11:14:47 -0600 Subject: [PATCH 30/35] docs: update to umami demo guide --- www/content/guides/umami-demo.mdx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/www/content/guides/umami-demo.mdx b/www/content/guides/umami-demo.mdx index d1729ea6..f7b8044c 100644 --- a/www/content/guides/umami-demo.mdx +++ b/www/content/guides/umami-demo.mdx @@ -16,11 +16,8 @@ composer create-project drupal/recommended-project umami ```json "extra": { "patches": { - "drupal/subrequests": { - "Get same results on different request": "https://www.drupal.org/files/issues/2019-07-18/change_request_type-63049395-09.patch" - }, "drupal/decoupled_router": { - "Unable to resolve path on node in other language than default": "https://www.drupal.org/files/issues/2021-05-05/3111456-34.patch" + "Unable to resolve path on node in other language than default": "https://www.drupal.org/files/issues/2024-10-22/decoupled_router-3111456-resolve-langcode-issue-78--external-redirects.patch" } }, } @@ -29,7 +26,7 @@ composer create-project drupal/recommended-project umami 3. Add dependencies ```bash -composer require drupal/next drupal/jsonapi_menu_items drupal/jsonapi_views 'drupal/jsonapi_resources:^1.0@beta' +composer require drupal/next drupal/jsonapi_menu_items drupal/jsonapi_views drupal/jsonapi_resources ``` 4. Install Drupal. Make sure you select **Demo: Umami Food Magazine (Experimental)** as the installation profile. From e267f77f20ac854e0f0273bfb1556ef8edc7358c Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Mon, 3 Feb 2025 11:14:06 -0600 Subject: [PATCH 31/35] docs: minor updates to GraphQL auth entry --- www/content/tutorials/graphql/configure-authentication.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/content/tutorials/graphql/configure-authentication.mdx b/www/content/tutorials/graphql/configure-authentication.mdx index aad7a494..5bca6e61 100644 --- a/www/content/tutorials/graphql/configure-authentication.mdx +++ b/www/content/tutorials/graphql/configure-authentication.mdx @@ -60,10 +60,12 @@ openssl rsa -in private.key -pubout > public.key 2. Fill in the following values: - **Label**: `Next.js site` +- **Client ID**: `nextjs_site` - **User**: `Select the user we created` - **Secret**: `Your secret` +- **Grant Types**: `Select 'Client Credentials'` - **Scopes**: `Select the role we created` 3. Click **Save** -_Important: note the client id (uuid) and the secret. These are going to be used as environment variables for the Next.js site._ +_Important: note the client id and the secret. These are going to be used as environment variables for the Next.js site._ From 2ca5e46f1e755a8be26f0ea7c7393c9e73824ff8 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Tue, 4 Feb 2025 14:17:46 -0600 Subject: [PATCH 32/35] docs: remove todo --- www/content/tutorials/quick-start/apply-patches.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/content/tutorials/quick-start/apply-patches.mdx b/www/content/tutorials/quick-start/apply-patches.mdx index 8b836c1e..727f2f92 100644 --- a/www/content/tutorials/quick-start/apply-patches.mdx +++ b/www/content/tutorials/quick-start/apply-patches.mdx @@ -16,8 +16,6 @@ At the time of this writing, a patch is required in order to use multilingual fe 1. Open your Drupal `composer.json` file. 2. Add the following patch under `"extra"`. -TODO - validate that these patches are necessary and correct. - ```json title=composer.json "extra": { // highlight-start From 3da32e436d8727708750cbe9ce09deb604a3e301 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 5 Feb 2025 17:37:11 -0600 Subject: [PATCH 33/35] docs: updates to draft mode quick start --- .../draft-mode/configure-content-types.mdx | 8 +++-- .../draft-mode/create-oauth-client.mdx | 35 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/www/content/tutorials/draft-mode/configure-content-types.mdx b/www/content/tutorials/draft-mode/configure-content-types.mdx index 1bb3ab76..212ba4f6 100644 --- a/www/content/tutorials/draft-mode/configure-content-types.mdx +++ b/www/content/tutorials/draft-mode/configure-content-types.mdx @@ -22,9 +22,11 @@ A _site resolver_ tells Drupal how to resolve the draft URL for an entity. 1. Visit `/admin/config/services/next/entity-types` 2. Click **Configure entity type** 3. Select **Article** from the the entity type list -4. Select **Site selector** as the **Site resolver** -5. Select **your site** under **Next.js sites** -6. Click **Save** +4. Click **Save** +5. Edit the newly created entity type. +6. On the Draft Mode tab, select **Site selector** as the **plugin** +7. Select **your site** under **Next.js sites** +8. Click **Save** --- @@ -78,15 +80,32 @@ openssl rsa -in private.key -pubout > public.key --- -## 5. Create Consumer +## 5. Create Scopes + +1. Visit _/admin/config/people/simple_oauth/oauth2_scope/dynamic/add_ +2. Fill in the following values: + +- **Machine-readable Name**: `nextjs_site` +- **Description**: `Next.js Site` +- **Grant Types**: `Client Credentials` +- **Granularity**: `Role` +- **Role**: `Next.js Site` + +3. Click **Save** + +--- + +## 6. Create Consumer 1. Visit _/admin/config/services/consumer/add_ 2. Fill in the following values: - **Label**: `Next.js site` -- **User**: `Select the user we created` +- **Client ID**: `nextjs_site` (or generate a UUID is preferred) - **Secret**: `Your secret` -- **Scopes**: `Select the role we created` +- **Grant Types**: `Client Credentials` +- **Scopes**: `nextjs_site` +- **User**: `Select the user we created` 3. Click **Save** @@ -110,15 +129,19 @@ DRUPAL_CLIENT_SECRET= ## 7. Update NextDrupal client. -Update the `NextDrupal` to use the **Bearer** authentication header. +Update the `NextDrupal` client to use the **Bearer** authentication header. ```ts title=lib/drupal.ts import { NextDrupal } from "next-drupal" +const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string +const clientId = process.env.DRUPAL_CLIENT_ID as string +const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string + export const drupal = new NextDrupal(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL, { auth: { - clientId: process.env.DRUPAL_CLIENT_ID, - clientSecret: process.env.DRUPAL_CLIENT_SECRET, + clientId, + clientSecret, }, }) ``` From 35c5f6e81975fccd4e30c313f72f211dc12822be Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Wed, 5 Feb 2025 19:32:15 -0600 Subject: [PATCH 34/35] docs: tweaks to oauth client docs to allow for revisions --- www/content/tutorials/draft-mode/create-oauth-client.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/content/tutorials/draft-mode/create-oauth-client.mdx b/www/content/tutorials/draft-mode/create-oauth-client.mdx index 73f4fcc9..e9b983ba 100644 --- a/www/content/tutorials/draft-mode/create-oauth-client.mdx +++ b/www/content/tutorials/draft-mode/create-oauth-client.mdx @@ -32,6 +32,7 @@ Next, assign the following permissions to the newly created role. - Bypass content access control - Issue subrequests - View user information +- View all revisions @@ -57,7 +58,7 @@ const article = await drupal.getResource( ## 3. Create User -Add a new user at `/admin/people/create` and **assign them all the roles that are going to be used for scopes, including the administrator role and the role we created above**. +Add a new user at `/admin/people/create` and **assign them all the roles that are going to be used for scopes, including the role we created above**. --- @@ -143,5 +144,6 @@ export const drupal = new NextDrupal(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL, { clientId, clientSecret, }, + withAuth: true, }) ``` From 6783a368bd090c81667518d7cd705ea71e867289 Mon Sep 17 00:00:00 2001 From: Brian Perry Date: Fri, 7 Feb 2025 10:00:12 -0600 Subject: [PATCH 35/35] docs: updates to on-demand validation quick start --- .../configure-entity-types.mdx | 15 +++- .../configure-revalidate-route.mdx | 27 ++++-- .../on-demand-revalidation/index.mdx | 6 +- .../set-revalidation-parameters.mdx | 89 +++++++++++++++++++ 4 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 www/content/tutorials/on-demand-revalidation/set-revalidation-parameters.mdx diff --git a/www/content/tutorials/on-demand-revalidation/configure-entity-types.mdx b/www/content/tutorials/on-demand-revalidation/configure-entity-types.mdx index 3cbc8681..a74d0cb6 100644 --- a/www/content/tutorials/on-demand-revalidation/configure-entity-types.mdx +++ b/www/content/tutorials/on-demand-revalidation/configure-entity-types.mdx @@ -15,11 +15,22 @@ A _revalidator_ tells Drupal how to revalidate an entity when it is created, upd +### Configure Tag-based Revalidation + 1. Visit `/admin/config/services/next/entity-types` 2. Click **Configure entity type** or **Edit** an existing one 3. Click **On-demand Revalidation** -4. Select **Path** as the revalidator **Plugin** -5. Check **Revalidate page** +4. Select **Cache Tag** as the revalidator **Plugin** +5. If using the **Path** plugin, check the **Revalidate page** checkbox 6. Click **Save** +### Configure Path-based Revalidation + +7. Visit `/admin/config/services/next/entity-types` +8. Click **Configure entity type** or **Edit** an existing one +9. Click **On-demand Revalidation** +10. Select **Path** as the revalidator **Plugin** +11. Check the **Revalidate page** checkbox +12. Click **Save** + If you want to revalidate multiple pages, you can configure **Additional paths**. Example: you might want to revalidate the `/blog` landing page when an Article is created. diff --git a/www/content/tutorials/on-demand-revalidation/configure-revalidate-route.mdx b/www/content/tutorials/on-demand-revalidation/configure-revalidate-route.mdx index 303611bb..013b0fd3 100644 --- a/www/content/tutorials/on-demand-revalidation/configure-revalidate-route.mdx +++ b/www/content/tutorials/on-demand-revalidation/configure-revalidate-route.mdx @@ -7,21 +7,35 @@ group: On-demand Revalidation Implement on-demand revalidation using an API route at `/revalidate`. +## Update Environment Variables + +First, update `.env.local` to include the revalidate secret you specified. + +```txt title=.env.local +# Required for On-demand Revalidation +DRUPAL_REVALIDATE_SECRET=secret +``` + +## Create the Revalidate Route + +Next, create the API route that will be used for on-demand revalidation. + -If you're using the Basic Starter, revalidate route are already created for you. You can skip this step. +If you're using the Basic Starter, the revalidate route is already created for you. You can skip this step. ## /app/api/revalidate/route.ts ```ts title=app/api/revalidate/route.ts -import { revalidatePath } from "next/cache" +import { revalidatePath, revalidateTag } from "next/cache" import type { NextRequest } from "next/server" async function handler(request: NextRequest) { const searchParams = request.nextUrl.searchParams const path = searchParams.get("path") + const tags = searchParams.get("tags") const secret = searchParams.get("secret") // Validate secret. @@ -29,13 +43,14 @@ async function handler(request: NextRequest) { return new Response("Invalid secret.", { status: 401 }) } - // Validate path. - if (!path) { - return new Response("Invalid path.", { status: 400 }) + // Either tags or path must be provided. + if (!path && !tags) { + return new Response("Missing path or tags.", { status: 400 }) } try { - revalidatePath(path) + path && revalidatePath(path) + tags?.split(",").forEach((tag) => revalidateTag(tag)) return new Response("Revalidated.") } catch (error) { diff --git a/www/content/tutorials/on-demand-revalidation/index.mdx b/www/content/tutorials/on-demand-revalidation/index.mdx index 9165758a..bd69c1ec 100644 --- a/www/content/tutorials/on-demand-revalidation/index.mdx +++ b/www/content/tutorials/on-demand-revalidation/index.mdx @@ -7,8 +7,4 @@ group: On-demand Revalidation [On-demand Revalidation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation) makes it easy to update your Next.js site when content on Drupal is created, updated or deleted. -Starting with `v1.6.0`, Next.js for Drupal supports On-demand Revalidation configurable per entity types. - -TODO - in v2, Next.js for Drupal supports... -TODO - maybe this section should just be renamed to validation? -TODO - maybe move revalidation examples from Building Pages to this section... +Next.js for Drupal supports On-demand Revalidation configurable per entity types. You can choose to revalidate content by path, or by tag. Tag-based invalidation is new as of Next Drupal 2.0 and allows you to invalidate content on your Next.js site using Drupal's powerful cache tag system. diff --git a/www/content/tutorials/on-demand-revalidation/set-revalidation-parameters.mdx b/www/content/tutorials/on-demand-revalidation/set-revalidation-parameters.mdx new file mode 100644 index 00000000..b356b6a3 --- /dev/null +++ b/www/content/tutorials/on-demand-revalidation/set-revalidation-parameters.mdx @@ -0,0 +1,89 @@ +--- +title: Set Revalidation Parameters +excerpt: Set Revalidation Parameters When Fetching Data in Next.js +weight: 370 +group: On-demand Revalidation +--- + +When fetching data using the Next Drupal Client, you will also need to set the necessary revalidation parameters. This tells Next.js that the content should be cached, and what metadata can be used to invalidate that cache. + + + +If you're using the Basic Starter, most of this configuration is already created for you. You will only need to replace `revalidate` with `tags` in the options object when using Next Drupal Client methods like `getResource` if you'd prefer to use tag-based revalidation. + + + +Consider the `getNode` function in the `[...slug]` dynamic route in the basic starter: + +```ts title=app/[..slug]/page.tsx +async function getNode(slug: string[]) { + const path = `/${slug.join("/")}` + + const params: JsonApiParams = {} + + // Translating the path also allows us to discover the entity type. + const translatedPath = await drupal.translatePath(path) + + if (!translatedPath) { + throw new Error("Resource not found", { cause: "NotFound" }) + } + + const type = translatedPath.jsonapi?.resourceName! + const uuid = translatedPath.entity.uuid + const tag = `${translatedPath.entity.type}:${translatedPath.entity.id}` + + if (type === "node--article") { + params.include = "field_image,uid" + } + + const resource = await drupal.getResource(type, uuid, { + params, + cache: "force-cache", + next: { + revalidate: 3600, + // Replace `revalidate` with `tags` if using tag based revalidation. + // tags: [tag], + }, + }) + + return resource +} +``` + +## Path-based Revalidation + +The call to `getResource` in the above example is already configured to use path-based invalidation. The data will be cached for 3600 seconds unless revalidated on-demand. + +```tsx +const resource = await drupal.getResource(type, uuid, { + params, + cache: "force-cache", + next: { + revalidate: 3600, + }, +}) +``` + +## Tag-based Revalidation + +To instead use tag-based invalidation, we can replace the `revalidate` option with the `tags` option: + +```tsx +const resource = await drupal.getResource(type, uuid, { + params, + cache: "force-cache", + next: { + tags: [tag], + }, +}) +``` + +In this example, the tag is set earlier in the function using data from the translated path: + +```tsx +const type = translatedPath.jsonapi?.resourceName! +const uuid = translatedPath.entity.uuid +const tag = `${translatedPath.entity.type}:${translatedPath.entity.id}` +``` + +This option accepts an array of tags, and values should match the cache tag values set by Drupal. With this configuration, the data will be cached until Drupal makes a call to the revalidate route using any of the matching cache tags.