Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: persisted operations #44

Closed
wants to merge 5 commits into from
Closed
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
13 changes: 11 additions & 2 deletions examples/ecommerce/components/DatalayerProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
'use client'

import { Provider, createClient } from '@/fuse/client'
import {
Provider,
ssrExchange,
cacheExchange,
fetchExchange,
createClient,
persistedExchange,
} from '@/fuse/client'
import React, { Suspense } from 'react'

export const DatalayerProvider = (props: any) => {
const [client, ssr] = React.useMemo(() => {
const { client, ssr } = createClient({
const ssr = ssrExchange();
const { client } = createClient({
url:
process.env.NODE_ENV === 'production'
? 'https://spacex-fuse.vercel.app/api/fuse'
: 'http://localhost:3000/api/fuse',
exchanges: [cacheExchange, ssr, persistedExchange, fetchExchange],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add this as an option rather than having folks specify exchanges I reckon

})

return [client, ssr]
Expand Down
8 changes: 7 additions & 1 deletion examples/spacex/app/api/fuse/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { createAPIRouteHandler } from 'fuse/next'
import persistedDocuments from '@/fuse/persisted-documents.json'

const layer = createAPIRouteHandler()
const layer = createAPIRouteHandler({
persistedOperations: {
enabled: true,
operations: persistedDocuments,
},
})

export const GET = layer
export const POST = layer
6 changes: 3 additions & 3 deletions examples/spacex/app/client/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export default function Page() {
}

const LaunchesQuery = graphql(`
query Launches_SSR($limit: Int, $offset: Int) {
launches(limit: $limit, offset: $offset) {
query Launches_SSR($offset: Int) {
launches(limit: 10, offset: $offset) {
nodes {
id
...LaunchFields
Expand All @@ -44,7 +44,7 @@ function Launches() {

const [result] = useQuery({
query: LaunchesQuery,
variables: { limit: 10, offset },
variables: { offset },
})

return (
Expand Down
5 changes: 2 additions & 3 deletions examples/spacex/app/rsc/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import * as React from 'react'
import { graphql } from '@/fuse'
import { execute } from '@/fuse/server'
import { LaunchItem } from '@/components/LaunchItem'
import { headers } from 'next/headers'

import styles from './page.module.css'
import { PageNumbers } from '@/components/PageNumbers'
import { LaunchDetails } from '@/components/LaunchDetails'

const LaunchesQuery = graphql(`
query Launches_RSC($limit: Int, $offset: Int) {
launches(limit: $limit, offset: $offset) {
query Launches_RSC($offset: Int) {
launches(limit: 10, offset: $offset) {
nodes {
id
...LaunchFields
Expand Down
20 changes: 18 additions & 2 deletions examples/spacex/components/DatalayerProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
'use client'

import { Provider, createClient } from '@/fuse/client'
import {
Provider,
ssrExchange,
cacheExchange,
fetchExchange,
createClient,
persistedExchange,
debugExchange,
} from '@/fuse/client'
import React, { Suspense } from 'react'

export const DatalayerProvider = (props: any) => {
const [client, ssr] = React.useMemo(() => {
const { client, ssr } = createClient({
const ssr = ssrExchange()
const { client } = createClient({
url:
process.env.NODE_ENV === 'production'
? 'https://spacex-fuse.vercel.app/api/fuse'
: 'http://localhost:3000/api/fuse',
exchanges: [
cacheExchange,
ssr,
persistedExchange,
debugExchange,
fetchExchange,
],
})

return [client, ssr]
Expand Down
79 changes: 30 additions & 49 deletions examples/spacex/fuse/fragment-masking.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,66 @@
import {
ResultOf,
DocumentTypeDecoration,
TypedDocumentNode,
} from '@graphql-typed-document-node/core'
import { FragmentDefinitionNode } from 'graphql'
import { Incremental } from './graphql'
import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
import { FragmentDefinitionNode } from 'graphql';
import { Incremental } from './graphql';

export type FragmentType<
TDocumentType extends DocumentTypeDecoration<any, any>,
> = TDocumentType extends DocumentTypeDecoration<infer TType, any>

export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<
infer TType,
any
>
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
? TKey extends string
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
: never
: never
: never
: never;

// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>,
): TType
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
): TType;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType:
| FragmentType<DocumentTypeDecoration<TType, any>>
| null
| undefined,
): TType | null | undefined
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>,
): ReadonlyArray<TType>
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
): ReadonlyArray<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType:
| ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
| null
| undefined,
): ReadonlyArray<TType> | null | undefined
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType:
| FragmentType<DocumentTypeDecoration<TType, any>>
| ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
| null
| undefined,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): TType | ReadonlyArray<TType> | null | undefined {
return fragmentType as any
return fragmentType as any;
}


export function makeFragmentData<
F extends DocumentTypeDecoration<any, any>,
FT extends ResultOf<F>,
FT extends ResultOf<F>
>(data: FT, _fragment: F): FragmentType<F> {
return data as FragmentType<F>
return data as FragmentType<F>;
}
export function isFragmentReady<TQuery, TFrag>(
queryNode: DocumentTypeDecoration<TQuery, any>,
fragmentNode: TypedDocumentNode<TFrag>,
data:
| FragmentType<TypedDocumentNode<Incremental<TFrag>, any>>
| null
| undefined,
data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined
): data is FragmentType<typeof fragmentNode> {
const deferredFields = (
queryNode as {
__meta__?: { deferredFields: Record<string, (keyof TFrag)[]> }
}
).__meta__?.deferredFields
const deferredFields = (queryNode as { __meta__?: { deferredFields: Record<string, (keyof TFrag)[]> } }).__meta__
?.deferredFields;

if (!deferredFields) return true
if (!deferredFields) return true;

const fragDef = fragmentNode.definitions[0] as
| FragmentDefinitionNode
| undefined
const fragName = fragDef?.name?.value
const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined;
const fragName = fragDef?.name?.value;

const fields = (fragName && deferredFields[fragName]) || []
return fields.length > 0 && fields.every((field) => data && field in data)
const fields = (fragName && deferredFields[fragName]) || [];
return fields.length > 0 && fields.every(field => data && field in data);
}
18 changes: 9 additions & 9 deletions examples/spacex/fuse/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
const documents = {
'\n query Launches_SSR($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n':
'\n query Launches_SSR($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n':
types.Launches_SsrDocument,
'\n query Launches_RSC($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n':
'\n query Launches_RSC($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n':
types.Launches_RscDocument,
'\n query LaunchDetails($id: ID!) {\n node(id: $id) {\n ... on Launch {\n id\n name\n details\n launchDate\n image\n site {\n ...LaunchSiteFields\n }\n rocket {\n cost\n country\n company\n description\n }\n }\n }\n }\n':
types.LaunchDetailsDocument,
Expand All @@ -29,7 +29,7 @@ const documents = {
types.TotalCountFieldsFragmentDoc,
'\n mutation Hello($name: String!) {\n sayHello(name: $name)\n }\n':
types.HelloDocument,
'\n query PageLaunches($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n name\n }\n totalCount\n }\n }\n':
'\n query PageLaunches($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n name\n }\n totalCount\n }\n }\n':
types.PageLaunchesDocument,
}

Expand All @@ -51,14 +51,14 @@ export function graphql(source: string): unknown
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query Launches_SSR($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n',
): (typeof documents)['\n query Launches_SSR($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n']
source: '\n query Launches_SSR($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n',
): (typeof documents)['\n query Launches_SSR($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n']
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query Launches_RSC($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n',
): (typeof documents)['\n query Launches_RSC($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n']
source: '\n query Launches_RSC($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n',
): (typeof documents)['\n query Launches_RSC($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n ...LaunchFields\n }\n ...TotalCountFields\n }\n }\n']
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down Expand Up @@ -99,8 +99,8 @@ export function graphql(
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: '\n query PageLaunches($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n name\n }\n totalCount\n }\n }\n',
): (typeof documents)['\n query PageLaunches($limit: Int, $offset: Int) {\n launches(limit: $limit, offset: $offset) {\n nodes {\n id\n name\n }\n totalCount\n }\n }\n']
source: '\n query PageLaunches($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n name\n }\n totalCount\n }\n }\n',
): (typeof documents)['\n query PageLaunches($offset: Int) {\n launches(limit: 10, offset: $offset) {\n nodes {\n id\n name\n }\n totalCount\n }\n }\n']

export function graphql(source: string) {
return (documents as any)[source] ?? {}
Expand Down
Loading