Skip to content
Snippets Groups Projects
Commit 7c680d31 authored by Coby Sher's avatar Coby Sher Committed by Brian Perry
Browse files

Issue #3391141 by coby.sher, brianperry, pratik_kamble: Flesh out...

Issue #3391141 by coby.sher, brianperry, pratik_kamble: Flesh out documentation ahead of POC 0.x Release
parent 98c1b5cf
No related branches found
No related tags found
1 merge request!14Update README and some TSDoc comments
Pipeline #33506 passed
......@@ -29,12 +29,20 @@ There are two main workspaces in the monorepo, `packages`, and `examples`
#### api-client
The `api-client` package includes the base `ApiClient` class which is meant to be extended by other classes that implement logic to make it easy to fetch data from Drupal.
#### json-api-client
The `json-api-client` package includes the `JsonApiClient` class which extends the `ApiClient`, and makes it easy to fetch data from Drupal's JSON:API without deep knowledge of it.
### Examples
Examples show how the packages can be used in a variety of ways.
#### json-api-client-example
The json-api-client-example utilizes the JsonApiClient class to demonstrate how various configuration options can be employed for retrieving data.
## Configure the Monorepo
To contribute to this project, follow the directions below.
......@@ -61,7 +69,7 @@ There are several scripts you can use in the monorepo.
```
- Build all packages in the monorepo:
```bash
pnpm build:pkgs
pnpm build:packages
```
- Start the `json-api-client` example in development mode:
......@@ -75,6 +83,18 @@ There are several scripts you can use in the monorepo.
pnpm prettier:fix
```
- Build the examples
```bash
pnpm build:examples
```
- Run the jso-api-client-example
```bash
pnpm serve:json-api-client-example
```
- Run commands in a targeted project, folder, or workspace, using the
[`pnpm` filter flag](https://pnpm.io/filtering).
......
# api-client
This package contains the base class for the Drupal API Client. For more information, see https://www.drupal.org/project/api_client for more information about this project.
## Installation
<!-- Add installation information when we know which namespace we are using -->
## Usage
The `ApiClient` is a base class meant to be extended.
The base class includes the following parameters:
- `BaseUrl`: The url to use for all API requests
- `apiPrefix`: The base path for the JSON:API endpoint
- `cache`: An optional caching interface
- `customFetch`: An optional method to use in place of the default `fetch` method
- `authentication`: Optional credentials for authenticated requests
- `defaultLocale`: An optional locale to use for requests by default.
For an example of an extended `ApiClient`, see [`JsonApiClient`](https://git.drupalcode.org/project/api_client/-/blob/canary/packages/json-api-client/README.md)
......@@ -35,14 +35,13 @@ export type ApiClientOptions = {
) => Promise<Response>;
/**
* Represents an optional authentication configuration.
* @type {Authentication}
* The authentication configuration to use for authenticated API requests.
*/
authentication?: Authentication;
/**
* Represents an optional default locale setting.
* @type {Locale}
* The default locale to use for all API requests.
* @see {@link Locale}
*/
defaultLocale?: Locale;
......@@ -65,8 +64,17 @@ export type ApiClientOptions = {
};
type Authentication = {
/**
* The type of authentication to use.
*/
type: "Basic";
/**
* The username to use for Basic authentication.
*/
username?: string;
/**
* The password to use for Basic authentication.
*/
password?: string;
};
......
import ApiClient from "../src/ApiClient";
test('addAuthorizationHeader should add Basic authorization header if authentication type is "basic"', () => {
// Arrange
const apiClient = new ApiClient("https://example.com", {
......
# jsonapi-client
This package contains the `JsonApiClient` class which extends the base `ApiClient` class from the `@drupal/api-client` package. See https://www.drupal.org/project/api_client for more information about this project.
## Installation
<!-- Add installation information when we know which namespace we are using -->
## Usage
```ts
import { JsonApiClient } from "@drupal/json-api-client";
import NodeCache from "node-cache";
import Jsona from "jsona";
// the baseUrl to fetch data from
const myDrupalUrl = process.env.MY_DRUPAL_SITE || "https://drupal.example.com";
const client = new JsonApiClient(myDrupalUrl, {
// supply a custom fetch method in order to add certain headers to each request
// or any other logic you may need before the fetch call
customFetch: (input: RequestInfo | URL, init?: RequestInit) => {
const newHeaders = new Headers(init?.headers);
newHeaders.set("X-Custom-Header", "my-custom-value");
const newInit = {
...init,
headers: newHeaders,
};
return fetch(input, newInit);
},
// the optional cache will cache a request and return the cached data if the request
// is made again with the same type same data.
// The default cache includes an interface that must be implemented.
// Here is an example using the node-cache package. node-cache implements get and set
// methods so it is compatible with the cache interface.
// See https://www.npmjs.com/package/node-cache for details on the node-cache package.
cache: new NodeCache(),
// the optional authentication object will be used to authenticate requests.
// Currently Basic auth is supported.
authentication: {
type: "Basic",
// It is recommended to store sensitive information in environment variables that
// are not checked in to the source code.
username: process.env.MY_USERNAME,
password: process.env.MY_SECRET_PASSWORD,
},
// The default locale will be in the URL of each request.
// example: https://drupal.example.com/en/jsonapi/node/article
defaultLocale: "en",
// the optional serializer will be used to serialize and deserialize data.
serializer: new Jsona(),
});
```
import { Sha256 } from "@aws-crypto/sha256-js";
import ApiClient, { BaseUrl } from "@drupal/api-client";
import { toHex } from "@smithy/util-hex-encoding";
import ApiClient, { BaseUrl, Locale } from "@drupal/api-client";
import { JsonApiClientOptions } from "./types";
import type {
EntityTypeWithBundle,
GetOptions,
JsonApiClientOptions,
} from "./types";
/**
* JSON:API Client class provides functionality specific to JSON:API server.
......@@ -13,8 +17,8 @@ export default class JsonApiClient extends ApiClient {
/**
* Creates a new instance of the JsonApiClient.
* @param baseUrl - The base URL of the API.
* @param options - (Optional) Additional options for configuring the API client.
* @param baseUrl - The base URL of the API. {@link BaseUrl}
* @param options - (Optional) Additional options for configuring the API client. {@link JsonApiClientOptions}
*/
constructor(baseUrl: BaseUrl, options?: JsonApiClientOptions) {
super(baseUrl, options);
......@@ -28,23 +32,20 @@ export default class JsonApiClient extends ApiClient {
* Retrieves data of a specific entity type and bundle from the JSON:API.
* @param type - The type of resource to retrieve, in the format "entityType--bundle".
* For example, "node--page".
* @param options - (Optional) Additional options for customizing the request.
* @param options - (Optional) Additional options for customizing the request. {@link GetOptions}
* @returns A Promise that resolves to the JSON data of the requested resource.
*
* @example
* Using JSONAPI.CollectionResourceDoc type from the jsonapi-typescript package
* ```
* ```ts
* const collection = await jsonApiClient.get<JSONAPI.CollectionResourceDoc<string, Recipe>>("node--recipe");
* ```
*/
async get<T>(
type: string,
options?: {
locale?: Locale;
queryString?: string;
},
) {
async get<T>(type: EntityTypeWithBundle, options?: GetOptions) {
const [entityTypeId, bundleId] = type.split("--");
if (!entityTypeId || !bundleId) {
throw new TypeError(`type must be in the format "entityType--bundle"`);
}
const localeSegment = options?.locale || this.defaultLocale;
const queryString = options?.queryString ? `?${options?.queryString}` : "";
......
import type { Locale } from "@drupal/api-client";
import { ApiClientOptions } from "@drupal/api-client";
export type JsonApiClientOptions = ApiClientOptions & { debug?: boolean };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type JsonApiParams = Record<string, any>;
/**
* Options for customizing the {@link }.
*/
export interface GetOptions {
/**
* The locale to use for the request.
* If not set, the default locale will be used.
* If no default locale is set, no locale will be used.
*/
locale?: Locale;
/**
* A URL encoded query string to append to the request.
* See {@link https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/fetching-resources-get} for some examples of valid query strings.
* @remarks To help generate a valid query string, use a library like drupal-jsonapi-params {@link https://www.npmjs.com/package/drupal-jsonapi-params} and the `toQueryString` method.
* @example
* ```ts
* const options = {
* queryString: "filter[title][value]=My%20recipe",
* };
* ```
*/
queryString?: string;
}
/**
* A string in the format "entityType--bundle".
*/
export type EntityTypeWithBundle = `${string}--${string}`;
import Jsona from "jsona";
import { Deserializer } from "jsonapi-serializer";
import JsonApiClient from "../src/JsonApiClient";
import nodePage from "./mocks/data/node-page.json";
import nodePageEnglish from "./mocks/data/node-page-en.json";
import nodePageSpanish from "./mocks/data/node-page-es.json";
import nodePageFilter from "./mocks/data/node-page-filter.json";
import nodePage from "./mocks/data/node-page.json";
import nodeRecipeJsona from "./mocks/data/node-recipe-deserialize-jsona.json";
import nodeRecipeJsonAPISerializer from "./mocks/data/node-recipe-deserialize-jsonapi-serializer.json";
import nodePageFilter from "./mocks/data/node-page-filter.json";
const baseUrl = "https://dev-drupal-api-client-poc.pantheonsite.io";
......@@ -24,6 +24,17 @@ describe("JsonApiClient", () => {
expect(result).toEqual(nodePage);
});
it("should throw an error if the type is not `entityType--bundle`", async () => {
const apiClient = new JsonApiClient(baseUrl);
const type = "nodePage";
try {
// @ts-expect-error
await apiClient.get(type);
} catch (error) {
expect(error).toBeInstanceOf(TypeError);
}
});
it("should fetch data for a given type with default locale", async () => {
const apiClient = new JsonApiClient(baseUrl, { defaultLocale });
const type = "node--page";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment