@faststore/api 1.2.23

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 (103) hide show
  1. package/CHANGELOG.md +333 -0
  2. package/README.md +115 -0
  3. package/dist/__generated__/schema.d.ts +281 -0
  4. package/dist/api.cjs.development.js +2226 -0
  5. package/dist/api.cjs.development.js.map +1 -0
  6. package/dist/api.cjs.production.min.js +2 -0
  7. package/dist/api.cjs.production.min.js.map +1 -0
  8. package/dist/api.esm.js +2217 -0
  9. package/dist/api.esm.js.map +1 -0
  10. package/dist/index.d.ts +136 -0
  11. package/dist/index.js +8 -0
  12. package/dist/platforms/vtex/clients/commerce/index.d.ts +29 -0
  13. package/dist/platforms/vtex/clients/commerce/types/Brand.d.ts +8 -0
  14. package/dist/platforms/vtex/clients/commerce/types/CategoryTree.d.ts +9 -0
  15. package/dist/platforms/vtex/clients/commerce/types/OrderForm.d.ts +347 -0
  16. package/dist/platforms/vtex/clients/commerce/types/Simulation.d.ts +154 -0
  17. package/dist/platforms/vtex/clients/fetch.d.ts +1 -0
  18. package/dist/platforms/vtex/clients/index.d.ts +32 -0
  19. package/dist/platforms/vtex/clients/search/index.d.ts +22 -0
  20. package/dist/platforms/vtex/clients/search/types/AttributeSearchResult.d.ts +56 -0
  21. package/dist/platforms/vtex/clients/search/types/ProductSearchResult.d.ts +199 -0
  22. package/dist/platforms/vtex/index.d.ts +144 -0
  23. package/dist/platforms/vtex/loaders/index.d.ts +7 -0
  24. package/dist/platforms/vtex/loaders/simulation.d.ts +5 -0
  25. package/dist/platforms/vtex/loaders/sku.d.ts +6 -0
  26. package/dist/platforms/vtex/resolvers/aggregateOffer.d.ts +7 -0
  27. package/dist/platforms/vtex/resolvers/aggregateRating.d.ts +2 -0
  28. package/dist/platforms/vtex/resolvers/collection.d.ts +8 -0
  29. package/dist/platforms/vtex/resolvers/facet.d.ts +5 -0
  30. package/dist/platforms/vtex/resolvers/facetValue.d.ts +5 -0
  31. package/dist/platforms/vtex/resolvers/mutation.d.ts +58 -0
  32. package/dist/platforms/vtex/resolvers/offer.d.ts +11 -0
  33. package/dist/platforms/vtex/resolvers/organization.d.ts +2 -0
  34. package/dist/platforms/vtex/resolvers/product.d.ts +5 -0
  35. package/dist/platforms/vtex/resolvers/productGroup.d.ts +3 -0
  36. package/dist/platforms/vtex/resolvers/query.d.ts +51 -0
  37. package/dist/platforms/vtex/resolvers/review.d.ts +2 -0
  38. package/dist/platforms/vtex/resolvers/searchResult.d.ts +5 -0
  39. package/dist/platforms/vtex/resolvers/seo.d.ts +7 -0
  40. package/dist/platforms/vtex/resolvers/validateCart.d.ts +71 -0
  41. package/dist/platforms/vtex/utils/enhanceSku.d.ts +5 -0
  42. package/dist/platforms/vtex/utils/facets.d.ts +12 -0
  43. package/dist/platforms/vtex/utils/slugify.d.ts +1 -0
  44. package/dist/platforms/vtex/utils/sort.d.ts +10 -0
  45. package/dist/typeDefs/index.d.ts +1 -0
  46. package/package.json +44 -0
  47. package/src/__generated__/schema.ts +324 -0
  48. package/src/index.ts +33 -0
  49. package/src/platforms/vtex/clients/commerce/index.ts +100 -0
  50. package/src/platforms/vtex/clients/commerce/types/Brand.ts +8 -0
  51. package/src/platforms/vtex/clients/commerce/types/CategoryTree.ts +9 -0
  52. package/src/platforms/vtex/clients/commerce/types/OrderForm.ts +371 -0
  53. package/src/platforms/vtex/clients/commerce/types/Simulation.ts +170 -0
  54. package/src/platforms/vtex/clients/fetch.ts +13 -0
  55. package/src/platforms/vtex/clients/index.ts +15 -0
  56. package/src/platforms/vtex/clients/search/index.ts +83 -0
  57. package/src/platforms/vtex/clients/search/types/AttributeSearchResult.ts +61 -0
  58. package/src/platforms/vtex/clients/search/types/ProductSearchResult.ts +223 -0
  59. package/src/platforms/vtex/index.ts +62 -0
  60. package/src/platforms/vtex/loaders/index.ts +16 -0
  61. package/src/platforms/vtex/loaders/simulation.ts +42 -0
  62. package/src/platforms/vtex/loaders/sku.ts +61 -0
  63. package/src/platforms/vtex/resolvers/aggregateOffer.ts +20 -0
  64. package/src/platforms/vtex/resolvers/aggregateRating.ts +7 -0
  65. package/src/platforms/vtex/resolvers/collection.ts +43 -0
  66. package/src/platforms/vtex/resolvers/facet.ts +11 -0
  67. package/src/platforms/vtex/resolvers/facetValue.ts +11 -0
  68. package/src/platforms/vtex/resolvers/mutation.ts +5 -0
  69. package/src/platforms/vtex/resolvers/offer.ts +26 -0
  70. package/src/platforms/vtex/resolvers/organization.ts +5 -0
  71. package/src/platforms/vtex/resolvers/product.ts +75 -0
  72. package/src/platforms/vtex/resolvers/productGroup.ts +9 -0
  73. package/src/platforms/vtex/resolvers/query.ts +115 -0
  74. package/src/platforms/vtex/resolvers/review.ts +11 -0
  75. package/src/platforms/vtex/resolvers/searchResult.ts +46 -0
  76. package/src/platforms/vtex/resolvers/seo.ts +10 -0
  77. package/src/platforms/vtex/resolvers/validateCart.ts +166 -0
  78. package/src/platforms/vtex/utils/enhanceSku.ts +8 -0
  79. package/src/platforms/vtex/utils/facets.ts +31 -0
  80. package/src/platforms/vtex/utils/slugify.ts +4 -0
  81. package/src/platforms/vtex/utils/sort.ts +10 -0
  82. package/src/typeDefs/aggregateOffer.graphql +10 -0
  83. package/src/typeDefs/aggregateRating.graphql +4 -0
  84. package/src/typeDefs/author.graphql +3 -0
  85. package/src/typeDefs/brand.graphql +3 -0
  86. package/src/typeDefs/breadcrumb.graphql +10 -0
  87. package/src/typeDefs/cart.graphql +13 -0
  88. package/src/typeDefs/collection.graphql +26 -0
  89. package/src/typeDefs/facet.graphql +14 -0
  90. package/src/typeDefs/image.graphql +9 -0
  91. package/src/typeDefs/index.ts +47 -0
  92. package/src/typeDefs/mutation.graphql +4 -0
  93. package/src/typeDefs/offer.graphql +21 -0
  94. package/src/typeDefs/order.graphql +9 -0
  95. package/src/typeDefs/organization.graphql +7 -0
  96. package/src/typeDefs/pageInfo.graphql +8 -0
  97. package/src/typeDefs/product.graphql +25 -0
  98. package/src/typeDefs/productGroup.graphql +5 -0
  99. package/src/typeDefs/query.graphql +64 -0
  100. package/src/typeDefs/review.graphql +9 -0
  101. package/src/typeDefs/seo.graphql +6 -0
  102. package/src/typeDefs/status.graphql +5 -0
  103. package/src/typings/schema.d.ts +7 -0
