@graphcommerce/algolia-products 9.0.0-canary.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/Config.graphqls +54 -0
  3. package/README.md +79 -0
  4. package/algolia-spec.yaml +4418 -0
  5. package/graphql/CustomerGroupId.graphql +3 -0
  6. package/graphql/GetAlgoliaSettings.graphql +122 -0
  7. package/graphql/ProductListItems_Algolia.graphql +3 -0
  8. package/hooks/useAlgoliaIndexName.ts +5 -0
  9. package/hooks/useAlgoliaQueryContext.ts +11 -0
  10. package/index.ts +8 -0
  11. package/link/customerGroupIdLink.ts +20 -0
  12. package/mesh/algoliaFacetsToAggregations.ts +209 -0
  13. package/mesh/algoliaHitToMagentoProduct.ts +201 -0
  14. package/mesh/getAlgoliaSettings.ts +21 -0
  15. package/mesh/getAttributeList.ts +31 -0
  16. package/mesh/getGroupId.ts +7 -0
  17. package/mesh/getIndexName.ts +11 -0
  18. package/mesh/getSearchResults.ts +35 -0
  19. package/mesh/getSearchResultsInput.ts +30 -0
  20. package/mesh/getSearchSuggestions.ts +27 -0
  21. package/mesh/getSearchSuggestionsInput.ts +21 -0
  22. package/mesh/getStoreConfig.ts +33 -0
  23. package/mesh/productFilterInputToAlgoliafacetFiltersInput.ts +81 -0
  24. package/mesh/resolvers.ts +126 -0
  25. package/mesh/sortOptions.ts +76 -0
  26. package/mesh/utils.ts +3 -0
  27. package/next-env.d.ts +4 -0
  28. package/package.json +32 -0
  29. package/plugins/GraphQLProviderAlgoliaCustomerGroupId.tsx +14 -0
  30. package/plugins/ProductListItemsBaseAlgolia.tsx +21 -0
  31. package/plugins/magentoProductApplyAlgoliaEngine.ts +25 -0
  32. package/plugins/magentoSearchApplyAlgoliaEngine.ts +26 -0
  33. package/plugins/meshConfigAlgolia.ts +66 -0
  34. package/schema/AlgoliaSchema.graphqls +60 -0
  35. package/schema/CustomerAlgoliaGroupId.graphqls +9 -0
  36. package/scripts/base-schema-filter.mts +45 -0
  37. package/scripts/generate-spec.mts +69 -0
  38. package/tsconfig.json +5 -0
  39. package/utils/applyCategoryEngineVariable.ts +11 -0
  40. package/utils/applyEngineVariable.ts +11 -0
