mirror of
https://github.com/vercel/commerce.git
synced 2025-07-26 03:31:23 +00:00
[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>
This commit is contained in:
62
framework/commerce/api/endpoints/cart.ts
Normal file
62
framework/commerce/api/endpoints/cart.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { CartSchema } from '../../types/cart'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const cartEndpoint: GetAPISchema<
|
||||
any,
|
||||
CartSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getCart'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
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']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create or add an item to the cart
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Update item in cart
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the cart
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default cartEndpoint
|
31
framework/commerce/api/endpoints/catalog/products.ts
Normal file
31
framework/commerce/api/endpoints/catalog/products.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { ProductsSchema } from '../../../types/product'
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
const productsEndpoint: GetAPISchema<
|
||||
any,
|
||||
ProductsSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (!isAllowedOperation(req, res, { GET: handlers['getProducts'] })) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = req.query
|
||||
return await handlers['getProducts']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default productsEndpoint
|
35
framework/commerce/api/endpoints/checkout.ts
Normal file
35
framework/commerce/api/endpoints/checkout.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { CheckoutSchema } from '../../types/checkout'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const checkoutEndpoint: GetAPISchema<
|
||||
any,
|
||||
CheckoutSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['checkout'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = null
|
||||
return await handlers['checkout']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default checkoutEndpoint
|
35
framework/commerce/api/endpoints/customer.ts
Normal file
35
framework/commerce/api/endpoints/customer.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { CustomerSchema } from '../../types/customer'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const customerEndpoint: GetAPISchema<
|
||||
any,
|
||||
CustomerSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getLoggedInCustomer'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = null
|
||||
return await handlers['getLoggedInCustomer']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default customerEndpoint
|
35
framework/commerce/api/endpoints/login.ts
Normal file
35
framework/commerce/api/endpoints/login.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { LoginSchema } from '../../types/login'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const loginEndpoint: GetAPISchema<
|
||||
any,
|
||||
LoginSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
POST: handlers['login'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = req.body ?? {}
|
||||
return await handlers['login']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default loginEndpoint
|
37
framework/commerce/api/endpoints/logout.ts
Normal file
37
framework/commerce/api/endpoints/logout.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { LogoutSchema } from '../../types/logout'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const logoutEndpoint: GetAPISchema<
|
||||
any,
|
||||
LogoutSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['logout'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const redirectTo = req.query.redirect_to
|
||||
const body = typeof redirectTo === 'string' ? { redirectTo } : {}
|
||||
|
||||
return await handlers['logout']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default logoutEndpoint
|
38
framework/commerce/api/endpoints/signup.ts
Normal file
38
framework/commerce/api/endpoints/signup.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { SignupSchema } from '../../types/signup'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const signupEndpoint: GetAPISchema<
|
||||
any,
|
||||
SignupSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
POST: handlers['signup'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['signup']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default signupEndpoint
|
58
framework/commerce/api/endpoints/wishlist.ts
Normal file
58
framework/commerce/api/endpoints/wishlist.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { WishlistSchema } from '../../types/wishlist'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const wishlistEndpoint: GetAPISchema<
|
||||
any,
|
||||
WishlistSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getWishlist'],
|
||||
POST: handlers['addItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const { cookies } = req
|
||||
const customerToken = cookies[config.customerCookie]
|
||||
|
||||
try {
|
||||
// Return current wishlist info
|
||||
if (req.method === 'GET') {
|
||||
const body = {
|
||||
customerToken,
|
||||
includeProducts: req.query.products === '1',
|
||||
}
|
||||
return await handlers['getWishlist']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Add an item to the wishlist
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the wishlist
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default wishlistEndpoint
|
@@ -1,7 +1,154 @@
|
||||
import type { NextApiHandler } from 'next'
|
||||
import type { RequestInit, Response } from '@vercel/fetch'
|
||||
import type { APIEndpoint, APIHandler } from './utils/types'
|
||||
import type { CartSchema } from '../types/cart'
|
||||
import type { CustomerSchema } from '../types/customer'
|
||||
import type { LoginSchema } from '../types/login'
|
||||
import type { LogoutSchema } from '../types/logout'
|
||||
import type { SignupSchema } from '../types/signup'
|
||||
import type { ProductsSchema } from '../types/product'
|
||||
import type { WishlistSchema } from '../types/wishlist'
|
||||
import type { CheckoutSchema } from '../types/checkout'
|
||||
import {
|
||||
defaultOperations,
|
||||
OPERATIONS,
|
||||
AllOperations,
|
||||
APIOperations,
|
||||
} from './operations'
|
||||
|
||||
export type APISchemas =
|
||||
| CartSchema
|
||||
| CustomerSchema
|
||||
| LoginSchema
|
||||
| LogoutSchema
|
||||
| SignupSchema
|
||||
| ProductsSchema
|
||||
| WishlistSchema
|
||||
| CheckoutSchema
|
||||
|
||||
export type GetAPISchema<
|
||||
C extends CommerceAPI<any>,
|
||||
S extends APISchemas = APISchemas
|
||||
> = {
|
||||
schema: S
|
||||
endpoint: EndpointContext<C, S['endpoint']>
|
||||
}
|
||||
|
||||
export type EndpointContext<
|
||||
C extends CommerceAPI,
|
||||
E extends EndpointSchemaBase
|
||||
> = {
|
||||
handler: Endpoint<C, E>
|
||||
handlers: EndpointHandlers<C, E>
|
||||
}
|
||||
|
||||
export type EndpointSchemaBase = {
|
||||
options: {}
|
||||
handlers: {
|
||||
[k: string]: { data?: any; body?: any }
|
||||
}
|
||||
}
|
||||
|
||||
export type Endpoint<
|
||||
C extends CommerceAPI,
|
||||
E extends EndpointSchemaBase
|
||||
> = APIEndpoint<C, EndpointHandlers<C, E>, any, E['options']>
|
||||
|
||||
export type EndpointHandlers<
|
||||
C extends CommerceAPI,
|
||||
E extends EndpointSchemaBase
|
||||
> = {
|
||||
[H in keyof E['handlers']]: APIHandler<
|
||||
C,
|
||||
EndpointHandlers<C, E>,
|
||||
E['handlers'][H]['data'],
|
||||
E['handlers'][H]['body'],
|
||||
E['options']
|
||||
>
|
||||
}
|
||||
|
||||
export type APIProvider = {
|
||||
config: CommerceAPIConfig
|
||||
operations: APIOperations<any>
|
||||
}
|
||||
|
||||
export type CommerceAPI<
|
||||
P extends APIProvider = APIProvider
|
||||
> = CommerceAPICore<P> & AllOperations<P>
|
||||
|
||||
export class CommerceAPICore<P extends APIProvider = APIProvider> {
|
||||
constructor(readonly provider: P) {}
|
||||
|
||||
getConfig(userConfig: Partial<P['config']> = {}): P['config'] {
|
||||
return Object.entries(userConfig).reduce(
|
||||
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
||||
{ ...this.provider.config }
|
||||
)
|
||||
}
|
||||
|
||||
setConfig(newConfig: Partial<P['config']>) {
|
||||
Object.assign(this.provider.config, newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
export function getCommerceApi<P extends APIProvider>(
|
||||
customProvider: P
|
||||
): CommerceAPI<P> {
|
||||
const commerce = Object.assign(
|
||||
new CommerceAPICore(customProvider),
|
||||
defaultOperations as AllOperations<P>
|
||||
)
|
||||
const ops = customProvider.operations
|
||||
|
||||
OPERATIONS.forEach((k) => {
|
||||
const op = ops[k]
|
||||
if (op) {
|
||||
commerce[k] = op({ commerce }) as AllOperations<P>[typeof k]
|
||||
}
|
||||
})
|
||||
|
||||
return commerce
|
||||
}
|
||||
|
||||
export function getEndpoint<
|
||||
P extends APIProvider,
|
||||
T extends GetAPISchema<any, any>
|
||||
>(
|
||||
commerce: CommerceAPI<P>,
|
||||
context: T['endpoint'] & {
|
||||
config?: P['config']
|
||||
options?: T['schema']['endpoint']['options']
|
||||
}
|
||||
): NextApiHandler {
|
||||
const cfg = commerce.getConfig(context.config)
|
||||
|
||||
return function apiHandler(req, res) {
|
||||
return context.handler({
|
||||
req,
|
||||
res,
|
||||
commerce,
|
||||
config: cfg,
|
||||
handlers: context.handlers,
|
||||
options: context.options ?? {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const createEndpoint = <API extends GetAPISchema<any, any>>(
|
||||
endpoint: API['endpoint']
|
||||
) => <P extends APIProvider>(
|
||||
commerce: CommerceAPI<P>,
|
||||
context?: Partial<API['endpoint']> & {
|
||||
config?: P['config']
|
||||
options?: API['schema']['endpoint']['options']
|
||||
}
|
||||
): NextApiHandler => {
|
||||
return getEndpoint(commerce, { ...endpoint, ...context })
|
||||
}
|
||||
|
||||
export interface CommerceAPIConfig {
|
||||
locale?: string
|
||||
locales?: string[]
|
||||
commerceUrl: string
|
||||
apiToken: string
|
||||
cartCookie: string
|
||||
|
177
framework/commerce/api/operations.ts
Normal file
177
framework/commerce/api/operations.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type { LoginOperation } from '../types/login'
|
||||
import type { GetAllPagesOperation, GetPageOperation } from '../types/page'
|
||||
import type { GetSiteInfoOperation } from '../types/site'
|
||||
import type { GetCustomerWishlistOperation } from '../types/wishlist'
|
||||
import type {
|
||||
GetAllProductPathsOperation,
|
||||
GetAllProductsOperation,
|
||||
GetProductOperation,
|
||||
} from '../types/product'
|
||||
import type { APIProvider, CommerceAPI } from '.'
|
||||
|
||||
const noop = () => {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
export const OPERATIONS = [
|
||||
'login',
|
||||
'getAllPages',
|
||||
'getPage',
|
||||
'getSiteInfo',
|
||||
'getCustomerWishlist',
|
||||
'getAllProductPaths',
|
||||
'getAllProducts',
|
||||
'getProduct',
|
||||
] as const
|
||||
|
||||
export const defaultOperations = OPERATIONS.reduce((ops, k) => {
|
||||
ops[k] = noop
|
||||
return ops
|
||||
}, {} as { [K in AllowedOperations]: typeof noop })
|
||||
|
||||
export type AllowedOperations = typeof OPERATIONS[number]
|
||||
|
||||
export type Operations<P extends APIProvider> = {
|
||||
login: {
|
||||
<T extends LoginOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
res: ServerResponse
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends LoginOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
res: ServerResponse
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getAllPages: {
|
||||
<T extends GetAllPagesOperation>(opts?: {
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetAllPagesOperation>(
|
||||
opts: {
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getPage: {
|
||||
<T extends GetPageOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetPageOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getSiteInfo: {
|
||||
<T extends GetSiteInfoOperation>(opts: {
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetSiteInfoOperation>(
|
||||
opts: {
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getCustomerWishlist: {
|
||||
<T extends GetCustomerWishlistOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
includeProducts?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetCustomerWishlistOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
includeProducts?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getAllProductPaths: {
|
||||
<T extends GetAllProductPathsOperation>(opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetAllProductPathsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getAllProducts: {
|
||||
<T extends GetAllProductsOperation>(opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetAllProductsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
|
||||
getProduct: {
|
||||
<T extends GetProductOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
<T extends GetProductOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: P['config']
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
}
|
||||
}
|
||||
|
||||
export type APIOperations<P extends APIProvider> = {
|
||||
[K in keyof Operations<P>]?: (ctx: OperationContext<P>) => Operations<P>[K]
|
||||
}
|
||||
|
||||
export type AllOperations<P extends APIProvider> = {
|
||||
[K in keyof APIOperations<P>]-?: P['operations'][K] extends (
|
||||
...args: any
|
||||
) => any
|
||||
? ReturnType<P['operations'][K]>
|
||||
: typeof noop
|
||||
}
|
||||
|
||||
export type OperationContext<P extends APIProvider> = {
|
||||
commerce: CommerceAPI<P>
|
||||
}
|
||||
|
||||
export type OperationOptions =
|
||||
| { query: string; url?: never }
|
||||
| { query?: never; url: string }
|
22
framework/commerce/api/utils/errors.ts
Normal file
22
framework/commerce/api/utils/errors.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Response } from '@vercel/fetch'
|
||||
|
||||
export class CommerceAPIError extends Error {
|
||||
status: number
|
||||
res: Response
|
||||
data: any
|
||||
|
||||
constructor(msg: string, res: Response, data?: any) {
|
||||
super(msg)
|
||||
this.name = 'CommerceApiError'
|
||||
this.status = res.status
|
||||
this.res = res
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
|
||||
export class CommerceNetworkError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg)
|
||||
this.name = 'CommerceNetworkError'
|
||||
}
|
||||
}
|
30
framework/commerce/api/utils/is-allowed-method.ts
Normal file
30
framework/commerce/api/utils/is-allowed-method.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
export type HTTP_METHODS = 'OPTIONS' | 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
|
||||
export default function isAllowedMethod(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
allowedMethods: HTTP_METHODS[]
|
||||
) {
|
||||
const methods = allowedMethods.includes('OPTIONS')
|
||||
? allowedMethods
|
||||
: [...allowedMethods, 'OPTIONS']
|
||||
|
||||
if (!req.method || !methods.includes(req.method)) {
|
||||
res.status(405)
|
||||
res.setHeader('Allow', methods.join(', '))
|
||||
res.end()
|
||||
return false
|
||||
}
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(200)
|
||||
res.setHeader('Allow', methods.join(', '))
|
||||
res.setHeader('Content-Length', '0')
|
||||
res.end()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
19
framework/commerce/api/utils/is-allowed-operation.ts
Normal file
19
framework/commerce/api/utils/is-allowed-operation.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import isAllowedMethod, { HTTP_METHODS } from './is-allowed-method'
|
||||
import { APIHandler } from './types'
|
||||
|
||||
export default function isAllowedOperation(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
allowedOperations: { [k in HTTP_METHODS]?: APIHandler<any, any> }
|
||||
) {
|
||||
const methods = Object.keys(allowedOperations) as HTTP_METHODS[]
|
||||
const allowedMethods = methods.reduce<HTTP_METHODS[]>((arr, method) => {
|
||||
if (allowedOperations[method]) {
|
||||
arr.push(method)
|
||||
}
|
||||
return arr
|
||||
}, [])
|
||||
|
||||
return isAllowedMethod(req, res, allowedMethods)
|
||||
}
|
49
framework/commerce/api/utils/types.ts
Normal file
49
framework/commerce/api/utils/types.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { CommerceAPI } from '..'
|
||||
|
||||
export type ErrorData = { message: string; code?: string }
|
||||
|
||||
export type APIResponse<Data = any> =
|
||||
| { data: Data; errors?: ErrorData[] }
|
||||
// If `data` doesn't include `null`, then `null` is only allowed on errors
|
||||
| (Data extends null
|
||||
? { data: null; errors?: ErrorData[] }
|
||||
: { data: null; errors: ErrorData[] })
|
||||
|
||||
export type APIHandlerContext<
|
||||
C extends CommerceAPI,
|
||||
H extends APIHandlers<C> = {},
|
||||
Data = any,
|
||||
Options extends {} = {}
|
||||
> = {
|
||||
req: NextApiRequest
|
||||
res: NextApiResponse<APIResponse<Data>>
|
||||
commerce: C
|
||||
config: C['provider']['config']
|
||||
handlers: H
|
||||
/**
|
||||
* Custom configs that may be used by a particular handler
|
||||
*/
|
||||
options: Options
|
||||
}
|
||||
|
||||
export type APIHandler<
|
||||
C extends CommerceAPI,
|
||||
H extends APIHandlers<C> = {},
|
||||
Data = any,
|
||||
Body = any,
|
||||
Options extends {} = {}
|
||||
> = (
|
||||
context: APIHandlerContext<C, H, Data, Options> & { body: Body }
|
||||
) => void | Promise<void>
|
||||
|
||||
export type APIHandlers<C extends CommerceAPI> = {
|
||||
[k: string]: APIHandler<C, any, any, any, any>
|
||||
}
|
||||
|
||||
export type APIEndpoint<
|
||||
C extends CommerceAPI = CommerceAPI,
|
||||
H extends APIHandlers<C> = {},
|
||||
Data = any,
|
||||
Options extends {} = {}
|
||||
> = (context: APIHandlerContext<C, H, Data, Options>) => void | Promise<void>
|
@@ -1,13 +1,14 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { MutationHook, HookFetcherFn } from '../utils/types'
|
||||
import type { LoginHook } from '../types/login'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseLogin<
|
||||
H extends MutationHook<any, any, any> = MutationHook<null, {}, {}>
|
||||
H extends MutationHook<LoginHook<any>> = MutationHook<LoginHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<null, {}> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<LoginHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.auth?.useLogin!
|
||||
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { LogoutHook } from '../types/logout'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseLogout<
|
||||
H extends MutationHook<any, any, any> = MutationHook<null>
|
||||
H extends MutationHook<LogoutHook<any>> = MutationHook<LogoutHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<null> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<LogoutHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.auth?.useLogout!
|
||||
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { SignupHook } from '../types/signup'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseSignup<
|
||||
H extends MutationHook<any, any, any> = MutationHook<null>
|
||||
H extends MutationHook<SignupHook<any>> = MutationHook<SignupHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<null> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<SignupHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.auth?.useSignup!
|
||||
|
||||
|
@@ -1,17 +1,14 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { Cart, CartItemBody, AddCartItemBody } from '../types'
|
||||
import type { AddItemHook } from '../types/cart'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseAddItem<
|
||||
H extends MutationHook<any, any, any> = MutationHook<Cart, {}, CartItemBody>
|
||||
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<
|
||||
Cart,
|
||||
AddCartItemBody<CartItemBody>
|
||||
> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.cart?.useAddItem!
|
||||
|
||||
|
@@ -1,28 +1,19 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { useHook, useSWRHook } from '../utils/use-hook'
|
||||
import type { HookFetcherFn, SWRHook } from '../utils/types'
|
||||
import type { Cart } from '../types'
|
||||
import type { SWRHook, HookFetcherFn } from '../utils/types'
|
||||
import type { GetCartHook } from '../types/cart'
|
||||
import { Provider, useCommerce } from '..'
|
||||
|
||||
export type FetchCartInput = {
|
||||
cartId?: Cart['id']
|
||||
}
|
||||
|
||||
export type UseCart<
|
||||
H extends SWRHook<any, any, any> = SWRHook<
|
||||
Cart | null,
|
||||
{},
|
||||
FetchCartInput,
|
||||
{ isEmpty?: boolean }
|
||||
>
|
||||
H extends SWRHook<GetCartHook<any>> = SWRHook<GetCartHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
|
||||
export const fetcher: HookFetcherFn<GetCartHook> = async ({
|
||||
options,
|
||||
input: { cartId },
|
||||
fetch,
|
||||
}) => {
|
||||
return cartId ? await fetch({ ...options }) : null
|
||||
return cartId ? await fetch(options) : null
|
||||
}
|
||||
|
||||
const fn = (provider: Provider) => provider.cart?.useCart!
|
||||
|
@@ -1,29 +1,14 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { Cart, LineItem, RemoveCartItemBody } from '../types'
|
||||
import type { RemoveItemHook } from '../types/cart'
|
||||
import type { Provider } from '..'
|
||||
|
||||
/**
|
||||
* Input expected by the action returned by the `useRemoveItem` hook
|
||||
*/
|
||||
export type RemoveItemInput = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type UseRemoveItem<
|
||||
H extends MutationHook<any, any, any> = MutationHook<
|
||||
Cart | null,
|
||||
{ item?: LineItem },
|
||||
RemoveItemInput,
|
||||
RemoveCartItemBody
|
||||
>
|
||||
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<
|
||||
Cart | null,
|
||||
RemoveCartItemBody
|
||||
> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.cart?.useRemoveItem!
|
||||
|
||||
|
@@ -1,32 +1,14 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { Cart, CartItemBody, LineItem, UpdateCartItemBody } from '../types'
|
||||
import type { UpdateItemHook } from '../types/cart'
|
||||
import type { Provider } from '..'
|
||||
|
||||
/**
|
||||
* Input expected by the action returned by the `useUpdateItem` hook
|
||||
*/
|
||||
export type UpdateItemInput<T extends CartItemBody> = T & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type UseUpdateItem<
|
||||
H extends MutationHook<any, any, any> = MutationHook<
|
||||
Cart | null,
|
||||
{
|
||||
item?: LineItem
|
||||
wait?: number
|
||||
},
|
||||
UpdateItemInput<CartItemBody>,
|
||||
UpdateCartItemBody<CartItemBody>
|
||||
>
|
||||
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<
|
||||
Cart | null,
|
||||
UpdateCartItemBody<CartItemBody>
|
||||
> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.cart?.useUpdateItem!
|
||||
|
||||
|
@@ -56,6 +56,19 @@ function withCommerceConfig(nextConfig = {}) {
|
||||
tsconfig.compilerOptions.paths['@framework'] = [`framework/${name}`]
|
||||
tsconfig.compilerOptions.paths['@framework/*'] = [`framework/${name}/*`]
|
||||
|
||||
// When running for production it may be useful to exclude the other providers
|
||||
// from TS checking
|
||||
if (process.env.VERCEL) {
|
||||
const exclude = tsconfig.exclude.filter(
|
||||
(item) => !item.startsWith('framework/')
|
||||
)
|
||||
|
||||
tsconfig.exclude = PROVIDERS.reduce((exclude, current) => {
|
||||
if (current !== name) exclude.push(`framework/${current}`)
|
||||
return exclude
|
||||
}, exclude)
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
tsconfigPath,
|
||||
prettier.format(JSON.stringify(tsconfig), { parser: 'json' })
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { useHook, useSWRHook } from '../utils/use-hook'
|
||||
import { SWRFetcher } from '../utils/default-fetcher'
|
||||
import type { CustomerHook } from '../types/customer'
|
||||
import type { HookFetcherFn, SWRHook } from '../utils/types'
|
||||
import type { Customer } from '../types'
|
||||
import { Provider } from '..'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseCustomer<
|
||||
H extends SWRHook<any, any, any> = SWRHook<Customer | null>
|
||||
H extends SWRHook<CustomerHook<any>> = SWRHook<CustomerHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<Customer | null, any> = SWRFetcher
|
||||
export const fetcher: HookFetcherFn<CustomerHook> = SWRFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.useCustomer!
|
||||
|
||||
|
@@ -6,35 +6,44 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { Fetcher, SWRHook, MutationHook } from './utils/types'
|
||||
import type { FetchCartInput } from './cart/use-cart'
|
||||
import type { Cart, Wishlist, Customer, SearchProductsData } from './types'
|
||||
|
||||
import type {
|
||||
Customer,
|
||||
Wishlist,
|
||||
Cart,
|
||||
Product,
|
||||
Signup,
|
||||
Login,
|
||||
Logout,
|
||||
} from '@commerce/types'
|
||||
|
||||
import type { Fetcher, SWRHook, MutationHook } from './utils/types'
|
||||
|
||||
const Commerce = createContext<CommerceContextValue<any> | {}>({})
|
||||
|
||||
export type Provider = CommerceConfig & {
|
||||
fetcher: Fetcher
|
||||
cart?: {
|
||||
useCart?: SWRHook<Cart | null, any, FetchCartInput>
|
||||
useAddItem?: MutationHook<any, any, any>
|
||||
useUpdateItem?: MutationHook<any, any, any>
|
||||
useRemoveItem?: MutationHook<any, any, any>
|
||||
useCart?: SWRHook<Cart.GetCartHook>
|
||||
useAddItem?: MutationHook<Cart.AddItemHook>
|
||||
useUpdateItem?: MutationHook<Cart.UpdateItemHook>
|
||||
useRemoveItem?: MutationHook<Cart.RemoveItemHook>
|
||||
}
|
||||
wishlist?: {
|
||||
useWishlist?: SWRHook<Wishlist | null, any, any>
|
||||
useAddItem?: MutationHook<any, any, any>
|
||||
useRemoveItem?: MutationHook<any, any, any>
|
||||
useWishlist?: SWRHook<Wishlist.GetWishlistHook>
|
||||
useAddItem?: MutationHook<Wishlist.AddItemHook>
|
||||
useRemoveItem?: MutationHook<Wishlist.RemoveItemHook>
|
||||
}
|
||||
customer?: {
|
||||
useCustomer?: SWRHook<Customer | null, any, any>
|
||||
useCustomer?: SWRHook<Customer.CustomerHook>
|
||||
}
|
||||
products?: {
|
||||
useSearch?: SWRHook<SearchProductsData, any, any>
|
||||
useSearch?: SWRHook<Product.SearchProductsHook>
|
||||
}
|
||||
auth?: {
|
||||
useSignup?: MutationHook<any, any, any>
|
||||
useLogin?: MutationHook<any, any, any>
|
||||
useLogout?: MutationHook<any, any, any>
|
||||
useSignup?: MutationHook<Signup.SignupHook>
|
||||
useLogin?: MutationHook<Login.LoginHook>
|
||||
useLogout?: MutationHook<Logout.LogoutHook>
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -149,7 +149,7 @@ export const handler: SWRHook<
|
||||
{ isEmpty?: boolean }
|
||||
> = {
|
||||
fetchOptions: {
|
||||
url: '/api/bigcommerce/cart',
|
||||
url: '/api/cart',
|
||||
method: 'GET',
|
||||
},
|
||||
async fetcher({ input: { cartId }, options, fetch }) {
|
||||
@@ -197,7 +197,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<Cart, {}, CartItemBody> = {
|
||||
fetchOptions: {
|
||||
url: '/api/bigcommerce/cart',
|
||||
url: '/api/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { useHook, useSWRHook } from '../utils/use-hook'
|
||||
import { SWRFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, SWRHook } from '../utils/types'
|
||||
import type { SearchProductsData } from '../types'
|
||||
import { Provider } from '..'
|
||||
import type { SearchProductsHook } from '../types/product'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseSearch<
|
||||
H extends SWRHook<any, any, any> = SWRHook<SearchProductsData>
|
||||
H extends SWRHook<SearchProductsHook<any>> = SWRHook<SearchProductsHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<SearchProductsData, any> = SWRFetcher
|
||||
export const fetcher: HookFetcherFn<SearchProductsHook> = SWRFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.products?.useSearch!
|
||||
|
||||
|
@@ -1,213 +0,0 @@
|
||||
import type { Wishlist as BCWishlist } from '../bigcommerce/api/wishlist'
|
||||
import type { Customer as BCCustomer } from '../bigcommerce/api/customers'
|
||||
import type { SearchProductsData as BCSearchProductsData } from '../bigcommerce/api/catalog/products'
|
||||
|
||||
export type Discount = {
|
||||
// The value of the discount, can be an amount or percentage
|
||||
value: number
|
||||
}
|
||||
|
||||
export type LineItem = {
|
||||
id: string
|
||||
variantId: string
|
||||
productId: string
|
||||
name: string
|
||||
quantity: number
|
||||
discounts: Discount[]
|
||||
// A human-friendly unique string automatically generated from the product’s name
|
||||
path: string
|
||||
variant: ProductVariant
|
||||
}
|
||||
|
||||
export type Measurement = {
|
||||
value: number
|
||||
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
|
||||
}
|
||||
|
||||
export type Image = {
|
||||
url: string
|
||||
altText?: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
export type ProductVariant = {
|
||||
id: string
|
||||
// The SKU (stock keeping unit) associated with the product variant.
|
||||
sku: string
|
||||
// The product variant’s title, or the product's name.
|
||||
name: string
|
||||
// Whether a customer needs to provide a shipping address when placing
|
||||
// an order for the product variant.
|
||||
requiresShipping: boolean
|
||||
// The product variant’s price after all discounts are applied.
|
||||
price: number
|
||||
// Product variant’s price, as quoted by the manufacturer/distributor.
|
||||
listPrice: number
|
||||
// Image associated with the product variant. Falls back to the product image
|
||||
// if no image is available.
|
||||
image?: Image
|
||||
// Indicates whether this product variant is in stock.
|
||||
isInStock?: boolean
|
||||
// Indicates if the product variant is available for sale.
|
||||
availableForSale?: boolean
|
||||
// The variant's weight. If a weight was not explicitly specified on the
|
||||
// variant this will be the product's weight.
|
||||
weight?: Measurement
|
||||
// The variant's height. If a height was not explicitly specified on the
|
||||
// variant, this will be the product's height.
|
||||
height?: Measurement
|
||||
// The variant's width. If a width was not explicitly specified on the
|
||||
// variant, this will be the product's width.
|
||||
width?: Measurement
|
||||
// The variant's depth. If a depth was not explicitly specified on the
|
||||
// variant, this will be the product's depth.
|
||||
depth?: Measurement
|
||||
}
|
||||
|
||||
// Shopping cart, a.k.a Checkout
|
||||
export type Cart = {
|
||||
id: string
|
||||
// ID of the customer to which the cart belongs.
|
||||
customerId?: string
|
||||
// The email assigned to this cart
|
||||
email?: string
|
||||
// The date and time when the cart was created.
|
||||
createdAt: string
|
||||
// The currency used for this cart
|
||||
currency: { code: string }
|
||||
// Specifies if taxes are included in the line items.
|
||||
taxesIncluded: boolean
|
||||
lineItems: LineItem[]
|
||||
// The sum of all the prices of all the items in the cart.
|
||||
// Duties, taxes, shipping and discounts excluded.
|
||||
lineItemsSubtotalPrice: number
|
||||
// Price of the cart before duties, shipping and taxes.
|
||||
subtotalPrice: number
|
||||
// The sum of all the prices of all the items in the cart.
|
||||
// Duties, taxes and discounts included.
|
||||
totalPrice: number
|
||||
// Discounts that have been applied on the cart.
|
||||
discounts?: Discount[]
|
||||
}
|
||||
|
||||
// TODO: Properly define this type
|
||||
export interface Wishlist extends BCWishlist {}
|
||||
|
||||
// TODO: Properly define this type
|
||||
export interface Customer extends BCCustomer {}
|
||||
|
||||
// TODO: Properly define this type
|
||||
export interface SearchProductsData extends BCSearchProductsData {}
|
||||
|
||||
/**
|
||||
* Cart mutations
|
||||
*/
|
||||
|
||||
// Base cart item body used for cart mutations
|
||||
export type CartItemBody = {
|
||||
variantId: string
|
||||
productId?: string
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
// Body used by the `getCart` operation handler
|
||||
export type GetCartHandlerBody = {
|
||||
cartId?: string
|
||||
}
|
||||
|
||||
// Body used by the add item to cart operation
|
||||
export type AddCartItemBody<T extends CartItemBody> = {
|
||||
item: T
|
||||
}
|
||||
|
||||
// Body expected by the add item to cart operation handler
|
||||
export type AddCartItemHandlerBody<T extends CartItemBody> = Partial<
|
||||
AddCartItemBody<T>
|
||||
> & {
|
||||
cartId?: string
|
||||
}
|
||||
|
||||
// Body used by the update cart item operation
|
||||
export type UpdateCartItemBody<T extends CartItemBody> = {
|
||||
itemId: string
|
||||
item: T
|
||||
}
|
||||
|
||||
// Body expected by the update cart item operation handler
|
||||
export type UpdateCartItemHandlerBody<T extends CartItemBody> = Partial<
|
||||
UpdateCartItemBody<T>
|
||||
> & {
|
||||
cartId?: string
|
||||
}
|
||||
|
||||
// Body used by the remove cart item operation
|
||||
export type RemoveCartItemBody = {
|
||||
itemId: string
|
||||
}
|
||||
|
||||
// Body expected by the remove cart item operation handler
|
||||
export type RemoveCartItemHandlerBody = Partial<RemoveCartItemBody> & {
|
||||
cartId?: string
|
||||
}
|
||||
|
||||
export type Category = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type Page = any
|
||||
|
||||
/**
|
||||
* Temporal types
|
||||
*/
|
||||
|
||||
interface Entity {
|
||||
id: string | number
|
||||
[prop: string]: any
|
||||
}
|
||||
|
||||
export interface Product extends Entity {
|
||||
name: string
|
||||
description: string
|
||||
descriptionHtml?: string
|
||||
slug?: string
|
||||
path?: string
|
||||
images: ProductImage[]
|
||||
variants: ProductVariant2[]
|
||||
price: ProductPrice
|
||||
options: ProductOption[]
|
||||
sku?: string
|
||||
}
|
||||
|
||||
interface ProductOption extends Entity {
|
||||
displayName: string
|
||||
values: ProductOptionValues[]
|
||||
}
|
||||
|
||||
interface ProductOptionValues {
|
||||
label: string
|
||||
hexColors?: string[]
|
||||
}
|
||||
|
||||
interface ProductImage {
|
||||
url: string
|
||||
alt?: string
|
||||
}
|
||||
|
||||
interface ProductVariant2 {
|
||||
id: string | number
|
||||
options: ProductOption[]
|
||||
}
|
||||
|
||||
interface ProductPrice {
|
||||
value: number
|
||||
currencyCode: 'USD' | 'ARS' | string | undefined
|
||||
retailPrice?: number
|
||||
salePrice?: number
|
||||
listPrice?: number
|
||||
extendedSalePrice?: number
|
||||
extendedListPrice?: number
|
||||
}
|
179
framework/commerce/types/cart.ts
Normal file
179
framework/commerce/types/cart.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import type { Discount, Measurement, Image } from './common'
|
||||
|
||||
export type SelectedOption = {
|
||||
// The option's id.
|
||||
id?: string
|
||||
// The product option’s name.
|
||||
name: string
|
||||
/// The product option’s value.
|
||||
value: string
|
||||
}
|
||||
|
||||
export type LineItem = {
|
||||
id: string
|
||||
variantId: string
|
||||
productId: string
|
||||
name: string
|
||||
quantity: number
|
||||
discounts: Discount[]
|
||||
// A human-friendly unique string automatically generated from the product’s name
|
||||
path: string
|
||||
variant: ProductVariant
|
||||
options?: SelectedOption[]
|
||||
}
|
||||
|
||||
export type ProductVariant = {
|
||||
id: string
|
||||
// The SKU (stock keeping unit) associated with the product variant.
|
||||
sku: string
|
||||
// The product variant’s title, or the product's name.
|
||||
name: string
|
||||
// Whether a customer needs to provide a shipping address when placing
|
||||
// an order for the product variant.
|
||||
requiresShipping: boolean
|
||||
// The product variant’s price after all discounts are applied.
|
||||
price: number
|
||||
// Product variant’s price, as quoted by the manufacturer/distributor.
|
||||
listPrice: number
|
||||
// Image associated with the product variant. Falls back to the product image
|
||||
// if no image is available.
|
||||
image?: Image
|
||||
// Indicates whether this product variant is in stock.
|
||||
isInStock?: boolean
|
||||
// Indicates if the product variant is available for sale.
|
||||
availableForSale?: boolean
|
||||
// The variant's weight. If a weight was not explicitly specified on the
|
||||
// variant this will be the product's weight.
|
||||
weight?: Measurement
|
||||
// The variant's height. If a height was not explicitly specified on the
|
||||
// variant, this will be the product's height.
|
||||
height?: Measurement
|
||||
// The variant's width. If a width was not explicitly specified on the
|
||||
// variant, this will be the product's width.
|
||||
width?: Measurement
|
||||
// The variant's depth. If a depth was not explicitly specified on the
|
||||
// variant, this will be the product's depth.
|
||||
depth?: Measurement
|
||||
}
|
||||
|
||||
// Shopping cart, a.k.a Checkout
|
||||
export type Cart = {
|
||||
id: string
|
||||
// ID of the customer to which the cart belongs.
|
||||
customerId?: string
|
||||
// The email assigned to this cart
|
||||
email?: string
|
||||
// The date and time when the cart was created.
|
||||
createdAt: string
|
||||
// The currency used for this cart
|
||||
currency: { code: string }
|
||||
// Specifies if taxes are included in the line items.
|
||||
taxesIncluded: boolean
|
||||
lineItems: LineItem[]
|
||||
// The sum of all the prices of all the items in the cart.
|
||||
// Duties, taxes, shipping and discounts excluded.
|
||||
lineItemsSubtotalPrice: number
|
||||
// Price of the cart before duties, shipping and taxes.
|
||||
subtotalPrice: number
|
||||
// The sum of all the prices of all the items in the cart.
|
||||
// Duties, taxes and discounts included.
|
||||
totalPrice: number
|
||||
// Discounts that have been applied on the cart.
|
||||
discounts?: Discount[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Base cart item body used for cart mutations
|
||||
*/
|
||||
export type CartItemBody = {
|
||||
variantId: string
|
||||
productId?: string
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks schema
|
||||
*/
|
||||
|
||||
export type CartTypes = {
|
||||
cart?: Cart
|
||||
item: LineItem
|
||||
itemBody: CartItemBody
|
||||
}
|
||||
|
||||
export type CartHooks<T extends CartTypes = CartTypes> = {
|
||||
getCart: GetCartHook<T>
|
||||
addItem: AddItemHook<T>
|
||||
updateItem: UpdateItemHook<T>
|
||||
removeItem: RemoveItemHook<T>
|
||||
}
|
||||
|
||||
export type GetCartHook<T extends CartTypes = CartTypes> = {
|
||||
data: T['cart'] | null
|
||||
input: {}
|
||||
fetcherInput: { cartId?: string }
|
||||
swrState: { isEmpty: boolean }
|
||||
}
|
||||
|
||||
export type AddItemHook<T extends CartTypes = CartTypes> = {
|
||||
data: T['cart']
|
||||
input?: T['itemBody']
|
||||
fetcherInput: T['itemBody']
|
||||
body: { item: T['itemBody'] }
|
||||
actionInput: T['itemBody']
|
||||
}
|
||||
|
||||
export type UpdateItemHook<T extends CartTypes = CartTypes> = {
|
||||
data: T['cart'] | null
|
||||
input: { item?: T['item']; wait?: number }
|
||||
fetcherInput: { itemId: string; item: T['itemBody'] }
|
||||
body: { itemId: string; item: T['itemBody'] }
|
||||
actionInput: T['itemBody'] & { id: string }
|
||||
}
|
||||
|
||||
export type RemoveItemHook<T extends CartTypes = CartTypes> = {
|
||||
data: T['cart'] | null
|
||||
input: { item?: T['item'] }
|
||||
fetcherInput: { itemId: string }
|
||||
body: { itemId: string }
|
||||
actionInput: { id: string }
|
||||
}
|
||||
|
||||
/**
|
||||
* API Schema
|
||||
*/
|
||||
|
||||
export type CartSchema<T extends CartTypes = CartTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: CartHandlers<T>
|
||||
}
|
||||
}
|
||||
|
||||
export type CartHandlers<T extends CartTypes = CartTypes> = {
|
||||
getCart: GetCartHandler<T>
|
||||
addItem: AddItemHandler<T>
|
||||
updateItem: UpdateItemHandler<T>
|
||||
removeItem: RemoveItemHandler<T>
|
||||
}
|
||||
|
||||
export type GetCartHandler<T extends CartTypes = CartTypes> = GetCartHook<T> & {
|
||||
body: { cartId?: string }
|
||||
}
|
||||
|
||||
export type AddItemHandler<T extends CartTypes = CartTypes> = AddItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type UpdateItemHandler<
|
||||
T extends CartTypes = CartTypes
|
||||
> = UpdateItemHook<T> & {
|
||||
data: T['cart']
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type RemoveItemHandler<
|
||||
T extends CartTypes = CartTypes
|
||||
> = RemoveItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
10
framework/commerce/types/checkout.ts
Normal file
10
framework/commerce/types/checkout.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export type CheckoutSchema = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
checkout: {
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
framework/commerce/types/common.ts
Normal file
16
framework/commerce/types/common.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export type Discount = {
|
||||
// The value of the discount, can be an amount or percentage
|
||||
value: number
|
||||
}
|
||||
|
||||
export type Measurement = {
|
||||
value: number
|
||||
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
|
||||
}
|
||||
|
||||
export type Image = {
|
||||
url: string
|
||||
altText?: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
22
framework/commerce/types/customer.ts
Normal file
22
framework/commerce/types/customer.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// TODO: define this type
|
||||
export type Customer = any
|
||||
|
||||
export type CustomerTypes = {
|
||||
customer: Customer
|
||||
}
|
||||
|
||||
export type CustomerHook<T extends CustomerTypes = CustomerTypes> = {
|
||||
data: T['customer'] | null
|
||||
fetchData: { customer: T['customer'] } | null
|
||||
}
|
||||
|
||||
export type CustomerSchema<T extends CustomerTypes = CustomerTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
getLoggedInCustomer: {
|
||||
data: { customer: T['customer'] } | null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
framework/commerce/types/index.ts
Normal file
25
framework/commerce/types/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as Cart from './cart'
|
||||
import * as Checkout from './checkout'
|
||||
import * as Common from './common'
|
||||
import * as Customer from './customer'
|
||||
import * as Login from './login'
|
||||
import * as Logout from './logout'
|
||||
import * as Page from './page'
|
||||
import * as Product from './product'
|
||||
import * as Signup from './signup'
|
||||
import * as Site from './site'
|
||||
import * as Wishlist from './wishlist'
|
||||
|
||||
export type {
|
||||
Cart,
|
||||
Checkout,
|
||||
Common,
|
||||
Customer,
|
||||
Login,
|
||||
Logout,
|
||||
Page,
|
||||
Product,
|
||||
Signup,
|
||||
Site,
|
||||
Wishlist,
|
||||
}
|
29
framework/commerce/types/login.ts
Normal file
29
framework/commerce/types/login.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type LoginBody = {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export type LoginTypes = {
|
||||
body: LoginBody
|
||||
}
|
||||
|
||||
export type LoginHook<T extends LoginTypes = LoginTypes> = {
|
||||
data: null
|
||||
actionInput: LoginBody
|
||||
fetcherInput: LoginBody
|
||||
body: T['body']
|
||||
}
|
||||
|
||||
export type LoginSchema<T extends LoginTypes = LoginTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
login: LoginHook<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type LoginOperation = {
|
||||
data: { result?: string }
|
||||
variables: unknown
|
||||
}
|
17
framework/commerce/types/logout.ts
Normal file
17
framework/commerce/types/logout.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export type LogoutTypes = {
|
||||
body: { redirectTo?: string }
|
||||
}
|
||||
|
||||
export type LogoutHook<T extends LogoutTypes = LogoutTypes> = {
|
||||
data: null
|
||||
body: T['body']
|
||||
}
|
||||
|
||||
export type LogoutSchema<T extends LogoutTypes = LogoutTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
logout: LogoutHook<T>
|
||||
}
|
||||
}
|
||||
}
|
28
framework/commerce/types/page.ts
Normal file
28
framework/commerce/types/page.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// TODO: define this type
|
||||
export type Page = {
|
||||
// ID of the Web page.
|
||||
id: string
|
||||
// Page name, as displayed on the storefront.
|
||||
name: string
|
||||
// Relative URL on the storefront for this page.
|
||||
url?: string
|
||||
// HTML or variable that populates this page’s `<body>` element, in default/desktop view. Required in POST if page type is `raw`.
|
||||
body: string
|
||||
// If true, this page appears in the storefront’s navigation menu.
|
||||
is_visible?: boolean
|
||||
// Order in which this page should display on the storefront. (Lower integers specify earlier display.)
|
||||
sort_order?: number
|
||||
}
|
||||
|
||||
export type PageTypes = {
|
||||
page: Page
|
||||
}
|
||||
|
||||
export type GetAllPagesOperation<T extends PageTypes = PageTypes> = {
|
||||
data: { pages: T['page'][] }
|
||||
}
|
||||
|
||||
export type GetPageOperation<T extends PageTypes = PageTypes> = {
|
||||
data: { page?: T['page'] }
|
||||
variables: { id: string }
|
||||
}
|
99
framework/commerce/types/product.ts
Normal file
99
framework/commerce/types/product.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
export type ProductImage = {
|
||||
url: string
|
||||
alt?: string
|
||||
}
|
||||
|
||||
export type ProductPrice = {
|
||||
value: number
|
||||
currencyCode?: 'USD' | 'ARS' | string
|
||||
retailPrice?: number
|
||||
salePrice?: number
|
||||
listPrice?: number
|
||||
extendedSalePrice?: number
|
||||
extendedListPrice?: number
|
||||
}
|
||||
|
||||
export type ProductOption = {
|
||||
__typename?: 'MultipleChoiceOption'
|
||||
id: string
|
||||
displayName: string
|
||||
values: ProductOptionValues[]
|
||||
}
|
||||
|
||||
export type ProductOptionValues = {
|
||||
label: string
|
||||
hexColors?: string[]
|
||||
}
|
||||
|
||||
export type ProductVariant = {
|
||||
id: string | number
|
||||
options: ProductOption[]
|
||||
availableForSale?: boolean
|
||||
}
|
||||
|
||||
export type Product = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
descriptionHtml?: string
|
||||
sku?: string
|
||||
slug?: string
|
||||
path?: string
|
||||
images: ProductImage[]
|
||||
variants: ProductVariant[]
|
||||
price: ProductPrice
|
||||
options: ProductOption[]
|
||||
}
|
||||
|
||||
export type SearchProductsBody = {
|
||||
search?: string
|
||||
categoryId?: string | number
|
||||
brandId?: string | number
|
||||
sort?: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export type ProductTypes = {
|
||||
product: Product
|
||||
searchBody: SearchProductsBody
|
||||
}
|
||||
|
||||
export type SearchProductsHook<T extends ProductTypes = ProductTypes> = {
|
||||
data: {
|
||||
products: T['product'][]
|
||||
found: boolean
|
||||
}
|
||||
body: T['searchBody']
|
||||
input: T['searchBody']
|
||||
fetcherInput: T['searchBody']
|
||||
}
|
||||
|
||||
export type ProductsSchema<T extends ProductTypes = ProductTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
getProducts: SearchProductsHook<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type GetAllProductPathsOperation<
|
||||
T extends ProductTypes = ProductTypes
|
||||
> = {
|
||||
data: { products: Pick<T['product'], 'path'>[] }
|
||||
variables: { first?: number }
|
||||
}
|
||||
|
||||
export type GetAllProductsOperation<T extends ProductTypes = ProductTypes> = {
|
||||
data: { products: T['product'][] }
|
||||
variables: {
|
||||
relevance?: 'featured' | 'best_selling' | 'newest'
|
||||
ids?: string[]
|
||||
first?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type GetProductOperation<T extends ProductTypes = ProductTypes> = {
|
||||
data: { product?: T['product'] }
|
||||
variables: { path: string; slug?: never } | { path?: never; slug: string }
|
||||
}
|
26
framework/commerce/types/signup.ts
Normal file
26
framework/commerce/types/signup.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export type SignupBody = {
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export type SignupTypes = {
|
||||
body: SignupBody
|
||||
}
|
||||
|
||||
export type SignupHook<T extends SignupTypes = SignupTypes> = {
|
||||
data: null
|
||||
body: T['body']
|
||||
actionInput: T['body']
|
||||
fetcherInput: T['body']
|
||||
}
|
||||
|
||||
export type SignupSchema<T extends SignupTypes = SignupTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
signup: SignupHook<T>
|
||||
}
|
||||
}
|
||||
}
|
20
framework/commerce/types/site.ts
Normal file
20
framework/commerce/types/site.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type Category = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type Brand = any
|
||||
|
||||
export type SiteTypes = {
|
||||
category: Category
|
||||
brand: Brand
|
||||
}
|
||||
|
||||
export type GetSiteInfoOperation<T extends SiteTypes = SiteTypes> = {
|
||||
data: {
|
||||
categories: T['category'][]
|
||||
brands: T['brand'][]
|
||||
}
|
||||
}
|
60
framework/commerce/types/wishlist.ts
Normal file
60
framework/commerce/types/wishlist.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
// TODO: define this type
|
||||
export type Wishlist = any
|
||||
|
||||
export type WishlistItemBody = {
|
||||
variantId: string | number
|
||||
productId: string
|
||||
}
|
||||
|
||||
export type WishlistTypes = {
|
||||
wishlist: Wishlist
|
||||
itemBody: WishlistItemBody
|
||||
}
|
||||
|
||||
export type GetWishlistHook<T extends WishlistTypes = WishlistTypes> = {
|
||||
data: T['wishlist'] | null
|
||||
body: { includeProducts?: boolean }
|
||||
input: { includeProducts?: boolean }
|
||||
fetcherInput: { customerId: string; includeProducts?: boolean }
|
||||
swrState: { isEmpty: boolean }
|
||||
}
|
||||
|
||||
export type AddItemHook<T extends WishlistTypes = WishlistTypes> = {
|
||||
data: T['wishlist']
|
||||
body: { item: T['itemBody'] }
|
||||
fetcherInput: { item: T['itemBody'] }
|
||||
actionInput: T['itemBody']
|
||||
}
|
||||
|
||||
export type RemoveItemHook<T extends WishlistTypes = WishlistTypes> = {
|
||||
data: T['wishlist'] | null
|
||||
body: { itemId: string }
|
||||
fetcherInput: { itemId: string }
|
||||
actionInput: { id: string }
|
||||
input: { wishlist?: { includeProducts?: boolean } }
|
||||
}
|
||||
|
||||
export type WishlistSchema<T extends WishlistTypes = WishlistTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
getWishlist: GetWishlistHook<T> & {
|
||||
data: T['wishlist'] | null
|
||||
body: { customerToken?: string }
|
||||
}
|
||||
addItem: AddItemHook<T> & {
|
||||
body: { customerToken?: string }
|
||||
}
|
||||
removeItem: RemoveItemHook<T> & {
|
||||
body: { customerToken?: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type GetCustomerWishlistOperation<
|
||||
T extends WishlistTypes = WishlistTypes
|
||||
> = {
|
||||
data: { wishlist?: T['wishlist'] }
|
||||
variables: { customerId: string }
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
import type { HookFetcherFn } from './types'
|
||||
|
||||
export const SWRFetcher: HookFetcherFn<any, any> = ({ options, fetch }) =>
|
||||
export const SWRFetcher: HookFetcherFn<any> = ({ options, fetch }) =>
|
||||
fetch(options)
|
||||
|
||||
export const mutationFetcher: HookFetcherFn<any, any> = ({
|
||||
export const mutationFetcher: HookFetcherFn<any> = ({
|
||||
input,
|
||||
options,
|
||||
fetch,
|
||||
|
@@ -36,14 +36,19 @@ export type HookFetcher<Data, Input = null, Result = any> = (
|
||||
fetch: <T = Result, Body = any>(options: FetcherOptions<Body>) => Promise<T>
|
||||
) => Data | Promise<Data>
|
||||
|
||||
export type HookFetcherFn<Data, Input = undefined, Result = any, Body = any> = (
|
||||
context: HookFetcherContext<Input, Result, Body>
|
||||
) => Data | Promise<Data>
|
||||
export type HookFetcherFn<H extends HookSchemaBase> = (
|
||||
context: HookFetcherContext<H>
|
||||
) => H['data'] | Promise<H['data']>
|
||||
|
||||
export type HookFetcherContext<Input = undefined, Result = any, Body = any> = {
|
||||
export type HookFetcherContext<H extends HookSchemaBase> = {
|
||||
options: HookFetcherOptions
|
||||
input: Input
|
||||
fetch: <T = Result, B = Body>(options: FetcherOptions<B>) => Promise<T>
|
||||
input: H['fetcherInput']
|
||||
fetch: <
|
||||
T = H['fetchData'] extends {} | null ? H['fetchData'] : any,
|
||||
B = H['body']
|
||||
>(
|
||||
options: FetcherOptions<B>
|
||||
) => Promise<T>
|
||||
}
|
||||
|
||||
export type HookFetcherOptions = { method?: string } & (
|
||||
@@ -58,7 +63,7 @@ export type HookSWRInput = [string, HookInputValue][]
|
||||
export type HookFetchInput = { [k: string]: HookInputValue }
|
||||
|
||||
export type HookFunction<
|
||||
Input extends { [k: string]: unknown } | null,
|
||||
Input extends { [k: string]: unknown } | undefined,
|
||||
T
|
||||
> = keyof Input extends never
|
||||
? () => T
|
||||
@@ -66,62 +71,72 @@ export type HookFunction<
|
||||
? (input?: Input) => T
|
||||
: (input: Input) => T
|
||||
|
||||
export type SWRHook<
|
||||
// Data obj returned by the hook and fetch operation
|
||||
Data,
|
||||
export type HookSchemaBase = {
|
||||
// Data obj returned by the hook
|
||||
data: any
|
||||
// Input expected by the hook
|
||||
Input extends { [k: string]: unknown } = {},
|
||||
// Input expected before doing a fetch operation
|
||||
FetchInput extends HookFetchInput = {},
|
||||
input?: {}
|
||||
// Input expected before doing a fetch operation (aka fetch handler)
|
||||
fetcherInput?: {}
|
||||
// Body object expected by the fetch operation
|
||||
body?: {}
|
||||
// Data returned by the fetch operation
|
||||
fetchData?: any
|
||||
}
|
||||
|
||||
export type SWRHookSchemaBase = HookSchemaBase & {
|
||||
// Custom state added to the response object of SWR
|
||||
State = {}
|
||||
> = {
|
||||
swrState?: {}
|
||||
}
|
||||
|
||||
export type MutationSchemaBase = HookSchemaBase & {
|
||||
// Input expected by the action returned by the hook
|
||||
actionInput?: {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SWR hook handler based on the schema of a hook
|
||||
*/
|
||||
export type SWRHook<H extends SWRHookSchemaBase> = {
|
||||
useHook(
|
||||
context: SWRHookContext<Data, FetchInput>
|
||||
context: SWRHookContext<H>
|
||||
): HookFunction<
|
||||
Input & { swrOptions?: SwrOptions<Data, FetchInput> },
|
||||
ResponseState<Data> & State
|
||||
H['input'] & { swrOptions?: SwrOptions<H['data'], H['fetcherInput']> },
|
||||
ResponseState<H['data']> & H['swrState']
|
||||
>
|
||||
fetchOptions: HookFetcherOptions
|
||||
fetcher?: HookFetcherFn<Data, FetchInput>
|
||||
fetcher?: HookFetcherFn<H>
|
||||
}
|
||||
|
||||
export type SWRHookContext<
|
||||
Data,
|
||||
FetchInput extends { [k: string]: unknown } = {}
|
||||
> = {
|
||||
export type SWRHookContext<H extends SWRHookSchemaBase> = {
|
||||
useData(context?: {
|
||||
input?: HookFetchInput | HookSWRInput
|
||||
swrOptions?: SwrOptions<Data, FetchInput>
|
||||
}): ResponseState<Data>
|
||||
swrOptions?: SwrOptions<H['data'], H['fetcherInput']>
|
||||
}): ResponseState<H['data']>
|
||||
}
|
||||
|
||||
export type MutationHook<
|
||||
// Data obj returned by the hook and fetch operation
|
||||
Data,
|
||||
// Input expected by the hook
|
||||
Input extends { [k: string]: unknown } = {},
|
||||
// Input expected by the action returned by the hook
|
||||
ActionInput extends { [k: string]: unknown } = {},
|
||||
// Input expected before doing a fetch operation
|
||||
FetchInput extends { [k: string]: unknown } = ActionInput
|
||||
> = {
|
||||
/**
|
||||
* Generates a mutation hook handler based on the schema of a hook
|
||||
*/
|
||||
export type MutationHook<H extends MutationSchemaBase> = {
|
||||
useHook(
|
||||
context: MutationHookContext<Data, FetchInput>
|
||||
): HookFunction<Input, HookFunction<ActionInput, Data | Promise<Data>>>
|
||||
context: MutationHookContext<H>
|
||||
): HookFunction<
|
||||
H['input'],
|
||||
HookFunction<H['actionInput'], H['data'] | Promise<H['data']>>
|
||||
>
|
||||
fetchOptions: HookFetcherOptions
|
||||
fetcher?: HookFetcherFn<Data, FetchInput>
|
||||
fetcher?: HookFetcherFn<H>
|
||||
}
|
||||
|
||||
export type MutationHookContext<
|
||||
Data,
|
||||
FetchInput extends { [k: string]: unknown } | null = {}
|
||||
> = {
|
||||
fetch: keyof FetchInput extends never
|
||||
? () => Data | Promise<Data>
|
||||
: Partial<FetchInput> extends FetchInput
|
||||
? (context?: { input?: FetchInput }) => Data | Promise<Data>
|
||||
: (context: { input: FetchInput }) => Data | Promise<Data>
|
||||
export type MutationHookContext<H extends MutationSchemaBase> = {
|
||||
fetch: keyof H['fetcherInput'] extends never
|
||||
? () => H['data'] | Promise<H['data']>
|
||||
: Partial<H['fetcherInput']> extends H['fetcherInput']
|
||||
? (context?: {
|
||||
input?: H['fetcherInput']
|
||||
}) => H['data'] | Promise<H['data']>
|
||||
: (context: { input: H['fetcherInput'] }) => H['data'] | Promise<H['data']>
|
||||
}
|
||||
|
||||
export type SwrOptions<Data, Input = null, Result = any> = ConfigInterface<
|
||||
|
@@ -2,10 +2,11 @@ import useSWR, { responseInterface } from 'swr'
|
||||
import type {
|
||||
HookSWRInput,
|
||||
HookFetchInput,
|
||||
Fetcher,
|
||||
SwrOptions,
|
||||
HookFetcherOptions,
|
||||
HookFetcherFn,
|
||||
Fetcher,
|
||||
SwrOptions,
|
||||
SWRHookSchemaBase,
|
||||
} from './types'
|
||||
import defineProperty from './define-property'
|
||||
import { CommerceError } from './errors'
|
||||
@@ -14,15 +15,15 @@ export type ResponseState<Result> = responseInterface<Result, CommerceError> & {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export type UseData = <Data = any, FetchInput extends HookFetchInput = {}>(
|
||||
export type UseData = <H extends SWRHookSchemaBase>(
|
||||
options: {
|
||||
fetchOptions: HookFetcherOptions
|
||||
fetcher: HookFetcherFn<Data, FetchInput>
|
||||
fetcher: HookFetcherFn<H>
|
||||
},
|
||||
input: HookFetchInput | HookSWRInput,
|
||||
fetcherFn: Fetcher,
|
||||
swrOptions?: SwrOptions<Data, FetchInput>
|
||||
) => ResponseState<Data>
|
||||
swrOptions?: SwrOptions<H['data'], H['fetcherInput']>
|
||||
) => ResponseState<H['data']>
|
||||
|
||||
const useData: UseData = (options, input, fetcherFn, swrOptions) => {
|
||||
const hookInput = Array.isArray(input) ? input : Object.entries(input)
|
||||
|
@@ -10,14 +10,14 @@ export function useFetcher() {
|
||||
|
||||
export function useHook<
|
||||
P extends Provider,
|
||||
H extends MutationHook<any, any, any> | SWRHook<any, any, any>
|
||||
H extends MutationHook<any> | SWRHook<any>
|
||||
>(fn: (provider: P) => H) {
|
||||
const { providerRef } = useCommerce<P>()
|
||||
const provider = providerRef.current
|
||||
return fn(provider)
|
||||
}
|
||||
|
||||
export function useSWRHook<H extends SWRHook<any, any, any>>(
|
||||
export function useSWRHook<H extends SWRHook<any>>(
|
||||
hook: PickRequired<H, 'fetcher'>
|
||||
) {
|
||||
const fetcher = useFetcher()
|
||||
@@ -30,7 +30,7 @@ export function useSWRHook<H extends SWRHook<any, any, any>>(
|
||||
})
|
||||
}
|
||||
|
||||
export function useMutationHook<H extends MutationHook<any, any, any>>(
|
||||
export function useMutationHook<H extends MutationHook<any>>(
|
||||
hook: PickRequired<H, 'fetcher'>
|
||||
) {
|
||||
const fetcher = useFetcher()
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { MutationHook } from '../utils/types'
|
||||
import type { AddItemHook } from '../types/wishlist'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseAddItem<
|
||||
H extends MutationHook<any, any, any> = MutationHook<any, {}, {}>
|
||||
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher = mutationFetcher
|
||||
|
@@ -1,28 +1,20 @@
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { RemoveItemHook } from '../types/wishlist'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type RemoveItemInput = {
|
||||
id: string | number
|
||||
}
|
||||
|
||||
export type UseRemoveItem<
|
||||
H extends MutationHook<any, any, any> = MutationHook<
|
||||
any | null,
|
||||
{ wishlist?: any },
|
||||
RemoveItemInput,
|
||||
{}
|
||||
>
|
||||
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<any | null, {}> = mutationFetcher
|
||||
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.wishlist?.useRemoveItem!
|
||||
|
||||
const useRemoveItem: UseRemoveItem = (input) => {
|
||||
const useRemoveItem: UseRemoveItem = (...args) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(input)
|
||||
return useMutationHook({ fetcher, ...hook })(...args)
|
||||
}
|
||||
|
||||
export default useRemoveItem
|
||||
|
@@ -1,25 +1,20 @@
|
||||
import { useHook, useSWRHook } from '../utils/use-hook'
|
||||
import { SWRFetcher } from '../utils/default-fetcher'
|
||||
import type { HookFetcherFn, SWRHook } from '../utils/types'
|
||||
import type { Wishlist } from '../types'
|
||||
import type { GetWishlistHook } from '../types/wishlist'
|
||||
import type { Provider } from '..'
|
||||
|
||||
export type UseWishlist<
|
||||
H extends SWRHook<any, any, any> = SWRHook<
|
||||
Wishlist | null,
|
||||
{ includeProducts?: boolean },
|
||||
{ customerId?: number; includeProducts: boolean },
|
||||
{ isEmpty?: boolean }
|
||||
>
|
||||
H extends SWRHook<GetWishlistHook<any>> = SWRHook<GetWishlistHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<Wishlist | null, any> = SWRFetcher
|
||||
export const fetcher: HookFetcherFn<GetWishlistHook> = SWRFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.wishlist?.useWishlist!
|
||||
|
||||
const useWishlist: UseWishlist = (input) => {
|
||||
const useWishlist: UseWishlist = (...args) => {
|
||||
const hook = useHook(fn)
|
||||
return useSWRHook({ fetcher, ...hook })(input)
|
||||
return useSWRHook({ fetcher, ...hook })(...args)
|
||||
}
|
||||
|
||||
export default useWishlist
|
||||
|
Reference in New Issue
Block a user