Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/unit-test-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,20 @@ jobs:
uses: ./.github/actions/run-coverage
with:
working-directory: packages/faustwp-cli

test-apollo-client-4:
name: "Test packages with Apollo Client 4"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Node.js 24
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
check-latest: true

- name: Install
run: npm ci

- name: Test with Apollo Client 4
run: npm run test:apollo-client-4
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Faust.js is a toolkit for building Next.js applications for headless WordPress s
- Node.js 22 or newer and npm 8 or newer. Use the latest release of a [supported version](https://nodejs.org/en/about/previous-releases) so you have its security fixes. Node.js 20 has reached end of life. If you need to stay on it, use `@faustwp/core` and `@faustwp/cli` 3.x and `@faustwp/blocks` 6.x.
- Next.js 12.1.6 or newer with the Pages Router (15.5.5 or newer if you use `@faustwp/blocks`). The examples and tests use Next.js 16.
- React 17.0.2 or newer, and at least the version your Next.js release requires (18.2 for Next.js 16).
- Apollo Client 3.6.6 or newer, below 4 (3.14 or newer if you use `@faustwp/blocks`).
- Apollo Client.
- macOS, Windows (including WSL) or Linux.

### WordPress
Expand Down
2 changes: 1 addition & 1 deletion docs/explanation/apollo-client-basics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Apollo Client Basics"
description: "Learn about core Apollo Client concepts like queries, fragments, mutations, and caching as they relate to Faust.js and WordPress."
---

Faust.js uses `@apollo/client@3` under the hood to perform GraphQL operations against your WordPress backend. Having a solid understanding of Apollo Client queries, fragments, and mutations will help you get the most out of Faust.js.
Faust.js uses `@apollo/client` under the hood to perform GraphQL operations against your WordPress backend. Having a solid understanding of Apollo Client queries, fragments, and mutations will help you get the most out of Faust.js.

## Apollo Client GraphQL Concepts

Expand Down
2 changes: 1 addition & 1 deletion docs/explanation/telemetry/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Faust collects completely anonymous telemetry data about general usage. Particip
- `@faustwp/blocks` version
- `@faustwp/block-editor-utils` version
- `@faustwp/experimental-app-router` version
- `@apollo/client@3` version
- `@apollo/client` version
- Node version
- Next.js version
- If the Node environment is in **dev mode** (i.e., whether `npm run dev` was run)
Expand Down
6 changes: 4 additions & 2 deletions docs/how-to/authentication/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ You are now ready to implement the auth functionality. Create a `/gated/index.js

```js title="pages/gated/index.js"
import { useAuth, getApolloAuthClient, useLogout } from "@faustwp/core";
import { gql, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

function AuthenticatedView() {
const client = getApolloAuthClient();
Expand Down Expand Up @@ -151,7 +152,8 @@ Here are some things you can do to continue to test and add to your auth strateg

```js title="pages/gated/index.js"
import { useAuth, getApolloAuthClient, useLogout } from "@faustwp/core";
import { gql, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

function AuthenticatedView() {
const client = getApolloAuthClient();
Expand Down
4 changes: 2 additions & 2 deletions docs/how-to/basic-setup/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ Save your `.env.local` file.
Install the required NPM packages in your Next.js app.

```bash
npm install @apollo/client@3 @faustwp/core graphql @faustwp/cli
npm install @apollo/client @faustwp/core graphql @faustwp/cli
```

> [!NOTE]
> Faust supports Apollo Client 3.x. Apollo Client 4 isn't supported, and npm stops with a peer dependency error (`ERESOLVE`) if you try to install it alongside `@faustwp/core`.
> Import Apollo's React hooks, like `useQuery`, from `@apollo/client/react`.


### D. Create Faust config file
Expand Down
2 changes: 1 addition & 1 deletion docs/how-to/migrate-from-legacy-faust/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Create a list of components that can be reused in the new version of Faust and m
Start by installing the Faust.js core packages and dependencies in your terminal:

```bash
npm i @faustwp/cli @faustwp/core graphql @apollo/client@3
npm i @faustwp/cli @faustwp/core graphql @apollo/client
```

Change the `package.json` scripts to use the new Faust cli:
Expand Down
9 changes: 6 additions & 3 deletions docs/how-to/query-data-in-the-browser/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ Make sure you can navigate to this page in your browser.
To query data in the browser, you need to import the `useQuery` from the `@apollo/client` package. This hook will execute queries using the same client used by templates on the server. That client comes from [`FaustProvider`](/docs/reference/faust-provider/), which also explains how to customize it.

```js title="page/client.js" {1,4}
import { gql, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

export default function Page(props) {
const { data } = useQuery();
Expand All @@ -43,7 +44,8 @@ export default function Page(props) {
It's time to write our GraphQL query to fetch some data. For this example we'll be fetching the site title and description from your WP site's general settings.

```js {4,9-17} title="page/client.js"
import { gql, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

export default function Page(props) {
const { data } = useQuery(Page.query);
Expand All @@ -69,7 +71,8 @@ Page.query = gql`
Now that we have our query, we can display the data on the page.

```js {5, 7-12} title="page/client.js"
import { gql, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

export default function Page(props) {
const { data } = useQuery(Page.query);
Expand Down
3 changes: 2 additions & 1 deletion docs/how-to/write-data-to-wordpress/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ Pass the auth client to Apollo's `useMutation` hook, the same way you pass it to

```js title="components/NewDraft.js"
import { getApolloAuthClient } from "@faustwp/core";
import { gql, useMutation } from "@apollo/client";
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";

const CREATE_DRAFT = gql`
mutation CreateDraft($title: String!, $content: String) {
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/use-auth/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ type UseAuth = {
Below is a full example of login/logout and showing/hiding authenticated content. This below example uses the “redirect” strategy:

```js
import { gql, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
import { getApolloAuthClient, useAuth } from "@faustwp/core";
import { useLogout } from "@faustwp/core";

Expand Down
3 changes: 2 additions & 1 deletion examples/next/faustwp-getting-started/pages/example.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { gql, useQuery } from '@apollo/client';
import { gql } from '@apollo/client';
import { useQuery } from '@apollo/client/react';
import * as MENUS from '../constants/menus';
import { BlogInfoFragment } from '../fragments/GeneralSettings';
import {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dev": "npm run build && npm run dev:next:getting-started",
"dev:next:getting-started": "rimraf examples/next/faustwp-getting-started/.next && npm run dev ---workspace=examples/next/faustwp-getting-started",
"test": "npm run build && npm test --workspace=@faustwp/core --workspace=@faustwp/cli --workspace=@faustwp/blocks",
"test:apollo-client-4": "bash scripts/test-apollo-client-4.sh",
"test:format": "npm run test:format --workspace=@faustwp/cli --workspace=@faustwp/core -- --ignore-path .eslintignore",
"wpe-build": "exit 1",
"lint": "eslint ./packages --ext js,jsx,ts,tsx --max-warnings=0",
Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"module": "dist/mjs/index.js",
"types": "dist/mjs/index.d.ts",
"peerDependencies": {
"@apollo/client": "^3.14.1",
"@apollo/client": "^3.14.1 || ^4.0.0",
"@faustwp/core": ">=1.1.0",
"@wordpress/style-engine": ">=1.18.0",
"next": "^16.4.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/faustwp-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"module": "dist/mjs/index.js",
"types": "dist/cjs/index.d.ts",
"peerDependencies": {
"@apollo/client": ">=3.6.6 <4",
"@apollo/client": ">=3.6.6 <5",
"next": "^16.4.2",
"react": ">=17.0.2",
"react-dom": ">=17.0.2"
Expand Down
32 changes: 14 additions & 18 deletions packages/faustwp-core/src/apollo/errorLoggingLink.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import {
ApolloLink,
FetchResult,
NextLink,
Operation,
Observable,
ServerError,
} from '@apollo/client';
import { ApolloLink, FetchResult, Operation, Observable } from '@apollo/client';
import { errorLog } from '../utils/log.js';

/**
* Checks if the given error is a server error.
* @param error The error to check.
* @returns A boolean indicating whether the error is a server error.
*/
function isServerError(error: unknown): error is ServerError {
if (
function isServerError(error: unknown): error is {
response: Response;
statusCode: number;
result?: unknown;
bodyText?: string;
} {
return (
typeof error === 'object' &&
error !== null &&
'response' in error &&
'result' in error &&
'statusCode' in error
) {
return true;
}
return false;
);
}

/**
Expand All @@ -39,8 +33,9 @@ export class ErrorLoggingLink extends ApolloLink {
// eslint-disable-next-line class-methods-use-this
request(
operation: Operation,
forward: NextLink,
): Observable<FetchResult> | null {
// Apollo Client 3 calls this NextLink and 4 calls it ForwardFunction.
forward: NonNullable<Parameters<ApolloLink['request']>[1]>,
): ReturnType<ApolloLink['request']> {
return new Observable<FetchResult>(
(observer: {
next: (value: FetchResult) => void;
Expand All @@ -59,7 +54,8 @@ export class ErrorLoggingLink extends ApolloLink {
// Check if the error is a server error
if (isServerError(error)) {
errorLog('Server error:', error);
errorLog('Fetch result:', error.result);
// Apollo Client 3 parses the body into result; 4 keeps the raw bodyText.
errorLog('Fetch result:', error.result ?? error.bodyText);
} else {
errorLog('Network error:', error);
}
Expand Down
39 changes: 26 additions & 13 deletions packages/faustwp-core/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {
ApolloClient,
ApolloClientOptions,
ApolloLink,
createHttpLink,
HttpLink,
InMemoryCache,
InMemoryCacheConfig,
NormalizedCacheObject,
Expand All @@ -23,6 +22,14 @@ import { getConfig } from './config/index.js';
import { getGraphqlEndpoint } from './lib/getGraphqlEndpoint.js';
import { hooks } from './wpHooks/index.js';

// Apollo Client 4 dropped the cache type parameter. These types are worked out
// in the app's own project, so they fit whichever version it installs: with
// 3.x the client is ApolloClient<any>, with 4.x it's ApolloClient.
export type FaustApolloClient = typeof ApolloClient.prototype;
export type FaustApolloClientOptions = ConstructorParameters<
typeof ApolloClient
>[0];

export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';

declare global {
Expand All @@ -34,10 +41,10 @@ declare global {
const windowApolloState =
typeof window !== 'undefined' ? window[APOLLO_STATE_PROP_NAME] : {};

let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;
let apolloAuthClient: ApolloClient<NormalizedCacheObject> | undefined;
let apolloClient: FaustApolloClient | undefined;
let apolloAuthClient: FaustApolloClient | undefined;

export function createApolloClient(authenticated = false) {
export function createApolloClient(authenticated = false): FaustApolloClient {
const { possibleTypes, usePersistedQueries, useGETForQueries } = getConfig();

let inMemoryCacheObject: InMemoryCacheConfig = {
Expand Down Expand Up @@ -65,7 +72,7 @@ export function createApolloClient(authenticated = false) {

let linkChain = ApolloLink.from([
new ErrorLoggingLink(),
createHttpLink({
new HttpLink({
uri: getGraphqlEndpoint(),
/**
* Only add this option if usePersistedQueries is not set/false.
Expand All @@ -76,6 +83,9 @@ export function createApolloClient(authenticated = false) {
}),
]);

// Apollo Client 4 deprecates createPersistedQueryLink and setContext in favor
// of link classes that Apollo Client 3 doesn't have, so keep these for now.

// If the user requested to use persisted queries, apply the link.
if (usePersistedQueries) {
linkChain = createPersistedQueryLink({
Expand All @@ -100,33 +110,36 @@ export function createApolloClient(authenticated = false) {
}).concat(linkChain);
}

let apolloClientOptions: ApolloClientOptions<NormalizedCacheObject> = {
let apolloClientOptions: FaustApolloClientOptions = {
ssrMode: typeof window === 'undefined',
devtools: {
enabled: typeof window !== 'undefined',
},
link: linkChain,
cache: new InMemoryCache(inMemoryCacheObject).restore(windowApolloState),
// Apollo Client 4 adds its name and version to every request. Leave them
// out so GET URLs, and any caches keyed on them, match Apollo Client 3.
...({ enhancedClientAwareness: { transport: false } } as object),
};

apolloClientOptions = hooks.applyFilters(
'apolloClientOptions',
apolloClientOptions,
{},
) as ApolloClientOptions<NormalizedCacheObject>;
) as FaustApolloClientOptions;

return new ApolloClient(apolloClientOptions);
}

export function getApolloClient(initialState = null) {
export function getApolloClient(initialState = null): FaustApolloClient {
// eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle
const _apolloClient = apolloClient ?? createApolloClient();

// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
const existingCache = _apolloClient.extract() as NormalizedCacheObject;

// Merge the initialState from getStaticProps/getServerSideProps in the existing cache
const data = merge(existingCache, initialState, {
Expand All @@ -148,7 +161,7 @@ export function getApolloClient(initialState = null) {
return _apolloClient;
}

export function getApolloAuthClient() {
export function getApolloAuthClient(): FaustApolloClient {
// eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle
const _apolloAuthClient = apolloAuthClient ?? createApolloClient(true);

Expand All @@ -158,7 +171,7 @@ export function getApolloAuthClient() {
}

export function addApolloState(
client: ApolloClient<NormalizedCacheObject>,
client: FaustApolloClient,
pageProps: AppProps<{
props: { [key: string]: any };
revalidate?: number | boolean;
Expand All @@ -177,7 +190,7 @@ export function addApolloState(

export function useApollo(
pageProps: AppProps<{ [key: string]: any }>['pageProps'],
) {
): FaustApolloClient {
const state = pageProps[APOLLO_STATE_PROP_NAME];
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const store = useMemo(() => getApolloClient(state), [state]);
Expand Down
3 changes: 2 additions & 1 deletion packages/faustwp-core/src/components/FaustProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { ApolloProvider } from '@apollo/client';
// eslint-disable-next-line import/extensions
import { ApolloProvider } from '@apollo/client/react';
// eslint-disable-next-line import/extensions
import { useRouter } from 'next/router';
// eslint-disable-next-line import/extensions
Expand Down
Loading
Loading