Add get-all-products operator

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe 2022-04-14 11:33:09 +07:00
parent e3ad683cf2
commit 0065566752
9 changed files with 22936 additions and 21 deletions

View File

@ -0,0 +1,22 @@
{
"overwrite": true,
"schema": "http://localhost:3000/graphql",
"documents": [
{
"./src/api/**/*.ts": {
"noRequire": true
}
}
],
"generates": {
"./schema.d.ts": {
"plugins": ["typescript", "typescript-operations"]
},
"./schema.graphql": {
"plugins": ["schema-ast"]
}
},
"hooks": {
"afterAllFileWrite": ["prettier --write"]
}
}

View File

@ -7,7 +7,8 @@
"build": "taskr build",
"dev": "taskr",
"types": "tsc --emitDeclarationOnly",
"prettier-fix": "prettier --write ."
"prettier-fix": "prettier --write .",
"generate": "graphql-codegen --config codegen.json"
},
"sideEffects": false,
"type": "module",
@ -48,7 +49,8 @@
},
"dependencies": {
"@vercel/commerce": "^0.0.1",
"@vercel/fetch": "^6.1.1"
"@vercel/fetch": "^6.1.1",
"graphql": "^16.3.0"
},
"peerDependencies": {
"next": "^12",
@ -56,6 +58,10 @@
"react-dom": "^17"
},
"devDependencies": {
"@graphql-codegen/cli": "2.6.2",
"@graphql-codegen/schema-ast": "^2.4.1",
"@graphql-codegen/typescript": "2.4.8",
"@graphql-codegen/typescript-operations": "2.3.5",
"@taskr/clear": "^1.1.0",
"@taskr/esnext": "^1.1.0",
"@taskr/watch": "^1.1.0",
@ -68,7 +74,9 @@
"react-dom": "^17.0.2",
"taskr": "^1.1.0",
"taskr-swc": "^0.0.1",
"typescript": "^4.5.4"
"typescript": "^4.5.4",
"@graphql-codegen/typescript-react-apollo": "3.2.11",
"@graphql-codegen/introspection": "2.1.1"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,json}": [

