diff --git a/.gitignore b/.gitignore
index 50d4285ba..22f1bf4f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ out/
# misc
.DS_Store
*.pem
+.idea
# debug
npm-debug.log*
diff --git a/framework/commerce/config.js b/framework/commerce/config.js
index dc20b518e..dc016d47a 100644
--- a/framework/commerce/config.js
+++ b/framework/commerce/config.js
@@ -7,7 +7,7 @@ const fs = require('fs')
const merge = require('deepmerge')
const prettier = require('prettier')
-const PROVIDERS = ['bigcommerce', 'shopify', 'swell']
+const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure']
function getProviderName() {
return (
diff --git a/framework/vendure/.env.template b/framework/vendure/.env.template
new file mode 100644
index 000000000..d8f7f14b5
--- /dev/null
+++ b/framework/vendure/.env.template
@@ -0,0 +1 @@
+NEXT_PUBLIC_VENDURE_SHOP_API_URL=http://localhost:3001/shop-api
diff --git a/framework/vendure/README.md b/framework/vendure/README.md
new file mode 100644
index 000000000..c1bcd7b5b
--- /dev/null
+++ b/framework/vendure/README.md
@@ -0,0 +1,33 @@
+# Vendure Storefront Data Hooks
+
+UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use [Vendure](http://vendure.io/) as a headless e-commerce platform.
+
+## Usage
+
+1. Clone this repo and install its dependencies with `yarn install` or `npm install`
+2. Set the Vendure provider and API URL in your `.env.local` file:
+ ```
+ COMMERCE_PROVIDER=vendure
+ NEXT_PUBLIC_VENDURE_SHOP_API_URL=https://demo.vendure.io/shop-api
+ NEXT_PUBLIC_VENDURE_LOCAL_URL=/vendure-shop-api
+ ```
+3. With the Vendure server running, start this project using `yarn dev` or `npm run dev`.
+
+## Known Limitations
+
+1. Vendure does not ship with built-in wishlist functionality.
+2. Nor does it come with any kind of blog/page-building feature. Both of these can be created as Vendure plugins, however.
+3. The entire Vendure customer flow is carried out via its GraphQL API. This means that there is no external, pre-existing checkout flow. The checkout flow must be created as part of the Next.js app. See https://github.com/vercel/commerce/issues/64 for further discusion.
+4. By default, the sign-up flow in Vendure uses email verification. This means that using the existing "sign up" flow from this project will not grant a new user the ability to authenticate, since the new account must first be verified. Again, the necessary parts to support this flow can be created as part of the Next.js app.
+
+## Code generation
+
+This provider makes use of GraphQL code generation. The [schema.graphql](./schema.graphql) and [schema.d.ts](./schema.d.ts) files contain the generated types & schema introspection results.
+
+When developing the provider, changes to any GraphQL operations should be followed by re-generation of the types and schema files:
+
+From the project root dir, run
+
+```sh
+graphql-codegen --config ./framework/vendure/codegen.json
+```
diff --git a/framework/vendure/api/cart/index.ts b/framework/vendure/api/cart/index.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/cart/index.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/catalog/index.ts b/framework/vendure/api/catalog/index.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/catalog/index.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/catalog/products.ts b/framework/vendure/api/catalog/products.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/catalog/products.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/checkout/index.ts b/framework/vendure/api/checkout/index.ts
new file mode 100644
index 000000000..21083e9b7
--- /dev/null
+++ b/framework/vendure/api/checkout/index.ts
@@ -0,0 +1,60 @@
+import { NextApiHandler } from 'next'
+
+const checkoutApi = async (req: any, res: any, config: any) => {
+ try {
+ const html = `
+
+
+
+
+
+ Checkout
+
+
+
+
Checkout not implemented :(
+
+ See #64
+
+
+
+
+ `
+
+ res.status(200)
+ res.setHeader('Content-Type', 'text/html')
+ res.write(html)
+ res.end()
+ } catch (error) {
+ console.error(error)
+
+ const message = 'An unexpected error ocurred'
+
+ res.status(500).json({ data: null, errors: [{ message }] })
+ }
+}
+
+export function createApiHandler(
+ handler: any,
+ handlers: H,
+ defaultOptions: Options
+) {
+ return function getApiHandler({
+ config,
+ operations,
+ options,
+ }: {
+ config?: any
+ operations?: Partial
+ options?: Options extends {} ? Partial : never
+ } = {}): NextApiHandler {
+ const ops = { ...operations, ...handlers }
+ const opts = { ...defaultOptions, ...options }
+
+ return function apiHandler(req, res) {
+ return handler(req, res, config, ops, opts)
+ }
+ }
+}
+
+export default createApiHandler(checkoutApi, {}, {})
diff --git a/framework/vendure/api/customers/index.ts b/framework/vendure/api/customers/index.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/customers/index.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/customers/login.ts b/framework/vendure/api/customers/login.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/customers/login.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/customers/logout.ts b/framework/vendure/api/customers/logout.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/customers/logout.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/customers/signup.ts b/framework/vendure/api/customers/signup.ts
new file mode 100644
index 000000000..ea9b101e1
--- /dev/null
+++ b/framework/vendure/api/customers/signup.ts
@@ -0,0 +1 @@
+export default function () {}
diff --git a/framework/vendure/api/index.ts b/framework/vendure/api/index.ts
new file mode 100644
index 000000000..f6b06a10c
--- /dev/null
+++ b/framework/vendure/api/index.ts
@@ -0,0 +1,51 @@
+import type { CommerceAPIConfig } from '@commerce/api'
+import fetchGraphqlApi from './utils/fetch-graphql-api'
+
+export interface VendureConfig extends CommerceAPIConfig {}
+
+const API_URL = process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
+
+if (!API_URL) {
+ throw new Error(
+ `The environment variable NEXT_PUBLIC_VENDURE_SHOP_API_URL is missing and it's required to access your store`
+ )
+}
+
+export class Config {
+ private config: VendureConfig
+
+ constructor(config: VendureConfig) {
+ this.config = {
+ ...config,
+ }
+ }
+
+ getConfig(userConfig: Partial = {}) {
+ return Object.entries(userConfig).reduce(
+ (cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
+ { ...this.config }
+ )
+ }
+
+ setConfig(newConfig: Partial) {
+ Object.assign(this.config, newConfig)
+ }
+}
+
+const ONE_DAY = 60 * 60 * 24
+const config = new Config({
+ commerceUrl: API_URL,
+ apiToken: '',
+ cartCookie: '',
+ customerCookie: '',
+ cartCookieMaxAge: ONE_DAY * 30,
+ fetch: fetchGraphqlApi,
+})
+
+export function getConfig(userConfig?: Partial) {
+ return config.getConfig(userConfig)
+}
+
+export function setConfig(newConfig: Partial) {
+ return config.setConfig(newConfig)
+}
diff --git a/framework/vendure/api/utils/fetch-graphql-api.ts b/framework/vendure/api/utils/fetch-graphql-api.ts
new file mode 100644
index 000000000..f769123e4
--- /dev/null
+++ b/framework/vendure/api/utils/fetch-graphql-api.ts
@@ -0,0 +1,37 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { GraphQLFetcher } from '@commerce/api'
+import { getConfig } from '..'
+import fetch from './fetch'
+
+const fetchGraphqlApi: GraphQLFetcher = async (
+ query: string,
+ { variables, preview } = {},
+ fetchOptions
+) => {
+ const config = getConfig()
+ const res = await fetch(config.commerceUrl, {
+ ...fetchOptions,
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${config.apiToken}`,
+ ...fetchOptions?.headers,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ query,
+ variables,
+ }),
+ })
+
+ const json = await res.json()
+ if (json.errors) {
+ throw new FetcherError({
+ errors: json.errors ?? [{ message: 'Failed to fetch Vendure API' }],
+ status: res.status,
+ })
+ }
+
+ return { data: json.data, res }
+}
+
+export default fetchGraphqlApi
diff --git a/framework/vendure/api/utils/fetch.ts b/framework/vendure/api/utils/fetch.ts
new file mode 100644
index 000000000..9d9fff3ed
--- /dev/null
+++ b/framework/vendure/api/utils/fetch.ts
@@ -0,0 +1,3 @@
+import zeitFetch from '@vercel/fetch'
+
+export default zeitFetch()
diff --git a/framework/vendure/api/wishlist/index.tsx b/framework/vendure/api/wishlist/index.tsx
new file mode 100644
index 000000000..a72856673
--- /dev/null
+++ b/framework/vendure/api/wishlist/index.tsx
@@ -0,0 +1,2 @@
+export type WishlistItem = { product: any; id: number }
+export default function () {}
diff --git a/framework/vendure/auth/use-login.tsx b/framework/vendure/auth/use-login.tsx
new file mode 100644
index 000000000..f0fc85cbc
--- /dev/null
+++ b/framework/vendure/auth/use-login.tsx
@@ -0,0 +1,50 @@
+import { useCallback } from 'react'
+import { MutationHook } from '@commerce/utils/types'
+import useLogin, { UseLogin } from '@commerce/auth/use-login'
+import { CommerceError, ValidationError } from '@commerce/utils/errors'
+import useCustomer from '../customer/use-customer'
+import { LoginMutation, LoginMutationVariables } from '../schema'
+import { loginMutation } from '../lib/mutations/log-in-mutation'
+
+export default useLogin as UseLogin
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: loginMutation,
+ },
+ async fetcher({ input: { email, password }, options, fetch }) {
+ if (!(email && password)) {
+ throw new CommerceError({
+ message: 'A email and password are required to login',
+ })
+ }
+
+ const variables: LoginMutationVariables = {
+ username: email,
+ password,
+ }
+
+ const { login } = await fetch({
+ ...options,
+ variables,
+ })
+
+ if (login.__typename !== 'CurrentUser') {
+ throw new ValidationError(login)
+ }
+
+ return null
+ },
+ useHook: ({ fetch }) => () => {
+ const { revalidate } = useCustomer()
+
+ return useCallback(
+ async function login(input) {
+ const data = await fetch({ input })
+ await revalidate()
+ return data
+ },
+ [fetch, revalidate]
+ )
+ },
+}
diff --git a/framework/vendure/auth/use-logout.tsx b/framework/vendure/auth/use-logout.tsx
new file mode 100644
index 000000000..93ba665b8
--- /dev/null
+++ b/framework/vendure/auth/use-logout.tsx
@@ -0,0 +1,32 @@
+import { useCallback } from 'react'
+import { MutationHook } from '@commerce/utils/types'
+import useLogout, { UseLogout } from '@commerce/auth/use-logout'
+import useCustomer from '../customer/use-customer'
+import { LogoutMutation } from '../schema'
+import { logoutMutation } from '../lib/mutations/log-out-mutation'
+
+export default useLogout as UseLogout
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: logoutMutation,
+ },
+ async fetcher({ options, fetch }) {
+ await fetch({
+ ...options,
+ })
+ return null
+ },
+ useHook: ({ fetch }) => () => {
+ const { mutate } = useCustomer()
+
+ return useCallback(
+ async function logout() {
+ const data = await fetch()
+ await mutate(null, false)
+ return data
+ },
+ [fetch, mutate]
+ )
+ },
+}
diff --git a/framework/vendure/auth/use-signup.tsx b/framework/vendure/auth/use-signup.tsx
new file mode 100644
index 000000000..816b95738
--- /dev/null
+++ b/framework/vendure/auth/use-signup.tsx
@@ -0,0 +1,68 @@
+import { useCallback } from 'react'
+import { MutationHook } from '@commerce/utils/types'
+import { CommerceError, ValidationError } from '@commerce/utils/errors'
+import useSignup, { UseSignup } from '@commerce/auth/use-signup'
+import useCustomer from '../customer/use-customer'
+import {
+ RegisterCustomerInput,
+ SignupMutation,
+ SignupMutationVariables,
+} from '../schema'
+import { signupMutation } from '../lib/mutations/sign-up-mutation'
+
+export default useSignup as UseSignup
+
+export type SignupInput = {
+ email: string
+ firstName: string
+ lastName: string
+ password: string
+}
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: signupMutation,
+ },
+ async fetcher({
+ input: { firstName, lastName, email, password },
+ options,
+ fetch,
+ }) {
+ if (!(firstName && lastName && email && password)) {
+ throw new CommerceError({
+ message:
+ 'A first name, last name, email and password are required to signup',
+ })
+ }
+ const variables: SignupMutationVariables = {
+ input: {
+ firstName,
+ lastName,
+ emailAddress: email,
+ password,
+ },
+ }
+ const { registerCustomerAccount } = await fetch({
+ ...options,
+ variables,
+ })
+
+ if (registerCustomerAccount.__typename !== 'Success') {
+ throw new ValidationError(registerCustomerAccount)
+ }
+
+ return null
+ },
+ useHook: ({ fetch }) => () => {
+ const { revalidate } = useCustomer()
+
+ return useCallback(
+ async function signup(input) {
+ const data = await fetch({ input })
+ await revalidate()
+ return data
+ },
+ [fetch, revalidate]
+ )
+ },
+}
diff --git a/framework/vendure/cart/index.ts b/framework/vendure/cart/index.ts
new file mode 100644
index 000000000..43c6db2b7
--- /dev/null
+++ b/framework/vendure/cart/index.ts
@@ -0,0 +1,5 @@
+export { default as useCart } from './use-cart'
+export { default as useAddItem } from './use-add-item'
+export { default as useRemoveItem } from './use-remove-item'
+export { default as useWishlistActions } from './use-cart-actions'
+export { default as useUpdateItem } from './use-cart-actions'
diff --git a/framework/vendure/cart/use-add-item.tsx b/framework/vendure/cart/use-add-item.tsx
new file mode 100644
index 000000000..42c5e5a63
--- /dev/null
+++ b/framework/vendure/cart/use-add-item.tsx
@@ -0,0 +1,52 @@
+import { Cart, CartItemBody } from '@commerce/types'
+import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
+import { CommerceError } from '@commerce/utils/errors'
+import { MutationHook } from '@commerce/utils/types'
+import { useCallback } from 'react'
+import useCart from './use-cart'
+import { AddItemToOrderMutation } from '../schema'
+import { normalizeCart } from '../lib/normalize'
+import { addItemToOrderMutation } from '../lib/mutations/add-item-to-order-mutation'
+
+export default useAddItem as UseAddItem
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: addItemToOrderMutation,
+ },
+ async fetcher({ input, options, fetch }) {
+ if (
+ input.quantity &&
+ (!Number.isInteger(input.quantity) || input.quantity! < 1)
+ ) {
+ throw new CommerceError({
+ message: 'The item quantity has to be a valid integer greater than 0',
+ })
+ }
+
+ const { addItemToOrder } = await fetch({
+ ...options,
+ variables: {
+ quantity: input.quantity || 1,
+ variantId: input.variantId,
+ },
+ })
+
+ if (addItemToOrder.__typename === 'Order') {
+ return normalizeCart(addItemToOrder)
+ }
+ throw new CommerceError(addItemToOrder)
+ },
+ useHook: ({ fetch }) => () => {
+ const { mutate } = useCart()
+
+ return useCallback(
+ async function addItem(input) {
+ const data = await fetch({ input })
+ await mutate(data, false)
+ return data
+ },
+ [fetch, mutate]
+ )
+ },
+}
diff --git a/framework/vendure/cart/use-cart-actions.tsx b/framework/vendure/cart/use-cart-actions.tsx
new file mode 100644
index 000000000..abb4a998e
--- /dev/null
+++ b/framework/vendure/cart/use-cart-actions.tsx
@@ -0,0 +1,13 @@
+import useAddItem from './use-add-item'
+import useRemoveItem from './use-remove-item'
+import useUpdateItem from './use-update-item'
+
+// This hook is probably not going to be used, but it's here
+// to show how a commerce should be structuring it
+export default function useCartActions() {
+ const addItem = useAddItem()
+ const updateItem = useUpdateItem()
+ const removeItem = useRemoveItem()
+
+ return { addItem, updateItem, removeItem }
+}
diff --git a/framework/vendure/cart/use-cart.tsx b/framework/vendure/cart/use-cart.tsx
new file mode 100644
index 000000000..ee9975f87
--- /dev/null
+++ b/framework/vendure/cart/use-cart.tsx
@@ -0,0 +1,49 @@
+import { Cart } from '@commerce/types'
+import { SWRHook } from '@commerce/utils/types'
+import useCart, { FetchCartInput, UseCart } from '@commerce/cart/use-cart'
+import { ActiveOrderQuery, CartFragment } from '../schema'
+import { normalizeCart } from '../lib/normalize'
+import { useMemo } from 'react'
+import { getCartQuery } from '../lib/queries/get-cart-query'
+
+export type CartResult = {
+ activeOrder?: CartFragment
+ addItemToOrder?: CartFragment
+ adjustOrderLine?: CartFragment
+ removeOrderLine?: CartFragment
+}
+
+export default useCart as UseCart
+
+export const handler: SWRHook<
+ Cart | null,
+ {},
+ FetchCartInput,
+ { isEmpty?: boolean }
+> = {
+ fetchOptions: {
+ query: getCartQuery,
+ },
+ async fetcher({ input: { cartId }, options, fetch }) {
+ const { activeOrder } = await fetch(options)
+ return activeOrder ? normalizeCart(activeOrder) : null
+ },
+ useHook: ({ useData }) => (input) => {
+ const response = useData({
+ swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
+ })
+
+ return useMemo(
+ () =>
+ Object.create(response, {
+ isEmpty: {
+ get() {
+ return (response.data?.lineItems.length ?? 0) <= 0
+ },
+ enumerable: true,
+ },
+ }),
+ [response]
+ )
+ },
+}
diff --git a/framework/vendure/cart/use-remove-item.tsx b/framework/vendure/cart/use-remove-item.tsx
new file mode 100644
index 000000000..f23f04d00
--- /dev/null
+++ b/framework/vendure/cart/use-remove-item.tsx
@@ -0,0 +1,48 @@
+import { useCallback } from 'react'
+import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
+import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
+import { CommerceError } from '@commerce/utils/errors'
+import useCart from './use-cart'
+import {
+ RemoveOrderLineMutation,
+ RemoveOrderLineMutationVariables,
+} from '../schema'
+import { Cart, LineItem, RemoveCartItemBody } from '@commerce/types'
+import { normalizeCart } from '../lib/normalize'
+import { removeOrderLineMutation } from '../lib/mutations/remove-order-line-mutation'
+
+export default useRemoveItem as UseRemoveItem
+
+export const handler = {
+ fetchOptions: {
+ query: removeOrderLineMutation,
+ },
+ async fetcher({ input, options, fetch }: HookFetcherContext) {
+ const variables: RemoveOrderLineMutationVariables = {
+ orderLineId: input.id,
+ }
+ const { removeOrderLine } = await fetch({
+ ...options,
+ variables,
+ })
+
+ if (removeOrderLine.__typename === 'Order') {
+ return normalizeCart(removeOrderLine)
+ }
+ throw new CommerceError(removeOrderLine)
+ },
+ useHook: ({
+ fetch,
+ }: MutationHookContext) => (ctx = {}) => {
+ const { mutate } = useCart()
+
+ return useCallback(
+ async function removeItem(input) {
+ const data = await fetch({ input })
+ await mutate(data, false)
+ return data
+ },
+ [fetch, mutate]
+ )
+ },
+}
diff --git a/framework/vendure/cart/use-update-item.tsx b/framework/vendure/cart/use-update-item.tsx
new file mode 100644
index 000000000..4e871514e
--- /dev/null
+++ b/framework/vendure/cart/use-update-item.tsx
@@ -0,0 +1,78 @@
+import { useCallback } from 'react'
+import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
+import { CommerceError, ValidationError } from '@commerce/utils/errors'
+import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
+import {
+ Cart,
+ CartItemBody,
+ LineItem,
+ UpdateCartItemBody,
+} from '@commerce/types'
+import useCart from './use-cart'
+import {
+ AdjustOrderLineMutation,
+ AdjustOrderLineMutationVariables,
+} from '../schema'
+import { normalizeCart } from '../lib/normalize'
+import { adjustOrderLineMutation } from '../lib/mutations/adjust-order-line-mutation'
+
+export default useUpdateItem as UseUpdateItem
+
+export const handler = {
+ fetchOptions: {
+ query: adjustOrderLineMutation,
+ },
+ async fetcher(context: HookFetcherContext>) {
+ const { input, options, fetch } = context
+ const variables: AdjustOrderLineMutationVariables = {
+ quantity: input.item.quantity || 1,
+ orderLineId: input.itemId,
+ }
+ const { adjustOrderLine } = await fetch({
+ ...options,
+ variables,
+ })
+
+ if (adjustOrderLine.__typename === 'Order') {
+ return normalizeCart(adjustOrderLine)
+ }
+ throw new CommerceError(adjustOrderLine)
+ },
+ useHook: ({
+ fetch,
+ }: MutationHookContext>) => (
+ ctx: {
+ item?: LineItem
+ wait?: number
+ } = {}
+ ) => {
+ const { item } = ctx
+ const { mutate } = useCart()
+
+ return useCallback(
+ async function addItem(input: Partial) {
+ const itemId = item?.id
+ const productId = input.productId ?? item?.productId
+ const variantId = input.productId ?? item?.variantId
+ if (!itemId || !productId || !variantId) {
+ throw new ValidationError({
+ message: 'Invalid input used for this operation',
+ })
+ }
+ const data = await fetch({
+ input: {
+ item: {
+ productId,
+ variantId,
+ quantity: input.quantity,
+ },
+ itemId,
+ },
+ })
+ await mutate(data, false)
+ return data
+ },
+ [fetch, mutate]
+ )
+ },
+}
diff --git a/framework/vendure/codegen.json b/framework/vendure/codegen.json
new file mode 100644
index 000000000..79a2b6ffa
--- /dev/null
+++ b/framework/vendure/codegen.json
@@ -0,0 +1,28 @@
+{
+ "schema": {
+ "http://localhost:3001/shop-api": {}
+ },
+ "documents": [
+ {
+ "./framework/vendure/**/*.{ts,tsx}": {
+ "noRequire": true
+ }
+ }
+ ],
+ "generates": {
+ "./framework/vendure/schema.d.ts": {
+ "plugins": ["typescript", "typescript-operations"],
+ "config": {
+ "scalars": {
+ "ID": "string"
+ }
+ }
+ },
+ "./framework/vendure/schema.graphql": {
+ "plugins": ["schema-ast"]
+ }
+ },
+ "hooks": {
+ "afterAllFileWrite": ["prettier --write"]
+ }
+}
diff --git a/framework/vendure/commerce.config.json b/framework/vendure/commerce.config.json
new file mode 100644
index 000000000..70806f062
--- /dev/null
+++ b/framework/vendure/commerce.config.json
@@ -0,0 +1,6 @@
+{
+ "provider": "vendure",
+ "features": {
+ "wishlist": false
+ }
+}
diff --git a/framework/vendure/common/get-all-pages.ts b/framework/vendure/common/get-all-pages.ts
new file mode 100644
index 000000000..1200b02b1
--- /dev/null
+++ b/framework/vendure/common/get-all-pages.ts
@@ -0,0 +1,35 @@
+import { getConfig, VendureConfig } from '../api'
+
+export type Page = any
+
+export type GetAllPagesResult<
+ T extends { pages: any[] } = { pages: Page[] }
+> = T
+
+async function getAllPages(opts?: {
+ config?: VendureConfig
+ preview?: boolean
+}): Promise
+
+async function getAllPages(opts: {
+ url: string
+ config?: VendureConfig
+ preview?: boolean
+}): Promise>
+
+async function getAllPages({
+ config,
+ preview,
+}: {
+ url?: string
+ config?: VendureConfig
+ preview?: boolean
+} = {}): Promise {
+ config = getConfig(config)
+
+ return {
+ pages: [],
+ }
+}
+
+export default getAllPages
diff --git a/framework/vendure/common/get-page.ts b/framework/vendure/common/get-page.ts
new file mode 100644
index 000000000..48a183630
--- /dev/null
+++ b/framework/vendure/common/get-page.ts
@@ -0,0 +1,40 @@
+import { VendureConfig, getConfig } from '../api'
+
+export type Page = any
+
+export type GetPageResult = T
+
+export type PageVariables = {
+ id: number
+}
+
+async function getPage(opts: {
+ url?: string
+ variables: PageVariables
+ config?: VendureConfig
+ preview?: boolean
+}): Promise
+
+async function getPage(opts: {
+ url: string
+ variables: V
+ config?: VendureConfig
+ preview?: boolean
+}): Promise>
+
+async function getPage({
+ url,
+ variables,
+ config,
+ preview,
+}: {
+ url?: string
+ variables: PageVariables
+ config?: VendureConfig
+ preview?: boolean
+}): Promise {
+ config = getConfig(config)
+ return {}
+}
+
+export default getPage
diff --git a/framework/vendure/common/get-site-info.ts b/framework/vendure/common/get-site-info.ts
new file mode 100644
index 000000000..9410c4493
--- /dev/null
+++ b/framework/vendure/common/get-site-info.ts
@@ -0,0 +1,49 @@
+import { getConfig, VendureConfig } from '../api'
+import { GetCollectionsQuery } from '../schema'
+import { arrayToTree } from '../lib/array-to-tree'
+import { getCollectionsQuery } from '../lib/queries/get-collections-query'
+
+export type Category = {
+ entityId: string
+ name: string
+ path: string
+ productCount: number
+}
+
+export type GetSiteInfoResult<
+ T extends { categories: any[]; brands: any[] } = {
+ categories: Category[]
+ brands: any[]
+ }
+> = T
+
+async function getSiteInfo({
+ query = getCollectionsQuery,
+ variables,
+ config,
+}: {
+ query?: string
+ variables?: any
+ config?: VendureConfig
+ preview?: boolean
+} = {}): Promise {
+ config = getConfig(config)
+ // RecursivePartial forces the method to check for every prop in the data, which is
+ // required in case there's a custom `query`
+ const { data } = await config.fetch(query, { variables })
+ const collections = data.collections?.items.map((i) => ({
+ ...i,
+ entityId: i.id,
+ path: i.slug,
+ productCount: i.productVariants.totalItems,
+ }))
+ const categories = arrayToTree(collections).children
+ const brands = [] as any[]
+
+ return {
+ categories: categories ?? [],
+ brands,
+ }
+}
+
+export default getSiteInfo
diff --git a/framework/vendure/customer/get-customer-wishlist.ts b/framework/vendure/customer/get-customer-wishlist.ts
new file mode 100644
index 000000000..81ec96956
--- /dev/null
+++ b/framework/vendure/customer/get-customer-wishlist.ts
@@ -0,0 +1,18 @@
+import { getConfig, VendureConfig } from '../api'
+
+async function getCustomerWishlist({
+ config,
+ variables,
+ includeProducts,
+}: {
+ url?: string
+ variables: any
+ config?: VendureConfig
+ includeProducts?: boolean
+}): Promise {
+ // Not implemented as Vendure does not ship with wishlist functionality at present
+ config = getConfig(config)
+ return { wishlist: {} }
+}
+
+export default getCustomerWishlist
diff --git a/framework/vendure/customer/index.ts b/framework/vendure/customer/index.ts
new file mode 100644
index 000000000..6c903ecc5
--- /dev/null
+++ b/framework/vendure/customer/index.ts
@@ -0,0 +1 @@
+export { default as useCustomer } from './use-customer'
diff --git a/framework/vendure/customer/use-customer.tsx b/framework/vendure/customer/use-customer.tsx
new file mode 100644
index 000000000..4de821253
--- /dev/null
+++ b/framework/vendure/customer/use-customer.tsx
@@ -0,0 +1,33 @@
+import { SWRHook } from '@commerce/utils/types'
+import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
+import { Customer } from '@commerce/types'
+import { ActiveCustomerQuery } from '../schema'
+import { activeCustomerQuery } from '../lib/queries/active-customer-query'
+
+export default useCustomer as UseCustomer
+
+export const handler: SWRHook = {
+ fetchOptions: {
+ query: activeCustomerQuery,
+ },
+ async fetcher({ options, fetch }) {
+ const { activeCustomer } = await fetch({
+ ...options,
+ })
+ return activeCustomer
+ ? ({
+ firstName: activeCustomer.firstName ?? '',
+ lastName: activeCustomer.lastName ?? '',
+ email: activeCustomer.emailAddress ?? '',
+ } as any)
+ : null
+ },
+ useHook: ({ useData }) => (input) => {
+ return useData({
+ swrOptions: {
+ revalidateOnFocus: false,
+ ...input?.swrOptions,
+ },
+ })
+ },
+}
diff --git a/framework/vendure/fetcher.ts b/framework/vendure/fetcher.ts
new file mode 100644
index 000000000..bf8f0dcd8
--- /dev/null
+++ b/framework/vendure/fetcher.ts
@@ -0,0 +1,51 @@
+import { Fetcher } from '@commerce/utils/types'
+import { FetcherError } from '@commerce/utils/errors'
+
+async function getText(res: Response) {
+ try {
+ return (await res.text()) || res.statusText
+ } catch (error) {
+ return res.statusText
+ }
+}
+
+async function getError(res: Response) {
+ if (res.headers.get('Content-Type')?.includes('application/json')) {
+ const data = await res.json()
+ return new FetcherError({ errors: data.errors, status: res.status })
+ }
+ return new FetcherError({ message: await getText(res), status: res.status })
+}
+
+export const fetcher: Fetcher = async ({
+ url,
+ method = 'POST',
+ variables,
+ query,
+ body: bodyObj,
+}) => {
+ const shopApiUrl =
+ process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL ||
+ process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL
+ if (!shopApiUrl) {
+ throw new Error(
+ 'The Vendure Shop API url has not been provided. Please define NEXT_PUBLIC_VENDURE_SHOP_API_URL in .env.local'
+ )
+ }
+ const hasBody = Boolean(variables || query)
+ const body = hasBody ? JSON.stringify({ query, variables }) : undefined
+ const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
+ const res = await fetch(shopApiUrl, {
+ method,
+ body,
+ headers,
+ credentials: 'include',
+ })
+
+ if (res.ok) {
+ const { data } = await res.json()
+ return data
+ }
+
+ throw await getError(res)
+}
diff --git a/framework/vendure/index.tsx b/framework/vendure/index.tsx
new file mode 100644
index 000000000..47e60c7df
--- /dev/null
+++ b/framework/vendure/index.tsx
@@ -0,0 +1,33 @@
+import * as React from 'react'
+import { ReactNode } from 'react'
+import {
+ CommerceConfig,
+ CommerceProvider as CoreCommerceProvider,
+ useCommerce as useCoreCommerce,
+} from '@commerce'
+import { vendureProvider } from './provider'
+
+export const vendureConfig: CommerceConfig = {
+ locale: 'en-us',
+ cartCookie: 'session',
+}
+
+export type VendureConfig = Partial
+
+export type VendureProps = {
+ children?: ReactNode
+ locale: string
+} & VendureConfig
+
+export function CommerceProvider({ children, ...config }: VendureProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+export const useCommerce = () => useCoreCommerce()
diff --git a/framework/vendure/lib/array-to-tree.ts b/framework/vendure/lib/array-to-tree.ts
new file mode 100644
index 000000000..403cf226e
--- /dev/null
+++ b/framework/vendure/lib/array-to-tree.ts
@@ -0,0 +1,67 @@
+export type HasParent = { id: string; parent?: { id: string } | null }
+export type TreeNode = T & {
+ children: Array>
+ expanded: boolean
+}
+export type RootNode = {
+ id?: string
+ children: Array>
+}
+
+export function arrayToTree(
+ nodes: T[],
+ currentState?: RootNode
+): RootNode {
+ const topLevelNodes: Array> = []
+ const mappedArr: { [id: string]: TreeNode } = {}
+ const currentStateMap = treeToMap(currentState)
+
+ // First map the nodes of the array to an object -> create a hash table.
+ for (const node of nodes) {
+ mappedArr[node.id] = { ...(node as any), children: [] }
+ }
+
+ for (const id of nodes.map((n) => n.id)) {
+ if (mappedArr.hasOwnProperty(id)) {
+ const mappedElem = mappedArr[id]
+ mappedElem.expanded = currentStateMap.get(id)?.expanded ?? false
+ const parent = mappedElem.parent
+ if (!parent) {
+ continue
+ }
+ // If the element is not at the root level, add it to its parent array of children.
+ const parentIsRoot = !mappedArr[parent.id]
+ if (!parentIsRoot) {
+ if (mappedArr[parent.id]) {
+ mappedArr[parent.id].children.push(mappedElem)
+ } else {
+ mappedArr[parent.id] = { children: [mappedElem] } as any
+ }
+ } else {
+ topLevelNodes.push(mappedElem)
+ }
+ }
+ }
+ // tslint:disable-next-line:no-non-null-assertion
+ const rootId = topLevelNodes.length ? topLevelNodes[0].parent!.id : undefined
+ return { id: rootId, children: topLevelNodes }
+}
+
+/**
+ * Converts an existing tree (as generated by the arrayToTree function) into a flat
+ * Map. This is used to persist certain states (e.g. `expanded`) when re-building the
+ * tree.
+ */
+function treeToMap(
+ tree?: RootNode
+): Map> {
+ const nodeMap = new Map>()
+ function visit(node: TreeNode) {
+ nodeMap.set(node.id, node)
+ node.children.forEach(visit)
+ }
+ if (tree) {
+ visit(tree as TreeNode)
+ }
+ return nodeMap
+}
diff --git a/framework/vendure/lib/fragments/cart-fragment.ts b/framework/vendure/lib/fragments/cart-fragment.ts
new file mode 100644
index 000000000..36371e07c
--- /dev/null
+++ b/framework/vendure/lib/fragments/cart-fragment.ts
@@ -0,0 +1,42 @@
+export const cartFragment = /* GraphQL */ `
+ fragment Cart on Order {
+ id
+ code
+ createdAt
+ totalQuantity
+ subTotal
+ subTotalWithTax
+ total
+ totalWithTax
+ currencyCode
+ customer {
+ id
+ }
+ lines {
+ id
+ quantity
+ linePriceWithTax
+ discountedLinePriceWithTax
+ featuredAsset {
+ id
+ preview
+ }
+ discounts {
+ description
+ amount
+ }
+ productVariant {
+ id
+ name
+ sku
+ price
+ priceWithTax
+ stockLevel
+ product {
+ slug
+ }
+ productId
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/fragments/search-result-fragment.ts b/framework/vendure/lib/fragments/search-result-fragment.ts
new file mode 100644
index 000000000..6155b5b47
--- /dev/null
+++ b/framework/vendure/lib/fragments/search-result-fragment.ts
@@ -0,0 +1,24 @@
+export const searchResultFragment = /* GraphQL */ `
+ fragment SearchResult on SearchResult {
+ productId
+ productName
+ description
+ description
+ slug
+ sku
+ currencyCode
+ productAsset {
+ id
+ preview
+ }
+ priceWithTax {
+ ... on SinglePrice {
+ value
+ }
+ ... on PriceRange {
+ min
+ max
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/mutations/add-item-to-order-mutation.ts b/framework/vendure/lib/mutations/add-item-to-order-mutation.ts
new file mode 100644
index 000000000..5ed41d972
--- /dev/null
+++ b/framework/vendure/lib/mutations/add-item-to-order-mutation.ts
@@ -0,0 +1,15 @@
+import { cartFragment } from '../fragments/cart-fragment'
+
+export const addItemToOrderMutation = /* GraphQL */ `
+ mutation addItemToOrder($variantId: ID!, $quantity: Int!) {
+ addItemToOrder(productVariantId: $variantId, quantity: $quantity) {
+ __typename
+ ...Cart
+ ... on ErrorResult {
+ errorCode
+ message
+ }
+ }
+ }
+ ${cartFragment}
+`
diff --git a/framework/vendure/lib/mutations/adjust-order-line-mutation.ts b/framework/vendure/lib/mutations/adjust-order-line-mutation.ts
new file mode 100644
index 000000000..ce9864b12
--- /dev/null
+++ b/framework/vendure/lib/mutations/adjust-order-line-mutation.ts
@@ -0,0 +1,15 @@
+import { cartFragment } from '../fragments/cart-fragment'
+
+export const adjustOrderLineMutation = /* GraphQL */ `
+ mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) {
+ adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) {
+ __typename
+ ...Cart
+ ... on ErrorResult {
+ errorCode
+ message
+ }
+ }
+ }
+ ${cartFragment}
+`
diff --git a/framework/vendure/lib/mutations/log-in-mutation.ts b/framework/vendure/lib/mutations/log-in-mutation.ts
new file mode 100644
index 000000000..75e2ddba9
--- /dev/null
+++ b/framework/vendure/lib/mutations/log-in-mutation.ts
@@ -0,0 +1,14 @@
+export const loginMutation = /* GraphQL */ `
+ mutation login($username: String!, $password: String!) {
+ login(username: $username, password: $password) {
+ __typename
+ ... on CurrentUser {
+ id
+ }
+ ... on ErrorResult {
+ errorCode
+ message
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/mutations/log-out-mutation.ts b/framework/vendure/lib/mutations/log-out-mutation.ts
new file mode 100644
index 000000000..6f222c5c8
--- /dev/null
+++ b/framework/vendure/lib/mutations/log-out-mutation.ts
@@ -0,0 +1,7 @@
+export const logoutMutation = /* GraphQL */ `
+ mutation logout {
+ logout {
+ success
+ }
+ }
+`
diff --git a/framework/vendure/lib/mutations/remove-order-line-mutation.ts b/framework/vendure/lib/mutations/remove-order-line-mutation.ts
new file mode 100644
index 000000000..47a89039e
--- /dev/null
+++ b/framework/vendure/lib/mutations/remove-order-line-mutation.ts
@@ -0,0 +1,15 @@
+import { cartFragment } from '../fragments/cart-fragment'
+
+export const removeOrderLineMutation = /* GraphQL */ `
+ mutation removeOrderLine($orderLineId: ID!) {
+ removeOrderLine(orderLineId: $orderLineId) {
+ __typename
+ ...Cart
+ ... on ErrorResult {
+ errorCode
+ message
+ }
+ }
+ }
+ ${cartFragment}
+`
diff --git a/framework/vendure/lib/mutations/sign-up-mutation.ts b/framework/vendure/lib/mutations/sign-up-mutation.ts
new file mode 100644
index 000000000..410e395c2
--- /dev/null
+++ b/framework/vendure/lib/mutations/sign-up-mutation.ts
@@ -0,0 +1,14 @@
+export const signupMutation = /* GraphQL */ `
+ mutation signup($input: RegisterCustomerInput!) {
+ registerCustomerAccount(input: $input) {
+ __typename
+ ... on Success {
+ success
+ }
+ ... on ErrorResult {
+ errorCode
+ message
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/normalize.ts b/framework/vendure/lib/normalize.ts
new file mode 100644
index 000000000..c64ff2136
--- /dev/null
+++ b/framework/vendure/lib/normalize.ts
@@ -0,0 +1,55 @@
+import { Cart, Product } from '@commerce/types'
+import { CartFragment, SearchResultFragment } from '../schema'
+
+export function normalizeSearchResult(item: SearchResultFragment): Product {
+ return {
+ id: item.productId,
+ name: item.productName,
+ description: item.description,
+ slug: item.slug,
+ path: item.slug,
+ images: [{ url: item.productAsset?.preview + '?w=800&mode=crop' || '' }],
+ variants: [],
+ price: {
+ value: (item.priceWithTax as any).min / 100,
+ currencyCode: item.currencyCode,
+ },
+ options: [],
+ sku: item.sku,
+ }
+}
+
+export function normalizeCart(order: CartFragment): Cart {
+ return {
+ id: order.id.toString(),
+ createdAt: order.createdAt,
+ taxesIncluded: true,
+ lineItemsSubtotalPrice: order.subTotalWithTax / 100,
+ currency: { code: order.currencyCode },
+ subtotalPrice: order.subTotalWithTax / 100,
+ totalPrice: order.totalWithTax / 100,
+ customerId: order.customer?.id,
+ lineItems: order.lines?.map((l) => ({
+ id: l.id,
+ name: l.productVariant.name,
+ quantity: l.quantity,
+ url: l.productVariant.product.slug,
+ variantId: l.productVariant.id,
+ productId: l.productVariant.productId,
+ images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }],
+ discounts: l.discounts.map((d) => ({ value: d.amount / 100 })),
+ path: '',
+ variant: {
+ id: l.productVariant.id,
+ name: l.productVariant.name,
+ sku: l.productVariant.sku,
+ price: l.discountedLinePriceWithTax / 100,
+ listPrice: l.linePriceWithTax / 100,
+ image: {
+ url: l.featuredAsset?.preview + '?preset=thumb' || '',
+ },
+ requiresShipping: true,
+ },
+ })),
+ }
+}
diff --git a/framework/vendure/lib/queries/active-customer-query.ts b/framework/vendure/lib/queries/active-customer-query.ts
new file mode 100644
index 000000000..65b280743
--- /dev/null
+++ b/framework/vendure/lib/queries/active-customer-query.ts
@@ -0,0 +1,10 @@
+export const activeCustomerQuery = /* GraphQL */ `
+ query activeCustomer {
+ activeCustomer {
+ id
+ firstName
+ lastName
+ emailAddress
+ }
+ }
+`
diff --git a/framework/vendure/lib/queries/get-all-product-paths-query.ts b/framework/vendure/lib/queries/get-all-product-paths-query.ts
new file mode 100644
index 000000000..29922dca2
--- /dev/null
+++ b/framework/vendure/lib/queries/get-all-product-paths-query.ts
@@ -0,0 +1,9 @@
+export const getAllProductPathsQuery = /* GraphQL */ `
+ query getAllProductPaths($first: Int = 100) {
+ products(options: { take: $first }) {
+ items {
+ slug
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/queries/get-all-products-query.ts b/framework/vendure/lib/queries/get-all-products-query.ts
new file mode 100644
index 000000000..1b44b2017
--- /dev/null
+++ b/framework/vendure/lib/queries/get-all-products-query.ts
@@ -0,0 +1,12 @@
+import { searchResultFragment } from '../fragments/search-result-fragment'
+
+export const getAllProductsQuery = /* GraphQL */ `
+ query getAllProducts($input: SearchInput!) {
+ search(input: $input) {
+ items {
+ ...SearchResult
+ }
+ }
+ }
+ ${searchResultFragment}
+`
diff --git a/framework/vendure/lib/queries/get-cart-query.ts b/framework/vendure/lib/queries/get-cart-query.ts
new file mode 100644
index 000000000..7faac355c
--- /dev/null
+++ b/framework/vendure/lib/queries/get-cart-query.ts
@@ -0,0 +1,10 @@
+import { cartFragment } from '../fragments/cart-fragment'
+
+export const getCartQuery = /* GraphQL */ `
+ query activeOrder {
+ activeOrder {
+ ...Cart
+ }
+ }
+ ${cartFragment}
+`
diff --git a/framework/vendure/lib/queries/get-collections-query.ts b/framework/vendure/lib/queries/get-collections-query.ts
new file mode 100644
index 000000000..ed0919652
--- /dev/null
+++ b/framework/vendure/lib/queries/get-collections-query.ts
@@ -0,0 +1,21 @@
+export const getCollectionsQuery = /* GraphQL */ `
+ query getCollections {
+ collections {
+ items {
+ id
+ name
+ description
+ slug
+ productVariants {
+ totalItems
+ }
+ parent {
+ id
+ }
+ children {
+ id
+ }
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/queries/get-product-query.ts b/framework/vendure/lib/queries/get-product-query.ts
new file mode 100644
index 000000000..b2c502da9
--- /dev/null
+++ b/framework/vendure/lib/queries/get-product-query.ts
@@ -0,0 +1,41 @@
+export const getProductQuery = /* GraphQL */ `
+ query getProduct($slug: String!) {
+ product(slug: $slug) {
+ id
+ name
+ slug
+ description
+ assets {
+ id
+ preview
+ name
+ }
+ variants {
+ id
+ priceWithTax
+ currencyCode
+ options {
+ id
+ name
+ code
+ groupId
+ group {
+ id
+ options {
+ name
+ }
+ }
+ }
+ }
+ optionGroups {
+ id
+ code
+ name
+ options {
+ id
+ name
+ }
+ }
+ }
+ }
+`
diff --git a/framework/vendure/lib/queries/search-query.ts b/framework/vendure/lib/queries/search-query.ts
new file mode 100644
index 000000000..f95c43703
--- /dev/null
+++ b/framework/vendure/lib/queries/search-query.ts
@@ -0,0 +1,13 @@
+import { searchResultFragment } from '../fragments/search-result-fragment'
+
+export const searchQuery = /* GraphQL */ `
+ query search($input: SearchInput!) {
+ search(input: $input) {
+ items {
+ ...SearchResult
+ }
+ totalItems
+ }
+ }
+ ${searchResultFragment}
+`
diff --git a/framework/vendure/next.config.js b/framework/vendure/next.config.js
new file mode 100644
index 000000000..96153ad1e
--- /dev/null
+++ b/framework/vendure/next.config.js
@@ -0,0 +1,8 @@
+const commerce = require('./commerce.config.json')
+
+module.exports = {
+ commerce,
+ images: {
+ domains: ['localhost', 'demo.vendure.io'],
+ },
+}
diff --git a/framework/vendure/product/get-all-product-paths.ts b/framework/vendure/product/get-all-product-paths.ts
new file mode 100644
index 000000000..dfbaff1b8
--- /dev/null
+++ b/framework/vendure/product/get-all-product-paths.ts
@@ -0,0 +1,48 @@
+import type {
+ GetAllProductPathsQuery,
+ GetAllProductPathsQueryVariables,
+} from '../schema'
+import { getConfig, VendureConfig } from '../api'
+import { getAllProductPathsQuery } from '../lib/queries/get-all-product-paths-query'
+
+export type GetAllProductPathsResult = {
+ products: Array<{ node: { path: string } }>
+}
+
+async function getAllProductPaths(opts?: {
+ variables?: GetAllProductPathsQueryVariables
+ config?: VendureConfig
+}): Promise
+
+async function getAllProductPaths<
+ T extends { products: any[] },
+ V = any
+>(opts: {
+ query: string
+ variables?: V
+ config?: VendureConfig
+}): Promise
+
+async function getAllProductPaths({
+ query = getAllProductPathsQuery,
+ variables,
+ config,
+}: {
+ query?: string
+ variables?: GetAllProductPathsQueryVariables
+ config?: VendureConfig
+} = {}): Promise {
+ config = getConfig(config)
+ // RecursivePartial forces the method to check for every prop in the data, which is
+ // required in case there's a custom `query`
+ const { data } = await config.fetch(query, {
+ variables,
+ })
+ const products = data.products.items
+
+ return {
+ products: products.map((p) => ({ node: { path: `/${p.slug}` } })),
+ }
+}
+
+export default getAllProductPaths
diff --git a/framework/vendure/product/get-all-products.ts b/framework/vendure/product/get-all-products.ts
new file mode 100644
index 000000000..b292e0246
--- /dev/null
+++ b/framework/vendure/product/get-all-products.ts
@@ -0,0 +1,39 @@
+import { Product } from '@commerce/types'
+import { getConfig, VendureConfig } from '../api'
+import { GetAllProductsQuery } from '../schema'
+import { normalizeSearchResult } from '../lib/normalize'
+import { getAllProductsQuery } from '../lib/queries/get-all-products-query'
+
+export type ProductVariables = { first?: number }
+
+async function getAllProducts(opts?: {
+ variables?: ProductVariables
+ config?: VendureConfig
+ preview?: boolean
+}): Promise<{ products: Product[] }>
+
+async function getAllProducts({
+ query = getAllProductsQuery,
+ variables: { ...vars } = {},
+ config,
+}: {
+ query?: string
+ variables?: ProductVariables
+ config?: VendureConfig
+ preview?: boolean
+} = {}): Promise<{ products: Product[] | any[] }> {
+ config = getConfig(config)
+ const variables = {
+ input: {
+ take: vars.first,
+ groupByProduct: true,
+ },
+ }
+ const { data } = await config.fetch(query, { variables })
+
+ return {
+ products: data.search.items.map((item) => normalizeSearchResult(item)),
+ }
+}
+
+export default getAllProducts
diff --git a/framework/vendure/product/get-product.ts b/framework/vendure/product/get-product.ts
new file mode 100644
index 000000000..735164ec1
--- /dev/null
+++ b/framework/vendure/product/get-product.ts
@@ -0,0 +1,57 @@
+import { Product } from '@commerce/types'
+import { getConfig, VendureConfig } from '../api'
+import { GetProductQuery } from '../schema'
+import { getProductQuery } from '../lib/queries/get-product-query'
+
+async function getProduct({
+ query = getProductQuery,
+ variables,
+ config,
+}: {
+ query?: string
+ variables: { slug: string }
+ config?: VendureConfig
+ preview?: boolean
+}): Promise {
+ config = getConfig(config)
+
+ const locale = config.locale
+ const { data } = await config.fetch(query, { variables })
+ const product = data.product
+
+ if (product) {
+ return {
+ product: {
+ id: product.id,
+ name: product.name,
+ description: product.description,
+ slug: product.slug,
+ images: product.assets.map((a) => ({
+ url: a.preview,
+ alt: a.name,
+ })),
+ variants: product.variants.map((v) => ({
+ id: v.id,
+ options: v.options.map((o) => ({
+ id: o.id,
+ displayName: o.name,
+ values: o.group.options.map((_o) => ({ label: _o.name })),
+ })),
+ })),
+ price: {
+ value: product.variants[0].priceWithTax / 100,
+ currencyCode: product.variants[0].currencyCode,
+ },
+ options: product.optionGroups.map((og) => ({
+ id: og.id,
+ displayName: og.name,
+ values: og.options.map((o) => ({ label: o.name })),
+ })),
+ } as Product,
+ }
+ }
+
+ return {}
+}
+
+export default getProduct
diff --git a/framework/vendure/product/index.ts b/framework/vendure/product/index.ts
new file mode 100644
index 000000000..b290c189f
--- /dev/null
+++ b/framework/vendure/product/index.ts
@@ -0,0 +1,4 @@
+export { default as usePrice } from './use-price'
+export { default as useSearch } from './use-search'
+export { default as getProduct } from './get-product'
+export { default as getAllProducts } from './get-all-products'
diff --git a/framework/vendure/product/use-price.tsx b/framework/vendure/product/use-price.tsx
new file mode 100644
index 000000000..0174faf5e
--- /dev/null
+++ b/framework/vendure/product/use-price.tsx
@@ -0,0 +1,2 @@
+export * from '@commerce/product/use-price'
+export { default } from '@commerce/product/use-price'
diff --git a/framework/vendure/product/use-search.tsx b/framework/vendure/product/use-search.tsx
new file mode 100644
index 000000000..00d9db36c
--- /dev/null
+++ b/framework/vendure/product/use-search.tsx
@@ -0,0 +1,65 @@
+import { SWRHook } from '@commerce/utils/types'
+import useSearch, { UseSearch } from '@commerce/product/use-search'
+import { Product } from '@commerce/types'
+import { SearchQuery, SearchQueryVariables } from '../schema'
+import { normalizeSearchResult } from '../lib/normalize'
+import { searchQuery } from '../lib/queries/search-query'
+
+export default useSearch as UseSearch
+
+export type SearchProductsInput = {
+ search?: string
+ categoryId?: string
+ brandId?: string
+ sort?: string
+}
+
+export type SearchProductsData = {
+ products: Product[]
+ found: boolean
+}
+
+export const handler: SWRHook<
+ SearchProductsData,
+ SearchProductsInput,
+ SearchProductsInput
+> = {
+ fetchOptions: {
+ query: searchQuery,
+ },
+ async fetcher({ input, options, fetch }) {
+ const { categoryId, brandId } = input
+
+ const variables: SearchQueryVariables = {
+ input: {
+ term: input.search,
+ collectionId: input.categoryId,
+ groupByProduct: true,
+ // TODO: what is the "sort" value?
+ },
+ }
+ const { search } = await fetch({
+ query: searchQuery,
+ variables,
+ })
+
+ return {
+ found: search.totalItems > 0,
+ products: search.items.map((item) => normalizeSearchResult(item)) ?? [],
+ }
+ },
+ useHook: ({ useData }) => (input = {}) => {
+ return useData({
+ input: [
+ ['search', input.search],
+ ['categoryId', input.categoryId],
+ ['brandId', input.brandId],
+ ['sort', input.sort],
+ ],
+ swrOptions: {
+ revalidateOnFocus: false,
+ ...input.swrOptions,
+ },
+ })
+ },
+}
diff --git a/framework/vendure/provider.ts b/framework/vendure/provider.ts
new file mode 100644
index 000000000..e100c277b
--- /dev/null
+++ b/framework/vendure/provider.ts
@@ -0,0 +1,21 @@
+import { Provider } from '@commerce'
+import { handler as useCart } from './cart/use-cart'
+import { handler as useAddItem } from './cart/use-add-item'
+import { handler as useUpdateItem } from './cart/use-update-item'
+import { handler as useRemoveItem } from './cart/use-remove-item'
+import { handler as useCustomer } from './customer/use-customer'
+import { handler as useSearch } from './product/use-search'
+import { handler as useLogin } from './auth/use-login'
+import { handler as useLogout } from './auth/use-logout'
+import { handler as useSignup } from './auth/use-signup'
+import { fetcher } from './fetcher'
+
+export const vendureProvider: Provider = {
+ locale: 'en-us',
+ cartCookie: 'session',
+ fetcher,
+ cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
+ customer: { useCustomer },
+ products: { useSearch },
+ auth: { useLogin, useLogout, useSignup },
+}
diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts
new file mode 100644
index 000000000..78e821257
--- /dev/null
+++ b/framework/vendure/schema.d.ts
@@ -0,0 +1,3122 @@
+export type Maybe = T | null
+export type Exact = {
+ [K in keyof T]: T[K]
+}
+export type MakeOptional = Omit &
+ { [SubKey in K]?: Maybe }
+export type MakeMaybe = Omit &
+ { [SubKey in K]: Maybe }
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+ ID: string
+ String: string
+ Boolean: boolean
+ Int: number
+ Float: number
+ /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
+ JSON: any
+ /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
+ DateTime: any
+ /** The `Upload` scalar type represents a file upload. */
+ Upload: any
+}
+
+export type Query = {
+ __typename?: 'Query'
+ /** The active Channel */
+ activeChannel: Channel
+ /** The active Customer */
+ activeCustomer?: Maybe
+ /**
+ * The active Order. Will be `null` until an Order is created via `addItemToOrder`. Once an Order reaches the
+ * state of `PaymentApproved` or `PaymentSettled`, then that Order is no longer considered "active" and this
+ * query will once again return `null`.
+ */
+ activeOrder?: Maybe
+ /** An array of supported Countries */
+ availableCountries: Array
+ /** A list of Collections available to the shop */
+ collections: CollectionList
+ /** Returns a Collection either by its id or slug. If neither 'id' nor 'slug' is speicified, an error will result. */
+ collection?: Maybe
+ /** Returns a list of eligible shipping methods based on the current active Order */
+ eligibleShippingMethods: Array
+ /** Returns a list of payment methods and their eligibility based on the current active Order */
+ eligiblePaymentMethods: Array
+ /** Returns information about the current authenticated User */
+ me?: Maybe
+ /** Returns the possible next states that the activeOrder can transition to */
+ nextOrderStates: Array
+ /**
+ * Returns an Order based on the id. Note that in the Shop API, only orders belonging to the
+ * currently-authenticated User may be queried.
+ */
+ order?: Maybe
+ /**
+ * Returns an Order based on the order `code`. For guest Orders (i.e. Orders placed by non-authenticated Customers)
+ * this query will only return the Order within 2 hours of the Order being placed. This allows an Order confirmation
+ * screen to be shown immediately after completion of a guest checkout, yet prevents security risks of allowing
+ * general anonymous access to Order data.
+ */
+ orderByCode?: Maybe
+ /** Get a Product either by id or slug. If neither 'id' nor 'slug' is speicified, an error will result. */
+ product?: Maybe
+ /** Get a list of Products */
+ products: ProductList
+ /** Search Products based on the criteria set by the `SearchInput` */
+ search: SearchResponse
+}
+
+export type QueryCollectionsArgs = {
+ options?: Maybe
+}
+
+export type QueryCollectionArgs = {
+ id?: Maybe
+ slug?: Maybe
+}
+
+export type QueryOrderArgs = {
+ id: Scalars['ID']
+}
+
+export type QueryOrderByCodeArgs = {
+ code: Scalars['String']
+}
+
+export type QueryProductArgs = {
+ id?: Maybe
+ slug?: Maybe
+}
+
+export type QueryProductsArgs = {
+ options?: Maybe
+}
+
+export type QuerySearchArgs = {
+ input: SearchInput
+}
+
+export type Mutation = {
+ __typename?: 'Mutation'
+ /** Adds an item to the order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+ addItemToOrder: UpdateOrderItemsResult
+ /** Remove an OrderLine from the Order */
+ removeOrderLine: RemoveOrderItemsResult
+ /** Remove all OrderLine from the Order */
+ removeAllOrderLines: RemoveOrderItemsResult
+ /** Adjusts an OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+ adjustOrderLine: UpdateOrderItemsResult
+ /** Applies the given coupon code to the active Order */
+ applyCouponCode: ApplyCouponCodeResult
+ /** Removes the given coupon code from the active Order */
+ removeCouponCode?: Maybe
+ /** Transitions an Order to a new state. Valid next states can be found by querying `nextOrderStates` */
+ transitionOrderToState?: Maybe
+ /** Sets the shipping address for this order */
+ setOrderShippingAddress: ActiveOrderResult
+ /** Sets the billing address for this order */
+ setOrderBillingAddress: ActiveOrderResult
+ /** Allows any custom fields to be set for the active order */
+ setOrderCustomFields: ActiveOrderResult
+ /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethods` query */
+ setOrderShippingMethod: SetOrderShippingMethodResult
+ /** Add a Payment to the Order */
+ addPaymentToOrder: AddPaymentToOrderResult
+ /** Set the Customer for the Order. Required only if the Customer is not currently logged in */
+ setCustomerForOrder: SetCustomerForOrderResult
+ /** Authenticates the user using the native authentication strategy. This mutation is an alias for `authenticate({ native: { ... }})` */
+ login: NativeAuthenticationResult
+ /** Authenticates the user using a named authentication strategy */
+ authenticate: AuthenticationResult
+ /** End the current authenticated session */
+ logout: Success
+ /**
+ * Register a Customer account with the given credentials. There are three possible registration flows:
+ *
+ * _If `authOptions.requireVerification` is set to `true`:_
+ *
+ * 1. **The Customer is registered _with_ a password**. A verificationToken will be created (and typically emailed to the Customer). That
+ * verificationToken would then be passed to the `verifyCustomerAccount` mutation _without_ a password. The Customer is then
+ * verified and authenticated in one step.
+ * 2. **The Customer is registered _without_ a password**. A verificationToken will be created (and typically emailed to the Customer). That
+ * verificationToken would then be passed to the `verifyCustomerAccount` mutation _with_ the chosed password of the Customer. The Customer is then
+ * verified and authenticated in one step.
+ *
+ * _If `authOptions.requireVerification` is set to `false`:_
+ *
+ * 3. The Customer _must_ be registered _with_ a password. No further action is needed - the Customer is able to authenticate immediately.
+ */
+ registerCustomerAccount: RegisterCustomerAccountResult
+ /** Regenerate and send a verification token for a new Customer registration. Only applicable if `authOptions.requireVerification` is set to true. */
+ refreshCustomerVerification: RefreshCustomerVerificationResult
+ /** Update an existing Customer */
+ updateCustomer: Customer
+ /** Create a new Customer Address */
+ createCustomerAddress: Address
+ /** Update an existing Address */
+ updateCustomerAddress: Address
+ /** Delete an existing Address */
+ deleteCustomerAddress: Success
+ /**
+ * Verify a Customer email address with the token sent to that address. Only applicable if `authOptions.requireVerification` is set to true.
+ *
+ * If the Customer was not registered with a password in the `registerCustomerAccount` mutation, the a password _must_ be
+ * provided here.
+ */
+ verifyCustomerAccount: VerifyCustomerAccountResult
+ /** Update the password of the active Customer */
+ updateCustomerPassword: UpdateCustomerPasswordResult
+ /**
+ * Request to update the emailAddress of the active Customer. If `authOptions.requireVerification` is enabled
+ * (as is the default), then the `identifierChangeToken` will be assigned to the current User and
+ * a IdentifierChangeRequestEvent will be raised. This can then be used e.g. by the EmailPlugin to email
+ * that verification token to the Customer, which is then used to verify the change of email address.
+ */
+ requestUpdateCustomerEmailAddress: RequestUpdateCustomerEmailAddressResult
+ /**
+ * Confirm the update of the emailAddress with the provided token, which has been generated by the
+ * `requestUpdateCustomerEmailAddress` mutation.
+ */
+ updateCustomerEmailAddress: UpdateCustomerEmailAddressResult
+ /** Requests a password reset email to be sent */
+ requestPasswordReset?: Maybe
+ /** Resets a Customer's password based on the provided token */
+ resetPassword: ResetPasswordResult
+}
+
+export type MutationAddItemToOrderArgs = {
+ productVariantId: Scalars['ID']
+ quantity: Scalars['Int']
+}
+
+export type MutationRemoveOrderLineArgs = {
+ orderLineId: Scalars['ID']
+}
+
+export type MutationAdjustOrderLineArgs = {
+ orderLineId: Scalars['ID']
+ quantity: Scalars['Int']
+}
+
+export type MutationApplyCouponCodeArgs = {
+ couponCode: Scalars['String']
+}
+
+export type MutationRemoveCouponCodeArgs = {
+ couponCode: Scalars['String']
+}
+
+export type MutationTransitionOrderToStateArgs = {
+ state: Scalars['String']
+}
+
+export type MutationSetOrderShippingAddressArgs = {
+ input: CreateAddressInput
+}
+
+export type MutationSetOrderBillingAddressArgs = {
+ input: CreateAddressInput
+}
+
+export type MutationSetOrderCustomFieldsArgs = {
+ input: UpdateOrderInput
+}
+
+export type MutationSetOrderShippingMethodArgs = {
+ shippingMethodId: Scalars['ID']
+}
+
+export type MutationAddPaymentToOrderArgs = {
+ input: PaymentInput
+}
+
+export type MutationSetCustomerForOrderArgs = {
+ input: CreateCustomerInput
+}
+
+export type MutationLoginArgs = {
+ username: Scalars['String']
+ password: Scalars['String']
+ rememberMe?: Maybe
+}
+
+export type MutationAuthenticateArgs = {
+ input: AuthenticationInput
+ rememberMe?: Maybe
+}
+
+export type MutationRegisterCustomerAccountArgs = {
+ input: RegisterCustomerInput
+}
+
+export type MutationRefreshCustomerVerificationArgs = {
+ emailAddress: Scalars['String']
+}
+
+export type MutationUpdateCustomerArgs = {
+ input: UpdateCustomerInput
+}
+
+export type MutationCreateCustomerAddressArgs = {
+ input: CreateAddressInput
+}
+
+export type MutationUpdateCustomerAddressArgs = {
+ input: UpdateAddressInput
+}
+
+export type MutationDeleteCustomerAddressArgs = {
+ id: Scalars['ID']
+}
+
+export type MutationVerifyCustomerAccountArgs = {
+ token: Scalars['String']
+ password?: Maybe
+}
+
+export type MutationUpdateCustomerPasswordArgs = {
+ currentPassword: Scalars['String']
+ newPassword: Scalars['String']
+}
+
+export type MutationRequestUpdateCustomerEmailAddressArgs = {
+ password: Scalars['String']
+ newEmailAddress: Scalars['String']
+}
+
+export type MutationUpdateCustomerEmailAddressArgs = {
+ token: Scalars['String']
+}
+
+export type MutationRequestPasswordResetArgs = {
+ emailAddress: Scalars['String']
+}
+
+export type MutationResetPasswordArgs = {
+ token: Scalars['String']
+ password: Scalars['String']
+}
+
+export type Address = Node & {
+ __typename?: 'Address'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ fullName?: Maybe
+ company?: Maybe
+ streetLine1: Scalars['String']
+ streetLine2?: Maybe
+ city?: Maybe
+ province?: Maybe
+ postalCode?: Maybe
+ country: Country
+ phoneNumber?: Maybe
+ defaultShippingAddress?: Maybe
+ defaultBillingAddress?: Maybe
+ customFields?: Maybe
+}
+
+export type Asset = Node & {
+ __typename?: 'Asset'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ name: Scalars['String']
+ type: AssetType
+ fileSize: Scalars['Int']
+ mimeType: Scalars['String']
+ width: Scalars['Int']
+ height: Scalars['Int']
+ source: Scalars['String']
+ preview: Scalars['String']
+ focalPoint?: Maybe
+ customFields?: Maybe
+}
+
+export type Coordinate = {
+ __typename?: 'Coordinate'
+ x: Scalars['Float']
+ y: Scalars['Float']
+}
+
+export type AssetList = PaginatedList & {
+ __typename?: 'AssetList'
+ items: Array
+ totalItems: Scalars['Int']
+}
+
+export enum AssetType {
+ Image = 'IMAGE',
+ Video = 'VIDEO',
+ Binary = 'BINARY',
+}
+
+export type CurrentUser = {
+ __typename?: 'CurrentUser'
+ id: Scalars['ID']
+ identifier: Scalars['String']
+ channels: Array
+}
+
+export type CurrentUserChannel = {
+ __typename?: 'CurrentUserChannel'
+ id: Scalars['ID']
+ token: Scalars['String']
+ code: Scalars['String']
+ permissions: Array
+}
+
+export type Channel = Node & {
+ __typename?: 'Channel'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ code: Scalars['String']
+ token: Scalars['String']
+ defaultTaxZone?: Maybe
+ defaultShippingZone?: Maybe
+ defaultLanguageCode: LanguageCode
+ currencyCode: CurrencyCode
+ pricesIncludeTax: Scalars['Boolean']
+ customFields?: Maybe
+}
+
+export type Collection = Node & {
+ __typename?: 'Collection'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ languageCode?: Maybe
+ name: Scalars['String']
+ slug: Scalars['String']
+ breadcrumbs: Array
+ position: Scalars['Int']
+ description: Scalars['String']
+ featuredAsset?: Maybe
+ assets: Array
+ parent?: Maybe
+ children?: Maybe>
+ filters: Array
+ translations: Array
+ productVariants: ProductVariantList
+ customFields?: Maybe
+}
+
+export type CollectionProductVariantsArgs = {
+ options?: Maybe
+}
+
+export type CollectionBreadcrumb = {
+ __typename?: 'CollectionBreadcrumb'
+ id: Scalars['ID']
+ name: Scalars['String']
+ slug: Scalars['String']
+}
+
+export type CollectionTranslation = {
+ __typename?: 'CollectionTranslation'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ languageCode: LanguageCode
+ name: Scalars['String']
+ slug: Scalars['String']
+ description: Scalars['String']
+}
+
+export type CollectionList = PaginatedList & {
+ __typename?: 'CollectionList'
+ items: Array
+ totalItems: Scalars['Int']
+}
+
+export type ProductVariantList = PaginatedList & {
+ __typename?: 'ProductVariantList'
+ items: Array
+ totalItems: Scalars['Int']
+}
+
+export enum GlobalFlag {
+ True = 'TRUE',
+ False = 'FALSE',
+ Inherit = 'INHERIT',
+}
+
+export enum AdjustmentType {
+ Promotion = 'PROMOTION',
+ DistributedOrderPromotion = 'DISTRIBUTED_ORDER_PROMOTION',
+}
+
+export enum DeletionResult {
+ /** The entity was successfully deleted */
+ Deleted = 'DELETED',
+ /** Deletion did not take place, reason given in message */
+ NotDeleted = 'NOT_DELETED',
+}
+
+/**
+ * @description
+ * Permissions for administrators and customers. Used to control access to
+ * GraphQL resolvers via the {@link Allow} decorator.
+ *
+ * @docsCategory common
+ */
+export enum Permission {
+ /** Authenticated means simply that the user is logged in */
+ Authenticated = 'Authenticated',
+ /** SuperAdmin has unrestricted access to all operations */
+ SuperAdmin = 'SuperAdmin',
+ /** Owner means the user owns this entity, e.g. a Customer's own Order */
+ Owner = 'Owner',
+ /** Public means any unauthenticated user may perform the operation */
+ Public = 'Public',
+ /** Grants permission to create Catalog */
+ CreateCatalog = 'CreateCatalog',
+ /** Grants permission to read Catalog */
+ ReadCatalog = 'ReadCatalog',
+ /** Grants permission to update Catalog */
+ UpdateCatalog = 'UpdateCatalog',
+ /** Grants permission to delete Catalog */
+ DeleteCatalog = 'DeleteCatalog',
+ /** Grants permission to create Customer */
+ CreateCustomer = 'CreateCustomer',
+ /** Grants permission to read Customer */
+ ReadCustomer = 'ReadCustomer',
+ /** Grants permission to update Customer */
+ UpdateCustomer = 'UpdateCustomer',
+ /** Grants permission to delete Customer */
+ DeleteCustomer = 'DeleteCustomer',
+ /** Grants permission to create Administrator */
+ CreateAdministrator = 'CreateAdministrator',
+ /** Grants permission to read Administrator */
+ ReadAdministrator = 'ReadAdministrator',
+ /** Grants permission to update Administrator */
+ UpdateAdministrator = 'UpdateAdministrator',
+ /** Grants permission to delete Administrator */
+ DeleteAdministrator = 'DeleteAdministrator',
+ /** Grants permission to create Order */
+ CreateOrder = 'CreateOrder',
+ /** Grants permission to read Order */
+ ReadOrder = 'ReadOrder',
+ /** Grants permission to update Order */
+ UpdateOrder = 'UpdateOrder',
+ /** Grants permission to delete Order */
+ DeleteOrder = 'DeleteOrder',
+ /** Grants permission to create Promotion */
+ CreatePromotion = 'CreatePromotion',
+ /** Grants permission to read Promotion */
+ ReadPromotion = 'ReadPromotion',
+ /** Grants permission to update Promotion */
+ UpdatePromotion = 'UpdatePromotion',
+ /** Grants permission to delete Promotion */
+ DeletePromotion = 'DeletePromotion',
+ /** Grants permission to create Settings */
+ CreateSettings = 'CreateSettings',
+ /** Grants permission to read Settings */
+ ReadSettings = 'ReadSettings',
+ /** Grants permission to update Settings */
+ UpdateSettings = 'UpdateSettings',
+ /** Grants permission to delete Settings */
+ DeleteSettings = 'DeleteSettings',
+}
+
+export enum SortOrder {
+ Asc = 'ASC',
+ Desc = 'DESC',
+}
+
+export enum ErrorCode {
+ UnknownError = 'UNKNOWN_ERROR',
+ NativeAuthStrategyError = 'NATIVE_AUTH_STRATEGY_ERROR',
+ InvalidCredentialsError = 'INVALID_CREDENTIALS_ERROR',
+ OrderStateTransitionError = 'ORDER_STATE_TRANSITION_ERROR',
+ EmailAddressConflictError = 'EMAIL_ADDRESS_CONFLICT_ERROR',
+ OrderLimitError = 'ORDER_LIMIT_ERROR',
+ NegativeQuantityError = 'NEGATIVE_QUANTITY_ERROR',
+ InsufficientStockError = 'INSUFFICIENT_STOCK_ERROR',
+ OrderModificationError = 'ORDER_MODIFICATION_ERROR',
+ IneligibleShippingMethodError = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+ OrderPaymentStateError = 'ORDER_PAYMENT_STATE_ERROR',
+ IneligiblePaymentMethodError = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
+ PaymentFailedError = 'PAYMENT_FAILED_ERROR',
+ PaymentDeclinedError = 'PAYMENT_DECLINED_ERROR',
+ CouponCodeInvalidError = 'COUPON_CODE_INVALID_ERROR',
+ CouponCodeExpiredError = 'COUPON_CODE_EXPIRED_ERROR',
+ CouponCodeLimitError = 'COUPON_CODE_LIMIT_ERROR',
+ AlreadyLoggedInError = 'ALREADY_LOGGED_IN_ERROR',
+ MissingPasswordError = 'MISSING_PASSWORD_ERROR',
+ PasswordAlreadySetError = 'PASSWORD_ALREADY_SET_ERROR',
+ VerificationTokenInvalidError = 'VERIFICATION_TOKEN_INVALID_ERROR',
+ VerificationTokenExpiredError = 'VERIFICATION_TOKEN_EXPIRED_ERROR',
+ IdentifierChangeTokenInvalidError = 'IDENTIFIER_CHANGE_TOKEN_INVALID_ERROR',
+ IdentifierChangeTokenExpiredError = 'IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR',
+ PasswordResetTokenInvalidError = 'PASSWORD_RESET_TOKEN_INVALID_ERROR',
+ PasswordResetTokenExpiredError = 'PASSWORD_RESET_TOKEN_EXPIRED_ERROR',
+ NotVerifiedError = 'NOT_VERIFIED_ERROR',
+ NoActiveOrderError = 'NO_ACTIVE_ORDER_ERROR',
+}
+
+export enum LogicalOperator {
+ And = 'AND',
+ Or = 'OR',
+}
+
+/** Retured when attempting an operation that relies on the NativeAuthStrategy, if that strategy is not configured. */
+export type NativeAuthStrategyError = ErrorResult & {
+ __typename?: 'NativeAuthStrategyError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+}
+
+/** Returned if the user authentication credentials are not valid */
+export type InvalidCredentialsError = ErrorResult & {
+ __typename?: 'InvalidCredentialsError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+ authenticationError: Scalars['String']
+}
+
+/** Returned if there is an error in transitioning the Order state */
+export type OrderStateTransitionError = ErrorResult & {
+ __typename?: 'OrderStateTransitionError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+ transitionError: Scalars['String']
+ fromState: Scalars['String']
+ toState: Scalars['String']
+}
+
+/** Retured when attemting to create a Customer with an email address already registered to an existing User. */
+export type EmailAddressConflictError = ErrorResult & {
+ __typename?: 'EmailAddressConflictError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+}
+
+/** Retured when the maximum order size limit has been reached. */
+export type OrderLimitError = ErrorResult & {
+ __typename?: 'OrderLimitError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+ maxItems: Scalars['Int']
+}
+
+/** Retured when attemting to set a negative OrderLine quantity. */
+export type NegativeQuantityError = ErrorResult & {
+ __typename?: 'NegativeQuantityError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+}
+
+/** Returned when attempting to add more items to the Order than are available */
+export type InsufficientStockError = ErrorResult & {
+ __typename?: 'InsufficientStockError'
+ errorCode: ErrorCode
+ message: Scalars['String']
+ quantityAvailable: Scalars['Int']
+ order: Order
+}
+
+export type PaginatedList = {
+ items: Array
+ totalItems: Scalars['Int']
+}
+
+export type Node = {
+ id: Scalars['ID']
+}
+
+export type ErrorResult = {
+ errorCode: ErrorCode
+ message: Scalars['String']
+}
+
+export type Adjustment = {
+ __typename?: 'Adjustment'
+ adjustmentSource: Scalars['String']
+ type: AdjustmentType
+ description: Scalars['String']
+ amount: Scalars['Int']
+}
+
+export type TaxLine = {
+ __typename?: 'TaxLine'
+ description: Scalars['String']
+ taxRate: Scalars['Float']
+}
+
+export type ConfigArg = {
+ __typename?: 'ConfigArg'
+ name: Scalars['String']
+ value: Scalars['String']
+}
+
+export type ConfigArgDefinition = {
+ __typename?: 'ConfigArgDefinition'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ required: Scalars['Boolean']
+ defaultValue?: Maybe
+ label?: Maybe
+ description?: Maybe
+ ui?: Maybe
+}
+
+export type ConfigurableOperation = {
+ __typename?: 'ConfigurableOperation'
+ code: Scalars['String']
+ args: Array
+}
+
+export type ConfigurableOperationDefinition = {
+ __typename?: 'ConfigurableOperationDefinition'
+ code: Scalars['String']
+ args: Array
+ description: Scalars['String']
+}
+
+export type DeletionResponse = {
+ __typename?: 'DeletionResponse'
+ result: DeletionResult
+ message?: Maybe
+}
+
+export type ConfigArgInput = {
+ name: Scalars['String']
+ /** A JSON stringified representation of the actual value */
+ value: Scalars['String']
+}
+
+export type ConfigurableOperationInput = {
+ code: Scalars['String']
+ arguments: Array
+}
+
+export type StringOperators = {
+ eq?: Maybe
+ notEq?: Maybe
+ contains?: Maybe
+ notContains?: Maybe
+ in?: Maybe>
+ notIn?: Maybe>
+ regex?: Maybe
+}
+
+export type BooleanOperators = {
+ eq?: Maybe
+}
+
+export type NumberRange = {
+ start: Scalars['Float']
+ end: Scalars['Float']
+}
+
+export type NumberOperators = {
+ eq?: Maybe
+ lt?: Maybe
+ lte?: Maybe
+ gt?: Maybe
+ gte?: Maybe
+ between?: Maybe
+}
+
+export type DateRange = {
+ start: Scalars['DateTime']
+ end: Scalars['DateTime']
+}
+
+export type DateOperators = {
+ eq?: Maybe
+ before?: Maybe
+ after?: Maybe
+ between?: Maybe
+}
+
+export type SearchInput = {
+ term?: Maybe
+ facetValueIds?: Maybe>
+ facetValueOperator?: Maybe
+ collectionId?: Maybe
+ collectionSlug?: Maybe
+ groupByProduct?: Maybe
+ take?: Maybe
+ skip?: Maybe
+ sort?: Maybe
+}
+
+export type SearchResultSortParameter = {
+ name?: Maybe
+ price?: Maybe
+}
+
+export type CreateCustomerInput = {
+ title?: Maybe
+ firstName: Scalars['String']
+ lastName: Scalars['String']
+ phoneNumber?: Maybe
+ emailAddress: Scalars['String']
+ customFields?: Maybe
+}
+
+export type CreateAddressInput = {
+ fullName?: Maybe
+ company?: Maybe
+ streetLine1: Scalars['String']
+ streetLine2?: Maybe
+ city?: Maybe
+ province?: Maybe
+ postalCode?: Maybe
+ countryCode: Scalars['String']
+ phoneNumber?: Maybe
+ defaultShippingAddress?: Maybe
+ defaultBillingAddress?: Maybe
+ customFields?: Maybe
+}
+
+export type UpdateAddressInput = {
+ id: Scalars['ID']
+ fullName?: Maybe
+ company?: Maybe
+ streetLine1?: Maybe
+ streetLine2?: Maybe
+ city?: Maybe
+ province?: Maybe
+ postalCode?: Maybe
+ countryCode?: Maybe
+ phoneNumber?: Maybe
+ defaultShippingAddress?: Maybe
+ defaultBillingAddress?: Maybe
+ customFields?: Maybe
+}
+
+/** Indicates that an operation succeeded, where we do not want to return any more specific information. */
+export type Success = {
+ __typename?: 'Success'
+ success: Scalars['Boolean']
+}
+
+export type ShippingMethodQuote = {
+ __typename?: 'ShippingMethodQuote'
+ id: Scalars['ID']
+ price: Scalars['Int']
+ priceWithTax: Scalars['Int']
+ name: Scalars['String']
+ description: Scalars['String']
+ /** Any optional metadata returned by the ShippingCalculator in the ShippingCalculationResult */
+ metadata?: Maybe
+}
+
+export type PaymentMethodQuote = {
+ __typename?: 'PaymentMethodQuote'
+ id: Scalars['ID']
+ code: Scalars['String']
+ isEligible: Scalars['Boolean']
+ eligibilityMessage?: Maybe
+}
+
+export type Country = Node & {
+ __typename?: 'Country'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ languageCode: LanguageCode
+ code: Scalars['String']
+ name: Scalars['String']
+ enabled: Scalars['Boolean']
+ translations: Array
+}
+
+export type CountryTranslation = {
+ __typename?: 'CountryTranslation'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ languageCode: LanguageCode
+ name: Scalars['String']
+}
+
+export type CountryList = PaginatedList & {
+ __typename?: 'CountryList'
+ items: Array
+ totalItems: Scalars['Int']
+}
+
+/**
+ * @description
+ * ISO 4217 currency code
+ *
+ * @docsCategory common
+ */
+export enum CurrencyCode {
+ /** United Arab Emirates dirham */
+ Aed = 'AED',
+ /** Afghan afghani */
+ Afn = 'AFN',
+ /** Albanian lek */
+ All = 'ALL',
+ /** Armenian dram */
+ Amd = 'AMD',
+ /** Netherlands Antillean guilder */
+ Ang = 'ANG',
+ /** Angolan kwanza */
+ Aoa = 'AOA',
+ /** Argentine peso */
+ Ars = 'ARS',
+ /** Australian dollar */
+ Aud = 'AUD',
+ /** Aruban florin */
+ Awg = 'AWG',
+ /** Azerbaijani manat */
+ Azn = 'AZN',
+ /** Bosnia and Herzegovina convertible mark */
+ Bam = 'BAM',
+ /** Barbados dollar */
+ Bbd = 'BBD',
+ /** Bangladeshi taka */
+ Bdt = 'BDT',
+ /** Bulgarian lev */
+ Bgn = 'BGN',
+ /** Bahraini dinar */
+ Bhd = 'BHD',
+ /** Burundian franc */
+ Bif = 'BIF',
+ /** Bermudian dollar */
+ Bmd = 'BMD',
+ /** Brunei dollar */
+ Bnd = 'BND',
+ /** Boliviano */
+ Bob = 'BOB',
+ /** Brazilian real */
+ Brl = 'BRL',
+ /** Bahamian dollar */
+ Bsd = 'BSD',
+ /** Bhutanese ngultrum */
+ Btn = 'BTN',
+ /** Botswana pula */
+ Bwp = 'BWP',
+ /** Belarusian ruble */
+ Byn = 'BYN',
+ /** Belize dollar */
+ Bzd = 'BZD',
+ /** Canadian dollar */
+ Cad = 'CAD',
+ /** Congolese franc */
+ Cdf = 'CDF',
+ /** Swiss franc */
+ Chf = 'CHF',
+ /** Chilean peso */
+ Clp = 'CLP',
+ /** Renminbi (Chinese) yuan */
+ Cny = 'CNY',
+ /** Colombian peso */
+ Cop = 'COP',
+ /** Costa Rican colon */
+ Crc = 'CRC',
+ /** Cuban convertible peso */
+ Cuc = 'CUC',
+ /** Cuban peso */
+ Cup = 'CUP',
+ /** Cape Verde escudo */
+ Cve = 'CVE',
+ /** Czech koruna */
+ Czk = 'CZK',
+ /** Djiboutian franc */
+ Djf = 'DJF',
+ /** Danish krone */
+ Dkk = 'DKK',
+ /** Dominican peso */
+ Dop = 'DOP',
+ /** Algerian dinar */
+ Dzd = 'DZD',
+ /** Egyptian pound */
+ Egp = 'EGP',
+ /** Eritrean nakfa */
+ Ern = 'ERN',
+ /** Ethiopian birr */
+ Etb = 'ETB',
+ /** Euro */
+ Eur = 'EUR',
+ /** Fiji dollar */
+ Fjd = 'FJD',
+ /** Falkland Islands pound */
+ Fkp = 'FKP',
+ /** Pound sterling */
+ Gbp = 'GBP',
+ /** Georgian lari */
+ Gel = 'GEL',
+ /** Ghanaian cedi */
+ Ghs = 'GHS',
+ /** Gibraltar pound */
+ Gip = 'GIP',
+ /** Gambian dalasi */
+ Gmd = 'GMD',
+ /** Guinean franc */
+ Gnf = 'GNF',
+ /** Guatemalan quetzal */
+ Gtq = 'GTQ',
+ /** Guyanese dollar */
+ Gyd = 'GYD',
+ /** Hong Kong dollar */
+ Hkd = 'HKD',
+ /** Honduran lempira */
+ Hnl = 'HNL',
+ /** Croatian kuna */
+ Hrk = 'HRK',
+ /** Haitian gourde */
+ Htg = 'HTG',
+ /** Hungarian forint */
+ Huf = 'HUF',
+ /** Indonesian rupiah */
+ Idr = 'IDR',
+ /** Israeli new shekel */
+ Ils = 'ILS',
+ /** Indian rupee */
+ Inr = 'INR',
+ /** Iraqi dinar */
+ Iqd = 'IQD',
+ /** Iranian rial */
+ Irr = 'IRR',
+ /** Icelandic króna */
+ Isk = 'ISK',
+ /** Jamaican dollar */
+ Jmd = 'JMD',
+ /** Jordanian dinar */
+ Jod = 'JOD',
+ /** Japanese yen */
+ Jpy = 'JPY',
+ /** Kenyan shilling */
+ Kes = 'KES',
+ /** Kyrgyzstani som */
+ Kgs = 'KGS',
+ /** Cambodian riel */
+ Khr = 'KHR',
+ /** Comoro franc */
+ Kmf = 'KMF',
+ /** North Korean won */
+ Kpw = 'KPW',
+ /** South Korean won */
+ Krw = 'KRW',
+ /** Kuwaiti dinar */
+ Kwd = 'KWD',
+ /** Cayman Islands dollar */
+ Kyd = 'KYD',
+ /** Kazakhstani tenge */
+ Kzt = 'KZT',
+ /** Lao kip */
+ Lak = 'LAK',
+ /** Lebanese pound */
+ Lbp = 'LBP',
+ /** Sri Lankan rupee */
+ Lkr = 'LKR',
+ /** Liberian dollar */
+ Lrd = 'LRD',
+ /** Lesotho loti */
+ Lsl = 'LSL',
+ /** Libyan dinar */
+ Lyd = 'LYD',
+ /** Moroccan dirham */
+ Mad = 'MAD',
+ /** Moldovan leu */
+ Mdl = 'MDL',
+ /** Malagasy ariary */
+ Mga = 'MGA',
+ /** Macedonian denar */
+ Mkd = 'MKD',
+ /** Myanmar kyat */
+ Mmk = 'MMK',
+ /** Mongolian tögrög */
+ Mnt = 'MNT',
+ /** Macanese pataca */
+ Mop = 'MOP',
+ /** Mauritanian ouguiya */
+ Mru = 'MRU',
+ /** Mauritian rupee */
+ Mur = 'MUR',
+ /** Maldivian rufiyaa */
+ Mvr = 'MVR',
+ /** Malawian kwacha */
+ Mwk = 'MWK',
+ /** Mexican peso */
+ Mxn = 'MXN',
+ /** Malaysian ringgit */
+ Myr = 'MYR',
+ /** Mozambican metical */
+ Mzn = 'MZN',
+ /** Namibian dollar */
+ Nad = 'NAD',
+ /** Nigerian naira */
+ Ngn = 'NGN',
+ /** Nicaraguan córdoba */
+ Nio = 'NIO',
+ /** Norwegian krone */
+ Nok = 'NOK',
+ /** Nepalese rupee */
+ Npr = 'NPR',
+ /** New Zealand dollar */
+ Nzd = 'NZD',
+ /** Omani rial */
+ Omr = 'OMR',
+ /** Panamanian balboa */
+ Pab = 'PAB',
+ /** Peruvian sol */
+ Pen = 'PEN',
+ /** Papua New Guinean kina */
+ Pgk = 'PGK',
+ /** Philippine peso */
+ Php = 'PHP',
+ /** Pakistani rupee */
+ Pkr = 'PKR',
+ /** Polish złoty */
+ Pln = 'PLN',
+ /** Paraguayan guaraní */
+ Pyg = 'PYG',
+ /** Qatari riyal */
+ Qar = 'QAR',
+ /** Romanian leu */
+ Ron = 'RON',
+ /** Serbian dinar */
+ Rsd = 'RSD',
+ /** Russian ruble */
+ Rub = 'RUB',
+ /** Rwandan franc */
+ Rwf = 'RWF',
+ /** Saudi riyal */
+ Sar = 'SAR',
+ /** Solomon Islands dollar */
+ Sbd = 'SBD',
+ /** Seychelles rupee */
+ Scr = 'SCR',
+ /** Sudanese pound */
+ Sdg = 'SDG',
+ /** Swedish krona/kronor */
+ Sek = 'SEK',
+ /** Singapore dollar */
+ Sgd = 'SGD',
+ /** Saint Helena pound */
+ Shp = 'SHP',
+ /** Sierra Leonean leone */
+ Sll = 'SLL',
+ /** Somali shilling */
+ Sos = 'SOS',
+ /** Surinamese dollar */
+ Srd = 'SRD',
+ /** South Sudanese pound */
+ Ssp = 'SSP',
+ /** São Tomé and Príncipe dobra */
+ Stn = 'STN',
+ /** Salvadoran colón */
+ Svc = 'SVC',
+ /** Syrian pound */
+ Syp = 'SYP',
+ /** Swazi lilangeni */
+ Szl = 'SZL',
+ /** Thai baht */
+ Thb = 'THB',
+ /** Tajikistani somoni */
+ Tjs = 'TJS',
+ /** Turkmenistan manat */
+ Tmt = 'TMT',
+ /** Tunisian dinar */
+ Tnd = 'TND',
+ /** Tongan paʻanga */
+ Top = 'TOP',
+ /** Turkish lira */
+ Try = 'TRY',
+ /** Trinidad and Tobago dollar */
+ Ttd = 'TTD',
+ /** New Taiwan dollar */
+ Twd = 'TWD',
+ /** Tanzanian shilling */
+ Tzs = 'TZS',
+ /** Ukrainian hryvnia */
+ Uah = 'UAH',
+ /** Ugandan shilling */
+ Ugx = 'UGX',
+ /** United States dollar */
+ Usd = 'USD',
+ /** Uruguayan peso */
+ Uyu = 'UYU',
+ /** Uzbekistan som */
+ Uzs = 'UZS',
+ /** Venezuelan bolívar soberano */
+ Ves = 'VES',
+ /** Vietnamese đồng */
+ Vnd = 'VND',
+ /** Vanuatu vatu */
+ Vuv = 'VUV',
+ /** Samoan tala */
+ Wst = 'WST',
+ /** CFA franc BEAC */
+ Xaf = 'XAF',
+ /** East Caribbean dollar */
+ Xcd = 'XCD',
+ /** CFA franc BCEAO */
+ Xof = 'XOF',
+ /** CFP franc (franc Pacifique) */
+ Xpf = 'XPF',
+ /** Yemeni rial */
+ Yer = 'YER',
+ /** South African rand */
+ Zar = 'ZAR',
+ /** Zambian kwacha */
+ Zmw = 'ZMW',
+ /** Zimbabwean dollar */
+ Zwl = 'ZWL',
+}
+
+export type CustomField = {
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+}
+
+export type StringCustomFieldConfig = CustomField & {
+ __typename?: 'StringCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ length?: Maybe
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+ pattern?: Maybe
+ options?: Maybe>
+}
+
+export type StringFieldOption = {
+ __typename?: 'StringFieldOption'
+ value: Scalars['String']
+ label?: Maybe>
+}
+
+export type LocaleStringCustomFieldConfig = CustomField & {
+ __typename?: 'LocaleStringCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ length?: Maybe
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+ pattern?: Maybe
+}
+
+export type IntCustomFieldConfig = CustomField & {
+ __typename?: 'IntCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+ min?: Maybe
+ max?: Maybe
+ step?: Maybe
+}
+
+export type FloatCustomFieldConfig = CustomField & {
+ __typename?: 'FloatCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+ min?: Maybe
+ max?: Maybe
+ step?: Maybe
+}
+
+export type BooleanCustomFieldConfig = CustomField & {
+ __typename?: 'BooleanCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+}
+
+/**
+ * Expects the same validation formats as the ` ` HTML element.
+ * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes
+ */
+export type DateTimeCustomFieldConfig = CustomField & {
+ __typename?: 'DateTimeCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+ min?: Maybe
+ max?: Maybe
+ step?: Maybe
+}
+
+export type RelationCustomFieldConfig = CustomField & {
+ __typename?: 'RelationCustomFieldConfig'
+ name: Scalars['String']
+ type: Scalars['String']
+ list: Scalars['Boolean']
+ label?: Maybe>
+ description?: Maybe>
+ readonly?: Maybe
+ internal?: Maybe
+ entity: Scalars['String']
+ scalarFields: Array
+}
+
+export type LocalizedString = {
+ __typename?: 'LocalizedString'
+ languageCode: LanguageCode
+ value: Scalars['String']
+}
+
+export type CustomFieldConfig =
+ | StringCustomFieldConfig
+ | LocaleStringCustomFieldConfig
+ | IntCustomFieldConfig
+ | FloatCustomFieldConfig
+ | BooleanCustomFieldConfig
+ | DateTimeCustomFieldConfig
+ | RelationCustomFieldConfig
+
+export type CustomerGroup = Node & {
+ __typename?: 'CustomerGroup'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ name: Scalars['String']
+ customers: CustomerList
+}
+
+export type CustomerGroupCustomersArgs = {
+ options?: Maybe
+}
+
+export type Customer = Node & {
+ __typename?: 'Customer'
+ id: Scalars['ID']
+ createdAt: Scalars['DateTime']
+ updatedAt: Scalars['DateTime']
+ title?: Maybe
+ firstName: Scalars['String']
+ lastName: Scalars['String']
+ phoneNumber?: Maybe
+ emailAddress: Scalars['String']
+ addresses?: Maybe>
+ orders: OrderList
+ user?: Maybe
+ customFields?: Maybe
+}
+
+export type CustomerOrdersArgs = {
+ options?: Maybe