diff --git a/docs/framework/config.md b/docs/framework/config.md index 223cdf4dc54..4973a884bed 100644 --- a/docs/framework/config.md +++ b/docs/framework/config.md @@ -239,13 +239,6 @@ The Google Tagmanager ID to be used on the site. This value is required even if you are configuring different values for each locale. -#### hygraphManagementApi: string - -Hygraph Management API. **Only used for migrations.** - -Optional: If the hygraphEndpoint is configured with the 'High Performance Content -API', this field is not required. - #### hygraphProjectId: string Hygraph Project ID. **Only used for migrations.** diff --git a/docs/hygraph/cli.md b/docs/hygraph/cli.md index 25e6e241739..0321c72f9c8 100644 --- a/docs/hygraph/cli.md +++ b/docs/hygraph/cli.md @@ -50,24 +50,10 @@ The following steps are needed to utilize this tool: - Can see schema view 5. Add this new token to your env file: `GC_HYGRAPH_WRITE_ACCESS_TOKEN="{YOUR_WRITE_ACCESS_TOKEN}"` -2. Run `yarn graphcommerce hygraph-migrate` -3. Select the migration you want to run and press enter. -4. The migrations should now be applied, check your Hygraph Schema if changes +2. Add your hygraphProjectId to your env file like this: + 1. Settings > Project > Id + 2. `GC_HYGRAPH_PROJECT_ID="{YOUR_PROJECT_ID}"` +3. Run `yarn graphcommerce hygraph-migrate` +4. Select the migration you want to run and press enter. +5. The migrations should now be applied, check your Hygraph Schema if changes are made. - -### Addtional config - -These configurations should not be necessary and can be deduced from the -hygraphEndpoint URL. This allows you to override them in case something goes -wrong. - -Before adding these make sure you've configure the 'High Performance Content -API' als your hygraphEndpoint. - -- Add your hygraphProjectId to your env file like this: - 1. Settings > Project > Id - 2. Add the project ID to your env file: - `GC_HYGRAPH_PROJECT_ID="{YOUR_PROJECT_ID}"` -- Add your hygraphManagementApi to your env file like this: - 1. Copy the Management API URL and add to your env file: - `GC_HYGRAPH_MANAGEMENT_API="{YOUR_MANAGEMENT_API}"` diff --git a/examples/magento-graphcms/.meshrc.yml b/examples/magento-graphcms/.meshrc.yml index 716dce965c8..426ee0362da 100644 --- a/examples/magento-graphcms/.meshrc.yml +++ b/examples/magento-graphcms/.meshrc.yml @@ -13,11 +13,11 @@ sources: # Remove mutations: `mutation { * }` - 'Mutation.!*' # Remove queries: `query { node, row*, asset*, scheduled*, *Version, user* }` - - 'Query.!{node,asset*,scheduled*,*Version,user*}' + - 'Query.!{node,asset*,scheduled*,*Version,user*,page}' # Remove field arguments: `query { anyfield(after,before,last,forceParentLocale,locales) { ... } }` - '*.*.!{after,before,last,forceParentLocale,locales,stage}' # Remove type any input or type fields: `input MyInput {}` or `type MyType { anyfield }` - - '*.!{localizations,scheduledIn,documentInStages*,createdAt*,updatedAt*,publishedAt*,createdBy,updatedBy,publishedBy,history,scheduledIn*}' + - '*.!{scheduledIn,documentInStages*,createdAt*,updatedAt*,publishedAt*,createdBy,updatedBy,publishedBy,history,scheduledIn*}' - prune: skipPruning: [] - name: m2 @@ -36,7 +36,6 @@ sources: X-Forwarded-For: "{context.headers['x-forwarded-for']}" serve: playground: true - plugins: - '@graphcommerce/graphql-mesh/plugin/forward-headers': forwardHeaders: diff --git a/examples/magento-graphcms/components/Blog/RowBlogContent.graphql b/examples/magento-graphcms/components/Blog/RowBlogContent.graphql index bd169574849..c0eb872e417 100644 --- a/examples/magento-graphcms/components/Blog/RowBlogContent.graphql +++ b/examples/magento-graphcms/components/Blog/RowBlogContent.graphql @@ -1,4 +1,5 @@ fragment RowBlogContent on RowBlogContent { + id content { raw } diff --git a/examples/magento-graphcms/components/Layout/FooterQueryFragment.graphql b/examples/magento-graphcms/components/GraphCMS/Footer/FooterQueryFragment.graphql similarity index 67% rename from examples/magento-graphcms/components/Layout/FooterQueryFragment.graphql rename to examples/magento-graphcms/components/GraphCMS/Footer/FooterQueryFragment.graphql index b4041bd54ae..c22a477a9fb 100644 --- a/examples/magento-graphcms/components/Layout/FooterQueryFragment.graphql +++ b/examples/magento-graphcms/components/GraphCMS/Footer/FooterQueryFragment.graphql @@ -1,4 +1,4 @@ -fragment FooterQueryFragment on Query { +fragment FooterQueryFragment on Query @inject(into: ["LayoutFragment"]) { footer(where: { identity: "footer" }) { id socialLinks { diff --git a/examples/magento-graphcms/components/GraphCMS/RowRenderer.graphql b/examples/magento-graphcms/components/GraphCMS/GcPage_Data_Hygraph.graphql similarity index 75% rename from examples/magento-graphcms/components/GraphCMS/RowRenderer.graphql rename to examples/magento-graphcms/components/GraphCMS/GcPage_Data_Hygraph.graphql index 318ee2e4fc0..8bfb0c3a5ee 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowRenderer.graphql +++ b/examples/magento-graphcms/components/GraphCMS/GcPage_Data_Hygraph.graphql @@ -1,5 +1,5 @@ -fragment RowRenderer on Page @inject(into: ["HygraphPage"]) { - content { +fragment PageRows_Hygraph on Page @inject(into: ["PageRows"]) { + rows { __typename ... on Node { id @@ -14,7 +14,7 @@ fragment RowRenderer on Page @inject(into: ["HygraphPage"]) { ...RowButtonLinkList ...RowServiceOptions ...RowContentLinks - ...RowProduct ...RowLinks + ...RowCategory } } diff --git a/examples/magento-graphcms/components/GraphCMS/GcRow.graphqls b/examples/magento-graphcms/components/GraphCMS/GcRow.graphqls new file mode 100644 index 00000000000..1cf24c5e133 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/GcRow.graphqls @@ -0,0 +1,14 @@ +union PageRow = + | RowBlogContent + | RowButtonLinkList + | RowCategory + | RowColumnOne + | RowColumnThree + | RowColumnTwo + | RowContentLinks + | RowHeroBanner + | RowLinks + | RowProduct + | RowQuote + | RowServiceOptions + | RowSpecialBanner diff --git a/examples/magento-graphcms/components/GraphCMS/PageContentQueryFragment.graphql b/examples/magento-graphcms/components/GraphCMS/PageContentQueryFragment.graphql deleted file mode 100644 index e2fcfc40073..00000000000 --- a/examples/magento-graphcms/components/GraphCMS/PageContentQueryFragment.graphql +++ /dev/null @@ -1,19 +0,0 @@ -fragment PageContentQueryFragment on Query { - pages(where: { url: $url }) { - title - metaTitle - metaDescription - metaRobots - url - author - date - relatedPages { - title - url - } - asset { - ...Asset - } - ...RowRenderer - } -} diff --git a/examples/magento-graphcms/components/GraphCMS/RowCategory/GetMagentoRowCategory.graphql b/examples/magento-graphcms/components/GraphCMS/RowCategory/GetMagentoRowCategory.graphql new file mode 100644 index 00000000000..3255de759d4 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowCategory/GetMagentoRowCategory.graphql @@ -0,0 +1,7 @@ +query GetMagentoRowCategory($uid: String!) { + categories(filters: { category_uid: { eq: $uid } }) { + items { + ...MagentoRowCategory + } + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowCategory/MagentoRowCategory.graphql b/examples/magento-graphcms/components/GraphCMS/RowCategory/MagentoRowCategory.graphql new file mode 100644 index 00000000000..d42cfe757fc --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowCategory/MagentoRowCategory.graphql @@ -0,0 +1,15 @@ +fragment MagentoRowCategory on CategoryTree { + uid + name + url_path + include_in_menu + available_sort_by + ...CategoryDescription + products(pageSize: 8) { + items { + __typename + uid + ...ProductListItem + } + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowCategory/RowCategory.graphql b/examples/magento-graphcms/components/GraphCMS/RowCategory/RowCategory.graphql new file mode 100644 index 00000000000..fc0500319b7 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowCategory/RowCategory.graphql @@ -0,0 +1,8 @@ +fragment RowCategory on RowCategory { + id + categoryUrl + rowCategoryVariant: variant + category { + ...MagentoRowCategory + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowCategory/RowCategory.tsx b/examples/magento-graphcms/components/GraphCMS/RowCategory/RowCategory.tsx new file mode 100644 index 00000000000..416199acdc4 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowCategory/RowCategory.tsx @@ -0,0 +1,74 @@ +import { InContextMaskProvider, useInContextQuery } from '@graphcommerce/graphql' +import { Box } from '@mui/material' +import { useRouter } from 'next/router' +import { GetMagentoRowCategoryDocument } from './GetMagentoRowCategory.gql' +import { RowCategoryFragment } from './RowCategory.gql' +import { Grid } from './variant/Grid' +import { Swipeable } from './variant/Swipeable' + +type VariantRenderer = Record< + NonNullable, + React.FC +> + +type RowCategoryProps = RowCategoryFragment & { + renderer?: Partial +} + +const defaultRenderer: Partial = { + Grid, + Swipeable, +} + +function RowCategoryPreview(props: RowCategoryProps) { + const { category, categoryUrl, rowCategoryVariant, renderer } = props + const router = useRouter() + const canShow = router.isPreview || process.env.NODE_ENV !== 'production' + if (category || !canShow) return null + + return ( + ({ + p: 2, + border: `3px dashed ${theme.palette.error.light}`, + m: 2, + borderRadius: 2, + })} + > + Hygraph RowCategory ({rowCategoryVariant}) was configured with Category URL " + {categoryUrl}", However Magento didn't return any results. + + ) +} + +export function RowCategory(props: RowCategoryProps) { + const { renderer, rowCategoryVariant, category, ...rest } = props + const mergedRenderer = { ...defaultRenderer, ...renderer } as VariantRenderer + + const scoped = useInContextQuery( + GetMagentoRowCategoryDocument, + { variables: { uid: category?.uid ?? '' }, skip: !category?.uid }, + { categories: { items: category ? [category] : [] } }, + ) + + if (!rowCategoryVariant) return null + + const RenderType = + mergedRenderer?.[rowCategoryVariant] ?? + (() => { + if (process.env.NODE_ENV !== 'production') + return <>renderer for {rowCategoryVariant} not found + return null + }) + + return ( + + + + + ) +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowCategory/variant/Grid.tsx b/examples/magento-graphcms/components/GraphCMS/RowCategory/variant/Grid.tsx new file mode 100644 index 00000000000..8678a836823 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowCategory/variant/Grid.tsx @@ -0,0 +1,34 @@ +import { productListLink } from '@graphcommerce/magento-product' +import { ContainerWithHeader } from '@graphcommerce/next-ui' +import { Link } from '@mui/material' +import { ProductListItems } from '../../../ProductListItems' +import { RowCategoryFragment } from '../RowCategory.gql' + +type GridProps = RowCategoryFragment + +export function Grid(props: GridProps) { + const { category } = props + + if (!category?.name) return null + const { name, include_in_menu, url_path, products } = category + + return ( + + {name} + + ) + } + > + + + ) +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowCategory/variant/Swipeable.tsx b/examples/magento-graphcms/components/GraphCMS/RowCategory/variant/Swipeable.tsx new file mode 100644 index 00000000000..c5294e57075 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowCategory/variant/Swipeable.tsx @@ -0,0 +1,53 @@ +import { AddProductsToCartForm } from '@graphcommerce/magento-product' +import { + filterNonNullableKeys, + RenderType, + responsiveVal, + SidebarSlider, + SidebarSliderProps, +} from '@graphcommerce/next-ui' +import { Typography } from '@mui/material' +import { productListRenderer } from '../../../ProductListItems' +import { RowCategoryFragment } from '../RowCategory.gql' + +type SwipeableProps = RowCategoryFragment & Pick + +export function Swipeable(props: SwipeableProps) { + const { category, sx = [] } = props + + // const items = category?.products?.items + if (!category || !category.products?.items) return null + + const { name } = category + const items = filterNonNullableKeys(category.products.items) + + return ( + + + {name} + + } + > + {items.map((item) => ( + + ))} + + + ) +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/GetMagentoRowProduct.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/GetMagentoRowProduct.graphql new file mode 100644 index 00000000000..b1fb3eade1a --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/GetMagentoRowProduct.graphql @@ -0,0 +1,8 @@ +query GetMagentoRowProduct($urlKey: String!) { + products(filter: { url_key: { eq: $urlKey } }, pageSize: 1) { + items { + uid + ...PageRows_CategoryData + } + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.tsx index 3addfe78490..25bdcdb6073 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.tsx @@ -1,65 +1,56 @@ import { InContextMaskProvider, useInContextQuery } from '@graphcommerce/graphql' -import { ProductListDocument, ProductListItemsFragment } from '@graphcommerce/magento-product' -import { ProductSpecsFragment } from '@graphcommerce/magento-product/components/ProductSpecs/ProductSpecs.gql' -import { filterNonNullableKeys } from '@graphcommerce/next-ui' -import { RowProductFragment } from './RowProduct.gql' -import { - Backstory, - Feature, - FeatureBoxed, - Grid, - Related, - Reviews, - Specs, - Swipeable, - Upsells, -} from './variant' +import { ProductListItemRenderer } from '@graphcommerce/magento-product' +import { productListRenderer } from '../../ProductListItems' +import { GetMagentoRowProductDocument } from './GetMagentoRowProduct.gql' +import { RowProductDeprecated } from './RowProductDeprecated' +import { RowProductFragment } from './graphql/RowProduct.gql' +import { Backstory, Feature, FeatureBoxed } from './variant' type VariantRenderer = Record< NonNullable, - React.FC + React.FC > type RowProductProps = RowProductFragment & { renderer?: Partial -} & ProductSpecsFragment & { sku?: string | null | undefined } & ProductListItemsFragment +} const defaultRenderer: Partial = { - Specs, Backstory, Feature, FeatureBoxed, - Grid, - Related, - Reviews, - Upsells, - Swipeable, + Related: () => <>Only available on the product page, + Reviews: () => <>Only available on the product page, + Specs: () => <>Only available on the product page, + Upsells: () => <>Only available on the product page, + Grid: RowProductDeprecated, + Swipeable: RowProductDeprecated, } export function RowProduct(props: RowProductProps) { - const { renderer, variant, items, ...rest } = props + const { renderer, variant, product } = props const mergedRenderer = { ...defaultRenderer, ...renderer } as VariantRenderer - const urlKeys = filterNonNullableKeys(items).map((item) => item.url_key) const scoped = useInContextQuery( - ProductListDocument, - { variables: { onlyItems: true, filters: { url_key: { in: urlKeys } } } }, - { products: { items } }, + GetMagentoRowProductDocument, + { variables: { urlKey: product?.url_key ?? '' }, skip: !product?.url_key }, + { products: { items: [product!] } }, ) - const { products } = scoped.data if (!variant) return null - const RenderType = - mergedRenderer?.[variant] ?? - (() => { - if (process.env.NODE_ENV !== 'production') return <>renderer for {variant} not found - return null - }) + const RenderType = mergedRenderer?.[variant] return ( - + {/* */} + {RenderType && ( + + )} ) } diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProductDeprecated.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProductDeprecated.tsx new file mode 100644 index 00000000000..6ce6fa4aa8a --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProductDeprecated.tsx @@ -0,0 +1,19 @@ +import { Box } from '@mui/material' +import { RowProductFragment } from './graphql/RowProduct.gql' + +export function RowProductDeprecated(props: RowProductFragment) { + const { variant, identity } = props + return process.env.NODE_ENV === 'development' ? ( + ({ + p: 2, + m: 3, + border: `3px dashed ${theme.palette.error.light}`, + borderRadius: 2, + })} + > + RowProduct with identity ‘{identity}’ and variant ‘{variant}’, should be migrated in Hygraph + to a RowCategory component. + + ) : null +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProductPage.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProductPage.tsx new file mode 100644 index 00000000000..2dee4c7042d --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProductPage.tsx @@ -0,0 +1,38 @@ +import { ProductListItemRenderer } from '@graphcommerce/magento-product' +import { productListRenderer } from '../../ProductListItems' +import { RowProductDeprecated } from './RowProductDeprecated' +import { RowProduct_ProductPageFragment } from './graphql/RowProduct_ProductPage.gql' +import { Backstory, Feature, FeatureBoxed, Related, Reviews, Specs, Upsells } from './variant' + +type VariantRenderer = Record< + NonNullable, + React.FC +> + +export type RowProductPageProps = RowProduct_ProductPageFragment & { + renderer?: Partial +} + +const defaultRenderer: Partial = { + Backstory, + Feature, + FeatureBoxed, + Specs, + Related, + Reviews, + Upsells, + Grid: RowProductDeprecated, + Swipeable: RowProductDeprecated, +} + +export function RowProductPage(props: RowProductPageProps) { + const { renderer, variant, product } = props + const mergedRenderer = { ...defaultRenderer, ...renderer } as VariantRenderer + + if (!variant) return null + + const RenderType = mergedRenderer?.[variant] + if (!variant) return null + + return +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageProductRows_RowProduct.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageProductRows_RowProduct.graphql new file mode 100644 index 00000000000..52b3c507032 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageProductRows_RowProduct.graphql @@ -0,0 +1,7 @@ +fragment PageProductRows_RowProduct on ProductInterface +@inject(into: ["ProductPageRows_ProductData"]) { + ...UpsellProducts + ...RelatedProducts + ...ProductReviews + ...ProductSpecs +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageProduct_RowProduct.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageProduct_RowProduct.graphql new file mode 100644 index 00000000000..de7834b170e --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageProduct_RowProduct.graphql @@ -0,0 +1,8 @@ +fragment PageProduct_RowProduct on ProductInterface @inject(into: ["ProductPageRows"]) { + page { + rows { + __typename + ...RowProduct_ProductPage + } + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageRows_RowProduct.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageRows_RowProduct.graphql new file mode 100644 index 00000000000..ffd014a0dee --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/PageRows_RowProduct.graphql @@ -0,0 +1,4 @@ +fragment Page_Product_RowProduct on ProductInterface @inject(into: ["PageRows_CategoryData"]) { + ...ProductListItem + ...ProductFeatureMediaBoxed +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/Page_RowProduct.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/Page_RowProduct.graphql new file mode 100644 index 00000000000..257a0dfd219 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/Page_RowProduct.graphql @@ -0,0 +1,8 @@ +# When we're requesting a **non-product page** also request the RowProduct fragments. +# For data specifc to the product page, see PageProduct_RowProduct.graphql +fragment Page_RowProduct on Page @inject(into: ["PageRows"]) { + rows { + __typename + ...RowProduct + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/RowProduct.graphql similarity index 77% rename from examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.graphql rename to examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/RowProduct.graphql index 89eddd5f446..cb94b08d190 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/RowProduct.graphql +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/RowProduct.graphql @@ -11,4 +11,7 @@ fragment RowProduct on RowProduct { pageLinks { ...PageLink } + product { + ...PageRows_CategoryData + } } diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/RowProduct_ProductPage.graphql b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/RowProduct_ProductPage.graphql new file mode 100644 index 00000000000..496a4fd6f99 --- /dev/null +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/graphql/RowProduct_ProductPage.graphql @@ -0,0 +1,6 @@ +fragment RowProduct_ProductPage on RowProduct { + ...RowProduct + product { + ...PageProductRows_RowProduct + } +} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Backstory.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Backstory.tsx index f57ab4d091b..a7ed991a364 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Backstory.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Backstory.tsx @@ -1,18 +1,16 @@ import { Asset, RichText } from '@graphcommerce/graphcms-ui' -import { AddProductsToCartForm, ProductListItemsFragment } from '@graphcommerce/magento-product' +import { AddProductsToCartForm } from '@graphcommerce/magento-product' import { ParagraphWithSidebarSlide, RenderType } from '@graphcommerce/next-ui' import { useTheme } from '@mui/material' import { productListRenderer } from '../../../ProductListItems' -import { RowProductFragment } from '../RowProduct.gql' +import { RowProductFragment } from '../graphql/RowProduct.gql' -type BackstoryProps = RowProductFragment & ProductListItemsFragment +type BackstoryProps = RowProductFragment export function Backstory(props: BackstoryProps) { - const { productCopy, asset, ...productListItems } = props + const { productCopy, asset, product } = props const theme = useTheme() - const singleItem = productListItems?.items?.[(productListItems.items?.length ?? 1) - 1] - - if (!singleItem) return null + if (!product) return null return ( @@ -25,7 +23,7 @@ export function Backstory(props: BackstoryProps) { slidingItems={ } diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Feature.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Feature.tsx index b3dd3d09d44..7b0eeeebbba 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Feature.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Feature.tsx @@ -2,15 +2,14 @@ import { RichText } from '@graphcommerce/graphcms-ui' import { Image } from '@graphcommerce/image' import { ImageText } from '@graphcommerce/next-ui' import { Typography, useTheme } from '@mui/material' -import { RowProductFragment } from '../RowProduct.gql' -import { ProductFeatureMediaFragment } from './ProductFeatureMedia.gql' +import { RowProductFragment } from '../graphql/RowProduct.gql' -type FeatureProps = RowProductFragment & ProductFeatureMediaFragment +type FeatureProps = RowProductFragment export function Feature(props: FeatureProps) { - const { productCopy, title, media_gallery } = props + const { productCopy, title, product } = props const theme = useTheme() - const item = media_gallery?.[2] ?? media_gallery?.[0] + const item = product?.media_gallery?.[2] ?? product?.media_gallery?.[0] if (!item) return null diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/FeatureBoxed.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/FeatureBoxed.tsx index 64f064d578d..a252fa25c96 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/FeatureBoxed.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/FeatureBoxed.tsx @@ -2,14 +2,13 @@ import { RichText } from '@graphcommerce/graphcms-ui' import { Image } from '@graphcommerce/image' import { ImageTextBoxed, responsiveVal } from '@graphcommerce/next-ui' import { Typography, useTheme } from '@mui/material' -import { RowProductFragment } from '../RowProduct.gql' -import { ProductFeatureMediaBoxedFragment } from './ProductFeatureMediaBoxed.gql' +import { RowProductFragment } from '../graphql/RowProduct.gql' -type FeatureBoxedProps = RowProductFragment & ProductFeatureMediaBoxedFragment +type FeatureBoxedProps = RowProductFragment export function FeatureBoxed(props: FeatureBoxedProps) { - const { productCopy, title, media_gallery } = props - const item = media_gallery?.[1] ?? media_gallery?.[0] + const { productCopy, title, product } = props + const item = product?.media_gallery?.[1] ?? product?.media_gallery?.[0] const theme = useTheme() if (!item) return null diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Grid.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Grid.tsx deleted file mode 100644 index 6fbf98caef0..00000000000 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Grid.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ContainerWithHeader } from '@graphcommerce/next-ui' -import { Link } from '@mui/material' -import { ProductListItems, ProductListItemsProps } from '../../../ProductListItems/ProductListItems' -import { RowProductFragment } from '../RowProduct.gql' - -type GridProps = RowProductFragment & ProductListItemsProps - -export function Grid(props: GridProps) { - const { title, pageLinks, productCopy, ...productListItems } = props - - return ( - ( - - {pageLink.title} - - ))} - > - - - ) -} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Related.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Related.tsx index 9791fc73cd3..78b6a31389d 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Related.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Related.tsx @@ -1,4 +1,4 @@ -import { AddProductsToCartForm, RelatedProductsFragment } from '@graphcommerce/magento-product' +import { AddProductsToCartForm } from '@graphcommerce/magento-product' import { SidebarSlider, RenderType, @@ -6,13 +6,14 @@ import { SidebarSliderProps, } from '@graphcommerce/next-ui' import { Typography } from '@mui/material' -import { productListRenderer } from '../../../ProductListItems/productListRenderer' -import { RowProductFragment } from '../RowProduct.gql' +import { productListRenderer } from '../../../ProductListItems' +import type { RowProductPageProps } from '../RowProductPage' -type RelatedProps = RowProductFragment & RelatedProductsFragment & Pick +type RelatedProps = RowProductPageProps & Pick export function Related(props: RelatedProps) { - const { title, related_products, sx } = props + const { title, product, sx } = props + const { related_products } = product ?? {} if (!related_products || related_products.length === 0) return null diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Reviews.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Reviews.tsx index ccc56e3d012..b8207d8c28c 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Reviews.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Reviews.tsx @@ -1,14 +1,15 @@ import { useQuery } from '@graphcommerce/graphql' -import { ProductReviews, ProductReviewsProps } from '@graphcommerce/magento-review' +import { ProductReviews } from '@graphcommerce/magento-review' import { StoreConfigDocument } from '@graphcommerce/magento-store' import { Row } from '@graphcommerce/next-ui' import { Box, Typography } from '@mui/material' -import { RowProductFragment } from '../RowProduct.gql' +import type { RowProductPageProps } from '../RowProductPage' -type ReviewsProps = RowProductFragment & Partial +type ReviewsProps = RowProductPageProps export function Reviews(props: ReviewsProps) { - const { title, reviews, url_key, review_count, sku } = props + const { title, product } = props + const { reviews, review_count, url_key, sku } = product ?? {} const { data, loading } = useQuery(StoreConfigDocument) diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Specs.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Specs.tsx index 5c1153f5c3c..4cf82887ffe 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Specs.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Specs.tsx @@ -1,13 +1,10 @@ import { ProductSpecs } from '@graphcommerce/magento-product' -import { ProductSpecsFragment } from '@graphcommerce/magento-product/components/ProductSpecs/ProductSpecs.gql' -import { RowProductFragment } from '../RowProduct.gql' +import type { RowProductPageProps } from '../RowProductPage' -type SpecsProps = RowProductFragment & ProductSpecsFragment +type SpecsProps = RowProductPageProps export function Specs(props: SpecsProps) { - const { title, aggregations, items } = props - - if (!aggregations && !items) return null - - return + const { title, product } = props + if (!product) return null + return } diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Swipeable.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Swipeable.tsx deleted file mode 100644 index 62d6e7354fe..00000000000 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Swipeable.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { AddProductsToCartForm, ProductListItemsFragment } from '@graphcommerce/magento-product' -import { - RenderType, - responsiveVal, - SidebarSlider, - SidebarSliderProps, -} from '@graphcommerce/next-ui' -import { Typography } from '@mui/material' -import { productListRenderer } from '../../../ProductListItems/productListRenderer' -import { RowProductFragment } from '../RowProduct.gql' - -type SwipeableProps = RowProductFragment & ProductListItemsFragment & Pick - -export function Swipeable(props: SwipeableProps) { - const { title, items, sx = [] } = props - - if (!items || items.length === 0) return null - - return ( - - - {title} - - } - > - {items?.map((item) => - item ? ( - - ) : null, - )} - - - ) -} diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Upsells.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Upsells.tsx index ab1a496cbed..ed3bad295c2 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Upsells.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/Upsells.tsx @@ -1,4 +1,4 @@ -import { AddProductsToCartForm, UpsellProductsFragment } from '@graphcommerce/magento-product' +import { AddProductsToCartForm } from '@graphcommerce/magento-product' import { SidebarSlider, RenderType, @@ -7,12 +7,13 @@ import { } from '@graphcommerce/next-ui' import { Typography } from '@mui/material' import { productListRenderer } from '../../../ProductListItems/productListRenderer' -import { RowProductFragment } from '../RowProduct.gql' +import type { RowProductPageProps } from '../RowProductPage' -type UpsellsProps = RowProductFragment & UpsellProductsFragment & Pick +type UpsellsProps = RowProductPageProps & Pick export function Upsells(props: UpsellsProps) { - const { title, upsell_products, sx } = props + const { title, product, sx } = props + const { upsell_products } = product ?? {} if (!upsell_products || upsell_products.length === 0) return null diff --git a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/index.tsx b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/index.tsx index a7123ddae51..963541a9a69 100644 --- a/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/index.tsx +++ b/examples/magento-graphcms/components/GraphCMS/RowProduct/variant/index.tsx @@ -1,9 +1,7 @@ export * from './Backstory' export * from './Feature' export * from './FeatureBoxed' -export * from './Grid' export * from './Related' export * from './Reviews' export * from './Specs' -export * from './Swipeable' export * from './Upsells' diff --git a/examples/magento-graphcms/components/GraphCMS/RowRenderer.tsx b/examples/magento-graphcms/components/GraphCMS/RowRenderer.tsx deleted file mode 100644 index 55f11894a0b..00000000000 --- a/examples/magento-graphcms/components/GraphCMS/RowRenderer.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { LazyHydrate, RenderType, TypeRenderer } from '@graphcommerce/next-ui' -import { memo } from 'react' -import { RowBlogContent } from '../Blog' -import { PageContentQueryFragment } from './PageContentQueryFragment.gql' -import { RowButtonLinkList } from './RowButtonLinkList/RowButtonLinkList' -import { RowColumnOne } from './RowColumnOne/RowColumnOne' -import { RowColumnThree } from './RowColumnThree/RowColumnThree' -import { RowColumnTwo } from './RowColumnTwo/RowColumnTwo' -import { RowContentLinks } from './RowContentLinks/RowContentLinks' -import { RowHeroBanner } from './RowHeroBanner/RowHeroBanner' -import { RowLinks } from './RowLinks/RowLinks' -import { RowProduct } from './RowProduct/RowProduct' -import { RowQuote } from './RowQuote/RowQuote' -import { RowRendererFragment } from './RowRenderer.gql' -import { RowServiceOptions } from './RowServiceOptions/RowServiceOptions' -import { RowSpecialBanner } from './RowSpecialBanner/RowSpecialBanner' - -type ContentTypeRenderer = TypeRenderer - -const defaultRenderer: Partial = { - RowColumnOne, - RowColumnTwo, - RowColumnThree, - RowHeroBanner, - RowSpecialBanner, - RowQuote, - RowBlogContent, - RowButtonLinkList, - RowServiceOptions, - RowContentLinks, - RowProduct, - RowLinks, -} - -export type PageProps = RowRendererFragment & { - renderer?: Partial - loadingEager?: number -} - -export const RowRenderer = memo((props) => { - const { content, renderer, loadingEager = 2 } = props - const mergedRenderer = { ...defaultRenderer, ...renderer } as ContentTypeRenderer - - return ( - <> - {content?.map((item, index) => ( - - - - ))} - - ) -}) diff --git a/examples/magento-graphcms/components/GraphCMS/index.ts b/examples/magento-graphcms/components/GraphCMS/index.ts deleted file mode 100644 index 925e70dccdf..00000000000 --- a/examples/magento-graphcms/components/GraphCMS/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './RowProduct/RowProduct' - -// The actual renderer -export * from './RowRenderer' diff --git a/examples/magento-graphcms/components/Layout/Layout.graphql b/examples/magento-graphcms/components/Layout/Layout.graphql index 05c9ba65625..cc241ae200c 100644 --- a/examples/magento-graphcms/components/Layout/Layout.graphql +++ b/examples/magento-graphcms/components/Layout/Layout.graphql @@ -1,7 +1,3 @@ query Layout { - menu: categories { - __typename - } - ...MenuQueryFragment - ...FooterQueryFragment + ...LayoutFragment } diff --git a/examples/magento-graphcms/components/Layout/LayoutFragment.graphql b/examples/magento-graphcms/components/Layout/LayoutFragment.graphql new file mode 100644 index 00000000000..7f0af5f1e8f --- /dev/null +++ b/examples/magento-graphcms/components/Layout/LayoutFragment.graphql @@ -0,0 +1,6 @@ +fragment LayoutFragment on Query { + menu: categories { + __typename + } + ...MenuQueryFragment +} diff --git a/examples/magento-graphcms/components/index.ts b/examples/magento-graphcms/components/index.ts index b58f561575f..7c4be478c2a 100644 --- a/examples/magento-graphcms/components/index.ts +++ b/examples/magento-graphcms/components/index.ts @@ -1,5 +1,4 @@ export * from './Blog' -export * from './GraphCMS' export * from './Layout' export * from './ProductListItems' export * from './ProductListLayout' diff --git a/examples/magento-graphcms/graphql/ProductPage2.graphql b/examples/magento-graphcms/graphql/ProductPage2.graphql index 408e80737bf..6238681583c 100644 --- a/examples/magento-graphcms/graphql/ProductPage2.graphql +++ b/examples/magento-graphcms/graphql/ProductPage2.graphql @@ -1,12 +1,10 @@ query ProductPage2( $urlKey: String! - $reviewPageSize: Int = 3 + $reviewPageSize: Int = 5 $reviewPage: Int = 1 $context: InContextInput - $useCustomAttributes: Boolean = false ) { products(filter: { url_key: { eq: $urlKey } }) @inContext(context: $context) { - ...ProductSpecs items { __typename uid @@ -17,15 +15,7 @@ query ProductPage2( ...DownloadableProductOptions ...BundleProductOptions ...GroupedProduct - } - } - # Workaround for https://github.com/magento/magento2/issues/32427 - relatedUpsells: products(filter: { url_key: { eq: $urlKey } }) @inContext(context: $context) { - items { - uid - ...UpsellProducts - ...RelatedProducts - ...ProductReviews + ...ProductPageRows } } } diff --git a/examples/magento-graphcms/lib/graphql/graphqlSsrClient.ts b/examples/magento-graphcms/lib/graphql/graphqlSsrClient.ts index bd3f968e2d0..2213c1e1086 100644 --- a/examples/magento-graphcms/lib/graphql/graphqlSsrClient.ts +++ b/examples/magento-graphcms/lib/graphql/graphqlSsrClient.ts @@ -37,7 +37,7 @@ function client(context: GetStaticPropsContext, fetchPolicy: FetchPolicy = 'no-c name: 'ssr', defaultOptions: { preview: context as PreviewConfig, - query: { errorPolicy: 'all', fetchPolicy }, + query: { fetchPolicy }, } as DefaultOptions, }) } diff --git a/examples/magento-graphcms/package.json b/examples/magento-graphcms/package.json index ec64eda760e..236afa6565a 100644 --- a/examples/magento-graphcms/package.json +++ b/examples/magento-graphcms/package.json @@ -33,6 +33,7 @@ "@graphcommerce/googletagmanager": "9.0.0-canary.72", "@graphcommerce/graphcms-ui": "9.0.0-canary.72", "@graphcommerce/graphql": "9.0.0-canary.72", + "@graphcommerce/graphql-gc-api": "9.0.0-canary.72", "@graphcommerce/graphql-mesh": "9.0.0-canary.72", "@graphcommerce/hygraph-cli": "9.0.0-canary.72", "@graphcommerce/hygraph-dynamic-rows": "9.0.0-canary.72", diff --git a/examples/magento-graphcms/pages/[...url].tsx b/examples/magento-graphcms/pages/[...url].tsx index f36befca5ac..0f6670ed79a 100644 --- a/examples/magento-graphcms/pages/[...url].tsx +++ b/examples/magento-graphcms/pages/[...url].tsx @@ -1,6 +1,16 @@ import { PageOptions } from '@graphcommerce/framer-next-pages' -import { Asset, hygraphPageContent, HygraphPagesQuery } from '@graphcommerce/graphcms-ui' +import { Asset } from '@graphcommerce/graphcms-ui' import { cacheFirst, flushMeasurePerf, InContextMaskProvider } from '@graphcommerce/graphql' +import { + PageQuery, + PageDocument, + isPageRedirect, + pageRedirect, + PageRows, + isPageNotFound, + pageRedirectOrNotFound, + isPageFound, +} from '@graphcommerce/graphql-gc-api' import { appendSiblingsAsChildren, CategoryBreadcrumbs, @@ -25,7 +35,7 @@ import { useProductList, } from '@graphcommerce/magento-product' import { redirectOrNotFound, StoreConfigDocument } from '@graphcommerce/magento-store' -import { GetStaticProps, LayoutHeader, LayoutTitle, MetaRobots } from '@graphcommerce/next-ui' +import { GetStaticProps, LayoutHeader, LayoutTitle } from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' import { Container } from '@mui/material' import { GetStaticPaths } from 'next' @@ -36,127 +46,142 @@ import { LayoutDocument, LayoutNavigation, LayoutNavigationProps, - RowProduct, - RowRenderer, } from '../components' import { CategoryPageDocument, CategoryPageQuery } from '../graphql/CategoryPage.gql' import { graphqlSharedClient, graphqlSsrClient } from '../lib/graphql/graphqlSsrClient' -export type CategoryProps = CategoryPageQuery & - HygraphPagesQuery & +export type CategoryProps = CategoryPageProps | RegularPageProps + +type CategoryPageProps = PageQuery & ProductListQuery & - ProductFiltersQuery & { filterTypes?: FilterTypes; params?: ProductListParams } + ProductFiltersQuery & { + category: NonNullable['items']>[0]> + filterTypes?: FilterTypes + params?: ProductListParams + } + +type RegularPageProps = { page: NonNullable } + export type CategoryRoute = { url: string[] } type GetPageStaticPaths = GetStaticPaths type GetPageStaticProps = GetStaticProps -function CategoryPage(props: CategoryProps) { - const { categories, pages, ...rest } = props - const productList = useProductList({ - ...rest, - category: categories?.items?.[0], - }) - const { products, params, category } = productList +function isCategoryPage(props: CategoryProps): props is CategoryPageProps { + return (props as CategoryPageProps).category !== undefined +} - const isLanding = category?.display_mode === 'PAGE' - const page = pages?.[0] - const isCategory = params && category && products?.items +function CategoryLanding(props: CategoryPageProps) { + const { page, ...rest } = props + const productList = useProductList(rest) + const { params, category } = productList return ( - + + + {category.name} + + + + {import.meta.graphCommerce.breadcrumbs && ( + ({ + mx: theme.page.horizontal, + height: 0, + [theme.breakpoints.down('md')]: { + '& .MuiBreadcrumbs-ol': { justifyContent: 'center' }, + }, + })} + /> + )} + } + title={{category?.name}} /> + + + + ) +} + +function CategoryProductList(props: CategoryPageProps) { + const { page, ...rest } = props + const productList = useProductList(rest) + const { params, category } = productList + + return ( + + - {category?.name ?? page.title} + {category.name} - {!isCategory && !isLanding && ( - - - {page.title} - - - )} - {isCategory && isLanding && ( - <> - {import.meta.graphCommerce.breadcrumbs && ( - ({ - mx: theme.page.horizontal, - height: 0, - [theme.breakpoints.down('md')]: { - '& .MuiBreadcrumbs-ol': { justifyContent: 'center' }, - }, - })} - /> - )} - } - title={{category?.name}} + + {import.meta.graphCommerce.productFiltersPro && + import.meta.graphCommerce.productFiltersLayout === 'SIDEBAR' && ( + - - )} - {isCategory && !isLanding && ( - <> - {import.meta.graphCommerce.productFiltersPro && - import.meta.graphCommerce.productFiltersLayout === 'SIDEBAR' && ( - - )} - {import.meta.graphCommerce.productFiltersPro && - import.meta.graphCommerce.productFiltersLayout !== 'SIDEBAR' && ( - - )} - {!import.meta.graphCommerce.productFiltersPro && ( - - )} - - )} - {page && ( - ( - - ), - }} + )} + {import.meta.graphCommerce.productFiltersPro && + import.meta.graphCommerce.productFiltersLayout !== 'SIDEBAR' && ( + + )} + {!import.meta.graphCommerce.productFiltersPro && ( + )} + + ) } +function RegularPage(props: RegularPageProps) { + const { page } = props + return ( + <> + + + {page.title} + + + + + + ) +} + +function CategoryPage(props: CategoryProps) { + if (isCategoryPage(props)) { + const { category } = props + if (category.display_mode === 'PAGE') return + return + } + + return +} + const pageOptions: PageOptions = { Layout: LayoutNavigation, } @@ -174,44 +199,37 @@ export const getStaticPaths: GetPageStaticPaths = async ({ locales = [] }) => { } export const getStaticProps: GetPageStaticProps = async (context) => { - const { params, locale } = context - const [url, query] = extractUrlQuery(params) + const [url, query] = extractUrlQuery(context.params) if (!url || !query) return { notFound: true } const client = graphqlSharedClient(context) + const staticClient = graphqlSsrClient(context) + const conf = client.query({ query: StoreConfigDocument }) + const pageQuery = client.query({ query: PageDocument, variables: { input: { href: `/${url}` } } }) + const categoryPage = staticClient.query({ query: CategoryPageDocument, variables: { url } }) + const layout = staticClient.query({ query: LayoutDocument }) const filterTypes = getFilterTypes(client) + const params = parseParams(url, query, await filterTypes) - const staticClient = graphqlSsrClient(context) - - const categoryPage = staticClient.query({ - query: CategoryPageDocument, - variables: { url }, - }) - const layout = staticClient.query({ - query: LayoutDocument, - fetchPolicy: cacheFirst(staticClient), - }) + const filteredCategoryUid = params && params.filters.category_uid?.in?.[0] - const productListParams = parseParams(url, query, await filterTypes) - const filteredCategoryUid = productListParams && productListParams.filters.category_uid?.in?.[0] + const categoryPromise = categoryPage.then((res) => res.data.categories?.items?.[0]) + const waitForSiblings = appendSiblingsAsChildren(categoryPromise, staticClient) - const category = categoryPage.then((res) => res.data.categories?.items?.[0]) - const waitForSiblings = appendSiblingsAsChildren(category, staticClient) let categoryUid = filteredCategoryUid if (!categoryUid) { - categoryUid = (await category)?.uid ?? '' - if (productListParams) productListParams.filters.category_uid = { in: [categoryUid] } + categoryUid = (await categoryPromise)?.uid ?? '' + if (params) params.filters.category_uid = { in: [categoryUid] } } - const pages = hygraphPageContent(staticClient, url, category) - const hasCategory = !!productListParams && categoryUid + const hasCategory = !!params && categoryUid const filters = hasCategory ? staticClient.query({ query: ProductFiltersDocument, variables: categoryDefaultsToProductListFilters( - await productListApplyCategoryDefaults(productListParams, (await conf).data, category), + await productListApplyCategoryDefaults(params, (await conf).data, categoryPromise), ), }) : undefined @@ -220,35 +238,52 @@ export const getStaticProps: GetPageStaticProps = async (context) => { ? staticClient.query({ query: ProductListDocument, variables: await productListApplyCategoryDefaults( - productListParams, + params, (await conf).data, - category, + categoryPromise, ), }) : undefined - const hasPage = filteredCategoryUid ? false : (await pages).data.pages.length > 0 - if (!hasCategory && !hasPage) return redirectOrNotFound(staticClient, conf, params, locale) + const parent = findParentBreadcrumbItem(await categoryPromise) + const up = parent + ? { href: `/${parent.category_url_path}`, title: parent.category_name } + : { href: `/`, title: i18n._(/* i18n */ 'Home') } - if ((await products)?.errors) return { notFound: true } + const pageResult = (await pageQuery).data + const category = await categoryPromise - const { category_url_path, category_name } = findParentBreadcrumbItem(await category) ?? {} + if (!category && pageResult) { + if (isPageRedirect(pageResult)) return pageRedirect(pageResult) - const up = - category_url_path && category_name - ? { href: `/${category_url_path}`, title: category_name } - : { href: `/`, title: i18n._(/* i18n */ 'Home') } + const result = { + props: { + page: pageResult.page, + apolloState: await conf.then(() => client.cache.extract()), + }, + revalidate: 60 * 20, + } + flushMeasurePerf() + return result + } + + if (!pageResult.page && !category) { + return redirectOrNotFound(staticClient, conf, context.params) + } + + if ((await products)?.errors) return { notFound: true } await waitForSiblings const result = { props: { - ...(await categoryPage).data, + category, + page: pageResult.page, + ...(await products)?.data, - ...(await pages).data, ...(await filters)?.data, ...(await layout).data, filterTypes: await filterTypes, - params: productListParams, + params, apolloState: await conf.then(() => client.cache.extract()), up, }, diff --git a/examples/magento-graphcms/pages/blog/page/[page].tsx b/examples/magento-graphcms/pages/blog/page/[page].tsx index 102b1ca7837..d8ab0ea70a2 100644 --- a/examples/magento-graphcms/pages/blog/page/[page].tsx +++ b/examples/magento-graphcms/pages/blog/page/[page].tsx @@ -1,5 +1,5 @@ +import { ContentArea, PageContent, pageContent } from '@graphcommerce/graphql-gc-api' import { PageOptions } from '@graphcommerce/framer-next-pages' -import { hygraphPageContent, HygraphPagesQuery } from '@graphcommerce/graphcms-ui' import { StoreConfigDocument } from '@graphcommerce/magento-store' import { PageMeta, @@ -15,20 +15,15 @@ import { GetStaticPaths } from 'next' import { useRouter } from 'next/router' import React from 'react' import { - BlogList, - BlogListDocument, - BlogListQuery, - BlogPathsDocument, - BlogPathsQuery, LayoutDocument, LayoutNavigation, LayoutNavigationProps, - RowRenderer, + productListRenderer, } from '../../../components' import { graphqlSsrClient, graphqlSharedClient } from '../../../lib/graphql/graphqlSsrClient' import { cacheFirst } from '@graphcommerce/graphql' -type Props = HygraphPagesQuery & BlogListQuery & BlogPathsQuery +type Props = BlogListQuery & BlogPathsQuery & { content: PageContent } type RouteProps = { page: string } type GetPageStaticPaths = GetStaticPaths type GetPageStaticProps = GetStaticProps @@ -36,24 +31,22 @@ type GetPageStaticProps = GetStaticProps - + - {title} + {content.title} - {title} + {content.title} @@ -67,7 +60,7 @@ function BlogPage(props: Props) { )} /> - + ) } @@ -103,7 +96,7 @@ export const getStaticProps: GetPageStaticProps = async (context) => { const staticClient = graphqlSsrClient(context) const conf = client.query({ query: StoreConfigDocument }) - const defaultPage = hygraphPageContent(staticClient, 'blog') + const content = pageContent(staticClient, 'blog') const layout = staticClient.query({ query: LayoutDocument, fetchPolicy: cacheFirst(staticClient), @@ -115,13 +108,13 @@ export const getStaticProps: GetPageStaticProps = async (context) => { }) const blogPaths = staticClient.query({ query: BlogPathsDocument }) - if (!(await defaultPage).data.pages?.[0]) return { notFound: true } + if ((await content).notFound) return { notFound: true } if (!(await blogPosts).data.blogPosts.length) return { notFound: true } if (Number(params?.page) <= 0) return { notFound: true } return { props: { - ...(await defaultPage).data, + content: await content, ...(await blogPosts).data, ...(await blogPaths).data, ...(await layout).data, diff --git a/examples/magento-graphcms/pages/blog/tagged/[url].tsx b/examples/magento-graphcms/pages/blog/tagged/[url].tsx index 7bd0890a9b5..a92b2dccf03 100644 --- a/examples/magento-graphcms/pages/blog/tagged/[url].tsx +++ b/examples/magento-graphcms/pages/blog/tagged/[url].tsx @@ -1,53 +1,44 @@ +import { ContentArea, PageContent, pageContent } from '@graphcommerce/graphql-gc-api' import { PageOptions } from '@graphcommerce/framer-next-pages' -import { hygraphPageContent, HygraphPagesQuery } from '@graphcommerce/graphcms-ui' import { redirectOrNotFound, StoreConfigDocument } from '@graphcommerce/magento-store' import { PageMeta, GetStaticProps, Row, LayoutTitle, LayoutHeader } from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' import { Trans } from '@lingui/react' import { GetStaticPaths } from 'next' import { - BlogAuthor, - BlogHeader, - BlogList, - BlogPostTaggedPathsDocument, - BlogListTaggedDocument, - BlogListTaggedQuery, - BlogTags, - BlogTitle, LayoutDocument, LayoutNavigation, LayoutNavigationProps, - RowRenderer, + productListRenderer, } from '../../../components' import { graphqlSsrClient, graphqlSharedClient } from '../../../lib/graphql/graphqlSsrClient' import { cacheFirst } from '@graphcommerce/graphql' -type Props = HygraphPagesQuery & BlogListTaggedQuery +type Props = BlogListTaggedQuery & { content: PageContent } type RouteProps = { url: string } type GetPageStaticPaths = GetStaticPaths type GetPageStaticProps = GetStaticProps function BlogPage(props: Props) { - const { pages, blogPosts } = props - const page = pages[0] - const title = page.title ?? '' + const { content, blogPosts } = props return ( <> - {title} + {content.title} - + - + - {page.author ? : null} - {page.asset ? : null} - - + {/* {page.author ? : null} */} + {/* {page.asset ? : null} */} + + + {/* */} @@ -85,7 +76,7 @@ export const getStaticProps: GetPageStaticProps = async (context) => { const staticClient = graphqlSsrClient(context) const limit = 99 const conf = client.query({ query: StoreConfigDocument }) - const page = hygraphPageContent(staticClient, `blog/tagged/${urlKey}`) + const content = pageContent(staticClient, `blog/tagged/${urlKey}`) const layout = staticClient.query({ query: LayoutDocument, fetchPolicy: cacheFirst(staticClient), @@ -96,12 +87,12 @@ export const getStaticProps: GetPageStaticProps = async (context) => { variables: { currentUrl: [`blog/tagged/${urlKey}`], first: limit, tagged: params?.url }, }) - if (!(await page).data.pages?.[0]) + if ((await content).notFound) return redirectOrNotFound(staticClient, conf, { url: `blog/${urlKey}` }, locale) return { props: { - ...(await page).data, + content: await content, ...(await blogPosts).data, ...(await layout).data, up: { href: '/blog', title: i18n._(/* i18n */ 'Blog') }, diff --git a/examples/magento-graphcms/pages/index.tsx b/examples/magento-graphcms/pages/index.tsx index 90de84f2dd4..b961a35b3ac 100644 --- a/examples/magento-graphcms/pages/index.tsx +++ b/examples/magento-graphcms/pages/index.tsx @@ -1,69 +1,30 @@ import { PageOptions } from '@graphcommerce/framer-next-pages' -import { hygraphPageContent, HygraphPagesQuery } from '@graphcommerce/graphcms-ui' -import { ProductListDocument, ProductListQuery } from '@graphcommerce/magento-product' -import { StoreConfigDocument } from '@graphcommerce/magento-store' -import { GetStaticProps, LayoutHeader, MetaRobots, PageMeta } from '@graphcommerce/next-ui' +import { cacheFirst } from '@graphcommerce/graphql' import { - LayoutDocument, - LayoutNavigation, - LayoutNavigationProps, - RowProduct, - RowRenderer, -} from '../components' + PageDocument, + Page, + PageMeta, + pageRedirectOrNotFound, + isPageFound, + PageRows, +} from '@graphcommerce/graphql-gc-api' +import { StoreConfigDocument } from '@graphcommerce/magento-store' +import { GetStaticProps, LayoutHeader } from '@graphcommerce/next-ui' +import { LayoutDocument, LayoutNavigation, LayoutNavigationProps } from '../components' import { graphqlSharedClient, graphqlSsrClient } from '../lib/graphql/graphqlSsrClient' -import { cacheFirst } from '@graphcommerce/graphql' -type Props = HygraphPagesQuery & { - latestList: ProductListQuery - favoritesList: ProductListQuery - swipableList: ProductListQuery -} +type Props = Page type RouteProps = { url: string } type GetPageStaticProps = GetStaticProps function CmsPage(props: Props) { - const { pages, latestList, favoritesList, swipableList } = props - const page = pages?.[0] - - const latest = latestList?.products?.items?.[0] - const favorite = favoritesList?.products?.items?.[0] - const swipable = swipableList?.products?.items?.[0] + const { page } = props return ( <> - - + - - {page && ( - { - const { identity } = rowProps - - if (identity === 'home-favorites') - return ( - - ) - if (identity === 'home-latest') - return - if (identity === 'home-swipable') - return ( - - ) - return ( - - ) - }, - }} - /> - )} + ) } @@ -79,37 +40,18 @@ export const getStaticProps: GetPageStaticProps = async (context) => { const staticClient = graphqlSsrClient(context) const conf = client.query({ query: StoreConfigDocument }) - const page = hygraphPageContent(staticClient, 'page/home') const layout = staticClient.query({ query: LayoutDocument, fetchPolicy: cacheFirst(staticClient), }) - // todo(paales): Remove when https://github.com/Urigo/graphql-mesh/issues/1257 is resolved - const favoritesList = staticClient.query({ - query: ProductListDocument, - variables: { onlyItems: true, pageSize: 8, filters: { category_uid: { eq: 'MTIx' } } }, - }) - - const latestList = staticClient.query({ - query: ProductListDocument, - variables: { onlyItems: true, pageSize: 8, filters: { category_uid: { eq: 'MTAy' } } }, - }) - - const swipableList = staticClient.query({ - query: ProductListDocument, - variables: { onlyItems: true, pageSize: 8, filters: { category_uid: { eq: 'MTIy' } } }, - }) - - if (!(await page).data.pages?.[0]) return { notFound: true } + const page = client.query({ query: PageDocument, variables: { input: { href: '/' } } }) + if (!isPageFound((await page).data)) return pageRedirectOrNotFound((await page).data) return { props: { ...(await page).data, ...(await layout).data, - latestList: (await latestList).data, - favoritesList: (await favoritesList).data, - swipableList: (await swipableList).data, apolloState: await conf.then(() => client.cache.extract()), }, revalidate: 60 * 20, diff --git a/examples/magento-graphcms/pages/modal/[...url].tsx b/examples/magento-graphcms/pages/modal/[...url].tsx index a7f56c8e02a..d82f85405ba 100644 --- a/examples/magento-graphcms/pages/modal/[...url].tsx +++ b/examples/magento-graphcms/pages/modal/[...url].tsx @@ -1,52 +1,39 @@ +import { ContentArea, PageContent, pageContent } from '@graphcommerce/graphql-gc-api' import { PageOptions } from '@graphcommerce/framer-next-pages' -import { hygraphPageContent, HygraphPagesQuery } from '@graphcommerce/graphcms-ui' import { StoreConfigDocument } from '@graphcommerce/magento-store' -import { - GetStaticProps, - MetaRobots, - LayoutOverlayHeader, - LayoutTitle, - PageMeta, -} from '@graphcommerce/next-ui' -import { Box, Typography } from '@mui/material' +import { GetStaticProps, LayoutOverlayHeader, LayoutTitle, PageMeta } from '@graphcommerce/next-ui' +import { Box } from '@mui/material' import { GetStaticPaths } from 'next' -import { LayoutDocument, LayoutOverlay, LayoutOverlayProps, RowRenderer } from '../../components' +import { + LayoutDocument, + LayoutOverlay, + LayoutOverlayProps, + productListRenderer, +} from '../../components' import { graphqlSsrClient, graphqlSharedClient } from '../../lib/graphql/graphqlSsrClient' import { cacheFirst } from '@graphcommerce/graphql' -type Props = HygraphPagesQuery +type Props = { content: PageContent } type RouteProps = { url: string[] } type GetPageStaticPaths = GetStaticPaths type GetPageStaticProps = GetStaticProps function ModalPage(props: Props) { - const { pages } = props - const page = pages?.[0] - - if (!pages?.[0]) return
- - const metaRobots = page?.metaRobots.toLowerCase().split('_').flat(1) as MetaRobots[] + const { content } = props return ( <> - {page.title} + {content.title} - + - {page.title} - - {page.metaDescription ?? ''} - + {content.title} - + ) } @@ -76,18 +63,17 @@ export const getStaticProps: GetPageStaticProps = async (context) => { const staticClient = graphqlSsrClient(context) const conf = client.query({ query: StoreConfigDocument }) - const page = hygraphPageContent(staticClient, `modal/${urlKey}`) - + const content = pageContent(staticClient, `modal/${urlKey}`) const layout = staticClient.query({ query: LayoutDocument, fetchPolicy: cacheFirst(staticClient), }) - if (!(await page).data.pages?.[0]) return { notFound: true } + if ((await content).notFound) return { notFound: true } return { props: { - ...(await page).data, + content: await content, ...(await layout).data, apolloState: await conf.then(() => client.cache.extract()), variantMd: 'bottom', diff --git a/examples/magento-graphcms/pages/p/[url].tsx b/examples/magento-graphcms/pages/p/[url].tsx index 029fc4ac133..523710a5326 100644 --- a/examples/magento-graphcms/pages/p/[url].tsx +++ b/examples/magento-graphcms/pages/p/[url].tsx @@ -1,11 +1,6 @@ import { PageOptions } from '@graphcommerce/framer-next-pages' -import { hygraphPageContent, HygraphPagesQuery } from '@graphcommerce/graphcms-ui' -import { - cacheFirst, - InContextMaskProvider, - mergeDeep, - useInContextQuery, -} from '@graphcommerce/graphql' +import { cacheFirst, InContextMaskProvider, useInContextQuery } from '@graphcommerce/graphql' +import { PageProductRows } from '@graphcommerce/graphql-gc-api' import { AddProductsToCartForm, AddProductsToCartFormProps, @@ -26,7 +21,7 @@ import { import { defaultConfigurableOptionsSelection } from '@graphcommerce/magento-product-configurable' import { RecentlyViewedProducts } from '@graphcommerce/magento-recently-viewed-products' import { jsonLdProductReview, ProductReviewChip } from '@graphcommerce/magento-review' -import { redirectOrNotFound, Money, StoreConfigDocument } from '@graphcommerce/magento-store' +import { Money, StoreConfigDocument } from '@graphcommerce/magento-store' import { ProductWishlistChipDetail } from '@graphcommerce/magento-wishlist' import { GetStaticProps, LayoutHeader, LayoutTitle, isTypename } from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' @@ -38,17 +33,13 @@ import { LayoutNavigation, LayoutNavigationProps, productListRenderer, - RowProduct, - RowRenderer, - Usps, } from '../../components' import { AddProductsToCartView } from '../../components/ProductView/AddProductsToCartView' -import { UspsDocument, UspsQuery } from '../../components/Usps/Usps.gql' +import { UspsQuery } from '../../components/Usps/Usps.gql' import { ProductPage2Document, ProductPage2Query } from '../../graphql/ProductPage2.gql' import { graphqlSharedClient, graphqlSsrClient } from '../../lib/graphql/graphqlSsrClient' -export type Props = HygraphPagesQuery & - UspsQuery & +export type Props = UspsQuery & ProductPage2Query & Pick & { urlKey: string } @@ -57,19 +48,12 @@ type GetPageStaticPaths = GetStaticPaths type GetPageStaticProps = GetStaticProps function ProductPage(props: Props) { - const { usps, sidebarUsps, pages, defaultValues, urlKey } = props + const { usps, sidebarUsps, defaultValues, urlKey } = props - const scopedQuery = useInContextQuery( - ProductPage2Document, - { variables: { urlKey, useCustomAttributes: import.meta.graphCommerce.magentoVersion >= 247 } }, - props, - ) - const { products, relatedUpsells } = scopedQuery.data + const scopedQuery = useInContextQuery(ProductPage2Document, { variables: { urlKey } }, props) + const { products } = scopedQuery.data - const product = mergeDeep( - products?.items?.[0], - relatedUpsells?.items?.find((item) => item?.uid === products?.items?.[0]?.uid), - ) + const product = products?.items?.[0] if (!product?.sku || !product.url_key) return null @@ -141,32 +125,20 @@ function ProductPage(props: Props) { - + {/* */} + + {/* */} } + right={<>hoi} + // right={} fontSize='responsive' /> - {pages?.[0] && ( - ( - - ), - }} - /> - )} + } @@ -201,36 +173,31 @@ export const getStaticProps: GetPageStaticProps = async (context) => { const urlKey = params?.url ?? '??' const conf = client.query({ query: StoreConfigDocument }) - const productPage = staticClient.query({ - query: ProductPage2Document, - variables: { urlKey, useCustomAttributes: import.meta.graphCommerce.magentoVersion >= 247 }, - }) const layout = staticClient.query({ query: LayoutDocument, fetchPolicy: cacheFirst(staticClient), }) - const product = productPage.then((pp) => - pp.data.products?.items?.find((p) => p?.url_key === urlKey), - ) + const productQuery = await staticClient.query({ + query: ProductPage2Document, + variables: { urlKey }, + }) + const product = productQuery.data.products?.items?.find((p) => p?.url_key === urlKey) - const pages = hygraphPageContent(staticClient, 'product/global', product, true) - if (!(await product)) return redirectOrNotFound(staticClient, conf, params, locale) + if (!product) return { notFound: true } + //return redirectOrNotFound(staticClient, conf, params, locale) - const category = productPageCategory(await product) + const category = productPageCategory(product) const up = category?.url_path && category?.name ? { href: `/${category.url_path}`, title: category.name } : { href: `/`, title: i18n._(/* i18n */ 'Home') } - const usps = staticClient.query({ query: UspsDocument, fetchPolicy: cacheFirst(staticClient) }) return { props: { urlKey, - ...defaultConfigurableOptionsSelection(urlKey, client, (await productPage).data), + ...defaultConfigurableOptionsSelection(urlKey, client, productQuery.data), ...(await layout).data, - ...(await pages).data, - ...(await usps).data, apolloState: await conf.then(() => client.cache.extract()), up, }, diff --git a/examples/magento-graphcms/pages/service/[[...url]].tsx b/examples/magento-graphcms/pages/service/[[...url]].tsx index b215a59ce4a..19d7f3e9127 100644 --- a/examples/magento-graphcms/pages/service/[[...url]].tsx +++ b/examples/magento-graphcms/pages/service/[[...url]].tsx @@ -1,9 +1,6 @@ +import { ContentArea, PageContent, pageContent } from '@graphcommerce/graphql-gc-api' import { PageOptions } from '@graphcommerce/framer-next-pages' -import { - PagesStaticPathsDocument, - hygraphPageContent, - HygraphPagesQuery, -} from '@graphcommerce/graphcms-ui' +import { PagesStaticPathsDocument } from '@graphcommerce/graphcms-ui' import { StoreConfigDocument, redirectOrNotFound } from '@graphcommerce/magento-store' import { PageMeta, GetStaticProps, LayoutOverlayHeader, LayoutTitle } from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' @@ -14,36 +11,33 @@ import { LayoutOverlay, LayoutOverlayProps, LayoutNavigationProps, - RowRenderer, + productListRenderer, } from '../../components' import { graphqlSsrClient, graphqlSharedClient } from '../../lib/graphql/graphqlSsrClient' import { cacheFirst } from '@graphcommerce/graphql' -type Props = HygraphPagesQuery +type Props = { content: PageContent } type RouteProps = { url: string[] } type GetPageStaticPaths = GetStaticPaths type GetPageStaticProps = GetStaticProps -function ServicePage({ pages }: Props) { - const title = pages?.[0].title ?? '' +function ServicePage(props: Props) { + const { content } = props return ( <> - + - {title} + {content.title} - {title} + {content.title} - + + ) } @@ -82,19 +76,19 @@ export const getStaticProps: GetPageStaticProps = async (context) => { const client = graphqlSharedClient(context) const staticClient = graphqlSsrClient(context) const conf = client.query({ query: StoreConfigDocument }) - const page = hygraphPageContent(staticClient, url) + const content = pageContent(staticClient, url) const layout = staticClient.query({ query: LayoutDocument, fetchPolicy: cacheFirst(staticClient), }) - if (!(await page).data.pages?.[0]) return redirectOrNotFound(staticClient, conf, { url }, locale) + if ((await content).notFound) return redirectOrNotFound(staticClient, conf, { url }, locale) const isRoot = url === 'service' return { props: { - ...(await page).data, + content: await content, ...(await layout).data, up: isRoot ? null : { href: '/service', title: i18n._(/* i18n */ 'Customer Service') }, apolloState: await conf.then(() => client.cache.extract()), diff --git a/examples/magento-graphcms/plugins/hygraph/HygraphGcPageRows.tsx b/examples/magento-graphcms/plugins/hygraph/HygraphGcPageRows.tsx new file mode 100644 index 00000000000..a7433b9f030 --- /dev/null +++ b/examples/magento-graphcms/plugins/hygraph/HygraphGcPageRows.tsx @@ -0,0 +1,73 @@ +import type { + ProductPageRows_ProductDataFragment, + PageProductRowsProps, + PageRows_CategoryDataFragment, + PageRowsProps, +} from '@graphcommerce/graphql-gc-api' +import type { PluginConfig, PluginProps } from '@graphcommerce/next-config' +import { RowBlogContent } from '../../components' +import { RowButtonLinkList } from '../../components/GraphCMS/RowButtonLinkList/RowButtonLinkList' +import { RowCategory } from '../../components/GraphCMS/RowCategory/RowCategory' +import { RowColumnOne } from '../../components/GraphCMS/RowColumnOne/RowColumnOne' +import { RowColumnThree } from '../../components/GraphCMS/RowColumnThree/RowColumnThree' +import { RowColumnTwo } from '../../components/GraphCMS/RowColumnTwo/RowColumnTwo' +import { RowContentLinks } from '../../components/GraphCMS/RowContentLinks/RowContentLinks' +import { RowHeroBanner } from '../../components/GraphCMS/RowHeroBanner/RowHeroBanner' +import { RowLinks } from '../../components/GraphCMS/RowLinks/RowLinks' +import { RowProduct } from '../../components/GraphCMS/RowProduct/RowProduct' +import { RowProductPage } from '../../components/GraphCMS/RowProduct/RowProductPage' +import { RowQuote } from '../../components/GraphCMS/RowQuote/RowQuote' +import { RowServiceOptions } from '../../components/GraphCMS/RowServiceOptions/RowServiceOptions' +import { RowSpecialBanner } from '../../components/GraphCMS/RowSpecialBanner/RowSpecialBanner' + +export const config: PluginConfig = { + type: 'component', + module: '@graphcommerce/graphql-gc-api', +} + +const renderer = { + PageRowFake: () => null, + RowColumnOne, + RowColumnTwo, + RowColumnThree, + RowHeroBanner, + RowSpecialBanner, + RowQuote, + RowBlogContent, + RowButtonLinkList, + RowServiceOptions, + RowContentLinks, + RowProduct, + RowLinks, + RowCategory, +} + +export const PageRows = ( + props: PluginProps & { + renderer?: Partial + }, +) => { + const { Prev, ...rest } = props + return +} + +const rendererProduct = { ...renderer, RowProduct: RowProductPage } + +export function PageProductRows< + P extends ProductPageRows_ProductDataFragment & PageRows_CategoryDataFragment, +>( + props: PluginProps> & { + renderer?: Partial['renderer']> + }, +) { + const { Prev, page, product, ...rest } = props + + const newPage = page && { + ...page, + rows: page?.rows?.map((row) => + row?.__typename === 'RowProduct' && !row.product ? { ...row, product } : row, + ), + } + + return +} diff --git a/examples/magento-graphcms/resolveProduct.ts b/examples/magento-graphcms/resolveProduct.ts new file mode 100644 index 00000000000..f3602c916a1 --- /dev/null +++ b/examples/magento-graphcms/resolveProduct.ts @@ -0,0 +1,10 @@ +// import { resolveRootDirectiveResolver } from './.mesh/index' + +import { MeshContext } from '@graphcommerce/graphql-mesh' + +const resolveProduct = (root, context: MeshContext, info) => { + console.log('ajaja') + // return context.accounts.Query.user({ root, args: { id: root.id }, context, info }) +} + +export default resolveProduct diff --git a/graphql.config.js b/graphql.config.js index e6ca6613a7f..454dd12443d 100644 --- a/graphql.config.js +++ b/graphql.config.js @@ -17,6 +17,7 @@ module.exports = { 'packages/graphql/apollo-client.graphql', 'packagesDev/graphql-codegen-near-operation-file/src/directive/env.graphqls', 'packagesDev/graphql-codegen-near-operation-file/src/directive/injectable.graphqls', + 'packages/graphql-mesh/directive/mesh.graphqls', ], documents: [ 'examples/magento-graphcms/components/**/*.graphql', diff --git a/packages/cli/package.json b/packages/cli/package.json index b220fd1f6b1..d67d98a25f4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,6 +24,7 @@ "dependencies": { "@graphql-codegen/cli": "5.0.2", "@graphql-mesh/cli": "latest", + "@graphql-mesh/compose-cli": "latest", "@graphql-mesh/cross-helpers": "latest", "@graphql-mesh/runtime": "latest", "@graphql-mesh/store": "latest", diff --git a/packages/ecommerce-ui/components/PreviewMode/PreviewMode.tsx b/packages/ecommerce-ui/components/PreviewMode/PreviewMode.tsx index 46be1b13122..ab25aa88171 100644 --- a/packages/ecommerce-ui/components/PreviewMode/PreviewMode.tsx +++ b/packages/ecommerce-ui/components/PreviewMode/PreviewMode.tsx @@ -96,13 +96,19 @@ function PreviewModeEnabled() { - ) } function PreviewModeDisabled() { - const form = useForm<{ secret: string }>({}) + const form = useForm<{ secret: string }>({ + defaultValues: { + secret: + process.env.NODE_ENV !== 'production' && import.meta.graphCommerce.previewSecret + ? import.meta.graphCommerce.previewSecret + : '', + }, + }) const submit = form.handleSubmit((formValues) => { const url = getPreviewUrl() diff --git a/packages/graphql-gc-api/README.md b/packages/graphql-gc-api/README.md new file mode 100644 index 00000000000..3a924255e12 --- /dev/null +++ b/packages/graphql-gc-api/README.md @@ -0,0 +1,200 @@ +# Graphql Gc Api + +A generic GraphQL API layer for GraphCommerce. + +## Goals + +- Reduce the Hygraph dependency in the example/magento-hygraph to a bare minumm + so that other examples like examples/magento-nocms and + examples/magento-othercms can be created. + +- Allow the API to be easily extended without having to map all sorts of data + but still have all the flexibility. + +- Offer a set of default components that can be used as extension points. + +## GraphQL Schema + +This package offers the following new queries + +```graphql +extend type Query { + page(input: GCPageInput!): Page +} + +type Page { + """ + The head of the page. + """ + head: PageHead + + """ + When the redirect is defined the page should not be rendered and the user should be redirected to the specified URL. + """ + redirect: PageRedirect +} +``` + +- [Query { page }](./schema/Query_page.graphqls) +- [Page](./schema/Page.graphqls) +- [PageHead](./schema/PageHead.graphqls) +- [PageRedirect](./schema/GCPageRedirect.graphqls) + +### Creating the GraphQL schema integration + +There are a few important steps to integrate with the GraphQL schema. First we +need to make your type compatible with Page. We take Hygraph as an example here. + +For example we would normally execute the following query: + +```graphql +query Pages { + pagesConnection(where: { url: "page/home" }) { + edges { + node { + url + } + head { + title + description + alternate { + rel + href + hreflang + } + } + } + } +} +``` + +And in the end we would like to have the following query working: + +```graphql +query Pages { + pagesConnection(where: { url: "page/home" }) { + edges { + node { + url + } + # This part is the PageHead + head { + title + description + alternate { + rel + href + hreflang + } + redirect { + href + permanent + } + } + } + } +} +``` + +### Extend your type + +RENAME your type to Page. + +Note: After making changines to the schema or .meshrc.yaml you always need to +run `yarn codegen` to see your changes. + +If you now run `yarn codegen` you'll in the GraphiQL interface that your Page +implements `Page`. When running the query you will see an empty `head` and empty +redirect. + +### Implement the `head`/`redirect` fields for your type + +1. Extend the mesh configuration with a + [plugin that references an additionalResolver](../hygraph-ui/plugins/meshConfigHygraph.ts) +2. Create the resolver in [mesh/resolvers.ts](../hygraph-ui/mesh/resolvers.ts). +3. Run `yarn codegen` to add the configuration. + +Note: when developing your resolver you do not need to run yarn codegen after +every change, only when you change the schema or the mesh configuration. + +You should now see the `head` and `redirect` fields populated with data in your +original query. + +### Resolve the page query to your created type. + +Now we want to be able to resolve this query. + +```graphql +query GCPage { + page(input: { href: "page/home" }) { + head { + title + canonical { + hreflang + href + } + description + alternate { + hreflang + href + } + robots { + name + content + } + } + ... on Page { + head { + __typename + } + content { + __typename + } + } + } +} +``` + +### Resolve the page query to your own query + +We define a `@resolveTo` for the page query in the mesh configuration. + +So in this case we want to run the earlier pagesConnection query (as mentioned +above) when the page query is called. + +The pagesConnection (sourceName) Query (sourceTypeName) should be calling +hygraph (sourceName). + +We want to take the href argument (args.input.href) from the page query and pass +it to the `url` input field of the pagesConnection. (sourceArgs). + +Finally when the pagesConnection has ran, we want to get the first edge and +return the node as the result. (result). + +Resulting in the following code: + +```graphql +extend type Query { + page(input: GCPageInput!): Page + @resolveTo( + sourceName: "hygraph" + sourceTypeName: "Query" + sourceFieldName: "pagesConnection" + sourceArgs: { where: { url: "{args.input.href}" } } + result: "edges[0].node" + ) +} +``` + +Learn more about +[combining multiple sources](https://the-guild.dev/graphql/mesh/docs/getting-started/combine-multiple-sources) + +Note: Because we change the schema, we need to run `yarn codegen` again. Note: +The resolveTo directive can doesn't really give warnings configured wrongly. + +After everything is set up you should be able to run the page query and get the +data from the pagesConnection query. + +## Frontend integration + +## Whats next diff --git a/packages/graphql-gc-api/components/Page/Page.graphql b/packages/graphql-gc-api/components/Page/Page.graphql new file mode 100644 index 00000000000..4a3b7b12169 --- /dev/null +++ b/packages/graphql-gc-api/components/Page/Page.graphql @@ -0,0 +1,7 @@ +query Page($input: GCPageInput!) { + page(input: $input) { + ...Page_Redirect + ...PageHead + ...PageRows + } +} diff --git a/packages/graphql-gc-api/components/Page/index.ts b/packages/graphql-gc-api/components/Page/index.ts new file mode 100644 index 00000000000..619a6d5a2bf --- /dev/null +++ b/packages/graphql-gc-api/components/Page/index.ts @@ -0,0 +1 @@ +export * from './Page.gql' diff --git a/packages/graphql-gc-api/components/PageCategory/PageCategoryRows.tsx b/packages/graphql-gc-api/components/PageCategory/PageCategoryRows.tsx new file mode 100644 index 00000000000..51c07c34fa7 --- /dev/null +++ b/packages/graphql-gc-api/components/PageCategory/PageCategoryRows.tsx @@ -0,0 +1,37 @@ +import { + TypeRenderer, + filterNonNullableKeys, + LazyHydrate, + RenderType, +} from '@graphcommerce/next-ui' +import { PageCategory_DataFragment } from './PageCategory_Data.gql' +import { PageRows_CategoryDataFragment } from './PageRows_CategoryData.gql' + +type PageRowTypeRenderer = TypeRenderer< + NonNullable>[number]> +> + +export type PageProductRowsProps

= { + page: PageCategory_DataFragment | null | undefined + renderer?: PageRowTypeRenderer + loadingEager?: number + category: P +} + +export function PageCategoryRows

( + props: PageProductRowsProps

, +) { + const { renderer, page, loadingEager = 2 } = props + + if (!renderer || !page) return null + + return ( + <> + {filterNonNullableKeys(page?.rows)?.map((item, index) => ( + + + + ))} + + ) +} diff --git a/packages/graphql-gc-api/components/PageCategory/PageCategory_Data.graphql b/packages/graphql-gc-api/components/PageCategory/PageCategory_Data.graphql new file mode 100644 index 00000000000..ee9e692f047 --- /dev/null +++ b/packages/graphql-gc-api/components/PageCategory/PageCategory_Data.graphql @@ -0,0 +1,5 @@ +# This fragment is added to the CategoryPAge query when the product page is loaded. +fragment PageCategory_Data on Page { + ...PageHead + ...PageRows +} diff --git a/packages/graphql-gc-api/components/PageCategory/PageProduct.graphql b/packages/graphql-gc-api/components/PageCategory/PageProduct.graphql new file mode 100644 index 00000000000..af3148fcac0 --- /dev/null +++ b/packages/graphql-gc-api/components/PageCategory/PageProduct.graphql @@ -0,0 +1,6 @@ +fragment PageCategory on CategoryTree { + page { + ...PageCategory_Data + } + ...PageRows_CategoryData +} diff --git a/packages/graphql-gc-api/components/PageHead/PageHead.graphql b/packages/graphql-gc-api/components/PageHead/PageHead.graphql new file mode 100644 index 00000000000..eff744d6ef4 --- /dev/null +++ b/packages/graphql-gc-api/components/PageHead/PageHead.graphql @@ -0,0 +1,36 @@ +fragment GcLinkRelTag on GcLinkRelTag { + rel + href + hreflang +} + +fragment GcMetaTag on GcMetaTag { + name + content +} + +# Data fragment used for the PageHead component. +fragment PageHead on Page { + head { + title + description + canonical { + hreflang + href + } + alternate { + hreflang + href + } + robots { + name + content + } + meta { + ...GcMetaTag + } + link { + ...GcLinkRelTag + } + } +} diff --git a/packages/graphql-gc-api/components/PageHead/PageHead.tsx b/packages/graphql-gc-api/components/PageHead/PageHead.tsx new file mode 100644 index 00000000000..80346ae75e6 --- /dev/null +++ b/packages/graphql-gc-api/components/PageHead/PageHead.tsx @@ -0,0 +1,58 @@ +import { usePageContext } from '@graphcommerce/framer-next-pages' +import { canonicalize } from '@graphcommerce/next-ui' +import Head from 'next/head' +import { useRouter } from 'next/router' +import { PageHeadFragment, GcMetaTagFragment, GcLinkRelTagFragment } from './PageHead.gql' + +type PageMetaProps = { + page: PageHeadFragment + children?: React.ReactNode +} + +export function PageMeta(props: PageMetaProps) { + const { + page: { head }, + children, + } = props + + const { active } = usePageContext() + const router = useRouter() + + if (!head || !active) return null + + const allLinkRel = new Map() + const allMeta = new Map() + + const canonical = canonicalize(router, head.canonical.href) + allLinkRel.set('canonical', { rel: 'canonical', href: canonical }) + + if (head?.description) { + allMeta.set('description', { name: 'description', content: head.description }) + allMeta.set('og:description', { name: 'og:description', content: head.description }) + } + + allMeta.set('og:title', { name: 'og:title', content: head.title }) + allMeta.set('og:url', { name: 'og:url', content: canonical }) + + head.link?.forEach((link) => allLinkRel.set(link.rel, link)) + head.robots?.forEach((robot) => allMeta.set(robot.name, robot)) + head.meta?.forEach((meta) => allMeta.set(meta.name, meta)) + + return ( + + {head.title.trim()} + {[...allLinkRel.values()].map((link) => ( + + ))} + {[...allMeta.values()].map((meta) => ( + + ))} + {children} + + ) +} diff --git a/packages/graphql-gc-api/components/PageHead/index.ts b/packages/graphql-gc-api/components/PageHead/index.ts new file mode 100644 index 00000000000..1ebba80ec24 --- /dev/null +++ b/packages/graphql-gc-api/components/PageHead/index.ts @@ -0,0 +1 @@ +export * from './PageHead' diff --git a/packages/graphql-gc-api/components/PageProduct/ProductPageRows.graphql b/packages/graphql-gc-api/components/PageProduct/ProductPageRows.graphql new file mode 100644 index 00000000000..2f7352b9c11 --- /dev/null +++ b/packages/graphql-gc-api/components/PageProduct/ProductPageRows.graphql @@ -0,0 +1,7 @@ +fragment ProductPageRows on ProductInterface { + page { + ...PageHead + ...PageRows + } + ...ProductPageRows_ProductData +} diff --git a/packages/graphql-gc-api/components/PageProduct/ProductPageRows.tsx b/packages/graphql-gc-api/components/PageProduct/ProductPageRows.tsx new file mode 100644 index 00000000000..d2d071992a2 --- /dev/null +++ b/packages/graphql-gc-api/components/PageProduct/ProductPageRows.tsx @@ -0,0 +1,38 @@ +import { + TypeRenderer, + filterNonNullableKeys, + LazyHydrate, + RenderType, +} from '@graphcommerce/next-ui' +import { PageRows_CategoryDataFragment } from './PageRows_CategoryData.gql' +import { ProductPageRowsFragment } from './ProductPageRows.gql' +import { ProductPageRows_ProductDataFragment } from './ProductPageRows_ProductData.gql' + +type PageRowTypeRenderer = TypeRenderer< + NonNullable>[number]> +> + +export type PageProductRowsProps

= { + page: ProductPageRowsFragment | null | undefined + renderer?: PageRowTypeRenderer + loadingEager?: number + product: P +} + +export function ProductPageRows< + P extends ProductPageRows_ProductDataFragment & PageRows_CategoryDataFragment, +>(props: PageProductRowsProps

) { + const { renderer, page, loadingEager = 2 } = props + + if (!renderer || !page) return null + + return ( + <> + {filterNonNullableKeys(page?.rows)?.map((item, index) => ( + + + + ))} + + ) +} diff --git a/packages/graphql-gc-api/components/PageProduct/ProductPageRows_ProductData.graphql b/packages/graphql-gc-api/components/PageProduct/ProductPageRows_ProductData.graphql new file mode 100644 index 00000000000..3a1b111a436 --- /dev/null +++ b/packages/graphql-gc-api/components/PageProduct/ProductPageRows_ProductData.graphql @@ -0,0 +1,3 @@ +fragment ProductPageRows_ProductData on ProductInterface { + ...PageRows_CategoryData +} diff --git a/packages/graphql-gc-api/components/PageProduct/index.ts b/packages/graphql-gc-api/components/PageProduct/index.ts new file mode 100644 index 00000000000..9aa750b8257 --- /dev/null +++ b/packages/graphql-gc-api/components/PageProduct/index.ts @@ -0,0 +1,2 @@ +export * from './ProductPageRows' +export * from './ProductPageRows_ProductData.gql' diff --git a/packages/graphql-gc-api/components/PageRows/PageRows.graphql b/packages/graphql-gc-api/components/PageRows/PageRows.graphql new file mode 100644 index 00000000000..83512165d29 --- /dev/null +++ b/packages/graphql-gc-api/components/PageRows/PageRows.graphql @@ -0,0 +1,9 @@ +# This fragment is used in the Page query to retrieve the data for a page. +fragment PageRows on Page { + rows { + __typename + ... on PageRowFake { + id + } + } +} diff --git a/packages/graphql-gc-api/components/PageRows/PageRows.tsx b/packages/graphql-gc-api/components/PageRows/PageRows.tsx new file mode 100644 index 00000000000..420da31c41c --- /dev/null +++ b/packages/graphql-gc-api/components/PageRows/PageRows.tsx @@ -0,0 +1,33 @@ +import { + filterNonNullableKeys, + LazyHydrate, + RenderType, + type TypeRenderer, +} from '@graphcommerce/next-ui' +import React from 'react' +import { PageRowsFragment } from '../Page/PageRows.gql' + +type PageRowTypeRenderer = TypeRenderer< + NonNullable>[number]> +> + +export type PageRowsProps = { + page: PageRowsFragment | null | undefined + renderer?: PageRowTypeRenderer + loadingEager?: number +} + +export const PageRows = React.memo((props: PageRowsProps) => { + const { renderer, page, loadingEager = 2 } = props + + if (!renderer || !page) return null + return ( + <> + {filterNonNullableKeys(page?.rows)?.map((item, index) => ( + + + + ))} + + ) +}) diff --git a/packages/graphql-gc-api/components/PageRows/PageRows_CategoryData.graphql b/packages/graphql-gc-api/components/PageRows/PageRows_CategoryData.graphql new file mode 100644 index 00000000000..7cda1538fda --- /dev/null +++ b/packages/graphql-gc-api/components/PageRows/PageRows_CategoryData.graphql @@ -0,0 +1,5 @@ +# When a category related component +fragment PageRows_CategoryData on CategoryTree { + uid + __typename +} diff --git a/packages/graphql-gc-api/components/PageRows/PageRows_ProductData.graphql b/packages/graphql-gc-api/components/PageRows/PageRows_ProductData.graphql new file mode 100644 index 00000000000..7cda1538fda --- /dev/null +++ b/packages/graphql-gc-api/components/PageRows/PageRows_ProductData.graphql @@ -0,0 +1,5 @@ +# When a category related component +fragment PageRows_CategoryData on CategoryTree { + uid + __typename +} diff --git a/packages/graphql-gc-api/components/PageRows/index.ts b/packages/graphql-gc-api/components/PageRows/index.ts new file mode 100644 index 00000000000..eabe927b4ab --- /dev/null +++ b/packages/graphql-gc-api/components/PageRows/index.ts @@ -0,0 +1,2 @@ +export * from './PageRows' +export * from './PageRows.gql' diff --git a/packages/graphql-gc-api/components/index.ts b/packages/graphql-gc-api/components/index.ts new file mode 100644 index 00000000000..cf92646c29e --- /dev/null +++ b/packages/graphql-gc-api/components/index.ts @@ -0,0 +1,4 @@ +export * from './Page' +export * from './PageHead' +export * from './PageProduct' +export * from './PageRows' diff --git a/packages/graphql-gc-api/index.ts b/packages/graphql-gc-api/index.ts new file mode 100644 index 00000000000..1464f012256 --- /dev/null +++ b/packages/graphql-gc-api/index.ts @@ -0,0 +1,2 @@ +export * from './components' +export * from './utils' diff --git a/packages/graphql-gc-api/package.json b/packages/graphql-gc-api/package.json new file mode 100644 index 00000000000..6b94329e035 --- /dev/null +++ b/packages/graphql-gc-api/package.json @@ -0,0 +1,32 @@ +{ + "name": "@graphcommerce/graphql-gc-api", + "homepage": "https://www.graphcommerce.org/", + "repository": "github:graphcommerce-org/graphcommerce", + "version": "9.0.0-canary.72", + "sideEffects": false, + "prettier": "@graphcommerce/prettier-config-pwa", + "eslintConfig": { + "extends": "@graphcommerce/eslint-config-pwa", + "parserOptions": { + "project": "./tsconfig.json" + } + }, + "peerDependencies": { + "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.62", + "@graphcommerce/graphql": "^9.0.0-canary.62", + "@graphcommerce/graphql-mesh": "^9.0.0-canary.62", + "@graphcommerce/magento-product": "^9.0.0-canary.62", + "@graphcommerce/next-ui": "^9.0.0-canary.62", + "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.62", + "@graphcommerce/react-hook-form": "^9.0.0-canary.62", + "@graphcommerce/typescript-config-pwa": "^9.0.0-canary.62", + "@lingui/core": "^4.2.1", + "@lingui/macro": "^4.2.1", + "@lingui/react": "^4.2.1", + "@mui/material": "^5.10.16", + "framer-motion": "^10.0.0", + "next": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } +} diff --git a/packages/graphql-gc-api/schema/CategoryInterface_gcPage.graphqls b/packages/graphql-gc-api/schema/CategoryInterface_gcPage.graphqls new file mode 100644 index 00000000000..a1d6589e020 --- /dev/null +++ b/packages/graphql-gc-api/schema/CategoryInterface_gcPage.graphqls @@ -0,0 +1,7 @@ +interface CategoryInterface { + page: Page +} + +type CategoryTree { + page: Page +} diff --git a/packages/graphql-gc-api/schema/Page.graphqls b/packages/graphql-gc-api/schema/Page.graphqls new file mode 100644 index 00000000000..4947748cfa5 --- /dev/null +++ b/packages/graphql-gc-api/schema/Page.graphqls @@ -0,0 +1,25 @@ +""" +This type only exists to make the PageRow union not fail. +""" +type PageRowFake { + id: ID! +} + +union PageRow = PageRowFake + +type Page { + """ + The head of the page. + """ + head: PageHead + + """ + When the redirect is defined the page should not be rendered and the user should be redirected to the specified URL. + """ + redirect: PageRedirect + + """ + Rows of the content + """ + rows: [PageRow] +} diff --git a/packages/graphql-gc-api/schema/PageHead.graphqls b/packages/graphql-gc-api/schema/PageHead.graphqls new file mode 100644 index 00000000000..2e5669df19c --- /dev/null +++ b/packages/graphql-gc-api/schema/PageHead.graphqls @@ -0,0 +1,89 @@ +""" +A meta tag that can be added to the head of the page. +""" +type GcMetaTag { + """ + Any meta tag name, see https://developers.google.com/search/docs/crawling-indexing/special-tags for documentation. + """ + name: String! + + """ + https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#directives + """ + content: String! +} + +type GcLinkRelAlternateTag { + """ + Used to specify the language of the target URL. Usually used in conjunction with rel="alternate" + + https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#hreflang + """ + hreflang: String + + """ + A fully qualified URL + + OR + + The relative part of the canonical URL. + As defined by: [pathname](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname) + [search](https://developer.mozilla.org/en-US/docs/Web/API/URL/search) + """ + href: String! +} + +type GcLinkRelTag { + """ + The type of link relationship. + + https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel + + Examples: + - canonical + - alternate + """ + rel: String! + + """ + Used to specify the language of the target URL. Usually used in conjunction with rel="alternate" + + https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#hreflang + """ + hreflang: String + + """ + A fully qualified URL + + OR + + The relative part of the canonical URL. + As defined by: [pathname](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname) + [search](https://developer.mozilla.org/en-US/docs/Web/API/URL/search) + """ + href: String! +} + +type PageHead { + """ + element of the page + """ + title: String! + + """ + <meta type"description" content=""> + """ + description: String + + canonical: GcLinkRelAlternateTag! + alternate: [GcLinkRelAlternateTag!] + + robots: [GcMetaTag!] + + """ + Additional link rel tags that can be added to the head of the page. + """ + link: [GcLinkRelTag!] + """ + Additional meta tags that can be added to the head of the page. + """ + meta: [GcMetaTag!] +} diff --git a/packages/graphql-gc-api/schema/PageRedirect.graphqls b/packages/graphql-gc-api/schema/PageRedirect.graphqls new file mode 100644 index 00000000000..e3299ba41e3 --- /dev/null +++ b/packages/graphql-gc-api/schema/PageRedirect.graphqls @@ -0,0 +1,16 @@ +type PageRedirect { + """ + A fully qualified URL + + OR + + The relative part of the canonical URL. + As defined by: [pathname](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname) + [search](https://developer.mozilla.org/en-US/docs/Web/API/URL/search) + """ + destination: String! + + """ + Defines if the redirect is permanent or temporary. + """ + permanent: Boolean! +} diff --git a/packages/graphql-gc-api/schema/ProductInterface_gcPage.graphqls b/packages/graphql-gc-api/schema/ProductInterface_gcPage.graphqls new file mode 100644 index 00000000000..9d11801afaa --- /dev/null +++ b/packages/graphql-gc-api/schema/ProductInterface_gcPage.graphqls @@ -0,0 +1,31 @@ +interface ProductInterface { + page: Page +} + +type BundleProduct { + page: Page +} + +type SimpleProduct { + page: Page +} + +type VirtualProduct { + page: Page +} + +type DownloadableProduct { + page: Page +} + +type GiftCardProduct { + page: Page +} + +type ConfigurableProduct { + page: Page +} + +type GroupedProduct { + page: Page +} diff --git a/packages/graphql-gc-api/schema/Query_page.graphqls b/packages/graphql-gc-api/schema/Query_page.graphqls new file mode 100644 index 00000000000..a3fc2ef6d18 --- /dev/null +++ b/packages/graphql-gc-api/schema/Query_page.graphqls @@ -0,0 +1,7 @@ +input GCPageInput { + href: String! +} + +extend type Query { + page(input: GCPageInput!): Page +} diff --git a/packages/graphql-gc-api/schema/Query_pageCanonicals.graphqls b/packages/graphql-gc-api/schema/Query_pageCanonicals.graphqls new file mode 100644 index 00000000000..53585f3013f --- /dev/null +++ b/packages/graphql-gc-api/schema/Query_pageCanonicals.graphqls @@ -0,0 +1,14 @@ +type PagesListOutput { + canonicals: [GcLinkRelTag!] +} + +input PagesListInput { + """ + Provide a base path to filter the list of pages. + """ + start_with: String +} + +extend type Query { + pageCanonicals(input: PagesListInput): PagesListOutput +} diff --git a/packages/graphql-gc-api/schema/Query_pageLayout.graphqls b/packages/graphql-gc-api/schema/Query_pageLayout.graphqls new file mode 100644 index 00000000000..0d13ed9506a --- /dev/null +++ b/packages/graphql-gc-api/schema/Query_pageLayout.graphqls @@ -0,0 +1,11 @@ +input PageLayoutInput { + scope: String! +} + +type PageLayout { + menu: GcMenu +} + +extend type Query { + pageLayout(input: PageLayoutInput!): PageLayout +} diff --git a/packages/graphql-gc-api/server.ts b/packages/graphql-gc-api/server.ts new file mode 100644 index 00000000000..336ce12bb91 --- /dev/null +++ b/packages/graphql-gc-api/server.ts @@ -0,0 +1 @@ +export {} diff --git a/packages/graphql-gc-api/utils/Page_Redirect.graphql b/packages/graphql-gc-api/utils/Page_Redirect.graphql new file mode 100644 index 00000000000..32dedc1357d --- /dev/null +++ b/packages/graphql-gc-api/utils/Page_Redirect.graphql @@ -0,0 +1,6 @@ +fragment Page_Redirect on Page { + redirect { + destination + permanent + } +} diff --git a/packages/graphql-gc-api/utils/index.ts b/packages/graphql-gc-api/utils/index.ts new file mode 100644 index 00000000000..f30597ff500 --- /dev/null +++ b/packages/graphql-gc-api/utils/index.ts @@ -0,0 +1,2 @@ +export * from './Page_Redirect.gql' +export * from './pageRedirectOrNotFound' diff --git a/packages/graphql-gc-api/utils/pageRedirectOrNotFound.ts b/packages/graphql-gc-api/utils/pageRedirectOrNotFound.ts new file mode 100644 index 00000000000..eeabc57941c --- /dev/null +++ b/packages/graphql-gc-api/utils/pageRedirectOrNotFound.ts @@ -0,0 +1,52 @@ +import type { SimplifyDeep, SetRequired } from 'type-fest' +import type { PageQuery } from '../components/Page/Page.gql' +import { Redirect } from 'next' + +type FoundPage = NonNullable<PageQuery['page']> + +export type Page = { + page: Omit<FoundPage, 'redirect'> +} + +export type PageRedirect = { + page: { redirect: NonNullable<FoundPage['redirect']> } +} + +export type PageNotFound = { page?: null | undefined } + +export function isPageNotFound( + query: Page | PageRedirect | PageNotFound | null | undefined, +): query is PageNotFound { + return !query?.page +} + +export function isPageRedirect( + query: Page | PageRedirect | PageNotFound | null | undefined, +): query is PageRedirect { + return Boolean(query?.page && 'redirect' in query.page && query.page.redirect) +} + +export function isPageFound( + query: Page | PageRedirect | PageNotFound | null | undefined, +): query is Page { + return !isPageNotFound(query) && !isPageRedirect(query) +} + +export function pageRedirect(query: PageQuery | null | undefined): { + redirect: Redirect + revalidate?: number | boolean +} { + if (isPageRedirect(query)) return { redirect: query.page.redirect } + throw Error('pageRedirect should only be called when it isPageRedirect(query) returns true') +} + +export function pageRedirectOrNotFound( + query: PageQuery | null | undefined, +): { redirect: Redirect; revalidate?: number | boolean } | { notFound: true } { + if (isPageRedirect(query)) return { redirect: query.page.redirect } + if (isPageNotFound(query)) return { notFound: true } + + throw Error( + 'pageRedirectOrNotFound should only be called when it isPageFound(query) returns false', + ) +} diff --git a/packages/graphql-mesh/directive/mesh.graphqls b/packages/graphql-mesh/directive/mesh.graphqls new file mode 100644 index 00000000000..f609dc4ddbf --- /dev/null +++ b/packages/graphql-mesh/directive/mesh.graphqls @@ -0,0 +1,21 @@ +scalar ResolveToSourceArgs +directive @resolveTo( + requiredSelectionSet: String + sourceName: String! + sourceTypeName: String! + sourceFieldName: String! + sourceSelectionSet: String + sourceArgs: ResolveToSourceArgs + keyField: String + keysArg: String + pubsubTopic: String + filterBy: String + additionalArgs: ResolveToSourceArgs + result: String + resultType: String +) on FIELD_DEFINITION + +enum CacheControlScope { + PUBLIC + PRIVATE +} diff --git a/packages/graphql-mesh/index.ts b/packages/graphql-mesh/index.ts index 51ceed6c09e..0d1d0311064 100644 --- a/packages/graphql-mesh/index.ts +++ b/packages/graphql-mesh/index.ts @@ -1,4 +1,9 @@ export * from './api/createEnvelop' export * from './api/apolloLink' export * from './.mesh' +export * from './utils/executeMesh' +export * from './utils/meshCache' +export * from './utils/selectionSetByPath' +export * from './utils/applySelectionSetsFromResolvers' +export * from './utils/delegateToSchemaSdk' export * from './utils/traverseSelectionSet' diff --git a/packages/graphql-mesh/package.json b/packages/graphql-mesh/package.json index a015cdfbb71..f2f39b14ffe 100644 --- a/packages/graphql-mesh/package.json +++ b/packages/graphql-mesh/package.json @@ -5,6 +5,7 @@ "version": "9.0.0-canary.72", "main": "index.ts", "dependencies": { + "@envelop/response-cache": "^6.2.1", "@graphql-mesh/apollo-link": "latest", "@graphql-mesh/config": "latest", "@graphql-mesh/cross-helpers": "latest", @@ -16,6 +17,7 @@ "@graphql-mesh/runtime": "latest", "@graphql-mesh/store": "latest", "@graphql-mesh/transform-encapsulate": "latest", + "@graphql-mesh/transform-federation": "latest", "@graphql-mesh/transform-filter-schema": "latest", "@graphql-mesh/transform-hoist-field": "latest", "@graphql-mesh/transform-naming-convention": "latest", diff --git a/packages/graphql-mesh/plugin/forward-headers.ts b/packages/graphql-mesh/plugin/forward-headers.ts index 2337f2a910c..038930996d5 100644 --- a/packages/graphql-mesh/plugin/forward-headers.ts +++ b/packages/graphql-mesh/plugin/forward-headers.ts @@ -1,6 +1,7 @@ import { isAsyncIterable } from '@envelop/core' import { MeshPlugin, MeshPluginOptions } from '@graphql-mesh/types' import type { MeshContext } from '../.mesh' +import { GraphQLSchema } from 'graphql' interface ForwardHeaderConfig { forwardHeaders?: string[] diff --git a/packages/graphql-mesh/utils/applySelectionSetsFromResolvers.ts b/packages/graphql-mesh/utils/applySelectionSetsFromResolvers.ts new file mode 100644 index 00000000000..f91a9c9d4b3 --- /dev/null +++ b/packages/graphql-mesh/utils/applySelectionSetsFromResolvers.ts @@ -0,0 +1,55 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { Resolver, selectionSetByPath, type Resolvers } from '@graphcommerce/graphql-mesh' +import { parseSelectionSet } from '@graphql-tools/utils' +import { + Kind, + FieldNode, + SelectionNode, + GraphQLResolveInfo, + GraphQLObjectType, + responsePathAsArray, +} from 'graphql' + +export function applySelectionSetsFromResolvers(info: GraphQLResolveInfo, resolvers: Resolvers) { + let name: string + if (info.returnType instanceof GraphQLObjectType) { + name = info.returnType.name + } else throw new Error('Could not determine returnType for selection set') + + const selectionSet = selectionSetByPath( + info.operation.selectionSet, + responsePathAsArray(info.path).join('.'), + ) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.entries(resolvers[name]).forEach(([field, resolverValue]) => { + const resolver = resolverValue as Resolver<unknown> + + const idx = selectionSet.selections.findIndex( + (selection) => selection.kind === Kind.FIELD && selection.name.value === field, + ) + + const currentSelectionSet = selectionSet.selections.find( + (selection) => selection.kind === Kind.FIELD && selection.name.value === field, + ) as FieldNode | undefined + + let replaceWith: readonly SelectionNode[] = [] + + if ('selectionSet' in resolver) { + if (typeof resolver.selectionSet === 'function' && currentSelectionSet) { + replaceWith = resolver.selectionSet(currentSelectionSet).selections + } + if (typeof resolver.selectionSet === 'string') { + replaceWith = parseSelectionSet(resolver.selectionSet).selections + } + } + + selectionSet.selections = [ + ...selectionSet.selections.slice(0, idx), + ...replaceWith, + ...selectionSet.selections.slice(idx + 1), + ] + }) + + return selectionSet +} diff --git a/packages/graphql-mesh/utils/delegateToSchemaSdk.ts b/packages/graphql-mesh/utils/delegateToSchemaSdk.ts new file mode 100644 index 00000000000..ad7d950e1dd --- /dev/null +++ b/packages/graphql-mesh/utils/delegateToSchemaSdk.ts @@ -0,0 +1,65 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { InContextSdkMethod } from '@graphql-mesh/types' +import { delegateToSchema } from '@graphql-tools/delegate' +// import { batchDelegateToSchema } from '@graphql-tools/batch-delegate' +import { GraphQLResolveInfo, OperationTypeNode, responsePathAsArray, print } from 'graphql' +import { MeshContext } from '../.mesh' +import { renameFromAliasToRegular, selectionSetByPath } from './selectionSetByPath' + +export function delegateToSchemaSdk< + SourceName extends keyof MeshContext, + SourceTypeName extends keyof MeshContext[SourceName], + SourceFieldName extends keyof MeshContext[SourceName][SourceTypeName], +>(options: { + context: MeshContext + info: GraphQLResolveInfo + sourceName: SourceName + sourceTypeName: SourceTypeName + sourceFieldName: SourceFieldName + sourceArgs: MeshContext[SourceName][SourceTypeName][SourceFieldName] extends InContextSdkMethod< + unknown, + infer A + > + ? A + : unknown +}): MeshContext[SourceName][SourceTypeName][SourceFieldName] extends InContextSdkMethod<infer R> + ? Promise<R> + : unknown { + const { context, info, sourceArgs, sourceFieldName, sourceTypeName } = options + + const operationMap: Record<string, OperationTypeNode> = { + Query: OperationTypeNode.QUERY, + Mutation: OperationTypeNode.MUTATION, + Subscription: OperationTypeNode.SUBSCRIPTION, + } + + return delegateToSchema({ + context, + info, + schema: info.schema, + operation: operationMap[sourceTypeName as string], + fieldName: sourceFieldName as string, + args: sourceArgs as Record<string, any>, + selectionSet: selectionSetByPath( + info.operation.selectionSet, + responsePathAsArray(info.path).join('.'), + ), + validateRequest: false, + transforms: [ + { + transformResult: (result) => { + renameFromAliasToRegular( + selectionSetByPath( + info.operation.selectionSet, + responsePathAsArray(info.path).join('.'), + ), + result.data[sourceFieldName], + ) + + console.dir(result.data[sourceFieldName], { depth: 5 }) + return result + }, + }, + ], + }) +} diff --git a/packages/graphql-mesh/utils/executeMesh.ts b/packages/graphql-mesh/utils/executeMesh.ts new file mode 100644 index 00000000000..daa48fc4ba2 --- /dev/null +++ b/packages/graphql-mesh/utils/executeMesh.ts @@ -0,0 +1,159 @@ +import { defaultBuildResponseCacheKey } from '@envelop/response-cache' +import type { TypedDocumentNode } from '@graphcommerce/graphql' +import type { MeshContext } from '@graphcommerce/graphql-mesh' +import { InContextSdkMethod, InContextSdkMethodRegularParams } from '@graphql-mesh/types' +import { + Kind, + OperationTypeNode, + OperationDefinitionNode, + FieldNode, + print, + ValueNode, + GraphQLResolveInfo, +} from 'graphql' +import type { Path } from 'react-hook-form' +import { meshCache, MeshCacheOptions } from './meshCache' +import { selectionSetByPath } from './selectionSetByPath' + +type OperationType = 'Query' | 'Mutation' | 'Subscription' +function getOperationType(def: OperationDefinitionNode) { + let operationType: OperationType | undefined + switch (def.operation) { + case OperationTypeNode.QUERY: + operationType = 'Query' + break + case OperationTypeNode.MUTATION: + operationType = 'Mutation' + break + case OperationTypeNode.SUBSCRIPTION: + operationType = 'Subscription' + break + } + return operationType +} + +function traverseValueNode<V extends Record<string, unknown>>( + value: ValueNode, + variables: V, +): unknown { + switch (value.kind) { + case Kind.NULL: + return null + case Kind.VARIABLE: + return variables[value.name.value] + case Kind.LIST: + return value.values.map((v) => traverseValueNode(v, variables)) + case Kind.OBJECT: + return value.fields.map((f) => ({ + [f.name.value]: traverseValueNode(f.value, variables), + })) + default: + return value.value + } +} + +function collectArguments<V extends Record<string, unknown>>(field: FieldNode, variables: V) { + if (!field?.arguments || field?.arguments.length === 0) return {} + const args: Record<string, unknown> = {} + field.arguments?.forEach((argument) => { + args[argument.name.value] = traverseValueNode<V>(argument.value, variables) + }, field.arguments) + return args +} + +function maybeExecutor( + context: MeshContext, + operationType: OperationType, + operationName: string, +): InContextSdkMethod | undefined { + const possibleLocations = Object.keys(context) + + let found: InContextSdkMethod | undefined + for (const location of possibleLocations) { + const ctx = context as unknown as Record< + string, + Record<string, Record<string, InContextSdkMethod>> + > + if (ctx[location]?.[operationType]?.[operationName]) { + found = ctx[location][operationType][operationName] + } + } + + if (!found) return undefined + + return (params) => { + console.warn( + '@todo executeMesh does not properly apply args', + (params as InContextSdkMethodRegularParams<any, any, any>).args, + ) + return found(params) + } +} + +async function executeMesh<Q, V extends Record<string, unknown>>( + document: TypedDocumentNode<Q, V>, + options: { variables: V }, + context: MeshContext, + root: object, + info: GraphQLResolveInfo, +): Promise<Q> { + const des = document.definitions.map(async (def) => { + if (def.kind !== Kind.OPERATION_DEFINITION) return null + + const executions = def.selectionSet.selections + .filter((s) => s.kind === Kind.FIELD) + .map( + async (field) => + [ + field.name.value, + await maybeExecutor( + context, + getOperationType(def), + field.name.value, + )?.({ + root, + info, + context, + selectionSet: selectionSetByPath<Q>(def.selectionSet, field.name.value as Path<Q>), + args: collectArguments<V>(field, options.variables), + }), + ] as const, + ) + + return Promise.all(executions) + }) + + const flattenedEntries = (await Promise.all(des)).flat(1).filter((v) => !!v) + return Object.fromEntries(flattenedEntries) as Q +} + +export function createDocumentExecutor( + context: MeshContext, + root: object, + info: GraphQLResolveInfo, +) { + const headers = (((context as any).headers ?? {}) as Record<string, unknown>) ?? {} + + return async <Q, V extends Record<string, unknown>>( + document: TypedDocumentNode<Q, V>, + options: { variables: V; headers: string[] } & Pick<MeshCacheOptions, 'ttl'>, + ) => { + const { ttl, ...rest } = options + + const cacheKey = await defaultBuildResponseCacheKey({ + documentString: print(document), + variableValues: { + // @todo make sure we automatically select the correct headers. + ...options.variables, + ...Object.fromEntries(options.headers.map((h) => [h, headers[h]])), + }, + sessionId: headers?.authorization ? String(headers?.authorization) : null, + }) + + return meshCache(() => executeMesh<Q, V>(document, rest, context, root, info), { + ttl, + context, + cacheKey, + }) + } +} diff --git a/packages/graphql-mesh/utils/meshCache.ts b/packages/graphql-mesh/utils/meshCache.ts new file mode 100644 index 00000000000..1541a7e6886 --- /dev/null +++ b/packages/graphql-mesh/utils/meshCache.ts @@ -0,0 +1,35 @@ +import { type MeshContext } from '../.mesh' + +type CacheEntry = { + createdAt: number + value: any +} + +export type MeshCacheOptions = { + cacheKey: string + context: MeshContext + /** + * Number of seconds the cache value will remain valid. + */ + ttl: number +} + +export async function meshCache<T extends (...args: any[]) => Promise<any>>( + cb: T, + options: MeshCacheOptions, +): Promise<ReturnType<T>> { + const { cacheKey, context, ttl = 0 } = options + + const now = Date.now() / 1000 + + const { value, createdAt } = ((await context.cache.get(cacheKey)) as CacheEntry) ?? {} + + if (value && ttl && now - createdAt < ttl) return value + + const res = await cb() + if (ttl !== 0) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + context.cache.set(cacheKey, { createdAt: now, value: res }) + } + return res +} diff --git a/packages/graphql-mesh/utils/selectionSetByPath.ts b/packages/graphql-mesh/utils/selectionSetByPath.ts new file mode 100644 index 00000000000..f9d486399c2 --- /dev/null +++ b/packages/graphql-mesh/utils/selectionSetByPath.ts @@ -0,0 +1,89 @@ +import { isObjectLike } from '@graphql-tools/utils' +import { Kind, SelectionNode, SelectionSetNode, print } from 'graphql' +import type { Path } from 'react-hook-form' + +function isNumeric(n: string) { + return !Number.isNaN(parseFloat(n)) +} + +export function selectionSetByPath<Q = string>( + incomingSelectionSet: SelectionSetNode, + path: Path<Q>, +) { + const pathArray = path.split(/[,[\].]+?/) + let selectionSet = incomingSelectionSet + let pathIndex = 0 + + while (pathIndex < pathArray.length) { + let currentValue = pathArray[pathIndex] + + const isNegation = currentValue.startsWith('!') + currentValue = isNegation ? currentValue.slice(1) : currentValue + + if (!isNumeric(currentValue)) { + const newSelections: SelectionNode[] = [] + + for (const selection of selectionSet.selections) { + if (selection.kind === Kind.FIELD) { + const val = selection.alias?.value ?? selection.name.value + if (!isNegation && val === currentValue) { + newSelections.push(...(selection.selectionSet?.selections ?? [])) + } + + if (isNegation && val !== currentValue) { + newSelections.push(...(selection.selectionSet?.selections ?? [])) + } + } + } + + selectionSet = { + kind: Kind.SELECTION_SET, + selections: newSelections, + } + } + + pathIndex++ + } + + return selectionSet +} + +/** + * A FieldNode can have an alias field, but we want to remap the data back to its values without the aliases. + * + * This is a recursive function. + */ +export function renameFromAliasToRegular(selectionSet: SelectionSetNode, data: any): any { + if (!data) return data + + if (Array.isArray(data)) { + data.forEach((v: object) => renameFromAliasToRegular(selectionSet, v)) + } else if (isObjectLike(data)) { + for (const selection of selectionSet.selections) { + if (selection.kind === Kind.FIELD && selection.alias?.value) { + data[selection.name.value] = data[selection.alias?.value ?? selection.name.value] + delete data[selection.alias.value] + } + } + + for (const selection of selectionSet.selections) { + if (selection.kind === Kind.INLINE_FRAGMENT) { + const typeCondition = selection.typeCondition?.name.value + if ('__typename' in data) { + // console.log('ues typename', selection) + if (data.__typename === typeCondition) { + renameFromAliasToRegular(selection.selectionSet, data) + } + } else { + // We should have a typename, else we can not properly distribute the data + } + } + + if (selection.kind === Kind.FRAGMENT_SPREAD) { + // console.log(selection) + } + } + } + + return data +} diff --git a/packages/graphql/apollo-client.graphql b/packages/graphql/apollo-client.graphql index c3a624d5349..e7b7433d647 100644 --- a/packages/graphql/apollo-client.graphql +++ b/packages/graphql/apollo-client.graphql @@ -8,11 +8,6 @@ The @connection directive allows you to specify a custom cache key for paginated """ directive @connection(key: String, filter: [String]) on FIELD -""" -This directive enables your queries to receive data for specific fields incrementally, instead of receiving all field data at the same time. This is helpful whenever some fields in a query take much longer to resolve than others. -""" -directive @defer(if: Boolean! = true, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT - # """ # Directs the executor to stream plural fields when the `if` argument is true or undefined. # """ diff --git a/packages/graphql/components/GraphQLProvider/measurePerformanceLink.ts b/packages/graphql/components/GraphQLProvider/measurePerformanceLink.ts index ceaae13c6dd..65d273a2777 100644 --- a/packages/graphql/components/GraphQLProvider/measurePerformanceLink.ts +++ b/packages/graphql/components/GraphQLProvider/measurePerformanceLink.ts @@ -1,9 +1,9 @@ /* eslint-disable no-console */ import { ApolloLink } from '@apollo/client' -import type { MeshFetchHTTPInformation } from '@graphql-mesh/plugin-http-details-extensions' import { print } from '@apollo/client/utilities' +import type { MeshFetchHTTPInformation } from '@graphql-mesh/plugin-http-details-extensions' import { cliHyperlink } from '../../lib/hyperlinker' - +import { responsePathAsArray, stripIgnoredCharacters } from 'graphql' const running = new Map< string, { @@ -109,12 +109,9 @@ export const flushMeasurePerf = () => { // padd the items to the max length items.forEach((item) => { item.forEach((_, index) => { - const [str] = (Array.isArray(item[index]) ? item[index] : [item[index], item[index]]) as [ - string, - string, - ] + const [str] = Array.isArray(item[index]) ? item[index] : [item[index], item[index]] - const val = (Array.isArray(item[index]) ? item[index][1] : item[index]) as string + const val = Array.isArray(item[index]) ? item[index][1] : item[index] const padLength = colWidths[index] + (val.length - str.length) @@ -123,8 +120,16 @@ export const flushMeasurePerf = () => { }) // render the items to a string + + const jajajaj = [[''], ...items].map((item) => item.join(' ')) + // console.log(jajajaj) + + jajajaj.forEach((item) => { + console.log(item) + }) + const output = [[''], ...items].map((item) => item.join(' ')).join('\n') - console.log(output) + // console.log(output) running.clear() } @@ -157,43 +162,51 @@ export const measurePerformanceLink = new ApolloLink((operation, forward) => { const httpDetails: MeshFetchHTTPInformation[] | undefined = data.extensions?.httpDetails let additional = [``, ``] as [string, string] - if (httpDetails) { - httpDetails.forEach((d) => { - const requestUrl = new URL(d.request.url) - requestUrl.searchParams.delete('extensions') - const title = `${d.sourceName} ${d.responseTime}ms` - additional = [ - `${additional[0]} ${title}`, - `${additional[1]} ${cliHyperlink(title, requestUrl.toString().replace(/\+/g, '%20'))}`, - ] - }) - } // Called after server responds const query = [ `# Variables: ${JSON.stringify(operation.variables)}`, `# Headers: ${JSON.stringify(operation.getContext().headers)}`, - print(operation.query), + stripIgnoredCharacters(print(operation.query)), ].join('\n') - const meshUrl = new URL(`${import.meta.graphCommerce.canonicalBaseUrl}/api/graphql`) + const meshUrl = new URL( + process.env.NODE_ENV === 'production' + ? `${import.meta.graphCommerce.canonicalBaseUrl}/api/graphql` + : 'http://localhost:3000/api/graphql', + ) + meshUrl.searchParams.set('query', query) + running.delete(operationString) running.set(operationString, { start: operation.getContext().measurePerformanceLinkStart as Date, end: new Date(), - operationName: [operation.operationName, operation.operationName], + operationName: [ + operation.operationName, + operation.operationName, + // cliHyperlink(operation.operationName, meshUrl.toString()), + ], additional, - // [ - // operation.operationName, - // cliHyperlink(operation.operationName, meshUrl.toString()), - // ], - // additional: [ - // `🔗 ${additional[0]}`, - // `${cliHyperlink('🔗', meshUrl.toString())} ${additional[1]}`, - // ], }) + if (httpDetails) { + running.delete(operationString) + + httpDetails.forEach((d) => { + const requestUrl = new URL(d.request.url) + requestUrl.searchParams.delete('extensions') + + const key = `${operationString}.${responsePathAsArray(d.path).join('.')}` + const name = `${operation.operationName}.${responsePathAsArray(d.path).join('.')} (${d.sourceName})` + running.set(key, { + start: new Date(d.request.timestamp), + end: new Date(d.response.timestamp), + operationName: [name, name], + }) + }) + } + markTimeout() return data diff --git a/packages/graphql/hooks/useInContextQuery.ts b/packages/graphql/hooks/useInContextQuery.ts index def9b1be590..0783edbb945 100644 --- a/packages/graphql/hooks/useInContextQuery.ts +++ b/packages/graphql/hooks/useInContextQuery.ts @@ -41,7 +41,7 @@ export function useInContextQuery< const clientQuery = useQuery<Q, V>(document, { ...options, variables: { ...options.variables, context } as V, - skip: skip && !context, + skip: skip || !context, }) let { data } = clientQuery diff --git a/packages/hygraph-cli/Config.graphqls b/packages/hygraph-cli/Config.graphqls index b44c3dbb15b..916454f9194 100644 --- a/packages/hygraph-cli/Config.graphqls +++ b/packages/hygraph-cli/Config.graphqls @@ -38,12 +38,4 @@ extend input GraphCommerceConfig { API', this field is not required. """ hygraphProjectId: String - - """ - Hygraph Management API. **Only used for migrations.** - - Optional: If the hygraphEndpoint is configured with the 'High Performance Content - API', this field is not required. - """ - hygraphManagementApi: String } diff --git a/packages/hygraph-cli/dist/migrations/graphcommerce8to9.js b/packages/hygraph-cli/dist/migrations/graphcommerce8to9.js index 45cd2866f87..319f297e9c7 100644 --- a/packages/hygraph-cli/dist/migrations/graphcommerce8to9.js +++ b/packages/hygraph-cli/dist/migrations/graphcommerce8to9.js @@ -16,12 +16,11 @@ const graphcommerce8to9 = async (schema, client) => { }); } const hasRowCategory = schema.models.some((m) => m.apiId === 'RowCategory'); - // if (!hasRowCategory) { migrationAction(schema, 'model', 'create', { apiId: 'RowCategory', displayName: 'Row Category', - apiIdPlural: 'RowProductLists', + apiIdPlural: 'RowCategories', description: 'A model that displays a category', }); migrationAction(schema, 'simpleField', 'create', { diff --git a/packages/hygraph-cli/dist/utils/getConfig.js b/packages/hygraph-cli/dist/utils/getConfig.js index 3279435dc73..b2b543b6b95 100644 --- a/packages/hygraph-cli/dist/utils/getConfig.js +++ b/packages/hygraph-cli/dist/utils/getConfig.js @@ -7,7 +7,7 @@ function getConfig(config) { throw new Error('Please provide GC_HYGRAPH_WRITE_ACCESS_TOKEN in your env file.'); } if (!projectId) { - projectId = new URL(hygraphEndpoint).pathname.split('/')?.[1]; + throw new Error('Please provide GC_HYGRAPH_PROJECT_ID in your env file.'); } if (!uri) { const endpoint = new URL(hygraphEndpoint); diff --git a/packages/hygraph-cli/src/migrations/graphcommerce8to9.ts b/packages/hygraph-cli/src/migrations/graphcommerce8to9.ts index 7f274e6d4b5..20d1d19cbb0 100644 --- a/packages/hygraph-cli/src/migrations/graphcommerce8to9.ts +++ b/packages/hygraph-cli/src/migrations/graphcommerce8to9.ts @@ -26,12 +26,11 @@ export const graphcommerce8to9: MigrationFunction = async (schema, client) => { const hasRowCategory = schema.models.some((m) => m.apiId === 'RowCategory') - // if (!hasRowCategory) { migrationAction(schema, 'model', 'create', { apiId: 'RowCategory', displayName: 'Row Category', - apiIdPlural: 'RowProductLists', + apiIdPlural: 'RowCategories', description: 'A model that displays a category', } satisfies BatchMigrationCreateModelInput) diff --git a/packages/hygraph-cli/src/utils/getConfig.ts b/packages/hygraph-cli/src/utils/getConfig.ts index b93059ece47..7da0449fe1b 100644 --- a/packages/hygraph-cli/src/utils/getConfig.ts +++ b/packages/hygraph-cli/src/utils/getConfig.ts @@ -19,7 +19,7 @@ export function getConfig(config: GraphCommerceConfig) { } if (!projectId) { - projectId = new URL(hygraphEndpoint).pathname.split('/')?.[1] + throw new Error('Please provide GC_HYGRAPH_PROJECT_ID in your env file.') } if (!uri) { diff --git a/packages/hygraph-dynamic-rows/graphql/AllDynamicRows.graphql b/packages/hygraph-dynamic-rows/graphql/AllDynamicRows.graphql index 05d10a8e4df..e3c94ef5bef 100644 --- a/packages/hygraph-dynamic-rows/graphql/AllDynamicRows.graphql +++ b/packages/hygraph-dynamic-rows/graphql/AllDynamicRows.graphql @@ -1,17 +1,18 @@ query AllDynamicRows($first: Int = 100, $skip: Int) { - dynamicRows(first: $first, skip: $skip) { - id - conditions { - __typename - ...ConditionNumber - ...ConditionText - ...ConditionAnd - } - } - - pagesConnection { + dynamicRowsConnection(first: $first, skip: $skip) { aggregate { count } + edges { + node { + id + conditions { + __typename + ...ConditionNumber + ...ConditionText + ...ConditionAnd + } + } + } } } diff --git a/packages/hygraph-dynamic-rows/graphql/DynamicRows.graphql b/packages/hygraph-dynamic-rows/graphql/DynamicRows.graphql index fd457770d6e..cf8d7512b88 100644 --- a/packages/hygraph-dynamic-rows/graphql/DynamicRows.graphql +++ b/packages/hygraph-dynamic-rows/graphql/DynamicRows.graphql @@ -1,5 +1,6 @@ query DynamicRows($rowIds: [ID!]!) { dynamicRows(where: { id_in: $rowIds }) { + id ...DynamicRow } } diff --git a/packages/hygraph-dynamic-rows/lib/getAllHygraphDynamicRows.ts b/packages/hygraph-dynamic-rows/lib/getAllHygraphDynamicRows.ts deleted file mode 100644 index 0d54e61c942..00000000000 --- a/packages/hygraph-dynamic-rows/lib/getAllHygraphDynamicRows.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { ApolloClient, NormalizedCacheObject, ApolloQueryResult } from '@apollo/client' -import { cacheFirst } from '@graphcommerce/graphql' -import { AllDynamicRowsDocument, AllDynamicRowsQuery } from '../graphql' - -type DynamicRows = AllDynamicRowsQuery['dynamicRows'] - -export async function getAllHygraphDynamicRows( - client: ApolloClient<NormalizedCacheObject>, - options: { pageSize?: number } = {}, -) { - const { pageSize = 100 } = options - - const query = client.query({ - query: AllDynamicRowsDocument, - variables: { first: pageSize }, - fetchPolicy: cacheFirst(client), - }) - - const pages: Promise<ApolloQueryResult<AllDynamicRowsQuery>>[] = [query] - - const { data } = await query - const totalPages = Math.ceil(data.pagesConnection.aggregate.count / pageSize) ?? 1 - if (totalPages > 1) { - for (let i = 2; i <= totalPages; i++) { - pages.push( - client.query({ - query: AllDynamicRowsDocument, - variables: { first: pageSize, skip: pageSize * (i - 1) }, - fetchPolicy: cacheFirst(client), - }), - ) - } - } - - const dynamicRows: DynamicRows = (await Promise.all(pages)) - .map((q) => q.data.dynamicRows) - .flat(1) - .map((row) => ({ - id: row.id, - conditions: row.conditions, - })) - - return dynamicRows -} diff --git a/packages/hygraph-dynamic-rows/lib/hygraphDynamicRows.ts b/packages/hygraph-dynamic-rows/lib/hygraphDynamicRows.ts index 2653802418e..a61991657b6 100644 --- a/packages/hygraph-dynamic-rows/lib/hygraphDynamicRows.ts +++ b/packages/hygraph-dynamic-rows/lib/hygraphDynamicRows.ts @@ -1,19 +1,17 @@ import { HygraphPagesQuery } from '@graphcommerce/graphcms-ui' -import { ApolloClient, NormalizedCacheObject, cacheFirst } from '@graphcommerce/graphql' import { ConditionTextFragment, ConditionNumberFragment, ConditionOrFragment, ConditionAndFragment, - DynamicRowsDocument, + DynamicRowsQuery, } from '../graphql' -import { getAllHygraphDynamicRows } from './getAllHygraphDynamicRows' /** * This generally works the same way as lodash get, however, when encountering an array it will * return all values. */ -function getByPath( +export function getByPath( value: unknown, query: string | Array<string | number>, ): (undefined | string | number | bigint)[] { @@ -47,7 +45,7 @@ function getByPath( } /** A recursive match function that is able to match a condition against the requested conditions. */ -function matchCondition( +export function matchCondition( condition: | ConditionTextFragment | ConditionNumberFragment @@ -81,44 +79,13 @@ function matchCondition( type Page = HygraphPagesQuery['pages'][number] -/** - * Fetch the page content for the given urls. - * - * - Uses an early bailout to check to reduce hygraph calls. - * - Implements an alias sytem to merge the content of multiple pages. - */ -export async function hygraphDynamicRows( - client: ApolloClient<NormalizedCacheObject>, - pageQuery: Promise<{ data: HygraphPagesQuery }>, - url: string, - cached: boolean, - additionalProperties?: Promise<object> | object, -): Promise<{ data: HygraphPagesQuery }> { - const fetchPolicy = cached ? cacheFirst(client) : undefined - - const allRoutes = await getAllHygraphDynamicRows(client) - - // Get the required rowIds from the conditions - const properties = { ...(await additionalProperties), url } - - const rowIds = allRoutes - .filter((availableDynamicRow) => - availableDynamicRow.conditions.some((condition) => matchCondition(condition, properties)), - ) - .map((row) => row.id) - const dynamicRows = - rowIds.length !== 0 - ? client.query({ query: DynamicRowsDocument, variables: { rowIds }, fetchPolicy }) - : undefined - - const [pageResult, dynamicResult] = await Promise.all([pageQuery, dynamicRows]) - - const page = pageResult.data.pages[0] as Page | undefined - +export function applyDynamicRows( + dynamicRows: DynamicRowsQuery['dynamicRows'], + incomingContent: Page['content'] = [], +) { // Create a copy of the content array. - const content = [...(page?.content ?? [])] - - dynamicResult?.data.dynamicRows.forEach((dynamicRow) => { + const content = [...incomingContent] + dynamicRows.forEach((dynamicRow) => { const { placement, target, rows, row } = dynamicRow if (!rows && !row) return @@ -137,24 +104,5 @@ export async function hygraphDynamicRows( if (placement === 'REPLACE') content.splice(targetIdx, 1, ...rowsToMerge) }) - if (!content.length) return pageResult - - const dynamicPage: Page = { - id: 'dynamic-page', - __typename: 'Page', - metaRobots: 'INDEX_FOLLOW', - metaTitle: '', - metaDescription: '', - url: '', - content: [], - relatedPages: [], - } - - // Return the merged page result. - return { - data: { - ...pageResult.data, - pages: [{ ...dynamicPage, ...page, content }], - }, - } + return content } diff --git a/packages/hygraph-dynamic-rows/mesh/resolvers.ts b/packages/hygraph-dynamic-rows/mesh/resolvers.ts new file mode 100644 index 00000000000..c7d6af56e23 --- /dev/null +++ b/packages/hygraph-dynamic-rows/mesh/resolvers.ts @@ -0,0 +1,79 @@ +/* eslint-disable @typescript-eslint/require-await */ +import { getProduct } from '@graphcommerce/graphcms-ui/mesh/resolvers' +import { + createDocumentExecutor, + type MeshContext, + type Resolvers, +} from '@graphcommerce/graphql-mesh' +import type { GraphQLResolveInfo } from 'graphql' +import { AllDynamicRowsDocument, AllDynamicRowsQuery } from '../graphql/AllDynamicRows.gql' +import { DynamicRowsDocument } from '../graphql/DynamicRows.gql' +import { applyDynamicRows, matchCondition } from '../lib/hygraphDynamicRows' + +async function getAllHygraphDynamicRows( + context: MeshContext, + options: { pageSize?: number; ttl: number }, + parent: object, + info: GraphQLResolveInfo, +) { + const { pageSize = 100, ttl } = options + + const execute = createDocumentExecutor(context, parent, info) + + const query = execute(AllDynamicRowsDocument, { + variables: { first: 100, skip: 0 }, + ttl, + headers: ['gcms-stage', 'gcms-locale'], + }) + + const pages: Promise<AllDynamicRowsQuery>[] = [query] + const data = await query + const totalPages = Math.ceil(data.dynamicRowsConnection.aggregate.count / pageSize) ?? 1 + if (totalPages > 1) { + for (let i = 2; i <= totalPages; i++) { + pages.push( + execute(AllDynamicRowsDocument, { + variables: { first: pageSize, skip: pageSize * (i - 1) }, + ttl, + headers: ['gcms-stage', 'gcms-locale'], + }), + ) + } + } + + return (await Promise.all(pages)) + .map((q) => q.dynamicRowsConnection.edges) + .flat(1) + .map(({ node }) => ({ id: node.id, conditions: node.conditions })) +} + +export const resolvers: Resolvers = { + // Page: { + // rows: { + // resolve: async (parent, args, context, info) => { + // const execute = createDocumentExecutor(context, parent, info) + // const allRoutes = await getAllHygraphDynamicRows(context, { ttl: 60 * 60 }, parent, info) + // const rowIds = allRoutes + // .filter((availableDynamicRow) => + // availableDynamicRow.conditions.some((condition) => + // matchCondition(condition, { + // ...parent, + // // ...args, + // ...getProduct(context, parent.url?.slice(2)), + // }), + // ), + // ) + // .map((row) => row.id) + // if (rowIds.length === 0) return parent.content + // const dynamicRows = await execute(DynamicRowsDocument, { + // variables: { rowIds }, + // ttl: 60 * 60, + // headers: ['gcms-stage', 'gcms-locale'], + // }) + // const rows = dynamicRows.dynamicRows.filter((row) => rowIds.includes(row.id)) + // if (rows.length === 0) return parent.content + // return applyDynamicRows(rows, parent.content) + // }, + // }, + // }, +} diff --git a/packages/hygraph-dynamic-rows/package.json b/packages/hygraph-dynamic-rows/package.json index 9cc79df7c28..7a14651212c 100644 --- a/packages/hygraph-dynamic-rows/package.json +++ b/packages/hygraph-dynamic-rows/package.json @@ -15,6 +15,7 @@ "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.72", "@graphcommerce/graphcms-ui": "^9.0.0-canary.72", "@graphcommerce/graphql": "^9.0.0-canary.72", + "@graphcommerce/graphql-mesh": "^9.0.0-canary.72", "@graphcommerce/image": "^9.0.0-canary.72", "@graphcommerce/next-ui": "^9.0.0-canary.72", "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.72", diff --git a/packages/hygraph-dynamic-rows/plugins/meshConfigDynamicRows.ts b/packages/hygraph-dynamic-rows/plugins/meshConfigDynamicRows.ts new file mode 100644 index 00000000000..b3c3c5cd83a --- /dev/null +++ b/packages/hygraph-dynamic-rows/plugins/meshConfigDynamicRows.ts @@ -0,0 +1,18 @@ +import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config' +import type { MeshConfigFunction } from '@graphcommerce/graphql-mesh/meshConfig' + +export const config: PluginConfig = { + module: '@graphcommerce/graphql-mesh/meshConfig', + type: 'function', +} + +export const meshConfig: FunctionPlugin<MeshConfigFunction> = (prev, conf, graphCommerceConfig) => { + const result = prev(conf, graphCommerceConfig) + return { + ...result, + additionalResolvers: [ + ...(result.additionalResolvers ?? []), + '@graphcommerce/hygraph-dynamic-rows/mesh/resolvers.ts', + ], + } +} diff --git a/packages/hygraph-ui/components/Asset/Asset.tsx b/packages/hygraph-ui/components/Asset/Asset.tsx index 06a3d67634b..a84888e4bc0 100644 --- a/packages/hygraph-ui/components/Asset/Asset.tsx +++ b/packages/hygraph-ui/components/Asset/Asset.tsx @@ -48,7 +48,7 @@ export const Asset = memo<AssetProps>((props) => { muted loop playsInline - disableRemotePlayback + // disableRemotePlayback sx={[...(Array.isArray(sx) ? sx : [sx])]} /> ) diff --git a/packages/hygraph-ui/components/index.ts b/packages/hygraph-ui/components/index.ts index ba63b58aa0a..e8d874cff84 100644 --- a/packages/hygraph-ui/components/index.ts +++ b/packages/hygraph-ui/components/index.ts @@ -1,2 +1,2 @@ -export * from './RichText' +export * from './RichText/index' export * from './Asset/Asset' diff --git a/packages/hygraph-ui/graphql/HygraphPage.graphql b/packages/hygraph-ui/graphql/HygraphPage.graphql index edf19d40d10..a6cd6b418c9 100644 --- a/packages/hygraph-ui/graphql/HygraphPage.graphql +++ b/packages/hygraph-ui/graphql/HygraphPage.graphql @@ -1,9 +1,4 @@ -fragment HygraphPage on Page { - title - metaTitle - metaDescription - metaRobots - url +fragment HygraphPage on Page @inject(into: ["PageHead"]) { author date relatedPages { @@ -13,4 +8,7 @@ fragment HygraphPage on Page { asset { ...Asset } + content { + __typename + } } diff --git a/packages/hygraph-ui/graphql/HygraphPages.graphql b/packages/hygraph-ui/graphql/HygraphPages.graphql deleted file mode 100644 index 535515a9c45..00000000000 --- a/packages/hygraph-ui/graphql/HygraphPages.graphql +++ /dev/null @@ -1,7 +0,0 @@ -query HygraphPages($url: String!) { - pages(where: { url: $url }) { - id - __typename - ...HygraphPage - } -} diff --git a/packages/hygraph-ui/graphql/index.ts b/packages/hygraph-ui/graphql/index.ts index e245b1dc74d..36958d67013 100644 --- a/packages/hygraph-ui/graphql/index.ts +++ b/packages/hygraph-ui/graphql/index.ts @@ -1,5 +1,4 @@ export * from './HygraphAllPages.gql' export * from './HygraphPage.gql' -export * from './HygraphPages.gql' export * from './PageLink.gql' export * from './PagesStaticPaths.gql' diff --git a/packages/hygraph-ui/lib/hygraphPageContent.ts b/packages/hygraph-ui/lib/hygraphPageContent.ts index 6c6e7e46a65..4dc413cc97d 100644 --- a/packages/hygraph-ui/lib/hygraphPageContent.ts +++ b/packages/hygraph-ui/lib/hygraphPageContent.ts @@ -1,4 +1,4 @@ -import { ApolloClient, NormalizedCacheObject } from '@graphcommerce/graphql' +import type { ApolloClient, NormalizedCacheObject } from '@graphcommerce/graphql' import { HygraphPagesQuery, HygraphPagesDocument } from '../graphql' import { getAllHygraphPages } from './getAllHygraphPages' diff --git a/packages/hygraph-ui/mesh/resolvers.ts b/packages/hygraph-ui/mesh/resolvers.ts new file mode 100644 index 00000000000..a46acbf0563 --- /dev/null +++ b/packages/hygraph-ui/mesh/resolvers.ts @@ -0,0 +1,288 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { + GcMetaTag, + MeshContext, + MetaRobots, + PageContent, + ProductInterface, + ResolversTypes, + RowCategory, + RowProduct, + type Resolvers, +} from '@graphcommerce/graphql-mesh' +import { Kind, responsePathAsArray } from 'graphql' + +const normalizeUrl = (href: string) => { + const cleanedhref = href.replaceAll(/^\/|\/$/g, '') + return `/${cleanedhref === 'page/home' ? '' : cleanedhref}` +} + +const denormalizeUrl = (href: string) => { + const cleanedhref = href.replaceAll(/^\/|\/$/g, '') + return cleanedhref === '' ? 'page/home' : cleanedhref +} + +type GCPageResolver = NonNullable<Resolvers['SimpleProduct']>['page'] + +export const contextData = new WeakMap<MeshContext, Record<string, ProductInterface>>() + +function initContextData(context: MeshContext) { + let value = contextData.get(context) + if (!value) { + value = {} + contextData.set(context, value) + } + return value +} + +export function setProduct(context: MeshContext, key: string, entity: ProductInterface) { + const value = initContextData(context) + value[key] = entity +} +export function getProduct(context: MeshContext, key: string): ProductInterface | undefined { + const value = initContextData(context) + return value[key] +} + +function isRowProduct(row: PageContent): row is RowProduct { + return '__typename' in row && row.__typename === 'RowProduct' +} + +const pageProductResolver: GCPageResolver = { + selectionSet: `{url_key}`, + resolve: async (root, args, context, info) => { + if (!root.url_key) return null + + setProduct(context, root.url_key, root) + + // console.log(root) + return context.hygraph.Query.pages({ + context, + info, + root, + key: root.url_key, + argsFromKeys: (keys) => ({ + where: { + url_in: [ + 'product/global', + // ...keys.map((key) => `p/${key}`) + ], + }, + }), + valuesFromResults: (results, keys) => + keys.map((key) => { + const page = + // results.find((r) => r.url === `p/${key}`) ?? + results.find((r) => r.url === 'product/global') + if (!page) return null + + page.content.forEach((row) => { + if (isRowProduct(row)) row.identity = key + }) + + return page + }), + }) + }, +} + +export const resolvers: Resolvers = { + // Resolve the query `page` to a page with the given URL + Query: { + page: { + resolve: async (root, args, context, info) => { + const page = await context.hygraph.Query.pages({ + root, + info, + context, + args: { where: { url: denormalizeUrl(args.input.href) } }, + }).then((r) => r[0]) + + console.log('oage') + + function mapRowProduct(row: RowProduct): RowCategory & { __typename: 'RowCategory' } { + const newRow: Partial<RowCategory & { __typename: 'RowCategory' }> = { + __typename: 'RowCategory', + } + if (row.id) newRow.id = row.id + if (row.locale) newRow.locale = row.locale + if (row.stage) newRow.stage = row.stage + if (row.localizations) newRow.localizations = row.localizations.map(mapRowProduct) + if (row.identity) newRow.categoryUrl = row.identity + if (row.pages) newRow.pages = row.pages + if (row.variant) newRow.variant = row.variant as 'Swipeable' | 'Grid' + return newRow as RowCategory & { __typename: 'RowCategory' } + } + + page.content = page.content.map((row) => + isRowProduct(row) && row.variant && ['Swipeable', 'Grid'].includes(row.variant) + ? mapRowProduct(row) + : row, + ) + + return page + }, + }, + }, + + Page: { + content: { + resolve: (root) => { + console.log('content') + return root.content + }, + }, + rows: { + selectionSet: (root) => ({ + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'content' }, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: + root.selectionSet?.selections?.filter( + (selection) => + selection.kind !== Kind.FIELD || + !['product', 'category'].includes(selection.name.value), + ) ?? [], + }, + }, + ], + }), + resolve: (root) => { + console.log('rows') + return root.content + }, + }, + head: { + selectionSet: `{ + id + url + locale + metaTitle + metaDescription + metaRobots + localizations(includeCurrent: false) { + locale + url + metaRobots + } + }`, + resolve: ({ head, url, locale, metaRobots, metaDescription, metaTitle, localizations }) => { + function formatRobots(bots?: MetaRobots, name: string = 'robots'): GcMetaTag { + const robots: string[] = [] + if (bots?.includes('NOINDEX')) robots.push('noindex') + if (bots?.includes('NOFOLLOW')) robots.push('nofollow') + return { name, content: robots.length > 0 ? robots.join(', ') : 'all' } + } + + return ( + head ?? { + canonical: { rel: 'canonical', href: normalizeUrl(url), hreflang: locale }, + robots: [formatRobots(metaRobots)], + alternate: localizations + ?.filter((v) => !formatRobots(v.metaRobots).content.includes('noindex')) + ?.map((loc) => ({ + rel: 'alternate', + href: normalizeUrl(loc.url), + hreflang: loc.locale, + })), + title: metaTitle, + description: metaDescription, + } + ) + }, + }, + }, + + SimpleProduct: { page: pageProductResolver }, + ConfigurableProduct: { page: pageProductResolver }, + BundleProduct: { page: pageProductResolver }, + VirtualProduct: { page: pageProductResolver }, + DownloadableProduct: { page: pageProductResolver }, + GroupedProduct: { page: pageProductResolver }, + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore GiftCardProduct is only available in Commerce + GiftCardProduct: { page: pageProductResolver }, + + RowProduct: { + product: { + selectionSet: `{ identity }`, + resolve: async (root, args, context, info) => { + // When the RowProduct is request as part of a product query, we expect the product to be + // already resolved and available in the context + if (responsePathAsArray(info.path)[0] === 'products') return null + + console.log('RowProduct', root.identity, responsePathAsArray(info.path)) + + const result = await context.m2.Query.products({ + root, + info, + context, + selectionSet: (subtree) => ({ + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'items' }, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { kind: Kind.FIELD, name: { kind: Kind.NAME, value: 'url_key' } }, + ...subtree.selections, + ], + }, + }, + ], + }), + key: root.identity, + argsFromKeys: (keys) => ({ filter: { url_key: { in: keys } } }), + valuesFromResults: (results, keys) => + keys.map((key) => results?.items?.find((r) => r?.url_key === key) ?? null), + }) + + return (result ?? null) as ResolversTypes['ProductInterface'] | null + }, + }, + }, + + RowCategory: { + category: { + selectionSet: `{ categoryUrl }`, + resolve: async (root, args, context, info) => { + if (responsePathAsArray(info.path)[0] === 'products') return null + console.log('RowCategory', root.categoryUrl, responsePathAsArray(info.path)) + + const result = await context.m2.Query.categories({ + root, + info, + context, + key: root.categoryUrl, + selectionSet: (subtree) => ({ + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'items' }, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { kind: Kind.FIELD, name: { kind: Kind.NAME, value: 'url_path' } }, + ...subtree.selections, + ], + }, + }, + ], + }), + argsFromKeys: (keys) => ({ filters: { url_path: { in: keys } } }), + valuesFromResults: (results, keys) => + results?.items?.find((r) => r?.url_path === keys[0]) ?? null, + }) + return (result ?? null) as ResolversTypes['CategoryTree'] | null + }, + }, + }, +} diff --git a/packages/hygraph-ui/package.json b/packages/hygraph-ui/package.json index c7c80e6db62..8fc33b6fd5c 100644 --- a/packages/hygraph-ui/package.json +++ b/packages/hygraph-ui/package.json @@ -15,6 +15,7 @@ "@graphcommerce/ecommerce-ui": "^9.0.0-canary.72", "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.72", "@graphcommerce/graphql": "^9.0.0-canary.72", + "@graphcommerce/graphql-gc-api": "^9.0.0-canary.72", "@graphcommerce/image": "^9.0.0-canary.72", "@graphcommerce/next-ui": "^9.0.0-canary.72", "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.72", diff --git a/packages/hygraph-ui/plugins/HygraphPreviewModeToolbar.tsx b/packages/hygraph-ui/plugins/HygraphPreviewModeToolbar.tsx index 233ef5c0211..98c331717c1 100644 --- a/packages/hygraph-ui/plugins/HygraphPreviewModeToolbar.tsx +++ b/packages/hygraph-ui/plugins/HygraphPreviewModeToolbar.tsx @@ -65,7 +65,7 @@ const HygraphConfig = React.memo(() => { } /> ), - [contentStages.data?.__type.enumValues, contentStages.loading, control], + [contentStages.data?.__type.enumValues, contentStages.loading, control, defaultValue], ) }) diff --git a/packages/hygraph-ui/plugins/hygraphGraphqlConfig.ts b/packages/hygraph-ui/plugins/hygraphGraphqlConfig.ts index 934297fcb44..ab0e3677e3e 100644 --- a/packages/hygraph-ui/plugins/hygraphGraphqlConfig.ts +++ b/packages/hygraph-ui/plugins/hygraphGraphqlConfig.ts @@ -17,16 +17,12 @@ export const graphqlConfig: FunctionPlugin<typeof graphqlConfigType> = (prev, co const locales = config.storefront.hygraphLocales - if (!locales) return prev(config) - const hygraphLink = setContext((_, context) => { if (!context.headers) context.headers = {} - context.headers['gcms-locales'] = locales.join(',') + if (locales) context.headers['gcms-locales'] = locales.join(',') const stage = config.previewData?.hygraphStage ?? 'DRAFT' - if (config.preview) { - context.headers['gcms-stage'] = stage - } + if (config.preview) context.headers['gcms-stage'] = stage return context }) diff --git a/packages/hygraph-ui/plugins/meshConfigHygraph.ts b/packages/hygraph-ui/plugins/meshConfigHygraph.ts new file mode 100644 index 00000000000..d47164280c0 --- /dev/null +++ b/packages/hygraph-ui/plugins/meshConfigHygraph.ts @@ -0,0 +1,23 @@ +import type { meshConfig as meshConfigBase } from '@graphcommerce/graphql-mesh/meshConfig' +import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config' + +export const config: PluginConfig = { + module: '@graphcommerce/graphql-mesh/meshConfig', + type: 'function', +} + +export const meshConfig: FunctionPlugin<typeof meshConfigBase> = ( + prev, + baseConfig, + graphCommerceConfig, +) => + prev( + { + ...baseConfig, + additionalResolvers: [ + ...(baseConfig.additionalResolvers ?? []), + '@graphcommerce/graphcms-ui/mesh/resolvers', + ], + }, + graphCommerceConfig, + ) diff --git a/packages/hygraph-ui/schema/RowCategory_category.graphqls b/packages/hygraph-ui/schema/RowCategory_category.graphqls new file mode 100644 index 00000000000..0b356ad39f5 --- /dev/null +++ b/packages/hygraph-ui/schema/RowCategory_category.graphqls @@ -0,0 +1,3 @@ +type RowCategory { + category: CategoryTree +} diff --git a/packages/hygraph-ui/schema/RowProduct_product.graphqls b/packages/hygraph-ui/schema/RowProduct_product.graphqls new file mode 100644 index 00000000000..e71a44433fb --- /dev/null +++ b/packages/hygraph-ui/schema/RowProduct_product.graphqls @@ -0,0 +1,3 @@ +type RowProduct { + product: ProductInterface +} diff --git a/packages/magento-category/utils/findParentBreadcrumbItem.ts b/packages/magento-category/utils/findParentBreadcrumbItem.ts index 63b6a4240db..49c3554f95d 100644 --- a/packages/magento-category/utils/findParentBreadcrumbItem.ts +++ b/packages/magento-category/utils/findParentBreadcrumbItem.ts @@ -1,12 +1,10 @@ import { filterNonNullableKeys } from '@graphcommerce/next-ui' import { CategoryBreadcrumbFragment } from '../components/CategoryBreadcrumb' -export function findParentBreadcrumbItem( - category: CategoryBreadcrumbFragment | null | undefined, -): NonNullable<CategoryBreadcrumbFragment['breadcrumbs']>[number] | undefined { +export function findParentBreadcrumbItem(category: CategoryBreadcrumbFragment | null | undefined) { const parentCategoryPath = category?.url_path?.split('/').slice(0, -1).join('/') - return filterNonNullableKeys(category?.breadcrumbs, ['category_url_path']).find( + return filterNonNullableKeys(category?.breadcrumbs, ['category_url_path', 'category_name']).find( (c) => parentCategoryPath === c.category_url_path, ) } diff --git a/packages/magento-customer/link/xMagentoCacheIdHeader.ts b/packages/magento-customer/link/xMagentoCacheIdHeader.ts index fbed2e04e99..993b4dacd62 100644 --- a/packages/magento-customer/link/xMagentoCacheIdHeader.ts +++ b/packages/magento-customer/link/xMagentoCacheIdHeader.ts @@ -14,8 +14,11 @@ export const xMagentoCacheIdHeader = new ApolloLink((operation, forward) => { const { cache } = operation.getContext() if (!cache) return data - const xMagentoCacheId = (data.extensions as { forwardedHeaders: Record<string, string> }) - .forwardedHeaders['x-magento-cache-id'] + type Extensions = { forwardedHeaders: Record<string, string> } | null + const xMagentoCacheId = (data?.extensions as Extensions)?.forwardedHeaders?.[ + 'x-magento-cache-id' + ] + if (!xMagentoCacheId) return data const tokenResult = cache.readQuery({ query: CustomerTokenDocument }) diff --git a/packages/magento-product-configurable/ConfigurableProductPage.graphql b/packages/magento-product-configurable/ConfigurableProductPage.graphql deleted file mode 100644 index 717701fd9d9..00000000000 --- a/packages/magento-product-configurable/ConfigurableProductPage.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query ConfigurableProductPage($urlKey: String, $useCustomAttributes: Boolean = false) { - ...ProductPageConfigurableQueryFragment -} diff --git a/packages/magento-product-configurable/ProductPageConfigurableQueryFragment.graphql b/packages/magento-product-configurable/ProductPageConfigurableQueryFragment.graphql deleted file mode 100644 index 5548af258da..00000000000 --- a/packages/magento-product-configurable/ProductPageConfigurableQueryFragment.graphql +++ /dev/null @@ -1,10 +0,0 @@ -fragment ProductPageConfigurableQueryFragment on Query { - typeProducts: products(filter: { url_key: { eq: $urlKey } }) { - ...ProductSpecs - items { - __typename - uid - ...ConfigurableProductForm - } - } -} diff --git a/packages/magento-product-configurable/index.ts b/packages/magento-product-configurable/index.ts index 53675bc6984..a04bf43a934 100644 --- a/packages/magento-product-configurable/index.ts +++ b/packages/magento-product-configurable/index.ts @@ -2,7 +2,6 @@ export * from './components' export * from './ConfigurableCartItem/ConfigurableCartItem' export * from './ConfigurableContext/ConfigurableContext' export * from './ConfigurableProductAddToCart/ConfigurableProductAddToCart' -export * from './ConfigurableProductPage.gql' export * from './graphql' export * from './hooks' export * from './utils' diff --git a/packages/magento-product/components/ProductListItems/getFilterTypes.ts b/packages/magento-product/components/ProductListItems/getFilterTypes.ts index 90a1960cfb5..9c1567936e0 100644 --- a/packages/magento-product/components/ProductListItems/getFilterTypes.ts +++ b/packages/magento-product/components/ProductListItems/getFilterTypes.ts @@ -1,4 +1,5 @@ -import { gql, ApolloClient, NormalizedCacheObject, TypedDocumentNode } from '@graphcommerce/graphql' +import type { ApolloClient, NormalizedCacheObject, TypedDocumentNode } from '@graphcommerce/graphql' +import { gql } from '@graphcommerce/graphql/apollo' import type { AttributeFrontendInputEnum, Exact } from '@graphcommerce/graphql-mesh' import { filterNonNullableKeys, nonNullable } from '@graphcommerce/next-ui' import { ProductFilterTypesDocument } from './ProductFilterTypes.gql' diff --git a/packages/magento-product/components/ProductPage/ProductPageQueryFragment.graphql b/packages/magento-product/components/ProductPage/ProductPageQueryFragment.graphql deleted file mode 100644 index 5065212fa21..00000000000 --- a/packages/magento-product/components/ProductPage/ProductPageQueryFragment.graphql +++ /dev/null @@ -1,11 +0,0 @@ -fragment ProductPageQueryFragment on Query { - products(filter: { url_key: { eq: $urlKey } }) { - ...ProductSpecs - items { - __typename - uid - ...ProductWeight - ...ProductPageItem - } - } -} diff --git a/packages/magento-product/components/ProductSpecs/ProductSpecs.graphql b/packages/magento-product/components/ProductSpecs/ProductSpecs.graphql index 130f93aba23..1734a5ae5bc 100644 --- a/packages/magento-product/components/ProductSpecs/ProductSpecs.graphql +++ b/packages/magento-product/components/ProductSpecs/ProductSpecs.graphql @@ -1,31 +1,16 @@ -fragment ProductSpecs on Products { - aggregations @skip(if: $useCustomAttributes) { - attribute_code - count - label - options { - count - label - value - } - } - items { - __typename - uid - ...ProductListItem - custom_attributesV2(filters: { is_visible_on_front: true }) @include(if: $useCustomAttributes) { - items { - code - __typename - ... on AttributeValue { +fragment ProductSpecs on ProductInterface { + custom_attributesV2(filters: { is_visible_on_front: true }) { + items { + code + __typename + ... on AttributeValue { + value + } + ... on AttributeSelectedOptions { + selected_options { + label value } - ... on AttributeSelectedOptions { - selected_options { - label - value - } - } } } } diff --git a/packages/magento-product/components/ProductSpecs/ProductSpecs.tsx b/packages/magento-product/components/ProductSpecs/ProductSpecs.tsx index 46cf82e37e1..acb2c13278b 100644 --- a/packages/magento-product/components/ProductSpecs/ProductSpecs.tsx +++ b/packages/magento-product/components/ProductSpecs/ProductSpecs.tsx @@ -1,27 +1,25 @@ +import { useQuery } from '@graphcommerce/graphql' import { responsiveVal, Row, SectionContainer, extendableComponent } from '@graphcommerce/next-ui' import { Box, SxProps, Theme } from '@mui/material' import { ProductSpecsFragment } from './ProductSpecs.gql' -import { ProductSpecsAggregations } from './ProductSpecsAggregations' -import { ProductSpecsCustomAttributes } from './ProductSpecsCustomAttributes' +import { ProductSpecsTypesDocument } from './ProductSpecsTypes.gql' -export type ProductSpecsProps = ProductSpecsFragment & { +export type ProductSpecsProps = { title?: string sx?: SxProps<Theme> children?: React.ReactNode + product: ProductSpecsFragment } -const name = 'ProductSpecs' as const +const name = 'ProductSpecs' const parts = ['root', 'specs', 'options'] as const const { classes } = extendableComponent(name, parts) export function ProductSpecs(props: ProductSpecsProps) { - const { aggregations, items, title, children, sx = [] } = props - const filter = ['price', 'category_id', 'size', 'new', 'sale', 'color'] - const specs = aggregations?.filter( - (attr) => !filter.includes(attr?.attribute_code ?? '') && attr?.options?.[0]?.value !== '0', - ) + const { title, children, sx = [], product } = props - if (specs?.length === 0) return null + const specs = product.custom_attributesV2?.items + const productSpecsTypes = useQuery(ProductSpecsTypesDocument) return ( <Row @@ -48,8 +46,31 @@ export function ProductSpecs(props: ProductSpecsProps) { }, })} > - {aggregations && <ProductSpecsAggregations aggregations={aggregations} />} - {items && <ProductSpecsCustomAttributes items={items} />} + {specs?.map((item) => ( + <li key={item?.code}> + <div> + { + productSpecsTypes?.data?.attributesList?.items?.find( + (type) => type?.code === item?.code, + )?.label + } + </div> + <Box className={classes.options} sx={{ display: 'grid', gridAutoFlow: 'row' }}> + {item?.__typename === 'AttributeSelectedOptions' && ( + <> + {item?.selected_options?.map((option) => ( + <span key={option?.value}> + {option?.label === '1' ? 'Yes' : option?.label} + </span> + ))} + </> + )} + {item?.__typename === 'AttributeValue' && ( + <span key={item?.value}>{item.value}</span> + )} + </Box> + </li> + ))} </Box> {children} </SectionContainer> diff --git a/packages/magento-product/components/ProductSpecs/ProductSpecsAggregations.tsx b/packages/magento-product/components/ProductSpecs/ProductSpecsAggregations.tsx deleted file mode 100644 index 7f813b68ff8..00000000000 --- a/packages/magento-product/components/ProductSpecs/ProductSpecsAggregations.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { extendableComponent } from '@graphcommerce/next-ui' -import { Box } from '@mui/material' -import { ProductSpecsFragment } from './ProductSpecs.gql' - -const name = 'ProductSpecs' as const -const parts = ['root', 'specs', 'options'] as const -const { classes } = extendableComponent(name, parts) - -export type ProductSpecsAggregationsProps = Pick<ProductSpecsFragment, 'aggregations'> - -export function ProductSpecsAggregations(props: ProductSpecsAggregationsProps) { - const { aggregations } = props - const filter = ['price', 'category_id', 'size', 'new', 'sale', 'color'] - const specs = aggregations?.filter( - (attr) => !filter.includes(attr?.attribute_code ?? '') && attr?.options?.[0]?.value !== '0', - ) - - if (specs?.length === 0) return null - - return ( - <> - {specs?.map((aggregation) => ( - <li key={aggregation?.attribute_code}> - <div>{aggregation?.label}</div> - <Box className={classes.options} sx={{ display: 'grid', gridAutoFlow: 'row' }}> - {aggregation?.options?.map((option) => ( - <span key={option?.value}>{option?.label === '1' ? 'Yes' : option?.label}</span> - ))} - </Box> - </li> - ))} - </> - ) -} diff --git a/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx b/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx deleted file mode 100644 index 21002f0a09e..00000000000 --- a/packages/magento-product/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useQuery } from '@graphcommerce/graphql' -import { extendableComponent } from '@graphcommerce/next-ui' -import { Box } from '@mui/material' -import { ProductSpecsFragment } from './ProductSpecs.gql' -import { ProductSpecsTypesDocument } from './ProductSpecsTypes.gql' - -const name = 'ProductSpecs' as const -const parts = ['root', 'specs', 'options'] as const -const { classes } = extendableComponent(name, parts) - -export type ProductSpecsCustomAttributesProps = Pick<ProductSpecsFragment, 'items'> - -export function ProductSpecsCustomAttributes(props: ProductSpecsCustomAttributesProps) { - const { items } = props - - const specs = items?.[0]?.custom_attributesV2?.items - - const productSpecsTypes = useQuery(ProductSpecsTypesDocument) - - if (items?.length === 0) return null - - return ( - <> - {specs?.map((item) => ( - <li key={item?.code}> - <div> - { - productSpecsTypes?.data?.attributesList?.items?.find( - (type) => type?.code === item?.code, - )?.label - } - </div> - <Box className={classes.options} sx={{ display: 'grid', gridAutoFlow: 'row' }}> - {item?.__typename === 'AttributeSelectedOptions' && ( - <> - {item?.selected_options?.map((option) => ( - <span key={option?.value}>{option?.label === '1' ? 'Yes' : option?.label}</span> - ))} - </> - )} - {item?.__typename === 'AttributeValue' && <span key={item?.value}>{item.value}</span>} - </Box> - </li> - ))} - </> - ) -} diff --git a/packages/magento-review/components/ProductReviews/ProductReviews.graphql b/packages/magento-review/components/ProductReviews/ProductReviews.graphql index e433b42b08e..d9a523a4519 100644 --- a/packages/magento-review/components/ProductReviews/ProductReviews.graphql +++ b/packages/magento-review/components/ProductReviews/ProductReviews.graphql @@ -1,5 +1,5 @@ fragment ProductReviews on ProductInterface { - reviews(pageSize: $reviewPageSize, currentPage: $reviewPage) { + reviews(pageSize: 3, currentPage: 1) { page_info { total_pages current_page diff --git a/packages/magento-store/PageMeta.tsx b/packages/magento-store/PageMeta.tsx index cad4b8c1506..b90d01deab5 100644 --- a/packages/magento-store/PageMeta.tsx +++ b/packages/magento-store/PageMeta.tsx @@ -1,16 +1,8 @@ import { useQuery } from '@graphcommerce/graphql' -import { - PageMeta as NextPageMeta, - PageMetaProps as NextPageMetaProps, -} from '@graphcommerce/next-ui' +import { PageMetaProps, PageMeta as PageMetaBase } from '@graphcommerce/next-ui' import { StoreConfigDocument } from './StoreConfig.gql' -type PageMetaProps = Omit<NextPageMetaProps, 'canonical'> & { - canonical?: string -} - export function PageMeta(props: PageMetaProps) { - const { children, title, ...pageMetaProps } = props const config = useQuery(StoreConfigDocument) const prefix = config.data?.storeConfig?.title_prefix ?? '' @@ -18,14 +10,14 @@ export function PageMeta(props: PageMetaProps) { const defaultTitle = config.data?.storeConfig?.default_title ?? '' const suffix = config.data?.storeConfig?.title_suffix ?? '' - let pageTitle = prefix ?? '' - if (title ?? defaultTitle) pageTitle += ` ${title ?? defaultTitle}` - if (separator && suffix) pageTitle += ` ${separator}` - if (suffix) pageTitle += ` ${suffix}` + function addPrefix(title: string) { + let pageTitle = prefix ?? '' + if (title ?? defaultTitle) pageTitle += ` ${title ?? defaultTitle}` + if (separator && suffix) pageTitle += ` ${separator}` + if (suffix) pageTitle += ` ${suffix}` + return pageTitle + } - return ( - <NextPageMeta title={pageTitle ?? ''} {...pageMetaProps}> - {children} - </NextPageMeta> - ) + const { title, ...pageMetaProps } = props + return <PageMetaBase title={addPrefix(title ?? '')} {...pageMetaProps} /> } diff --git a/packages/next-ui/Blog/BlogHeader/BlogHeader.tsx b/packages/next-ui/Blog/BlogHeader/BlogHeader.tsx index 4dbd89f12c7..9f3eb114399 100644 --- a/packages/next-ui/Blog/BlogHeader/BlogHeader.tsx +++ b/packages/next-ui/Blog/BlogHeader/BlogHeader.tsx @@ -8,7 +8,7 @@ export type BlogHeaderProps = { asset?: React.ReactNode } -const name = 'BlogHeader' as const +const name = 'BlogHeader' const parts = ['header', 'asset'] as const const { classes } = extendableComponent(name, parts) diff --git a/packages/next-ui/PageMeta/canonicalize.ts b/packages/next-ui/PageMeta/canonicalize.ts index 892b2a750d9..c962eda4218 100644 --- a/packages/next-ui/PageMeta/canonicalize.ts +++ b/packages/next-ui/PageMeta/canonicalize.ts @@ -12,6 +12,11 @@ type PartialNextRouter = Pick< 'pathname' | 'locale' | 'locales' | 'isLocaleDomain' | 'domainLocales' | 'defaultLocale' > +export function canonicalize(router: PartialNextRouter, incoming: Canonical): string +export function canonicalize( + router: PartialNextRouter, + incoming: Canonical | undefined, +): string | undefined export function canonicalize(router: PartialNextRouter, incoming?: Canonical) { let canonical = incoming diff --git a/packages/universal-schema/README.md b/packages/universal-schema/README.md new file mode 100644 index 00000000000..299e1d2a981 --- /dev/null +++ b/packages/universal-schema/README.md @@ -0,0 +1,8 @@ +A unufied data model based on the spec defined by ecommerce platforms like +Shopify, BigCommerce, and Magento. + +Backends to check for schema's: https://graphcommerce.vercel.app/api/graphql + +| Entity | Magento | Shopify | BigCommerce | +| ------- | ----------------------------------------------------------------------------------------------------------------------- | ------- | ----------- | +| Product | [ProductInterface](https://developer.adobe.com/commerce/webapi/graphql-api/beta/index.html#definition-ProductInterface) | diff --git a/packagesDev/graphql-codegen-relay-optimizer-plugin/dist/index.js b/packagesDev/graphql-codegen-relay-optimizer-plugin/dist/index.js index fb2b2bab972..243a2c9ca01 100644 --- a/packagesDev/graphql-codegen-relay-optimizer-plugin/dist/index.js +++ b/packagesDev/graphql-codegen-relay-optimizer-plugin/dist/index.js @@ -38,6 +38,7 @@ _config) => { /* GraphQL */ ` directive @connection(key: String!, filter: [String!]) on FIELD directive @client on FIELD + directive @defer(if: Boolean! = true, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT `, ]); const documentAsts = documents.reduce((prev, v) => [...prev, ...(v.document?.definitions ?? [])], []); diff --git a/packagesDev/graphql-codegen-relay-optimizer-plugin/src/index.ts b/packagesDev/graphql-codegen-relay-optimizer-plugin/src/index.ts index b9b368187a6..1b78c02e46a 100644 --- a/packagesDev/graphql-codegen-relay-optimizer-plugin/src/index.ts +++ b/packagesDev/graphql-codegen-relay-optimizer-plugin/src/index.ts @@ -43,6 +43,7 @@ export const plugin: PluginFunction<RelayOptimizerPluginConfig> = ( /* GraphQL */ ` directive @connection(key: String!, filter: [String!]) on FIELD directive @client on FIELD + directive @defer(if: Boolean! = true, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT `, ]) diff --git a/packagesDev/next-config/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap b/packagesDev/next-config/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap index daa2373815c..69ffe1f65dc 100644 --- a/packagesDev/next-config/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +++ b/packagesDev/next-config/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap @@ -54,7 +54,6 @@ exports[`traverses a schema and returns a list of env variables that match 1`] = "GC_GOOGLE_RECAPTCHA_KEY", "GC_GOOGLE_TAGMANAGER_ID", "GC_HYGRAPH_ENDPOINT", - "GC_HYGRAPH_MANAGEMENT_API", "GC_HYGRAPH_PROJECT_ID", "GC_HYGRAPH_WRITE_ACCESS_TOKEN", "GC_LIMIT_SSG", diff --git a/packagesDev/next-config/dist/generated/config.js b/packagesDev/next-config/dist/generated/config.js index dc3a907c414..50d56ccfdea 100644 --- a/packagesDev/next-config/dist/generated/config.js +++ b/packagesDev/next-config/dist/generated/config.js @@ -96,7 +96,6 @@ function GraphCommerceConfigSchema() { googleRecaptchaKey: _zod.z.string().nullish(), googleTagmanagerId: _zod.z.string().nullish(), hygraphEndpoint: _zod.z.string().min(1), - hygraphManagementApi: _zod.z.string().nullish(), hygraphProjectId: _zod.z.string().nullish(), hygraphWriteAccessToken: _zod.z.string().nullish(), limitSsg: _zod.z.boolean().nullish(), diff --git a/packagesDev/next-config/src/generated/config.ts b/packagesDev/next-config/src/generated/config.ts index b2b61aaccdc..ab5075117ac 100644 --- a/packagesDev/next-config/src/generated/config.ts +++ b/packagesDev/next-config/src/generated/config.ts @@ -229,13 +229,6 @@ export type GraphCommerceConfig = { * Project settings -> API Access -> High Performance Read-only Content API */ hygraphEndpoint: Scalars['String']['input']; - /** - * Hygraph Management API. **Only used for migrations.** - * - * Optional: If the hygraphEndpoint is configured with the 'High Performance Content - * API', this field is not required. - */ - hygraphManagementApi?: InputMaybe<Scalars['String']['input']>; /** * Hygraph Project ID. **Only used for migrations.** * @@ -514,7 +507,6 @@ export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerc googleRecaptchaKey: z.string().nullish(), googleTagmanagerId: z.string().nullish(), hygraphEndpoint: z.string().min(1), - hygraphManagementApi: z.string().nullish(), hygraphProjectId: z.string().nullish(), hygraphWriteAccessToken: z.string().nullish(), limitSsg: z.boolean().nullish(), diff --git a/packagesDev/next-config/src/index.ts b/packagesDev/next-config/src/index.ts index be5a1c50300..f91eb2285de 100644 --- a/packagesDev/next-config/src/index.ts +++ b/packagesDev/next-config/src/index.ts @@ -11,7 +11,7 @@ export * from './config' export * from './runtimeCachingOptimizations' export * from './interceptors/commands/codegenInterceptors' -export type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & { +export type PluginProps<P = Record<string, unknown>> = P & { Prev: React.FC<P> } diff --git a/yarn.lock b/yarn.lock index ce60e25924f..d28bdb7600a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -210,6 +210,15 @@ __metadata: languageName: node linkType: hard +"@apollo/cache-control-types@npm:^1.0.2": + version: 1.0.3 + resolution: "@apollo/cache-control-types@npm:1.0.3" + peerDependencies: + graphql: 14.x || 15.x || 16.x + checksum: 10c0/b49a9e99c7d5af6dfe12b775eb6374c8a54894e17ffa882b3d85f4501ca19ee413bdcc1a787a4b44dcc2903ce2c28f19b69116f338f88670c4f6f2e10a0bc498 + languageName: node + linkType: hard + "@apollo/client@npm:~3.10.8, @apollo/client@npm:~3.2.5 || ~3.3.0 || ~3.4.0 || ~3.5.0 || ~3.6.0 || ~3.7.0 || ~3.8.0 || ~3.9.0 || ~3.10.0": version: 3.10.8 resolution: "@apollo/client@npm:3.10.8" @@ -247,6 +256,32 @@ __metadata: languageName: node linkType: hard +"@apollo/federation-internals@npm:2.8.4": + version: 2.8.4 + resolution: "@apollo/federation-internals@npm:2.8.4" + dependencies: + "@types/uuid": "npm:^9.0.0" + chalk: "npm:^4.1.0" + js-levenshtein: "npm:^1.1.6" + uuid: "npm:^9.0.0" + peerDependencies: + graphql: ^16.5.0 + checksum: 10c0/3c3cbe56db3dbf09f152ca22fe2b9c2729aec5c3d5bf598fe82bf61219667d891e612963ca1b444a546e47cbd6f9f2c5484c482dbba5858b53b819ad5e65afcc + languageName: node + linkType: hard + +"@apollo/subgraph@npm:^2.4.1": + version: 2.8.4 + resolution: "@apollo/subgraph@npm:2.8.4" + dependencies: + "@apollo/cache-control-types": "npm:^1.0.2" + "@apollo/federation-internals": "npm:2.8.4" + peerDependencies: + graphql: ^16.5.0 + checksum: 10c0/2b40fc3227e4c06853495182b18a0809569851a5536c8159fe858a6939e8cda839cc3ac93ca0ba6b8f74e4d58dad7307c2764683b3d26980a37d70fc8ae3f61e + languageName: node + linkType: hard + "@ardatan/relay-compiler@npm:12.0.0": version: 12.0.0 resolution: "@ardatan/relay-compiler@npm:12.0.0" @@ -2112,6 +2147,15 @@ __metadata: languageName: node linkType: hard +"@commander-js/extra-typings@npm:^12.0.1": + version: 12.1.0 + resolution: "@commander-js/extra-typings@npm:12.1.0" + peerDependencies: + commander: ~12.1.0 + checksum: 10c0/5d29eaa724b577e2a52a393ad54992924d2559931b8e493ab892477b7a4e878e475c6bf771260f8585d835f7d8e17ae4a2656c191e9595d210ae0b48291c0b3d + languageName: node + linkType: hard + "@corex/deepmerge@npm:^4.0.43": version: 4.0.43 resolution: "@corex/deepmerge@npm:4.0.43" @@ -2372,6 +2416,22 @@ __metadata: languageName: node linkType: hard +"@envelop/response-cache@npm:^6.2.1": + version: 6.2.1 + resolution: "@envelop/response-cache@npm:6.2.1" + dependencies: + "@graphql-tools/utils": "npm:^10.0.3" + "@whatwg-node/fetch": "npm:^0.9.0" + fast-json-stable-stringify: "npm:^2.1.0" + lru-cache: "npm:^10.0.0" + tslib: "npm:^2.5.0" + peerDependencies: + "@envelop/core": ^5.0.0 + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10c0/2faaabd3da9fbae324c6fe916757ff9e314f9545808a17acedb9013e74f38fb5458f45bd8b9f540020607deaba22cd63e0075d1c9214a698be726ebb1e8e5ae7 + languageName: node + linkType: hard + "@envelop/types@npm:5.0.0": version: 5.0.0 resolution: "@envelop/types@npm:5.0.0" @@ -2884,6 +2944,7 @@ __metadata: dependencies: "@graphql-codegen/cli": "npm:5.0.2" "@graphql-mesh/cli": "npm:latest" + "@graphql-mesh/compose-cli": "npm:latest" "@graphql-mesh/cross-helpers": "npm:latest" "@graphql-mesh/runtime": "npm:latest" "@graphql-mesh/store": "npm:latest" @@ -3220,6 +3281,7 @@ __metadata: "@graphcommerce/ecommerce-ui": ^9.0.0-canary.72 "@graphcommerce/eslint-config-pwa": ^9.0.0-canary.72 "@graphcommerce/graphql": ^9.0.0-canary.72 + "@graphcommerce/graphql-gc-api": ^9.0.0-canary.72 "@graphcommerce/image": ^9.0.0-canary.72 "@graphcommerce/next-ui": ^9.0.0-canary.72 "@graphcommerce/prettier-config-pwa": ^9.0.0-canary.72 @@ -3282,10 +3344,34 @@ __metadata: languageName: unknown linkType: soft +"@graphcommerce/graphql-gc-api@npm:9.0.0-canary.72, @graphcommerce/graphql-gc-api@workspace:packages/graphql-gc-api": + version: 0.0.0-use.local + resolution: "@graphcommerce/graphql-gc-api@workspace:packages/graphql-gc-api" + peerDependencies: + "@graphcommerce/eslint-config-pwa": ^9.0.0-canary.62 + "@graphcommerce/graphql": ^9.0.0-canary.62 + "@graphcommerce/graphql-mesh": ^9.0.0-canary.62 + "@graphcommerce/magento-product": ^9.0.0-canary.62 + "@graphcommerce/next-ui": ^9.0.0-canary.62 + "@graphcommerce/prettier-config-pwa": ^9.0.0-canary.62 + "@graphcommerce/react-hook-form": ^9.0.0-canary.62 + "@graphcommerce/typescript-config-pwa": ^9.0.0-canary.62 + "@lingui/core": ^4.2.1 + "@lingui/macro": ^4.2.1 + "@lingui/react": ^4.2.1 + "@mui/material": ^5.10.16 + framer-motion: ^10.0.0 + next: "*" + react: ^18.2.0 + react-dom: ^18.2.0 + languageName: unknown + linkType: soft + "@graphcommerce/graphql-mesh@npm:9.0.0-canary.72, @graphcommerce/graphql-mesh@workspace:packages/graphql-mesh": version: 0.0.0-use.local resolution: "@graphcommerce/graphql-mesh@workspace:packages/graphql-mesh" dependencies: + "@envelop/response-cache": "npm:^6.2.1" "@graphql-mesh/apollo-link": "npm:latest" "@graphql-mesh/config": "npm:latest" "@graphql-mesh/cross-helpers": "npm:latest" @@ -3297,6 +3383,7 @@ __metadata: "@graphql-mesh/runtime": "npm:latest" "@graphql-mesh/store": "npm:latest" "@graphql-mesh/transform-encapsulate": "npm:latest" + "@graphql-mesh/transform-federation": "npm:latest" "@graphql-mesh/transform-filter-schema": "npm:latest" "@graphql-mesh/transform-hoist-field": "npm:latest" "@graphql-mesh/transform-naming-convention": "npm:latest" @@ -3408,6 +3495,7 @@ __metadata: "@graphcommerce/eslint-config-pwa": ^9.0.0-canary.72 "@graphcommerce/graphcms-ui": ^9.0.0-canary.72 "@graphcommerce/graphql": ^9.0.0-canary.72 + "@graphcommerce/graphql-mesh": ^9.0.0-canary.72 "@graphcommerce/image": ^9.0.0-canary.72 "@graphcommerce/next-ui": ^9.0.0-canary.72 "@graphcommerce/prettier-config-pwa": ^9.0.0-canary.72 @@ -3925,6 +4013,7 @@ __metadata: "@graphcommerce/googletagmanager": "npm:9.0.0-canary.72" "@graphcommerce/graphcms-ui": "npm:9.0.0-canary.72" "@graphcommerce/graphql": "npm:9.0.0-canary.72" + "@graphcommerce/graphql-gc-api": "npm:9.0.0-canary.72" "@graphcommerce/graphql-mesh": "npm:9.0.0-canary.72" "@graphcommerce/hygraph-cli": "npm:9.0.0-canary.72" "@graphcommerce/hygraph-dynamic-rows": "npm:9.0.0-canary.72" @@ -5146,6 +5235,30 @@ __metadata: languageName: node linkType: hard +"@graphql-mesh/compose-cli@npm:latest": + version: 0.6.6 + resolution: "@graphql-mesh/compose-cli@npm:0.6.6" + dependencies: + "@commander-js/extra-typings": "npm:^12.0.1" + "@graphql-mesh/fusion-composition": "npm:^0.1.7" + "@graphql-mesh/utils": "npm:^0.99.5" + "@graphql-tools/graphql-file-loader": "npm:8.0.1" + "@graphql-tools/load": "npm:^8.0.1" + "@graphql-tools/schema": "npm:^10.0.4" + "@graphql-tools/utils": "npm:^10.2.3" + "@whatwg-node/fetch": "npm:^0.9.14" + commander: "npm:^12.0.0" + dotenv: "npm:^16.3.1" + jiti: "npm:^1.21.6" + peerDependencies: + "@graphql-mesh/types": ^0.99.5 + graphql: "*" + bin: + mesh-compose: esm/bin.js + checksum: 10c0/eb2c8aa54a6ac5e6dda3bd4fe21ec3d58c6eecdab82e28e53576d1a95227d1735a4ab1b1ca3c02779f34be92c828c2d076a1c46326ee88a6a07c33737125c79c + languageName: node + linkType: hard + "@graphql-mesh/config@npm:^0.101.1, @graphql-mesh/config@npm:latest": version: 0.101.1 resolution: "@graphql-mesh/config@npm:0.101.1" @@ -5187,6 +5300,27 @@ __metadata: languageName: node linkType: hard +"@graphql-mesh/fusion-composition@npm:^0.1.7": + version: 0.1.7 + resolution: "@graphql-mesh/fusion-composition@npm:0.1.7" + dependencies: + "@graphql-mesh/utils": "npm:^0.99.5" + "@graphql-tools/schema": "npm:^10.0.4" + "@graphql-tools/stitching-directives": "npm:^3.1.2" + "@graphql-tools/utils": "npm:^10.3.2" + "@theguild/federation-composition": "npm:^0.12.0" + change-case: "npm:^4.1.2" + graphql-scalars: "npm:^1.23.0" + minimatch: "npm:^10.0.0" + pluralize: "npm:^8.0.0" + snake-case: "npm:^3.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/4533f12a578252150d80ee6e48308200e386945f2f4bd02be7bd820807c71c88698a7fb39fa3d654c942300e0ecf1ea5427439f41c3e25a119d55f8272a313a7 + languageName: node + linkType: hard + "@graphql-mesh/graphql@npm:latest": version: 0.99.1 resolution: "@graphql-mesh/graphql@npm:0.99.1" @@ -5366,6 +5500,20 @@ __metadata: languageName: node linkType: hard +"@graphql-mesh/string-interpolation@npm:^0.5.5": + version: 0.5.5 + resolution: "@graphql-mesh/string-interpolation@npm:0.5.5" + dependencies: + dayjs: "npm:1.11.12" + json-pointer: "npm:0.6.2" + lodash.get: "npm:4.4.2" + peerDependencies: + graphql: "*" + tslib: ^2.4.0 + checksum: 10c0/424ed831359a8d4a9b4a06a06a07c23855a034eda15bdae4f6db9ed28884be193c1165ca709374d7b862eafb5eea188f1d4e71d0aad479dbf081aecd7f3df188 + languageName: node + linkType: hard + "@graphql-mesh/transform-encapsulate@npm:latest": version: 0.99.1 resolution: "@graphql-mesh/transform-encapsulate@npm:0.99.1" @@ -5382,6 +5530,26 @@ __metadata: languageName: node linkType: hard +"@graphql-mesh/transform-federation@npm:latest": + version: 0.99.6 + resolution: "@graphql-mesh/transform-federation@npm:0.99.6" + dependencies: + "@apollo/subgraph": "npm:^2.4.1" + "@graphql-mesh/string-interpolation": "npm:^0.5.5" + "@graphql-tools/delegate": "npm:^10.0.16" + "@graphql-tools/stitching-directives": "npm:^3.1.2" + dset: "npm:^3.1.2" + graphql-transform-federation: "npm:^2.2.0" + peerDependencies: + "@graphql-mesh/types": ^0.99.5 + "@graphql-mesh/utils": ^0.99.5 + "@graphql-tools/utils": ^10.2.3 + graphql: "*" + tslib: ^2.4.0 + checksum: 10c0/907ade7208c82d48f4f7f204b42e0b6becf90a93bae5fd52d2e7becca27cf15aa14768d2efbc39a1d63830894bc58f188ef732f42078d695a2a173f2ef4f6a03 + languageName: node + linkType: hard + "@graphql-mesh/transform-filter-schema@npm:latest": version: 0.99.1 resolution: "@graphql-mesh/transform-filter-schema@npm:0.99.1" @@ -5590,6 +5758,29 @@ __metadata: languageName: node linkType: hard +"@graphql-mesh/utils@npm:^0.99.5": + version: 0.99.5 + resolution: "@graphql-mesh/utils@npm:0.99.5" + dependencies: + "@graphql-mesh/string-interpolation": "npm:^0.5.5" + "@graphql-tools/delegate": "npm:^10.0.16" + "@whatwg-node/disposablestack": "npm:^0.0.1" + "@whatwg-node/fetch": "npm:^0.9.13" + dset: "npm:^3.1.2" + js-yaml: "npm:^4.1.0" + lodash.get: "npm:^4.4.2" + lodash.topath: "npm:^4.5.2" + tiny-lru: "npm:^11.0.0" + peerDependencies: + "@graphql-mesh/cross-helpers": ^0.4.4 + "@graphql-mesh/types": ^0.99.5 + "@graphql-tools/utils": ^10.2.3 + graphql: "*" + tslib: ^2.4.0 + checksum: 10c0/42f2c0507c29983e6f3a02199f0a74656d1a94beb0c08db89923fdeb8fedbaf9232f2ba997cc3573724af5beffc7124e32c6664968387866306dad6e066f5475 + languageName: node + linkType: hard + "@graphql-tools/apollo-engine-loader@npm:^8.0.0": version: 8.0.1 resolution: "@graphql-tools/apollo-engine-loader@npm:8.0.1" @@ -5664,6 +5855,22 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/delegate@npm:^10.0.16": + version: 10.0.17 + resolution: "@graphql-tools/delegate@npm:10.0.17" + dependencies: + "@graphql-tools/batch-execute": "npm:^9.0.4" + "@graphql-tools/executor": "npm:^1.3.0" + "@graphql-tools/schema": "npm:^10.0.4" + "@graphql-tools/utils": "npm:^10.2.3" + dataloader: "npm:^2.2.2" + tslib: "npm:^2.5.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/c136026d328ce29d542077305a639d8f84c254b8ef40a9d187b410b3b5d93bbd1c51c424c4318c2df02e8e8a03bea1cf336268010b59d6c28168b3e5c0fa847d + languageName: node + linkType: hard + "@graphql-tools/documents@npm:^1.0.0": version: 1.0.1 resolution: "@graphql-tools/documents@npm:1.0.1" @@ -5739,6 +5946,21 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/executor@npm:^1.3.0": + version: 1.3.0 + resolution: "@graphql-tools/executor@npm:1.3.0" + dependencies: + "@graphql-tools/utils": "npm:^10.2.3" + "@graphql-typed-document-node/core": "npm:3.2.0" + "@repeaterjs/repeater": "npm:^3.0.4" + tslib: "npm:^2.4.0" + value-or-promise: "npm:^1.0.12" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/0e4ec4bcd7ba3f4d0053ae59117fa0367ee9444839f2c0568058494abfa1b4196e53aa738c88cb68a02f30d0a73827578f7f5cb7125a874063a4f1331cd98d62 + languageName: node + linkType: hard + "@graphql-tools/federation@npm:^2.1.2": version: 2.1.2 resolution: "@graphql-tools/federation@npm:2.1.2" @@ -5796,7 +6018,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/graphql-file-loader@npm:^8.0.0": +"@graphql-tools/graphql-file-loader@npm:8.0.1, @graphql-tools/graphql-file-loader@npm:^8.0.0": version: 8.0.1 resolution: "@graphql-tools/graphql-file-loader@npm:8.0.1" dependencies: @@ -5855,7 +6077,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/load@npm:^8.0.0": +"@graphql-tools/load@npm:^8.0.0, @graphql-tools/load@npm:^8.0.1": version: 8.0.2 resolution: "@graphql-tools/load@npm:8.0.2" dependencies: @@ -6001,6 +6223,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/stitching-directives@npm:^3.1.2": + version: 3.1.2 + resolution: "@graphql-tools/stitching-directives@npm:3.1.2" + dependencies: + "@graphql-tools/delegate": "npm:^10.0.4" + "@graphql-tools/utils": "npm:^10.0.13" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/f58f5d1631f56ca391b7046d32dd00da8b82363b4d9e3dda38a5db46dca4579a2b1a309c864d23cd29eade3ff32d753057f099180adebf8b8b1fc580039d121c + languageName: node + linkType: hard + "@graphql-tools/url-loader@npm:^8.0.0, @graphql-tools/url-loader@npm:^8.0.2": version: 8.0.2 resolution: "@graphql-tools/url-loader@npm:8.0.2" @@ -6024,7 +6259,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.13, @graphql-tools/utils@npm:^10.1.0, @graphql-tools/utils@npm:^10.1.1, @graphql-tools/utils@npm:^10.2.1, @graphql-tools/utils@npm:^10.2.3, @graphql-tools/utils@npm:^10.3.0, @graphql-tools/utils@npm:^10.3.2": +"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.13, @graphql-tools/utils@npm:^10.0.3, @graphql-tools/utils@npm:^10.1.0, @graphql-tools/utils@npm:^10.1.1, @graphql-tools/utils@npm:^10.2.1, @graphql-tools/utils@npm:^10.2.3, @graphql-tools/utils@npm:^10.3.0, @graphql-tools/utils@npm:^10.3.2": version: 10.3.2 resolution: "@graphql-tools/utils@npm:10.3.2" dependencies: @@ -7905,6 +8140,20 @@ __metadata: languageName: node linkType: hard +"@theguild/federation-composition@npm:^0.12.0": + version: 0.12.1 + resolution: "@theguild/federation-composition@npm:0.12.1" + dependencies: + constant-case: "npm:^3.0.0" + debug: "npm:4.3.4" + json5: "npm:^2.2.0" + lodash.sortby: "npm:^4.7.0" + peerDependencies: + graphql: ^16.0.0 + checksum: 10c0/a035e6d7af2cda230028a1bf9e3414607ef2eaa320395bb02d39559c0aa7136ed37143b4601e3ff0b1efea6e26a4461f67f8c46a0d6526a9894b427cc07f4ce3 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -8438,6 +8687,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^9.0.0": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + "@types/ws@npm:^8.0.0": version: 8.5.11 resolution: "@types/ws@npm:8.5.11" @@ -8764,6 +9020,15 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/disposablestack@npm:^0.0.1": + version: 0.0.1 + resolution: "@whatwg-node/disposablestack@npm:0.0.1" + dependencies: + tslib: "npm:^2.6.3" + checksum: 10c0/8e1ecd8695bc0ea9630a4daffbaa4b461261e2421ac2ce56f95b63a38835e3d402f3c5f9be00cbb8afdcf85f0cf46105ef1cac1931c2e7ed929660e02ccd1172 + languageName: node + linkType: hard + "@whatwg-node/events@npm:^0.0.3": version: 0.0.3 resolution: "@whatwg-node/events@npm:0.0.3" @@ -8801,6 +9066,16 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/fetch@npm:^0.9.14": + version: 0.9.19 + resolution: "@whatwg-node/fetch@npm:0.9.19" + dependencies: + "@whatwg-node/node-fetch": "npm:^0.5.16" + urlpattern-polyfill: "npm:^10.0.0" + checksum: 10c0/df446e2348c1ac795f6dd241f5c4c89dc303bfcc44ab27fa85c959d40b542deb6335d19bf5ad8271a40c1d2be66987932bfe83a7fce11831a33dbd97c7d5711e + languageName: node + linkType: hard + "@whatwg-node/node-fetch@npm:^0.3.6": version: 0.3.6 resolution: "@whatwg-node/node-fetch@npm:0.3.6" @@ -8814,6 +9089,18 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/node-fetch@npm:^0.5.16": + version: 0.5.20 + resolution: "@whatwg-node/node-fetch@npm:0.5.20" + dependencies: + "@kamilkisiela/fast-url-parser": "npm:^1.1.4" + busboy: "npm:^1.6.0" + fast-querystring: "npm:^1.1.1" + tslib: "npm:^2.6.3" + checksum: 10c0/ce1ac9fe52afc027530755f1d09eeddd89e5199c25fa6bd6d3917d672eb2beeb39ea05638679a0581ac4a77e2ca47fc1e88dd04878c1af6b942601a8249c524d + languageName: node + linkType: hard + "@whatwg-node/node-fetch@npm:^0.5.7": version: 0.5.11 resolution: "@whatwg-node/node-fetch@npm:0.5.11" @@ -10300,6 +10587,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^12.0.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -10369,7 +10663,7 @@ __metadata: languageName: node linkType: hard -"constant-case@npm:^3.0.4": +"constant-case@npm:^3.0.0, constant-case@npm:^3.0.4": version: 3.0.4 resolution: "constant-case@npm:3.0.4" dependencies: @@ -10716,6 +11010,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:1.11.12": + version: 1.11.12 + resolution: "dayjs@npm:1.11.12" + checksum: 10c0/9673d37f3f9ad8a91caaeae9b3fea9a0010c81c7f58599fb9d860bc3359b86632fbff8eb7dddc86c2acaab01c5e6860bc672952f17b58c9286140c52b077c8e4 + languageName: node + linkType: hard + "debounce@npm:^1.2.0": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -10735,6 +11036,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + languageName: node + linkType: hard + "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -11058,7 +11371,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:16.4.5, dotenv@npm:^16.0.0, dotenv@npm:^16.0.3": +"dotenv@npm:16.4.5, dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3.1": version: 16.4.5 resolution: "dotenv@npm:16.4.5" checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f @@ -12911,7 +13224,7 @@ __metadata: languageName: node linkType: hard -"graphql-scalars@npm:^1.22.4": +"graphql-scalars@npm:^1.22.4, graphql-scalars@npm:^1.23.0": version: 1.23.0 resolution: "graphql-scalars@npm:1.23.0" dependencies: @@ -12933,6 +13246,16 @@ __metadata: languageName: node linkType: hard +"graphql-transform-federation@npm:^2.2.0": + version: 2.2.0 + resolution: "graphql-transform-federation@npm:2.2.0" + peerDependencies: + "@apollo/federation": 0 + graphql: ^14.0.0 || ^15.0.0 + checksum: 10c0/96d4a9f13530b9ee7809f0eb795bfb206634ddb8c863ac1ea2e739292c99120850a9d970c4b6ba7ecde4422591fe0479a7a77d0ca7310bfafe67be556c203901 + languageName: node + linkType: hard + "graphql-type-json@npm:0.3.2": version: 0.3.2 resolution: "graphql-type-json@npm:0.3.2" @@ -14661,6 +14984,13 @@ __metadata: languageName: node linkType: hard +"js-levenshtein@npm:^1.1.6": + version: 1.1.6 + resolution: "js-levenshtein@npm:1.1.6" + checksum: 10c0/14045735325ea1fd87f434a74b11d8a14380f090f154747e613529c7cff68b5ee607f5230fa40665d5fb6125a3791f4c223f73b9feca754f989b059f5c05864f + languageName: node + linkType: hard + "js-sha256@npm:^0.10.1": version: 0.10.1 resolution: "js-sha256@npm:0.10.1" @@ -16876,6 +17206,13 @@ __metadata: languageName: node linkType: hard +"pluralize@npm:^8.0.0": + version: 8.0.0 + resolution: "pluralize@npm:8.0.0" + checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 + languageName: node + linkType: hard + "pofile@npm:^1.1.4": version: 1.1.4 resolution: "pofile@npm:1.1.4" @@ -18217,7 +18554,7 @@ __metadata: languageName: node linkType: hard -"snake-case@npm:^3.0.4": +"snake-case@npm:^3.0.0, snake-case@npm:^3.0.4": version: 3.0.4 resolution: "snake-case@npm:3.0.4" dependencies: @@ -19503,6 +19840,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1"