mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 05:31:22 +00:00
Taking multiple steps into better API types
This commit is contained in:
parent
df07a55740
commit
149df82f45
@ -1,7 +1,7 @@
|
|||||||
|
import { normalizeCart } from '@framework/lib/normalize'
|
||||||
import { parseCartItem } from '../utils/parse-item'
|
import { parseCartItem } from '../utils/parse-item'
|
||||||
import getCartCookie from '../utils/get-cart-cookie'
|
import getCartCookie from '../utils/get-cart-cookie'
|
||||||
import type { CartHandlers } from '.'
|
import type { CartHandlers } from '.'
|
||||||
import { normalizeCart } from '@framework/lib/normalize'
|
|
||||||
|
|
||||||
const addItem: CartHandlers['addItem'] = async ({
|
const addItem: CartHandlers['addItem'] = async ({
|
||||||
res,
|
res,
|
||||||
|
35
framework/bigcommerce/api/cart/get-cart.ts
Normal file
35
framework/bigcommerce/api/cart/get-cart.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { normalizeCart } from '@framework/lib/normalize'
|
||||||
|
import { BigcommerceApiError } from '../utils/errors'
|
||||||
|
import getCartCookie from '../utils/get-cart-cookie'
|
||||||
|
import type { BigcommerceCart } from '../../types'
|
||||||
|
import type { CartHandlers } from '.'
|
||||||
|
|
||||||
|
// Return current cart info
|
||||||
|
const getCart: CartHandlers['getCart'] = async ({
|
||||||
|
res,
|
||||||
|
body: { cartId },
|
||||||
|
config,
|
||||||
|
}) => {
|
||||||
|
let result: { data?: BigcommerceCart } = {}
|
||||||
|
|
||||||
|
if (cartId) {
|
||||||
|
try {
|
||||||
|
result = await config.storeApiFetch(
|
||||||
|
`/v3/carts/${cartId}?include=line_items.physical_items.options`
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof BigcommerceApiError && error.status === 404) {
|
||||||
|
// Remove the cookie if it exists but the cart wasn't found
|
||||||
|
res.setHeader('Set-Cookie', getCartCookie(config.cartCookie))
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
data: result.data ? normalizeCart(result.data) : null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCart
|
@ -1,86 +1,34 @@
|
|||||||
import { CartHandlers as CartHandlersCore } from '@commerce/api'
|
import { EndpointSchema } from '@commerce/api'
|
||||||
import isAllowedMethod from '../utils/is-allowed-method'
|
import getCart from './get-cart'
|
||||||
import createApiHandler, {
|
import addItem from './add-item'
|
||||||
BigcommerceApiHandler,
|
|
||||||
} from '../utils/create-api-handler'
|
|
||||||
import { BigcommerceApiError } from '../utils/errors'
|
|
||||||
import getCart from './handlers/get-cart'
|
|
||||||
import addItem from './handlers/add-item'
|
|
||||||
import updateItem from './handlers/update-item'
|
import updateItem from './handlers/update-item'
|
||||||
import removeItem from './handlers/remove-item'
|
import removeItem from './handlers/remove-item'
|
||||||
import type {
|
import type {
|
||||||
BigcommerceCart,
|
|
||||||
GetCartHandlerBody,
|
GetCartHandlerBody,
|
||||||
AddCartItemHandlerBody,
|
AddCartItemHandlerBody,
|
||||||
UpdateCartItemHandlerBody,
|
UpdateCartItemHandlerBody,
|
||||||
RemoveCartItemHandlerBody,
|
RemoveCartItemHandlerBody,
|
||||||
Cart,
|
Cart,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { APIHandler } from '@commerce/api/utils/types'
|
import type { CommerceAPIEndpoints } from '..'
|
||||||
import { CommerceAPI } from '..'
|
|
||||||
|
|
||||||
export type CartHandlers<
|
export type CartEndpointSchema = EndpointSchema<
|
||||||
C extends CommerceAPI = CommerceAPI
|
'cart',
|
||||||
> = CartHandlersCore<
|
|
||||||
C,
|
|
||||||
{
|
{
|
||||||
getCart: APIHandler<C, CartHandlers<C>, Cart | null, GetCartHandlerBody>
|
options: {}
|
||||||
addItem: APIHandler<C, CartHandlers<C>, Cart, AddCartItemHandlerBody>
|
operations: {
|
||||||
updateItem: APIHandler<C, CartHandlers<C>, Cart, UpdateCartItemHandlerBody>
|
getCart: {
|
||||||
removeItem: APIHandler<C, CartHandlers<C>, Cart, RemoveCartItemHandlerBody>
|
data: Cart | null
|
||||||
|
body: GetCartHandlerBody
|
||||||
|
options: { yay: string }
|
||||||
|
}
|
||||||
|
addItem: { data: Cart; body: AddCartItemHandlerBody; options: {} }
|
||||||
|
updateItem: { data: Cart; body: UpdateCartItemHandlerBody; options: {} }
|
||||||
|
removeItem: { data: Cart; body: RemoveCartItemHandlerBody; options: {} }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
|
export type CartAPI = CommerceAPIEndpoints['cart']
|
||||||
|
|
||||||
// TODO: a complete implementation should have schema validation for `req.body`
|
export const operations = { getCart, addItem }
|
||||||
const cartApi: BigcommerceApiHandler<BigcommerceCart, CartHandlers> = async (
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
config,
|
|
||||||
handlers
|
|
||||||
) => {
|
|
||||||
if (!isAllowedMethod(req, res, METHODS)) return
|
|
||||||
|
|
||||||
const { cookies } = req
|
|
||||||
const cartId = cookies[config.cartCookie]
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Return current cart info
|
|
||||||
if (req.method === 'GET') {
|
|
||||||
const body = { cartId }
|
|
||||||
return await handlers['getCart']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create or add an item to the cart
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
const body = { ...req.body, cartId }
|
|
||||||
return await handlers['addItem']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update item in cart
|
|
||||||
if (req.method === 'PUT') {
|
|
||||||
const body = { ...req.body, cartId }
|
|
||||||
return await handlers['updateItem']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove an item from the cart
|
|
||||||
if (req.method === 'DELETE') {
|
|
||||||
const body = { ...req.body, cartId }
|
|
||||||
return await handlers['removeItem']({ req, res, config, body })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
|
|
||||||
const message =
|
|
||||||
error instanceof BigcommerceApiError
|
|
||||||
? 'An unexpected error ocurred with the Bigcommerce API'
|
|
||||||
: 'An unexpected error ocurred'
|
|
||||||
|
|
||||||
res.status(500).json({ data: null, errors: [{ message }] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handlers = { getCart, addItem, updateItem, removeItem }
|
|
||||||
|
|
||||||
export default createApiHandler(cartApi, handlers, {})
|
|
||||||
|
@ -2,9 +2,11 @@ import type { RequestInit } from '@vercel/fetch'
|
|||||||
import {
|
import {
|
||||||
CommerceAPI as CoreCommerceAPI,
|
CommerceAPI as CoreCommerceAPI,
|
||||||
CommerceAPIConfig,
|
CommerceAPIConfig,
|
||||||
|
GetEndpointsSchema,
|
||||||
} from '@commerce/api'
|
} from '@commerce/api'
|
||||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||||
import fetchStoreApi from './utils/fetch-store-api'
|
import fetchStoreApi from './utils/fetch-store-api'
|
||||||
|
import { CartEndpointSchema } from './cart'
|
||||||
|
|
||||||
export interface BigcommerceConfig extends CommerceAPIConfig {
|
export interface BigcommerceConfig extends CommerceAPIConfig {
|
||||||
// Indicates if the returned metadata with translations should be applied to the
|
// Indicates if the returned metadata with translations should be applied to the
|
||||||
@ -104,14 +106,19 @@ export const provider = {
|
|||||||
|
|
||||||
export type Provider = typeof provider
|
export type Provider = typeof provider
|
||||||
|
|
||||||
|
export type EndpointsSchema = { cart: CartEndpointSchema }
|
||||||
|
|
||||||
export class CommerceAPI<
|
export class CommerceAPI<
|
||||||
P extends Provider = Provider
|
P extends Provider = Provider,
|
||||||
> extends CoreCommerceAPI<P> {
|
E extends EndpointsSchema = EndpointsSchema
|
||||||
|
> extends CoreCommerceAPI<P, E> {
|
||||||
constructor(readonly provider: P = provider) {
|
constructor(readonly provider: P = provider) {
|
||||||
super(provider)
|
super(provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CommerceAPIEndpoints = GetEndpointsSchema<CommerceAPI>
|
||||||
|
|
||||||
export function getConfig(userConfig?: Partial<BigcommerceConfig>) {
|
export function getConfig(userConfig?: Partial<BigcommerceConfig>) {
|
||||||
return config.getConfig(userConfig)
|
return config.getConfig(userConfig)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ export type BigcommerceCart = {
|
|||||||
|
|
||||||
export type Cart = Core.Cart & {
|
export type Cart = Core.Cart & {
|
||||||
lineItems: LineItem[]
|
lineItems: LineItem[]
|
||||||
|
core: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LineItem = Core.LineItem
|
export type LineItem = Core.LineItem
|
||||||
|
@ -5,8 +5,14 @@ import type { Cart } from '../types'
|
|||||||
|
|
||||||
export type CartEndpoint = APIEndpoint<any, any, CartHandlers<CommerceAPI>, any>
|
export type CartEndpoint = APIEndpoint<any, any, CartHandlers<CommerceAPI>, any>
|
||||||
|
|
||||||
export type CartHandlersBase<C> = {
|
export type CartHandlersBase<C extends CommerceAPI> = {
|
||||||
getCart: APIHandler<any, CartHandlersBase<C>, Cart | null, any>
|
getCart: APIHandler<
|
||||||
|
any,
|
||||||
|
CartHandlersBase<C>,
|
||||||
|
Cart | null,
|
||||||
|
any,
|
||||||
|
{ yay: string }
|
||||||
|
>
|
||||||
addItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
addItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
||||||
updateItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
updateItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
||||||
removeItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
removeItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
||||||
@ -17,13 +23,103 @@ export type CartHandlers<
|
|||||||
T extends CartHandlersBase<C> = CartHandlersBase<C>
|
T extends CartHandlersBase<C> = CartHandlersBase<C>
|
||||||
> = T
|
> = T
|
||||||
|
|
||||||
export type Endpoints = CartEndpoint
|
export type CartHandlersType = {
|
||||||
|
getCart: { data: Cart | null; body: any; options: {} }
|
||||||
|
addItem: { data: Cart; body: any; options: {} }
|
||||||
|
updateItem: { data: Cart; body: any; options: {} }
|
||||||
|
removeItem: { data: Cart; body: any; options: {} }
|
||||||
|
}
|
||||||
|
|
||||||
export type EndpointHandlers<E> = E extends APIEndpoint<any, any, infer T>
|
export type CartHandlers2<
|
||||||
|
C extends CommerceAPI,
|
||||||
|
T extends CartHandlersType = CartHandlersType
|
||||||
|
> = {
|
||||||
|
getCart: APIHandler<
|
||||||
|
any,
|
||||||
|
CartHandlersBase<C>,
|
||||||
|
Cart | null,
|
||||||
|
any,
|
||||||
|
{ yay: string }
|
||||||
|
>
|
||||||
|
addItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
||||||
|
updateItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
||||||
|
removeItem: APIHandler<any, CartHandlersBase<C>, Cart, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EndpointsSchema = {
|
||||||
|
cart?: {
|
||||||
|
options: {}
|
||||||
|
operations: {
|
||||||
|
getCart: { data?: Cart | null; body?: any }
|
||||||
|
addItem: { data?: Cart; body?: any }
|
||||||
|
updateItem: { data?: Cart; body?: any }
|
||||||
|
removeItem: { data?: Cart; body?: any }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetEndpointsSchema<
|
||||||
|
C extends CommerceAPI,
|
||||||
|
Schema extends EndpointsSchema = C extends CommerceAPI<any, infer E>
|
||||||
|
? E
|
||||||
|
: never
|
||||||
|
> = {
|
||||||
|
[E in keyof EndpointsSchema]-?: Schema[E] & {
|
||||||
|
endpoint: Endpoint<C, NonNullable<Schema[E]>>
|
||||||
|
handlers: EndpointHandlers<C, NonNullable<Schema[E]>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type X = Endpoint<CommerceAPI, NonNullable<EndpointsSchema['cart']>>
|
||||||
|
|
||||||
|
export type EndpointSchemaBase = {
|
||||||
|
options: {}
|
||||||
|
operations: {
|
||||||
|
[k: string]: { data?: any; body?: any }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OperationData<T> = T extends { data?: infer D; body?: any }
|
||||||
|
? D
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type EndpointSchema<
|
||||||
|
E extends keyof EndpointsSchema,
|
||||||
|
Handlers extends EndpointsSchema[E]
|
||||||
|
> = Handlers
|
||||||
|
|
||||||
|
export type Endpoint<
|
||||||
|
C extends CommerceAPI,
|
||||||
|
E extends EndpointSchemaBase
|
||||||
|
> = APIEndpoint<
|
||||||
|
C,
|
||||||
|
EndpointHandlers<C, E>,
|
||||||
|
OperationData<E['operations'][keyof E['operations']]>,
|
||||||
|
E['options']
|
||||||
|
>
|
||||||
|
|
||||||
|
export type EndpointHandlers<
|
||||||
|
C extends CommerceAPI,
|
||||||
|
E extends EndpointSchemaBase
|
||||||
|
> = {
|
||||||
|
[H in keyof E['operations']]: APIHandler<
|
||||||
|
C,
|
||||||
|
EndpointHandlers<C, E>,
|
||||||
|
E['operations'][H]['data'],
|
||||||
|
E['operations'][H]['body'],
|
||||||
|
E['options']
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommerceEndpointsSchema<C> = C extends CommerceAPI<any, infer E>
|
||||||
|
? E
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type HandlerOperations<E> = E extends APIEndpoint<any, any, infer T>
|
||||||
? T
|
? T
|
||||||
: never
|
: never
|
||||||
|
|
||||||
export type EndpointOptions<E> = E extends APIEndpoint<any, any, any, infer T>
|
export type HandlerOptions<E> = E extends APIEndpoint<any, any, any, infer T>
|
||||||
? T
|
? T
|
||||||
: never
|
: never
|
||||||
|
|
||||||
@ -33,7 +129,7 @@ export type APIProvider = {
|
|||||||
|
|
||||||
export class CommerceAPI<
|
export class CommerceAPI<
|
||||||
P extends APIProvider = APIProvider,
|
P extends APIProvider = APIProvider,
|
||||||
E extends Endpoints = Endpoints
|
E extends EndpointsSchema = EndpointsSchema
|
||||||
> {
|
> {
|
||||||
constructor(readonly provider: P) {
|
constructor(readonly provider: P) {
|
||||||
this.provider = provider
|
this.provider = provider
|
||||||
@ -53,8 +149,8 @@ export class CommerceAPI<
|
|||||||
endpoint(context: {
|
endpoint(context: {
|
||||||
handler: E
|
handler: E
|
||||||
config?: P['config']
|
config?: P['config']
|
||||||
operations: EndpointHandlers<typeof context.handler>
|
operations: HandlerOperations<typeof context.handler>
|
||||||
options?: EndpointOptions<typeof context.handler>
|
options?: HandlerOptions<typeof context.handler>
|
||||||
}): NextApiHandler {
|
}): NextApiHandler {
|
||||||
const commerce = this
|
const commerce = this
|
||||||
const cfg = this.getConfig(context.config)
|
const cfg = this.getConfig(context.config)
|
||||||
|
@ -37,8 +37,8 @@ export type APIHandler<
|
|||||||
context: APIHandlerContext<C, H, Data, Options> & { body: Body }
|
context: APIHandlerContext<C, H, Data, Options> & { body: Body }
|
||||||
) => void | Promise<void>
|
) => void | Promise<void>
|
||||||
|
|
||||||
export type APIHandlers<C extends CommerceAPI, Data = any> = {
|
export type APIHandlers<C extends CommerceAPI> = {
|
||||||
[k: string]: APIHandler<C, any, Data, any, any>
|
[k: string]: APIHandler<C, any, any, any, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type APIEndpoint<
|
export type APIEndpoint<
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import cartApi from '@framework/api/cart'
|
|
||||||
import cart from '@commerce/api/endpoints/cart'
|
import cart from '@commerce/api/endpoints/cart'
|
||||||
|
import { operations } from '@framework/api/cart'
|
||||||
import commerce from '@lib/api/commerce'
|
import commerce from '@lib/api/commerce'
|
||||||
|
|
||||||
const x = commerce.endpoint({ handler: cart, operations: {} as any })
|
export default commerce.endpoint({ handler: cart, operations })
|
||||||
|
|
||||||
export default cartApi()
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user