+
+export function CoreCommerceProvider({
provider,
children,
- config,
}: CommerceProps
) {
- if (!config) {
- throw new Error('CommerceProvider requires a valid config object')
- }
-
const providerRef = useRef(provider)
// TODO: Remove the fetcherRef
const fetcherRef = useRef(provider.fetcher)
- // Because the config is an object, if the parent re-renders this provider
- // will re-render every consumer unless we memoize the config
+ // If the parent re-renders this provider will re-render every
+ // consumer unless we memoize the config
+ const { locale, cartCookie } = providerRef.current
const cfg = useMemo(
- () => ({
- providerRef,
- fetcherRef,
- locale: config.locale,
- cartCookie: config.cartCookie,
- }),
- [config.locale, config.cartCookie]
+ () => ({ providerRef, fetcherRef, locale, cartCookie }),
+ [locale, cartCookie]
)
return {children}
}
+export function getCommerceProvider
(provider: P) {
+ return function CommerceProvider({
+ children,
+ ...props
+ }: CommerceProviderProps) {
+ return (
+
+ {children}
+
+ )
+ }
+}
+
export function useCommerce
() {
return useContext(Commerce) as CommerceContextValue
}
diff --git a/framework/commerce/product/use-price.tsx b/packages/commerce/src/product/use-price.tsx
similarity index 100%
rename from framework/commerce/product/use-price.tsx
rename to packages/commerce/src/product/use-price.tsx
diff --git a/framework/commerce/product/use-search.tsx b/packages/commerce/src/product/use-search.tsx
similarity index 100%
rename from framework/commerce/product/use-search.tsx
rename to packages/commerce/src/product/use-search.tsx
diff --git a/framework/commerce/types/cart.ts b/packages/commerce/src/types/cart.ts
similarity index 100%
rename from framework/commerce/types/cart.ts
rename to packages/commerce/src/types/cart.ts
diff --git a/packages/commerce/src/types/checkout.ts b/packages/commerce/src/types/checkout.ts
new file mode 100644
index 000000000..417604fdb
--- /dev/null
+++ b/packages/commerce/src/types/checkout.ts
@@ -0,0 +1,57 @@
+import type { UseSubmitCheckout } from '../checkout/use-submit-checkout'
+import type { Address, AddressFields } from './customer/address'
+import type { Card, CardFields } from './customer/card'
+
+// Index
+export type Checkout = any
+
+export type CheckoutTypes = {
+ card?: Card | CardFields
+ address?: Address | AddressFields
+ checkout?: Checkout
+ hasPayment?: boolean
+ hasShipping?: boolean
+}
+
+export type SubmitCheckoutHook = {
+ data: T
+ input?: T
+ fetcherInput: T
+ body: { item: T }
+ actionInput: T
+}
+
+export type GetCheckoutHook = {
+ data: T['checkout'] | null
+ input: {}
+ fetcherInput: { cartId?: string }
+ swrState: { isEmpty: boolean }
+ mutations: { submit: UseSubmitCheckout }
+}
+
+export type CheckoutHooks = {
+ submitCheckout?: SubmitCheckoutHook
+ getCheckout: GetCheckoutHook
+}
+
+export type GetCheckoutHandler =
+ GetCheckoutHook & {
+ body: { cartId: string }
+ }
+
+export type SubmitCheckoutHandler =
+ SubmitCheckoutHook & {
+ body: { cartId: string }
+ }
+
+export type CheckoutHandlers = {
+ getCheckout: GetCheckoutHandler
+ submitCheckout?: SubmitCheckoutHandler
+}
+
+export type CheckoutSchema = {
+ endpoint: {
+ options: {}
+ handlers: CheckoutHandlers
+ }
+}
diff --git a/framework/commerce/types/common.ts b/packages/commerce/src/types/common.ts
similarity index 100%
rename from framework/commerce/types/common.ts
rename to packages/commerce/src/types/common.ts
diff --git a/packages/commerce/src/types/customer/address.ts b/packages/commerce/src/types/customer/address.ts
new file mode 100644
index 000000000..8dc6ffc0d
--- /dev/null
+++ b/packages/commerce/src/types/customer/address.ts
@@ -0,0 +1,111 @@
+export interface Address {
+ id: string
+ mask: string
+}
+
+export interface AddressFields {
+ type: string
+ firstName: string
+ lastName: string
+ company: string
+ streetNumber: string
+ apartments: string
+ zipCode: string
+ city: string
+ country: string
+}
+
+export type CustomerAddressTypes = {
+ address?: Address
+ fields: AddressFields
+}
+
+export type GetAddressesHook<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = {
+ data: T['address'][] | null
+ input: {}
+ fetcherInput: { cartId?: string }
+ swrState: { isEmpty: boolean }
+}
+
+export type AddItemHook =
+ {
+ data: T['address']
+ input?: T['fields']
+ fetcherInput: T['fields']
+ body: { item: T['fields'] }
+ actionInput: T['fields']
+ }
+
+export type UpdateItemHook<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = {
+ data: T['address'] | null
+ input: { item?: T['fields']; wait?: number }
+ fetcherInput: { itemId: string; item: T['fields'] }
+ body: { itemId: string; item: T['fields'] }
+ actionInput: T['fields'] & { id: string }
+}
+
+export type RemoveItemHook<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = {
+ data: T['address'] | null
+ input: { item?: T['address'] }
+ fetcherInput: { itemId: string }
+ body: { itemId: string }
+ actionInput: { id: string }
+}
+
+export type CustomerAddressHooks<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = {
+ getAddresses: GetAddressesHook
+ addItem: AddItemHook
+ updateItem: UpdateItemHook
+ removeItem: RemoveItemHook
+}
+
+export type AddressHandler<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = GetAddressesHook & {
+ body: { cartId?: string }
+}
+
+export type AddItemHandler<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = AddItemHook & {
+ body: { cartId: string }
+}
+
+export type UpdateItemHandler<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = UpdateItemHook & {
+ data: T['address']
+ body: { cartId: string }
+}
+
+export type RemoveItemHandler<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = RemoveItemHook & {
+ body: { cartId: string }
+}
+
+export type CustomerAddressHandlers<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = {
+ getAddresses: GetAddressesHook
+ addItem: AddItemHandler
+ updateItem: UpdateItemHandler
+ removeItem: RemoveItemHandler
+}
+
+export type CustomerAddressSchema<
+ T extends CustomerAddressTypes = CustomerAddressTypes
+> = {
+ endpoint: {
+ options: {}
+ handlers: CustomerAddressHandlers
+ }
+}
diff --git a/packages/commerce/src/types/customer/card.ts b/packages/commerce/src/types/customer/card.ts
new file mode 100644
index 000000000..e9b220dcc
--- /dev/null
+++ b/packages/commerce/src/types/customer/card.ts
@@ -0,0 +1,102 @@
+export interface Card {
+ id: string
+ mask: string
+ provider: string
+}
+
+export interface CardFields {
+ cardHolder: string
+ cardNumber: string
+ cardExpireDate: string
+ cardCvc: string
+ firstName: string
+ lastName: string
+ company: string
+ streetNumber: string
+ zipCode: string
+ city: string
+ country: string
+}
+
+export type CustomerCardTypes = {
+ card?: Card
+ fields: CardFields
+}
+
+export type GetCardsHook = {
+ data: T['card'][] | null
+ input: {}
+ fetcherInput: { cartId?: string }
+ swrState: { isEmpty: boolean }
+}
+
+export type AddItemHook = {
+ data: T['card']
+ input?: T['fields']
+ fetcherInput: T['fields']
+ body: { item: T['fields'] }
+ actionInput: T['fields']
+}
+
+export type UpdateItemHook = {
+ data: T['card'] | null
+ input: { item?: T['fields']; wait?: number }
+ fetcherInput: { itemId: string; item: T['fields'] }
+ body: { itemId: string; item: T['fields'] }
+ actionInput: T['fields'] & { id: string }
+}
+
+export type RemoveItemHook = {
+ data: T['card'] | null
+ input: { item?: T['card'] }
+ fetcherInput: { itemId: string }
+ body: { itemId: string }
+ actionInput: { id: string }
+}
+
+export type CustomerCardHooks =
+ {
+ getCards: GetCardsHook
+ addItem: AddItemHook
+ updateItem: UpdateItemHook
+ removeItem: RemoveItemHook
+ }
+
+export type CardsHandler =
+ GetCardsHook & {
+ body: { cartId?: string }
+ }
+
+export type AddItemHandler =
+ AddItemHook & {
+ body: { cartId: string }
+ }
+
+export type UpdateItemHandler =
+ UpdateItemHook & {
+ data: T['card']
+ body: { cartId: string }
+ }
+
+export type RemoveItemHandler =
+ RemoveItemHook & {
+ body: { cartId: string }
+ }
+
+export type CustomerCardHandlers<
+ T extends CustomerCardTypes = CustomerCardTypes
+> = {
+ getCards: GetCardsHook
+ addItem: AddItemHandler
+ updateItem: UpdateItemHandler
+ removeItem: RemoveItemHandler
+}
+
+export type CustomerCardSchema<
+ T extends CustomerCardTypes = CustomerCardTypes
+> = {
+ endpoint: {
+ options: {}
+ handlers: CustomerCardHandlers
+ }
+}
diff --git a/framework/commerce/types/customer.ts b/packages/commerce/src/types/customer/index.ts
similarity index 87%
rename from framework/commerce/types/customer.ts
rename to packages/commerce/src/types/customer/index.ts
index ba90acdf4..f0b210f62 100644
--- a/framework/commerce/types/customer.ts
+++ b/packages/commerce/src/types/customer/index.ts
@@ -1,3 +1,6 @@
+export * as Card from './card'
+export * as Address from './address'
+
// TODO: define this type
export type Customer = any
diff --git a/framework/commerce/types/index.ts b/packages/commerce/src/types/index.ts
similarity index 100%
rename from framework/commerce/types/index.ts
rename to packages/commerce/src/types/index.ts
diff --git a/framework/commerce/types/login.ts b/packages/commerce/src/types/login.ts
similarity index 100%
rename from framework/commerce/types/login.ts
rename to packages/commerce/src/types/login.ts
diff --git a/framework/commerce/types/logout.ts b/packages/commerce/src/types/logout.ts
similarity index 100%
rename from framework/commerce/types/logout.ts
rename to packages/commerce/src/types/logout.ts
diff --git a/framework/commerce/types/page.ts b/packages/commerce/src/types/page.ts
similarity index 100%
rename from framework/commerce/types/page.ts
rename to packages/commerce/src/types/page.ts
diff --git a/framework/commerce/types/product.ts b/packages/commerce/src/types/product.ts
similarity index 100%
rename from framework/commerce/types/product.ts
rename to packages/commerce/src/types/product.ts
diff --git a/framework/commerce/types/signup.ts b/packages/commerce/src/types/signup.ts
similarity index 100%
rename from framework/commerce/types/signup.ts
rename to packages/commerce/src/types/signup.ts
diff --git a/framework/commerce/types/site.ts b/packages/commerce/src/types/site.ts
similarity index 100%
rename from framework/commerce/types/site.ts
rename to packages/commerce/src/types/site.ts
diff --git a/framework/commerce/types/wishlist.ts b/packages/commerce/src/types/wishlist.ts
similarity index 100%
rename from framework/commerce/types/wishlist.ts
rename to packages/commerce/src/types/wishlist.ts
diff --git a/framework/commerce/utils/default-fetcher.ts b/packages/commerce/src/utils/default-fetcher.ts
similarity index 100%
rename from framework/commerce/utils/default-fetcher.ts
rename to packages/commerce/src/utils/default-fetcher.ts
diff --git a/framework/commerce/utils/define-property.ts b/packages/commerce/src/utils/define-property.ts
similarity index 58%
rename from framework/commerce/utils/define-property.ts
rename to packages/commerce/src/utils/define-property.ts
index 875aaaa82..e89735226 100644
--- a/framework/commerce/utils/define-property.ts
+++ b/packages/commerce/src/utils/define-property.ts
@@ -11,16 +11,18 @@ type InferValue = Desc extends {
? Record
: never
-type DefineProperty =
- Desc extends { writable: any; set(val: any): any }
- ? never
- : Desc extends { writable: any; get(): any }
- ? never
- : Desc extends { writable: false }
- ? Readonly>
- : Desc extends { writable: true }
- ? InferValue
- : Readonly>
+type DefineProperty<
+ Prop extends PropertyKey,
+ Desc extends PropertyDescriptor
+> = Desc extends { writable: any; set(val: any): any }
+ ? never
+ : Desc extends { writable: any; get(): any }
+ ? never
+ : Desc extends { writable: false }
+ ? Readonly>
+ : Desc extends { writable: true }
+ ? InferValue
+ : Readonly>
export default function defineProperty<
Obj extends object,
diff --git a/framework/commerce/utils/errors.ts b/packages/commerce/src/utils/errors.ts
similarity index 100%
rename from framework/commerce/utils/errors.ts
rename to packages/commerce/src/utils/errors.ts
diff --git a/framework/commerce/utils/types.ts b/packages/commerce/src/utils/types.ts
similarity index 89%
rename from framework/commerce/utils/types.ts
rename to packages/commerce/src/utils/types.ts
index 751cea4a5..317fea165 100644
--- a/framework/commerce/utils/types.ts
+++ b/packages/commerce/src/utils/types.ts
@@ -1,4 +1,4 @@
-import type { ConfigInterface } from 'swr'
+import type { SWRConfiguration } from 'swr'
import type { CommerceError } from './errors'
import type { ResponseState } from './use-data'
@@ -10,10 +10,9 @@ export type Override = Omit & K
/**
* Returns the properties in T with the properties in type K changed from optional to required
*/
-export type PickRequired = Omit &
- {
- [P in K]-?: NonNullable
- }
+export type PickRequired = Omit & {
+ [P in K]-?: NonNullable
+}
/**
* Core fetcher added by CommerceProvider
@@ -87,6 +86,8 @@ export type HookSchemaBase = {
export type SWRHookSchemaBase = HookSchemaBase & {
// Custom state added to the response object of SWR
swrState?: {}
+ // Instances of MutationSchemaBase that the hook returns for better DX
+ mutations?: Record['useHook']>>
}
export type MutationSchemaBase = HookSchemaBase & {
@@ -102,7 +103,7 @@ export type SWRHook = {
context: SWRHookContext
): HookFunction<
H['input'] & { swrOptions?: SwrOptions },
- ResponseState & H['swrState']
+ ResponseState & H['swrState'] & H['mutations']
>
fetchOptions: HookFetcherOptions
fetcher?: HookFetcherFn
@@ -139,7 +140,7 @@ export type MutationHookContext = {
: (context: { input: H['fetcherInput'] }) => H['data'] | Promise
}
-export type SwrOptions = ConfigInterface<
+export type SwrOptions = SWRConfiguration<
Data,
CommerceError,
HookFetcher
diff --git a/framework/commerce/utils/use-data.tsx b/packages/commerce/src/utils/use-data.tsx
similarity index 100%
rename from framework/commerce/utils/use-data.tsx
rename to packages/commerce/src/utils/use-data.tsx
diff --git a/framework/commerce/utils/use-hook.ts b/packages/commerce/src/utils/use-hook.ts
similarity index 100%
rename from framework/commerce/utils/use-hook.ts
rename to packages/commerce/src/utils/use-hook.ts
diff --git a/framework/commerce/wishlist/index.ts b/packages/commerce/src/wishlist/index.ts
similarity index 100%
rename from framework/commerce/wishlist/index.ts
rename to packages/commerce/src/wishlist/index.ts
diff --git a/framework/commerce/wishlist/use-add-item.tsx b/packages/commerce/src/wishlist/use-add-item.tsx
similarity index 100%
rename from framework/commerce/wishlist/use-add-item.tsx
rename to packages/commerce/src/wishlist/use-add-item.tsx
diff --git a/framework/commerce/wishlist/use-remove-item.tsx b/packages/commerce/src/wishlist/use-remove-item.tsx
similarity index 100%
rename from framework/commerce/wishlist/use-remove-item.tsx
rename to packages/commerce/src/wishlist/use-remove-item.tsx
diff --git a/framework/commerce/wishlist/use-wishlist.tsx b/packages/commerce/src/wishlist/use-wishlist.tsx
similarity index 100%
rename from framework/commerce/wishlist/use-wishlist.tsx
rename to packages/commerce/src/wishlist/use-wishlist.tsx
diff --git a/packages/commerce/taskfile.js b/packages/commerce/taskfile.js
new file mode 100644
index 000000000..39b1b2a86
--- /dev/null
+++ b/packages/commerce/taskfile.js
@@ -0,0 +1,20 @@
+export async function build(task, opts) {
+ await task
+ .source('src/**/*.+(ts|tsx|js)')
+ .swc({ dev: opts.dev, outDir: 'dist', baseUrl: 'src' })
+ .target('dist')
+ .source('src/**/*.+(cjs|json)')
+ .target('dist')
+ task.$.log('Compiled src files')
+}
+
+export async function release(task) {
+ await task.clear('dist').start('build')
+}
+
+export default async function dev(task) {
+ const opts = { dev: true }
+ await task.clear('dist')
+ await task.start('build', opts)
+ await task.watch('src/**/*.+(ts|tsx|js|cjs|json)', 'build', opts)
+}
diff --git a/packages/commerce/tsconfig.json b/packages/commerce/tsconfig.json
new file mode 100644
index 000000000..cd04ab2ff
--- /dev/null
+++ b/packages/commerce/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "outDir": "dist",
+ "baseUrl": "src",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "declaration": true,
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "incremental": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/commercejs/.env.template b/packages/commercejs/.env.template
new file mode 100644
index 000000000..daeb86c06
--- /dev/null
+++ b/packages/commercejs/.env.template
@@ -0,0 +1,7 @@
+COMMERCE_PROVIDER=commercejs
+
+# Public key for your Commerce.js account
+NEXT_PUBLIC_COMMERCEJS_PUBLIC_KEY=
+
+# The URL for the current deployment, optional but should be used for production deployments
+NEXT_PUBLIC_COMMERCEJS_DEPLOYMENT_URL=
diff --git a/packages/commercejs/.prettierignore b/packages/commercejs/.prettierignore
new file mode 100644
index 000000000..f06235c46
--- /dev/null
+++ b/packages/commercejs/.prettierignore
@@ -0,0 +1,2 @@
+node_modules
+dist
diff --git a/packages/commercejs/.prettierrc b/packages/commercejs/.prettierrc
new file mode 100644
index 000000000..e1076edfa
--- /dev/null
+++ b/packages/commercejs/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "useTabs": false
+}
diff --git a/packages/commercejs/README.md b/packages/commercejs/README.md
new file mode 100644
index 000000000..20aaf70e5
--- /dev/null
+++ b/packages/commercejs/README.md
@@ -0,0 +1,13 @@
+# [Commerce.js](https://commercejs.com/) Provider
+
+**Demo:** https://commercejs.vercel.store/
+
+To use this provider you must have a [Commerce.js account](https://commercejs.com/) and you should add some products in the Commerce.js dashboard.
+
+Next, copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
+
+```bash
+cp packages/commercejs/.env.template .env.local
+```
+
+Then, set the environment variables in `.env.local` to match the ones from your store. You'll need your Commerce.js public API key, which can be found in your Commerce.js dashboard in the `Developer -> API keys` section.
diff --git a/packages/commercejs/global.d.ts b/packages/commercejs/global.d.ts
new file mode 100644
index 000000000..3fc378752
--- /dev/null
+++ b/packages/commercejs/global.d.ts
@@ -0,0 +1 @@
+declare module '@components/checkout/context'
diff --git a/packages/commercejs/package.json b/packages/commercejs/package.json
new file mode 100644
index 000000000..f1ae97dba
--- /dev/null
+++ b/packages/commercejs/package.json
@@ -0,0 +1,80 @@
+{
+ "name": "@vercel/commerce-commercejs",
+ "version": "0.0.1",
+ "license": "MIT",
+ "scripts": {
+ "release": "taskr release",
+ "build": "taskr build",
+ "dev": "taskr",
+ "types": "tsc --emitDeclarationOnly",
+ "prettier-fix": "prettier --write ."
+ },
+ "sideEffects": false,
+ "type": "module",
+ "exports": {
+ ".": "./dist/index.js",
+ "./*": [
+ "./dist/*.js",
+ "./dist/*/index.js"
+ ],
+ "./next.config": "./dist/next.config.cjs"
+ },
+ "typesVersions": {
+ "*": {
+ "*": [
+ "src/*",
+ "src/*/index"
+ ],
+ "next.config": [
+ "dist/next.config.d.cts"
+ ]
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "publishConfig": {
+ "typesVersions": {
+ "*": {
+ "*": [
+ "dist/*.d.ts",
+ "dist/*/index.d.ts"
+ ],
+ "next.config": [
+ "dist/next.config.d.cts"
+ ]
+ }
+ }
+ },
+ "dependencies": {
+ "@chec/commerce.js": "^2.8.0",
+ "@vercel/commerce": "^0.0.1"
+ },
+ "peerDependencies": {
+ "next": "^12",
+ "react": "^17",
+ "react-dom": "^17"
+ },
+ "devDependencies": {
+ "@taskr/clear": "^1.1.0",
+ "@taskr/esnext": "^1.1.0",
+ "@taskr/watch": "^1.1.0",
+ "@types/chec__commerce.js": "^2.8.4",
+ "@types/node": "^17.0.8",
+ "@types/react": "^17.0.38",
+ "lint-staged": "^12.1.7",
+ "next": "^12.0.8",
+ "prettier": "^2.5.1",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "taskr": "^1.1.0",
+ "taskr-swc": "^0.0.1",
+ "typescript": "^4.5.4"
+ },
+ "lint-staged": {
+ "**/*.{js,jsx,ts,tsx,json}": [
+ "prettier --write",
+ "git add"
+ ]
+ }
+}
diff --git a/framework/local/api/endpoints/checkout/index.ts b/packages/commercejs/src/api/endpoints/cart/index.ts
similarity index 100%
rename from framework/local/api/endpoints/checkout/index.ts
rename to packages/commercejs/src/api/endpoints/cart/index.ts
diff --git a/framework/local/api/endpoints/customer/index.ts b/packages/commercejs/src/api/endpoints/catalog/index.ts
similarity index 100%
rename from framework/local/api/endpoints/customer/index.ts
rename to packages/commercejs/src/api/endpoints/catalog/index.ts
diff --git a/framework/local/api/endpoints/login/index.ts b/packages/commercejs/src/api/endpoints/catalog/products/index.ts
similarity index 100%
rename from framework/local/api/endpoints/login/index.ts
rename to packages/commercejs/src/api/endpoints/catalog/products/index.ts
diff --git a/framework/local/api/endpoints/catalog/products.ts b/packages/commercejs/src/api/endpoints/checkout/get-checkout.ts
similarity index 100%
rename from framework/local/api/endpoints/catalog/products.ts
rename to packages/commercejs/src/api/endpoints/checkout/get-checkout.ts
diff --git a/packages/commercejs/src/api/endpoints/checkout/index.ts b/packages/commercejs/src/api/endpoints/checkout/index.ts
new file mode 100644
index 000000000..1072902e6
--- /dev/null
+++ b/packages/commercejs/src/api/endpoints/checkout/index.ts
@@ -0,0 +1,23 @@
+import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
+import checkoutEndpoint from '@vercel/commerce/api/endpoints/checkout'
+import type { CheckoutSchema } from '../../../types/checkout'
+import type { CommercejsAPI } from '../..'
+
+import submitCheckout from './submit-checkout'
+import getCheckout from './get-checkout'
+
+export type CheckoutAPI = GetAPISchema
+
+export type CheckoutEndpoint = CheckoutAPI['endpoint']
+
+export const handlers: CheckoutEndpoint['handlers'] = {
+ submitCheckout,
+ getCheckout,
+}
+
+const checkoutApi = createEndpoint({
+ handler: checkoutEndpoint,
+ handlers,
+})
+
+export default checkoutApi
diff --git a/packages/commercejs/src/api/endpoints/checkout/submit-checkout.ts b/packages/commercejs/src/api/endpoints/checkout/submit-checkout.ts
new file mode 100644
index 000000000..e0f3012cc
--- /dev/null
+++ b/packages/commercejs/src/api/endpoints/checkout/submit-checkout.ts
@@ -0,0 +1,44 @@
+import type { CardFields } from '@vercel/commerce/types/customer/card'
+import type { AddressFields } from '@vercel/commerce/types/customer/address'
+import type { CheckoutEndpoint } from '.'
+import sdkFetcherFunction from '../../utils/sdk-fetch'
+import { normalizeTestCheckout } from '../../../utils/normalize-checkout'
+
+const submitCheckout: CheckoutEndpoint['handlers']['submitCheckout'] = async ({
+ res,
+ body: { item, cartId },
+ config: { sdkFetch },
+}) => {
+ const sdkFetcher: typeof sdkFetcherFunction = sdkFetch
+
+ // Generate a checkout token
+ const { id: checkoutToken } = await sdkFetcher(
+ 'checkout',
+ 'generateTokenFrom',
+ 'cart',
+ cartId
+ )
+
+ const shippingMethods = await sdkFetcher(
+ 'checkout',
+ 'getShippingOptions',
+ checkoutToken,
+ {
+ country: 'US',
+ }
+ )
+
+ const shippingMethodToUse = shippingMethods?.[0]?.id || ''
+ const checkoutData = normalizeTestCheckout({
+ paymentInfo: item?.card as CardFields,
+ shippingInfo: item?.address as AddressFields,
+ shippingOption: shippingMethodToUse,
+ })
+
+ // Capture the order
+ await sdkFetcher('checkout', 'capture', checkoutToken, checkoutData)
+
+ res.status(200).json({ data: null, errors: [] })
+}
+
+export default submitCheckout
diff --git a/framework/local/api/endpoints/logout/index.ts b/packages/commercejs/src/api/endpoints/customer/address/index.ts
similarity index 100%
rename from framework/local/api/endpoints/logout/index.ts
rename to packages/commercejs/src/api/endpoints/customer/address/index.ts
diff --git a/framework/local/api/endpoints/signup/index.ts b/packages/commercejs/src/api/endpoints/customer/card/index.ts
similarity index 100%
rename from framework/local/api/endpoints/signup/index.ts
rename to packages/commercejs/src/api/endpoints/customer/card/index.ts
diff --git a/framework/reactioncommerce/api/endpoints/checkout/index.ts b/packages/commercejs/src/api/endpoints/customer/index.ts
similarity index 100%
rename from framework/reactioncommerce/api/endpoints/checkout/index.ts
rename to packages/commercejs/src/api/endpoints/customer/index.ts
diff --git a/packages/commercejs/src/api/endpoints/login/index.ts b/packages/commercejs/src/api/endpoints/login/index.ts
new file mode 100644
index 000000000..a6dbb4432
--- /dev/null
+++ b/packages/commercejs/src/api/endpoints/login/index.ts
@@ -0,0 +1,18 @@
+import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
+import loginEndpoint from '@vercel/commerce/api/endpoints/login'
+import type { LoginSchema } from '../../../types/login'
+import type { CommercejsAPI } from '../..'
+import login from './login'
+
+export type LoginAPI = GetAPISchema
+
+export type LoginEndpoint = LoginAPI['endpoint']
+
+export const handlers: LoginEndpoint['handlers'] = { login }
+
+const loginApi = createEndpoint({
+ handler: loginEndpoint,
+ handlers,
+})
+
+export default loginApi
diff --git a/packages/commercejs/src/api/endpoints/login/login.ts b/packages/commercejs/src/api/endpoints/login/login.ts
new file mode 100644
index 000000000..b9088ad22
--- /dev/null
+++ b/packages/commercejs/src/api/endpoints/login/login.ts
@@ -0,0 +1,33 @@
+import { serialize } from 'cookie'
+import sdkFetcherFunction from '../../utils/sdk-fetch'
+import { getDeploymentUrl } from '../../../utils/get-deployment-url'
+import type { LoginEndpoint } from '.'
+
+const login: LoginEndpoint['handlers']['login'] = async ({
+ req,
+ res,
+ config: { sdkFetch, customerCookie },
+}) => {
+ const sdkFetcher: typeof sdkFetcherFunction = sdkFetch
+ const redirectUrl = getDeploymentUrl()
+ try {
+ const loginToken = req.query?.token as string
+ if (!loginToken) {
+ res.redirect(redirectUrl)
+ }
+ const { jwt } = await sdkFetcher('customer', 'getToken', loginToken, false)
+ res.setHeader(
+ 'Set-Cookie',
+ serialize(customerCookie, jwt, {
+ secure: process.env.NODE_ENV === 'production',
+ maxAge: 60 * 60 * 24,
+ path: '/',
+ })
+ )
+ res.redirect(redirectUrl)
+ } catch {
+ res.redirect(redirectUrl)
+ }
+}
+
+export default login
diff --git a/framework/reactioncommerce/api/endpoints/customers/index.ts b/packages/commercejs/src/api/endpoints/logout/index.ts
similarity index 100%
rename from framework/reactioncommerce/api/endpoints/customers/index.ts
rename to packages/commercejs/src/api/endpoints/logout/index.ts
diff --git a/framework/vendure/api/endpoints/cart/index.ts b/packages/commercejs/src/api/endpoints/signup/index.ts
similarity index 100%
rename from framework/vendure/api/endpoints/cart/index.ts
rename to packages/commercejs/src/api/endpoints/signup/index.ts
diff --git a/framework/local/api/endpoints/wishlist/index.tsx b/packages/commercejs/src/api/endpoints/wishlist/index.tsx
similarity index 100%
rename from framework/local/api/endpoints/wishlist/index.tsx
rename to packages/commercejs/src/api/endpoints/wishlist/index.tsx
diff --git a/packages/commercejs/src/api/index.ts b/packages/commercejs/src/api/index.ts
new file mode 100644
index 000000000..1bc821865
--- /dev/null
+++ b/packages/commercejs/src/api/index.ts
@@ -0,0 +1,46 @@
+import type { CommerceAPI, CommerceAPIConfig } from '@vercel/commerce/api'
+import { getCommerceApi as commerceApi } from '@vercel/commerce/api'
+
+import getAllPages from './operations/get-all-pages'
+import getPage from './operations/get-page'
+import getSiteInfo from './operations/get-site-info'
+import getAllProductPaths from './operations/get-all-product-paths'
+import getAllProducts from './operations/get-all-products'
+import getProduct from './operations/get-product'
+import sdkFetch from './utils/sdk-fetch'
+import createGraphqlFetcher from './utils/graphql-fetch'
+import { API_URL, CART_COOKIE, CUSTOMER_COOKIE } from '../constants'
+
+export interface CommercejsConfig extends CommerceAPIConfig {
+ sdkFetch: typeof sdkFetch
+}
+
+const config: CommercejsConfig = {
+ commerceUrl: API_URL,
+ cartCookie: CART_COOKIE,
+ cartCookieMaxAge: 2592000,
+ customerCookie: CUSTOMER_COOKIE,
+ apiToken: '',
+ fetch: createGraphqlFetcher(() => getCommerceApi().getConfig()),
+ sdkFetch,
+}
+
+const operations = {
+ getAllPages,
+ getPage,
+ getSiteInfo,
+ getAllProductPaths,
+ getAllProducts,
+ getProduct,
+}
+
+export const provider = { config, operations }
+
+export type Provider = typeof provider
+export type CommercejsAPI = CommerceAPI
+
+export function getCommerceApi
(
+ customProvider: P = provider as any
+): CommercejsAPI
{
+ return commerceApi(customProvider as any)
+}
diff --git a/packages/commercejs/src/api/operations/get-all-pages.ts b/packages/commercejs/src/api/operations/get-all-pages.ts
new file mode 100644
index 000000000..c8c9e41b2
--- /dev/null
+++ b/packages/commercejs/src/api/operations/get-all-pages.ts
@@ -0,0 +1,21 @@
+import type { CommercejsConfig } from '..'
+import { GetAllPagesOperation } from '../../types/page'
+
+export type Page = { url: string }
+export type GetAllPagesResult = { pages: Page[] }
+
+export default function getAllPagesOperation() {
+ async function getAllPages({
+ config,
+ preview,
+ }: {
+ url?: string
+ config?: Partial
+ preview?: boolean
+ } = {}): Promise {
+ return Promise.resolve({
+ pages: [],
+ })
+ }
+ return getAllPages
+}
diff --git a/packages/commercejs/src/api/operations/get-all-product-paths.ts b/packages/commercejs/src/api/operations/get-all-product-paths.ts
new file mode 100644
index 000000000..03b7eee96
--- /dev/null
+++ b/packages/commercejs/src/api/operations/get-all-product-paths.ts
@@ -0,0 +1,35 @@
+import type { OperationContext } from '@vercel/commerce/api/operations'
+import type {
+ GetAllProductPathsOperation,
+ CommercejsProduct,
+} from '../../types/product'
+
+import type { CommercejsConfig, Provider } from '..'
+
+export type GetAllProductPathsResult = {
+ products: Array<{ path: string }>
+}
+
+export default function getAllProductPathsOperation({
+ commerce,
+}: OperationContext) {
+ async function getAllProductPaths({
+ config,
+ }: {
+ config?: Partial
+ } = {}): Promise {
+ const { sdkFetch } = commerce.getConfig(config)
+ const { data } = await sdkFetch('products', 'list')
+
+ // Match a path for every product retrieved
+ const productPaths = data.map(({ permalink }: CommercejsProduct) => ({
+ path: `/${permalink}`,
+ }))
+
+ return {
+ products: productPaths,
+ }
+ }
+
+ return getAllProductPaths
+}
diff --git a/packages/commercejs/src/api/operations/get-all-products.ts b/packages/commercejs/src/api/operations/get-all-products.ts
new file mode 100644
index 000000000..485ea22ee
--- /dev/null
+++ b/packages/commercejs/src/api/operations/get-all-products.ts
@@ -0,0 +1,29 @@
+import type { OperationContext } from '@vercel/commerce/api/operations'
+import type { GetAllProductsOperation } from '../../types/product'
+import type { CommercejsConfig, Provider } from '../index'
+
+import { normalizeProduct } from '../../utils/normalize-product'
+
+export default function getAllProductsOperation({
+ commerce,
+}: OperationContext) {
+ async function getAllProducts({
+ config,
+ }: {
+ config?: Partial
+ } = {}): Promise {
+ const { sdkFetch } = commerce.getConfig(config)
+ const { data } = await sdkFetch('products', 'list', {
+ sortBy: 'sort_order',
+ })
+
+ const productsFormatted =
+ data?.map((product: any) => normalizeProduct(product)) || []
+
+ return {
+ products: productsFormatted,
+ }
+ }
+
+ return getAllProducts
+}
diff --git a/packages/commercejs/src/api/operations/get-page.ts b/packages/commercejs/src/api/operations/get-page.ts
new file mode 100644
index 000000000..f4b69c90d
--- /dev/null
+++ b/packages/commercejs/src/api/operations/get-page.ts
@@ -0,0 +1,15 @@
+import { GetPageOperation } from '../../types/page'
+
+export type Page = any
+export type GetPageResult = { page?: Page }
+
+export type PageVariables = {
+ id: number
+}
+
+export default function getPageOperation() {
+ async function getPage(): Promise {
+ return Promise.resolve({})
+ }
+ return getPage
+}
diff --git a/packages/commercejs/src/api/operations/get-product.ts b/packages/commercejs/src/api/operations/get-product.ts
new file mode 100644
index 000000000..c8fa5901b
--- /dev/null
+++ b/packages/commercejs/src/api/operations/get-product.ts
@@ -0,0 +1,44 @@
+import type { OperationContext } from '@vercel/commerce/api/operations'
+import type { GetProductOperation } from '../../types/product'
+import type { CommercejsConfig, Provider } from '../index'
+import { normalizeProduct } from '../../utils/normalize-product'
+
+export default function getProductOperation({
+ commerce,
+}: OperationContext) {
+ async function getProduct({
+ config,
+ variables,
+ }: {
+ query?: string
+ variables?: T['variables']
+ config?: Partial
+ preview?: boolean
+ } = {}): Promise {
+ const { sdkFetch } = commerce.getConfig(config)
+
+ // Fetch a product by its permalink.
+ const product = await sdkFetch(
+ 'products',
+ 'retrieve',
+ variables?.slug || '',
+ {
+ type: 'permalink',
+ }
+ )
+
+ const { data: variants } = await sdkFetch(
+ 'products',
+ 'getVariants',
+ product.id
+ )
+
+ const productFormatted = normalizeProduct(product, variants)
+
+ return {
+ product: productFormatted,
+ }
+ }
+
+ return getProduct
+}
diff --git a/packages/commercejs/src/api/operations/get-site-info.ts b/packages/commercejs/src/api/operations/get-site-info.ts
new file mode 100644
index 000000000..0b4046d7c
--- /dev/null
+++ b/packages/commercejs/src/api/operations/get-site-info.ts
@@ -0,0 +1,36 @@
+import type { OperationContext } from '@vercel/commerce/api/operations'
+import type { Category, GetSiteInfoOperation } from '../../types/site'
+import { normalizeCategory } from '../../utils/normalize-category'
+import type { CommercejsConfig, Provider } from '../index'
+
+export type GetSiteInfoResult<
+ T extends { categories: any[]; brands: any[] } = {
+ categories: Category[]
+ brands: any[]
+ }
+> = T
+
+export default function getSiteInfoOperation({
+ commerce,
+}: OperationContext) {
+ async function getSiteInfo({
+ config,
+ }: {
+ query?: string
+ variables?: any
+ config?: Partial
+ preview?: boolean
+ } = {}): Promise {
+ const { sdkFetch } = commerce.getConfig(config)
+ const { data: categories } = await sdkFetch('categories', 'list')
+
+ const formattedCategories = categories.map(normalizeCategory)
+
+ return {
+ categories: formattedCategories,
+ brands: [],
+ }
+ }
+
+ return getSiteInfo
+}
diff --git a/packages/commercejs/src/api/operations/index.ts b/packages/commercejs/src/api/operations/index.ts
new file mode 100644
index 000000000..84b04a978
--- /dev/null
+++ b/packages/commercejs/src/api/operations/index.ts
@@ -0,0 +1,6 @@
+export { default as getAllPages } from './get-all-pages'
+export { default as getPage } from './get-page'
+export { default as getSiteInfo } from './get-site-info'
+export { default as getProduct } from './get-product'
+export { default as getAllProducts } from './get-all-products'
+export { default as getAllProductPaths } from './get-all-product-paths'
diff --git a/packages/commercejs/src/api/utils/graphql-fetch.ts b/packages/commercejs/src/api/utils/graphql-fetch.ts
new file mode 100644
index 000000000..64ebf4d6b
--- /dev/null
+++ b/packages/commercejs/src/api/utils/graphql-fetch.ts
@@ -0,0 +1,14 @@
+import type { GraphQLFetcher } from '@vercel/commerce/api'
+import type { CommercejsConfig } from '../'
+
+import { FetcherError } from '@vercel/commerce/utils/errors'
+
+const fetchGraphqlApi: (getConfig: () => CommercejsConfig) => GraphQLFetcher =
+ () => async () => {
+ throw new FetcherError({
+ errors: [{ message: 'GraphQL fetch is not implemented' }],
+ status: 500,
+ })
+ }
+
+export default fetchGraphqlApi
diff --git a/packages/commercejs/src/api/utils/sdk-fetch.ts b/packages/commercejs/src/api/utils/sdk-fetch.ts
new file mode 100644
index 000000000..d080c54b4
--- /dev/null
+++ b/packages/commercejs/src/api/utils/sdk-fetch.ts
@@ -0,0 +1,21 @@
+import { commerce } from '../../lib/commercejs'
+import Commerce from '@chec/commerce.js'
+
+type MethodKeys = {
+ [K in keyof T]: T[K] extends (...args: any) => infer R ? K : never
+}[keyof T]
+
+// Calls the relevant Commerce.js SDK method based on resource and method arguments.
+export default async function sdkFetch<
+ Resource extends keyof Commerce,
+ Method extends MethodKeys
+>(
+ resource: Resource,
+ method: Method,
+ ...variables: Parameters
+): Promise> {
+ //@ts-ignore
+ // Provider TODO: Fix types here.
+ const data = await commerce[resource][method](...variables)
+ return data
+}
diff --git a/framework/local/auth/index.ts b/packages/commercejs/src/auth/index.ts
similarity index 100%
rename from framework/local/auth/index.ts
rename to packages/commercejs/src/auth/index.ts
diff --git a/packages/commercejs/src/auth/use-login.tsx b/packages/commercejs/src/auth/use-login.tsx
new file mode 100644
index 000000000..4287d39d6
--- /dev/null
+++ b/packages/commercejs/src/auth/use-login.tsx
@@ -0,0 +1,34 @@
+import { useCallback } from 'react'
+import { MutationHook } from '@vercel/commerce/utils/types'
+import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login'
+import type { LoginHook } from '@vercel/commerce/types/login'
+import { getDeploymentUrl } from '../utils/get-deployment-url'
+
+export default useLogin as UseLogin
+
+const getLoginCallbackUrl = () => {
+ const baseUrl = getDeploymentUrl()
+ const API_ROUTE_PATH = 'api/login'
+ return `${baseUrl}/${API_ROUTE_PATH}`
+}
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: 'customer',
+ method: 'login',
+ },
+ async fetcher({ input, options: { query, method }, fetch }) {
+ await fetch({
+ query,
+ method,
+ variables: [input.email, getLoginCallbackUrl()],
+ })
+ return null
+ },
+ useHook: ({ fetch }) =>
+ function useHook() {
+ return useCallback(async function login(input) {
+ return fetch({ input })
+ }, [])
+ },
+}
diff --git a/packages/commercejs/src/auth/use-logout.tsx b/packages/commercejs/src/auth/use-logout.tsx
new file mode 100644
index 000000000..7c60f3442
--- /dev/null
+++ b/packages/commercejs/src/auth/use-logout.tsx
@@ -0,0 +1,27 @@
+import { useCallback } from 'react'
+import Cookies from 'js-cookie'
+import { MutationHook } from '@vercel/commerce/utils/types'
+import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout'
+import type { LogoutHook } from '@vercel/commerce/types/logout'
+import useCustomer from '../customer/use-customer'
+import { CUSTOMER_COOKIE } from '../constants'
+
+export default useLogout as UseLogout
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: '_',
+ method: '_',
+ },
+ useHook: () => () => {
+ const { mutate } = useCustomer()
+ return useCallback(
+ async function logout() {
+ Cookies.remove(CUSTOMER_COOKIE)
+ await mutate(null, false)
+ return null
+ },
+ [mutate]
+ )
+ },
+}
diff --git a/packages/commercejs/src/auth/use-signup.tsx b/packages/commercejs/src/auth/use-signup.tsx
new file mode 100644
index 000000000..1cc9538e7
--- /dev/null
+++ b/packages/commercejs/src/auth/use-signup.tsx
@@ -0,0 +1,17 @@
+import { MutationHook } from '@vercel/commerce/utils/types'
+import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup'
+
+export default useSignup as UseSignup
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: '',
+ },
+ async fetcher() {
+ return null
+ },
+ useHook:
+ ({ fetch }) =>
+ () =>
+ () => {},
+}
diff --git a/framework/local/cart/index.ts b/packages/commercejs/src/cart/index.ts
similarity index 100%
rename from framework/local/cart/index.ts
rename to packages/commercejs/src/cart/index.ts
diff --git a/packages/commercejs/src/cart/use-add-item.tsx b/packages/commercejs/src/cart/use-add-item.tsx
new file mode 100644
index 000000000..3bbad1147
--- /dev/null
+++ b/packages/commercejs/src/cart/use-add-item.tsx
@@ -0,0 +1,45 @@
+import type { AddItemHook } from '@vercel/commerce/types/cart'
+import type { MutationHook } from '@vercel/commerce/utils/types'
+import { useCallback } from 'react'
+import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item'
+import type { CommercejsCart } from '../types/cart'
+import { normalizeCart } from '../utils/normalize-cart'
+import useCart from './use-cart'
+
+export default useAddItem as UseAddItem
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: 'cart',
+ method: 'add',
+ },
+ async fetcher({ input: item, options, fetch }) {
+ // Frontend stringifies variantId even if undefined.
+ const hasVariant = !item.variantId || item.variantId !== 'undefined'
+
+ const variables = [item.productId, item?.quantity || 1]
+ if (hasVariant) {
+ variables.push(item.variantId)
+ }
+
+ const { cart } = await fetch<{ cart: CommercejsCart }>({
+ query: options.query,
+ method: options.method,
+ variables,
+ })
+ return normalizeCart(cart)
+ },
+ useHook: ({ fetch }) =>
+ function useHook() {
+ const { mutate } = useCart()
+
+ return useCallback(
+ async function addItem(input) {
+ const cart = await fetch({ input })
+ await mutate(cart, false)
+ return cart
+ },
+ [mutate]
+ )
+ },
+}
diff --git a/packages/commercejs/src/cart/use-cart.tsx b/packages/commercejs/src/cart/use-cart.tsx
new file mode 100644
index 000000000..57592ec37
--- /dev/null
+++ b/packages/commercejs/src/cart/use-cart.tsx
@@ -0,0 +1,41 @@
+import { useMemo } from 'react'
+import type { GetCartHook } from '@vercel/commerce/types/cart'
+import { SWRHook } from '@vercel/commerce/utils/types'
+import useCart, { UseCart } from '@vercel/commerce/cart/use-cart'
+import type { CommercejsCart } from '../types/cart'
+import { normalizeCart } from '../utils/normalize-cart'
+
+export default useCart as UseCart
+
+export const handler: SWRHook = {
+ fetchOptions: {
+ query: 'cart',
+ method: 'retrieve',
+ },
+ async fetcher({ options, fetch }) {
+ const cart = await fetch({
+ query: options.query,
+ method: options.method,
+ })
+ return normalizeCart(cart)
+ },
+ useHook: ({ useData }) =>
+ function useHook(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/packages/commercejs/src/cart/use-remove-item.tsx b/packages/commercejs/src/cart/use-remove-item.tsx
new file mode 100644
index 000000000..c9e57872d
--- /dev/null
+++ b/packages/commercejs/src/cart/use-remove-item.tsx
@@ -0,0 +1,36 @@
+import { useCallback } from 'react'
+import type { MutationHook } from '@vercel/commerce/utils/types'
+import type { RemoveItemHook } from '@vercel/commerce/types/cart'
+import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item'
+import type { CommercejsCart } from '../types/cart'
+import { normalizeCart } from '../utils/normalize-cart'
+import useCart from './use-cart'
+
+export default useRemoveItem as UseRemoveItem
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: 'cart',
+ method: 'remove',
+ },
+ async fetcher({ input, options, fetch }) {
+ const { cart } = await fetch<{ cart: CommercejsCart }>({
+ query: options.query,
+ method: options.method,
+ variables: input.itemId,
+ })
+ return normalizeCart(cart)
+ },
+ useHook: ({ fetch }) =>
+ function useHook() {
+ const { mutate } = useCart()
+ return useCallback(
+ async function removeItem(input) {
+ const cart = await fetch({ input: { itemId: input.id } })
+ await mutate(cart, false)
+ return cart
+ },
+ [mutate]
+ )
+ },
+}
diff --git a/packages/commercejs/src/cart/use-update-item.tsx b/packages/commercejs/src/cart/use-update-item.tsx
new file mode 100644
index 000000000..1546be036
--- /dev/null
+++ b/packages/commercejs/src/cart/use-update-item.tsx
@@ -0,0 +1,76 @@
+import type { UpdateItemHook, LineItem } from '@vercel/commerce/types/cart'
+import type {
+ HookFetcherContext,
+ MutationHookContext,
+} from '@vercel/commerce/utils/types'
+import { ValidationError } from '@vercel/commerce/utils/errors'
+import debounce from 'lodash.debounce'
+import { useCallback } from 'react'
+import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item'
+import type { CommercejsCart } from '../types/cart'
+import { normalizeCart } from '../utils/normalize-cart'
+import useCart from './use-cart'
+
+export default useUpdateItem as UseUpdateItem
+
+export type UpdateItemActionInput