commerce/framework/commerce/new-provider.md
Luis Alvarez D a98c95d447
[WIP] Node.js provider for the API (#252)
* Adding multiple initial files

* Updated the default cart endpoint

* Fixes

* Updated CommerceAPI class for better usage

* Adding more migration changes

* Taking multiple steps into better API types

* Adding more experimental types

* Removed many testing types

* Adding types, fixes and other updates

* Updated commerce types

* Updated types for hooks now using the API

* Updated mutation types

* Simplified cart types for the provider

* Updated cart hooks

* Remove normalizers from the hooks

* Updated cart endpoint

* Removed cart handlers

* bug fixes

* Improve quantity input behavior in cart item

* Removed endpoints folder

* Making progress on api operations

* Moved method

* Moved types

* Changed the way ops are created

* Added customer endpoint

* Login endpoint

* Added logout endpoint

* Add missing logout files

* Added signup endpoint

* Removed customers old endpoints

* Moved endpoints to nested folder

* Removed old customer endpoint builders

* Updated login operation

* Updated login operation

* Added getAllPages operation

* Renamed endpoint operations to handlers

* Changed import

* Renamed operations to handlers in usage

* Moved getAllPages everywhere

* Moved getPage

* Updated getPage usage

* Moved getSiteInfo

* Added def types for product

* Updated type

* moved products catalog endpoint

* removed old catalog endpoint

* Moved wishlist

* Removed commerce.endpoint

* Replaced references to commerce.endpoint

* Updated catalog products

* Moved checkout api

* Added the get customer wishlist operation

* Removed old wishlist stuff

* Added getAllProductPaths operation

* updated reference to operation

* Moved getAllProducts

* Updated getProduct operation

* Removed old getConfig and references

* Removed is-allowed-method from BC

* Updated types for auth hooks

* Updated useCustomer and core types

* Updated useData and util hooks

* Updated useSearch hook

* Updated types for useWishlist

* Added index for types

* Fixes

* Updated urls to the API

* Renamed fetchInput to fetcherInput

* Updated fetch type

* Fixes in search hook

* Updated Shopify Provider Structure (#340)

* Add codegen, update fragments & schemas

* Update checkout-create.ts

* Update checkout-create.ts

* Update README.md

* Update product mutations & queries

* Uptate customer fetch types

* Update schemas

* Start updates

* Moved Page, AllPages & Site Info

* Moved product, all products (paths)

* Add translations, update operations & fixes

* Update api endpoints, types & fixes

* Add api checkout endpoint

* Updates

* Fixes

* Update commerce.config.json

Co-authored-by: B <curciobelen@gmail.com>

* Added category type and normalizer

* updated init script to exclude other providers

* Excluded swell and venture temporarily

* Fix category & color normalization

* Fixed category normalizer in shopify

* Don't use getSlug for category on /search

* Update colors.ts

Co-authored-by: cond0r <pinte_catalin@yahoo.com>
Co-authored-by: B <curciobelen@gmail.com>
2021-06-01 03:18:10 -05:00

6.9 KiB

Adding a new Commerce Provider

A commerce provider is a headless e-commerce platform that integrates with the Commerce Framework. Right now we have the following providers:

Adding a commerce provider means adding a new folder in framework with a folder structure like the next one:

  • api
    • index.ts
  • product
    • usePrice
    • useSearch
    • getProduct
    • getAllProducts
  • wishlist
    • useWishlist
    • useAddItem
    • useRemoveItem
  • auth
    • useLogin
    • useLogout
    • useSignup
  • customer
    • useCustomer
    • getCustomerId
    • getCustomerWistlist
  • cart
    • useCart
    • useAddItem
    • useRemoveItem
    • useUpdateItem
  • env.template
  • index.ts
  • provider.ts
  • commerce.config.json
  • next.config.js
  • README.md

provider.ts exports a provider object with handlers for the Commerce Hooks and api/index.ts exports a Node.js provider for the Commerce API

Important: We use TypeScript for every provider and expect its usage for every new one.

The app imports from the provider directly instead of the core commerce folder (framework/commerce), but all providers are interchangeable and to achieve it every provider always has to implement the core types and helpers.

The provider folder should only depend on framework/commerce and dependencies in the main package.json. In the future we'll move the framework folder to a package that can be shared easily for multiple apps.

Adding the provider hooks

Using BigCommerce as an example. The first thing to do is export a CommerceProvider component that includes a provider object with all the handlers that can be used for hooks:

import type { ReactNode } from 'react'
import {
  CommerceConfig,
  CommerceProvider as CoreCommerceProvider,
  useCommerce as useCoreCommerce,
} from '@commerce'
import { bigcommerceProvider, BigcommerceProvider } from './provider'

export { bigcommerceProvider }
export type { BigcommerceProvider }

export const bigcommerceConfig: CommerceConfig = {
  locale: 'en-us',
  cartCookie: 'bc_cartId',
}

export type BigcommerceConfig = Partial<CommerceConfig>

export type BigcommerceProps = {
  children?: ReactNode
  locale: string
} & BigcommerceConfig

export function CommerceProvider({ children, ...config }: BigcommerceProps) {
  return (
    <CoreCommerceProvider
      provider={bigcommerceProvider}
      config={{ ...bigcommerceConfig, ...config }}
    >
      {children}
    </CoreCommerceProvider>
  )
}

export const useCommerce = () => useCoreCommerce<BigcommerceProvider>()

The exported types and components extend from the core ones exported by @commerce, which refers to framework/commerce.

The bigcommerceProvider object looks like this:

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 useWishlist } from './wishlist/use-wishlist'
import { handler as useWishlistAddItem } from './wishlist/use-add-item'
import { handler as useWishlistRemoveItem } from './wishlist/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 bigcommerceProvider = {
  locale: 'en-us',
  cartCookie: 'bc_cartId',
  fetcher,
  cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
  wishlist: {
    useWishlist,
    useAddItem: useWishlistAddItem,
    useRemoveItem: useWishlistRemoveItem,
  },
  customer: { useCustomer },
  products: { useSearch },
  auth: { useLogin, useLogout, useSignup },
}

