mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 05:31:22 +00:00
Making progress on api operations
This commit is contained in:
parent
2281490342
commit
beccca8eaf
@ -1,13 +1,19 @@
|
|||||||
import type { NextApiHandler } from 'next'
|
import type { NextApiHandler } from 'next'
|
||||||
import type { RequestInit } from '@vercel/fetch'
|
import type { RequestInit } from '@vercel/fetch'
|
||||||
import {
|
import { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
|
||||||
CommerceAPI as CoreCommerceAPI,
|
|
||||||
CommerceAPIConfig,
|
|
||||||
} 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 type { CartAPI } from './cart'
|
import type { CartAPI } from './cart'
|
||||||
|
import login from './operations/login'
|
||||||
|
import {
|
||||||
|
Operations,
|
||||||
|
defaultOperations,
|
||||||
|
AllowedOperations,
|
||||||
|
OPERATIONS,
|
||||||
|
getOperations,
|
||||||
|
APIOperations,
|
||||||
|
} from '@commerce/api/operations'
|
||||||
|
|
||||||
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
|
||||||
@ -103,25 +109,29 @@ const config2: BigcommerceConfig = {
|
|||||||
|
|
||||||
export const provider = {
|
export const provider = {
|
||||||
config: config2,
|
config: config2,
|
||||||
|
operations: { login },
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Provider = typeof provider
|
export type Provider = typeof provider
|
||||||
|
|
||||||
export type APIs = CartAPI
|
export type APIs = CartAPI
|
||||||
|
|
||||||
export class CommerceAPI extends CoreCommerceAPI<Provider> {
|
export function getCommerceApi<P extends Provider>(
|
||||||
constructor(customProvider: Provider = provider) {
|
customProvider: P = provider as any
|
||||||
super(customProvider)
|
) {
|
||||||
}
|
const commerce = new CommerceAPI(customProvider)
|
||||||
|
const operations = getOperations(customProvider.operations, { commerce })
|
||||||
|
|
||||||
|
return Object.assign(commerce, operations, {
|
||||||
endpoint<E extends APIs>(
|
endpoint<E extends APIs>(
|
||||||
context: E['endpoint'] & {
|
context: E['endpoint'] & {
|
||||||
config?: Provider['config']
|
config?: P['config']
|
||||||
options?: E['schema']['endpoint']['options']
|
options?: E['schema']['endpoint']['options']
|
||||||
}
|
}
|
||||||
): NextApiHandler {
|
): NextApiHandler {
|
||||||
return super.endpoint(context)
|
return super.endpoint(context)
|
||||||
}
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfig(userConfig?: Partial<BigcommerceConfig>) {
|
export function getConfig(userConfig?: Partial<BigcommerceConfig>) {
|
||||||
|
78
framework/bigcommerce/api/operations/login.ts
Normal file
78
framework/bigcommerce/api/operations/login.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import type { ServerResponse } from 'http'
|
||||||
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
|
import type { LoginMutation, LoginMutationVariables } from '../../schema'
|
||||||
|
import type { RecursivePartial } from '../utils/types'
|
||||||
|
import concatHeader from '../utils/concat-cookie'
|
||||||
|
import type { BigcommerceConfig, Provider } from '..'
|
||||||
|
|
||||||
|
export const loginMutation = /* GraphQL */ `
|
||||||
|
mutation login($email: String!, $password: String!) {
|
||||||
|
login(email: $email, password: $password) {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export type LoginResult<T extends { result?: any } = { result?: string }> = T
|
||||||
|
|
||||||
|
export type LoginVariables = LoginMutationVariables
|
||||||
|
|
||||||
|
function loginOperation({ commerce }: OperationContext<Provider>) {
|
||||||
|
async function login(opts: {
|
||||||
|
variables: LoginVariables
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
res: ServerResponse
|
||||||
|
}): Promise<LoginResult>
|
||||||
|
|
||||||
|
async function login<T extends { result?: any }, V = any>(opts: {
|
||||||
|
query: string
|
||||||
|
variables: V
|
||||||
|
res: ServerResponse
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
}): Promise<LoginResult<T>>
|
||||||
|
|
||||||
|
async function login({
|
||||||
|
query = loginMutation,
|
||||||
|
variables,
|
||||||
|
res: response,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
query?: string
|
||||||
|
variables: LoginVariables
|
||||||
|
res: ServerResponse
|
||||||
|
config?: BigcommerceConfig
|
||||||
|
}): Promise<LoginResult> {
|
||||||
|
config = commerce.getConfig(config)
|
||||||
|
|
||||||
|
const { data, res } = await config.fetch<RecursivePartial<LoginMutation>>(
|
||||||
|
query,
|
||||||
|
{ variables }
|
||||||
|
)
|
||||||
|
// Bigcommerce returns a Set-Cookie header with the auth cookie
|
||||||
|
let cookie = res.headers.get('Set-Cookie')
|
||||||
|
|
||||||
|
if (cookie && typeof cookie === 'string') {
|
||||||
|
// In development, don't set a secure cookie or the browser will ignore it
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
cookie = cookie.replace('; Secure', '')
|
||||||
|
// SameSite=none can't be set unless the cookie is Secure
|
||||||
|
// bc seems to sometimes send back SameSite=None rather than none so make
|
||||||
|
// this case insensitive
|
||||||
|
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setHeader(
|
||||||
|
'Set-Cookie',
|
||||||
|
concatHeader(response.getHeader('Set-Cookie'), cookie)!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: data.login?.result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return login
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loginOperation
|
@ -1,73 +0,0 @@
|
|||||||
import type { ServerResponse } from 'http'
|
|
||||||
import type { LoginMutation, LoginMutationVariables } from '../schema'
|
|
||||||
import type { RecursivePartial } from '../api/utils/types'
|
|
||||||
import concatHeader from '../api/utils/concat-cookie'
|
|
||||||
import { BigcommerceConfig, getConfig } from '../api'
|
|
||||||
|
|
||||||
export const loginMutation = /* GraphQL */ `
|
|
||||||
mutation login($email: String!, $password: String!) {
|
|
||||||
login(email: $email, password: $password) {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export type LoginResult<T extends { result?: any } = { result?: string }> = T
|
|
||||||
|
|
||||||
export type LoginVariables = LoginMutationVariables
|
|
||||||
|
|
||||||
async function login(opts: {
|
|
||||||
variables: LoginVariables
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
res: ServerResponse
|
|
||||||
}): Promise<LoginResult>
|
|
||||||
|
|
||||||
async function login<T extends { result?: any }, V = any>(opts: {
|
|
||||||
query: string
|
|
||||||
variables: V
|
|
||||||
res: ServerResponse
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
}): Promise<LoginResult<T>>
|
|
||||||
|
|
||||||
async function login({
|
|
||||||
query = loginMutation,
|
|
||||||
variables,
|
|
||||||
res: response,
|
|
||||||
config,
|
|
||||||
}: {
|
|
||||||
query?: string
|
|
||||||
variables: LoginVariables
|
|
||||||
res: ServerResponse
|
|
||||||
config?: BigcommerceConfig
|
|
||||||
}): Promise<LoginResult> {
|
|
||||||
config = getConfig(config)
|
|
||||||
|
|
||||||
const { data, res } = await config.fetch<RecursivePartial<LoginMutation>>(
|
|
||||||
query,
|
|
||||||
{ variables }
|
|
||||||
)
|
|
||||||
// Bigcommerce returns a Set-Cookie header with the auth cookie
|
|
||||||
let cookie = res.headers.get('Set-Cookie')
|
|
||||||
|
|
||||||
if (cookie && typeof cookie === 'string') {
|
|
||||||
// In development, don't set a secure cookie or the browser will ignore it
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
cookie = cookie.replace('; Secure', '')
|
|
||||||
// SameSite=none can't be set unless the cookie is Secure
|
|
||||||
// bc seems to sometimes send back SameSite=None rather than none so make
|
|
||||||
// this case insensitive
|
|
||||||
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
|
|
||||||
}
|
|
||||||
|
|
||||||
response.setHeader(
|
|
||||||
'Set-Cookie',
|
|
||||||
concatHeader(response.getHeader('Set-Cookie'), cookie)!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: data.login?.result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default login
|
|
@ -2,6 +2,7 @@ import type { NextApiHandler } from 'next'
|
|||||||
import type { RequestInit, Response } from '@vercel/fetch'
|
import type { RequestInit, Response } from '@vercel/fetch'
|
||||||
import type { APIEndpoint, APIHandler } from './utils/types'
|
import type { APIEndpoint, APIHandler } from './utils/types'
|
||||||
import type { CartSchema } from '../types/cart'
|
import type { CartSchema } from '../types/cart'
|
||||||
|
import { APIOperations } from './operations'
|
||||||
|
|
||||||
export type APISchemas = CartSchema
|
export type APISchemas = CartSchema
|
||||||
|
|
||||||
@ -48,14 +49,13 @@ export type EndpointHandlers<
|
|||||||
|
|
||||||
export type APIProvider = {
|
export type APIProvider = {
|
||||||
config: CommerceAPIConfig
|
config: CommerceAPIConfig
|
||||||
|
operations: APIOperations<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommerceAPI<P extends APIProvider = APIProvider> {
|
export class CommerceAPI<P extends APIProvider = APIProvider> {
|
||||||
constructor(readonly provider: P) {
|
constructor(readonly provider: P) {}
|
||||||
this.provider = provider
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfig(userConfig: Partial<P['config']> = {}) {
|
getConfig(userConfig: Partial<P['config']> = {}): P['config'] {
|
||||||
return Object.entries(userConfig).reduce(
|
return Object.entries(userConfig).reduce(
|
||||||
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
|
||||||
{ ...this.provider.config }
|
{ ...this.provider.config }
|
||||||
|
57
framework/commerce/api/operations.ts
Normal file
57
framework/commerce/api/operations.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import type { ServerResponse } from 'http'
|
||||||
|
import type { APIProvider, CommerceAPI, CommerceAPIConfig } from '.'
|
||||||
|
|
||||||
|
const noop = () => {
|
||||||
|
throw new Error('Not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginResult<T extends { result?: any } = { result?: string }> = T
|
||||||
|
|
||||||
|
export const OPERATIONS = ['login'] as const
|
||||||
|
|
||||||
|
export const defaultOperations = OPERATIONS.reduce((ops, k) => {
|
||||||
|
ops[k] = noop
|
||||||
|
return ops
|
||||||
|
}, {} as { [K in AllowedOperations]: typeof noop })
|
||||||
|
|
||||||
|
export function getOperations<P extends APIProvider>(
|
||||||
|
ops: P['operations'],
|
||||||
|
ctx: { commerce: CommerceAPI<P> }
|
||||||
|
) {
|
||||||
|
return OPERATIONS.reduce<Operations<P>>((carry, k) => {
|
||||||
|
carry[k] = ops[k]({ ...ctx, operations: carry })
|
||||||
|
return carry
|
||||||
|
}, defaultOperations) as APIOperations2<P>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AllowedOperations = typeof OPERATIONS[number]
|
||||||
|
|
||||||
|
export type Operations<P extends APIProvider> = {
|
||||||
|
login: {
|
||||||
|
(opts: {
|
||||||
|
variables: any
|
||||||
|
config?: P['config']
|
||||||
|
res: ServerResponse
|
||||||
|
}): Promise<LoginResult>
|
||||||
|
|
||||||
|
<T extends { result?: any }, V = any>(opts: {
|
||||||
|
query: string
|
||||||
|
variables: V
|
||||||
|
res: ServerResponse
|
||||||
|
config?: P['config'] | undefined
|
||||||
|
}): Promise<LoginResult<T>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type APIOperations<P extends APIProvider> = {
|
||||||
|
[K in keyof Operations<P>]: (ctx: OperationContext<P>) => Operations<P>[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type APIOperations2<P extends APIProvider> = {
|
||||||
|
[K in keyof APIOperations<P>]: ReturnType<P['operations'][K]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OperationContext<P extends APIProvider> = {
|
||||||
|
commerce: CommerceAPI<P>
|
||||||
|
operations: Operations<P>
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
import { CommerceAPI } from '@framework/api'
|
import { getCommerceApi } from '@framework/api'
|
||||||
|
|
||||||
export default new CommerceAPI()
|
export default getCommerceApi()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user