Fetch product images, standardize API fetch using Spree SDK

This commit is contained in:
tniezg 2021-07-27 17:42:05 +02:00
parent c90fa7abf2
commit 14e7a4fe08
17 changed files with 486 additions and 216 deletions

View File

@ -4,10 +4,7 @@ COMMERCE_PROVIDER=spree
{# public (available in the web browser) #} {# public (available in the web browser) #}
NEXT_PUBLIC_SPREE_API_HOST=http://localhost:3000 NEXT_PUBLIC_SPREE_API_HOST=http://localhost:3000
{# private #}
NEXT_PUBLIC_SPREE_DEFAULT_LOCALE=en-us NEXT_PUBLIC_SPREE_DEFAULT_LOCALE=en-us
NEXT_PUBLIC_SPREE_CART_COOKIE_NAME=spree_cart NEXT_PUBLIC_SPREE_CART_COOKIE_NAME=spree_cart
NEXT_PUBLIC_SPREE_IMAGE_HOST=http://localhost:3000
{# # TODO: #} NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN=localhost
{# # COMMERCE_IMAGE_HOST #}

View File

@ -1,4 +1,4 @@
import type { APIProvider, CommerceAPI, CommerceAPIConfig } from '@commerce/api' import type { APIProvider, CommerceAPIConfig } from '@commerce/api'
import { getCommerceApi as commerceApi } from '@commerce/api' import { getCommerceApi as commerceApi } from '@commerce/api'
import createApiFetch from './utils/create-api-fetch' import createApiFetch from './utils/create-api-fetch'

View File

@ -1,78 +1,142 @@
import { Product } from '@commerce/types/product' import type {
import { GetAllProductsOperation } from '@commerce/types/product' Product,
ProductOption,
ProductOptionValues,
ProductPrice,
ProductVariant,
} from '@commerce/types/product'
import type { GetAllProductsOperation } from '@commerce/types/product'
import type { OperationContext } from '@commerce/api/operations' import type { OperationContext } from '@commerce/api/operations'
import type { LocalConfig, Provider, SpreeApiProvider } from '../index'
import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product' import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
// import data from '../../../local/data.json' import { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import type { SpreeApiConfig, SpreeApiProvider } from '../index'
import type { SpreeSdkVariables } from 'framework/spree/types'
import { findIncluded, findIncludedOfType } from 'framework/spree/utils/jsonApi'
import getMediaGallery from 'framework/spree/utils/getMediaGallery'
import createGetAbsoluteImageUrl from 'framework/spree/utils/createGetAbsoluteImageUrl'
import { requireConfigValue } from 'framework/spree/isomorphicConfig'
import SpreeResponseContentError from 'framework/spree/errors/SpreeResponseContentError'
import expandOptions from 'framework/spree/utils/expandOptions'
export default function getAllProductsOperation({ export default function getAllProductsOperation({
commerce, commerce,
}: OperationContext<SpreeApiProvider>) { }: OperationContext<SpreeApiProvider>) {
async function getAllProducts<T extends GetAllProductsOperation>({ async function getAllProducts<T extends GetAllProductsOperation>({
query = 'products.list', variables: getAllProductsVariables = {},
variables = { first: 10 },
config: userConfig, config: userConfig,
}: { }: {
query?: string
variables?: T['variables'] variables?: T['variables']
config?: Partial<LocalConfig> config?: Partial<SpreeApiConfig>
} = {}): Promise<{ products: Product[] | any[] }> { } = {}): Promise<{ products: Product[] | any[] }> {
console.info(
'getAllProducts called. Configuration: ',
'getAllProductsVariables: ',
getAllProductsVariables,
'config: ',
userConfig
)
const first = getAllProductsVariables.first
const variables: SpreeSdkVariables = {
methodPath: 'products.list',
arguments: [
{
include: 'variants,images,option_types,variants.option_values',
per_page: first,
},
],
}
const config = commerce.getConfig(userConfig) const config = commerce.getConfig(userConfig)
const { fetch: apiFetch /*, locale*/ } = config const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
const first = variables.first // How many products to fetch.
console.log( const { data: spreeSuccessResponse } = await apiFetch<IProducts>(
'sdfuasdufahsdf variables = ', '__UNUSED__',
variables,
'query = ',
query,
'config = ',
config
)
console.log('sdfasdg')
const { data } = await apiFetch<IProducts>(
query,
{ variables } { variables }
// {
// ...(locale && {}),
// }
) )
console.log('asuidfhasdf', data) const normalizedProducts: Product[] = spreeSuccessResponse.data.map(
(spreeProduct) => {
const spreeImageRecords = findIncludedOfType(
spreeSuccessResponse,
spreeProduct,
'images'
)
// return { const images = getMediaGallery(
// products: data.products.edges.map(({ node }) => spreeImageRecords,
// normalizeProduct(node as ShopifyProduct) createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost'))
// ), )
// }
const price: ProductPrice = {
value: parseFloat(spreeProduct.attributes.price),
currencyCode: spreeProduct.attributes.currency,
}
// TODO: Add sku to product object equal to master SKU from Spree.
// Currently, the Spree API doesn't return it.
const hasNonMasterVariants =
(spreeProduct.relationships.variants.data as RelationType[]).length >
0
let variants: ProductVariant[]
let options: ProductOption[] = []
if (hasNonMasterVariants) {
const spreeVariantRecords = findIncludedOfType(
spreeSuccessResponse,
spreeProduct,
'variants'
)
variants = spreeVariantRecords.map((spreeVariantRecord) => {
const spreeOptionValues = findIncludedOfType(
spreeSuccessResponse,
spreeVariantRecord,
'option_values'
)
let variantOptions: ProductOption[] = []
// Only include options which are used by variants.
spreeOptionValues.forEach((spreeOptionValue) => {
variantOptions = expandOptions(
spreeSuccessResponse,
spreeOptionValue,
variantOptions
)
options = expandOptions(
spreeSuccessResponse,
spreeOptionValue,
options
)
})
return {
id: spreeVariantRecord.id,
options: variantOptions,
}
})
} else {
variants = []
}
const normalizedProducts: Product[] = data.data.map((spreeProduct) => {
return { return {
id: spreeProduct.id, id: spreeProduct.id,
name: spreeProduct.attributes.name, name: spreeProduct.attributes.name,
description: spreeProduct.attributes.description, description: spreeProduct.attributes.description,
images: [], images,
variants: [], variants,
options: [], options,
price: { price,
value: 10,
currencyCode: 'USD',
retailPrice: 8,
salePrice: 7,
listPrice: 6,
extendedSalePrice: 2,
extendedListPrice: 1,
},
} }
}) }
)
return { return { products: normalizedProducts }
// products: data.products,
// TODO: Return Spree products.
products: normalizedProducts,
}
} }
return getAllProducts return getAllProducts

View File

@ -1,7 +1,3 @@
// import { FetcherError } from '@commerce/utils/errors'
// import type { GraphQLFetcher } from '@commerce/api'
// import type { BigcommerceConfig } from '../index'
import { GraphQLFetcher, GraphQLFetcherResult } from '@commerce/api' import { GraphQLFetcher, GraphQLFetcherResult } from '@commerce/api'
import { SpreeApiConfig } from '..' import { SpreeApiConfig } from '..'
import { errors, makeClient } from '@spree/storefront-api-v2-sdk' import { errors, makeClient } from '@spree/storefront-api-v2-sdk'
@ -9,81 +5,54 @@ import { requireConfigValue } from 'framework/spree/isomorphicConfig'
import convertSpreeErrorToGraphQlError from 'framework/spree/utils/convertSpreeErrorToGraphQlError' import convertSpreeErrorToGraphQlError from 'framework/spree/utils/convertSpreeErrorToGraphQlError'
import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse' import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
import type { import type {
JsonApiDocument,
JsonApiListResponse, JsonApiListResponse,
JsonApiResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi' } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
// import fetch from './fetch' import getSpreeSdkMethodFromEndpointPath from 'framework/spree/utils/getSpreeSdkMethodFromEndpointPath'
import { SpreeSdkVariables } from 'framework/spree/types'
// const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher = import SpreeSdkMethodFromEndpointPathError from 'framework/spree/errors/SpreeSdkMethodFromEndpointPathError'
// (getConfig) =>
// async (query: string, { variables, preview } = {}, fetchOptions) => {
// // log.warn(query)
// const config = getConfig()
// const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
// ...fetchOptions,
// method: 'POST',
// headers: {
// Authorization: `Bearer ${config.apiToken}`,
// ...fetchOptions?.headers,
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// query,
// variables,
// }),
// })
// const json = await res.json()
// if (json.errors) {
// throw new FetcherError({
// errors: json.errors ?? [{ message: 'Failed to fetch Bigcommerce API' }],
// status: res.status,
// })
// }
// return { data: json.data, res }
// }
// export default fetchGraphqlApi
const createApiFetch: ( const createApiFetch: (
getConfig: () => SpreeApiConfig getConfig: () => SpreeApiConfig
) => GraphQLFetcher< ) => GraphQLFetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = (
GraphQLFetcherResult<JsonApiDocument | JsonApiListResponse> getConfig
> = (getConfig) => { ) => {
const client = makeClient({ host: requireConfigValue('spreeApiHost') }) const client = makeClient({ host: requireConfigValue('spreeApiHost') })
// FIXME: Allow Spree SDK to use fetch instead of axios. return async (url, queryData = {}, fetchOptions = {}) => {
return async (query, queryData = {}, fetchOptions = {}) => { console.log(
const url = query 'apiFetch called. query = ',
console.log('ydsfgasgdfagsdf', url) url,
'url = ',
queryData,
'fetchOptions = ',
fetchOptions
)
const { variables } = queryData const { variables } = queryData
let prev = null // FIXME:
const clientEndpointMethod = url
.split('.')
.reduce((clientNode: any, pathPart) => {
prev = clientNode
//FIXME: use actual type instead of any.
// TODO: Fix clientNode type
return clientNode[pathPart]
}, client)
.bind(prev)
console.log('aisdfuiuashdf', clientEndpointMethod) if (!variables) {
throw new SpreeSdkMethodFromEndpointPathError(
`Required SpreeSdkVariables not provided.`
)
}
const storeResponse: ResultResponse<JsonApiDocument | JsonApiListResponse> = const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
await clientEndpointMethod() // FIXME: Not the best to use variables here as it's type is any. await getSpreeSdkMethodFromEndpointPath(
// await clientEndpointMethod(...variables.args) // FIXME: Not the best to use variables here as it's type is any. client,
variables.methodPath
console.log('87868767868', storeResponse) )(...variables.arguments)
if (storeResponse.success()) { if (storeResponse.success()) {
return { return {
data: storeResponse.success(), data: storeResponse.success(),
res: storeResponse as any, //FIXME: MUST BE FETCH RESPONSE res: storeResponse as any, //FIXME: MUST BE fetch() RESPONSE instead of axios.
} }
} }
// FIXME: Allow Spree SDK to use fetch instead of axios
// (https://github.com/spree/spree-storefront-api-v2-js-sdk/issues/189)
const storeResponseError = storeResponse.fail() const storeResponseError = storeResponse.fail()
if (storeResponseError instanceof errors.SpreeError) { if (storeResponseError instanceof errors.SpreeError) {
@ -91,64 +60,7 @@ const createApiFetch: (
} }
throw storeResponseError throw storeResponseError
// throw getError(
// [
// {
// message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`,
// },
// ],
// 500
// )
// console.log('jsdkfhjasdf', getConfig())
// // await
// return {
// data: [],
// res: ,
// }
} }
} }
export default createApiFetch export default createApiFetch
// LOCAL
// fetch<Data = any, Variables = any>(
// query: string,
// queryData?: CommerceAPIFetchOptions<Variables>,
// fetchOptions?: RequestInit
// ): Promise<GraphQLFetcherResult<Data>>
// import { FetcherError } from '@commerce/utils/errors'
// import type { GraphQLFetcher } from '@commerce/api'
// import type { LocalConfig } from '../index'
// import fetch from './fetch'
// const fetchGraphqlApi: (getConfig: () => LocalConfig) => GraphQLFetcher =
// (getConfig) =>
// async (query: string, { variables, preview } = {}, fetchOptions) => {
// const config = getConfig()
// const res = await fetch(config.commerceUrl, {
// ...fetchOptions,
// method: 'POST',
// headers: {
// ...fetchOptions?.headers,
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// query,
// variables,
// }),
// })
// const json = await res.json()
// if (json.errors) {
// throw new FetcherError({
// errors: json.errors ?? [{ message: 'Failed to fetch for API' }],
// status: res.status,
// })
// }
// return { data: json.data, res }
// }
// export default fetchGraphqlApi

View File

@ -0,0 +1 @@
export default class SpreeResponseContentError extends Error {}

View File

@ -0,0 +1 @@
export default class SpreeSdkMethodFromEndpointPathError extends Error {}

View File

@ -3,11 +3,12 @@ import convertSpreeErrorToGraphQlError from './utils/convertSpreeErrorToGraphQlE
import { makeClient } from '@spree/storefront-api-v2-sdk' import { makeClient } from '@spree/storefront-api-v2-sdk'
import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse' import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
import type { import type {
JsonApiDocument,
JsonApiListResponse, JsonApiListResponse,
JsonApiResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi' } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import { errors } from '@spree/storefront-api-v2-sdk' import { errors } from '@spree/storefront-api-v2-sdk'
import { requireConfigValue } from './isomorphicConfig' import { requireConfigValue } from './isomorphicConfig'
import getSpreeSdkMethodFromEndpointPath from './utils/getSpreeSdkMethodFromEndpointPath'
// import { handleFetchResponse } from './utils' // import { handleFetchResponse } from './utils'
const client = makeClient({ host: requireConfigValue('spreeApiHost') }) const client = makeClient({ host: requireConfigValue('spreeApiHost') })
@ -31,32 +32,10 @@ const fetcher: Fetcher = async (requestOptions) => {
`Fetching products using options: ${JSON.stringify(requestOptions)}.` `Fetching products using options: ${JSON.stringify(requestOptions)}.`
) )
// const storeResponse = await fetch(url, {
// method,
// body: JSON.stringify({ query, variables: vars }),
// headers: {
// 'X-Shopify-Storefront-Access-Token': API_TOKEN,
// 'Content-Type': 'application/json', TODO: Probably not needed. Check!
// },
// })
// const storeResponse.json()
// if (storeResponse.ok) {
// return
// }
// TODO: Not best to use url for finding the method, but should be good enough for now. // TODO: Not best to use url for finding the method, but should be good enough for now.
const clientEndpointMethod = url const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
.split('.') await getSpreeSdkMethodFromEndpointPath(client, url)(...variables.args) // TODO: Not the best to use variables here as it's type is any.
.reduce((clientNode: any, pathPart) => {
// TODO: Fix clientNode type
return clientNode[pathPart]
}, client)
const storeResponse: ResultResponse<JsonApiDocument | JsonApiListResponse> =
await clientEndpointMethod(...variables.args) // TODO: Not the best to use variables here as it's type is any.
if (storeResponse.success()) { if (storeResponse.success()) {
return storeResponse.success() return storeResponse.success()

View File

@ -5,12 +5,13 @@ const isomorphicConfig = {
spreeApiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST, spreeApiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST,
defaultLocale: process.env.NEXT_PUBLIC_SPREE_DEFAULT_LOCALE, defaultLocale: process.env.NEXT_PUBLIC_SPREE_DEFAULT_LOCALE,
cartCookieName: process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_NAME, cartCookieName: process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_NAME,
spreeImageHost: process.env.NEXT_PUBLIC_SPREE_IMAGE_HOST,
} }
export default forceIsomorphicConfigValues( export default forceIsomorphicConfigValues(
isomorphicConfig, isomorphicConfig,
['defaultLocale', 'cartCookieName'], [],
['spreeApiHost'] ['spreeApiHost', 'defaultLocale', 'cartCookieName', 'spreeImageHost']
) )
type IsomorphicConfig = typeof isomorphicConfig type IsomorphicConfig = typeof isomorphicConfig

View File

@ -2,7 +2,7 @@ const commerce = require('./commerce.config.json')
module.exports = { module.exports = {
commerce, commerce,
// images: { images: {
// domains: [process.env.COMMERCE_IMAGE_HOST], domains: [process.env.NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN],
// }, },
} }

View File

@ -1,5 +1,35 @@
import type {
JsonApiDocument,
JsonApiListResponse,
JsonApiSingleResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
export type UnknownObjectValues = Record<string, unknown> export type UnknownObjectValues = Record<string, unknown>
export type NonUndefined<T> = T extends undefined ? never : T export type NonUndefined<T> = T extends undefined ? never : T
export type ValueOf<T> = T[keyof T] export type ValueOf<T> = T[keyof T]
export type SpreeSdkMethodReturnType = Promise<
ResultResponse<JsonApiSingleResponse | JsonApiListResponse>
>
export type SpreeSdkMethod = (...args: any[]) => SpreeSdkMethodReturnType
export type SpreeSdkVariables = {
methodPath: string
arguments: any[]
}
export interface ImageStyle {
url: string
width: string
height: string
}
export interface SpreeProductImage extends JsonApiDocument {
attributes: {
styles: ImageStyle[]
}
}

View File

@ -0,0 +1,20 @@
import { SpreeProductImage } from '../types'
import getImageUrl from './getImageUrl'
const createGetAbsoluteImageUrl =
(host: string) =>
(
image: SpreeProductImage,
minWidth: number,
minHeight: number
): string | null => {
const url = getImageUrl(image, minWidth, minHeight)
if (url === null) {
return null
}
return `${host}${url}`
}
export default createGetAbsoluteImageUrl

View File

@ -0,0 +1,89 @@
import type {
ProductOption,
ProductOptionValues,
} from '@commerce/types/product'
import type { JsonApiDocument } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import SpreeResponseContentError from '../errors/SpreeResponseContentError'
import { findIncluded } from './jsonApi'
const isColorProductOption = (productOption: ProductOption) =>
productOption.displayName === 'Color'
const expandOptions = (
spreeSuccessResponse: IProducts,
spreeOptionValue: JsonApiDocument,
accumulatedOptions: ProductOption[]
): ProductOption[] => {
const spreeOptionTypeIdentifier = spreeOptionValue.relationships.option_type
.data as RelationType
const existingOptionIndex = accumulatedOptions.findIndex(
(option) => option.id == spreeOptionTypeIdentifier.id
)
let option: ProductOption
if (existingOptionIndex === -1) {
const spreeOptionType = findIncluded(
spreeSuccessResponse,
spreeOptionTypeIdentifier.type,
spreeOptionTypeIdentifier.id
)
if (!spreeOptionType) {
throw new SpreeResponseContentError(
`Option type with id ${spreeOptionTypeIdentifier.id} not found.`
)
}
option = {
id: spreeOptionType.id,
displayName: spreeOptionType.attributes.presentation,
values: [],
}
} else {
const existingOption = accumulatedOptions[existingOptionIndex]
option = existingOption
}
let optionValue: ProductOptionValues
const label = isColorProductOption(option)
? spreeOptionValue.attributes.name
: spreeOptionValue.attributes.presentation
const productOptionValueExists = option.values.some(
(optionValue: ProductOptionValues) => optionValue.label === label
)
if (!productOptionValueExists) {
if (isColorProductOption(option)) {
optionValue = {
label,
hexColors: [spreeOptionValue.attributes.presentation],
}
} else {
optionValue = {
label,
}
}
const expandedOptionValues = [...option.values, optionValue]
const expandedOptions = [...accumulatedOptions]
expandedOptions[existingOptionIndex] = {
...option,
values: expandedOptionValues,
}
return expandedOptions
}
return accumulatedOptions
}
export default expandOptions

View File

@ -4,7 +4,7 @@ import isServer from './isServer'
const generateMisconfigurationErrorMessage = ( const generateMisconfigurationErrorMessage = (
keys: Array<string | number | symbol> keys: Array<string | number | symbol>
) => `${keys.join(', ')} must have values before running the Framework.` ) => `${keys.join(', ')} must have a value before running the Framework.`
const forceIsomorphicConfigValues = < const forceIsomorphicConfigValues = <
X extends keyof T, X extends keyof T,

View File

@ -0,0 +1,44 @@
// Based on https://github.com/spark-solutions/spree2vuestorefront/blob/d88d85ae1bcd2ec99b13b81cd2e3c25600a0216e/src/utils/index.ts
import type { ImageStyle, SpreeProductImage } from '../types'
const getImageUrl = (
image: SpreeProductImage,
minWidth: number,
_: number
): string | null => {
// every image is still resized in vue-storefront-api, no matter what getImageUrl returns
if (image) {
const {
attributes: { styles },
} = image
const bestStyleIndex = styles.reduce(
(bSIndex: number | null, style: ImageStyle, styleIndex: number) => {
// assuming all images are the same dimensions, just scaled
if (bSIndex === null) {
return 0
}
const bestStyle = styles[bSIndex]
const widthDiff = +bestStyle.width - minWidth
const minWidthDiff = +style.width - minWidth
if (widthDiff < 0 && minWidthDiff > 0) {
return styleIndex
}
if (widthDiff > 0 && minWidthDiff < 0) {
return bSIndex
}
return Math.abs(widthDiff) < Math.abs(minWidthDiff)
? bSIndex
: styleIndex
},
null
)
if (bestStyleIndex !== null) {
return styles[bestStyleIndex].url
}
}
return null
}
export default getImageUrl

View File

@ -0,0 +1,30 @@
// Based on https://github.com/spark-solutions/spree2vuestorefront/blob/d88d85ae1bcd2ec99b13b81cd2e3c25600a0216e/src/utils/index.ts
import type { ProductImage } from '@commerce/types/product'
import type { SpreeProductImage } from '../types'
const getMediaGallery = (
images: SpreeProductImage[],
getImageUrl: (
image: SpreeProductImage,
minWidth: number,
minHeight: number
) => string | null
) => {
return images.reduce<ProductImage[]>((productImages, _, imageIndex) => {
const imageUrl = getImageUrl(images[imageIndex], 9001, 9001)
if (imageUrl) {
return [
...productImages,
{
url: imageUrl,
},
]
}
return productImages
}, [])
}
export default getMediaGallery

View File

@ -0,0 +1,56 @@
import type { Client } from '@spree/storefront-api-v2-sdk'
import SpreeSdkMethodFromEndpointPathError from '../errors/SpreeSdkMethodFromEndpointPathError'
import { SpreeSdkMethod } from '../types'
const getSpreeSdkMethodFromEndpointPath = <
ExactSpreeSdkClientType extends Client
>(
client: ExactSpreeSdkClientType,
path: string
) => {
const pathParts = path.split('.')
const reachedPath: string[] = []
let node = <Record<string, unknown>>client
console.log(`Looking for ${path} in Spree Sdk.`)
while (reachedPath.length < pathParts.length - 1) {
const checkedPathPart = pathParts[reachedPath.length]
const checkedNode = node[checkedPathPart]
console.log(`Checking part ${checkedPathPart}.`)
if (typeof checkedNode !== 'object') {
throw new SpreeSdkMethodFromEndpointPathError(
`Couldn't reach ${path}. Farthest path reached was: ${reachedPath.join(
'.'
)}.`
)
}
if (checkedNode === null) {
throw new SpreeSdkMethodFromEndpointPathError(
`Path ${path} doesn't exist.`
)
}
node = <Record<string, unknown>>checkedNode
reachedPath.push(checkedPathPart)
}
if (
reachedPath.length !== pathParts.length - 1 ||
typeof node[pathParts[reachedPath.length]] !== 'function'
) {
throw new SpreeSdkMethodFromEndpointPathError(
`Couldn't reach ${path}. Farthest path reached was: ${reachedPath.join(
'.'
)}.`
)
}
return (...args: any[]) =>
(node[pathParts[reachedPath.length]] as SpreeSdkMethod)(...args)
}
export default getSpreeSdkMethodFromEndpointPath

View File

@ -0,0 +1,46 @@
// Based on https://github.com/spark-solutions/spree2vuestorefront
import type {
JsonApiResponse,
JsonApiDocument,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
export const findIncluded = <T extends JsonApiDocument>(
response: JsonApiResponse,
objectType: string,
objectId: string
): T | null => {
if (!response.included) {
return null
}
return (
(response.included.find(
(includedObject) =>
includedObject.type === objectType && includedObject.id === objectId
) as T) || null
)
}
export const findIncludedOfType = <T extends JsonApiDocument>(
response: JsonApiResponse,
singlePrimaryRecord: JsonApiDocument,
objectRelationshipType: string
): T[] => {
if (!response.included) {
return []
}
const typeRelationships =
singlePrimaryRecord.relationships[objectRelationshipType]
if (!typeRelationships) {
return []
}
return typeRelationships.data
.map((typeObject: JsonApiDocument) =>
findIncluded(response, typeObject.type, typeObject.id)
)
.filter((typeRecord: JsonApiDocument | null) => !!typeRecord)
}