7982
packages/opencommerce/schema.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,41 +2,39 @@ import type {
OperationContext,
OperationOptions,
} from '@vercel/commerce/api/operations'
import { GetAllProductsOperation } from '../../types/product'
import type { GetAllProductsOperation } from '../../types/product'
import {
GetAllProductsQuery,
GetAllProductsQueryVariables,
Product as ShopifyProduct,
CatalogItemProduct,
CatalogItemsQuery,
CatalogItemsQueryVariables,
Product as OpenCommerceProduct,
} from '../../../schema'
import type { ShopifyConfig, Provider } from '..'
import getAllProductsQuery from '../../utils/queries/get-all-products-query'
import { normalizeProduct } from '../../utils'
import catalogItemsQuery from '../queries/product'
import type { OpenCommerceConfig, Provider } from '..'
import { normalizeProduct } from '../../utils/normalize'
export default function getAllProductsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProducts<T extends GetAllProductsOperation>(opts?: {
variables?: T['variables']
config?: Partial<ShopifyConfig>
config?: Partial<OpenCommerceConfig>
preview?: boolean
}): Promise<T['data']>
async function getAllProducts<T extends GetAllProductsOperation>({
query = getAllProductsQuery,
query = catalogItemsQuery,
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<ShopifyConfig>
variables?: CatalogItemsQueryVariables
config?: Partial<OpenCommerceConfig>
preview?: boolean
} = {}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(config)
const { data } = await fetch<
GetAllProductsQuery,
GetAllProductsQueryVariables
>(
const { data } = await fetch<CatalogItemsQuery, CatalogItemsQueryVariables>(
query,
{ variables },
{
@ -49,9 +47,12 @@ export default function getAllProductsOperation({
)
return {
products: data.products.edges.map(({ node }) =>
normalizeProduct(node as ShopifyProduct)
),
products:
data.catalogItems?.edges?.map((item) =>
normalizeProduct(
item?.node ? (item.node as CatalogItemProduct) : null
)
) ?? [],
}
}

View File

@ -0,0 +1,62 @@
const catalogItemsQuery = /* GraphQL */ `
query catalogItems(
$first: ConnectionLimitInt = 250
$sortBy: CatalogItemSortByField = updatedAt
$tagIds: [ID]
$shopIds: [ID]!
$searchQuery: String
) {
catalogItems(
first: $first
sortBy: $sortBy
tagIds: $tagIds
shopIds: $shopIds
searchQuery: $searchQuery
) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
_id
... on CatalogItemProduct {
product {
_id
title
slug
description
vendor
isLowQuantity
isSoldOut
isBackorder
shop {
currency {
code
}
}
pricing {
currency {
code
}
displayPrice
minPrice
maxPrice
}
media {
URLs {
thumbnail
small
medium
large
original
}
}
}
}
}
}
}
}
`
export default catalogItemsQuery

View File

@ -0,0 +1 @@
export * from '@vercel/commerce/types/product'

View File

@ -0,0 +1,207 @@
import type {
Product,
ProductOption,
ProductOptionValues,
ProductVariant,
} from '../types/product'
import {
CatalogItemProduct,
CatalogProduct,
CatalogProductVariant,
ImageInfo,
} from '../../schema'
const normalizeProductImages = (images: ImageInfo[], name: string) =>
images.map((image) => ({
url: image?.URLs?.original || image?.URLs?.medium || '',
alt: name,
}))
export function normalizeProduct(
productNode: CatalogItemProduct | null | undefined
): Product {
const product = productNode?.product
if (!product) {
return <Product>{}
}
const {
_id,
productId,
title,
description,
slug,
sku,
media,
pricing,
variants,
} = <CatalogProduct>product
return {
id: productId ?? _id,
name: title ?? '',
description: description ?? '',
slug: slug?.replace(/^\/+|\/+$/g, '') ?? '',
path: slug ?? '',
sku: sku ?? '',
images: media?.length
? normalizeProductImages(<ImageInfo[]>media, title ?? '')
: [],
vendor: product.vendor ?? undefined,
price: {
value: pricing[0]?.minPrice ?? 0,
currencyCode: pricing[0]?.currency.code,
},
variants: !!variants
? normalizeProductVariants(<CatalogProductVariant[]>variants)
: [],
options: !!variants
? groupProductOptionsByAttributeLabel(<CatalogProductVariant[]>variants)
: [],
}
}
function groupProductOptionsByAttributeLabel(
variants: CatalogProductVariant[]
): ProductOption[] {
return variants.reduce(
(
groupedOptions: ProductOption[],
currentVariant: CatalogProductVariant
) => {
groupedOptions = mergeVariantOptionsWithExistingOptions(
groupedOptions,
currentVariant
)
if (variantHasOptions(currentVariant)) {
;(<CatalogProductVariant[]>currentVariant.options).forEach(
(variantOption) => {
groupedOptions = mergeVariantOptionsWithExistingOptions(
groupedOptions,
variantOption
)
}
)
}
return groupedOptions
},
[]
)
}
function mergeVariantOptionsWithExistingOptions(
groupedOptions: ProductOption[],
currentVariant: CatalogProductVariant
): ProductOption[] {
const matchingOptionIndex = findCurrentVariantOptionsInGroupedOptions(
groupedOptions,
currentVariant
)
return matchingOptionIndex !== -1
? mergeWithExistingOptions(
groupedOptions,
currentVariant,
matchingOptionIndex
)
: addNewProductOption(groupedOptions, currentVariant)
}
function addNewProductOption(
groupedOptions: ProductOption[],
currentVariant: CatalogProductVariant
) {
return [...groupedOptions, normalizeProductOption(currentVariant)]
}
function findCurrentVariantOptionsInGroupedOptions(
groupedOptions: ProductOption[],
currentVariant: CatalogProductVariant
): number {
return groupedOptions.findIndex(
(option) =>
option.displayName.toLowerCase() ===
currentVariant.attributeLabel.toLowerCase()
)
}
function mergeWithExistingOptions(
groupedOptions: ProductOption[],
currentVariant: CatalogProductVariant,
matchingOptionIndex: number
) {
const currentVariantOption = normalizeProductOption(currentVariant)
groupedOptions[matchingOptionIndex].values = [
...groupedOptions[matchingOptionIndex].values,
...currentVariantOption.values,
]
return groupedOptions
}
const normalizeProductVariants = (
variants: Array<CatalogProductVariant>
): ProductVariant[] => {
return variants.reduce(
(productVariants: ProductVariant[], variant: CatalogProductVariant) => {
if (variantHasOptions(variant)) {
productVariants.push(...flatVariantOptions(variant))
return productVariants
}
const { sku, title, pricing = [], variantId } = variant ?? {}
const variantPrice = pricing[0]?.price ?? pricing[0]?.minPrice ?? 0
productVariants.push(<ProductVariant>{
id: variantId ?? '',
name: title,
sku: sku ?? variantId,
price: variantPrice,
listPrice: pricing[0]?.compareAtPrice?.amount ?? variantPrice,
requiresShipping: true,
options: [normalizeProductOption(variant)],
})
return productVariants
},
[]
)
}
const normalizeProductOption = (variant: CatalogProductVariant) => {
const option = <ProductOption>{
__typename: 'MultipleChoiceOption',
id: variant._id,
displayName: variant.attributeLabel,
values: variant.optionTitle ? [{ label: variant.optionTitle }] : [],
}
option.values = option.values.map((value) =>
colorizeProductOptionValue(value, option.displayName)
)
return option
}
function flatVariantOptions(variant: CatalogProductVariant): ProductVariant[] {
const variantOptions = <CatalogProductVariant[]>variant.options
return normalizeProductVariants(variantOptions).map((variantOption) => {
variantOption.options.push(normalizeProductOption(variant))
return variantOption
})
}
function variantHasOptions(variant: CatalogProductVariant) {
return !!variant.options && variant.options.length != 0
}
function colorizeProductOptionValue(
value: ProductOptionValues,
displayName: string
): ProductOptionValues {
if (displayName.toLowerCase() === 'color') {
value.hexColors = [value.label]
}
return value
}

137
yarn.lock
View File

@ -525,6 +525,52 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@graphql-codegen/cli@2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-2.6.2.tgz#a9aa4656141ee0998cae8c7ad7d0bf9ca8e0c9ae"
integrity sha512-UO75msoVgvLEvfjCezM09cQQqp32+mR8Ma1ACsBpr7nroFvHbgcu2ulx1cMovg4sxDBCsvd9Eq/xOOMpARUxtw==
dependencies:
"@graphql-codegen/core" "2.5.1"
"@graphql-codegen/plugin-helpers" "^2.4.1"
"@graphql-tools/apollo-engine-loader" "^7.0.5"
"@graphql-tools/code-file-loader" "^7.0.6"
"@graphql-tools/git-loader" "^7.0.5"
"@graphql-tools/github-loader" "^7.0.5"
"@graphql-tools/graphql-file-loader" "^7.0.5"
"@graphql-tools/json-file-loader" "^7.1.2"
"@graphql-tools/load" "^7.3.0"
"@graphql-tools/prisma-loader" "^7.0.6"
"@graphql-tools/url-loader" "^7.0.11"
"@graphql-tools/utils" "^8.1.1"
ansi-escapes "^4.3.1"
chalk "^4.1.0"
change-case-all "1.0.14"
chokidar "^3.5.2"
common-tags "^1.8.0"
cosmiconfig "^7.0.0"
debounce "^1.2.0"
dependency-graph "^0.11.0"
detect-indent "^6.0.0"
glob "^7.1.6"
globby "^11.0.4"
graphql-config "^4.1.0"
inquirer "^8.0.0"
is-glob "^4.0.1"
json-to-pretty-yaml "^1.2.2"
latest-version "5.1.0"
listr "^0.14.3"
listr-update-renderer "^0.5.0"
log-symbols "^4.0.0"
minimatch "^4.0.0"
mkdirp "^1.0.4"
string-env-interpolation "^1.0.1"
ts-log "^2.2.3"
tslib "~2.3.0"
valid-url "^1.0.9"
wrap-ansi "^7.0.0"
yaml "^1.10.0"
yargs "^17.0.0"
"@graphql-codegen/cli@^2.3.1":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-2.4.0.tgz#7df3ee2bdd5b88a5904ee6f52eafeb370ef70e51"
@ -581,6 +627,24 @@
"@graphql-tools/utils" "^8.1.1"
tslib "~2.3.0"
"@graphql-codegen/core@2.5.1":
version "2.5.1"
resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-2.5.1.tgz#e3d50d3449b8c58b74ea08e97faf656a1b7fc8a1"
integrity sha512-alctBVl2hMnBXDLwkgmnFPrZVIiBDsWJSmxJcM4GKg1PB23+xuov35GE47YAyAhQItE1B1fbYnbb1PtGiDZ4LA==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.1"
"@graphql-tools/schema" "^8.1.2"
"@graphql-tools/utils" "^8.1.1"
tslib "~2.3.0"
"@graphql-codegen/introspection@2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@graphql-codegen/introspection/-/introspection-2.1.1.tgz#5f3aac47ef46ed817baf969e78dd2dd6d307b18a"
integrity sha512-O9zsy0IoFYDo37pBVF4pSvRMDx/AKdgOxyko4R/O+0DHEw9Nya/pQ3dbn+LDLj2n6X+xOXUBUfFvqhODTqU28w==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.3.2"
tslib "~2.3.0"
"@graphql-codegen/plugin-helpers@^2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.3.2.tgz#3f9ba625791901d19be733db1dfc9a3dbd0dac44"
@ -593,6 +657,18 @@
lodash "~4.17.0"
tslib "~2.3.0"
"@graphql-codegen/plugin-helpers@^2.4.0", "@graphql-codegen/plugin-helpers@^2.4.1":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.4.2.tgz#e4f6b74dddcf8a9974fef5ce48562ae0980f9fed"
integrity sha512-LJNvwAPv/sKtI3RnRDm+nPD+JeOfOuSOS4FFIpQCMUCyMnFcchV/CPTTv7tT12fLUpEg6XjuFfDBvOwndti30Q==
dependencies:
"@graphql-tools/utils" "^8.5.2"
change-case-all "1.0.14"
common-tags "1.8.2"
import-from "4.0.0"
lodash "~4.17.0"
tslib "~2.3.0"
"@graphql-codegen/schema-ast@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@graphql-codegen/schema-ast/-/schema-ast-2.4.1.tgz#ad742b53e32f7a2fbff8ea8a91ba7e617e6ef236"
@ -602,6 +678,17 @@
"@graphql-tools/utils" "^8.1.1"
tslib "~2.3.0"
"@graphql-codegen/typescript-operations@2.3.5":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-2.3.5.tgz#1e77b96da0949f9e0ecd6054eb28b582936e35e8"
integrity sha512-GCZQW+O+cIF62ioPkQMoSJGzjJhtr7ttZGJOAoN/Q/oolG8ph9jNFePKO67tSQ/POAs5HLqfat4kAlCK8OPV3Q==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-codegen/typescript" "^2.4.8"
"@graphql-codegen/visitor-plugin-common" "2.7.4"
auto-bind "~4.0.0"
tslib "~2.3.0"
"@graphql-codegen/typescript-operations@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-2.2.2.tgz#028f2850ae85021d8932b5670e5feca7b0d5c767"
@ -613,6 +700,28 @@
auto-bind "~4.0.0"
tslib "~2.3.0"
"@graphql-codegen/typescript-react-apollo@3.2.11":
version "3.2.11"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-react-apollo/-/typescript-react-apollo-3.2.11.tgz#dc13abc1ec24aa78f7f0774c1f52da5d982dd1fc"
integrity sha512-Bfo7/OprnWk/srhA/3I0cKySg/SyVPX3ZoxzTk6ChYVBsy69jKDkdPWwlmE7Fjfv7+5G+xXb99OoqUUgBLma3w==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-codegen/visitor-plugin-common" "2.7.4"
auto-bind "~4.0.0"
change-case-all "1.0.14"
tslib "~2.3.0"
"@graphql-codegen/typescript@2.4.8", "@graphql-codegen/typescript@^2.4.8":
version "2.4.8"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-2.4.8.tgz#e8110baba9713cde72d57a5c95aa27400363ed9a"
integrity sha512-tVsHIkuyenBany7c5IMU1yi4S1er2hgyXJGNY7PcyhpJMx0eChmbqz1VTiZxDEwi8mDBS2mn3TaSJMh6xuJM5g==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-codegen/schema-ast" "^2.4.1"
"@graphql-codegen/visitor-plugin-common" "2.7.4"
auto-bind "~4.0.0"
tslib "~2.3.0"
"@graphql-codegen/typescript@^2.4.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-2.4.2.tgz#a239d5fd8f11140d5d4c81cfae7ff02054b724dc"
@ -640,6 +749,22 @@
parse-filepath "^1.0.2"
tslib "~2.3.0"
"@graphql-codegen/visitor-plugin-common@2.7.4":
version "2.7.4"
resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.7.4.tgz#fbc8aec9df0035b8f29fa64a6356cbb02062da5d"
integrity sha512-aaDoEudDD+B7DK/UwDSL2Fzej75N9hNJ3N8FQuTIeDyw6FNGWUxmkjVBLQGlzfnYfK8IYkdfYkrPn3Skq0pVxA==
dependencies:
"@graphql-codegen/plugin-helpers" "^2.4.0"
"@graphql-tools/optimize" "^1.0.1"
"@graphql-tools/relay-operation-optimizer" "^6.3.7"
"@graphql-tools/utils" "^8.3.0"
auto-bind "~4.0.0"
change-case-all "1.0.14"
dependency-graph "^0.11.0"
graphql-tag "^2.11.0"
parse-filepath "^1.0.2"
tslib "~2.3.0"
"@graphql-tools/apollo-engine-loader@^7.0.5":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-7.2.2.tgz#1463f5d2d95b5ca2b602b35b4922b38935013860"
@ -3741,6 +3866,11 @@ graphql-ws@^5.4.1:
resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.5.5.tgz#f375486d3f196e2a2527b503644693ae3a8670a9"
integrity sha512-hvyIS71vs4Tu/yUYHPvGXsTgo0t3arU820+lT5VjZS2go0ewp2LqyCgxEN56CzOG7Iys52eRhHBiD1gGRdiQtw==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
gzip-size@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
@ -4962,6 +5092,13 @@ minimatch@3.0.4, minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimatch@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"
integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==
dependencies:
brace-expansion "^1.1.7"
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"