@@ -0,0 +1,30 @@
1
+ import type {
2
+ AlgoliasearchParamsObject_Input,
3
+ MeshContext,
4
+ QueryproductsArgs,
5
+ } from '@graphcommerce/graphql-mesh'
6
+ import { getStoreConfig } from './getStoreConfig'
7
+ import {
8
+ productFilterInputToAlgoliaFacetFiltersInput,
9
+ productFilterInputToAlgoliaNumericFiltersInput,
10
+ } from './productFilterInputToAlgoliafacetFiltersInput'
11
+
12
+ export async function getSearchResultsInput(
13
+ args: QueryproductsArgs,
14
+ context: MeshContext,
15
+ ): Promise<AlgoliasearchParamsObject_Input> {
16
+ const { engine, ...filters } = args.filter ?? {}
17
+
18
+ return {
19
+ query: args.search ?? '',
20
+ facets: ['*'],
21
+ hitsPerPage: args.pageSize ? args.pageSize : 10,
22
+ page: args.currentPage ? args.currentPage - 1 : 0,
23
+ facetFilters: productFilterInputToAlgoliaFacetFiltersInput(filters),
24
+ numericFilters: await productFilterInputToAlgoliaNumericFiltersInput(
25
+ getStoreConfig(context),
26
+ filters,
27
+ ),
28
+ analytics: true,
29
+ }
30
+ }
@@ -0,0 +1,27 @@
1
+ import type { MeshContext, SearchSuggestion } from '@graphcommerce/graphql-mesh'
2
+ import { filterNonNullableKeys } from '@graphcommerce/next-ui'
3
+ import { getSearchSuggestionsInput, getSuggestionsIndexName } from './getSearchSuggestionsInput'
4
+
5
+ export async function getSearchSuggestions(
6
+ search: string,
7
+ context: MeshContext,
8
+ ): Promise<SearchSuggestion[]> {
9
+ const suggestions = await context.algolia.Query.algolia_searchSingleIndex({
10
+ args: {
11
+ indexName: getSuggestionsIndexName(context),
12
+ input: await getSearchSuggestionsInput(search, context),
13
+ },
14
+ selectionSet: /* GraphQL */ `
15
+ {
16
+ hits {
17
+ __typename
18
+ objectID
19
+ additionalProperties
20
+ }
21
+ }
22
+ `,
23
+ context,
24
+ })
25
+
26
+ return filterNonNullableKeys(suggestions?.hits, []).map((hit) => ({ search: hit.objectID }))
27
+ }
@@ -0,0 +1,21 @@
1
+ import type { MeshContext, Queryalgolia_searchSingleIndexArgs } from '@graphcommerce/graphql-mesh'
2
+ import { getIndexName } from './getIndexName'
3
+
4
+ export function isSuggestionsEnabled() {
5
+ return Boolean(import.meta.graphCommerce.algolia.suggestionsSuffix)
6
+ }
7
+
8
+ export function getSuggestionsIndexName(context: MeshContext) {
9
+ return `${getIndexName(context).replace('_products', import.meta.graphCommerce.algolia.suggestionsSuffix ?? '')}`
10
+ }
11
+
12
+ // eslint-disable-next-line @typescript-eslint/require-await
13
+ export async function getSearchSuggestionsInput(
14
+ search: string,
15
+ context: MeshContext,
16
+ ): Promise<Queryalgolia_searchSingleIndexArgs['input']> {
17
+ return {
18
+ query: search,
19
+ hitsPerPage: 5,
20
+ }
21
+ }
@@ -0,0 +1,33 @@
1
+ import { MeshContext, Maybe, StoreConfig } from '@graphcommerce/graphql-mesh'
2
+
3
+ export type GetStoreConfigReturn =
4
+ | Maybe<
5
+ Pick<
6
+ StoreConfig,
7
+ | 'root_category_uid'
8
+ | 'default_display_currency_code'
9
+ | 'base_link_url'
10
+ | 'product_url_suffix'
11
+ >
12
+ >
13
+ | undefined
14
+
15
+ let configCache: Promise<StoreConfig>
16
+
17
+ export function getStoreConfig(context: MeshContext): Promise<StoreConfig> {
18
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
19
+ if (!configCache) {
20
+ configCache = context.m2.Query.storeConfig({
21
+ context,
22
+ selectionSet: /* GraphQL */ `
23
+ {
24
+ root_category_uid
25
+ default_display_currency_code
26
+ base_link_url
27
+ product_url_suffix
28
+ }
29
+ `,
30
+ })
31
+ }
32
+ return configCache
33
+ }
@@ -0,0 +1,81 @@
1
+ import type {
2
+ ProductAttributeFilterInput,
3
+ AlgolianumericFilters_Input,
4
+ } from '@graphcommerce/graphql-mesh'
5
+ import {
6
+ isFilterTypeEqual,
7
+ isFilterTypeRange,
8
+ isFilterTypeMatch,
9
+ } from '@graphcommerce/magento-product'
10
+ import { InputMaybe } from '@graphcommerce/next-config'
11
+ import { GetStoreConfigReturn } from './getStoreConfig'
12
+ import { nonNullable } from './utils'
13
+
14
+ /**
15
+ * Map filters recieved from arguments to algolia facetfilter format
16
+ *
17
+ * https://www.algolia.com/doc/api-reference/api-parameters/facetFilters/
18
+ */
19
+ export function productFilterInputToAlgoliaFacetFiltersInput(
20
+ filters?: InputMaybe<ProductAttributeFilterInput>,
21
+ ) {
22
+ const filterArray: (string | string[])[] = []
23
+ if (!filters) {
24
+ return []
25
+ }
26
+
27
+ Object.entries(filters).forEach(([key, value]) => {
28
+ if (isFilterTypeEqual(value)) {
29
+ if (value.in) {
30
+ const values = value.in.filter(nonNullable)
31
+ if (key === 'category_uid') filterArray.push(values.map((v) => `categoryIds:${atob(v)}`))
32
+ else filterArray.push(values.map((v) => `${key}:${v}`))
33
+ }
34
+
35
+ if (value.eq) {
36
+ if (key === 'category_uid') filterArray.push(`categoryIds:${atob(value.eq)}`)
37
+ else filterArray.push(`${key}:${value.eq}`)
38
+ }
39
+ }
40
+
41
+ if (isFilterTypeMatch(value)) {
42
+ throw Error('Match filters are not supported')
43
+ }
44
+ })
45
+
46
+ return filterArray
47
+ }
48
+
49
+ /**
50
+ * Map filters recieved from arguments to algolia facetfilter format
51
+ *
52
+ * https://www.algolia.com/doc/api-reference/api-parameters/numericFilters/#examples
53
+ */
54
+ export async function productFilterInputToAlgoliaNumericFiltersInput(
55
+ storeConfig: Promise<GetStoreConfigReturn>,
56
+ filters?: InputMaybe<ProductAttributeFilterInput>,
57
+ ) {
58
+ if (!filters) return []
59
+
60
+ const filterArray: AlgolianumericFilters_Input[] = []
61
+
62
+ for (const [key, value] of Object.entries(filters)) {
63
+ if (isFilterTypeRange(value)) {
64
+ if (key === 'price') {
65
+ // eslint-disable-next-line no-await-in-loop
66
+ const currencyCode = (await storeConfig)?.default_display_currency_code
67
+ filterArray.push(
68
+ { numericFilters_Input: { String: `${key}.${currencyCode}.default >= ${value.from}` } },
69
+ { numericFilters_Input: { String: `${key}.${currencyCode}.default <= ${value.to}` } },
70
+ )
71
+ } else {
72
+ filterArray.push(
73
+ { numericFilters_Input: { String: `${key} >= ${value.from}` } },
74
+ { numericFilters_Input: { String: `${key} <= ${value.to}` } },
75
+ )
76
+ }
77
+ }
78
+ }
79
+
80
+ return filterArray
81
+ }
@@ -0,0 +1,126 @@
1
+ import {
2
+ AlgoliasearchResponse,
3
+ hasSelectionSetPath,
4
+ type Resolvers,
5
+ } from '@graphcommerce/graphql-mesh'
6
+ import type { GraphQLError, GraphQLResolveInfo } from 'graphql'
7
+ import { algoliaFacetsToAggregations, getCategoryList } from './algoliaFacetsToAggregations'
8
+ import { algoliaHitToMagentoProduct, ProductsItemsItem } from './algoliaHitToMagentoProduct'
9
+ import { getAlgoliaSettings } from './getAlgoliaSettings'
10
+ import { getAttributeList } from './getAttributeList'
11
+ import { getGroupId } from './getGroupId'
12
+ import { getSearchResults } from './getSearchResults'
13
+ import { getSearchSuggestions } from './getSearchSuggestions'
14
+ import { isSuggestionsEnabled } from './getSearchSuggestionsInput'
15
+ import { getStoreConfig } from './getStoreConfig'
16
+ import { sortingOptions } from './sortOptions'
17
+
18
+ function isAlgoliaResponse<T extends object>(
19
+ root: T,
20
+ ): root is T & { algoliaSearchResults: AlgoliasearchResponse } {
21
+ return 'algoliaSearchResults' in root
22
+ }
23
+
24
+ function hasSearchRequest(info: GraphQLResolveInfo) {
25
+ const hasItems = hasSelectionSetPath(info.operation.selectionSet, 'products.items')
26
+ const hasAggregations = hasSelectionSetPath(info.operation.selectionSet, 'products.aggregations')
27
+ const hasPageInfo = hasSelectionSetPath(info.operation.selectionSet, 'products.page_info')
28
+ const hasTotalCount = hasSelectionSetPath(info.operation.selectionSet, 'products.total_count')
29
+ const hasSortFields = hasSelectionSetPath(info.operation.selectionSet, 'products.sort_fields')
30
+
31
+ return Boolean(hasItems || hasAggregations || hasPageInfo || hasTotalCount || hasSortFields)
32
+ }
33
+
34
+ function hasSuggestionsRequest(info: GraphQLResolveInfo) {
35
+ return hasSelectionSetPath(info.operation.selectionSet, 'products.suggestions')
36
+ }
37
+
38
+ function isGraphQLError(err: unknown): err is GraphQLError {
39
+ return !!(err as GraphQLError)?.message
40
+ }
41
+
42
+ export const resolvers: Resolvers = {
43
+ Products: {
44
+ aggregations: async (root, _args, context) => {
45
+ if (!isAlgoliaResponse(root)) return root.aggregations ?? null
46
+
47
+ return algoliaFacetsToAggregations(
48
+ root.algoliaSearchResults?.facets,
49
+ await getAttributeList(context),
50
+ await getStoreConfig(context),
51
+ await getCategoryList(context),
52
+ getGroupId(context),
53
+ )
54
+ },
55
+
56
+ sort_fields: async (root, _args, context) => {
57
+ if (isAlgoliaResponse(root)) {
58
+ return {
59
+ default: 'relevance',
60
+ options: Object.values(
61
+ sortingOptions(
62
+ await getAlgoliaSettings(context),
63
+ await getAttributeList(context),
64
+ context,
65
+ ),
66
+ ),
67
+ }
68
+ }
69
+ return root.sort_fields ?? null
70
+ },
71
+
72
+ total_count: (root) => {
73
+ if (!isAlgoliaResponse(root)) return root.total_count ?? null
74
+ return root.algoliaSearchResults?.nbHits
75
+ },
76
+
77
+ page_info: (root) => {
78
+ if (!isAlgoliaResponse(root)) return root.page_info ?? null
79
+ return {
80
+ current_page: root.algoliaSearchResults.page + 1,
81
+ page_size: root.algoliaSearchResults.hitsPerPage,
82
+ total_pages: root.algoliaSearchResults.nbPages,
83
+ }
84
+ },
85
+
86
+ items: async (root, args, context) => {
87
+ if (!isAlgoliaResponse(root)) return root.items ?? null
88
+
89
+ const items: (ProductsItemsItem | null)[] = []
90
+
91
+ const config = await getStoreConfig(context)
92
+ for (const hit of root.algoliaSearchResults.hits) {
93
+ if (hit?.objectID) {
94
+ const product = algoliaHitToMagentoProduct(hit, config, getGroupId(context))
95
+ items.push(product)
96
+ }
97
+ }
98
+
99
+ return items
100
+ },
101
+ },
102
+ Query: {
103
+ products: async (root, args, context, info) => {
104
+ const isAgolia = (args.filter?.engine?.in ?? [args.filter?.engine?.eq])[0] === 'algolia'
105
+
106
+ if (!isAgolia) return context.m2.Query.products({ root, args, context, info })
107
+
108
+ const searchSuggestsions =
109
+ isSuggestionsEnabled() &&
110
+ hasSuggestionsRequest(info) &&
111
+ args.search &&
112
+ getSearchSuggestions(args.search, context)
113
+
114
+ const searchResults = hasSearchRequest(info) ? getSearchResults(args, context, info) : null
115
+
116
+ if (isGraphQLError(await searchResults))
117
+ return context.m2.Query.products({ root, args, context, info })
118
+
119
+ return {
120
+ algoliaSearchResults: await searchResults,
121
+ suggestions: (await searchSuggestsions) || null,
122
+ algolia_queryID: (await searchResults)?.queryID,
123
+ }
124
+ },
125
+ },
126
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ AlgoliasettingsResponse,
3
+ MeshContext,
4
+ ProductAttributeSortInput,
5
+ SortEnum,
6
+ SortField,
7
+ } from '@graphcommerce/graphql-mesh'
8
+ import { nonNullable } from '@graphcommerce/magento-customer'
9
+ import { AttributeList } from './getAttributeList'
10
+ import { getIndexName } from './getIndexName'
11
+
12
+ export type SortingOptions = Record<string, SortField & { dirs: SortEnum[] }>
13
+
14
+ export async function sortingOptions(
15
+ settings: AlgoliasettingsResponse,
16
+ attributeList: AttributeList,
17
+ context: MeshContext,
18
+ ): Promise<SortingOptions> {
19
+ const sortRecord: SortingOptions = {
20
+ relevance: { label: 'Relevance', value: 'relevance', dirs: ['DESC'] },
21
+ }
22
+
23
+ ;(settings.replicas ?? [])
24
+ .map((replica) => {
25
+ if (!replica) return null
26
+
27
+ const regex = new RegExp(
28
+ `virtual\\(${getIndexName(context)}_(?<sortIndex>.*)_(?<dir>[^_]+)\\)`,
29
+ )
30
+
31
+ const res = regex.exec(replica)
32
+ if (!res) return null
33
+
34
+ const { dir, sortIndex } = res.groups ?? {}
35
+
36
+ return dir.toUpperCase() === 'DESC'
37
+ ? { dir: 'DESC' as const, sortIndex }
38
+ : { dir: 'ASC' as const, sortIndex }
39
+ })
40
+ .filter(nonNullable)
41
+ .forEach((curr) => {
42
+ let attributeCode = curr.sortIndex
43
+ if (curr.sortIndex.startsWith('price')) attributeCode = 'price'
44
+
45
+ if (!sortRecord[attributeCode]) {
46
+ sortRecord[attributeCode] = {
47
+ value: attributeCode,
48
+ label: attributeList.find((attr) => attr.code === attributeCode)?.label,
49
+ dirs: [curr.dir],
50
+ }
51
+ } else {
52
+ sortRecord[attributeCode].dirs.push(curr.dir)
53
+ }
54
+ }, {})
55
+
56
+ return sortRecord
57
+ }
58
+
59
+ export async function getSortedIndex(
60
+ context: MeshContext,
61
+ settings: Promise<AlgoliasettingsResponse>,
62
+ sortInput: ProductAttributeSortInput | null = {},
63
+ ): Promise<string> {
64
+ const baseIndex = getIndexName(context)
65
+ // const availableSorting = Object.values(sortOptions)
66
+ const [attr, dir] = Object.entries(sortInput ?? {}).filter(nonNullable)?.[0] ?? []
67
+ if (!attr || !dir) return baseIndex
68
+
69
+ const found = (await settings).replicas?.find(
70
+ (replica) =>
71
+ replica?.startsWith(`virtual(${baseIndex}_${attr}`) &&
72
+ replica?.endsWith(`${dir?.toLowerCase()})`),
73
+ )
74
+
75
+ return found ? found?.slice(8, -1) : baseIndex
76
+ }
package/mesh/utils.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function nonNullable<T>(value: T): value is NonNullable<T> {
2
+ return value !== null && value !== undefined
3
+ }
package/next-env.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/types/global" />
3
+ /// <reference types="next/image-types/global" />
4
+ /// <reference types="@graphcommerce/next-ui/types" />
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@graphcommerce/algolia-products",
3
+ "homepage": "https://www.graphcommerce.org/",
4
+ "repository": "github:graphcommerce-org/graphcommerce",
5
+ "version": "9.0.0-canary.100",
6
+ "sideEffects": false,
7
+ "prettier": "@graphcommerce/prettier-config-pwa",
8
+ "eslintConfig": {
9
+ "extends": "@graphcommerce/eslint-config-pwa",
10
+ "parserOptions": {
11
+ "project": "./tsconfig.json"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "generate": "tsx scripts/generate-spec.mts"
16
+ },
17
+ "peerDependencies": {
18
+ "@graphcommerce/google-datalayer": "^9.0.0-canary.100",
19
+ "@graphcommerce/graphql": "^9.0.0-canary.100",
20
+ "@graphcommerce/graphql-mesh": "^9.0.0-canary.100",
21
+ "@graphcommerce/magento-customer": "^9.0.0-canary.100",
22
+ "@graphcommerce/magento-product": "^9.0.0-canary.100",
23
+ "@graphcommerce/magento-search": "^9.0.0-canary.100",
24
+ "@graphcommerce/next-config": "^9.0.0-canary.100",
25
+ "@graphcommerce/next-ui": "^9.0.0-canary.100",
26
+ "react": "^18.2.0"
27
+ },
28
+ "devDependencies": {
29
+ "graphql": "^16.0.0",
30
+ "tsx": "^4.16.2"
31
+ }
32
+ }
@@ -0,0 +1,14 @@
1
+ import { GraphQLProviderProps } from '@graphcommerce/graphql'
2
+ import type { PluginConfig, PluginProps } from '@graphcommerce/next-config'
3
+ import { customerGroupIdLink } from '../link/customerGroupIdLink'
4
+
5
+ export const config: PluginConfig = {
6
+ type: 'component',
7
+ module: '@graphcommerce/graphql',
8
+ ifConfig: 'algolia.customerGroupPricingEnabled',
9
+ }
10
+
11
+ export function GraphQLProvider(props: PluginProps<GraphQLProviderProps>) {
12
+ const { Prev, links = [], ...rest } = props
13
+ return <Prev {...rest} links={[...links, customerGroupIdLink]} />
14
+ }
@@ -0,0 +1,21 @@
1
+ import type { ProductItemsGridProps } from '@graphcommerce/magento-product'
2
+ import type { PluginConfig, PluginProps } from '@graphcommerce/next-config'
3
+ import { useMemo } from 'react'
4
+ import { AlgoliaQueryContext } from '../hooks/useAlgoliaQueryContext'
5
+
6
+ export const config: PluginConfig = {
7
+ module: '@graphcommerce/magento-product',
8
+ type: 'component',
9
+ }
10
+
11
+ export function ProductListItemsBase(props: PluginProps<ProductItemsGridProps>) {
12
+ const { Prev, ...rest } = props
13
+
14
+ return (
15
+ <AlgoliaQueryContext.Provider
16
+ value={useMemo(() => ({ queryID: rest.algolia_queryID }), [rest.algolia_queryID])}
17
+ >
18
+ <Prev {...rest} />
19
+ </AlgoliaQueryContext.Provider>
20
+ )
21
+ }
@@ -0,0 +1,25 @@
1
+ import type {
2
+ useProductListApplyCategoryDefaults as useProductListApplyDefaults,
3
+ productListApplyCategoryDefaults as productListApplyDefaults,
4
+ categoryDefaultsToProductListFilters as defaultsToProductListFilters,
5
+ } from '@graphcommerce/magento-product'
6
+ import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
7
+ import { applyEngineVariables } from '../utils/applyEngineVariable'
8
+
9
+ export const config: PluginConfig = {
10
+ module: '@graphcommerce/magento-product',
11
+ type: 'function',
12
+ ifConfig: 'algolia.catalogEnabled',
13
+ }
14
+
15
+ export const useProductListApplyCategoryDefaults: FunctionPlugin<
16
+ typeof useProductListApplyDefaults
17
+ > = (prev, params, category) => applyEngineVariables(prev(params, category))
18
+
19
+ export const productListApplyCategoryDefaults: FunctionPlugin<
20
+ typeof productListApplyDefaults
21
+ > = async (prev, params, conf, category) => applyEngineVariables(await prev(params, conf, category))
22
+
23
+ export const categoryDefaultsToProductListFilters: FunctionPlugin<
24
+ typeof defaultsToProductListFilters
25
+ > = (prev, variables) => applyEngineVariables(prev(variables))
@@ -0,0 +1,26 @@
1
+ import type {
2
+ useProductListApplySearchDefaults as useProductListApplyDefaults,
3
+ productListApplySearchDefaults as productListApplyDefaults,
4
+ searchDefaultsToProductListFilters as defaultsToProductListFilters,
5
+ } from '@graphcommerce/magento-search'
6
+ import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
7
+ import { applyEngineVariables } from '../utils/applyEngineVariable'
8
+
9
+ export const config: PluginConfig = {
10
+ module: '@graphcommerce/magento-search',
11
+ type: 'function',
12
+ }
13
+
14
+ export const useProductListApplySearchDefaults: FunctionPlugin<
15
+ typeof useProductListApplyDefaults
16
+ > = (prev, params) => applyEngineVariables(prev(params))
17
+
18
+ export const productListApplySearchDefaults: FunctionPlugin<typeof productListApplyDefaults> = (
19
+ prev,
20
+ params,
21
+ conf,
22
+ ) => applyEngineVariables(prev(params, conf))
23
+
24
+ export const searchDefaultsToProductListFilters: FunctionPlugin<
25
+ typeof defaultsToProductListFilters
26
+ > = (prev, variables) => applyEngineVariables(prev(variables))
@@ -0,0 +1,66 @@
1
+ import type { meshConfig as meshConfigBase } from '@graphcommerce/graphql-mesh/meshConfig'
2
+ import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
3
+
4
+ export const config: PluginConfig = {
5
+ module: '@graphcommerce/graphql-mesh/meshConfig',
6
+ type: 'function',
7
+ }
8
+
9
+ export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
10
+ prev,
11
+ baseConfig,
12
+ graphCommerceConfig,
13
+ ) =>
14
+ prev(
15
+ {
16
+ ...baseConfig,
17
+ sources: [
18
+ ...baseConfig.sources,
19
+ {
20
+ name: 'algolia',
21
+ handler: {
22
+ openapi: {
23
+ endpoint: `https://${graphCommerceConfig.algolia.applicationId}.algolia.net/`,
24
+ source: '@graphcommerce/algolia-products/algolia-spec.yaml',
25
+ ignoreErrorResponses: true,
26
+ schemaHeaders: {
27
+ 'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
28
+ 'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
29
+ },
30
+ operationHeaders: {
31
+ 'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
32
+ 'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
33
+ },
34
+ selectQueryOrMutationField: [
35
+ { type: 'Query', fieldName: 'searchSingleIndex' },
36
+ { type: 'Query', fieldName: 'searchForFacetValues' },
37
+ ],
38
+ },
39
+ },
40
+ transforms: [
41
+ {
42
+ prefix: {
43
+ value: 'algolia_',
44
+ includeRootOperations: true,
45
+ includeTypes: false,
46
+ mode: 'bare',
47
+ },
48
+ },
49
+ {
50
+ prefix: {
51
+ value: 'Algolia',
52
+ includeRootOperations: false,
53
+ includeTypes: true,
54
+ mode: 'bare',
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ additionalResolvers: [
61
+ ...(baseConfig.additionalResolvers ?? []),
62
+ '@graphcommerce/algolia-products/mesh/resolvers.ts',
63
+ ],
64
+ },
65
+ graphCommerceConfig,
66
+ )
@@ -0,0 +1,60 @@
1
+ type AlgoliaPriceValue {
2
+ default: Float
3
+ default_formatted: String
4
+ special_from_date: String
5
+ special_to_date: String
6
+ default_max: Float
7
+ default_original_formatted: String
8
+ }
9
+
10
+ type AlgoliaPrice {
11
+ SOME_CURRENCY: AlgoliaPriceValue
12
+ }
13
+
14
+ scalar AlgoliaSku
15
+
16
+ type AlgoliaProductHitAdditionalProperties {
17
+ name: String
18
+ sku: AlgoliaSku
19
+ price: AlgoliaPrice
20
+
21
+ # categories
22
+ # categories_without_path
23
+ # categoryIds
24
+ is_stock: Int
25
+ ordered_qty: Int
26
+ created_at: String
27
+ url: String
28
+ visibility_search: Int
29
+ visibility_catalog: Int
30
+ type_id: String
31
+ rating_summary: String
32
+ thumbnail_url: String
33
+ image_url: String
34
+ }
35
+
36
+ type Algoliahit {
37
+ additionalProperties: AlgoliaProductHitAdditionalProperties
38
+ }
39
+
40
+ input ProductAttributeFilterInput {
41
+ """
42
+ When set to true, the query is passed to Algolia.
43
+
44
+ This is faster, but is only recommended for search and category pages as the data could be out of date and many fields of a product aren't supported.
45
+ """
46
+ engine: FilterEqualTypeInput
47
+ }
48
+
49
+ input CategoryFilterInput {
50
+ """
51
+ When set to true, the categories query is passed to Algolia
52
+
53
+ This is faster, but is only recommended for search and category pages as the data could be out of date and many fields of a product aren't supported.
54
+ """
55
+ engine: FilterEqualTypeInput
56
+ }
57
+
58
+ type Products {
59
+ algolia_queryID: String
60
+ }
@@ -0,0 +1,9 @@
1
+ type Customer {
2
+ group_id: Int
3
+ @resolveTo(
4
+ sourceName: "m2rest"
5
+ sourceTypeName: "Query"
6
+ sourceFieldName: "m2rest_GetV1CustomersMe"
7
+ result: "group_id"
8
+ )
9
+ }