diff --git a/example/src/pages/index.tsx b/example/src/pages/index.tsx index 5f30290..98a9ae6 100644 --- a/example/src/pages/index.tsx +++ b/example/src/pages/index.tsx @@ -1,4 +1,4 @@ -import type { NextPage } from 'next'; +import type { InferGetServerSidePropsType, NextPage } from 'next'; import Head from 'next/head'; import Image from 'next/image'; import Link from 'next/link'; @@ -15,11 +15,8 @@ import { UsersQuery } from '../types/generated'; -interface HomeProps { - users?: UserFragment[] | null; -} - -const Home: NextPage = () => { +const Home: NextPage> = ({ users }) => { + console.log('users from server side hydration', users); const { data } = useQuery(USERS_QUERY); return ( @@ -67,7 +64,7 @@ const Home: NextPage = () => { ) } -export const getServerSideProps = getServerSideApolloProps({ +export const getServerSideProps = getServerSideApolloProps<{ users?: UserFragment[] | null }>({ hydrateQueries: ['users'], onClientInitialized: async (ctx, apolloClient) => { if (ctx.query.addUser) { @@ -94,10 +91,11 @@ export const getServerSideProps = getServerSideApolloProps({ } return { props: {} }; }, - onHydrationComplete: ({ results }) => { - const users = results?.users?.data.users ?? null; + onHydrationComplete: ({ users }) => { return { - props: { users }, + props: { + users: users?.data.users ?? null + }, }; }, }); diff --git a/example/src/pages/profile/[userId].tsx b/example/src/pages/profile/[userId].tsx index 6bf5ba0..04ae65e 100644 --- a/example/src/pages/profile/[userId].tsx +++ b/example/src/pages/profile/[userId].tsx @@ -1,4 +1,4 @@ -import type { NextPage } from 'next'; +import type { InferGetServerSidePropsType, NextPage } from 'next'; import Head from 'next/head'; import Image from 'next/image'; import Link from 'next/link'; @@ -8,11 +8,7 @@ import { BOOKS_QUERY, USER_QUERY } from '../../gql'; import { BooksQuery, UserQuery, UserQueryVariables } from '../../types/generated'; import styles from '../../styles/Home.module.css'; -interface ProfilePageProps { - userId: string; -} - -const ProfilePage: NextPage = ({ userId }) => { +const ProfilePage: NextPage> = ({ userId }) => { const { data: userData } = useQuery(USER_QUERY, { variables: { id: userId } }); @@ -86,10 +82,10 @@ const ProfilePage: NextPage = ({ userId }) => { ) } -export const getServerSideProps = getServerSideApolloProps({ +export const getServerSideProps = getServerSideApolloProps<{ userId: string }>({ hydrateQueries: ['user', 'books'], - onHydrationComplete: ({ results }) => { - const user = results?.user?.data.user; + onHydrationComplete: ({ user: userResult }) => { + const user = userResult?.data.user; if (!user) { return { diff --git a/example/yarn.lock b/example/yarn.lock index cd0d68b..ab1ccfb 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1321,9 +1321,9 @@ integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== "@types/node@*": - version "18.7.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154" - integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== + version "18.7.19" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.19.tgz#ad83aa9b7af470fab7e0f562be87e97dc8ffe08e" + integrity sha512-Sq1itGUKUX1ap7GgZlrzdBydjbsJL/NSQt/4wkAxUJ7/OS5c2WkoN6WSpWc2Yc5wtKMZOUA0VCs/j2XJadN3HA== "@types/node@17.0.25": version "17.0.25" @@ -2293,9 +2293,9 @@ ecdsa-sig-formatter@1.0.11: safe-buffer "^5.0.1" electron-to-chromium@^1.4.251: - version "1.4.258" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.258.tgz#44c5456f487be082f038282fbcfd7b06ae99720d" - integrity sha512-vutF4q0dTUXoAFI7Vbtdwen/BJVwPgj8GRg/SElOodfH7VTX+svUe62A5BG41QRQGk5HsZPB0M++KH1lAlOt0A== + version "1.4.260" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.260.tgz#9aa3348d037686b47ccc5d3b48fe417b1f20017a" + integrity sha512-1GxPM2Bdz1AjuNjho9/TqJfxM7KZ7R8s4vA5cbbIoVacQXfvZlV+d7Y1lu4BhGzEBfjjhakr3NXKqN0PxPXIsg== emoji-regex@^8.0.0: version "8.0.0" @@ -3116,9 +3116,9 @@ is-boolean-object@^1.1.0: has-tostringtag "^1.0.0" is-callable@^1.1.4, is-callable@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.6.tgz#fd6170b0b8c7e2cc73de342ef8284a2202023c44" - integrity sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q== + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-core-module@^2.7.0, is-core-module@^2.9.0: version "2.10.0" @@ -3678,10 +3678,9 @@ next@12.1.5: "@next/swc-win32-ia32-msvc" "12.1.5" "@next/swc-win32-x64-msvc" "12.1.5" -nextjs-apollo-client@latest: - version "0.2.2" - resolved "https://registry.yarnpkg.com/nextjs-apollo-client/-/nextjs-apollo-client-0.2.2.tgz#de2e31b892561caab932a8daf0403d6738d9ed89" - integrity sha512-kW9MJMSuI6dr6M8M9MC9GVzmFWP4FJS617OJK2YHyo4QTAp3Pcg5buHw2EE00AqfhgHqKuoJo9cDmHJFl63Egw== +"nextjs-apollo-client@file:../nextjs-apollo-client-0.3.0-alpha.0.tgz": + version "0.3.0-alpha.0" + resolved "file:../nextjs-apollo-client-0.3.0-alpha.0.tgz#f6002d54d46d9cc42d05a7fd0fe2ca578558073a" dependencies: deepmerge "^4.2.2" lodash-es "^4.17.21" diff --git a/src/NextApolloClient.ts b/src/NextApolloClient.ts index 6b509ca..61e40cf 100644 --- a/src/NextApolloClient.ts +++ b/src/NextApolloClient.ts @@ -15,6 +15,7 @@ import { ApolloClientConfig, GetServerSideApolloProps, GetServerSideApolloPropsOptions, + HydrationCompleteResults, HydrationResponse, InitializeApolloArgs, PartialApolloClientOptions, @@ -28,7 +29,7 @@ export interface NextApolloClientOptions { hydrationMap?: QueryHydrationMap; } -export class NextApolloClient { +export class NextApolloClient { private readonly _client!: NextApolloClientOptions['client']; private readonly _hydrationMap?: NextApolloClientOptions['hydrationMap']; @@ -121,12 +122,12 @@ export class NextApolloClient { onClientInitialized, onHydrationComplete, }: GetServerSideApolloPropsOptions< - TProps, + THydrationMap, (keyof THydrationMap)[], - THydrationMap - > = {}): GetServerSideProps => async ( + TProps + > = {}): GetServerSideProps => async ( ctx: GetServerSidePropsContext - ): Promise> => { + ): Promise> => { const apolloClient = this.initializeApollo({ headers: ctx.req.headers }); let baseProps = {}; @@ -159,15 +160,11 @@ export class NextApolloClient { } if (curr.status === 'fulfilled') { const currentKey = Object.keys(curr.value.data)[0]; - // @ts-ignore - acc['results'] = { - ...acc['results'], - [currentKey]: curr.value, - }; + acc = { ...acc, [currentKey]: curr.value }; } return acc; }, - {} + {} as HydrationCompleteResults ); if (onHydrationComplete) { diff --git a/src/types/index.ts b/src/types/index.ts index 9436b6d..5b3e38b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -38,26 +38,31 @@ type ApolloClientFn = ( export type ApolloClientConfig = PartialApolloClientOptions | ApolloClientFn; // getServerSideProps types -export type HydrationResponse = { +export type HydrationResponse = HydrationCompleteResults< + THydrationMap +> & { errors?: ApolloError[]; - results?: THydrationMap extends any ? AnyHydrationResults : HydrationResults; }; +export type HydrationCompleteResults< + THydrationMap extends QueryHydrationMap +> = THydrationMap extends never ? AnyHydrationResults : HydrationResults; + export type HydrateQueries = | THydrationMap | PureQueryOptions[] | ((ctx: GetServerSidePropsContext) => PureQueryOptions[]); -export type ServerSidePropsResult = +export type ServerSidePropsResult> = | void | Promise | Promise> | GetServerSidePropsResult; export interface GetServerSideApolloPropsOptions< - TProps = Record & GetServerSideApolloProps, + THydrationMap extends QueryHydrationMap, THydrationMapKeys = any, - THydrationMap extends QueryHydrationMap = any + TProps = Record > { hydrateQueries?: HydrateQueries; onClientInitialized?: ( @@ -77,11 +82,19 @@ export interface GetServerSideApolloProps { export type QueryHydrationMap = Record QueryOptions>; export type HydrationResults = { - [K in keyof T]?: ApolloQueryResult>>; + [K in keyof T]?: ApolloQueryResult< + ReturnType extends QueryOptions ? HydrationQueryOptions> : any + >; }; export type AnyHydrationResults = { [K in keyof T]?: ApolloQueryResult; }; -export type HydrationQueryOptions = T extends QueryOptions ? Q : never; +export type HydrationQueryOptions = T extends QueryOptions + ? StrictlyUnknown extends true + ? any + : Q + : never; + +type StrictlyUnknown = unknown extends T ? true : false; diff --git a/test/getServerSideProps.test.ts b/test/getServerSideProps.test.ts index 6e96290..2997a65 100644 --- a/test/getServerSideProps.test.ts +++ b/test/getServerSideProps.test.ts @@ -114,11 +114,10 @@ describe('getServerSideApolloProps', () => { client: () => apolloClient, }); - const onHydrationComplete: GetServerSideApolloPropsOptions< - any - >['onHydrationComplete'] = jest.fn(({ results }) => { - const users = results?.users?.data?.users; - return { props: { users } }; + const onHydrationComplete: GetServerSideApolloPropsOptions<{ + users: any; + }>['onHydrationComplete'] = jest.fn(({ users }) => { + return { props: { users: users?.data.users } }; }); const result = await getServerSideApolloProps({ hydrateQueries: [{ query: USERS_QUERY }], @@ -127,7 +126,7 @@ describe('getServerSideApolloProps', () => { expect(spy).toHaveBeenCalledWith({ query: USERS_QUERY }); expect(onHydrationComplete).toHaveBeenCalledWith({ - results: { users: { data: { users: [] }, loading: false, networkStatus: 7 } }, + users: { data: { users: [] }, loading: false, networkStatus: 7 }, }); expect(result).toEqual({ props: { @@ -147,8 +146,8 @@ describe('getServerSideApolloProps', () => { const onHydrationComplete: GetServerSideApolloPropsOptions< any - >['onHydrationComplete'] = jest.fn(({ results }) => { - const users = results?.books?.data.books; + >['onHydrationComplete'] = jest.fn(({ books }) => { + const users = books?.data.books; return !users ? { redirect: { destination: '/', permanent: false } } : { props: { users } }; }); const result = await getServerSideApolloProps({ @@ -173,8 +172,8 @@ describe('getServerSideApolloProps', () => { const onHydrationComplete: GetServerSideApolloPropsOptions< any - >['onHydrationComplete'] = jest.fn(({ results }) => { - const users = results?.books?.data.books; + >['onHydrationComplete'] = jest.fn(({ books }) => { + const users = books?.data.books; return !users ? { notFound: true } : { props: { users } }; }); const result = await getServerSideApolloProps({ diff --git a/test/utils/index.ts b/test/utils/index.ts index 7489774..d02d109 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -1,4 +1,4 @@ -import { gql } from '@apollo/client'; +import { gql, OperationVariables, QueryOptions } from '@apollo/client'; import { createMockClient } from '@apollo/client/testing'; import { generateHydrationMap } from '../../src'; @@ -26,4 +26,20 @@ export const context = { req: { headers: {} }, }; -export const hydrationMap = generateHydrationMap({ users: () => ({ query: USERS_QUERY }) }); +export type UsersQuery = { + __typename?: 'Query'; + users: Array<{ + __typename?: 'User'; + id: string; + firstName: string; + lastName: string; + username: string; + email: string; + phone?: string | null; + img: string; + }>; +}; + +export const hydrationMap = generateHydrationMap({ + users: (): QueryOptions => ({ query: USERS_QUERY }), +});