export type BigcommerceProvider = typeof bigcommerceProvider

The provider object, in this case bigcommerceProvider, has to match the Provider type defined in framework/commerce.

A hook handler, like useCart, looks like this:

import { useMemo } from 'react'
import { SWRHook } from '@commerce/utils/types'
import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart'
import { normalizeCart } from '../lib/normalize'
import type { Cart } from '../types'

export default useCart as UseCart<typeof handler>

export const handler: SWRHook<
  Cart | null,
  {},
  FetchCartInput,
  { isEmpty?: boolean }
> = {
  fetchOptions: {
    url: '/api/cart',
    method: 'GET',
  },
  async fetcher({ input: { cartId }, options, fetch }) {
    const data = cartId ? await fetch(options) : null
    return data && normalizeCart(data)
  },
  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]
    )
  },
}

In the case of data fetching hooks like useCart each handler has to implement the SWRHook type that's defined in the core types. For mutations it's the MutationHook, e.g for useAddItem:

import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import { normalizeCart } from '../lib/normalize'
import type {
  Cart,
  BigcommerceCart,
  CartItemBody,
  AddCartItemBody,
} from '../types'
import useCart from './use-cart'

export default useAddItem as UseAddItem<typeof handler>

export const handler: MutationHook<Cart, {}, CartItemBody> = {
  fetchOptions: {
    url: '/api/cart',
    method: 'POST',
  },
  async fetcher({ input: item, options, fetch }) {
    if (
      item.quantity &&
      (!Number.isInteger(item.quantity) || item.quantity! < 1)
    ) {
      throw new CommerceError({
        message: 'The item quantity has to be a valid integer greater than 0',
      })
    }

    const data = await fetch<BigcommerceCart, AddCartItemBody>({
      ...options,
      body: { item },
    })

    return normalizeCart(data)
  },
  useHook: ({ fetch }) => () => {
    const { mutate } = useCart()

    return useCallback(
      async function addItem(input) {
        const data = await fetch({ input })
        await mutate(data, false)
        return data
      },
      [fetch, mutate]
    )
  },
}

Adding the Node.js provider API

TODO

The commerce API is currently going through a refactor in https://github.com/vercel/commerce/pull/252 - We'll update the docs once the API is released.