@@ -0,0 +1,223 @@
1
+ export interface ProductSearchResult {
2
+ total: number
3
+ products: Product[]
4
+ pagination: Pagination
5
+ sampling: boolean
6
+ options: Options
7
+ translated: boolean
8
+ locale: string
9
+ query: string
10
+ operator: string
11
+ fuzzy: string
12
+ correction: Correction
13
+ }
14
+
15
+ export interface Correction {
16
+ misspelled: boolean
17
+ }
18
+
19
+ export interface Options {
20
+ sorts: Sort[]
21
+ counts: Count[]
22
+ }
23
+
24
+ export interface Count {
25
+ count: number
26
+ proxyURL: string
27
+ }
28
+
29
+ export interface Sort {
30
+ field: string
31
+ order: string
32
+ active?: boolean
33
+ proxyURL: string
34
+ }
35
+
36
+ export interface Pagination {
37
+ count: number
38
+ current: Current
39
+ before: any[]
40
+ after: Current[]
41
+ perPage: number
42
+ next: Current
43
+ previous: First
44
+ first: First
45
+ last: Current
46
+ }
47
+
48
+ export interface Current {
49
+ index: number
50
+ proxyURL: string
51
+ }
52
+
53
+ export interface First {
54
+ index: number
55
+ }
56
+
57
+ export interface Product {
58
+ unitMultiplier: number
59
+ year: number
60
+ extraData: ExtraDatum[]
61
+ release: number
62
+ discount: number
63
+ reference: string
64
+ split: Split
65
+ collections: Collection[]
66
+ price: number
67
+ customSort: number
68
+ stickers: Sticker[]
69
+ id: string
70
+ stock: number
71
+ brand: string
72
+ availableTradePolicies: string[]
73
+ categoryTrees: CategoryTree[]
74
+ images: Image[]
75
+ locationAttributes: any[]
76
+ tax: number
77
+ productScore: number
78
+ storeSplitAttribute: string
79
+ brandID: string
80
+ installment: Installment
81
+ name: string
82
+ boost: Boost
83
+ skus: Sku[]
84
+ link: string
85
+ wear: number
86
+ description: string
87
+ showIfNotAvailable: boolean
88
+ clusterHighlights: ClusterHighlights
89
+ categories: string[]
90
+ timestamp: number
91
+ product: string
92
+ oldPrice: number
93
+ productSpecifications: any[]
94
+ url: string
95
+ measurementUnit: string
96
+ categoryIDS: string[]
97
+ textAttributes: TextAttribute[]
98
+ numberAttributes: NumberAttribute[]
99
+ headSku: string
100
+ specificationGroups: string
101
+ extraInfo: ExtraInfo
102
+ oldPriceText: string
103
+ priceText: string
104
+ }
105
+
106
+ export interface Boost {
107
+ newness: number
108
+ image: number
109
+ revenue: number
110
+ discount: number
111
+ productScore: number
112
+ click: number
113
+ availableSpecsCount: number
114
+ promotion: number
115
+ order: number
116
+ }
117
+
118
+ export interface CategoryTree {
119
+ categoryNames: string[]
120
+ categoryIDS: string[]
121
+ }
122
+
123
+ export interface ClusterHighlights {
124
+ the140: string
125
+ }
126
+
127
+ export interface Collection {
128
+ id: string
129
+ position: number
130
+ }
131
+
132
+ export interface ExtraDatum {
133
+ value: string
134
+ key: string
135
+ }
136
+
137
+ export interface ExtraInfo {
138
+ sellerID: string
139
+ }
140
+
141
+ export interface Image {
142
+ name: string
143
+ value: string
144
+ }
145
+
146
+ export interface Installment {
147
+ interest: boolean
148
+ count: number
149
+ paymentGroupName: string
150
+ value: number
151
+ paymentName: string
152
+ valueText?: string
153
+ }
154
+
155
+ export interface NumberAttribute {
156
+ labelKey: string
157
+ value: number
158
+ key: string
159
+ }
160
+
161
+ export interface Sku {
162
+ images: Image[]
163
+ nameComplete: string
164
+ complementName: string
165
+ policies: Policy[]
166
+ videos: any[]
167
+ reference: string
168
+ idWithSplit: string
169
+ ean: string
170
+ name: string
171
+ attributes: ExtraDatum[]
172
+ id: string
173
+ sellers: Seller[]
174
+ }
175
+
176
+ export interface Policy {
177
+ id: string
178
+ sellers: Seller[]
179
+ }
180
+
181
+ export interface Seller {
182
+ default: boolean
183
+ oldPrice?: number
184
+ price?: number
185
+ installment?: Installment
186
+ name: string
187
+ tax: number
188
+ teasers: any[]
189
+ id: string
190
+ }
191
+
192
+ export interface Split {
193
+ labelValue: string
194
+ labelKey: string
195
+ }
196
+
197
+ export interface Sticker {
198
+ image: string
199
+ name: string
200
+ location: string
201
+ target: string
202
+ }
203
+
204
+ export interface TextAttribute {
205
+ joinedValue: string
206
+ isSku: boolean
207
+ joinedKey: string
208
+ joinedKeyTranslations: JoinedTranslations
209
+ isFilter: boolean
210
+ labelValue: string
211
+ id: string[]
212
+ labelKey: string
213
+ value: string
214
+ key: string
215
+ joinedValueTranslations: JoinedTranslations
216
+ valueID?: string
217
+ }
218
+
219
+ export interface JoinedTranslations {
220
+ spanish: string
221
+ english: string
222
+ italian: string
223
+ }
@@ -0,0 +1,62 @@
1
+ import { getClients } from './clients'
2
+ import { getLoaders } from './loaders'
3
+ import { StoreAggregateOffer } from './resolvers/aggregateOffer'
4
+ import { StoreAggregateRating } from './resolvers/aggregateRating'
5
+ import { StoreCollection } from './resolvers/collection'
6
+ import { StoreFacet } from './resolvers/facet'
7
+ import { StoreFacetValue } from './resolvers/facetValue'
8
+ import { Mutation } from './resolvers/mutation'
9
+ import { StoreOffer } from './resolvers/offer'
10
+ import { StoreProduct } from './resolvers/product'
11
+ import { StoreProductGroup } from './resolvers/productGroup'
12
+ import { Query } from './resolvers/query'
13
+ import { StoreReview } from './resolvers/review'
14
+ import { StoreSearchResult } from './resolvers/searchResult'
15
+ import { StoreSeo } from './resolvers/seo'
16
+ import type { Loaders } from './loaders'
17
+ import type { Clients } from './clients'
18
+
19
+ export interface Options {
20
+ platform: 'vtex'
21
+ account: string
22
+ environment: 'vtexcommercestable' | 'vtexcommercebeta'
23
+ // Default sales channel to use for fetching products
24
+ channel: string
25
+ }
26
+
27
+ export interface Context {
28
+ clients: Clients
29
+ loaders: Loaders
30
+ }
31
+
32
+ export type Resolver<R = unknown, A = unknown> = (
33
+ root: R,
34
+ args: A,
35
+ ctx: Context,
36
+ info: any
37
+ ) => any
38
+
39
+ const Resolvers = {
40
+ StoreCollection,
41
+ StoreAggregateOffer,
42
+ StoreProduct,
43
+ StoreSeo,
44
+ StoreFacet,
45
+ StoreFacetValue,
46
+ StoreOffer,
47
+ StoreAggregateRating,
48
+ StoreReview,
49
+ StoreProductGroup,
50
+ StoreSearchResult,
51
+ Query,
52
+ Mutation,
53
+ }
54
+
55
+ export const getContextFactory = (options: Options) => (ctx: any) => {
56
+ ctx.clients = getClients(options)
57
+ ctx.loaders = getLoaders(options, ctx.clients)
58
+
59
+ return ctx
60
+ }
61
+
62
+ export const getResolvers = (_: Options) => Resolvers
@@ -0,0 +1,16 @@
1
+ import { getSimulationLoader } from './simulation'
2
+ import { getSkuLoader } from './sku'
3
+ import type { Options } from '..'
4
+ import type { Clients } from '../clients'
5
+
6
+ export type Loaders = ReturnType<typeof getLoaders>
7
+
8
+ export const getLoaders = (options: Options, clients: Clients) => {
9
+ const skuLoader = getSkuLoader(options, clients)
10
+ const simulationLoader = getSimulationLoader(options, clients)
11
+
12
+ return {
13
+ skuLoader,
14
+ simulationLoader,
15
+ }
16
+ }
@@ -0,0 +1,42 @@
1
+ import DataLoader from 'dataloader'
2
+
3
+ import type {
4
+ PayloadItem,
5
+ Simulation,
6
+ } from '../clients/commerce/types/Simulation'
7
+ import type { Options } from '..'
8
+ import type { Clients } from '../clients'
9
+
10
+ export const getSimulationLoader = (_: Options, clients: Clients) => {
11
+ const loader = async (items: readonly PayloadItem[][]) => {
12
+ const simulated = await clients.commerce.checkout.simulation({
13
+ items: [...items.flat()],
14
+ })
15
+
16
+ const itemsIndices = items.reduce(
17
+ (acc, curr) => [...acc, curr.length + acc[acc.length - 1]],
18
+ [0]
19
+ )
20
+
21
+ if (simulated.items.length !== itemsIndices[itemsIndices.length - 1]) {
22
+ const askedItems = itemsIndices[itemsIndices.length - 1]
23
+ const returnedItems = simulated.items.length
24
+
25
+ throw new Error(
26
+ `Simulation asked for ${askedItems}, but received ${returnedItems} items`
27
+ )
28
+ }
29
+
30
+ return items.map((__, index) => ({
31
+ ...simulated,
32
+ items: simulated.items.slice(
33
+ itemsIndices[index],
34
+ itemsIndices[index + 1]
35
+ ),
36
+ }))
37
+ }
38
+
39
+ return new DataLoader<PayloadItem[], Simulation>(loader, {
40
+ maxBatchSize: 20,
41
+ })
42
+ }
@@ -0,0 +1,61 @@
1
+ import DataLoader from 'dataloader'
2
+
3
+ import { enhanceSku } from '../utils/enhanceSku'
4
+ import type { EnhancedSku } from '../utils/enhanceSku'
5
+ import type { Options } from '..'
6
+ import type { Clients } from '../clients'
7
+ import type { SelectedFacet } from '../utils/facets'
8
+
9
+ export const getSkuLoader = (_: Options, clients: Clients) => {
10
+ const loader = async (facetsList: readonly SelectedFacet[][]) => {
11
+ const skuIds = facetsList.map((facets) => {
12
+ const maybeFacet = facets.find(({ key }) => key === 'id')
13
+
14
+ if (!maybeFacet) {
15
+ throw new Error(
16
+ 'Error while loading SKU. Needs to pass an id to selected facets'
17
+ )
18
+ }
19
+
20
+ return maybeFacet.value
21
+ })
22
+
23
+ const indexById = skuIds.reduce(
24
+ (acc, id, index) => ({ ...acc, [id]: index }),
25
+ {} as Record<string, number>
26
+ )
27
+
28
+ const { products } = await clients.search.products({
29
+ query: `sku:${skuIds.join(';')}`,
30
+ page: 0,
31
+ count: skuIds.length,
32
+ })
33
+
34
+ if (products.length !== skuIds.length) {
35
+ throw new Error(
36
+ `Sku batching went wrong. Asked for ${skuIds.length} sku(s) but search api returned ${products.length} sku(s)`
37
+ )
38
+ }
39
+
40
+ const sorted = new Array<EnhancedSku>(products.length)
41
+
42
+ // O(n*m) sort, where n = skuIds.length and m is the number of skus per product
43
+ for (const product of products) {
44
+ const sku = product.skus.find((item) => indexById[item.id] != null)
45
+
46
+ if (sku == null) {
47
+ throw new Error(`Could not find sku for product ${product.id}`)
48
+ }
49
+
50
+ const index = indexById[sku.id]
51
+
52
+ sorted[index] = enhanceSku(sku, product)
53
+ }
54
+
55
+ return sorted
56
+ }
57
+
58
+ return new DataLoader<SelectedFacet[], EnhancedSku>(loader, {
59
+ maxBatchSize: 99, // Max allowed batch size of Search API
60
+ })
61
+ }
@@ -0,0 +1,20 @@
1
+ import type { EnhancedSku } from '../utils/enhanceSku'
2
+ import type { Simulation } from '../clients/commerce/types/Simulation'
3
+
4
+ type Resolvers = (root: Simulation & { product: EnhancedSku }) => unknown
5
+
6
+ export const StoreAggregateOffer: Record<string, Resolvers> = {
7
+ highPrice: ({ items }) =>
8
+ items.reduce(
9
+ (acc, curr) => (acc > curr.sellingPrice ? acc : curr.sellingPrice),
10
+ items[0]?.sellingPrice ?? 0
11
+ ) / 1e2,
12
+ lowPrice: ({ items }) =>
13
+ items.reduce(
14
+ (acc, curr) => (acc < curr.sellingPrice ? acc : curr.sellingPrice),
15
+ items[0]?.sellingPrice ?? 0
16
+ ) / 1e2,
17
+ offerCount: ({ items }) => items.length,
18
+ priceCurrency: () => '',
19
+ offers: ({ items, product }) => items.map((item) => ({ ...item, product })),
20
+ }
@@ -0,0 +1,7 @@
1
+ import type { Resolver } from '..'
2
+
3
+ // TODO: Add a review system integration
4
+ export const StoreAggregateRating: Record<string, Resolver> = {
5
+ ratingValue: () => 5,
6
+ reviewCount: () => 0,
7
+ }
@@ -0,0 +1,43 @@
1
+ import type { Resolver } from '..'
2
+ import type { Brand } from '../clients/commerce/types/Brand'
3
+ import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
4
+ import { slugify } from '../utils/slugify'
5
+
6
+ type Root = Brand | (CategoryTree & { level: number })
7
+
8
+ const isBrand = (x: any): x is Brand => x.type === 'brand'
9
+
10
+ export const StoreCollection: Record<string, Resolver<Root>> = {
11
+ id: ({ id }) => id.toString(),
12
+ slug: ({ name }) => slugify(name),
13
+ seo: (root) =>
14
+ isBrand(root)
15
+ ? {
16
+ title: root.title,
17
+ description: root.metaTagDescription,
18
+ }
19
+ : {
20
+ title: root.Title,
21
+ description: root.MetaTagDescription,
22
+ },
23
+ type: (root) =>
24
+ isBrand(root) ? 'Brand' : root.level === 0 ? 'Department' : 'Category',
25
+ meta: (root) =>
26
+ isBrand(root)
27
+ ? {
28
+ selectedFacets: [{ key: 'brand', value: slugify(root.name) }],
29
+ }
30
+ : {
31
+ selectedFacets: new URL(root.url).pathname
32
+ .slice(1)
33
+ .split('/')
34
+ .map((segment, index) => ({
35
+ key: `category-${index + 1}`,
36
+ value: slugify(segment),
37
+ })),
38
+ },
39
+ breadcrumbList: () => ({
40
+ itemListElement: [],
41
+ numberOfItems: 0,
42
+ }),
43
+ }
@@ -0,0 +1,11 @@
1
+ import type { Resolver } from '..'
2
+ import type { Attribute } from '../clients/search/types/AttributeSearchResult'
3
+
4
+ type Root = Attribute
5
+
6
+ export const StoreFacet: Record<string, Resolver<Root>> = {
7
+ key: ({ key }) => key,
8
+ label: ({ label }) => label,
9
+ values: ({ values }) => values,
10
+ type: ({ type }) => (type === 'text' ? 'BOOLEAN' : 'RANGE'),
11
+ }
@@ -0,0 +1,11 @@
1
+ import type { Resolver } from '..'
2
+ import type { Value } from '../clients/search/types/AttributeSearchResult'
3
+
4
+ type Root = Value
5
+
6
+ export const StoreFacetValue: Record<string, Resolver<Root>> = {
7
+ value: ({ key, from, to }) => key ?? `${from}-to-${to}`,
8
+ label: ({ label }) => label ?? 'unknown',
9
+ selected: ({ active }) => active,
10
+ quantity: ({ count }) => count,
11
+ }
@@ -0,0 +1,5 @@
1
+ import { validateCart } from './validateCart'
2
+
3
+ export const Mutation = {
4
+ validateCart,
5
+ }
@@ -0,0 +1,26 @@
1
+ import type { EnhancedSku } from '../utils/enhanceSku'
2
+ import type { Resolver } from '..'
3
+ import type { Item } from '../clients/commerce/types/Simulation'
4
+ import type { OrderFormItem } from '../clients/commerce/types/OrderForm'
5
+
6
+ type Root =
7
+ | (Item & { product: EnhancedSku }) // when querying search/product
8
+ | (OrderFormItem & { product: Promise<EnhancedSku> }) // when querying order
9
+
10
+ export const StoreOffer: Record<string, Resolver<Root>> = {
11
+ priceCurrency: () => '',
12
+ priceValidUntil: ({ priceValidUntil }) => priceValidUntil ?? '',
13
+ itemCondition: () => 'https://schema.org/NewCondition',
14
+ availability: ({ availability }) =>
15
+ availability === 'available'
16
+ ? 'https://schema.org/InStock'
17
+ : 'https://schema.org/OutOfStock',
18
+ seller: ({ seller }) => ({
19
+ identifier: seller,
20
+ }),
21
+ price: ({ sellingPrice }) => sellingPrice / 1e2, // TODO add spot price calculation
22
+ sellingPrice: ({ sellingPrice }) => sellingPrice / 1e2,
23
+ listPrice: ({ listPrice }) => listPrice / 1e2,
24
+ itemOffered: ({ product }) => product,
25
+ quantity: ({ quantity }) => quantity,
26
+ }
@@ -0,0 +1,5 @@
1
+ import type { Resolver } from '..'
2
+
3
+ export const StoreOrganization: Record<string, Resolver> = {
4
+ identifier: () => '',
5
+ }
@@ -0,0 +1,75 @@
1
+ import type { Resolver } from '..'
2
+ import type { EnhancedSku } from '../utils/enhanceSku'
3
+
4
+ type Root = EnhancedSku
5
+
6
+ const DEFAULT_IMAGE = {
7
+ name: 'image',
8
+ value:
9
+ 'https://storecomponents.vtexassets.com/assets/faststore/images/image___117a6d3e229a96ad0e0d0876352566e2.svg',
10
+ }
11
+
12
+ const getSlug = (link: string, id: string) => `${link}-${id}`
13
+ const getPath = (link: string, id: string) => `/${getSlug(link, id)}/p`
14
+
15
+ export const StoreProduct: Record<string, Resolver<Root>> = {
16
+ productID: ({ id }) => id,
17
+ name: ({ isVariantOf, name }) => name ?? isVariantOf.name,
18
+ slug: ({ isVariantOf: { link }, id }) => getSlug(link, id),
19
+ description: ({ isVariantOf: { description } }) => description,
20
+ seo: ({ isVariantOf: { name, description } }) => ({
21
+ title: name,
22
+ description,
23
+ }),
24
+ brand: ({ isVariantOf: { brand } }) => ({ name: brand }),
25
+ breadcrumbList: ({ isVariantOf: { categoryTrees, name, link }, id }) => ({
26
+ itemListElement: [
27
+ ...categoryTrees.map(({ categoryNames }, index) => ({
28
+ name: categoryNames[categoryNames.length - 1],
29
+ item: `/${categoryNames.join('/').toLowerCase()}`,
30
+ position: index + 1,
31
+ })),
32
+ {
33
+ name,
34
+ item: getPath(link, id),
35
+ position: categoryTrees.length + 1,
36
+ },
37
+ ],
38
+ numberOfItems: categoryTrees.length,
39
+ }),
40
+ image: ({ isVariantOf, images }) =>
41
+ (images ?? isVariantOf.images ?? [DEFAULT_IMAGE]).map(
42
+ ({ name, value }) => ({
43
+ alternateName: name ?? '',
44
+ url: value.replace('vteximg.com.br', 'vtexassets.com'),
45
+ })
46
+ ),
47
+ sku: ({
48
+ isVariantOf: {
49
+ skus: [sku],
50
+ },
51
+ }) => sku.id,
52
+ gtin: ({ reference }) => reference ?? '',
53
+ review: () => [],
54
+ aggregateRating: () => ({}),
55
+ offers: async (product, _, ctx) => {
56
+ const {
57
+ loaders: { simulationLoader },
58
+ } = ctx
59
+
60
+ const { sellers, id } = product
61
+
62
+ // Unique seller ids
63
+ const sellerIds = sellers.map((seller) => seller.id)
64
+ const items = Array.from(new Set(sellerIds)).map((seller) => ({
65
+ quantity: 1,
66
+ seller,
67
+ id,
68
+ }))
69
+
70
+ const simulation = await simulationLoader.load(items)
71
+
72
+ return { ...simulation, product }
73
+ },
74
+ isVariantOf: ({ isVariantOf }) => isVariantOf,
75
+ }
@@ -0,0 +1,9 @@
1
+ import { enhanceSku } from '../utils/enhanceSku'
2
+ import type { Product } from '../clients/search/types/ProductSearchResult'
3
+ import type { Resolver } from '..'
4
+
5
+ export const StoreProductGroup: Record<string, Resolver<Product>> = {
6
+ hasVariant: (root) => root.skus.map((sku) => enhanceSku(sku, root)),
7
+ productGroupID: ({ product }) => product,
8
+ name: ({ name }) => name,
9
+ }