@faststore/api 1.10.19 → 1.10.34

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.
package/dist/index.d.ts CHANGED
@@ -71,6 +71,11 @@ export declare const getResolvers: (options: Options) => {
71
71
  }, unknown, any>>;
72
72
  StoreSearchResult: Record<string, import("./platforms/vtex").Resolver<Pick<import("./platforms/vtex/clients/search").SearchArgs, "sort" | "selectedFacets" | "hideUnavailableItems" | "query" | "page" | "count" | "fuzzy">, unknown, any>>;
73
73
  StorePropertyValue: Record<string, import("./platforms/vtex").Resolver<import("./__generated__/schema").IStorePropertyValue, unknown, any>>;
74
+ SkuVariants: Record<string, import("./platforms/vtex").Resolver<import("./platforms/vtex/clients/search/types/ProductSearchResult").Item & {
75
+ isVariantOf: import("./platforms/vtex/clients/search/types/ProductSearchResult").Product;
76
+ } & {
77
+ attachmentsValues?: import("./platforms/vtex/clients/commerce/types/OrderForm").Attachment[] | undefined;
78
+ }, unknown, any>>;
74
79
  ObjectOrString: import("graphql").GraphQLScalarType;
75
80
  Query: {
76
81
  product: (_: unknown, { locator }: import("./__generated__/schema").QueryProductArgs, ctx: import("./platforms/vtex").Context) => Promise<import("./platforms/vtex/utils/enhanceSku").EnhancedSku>;
@@ -101,6 +101,11 @@ export declare const getResolvers: (_: Options) => {
101
101
  }, unknown, any>>;
102
102
  StoreSearchResult: Record<string, Resolver<Pick<SearchArgs, "hideUnavailableItems" | "query" | "page" | "count" | "sort" | "selectedFacets" | "fuzzy">, unknown, any>>;
103
103
  StorePropertyValue: Record<string, Resolver<import("../..").IStorePropertyValue, unknown, any>>;
104
+ SkuVariants: Record<string, Resolver<import("./clients/search/types/ProductSearchResult").Item & {
105
+ isVariantOf: import("./clients/search/types/ProductSearchResult").Product;
106
+ } & {
107
+ attachmentsValues?: import("./clients/commerce/types/OrderForm").Attachment[] | undefined;
108
+ }, unknown, any>>;
104
109
  ObjectOrString: import("graphql").GraphQLScalarType;
105
110
  Query: {
106
111
  product: (_: unknown, { locator }: import("../..").QueryProductArgs, ctx: Context) => Promise<import("./utils/enhanceSku").EnhancedSku>;
@@ -0,0 +1,6 @@
1
+ import type { Resolver } from '..';
2
+ import type { PromiseType } from '../../../typings';
3
+ import type { StoreProduct } from './product';
4
+ declare type Root = PromiseType<ReturnType<typeof StoreProduct.isVariantOf>>;
5
+ export declare const SkuVariants: Record<string, Resolver<Root>>;
6
+ export {};
@@ -0,0 +1,15 @@
1
+ import { StoreProduct as StoreProductType } from '../../..';
2
+ import type { Product, Item } from '../clients/search/types/ProductSearchResult';
3
+ export declare type SkuVariants = StoreProductType[];
4
+ export declare type SkuVariantsByName = Record<string, Array<FormattedSkuVariant>>;
5
+ declare type FormattedSkuVariant = {
6
+ alt: string;
7
+ src: string;
8
+ label: string;
9
+ value: string;
10
+ };
11
+ export declare function createSlugsMap(variants: Item[], dominantVariantName: string, baseSlug: string): Record<string, string>;
12
+ export declare function getActiveSkuVariations(variations: Item['variations']): Record<string, string>;
13
+ export declare function getVariantsByName(skuSpecifications: Product['skuSpecifications']): Record<string, string[]>;
14
+ export declare function getFormattedVariations(variants: Item[], dominantVariantName: string, dominantVariantValue: string): Record<string, FormattedSkuVariant[]>;
15
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/api",
3
- "version": "1.10.19",
3
+ "version": "1.10.34",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "express-graphql": "^0.12.0",
38
38
  "graphql": "^15.6.0",
39
39
  "jest-transform-graphql": "^2.1.0",
40
- "shared": "^1.10.18",
40
+ "shared": "^1.10.34",
41
41
  "ts-jest": "25.5.1",
42
42
  "tsdx": "^0.14.1",
43
43
  "tslib": "^2.3.1",
@@ -46,5 +46,5 @@
46
46
  "peerDependencies": {
47
47
  "graphql": "^15.6.0"
48
48
  },
49
- "gitHead": "9678f28100f7829663fddb743835a4ea1f60a0da"
49
+ "gitHead": "5d59e28c724e389b596e2bb13b1464314eab275f"
50
50
  }
@@ -9,7 +9,69 @@ export type Scalars = {
9
9
  Boolean: boolean;
10
10
  Int: number;
11
11
  Float: number;
12
+ /**
13
+ * Example:
14
+ *
15
+ * ```json
16
+ * {
17
+ * Color: 'Red', Size: '42'
18
+ * }
19
+ * ```
20
+ */
21
+ ActiveVariations: any;
22
+ /**
23
+ * Example:
24
+ *
25
+ * ```json
26
+ * {
27
+ * Color: [
28
+ * {
29
+ * src: "https://storecomponents.vtexassets.com/...",
30
+ * alt: "...",
31
+ * label: "...",
32
+ * value: "..."
33
+ * },
34
+ * {
35
+ * src: "https://storecomponents.vtexassets.com/...",
36
+ * alt: "...",
37
+ * label: "...",
38
+ * value: "..."
39
+ * }
40
+ * ],
41
+ * Size: [
42
+ * {
43
+ * src: "https://storecomponents.vtexassets.com/...",
44
+ * alt: "...",
45
+ * label: "...",
46
+ * value: "..."
47
+ * }
48
+ * ]
49
+ * }
50
+ * ```
51
+ */
52
+ FormattedVariants: any;
12
53
  ObjectOrString: any;
54
+ /**
55
+ * Example:
56
+ *
57
+ * ```json
58
+ * {
59
+ * 'Color-Red-Size-40': 'classic-shoes-37'
60
+ * }
61
+ * ```
62
+ */
63
+ SlugsMap: any;
64
+ /**
65
+ * Example:
66
+ *
67
+ * ```json
68
+ * {
69
+ * Color: [ "Red", "Blue", "Green" ],
70
+ * Size: [ "40", "41" ]
71
+ * }
72
+ * ```
73
+ */
74
+ VariantsByName: any;
13
75
  };
14
76
 
15
77
  /** Person data input to the newsletter. */
@@ -206,6 +268,37 @@ export type QuerySearchArgs = {
206
268
  term?: Maybe<Scalars['String']>;
207
269
  };
208
270
 
271
+ export type SkuVariants = {
272
+ __typename?: 'SkuVariants';
273
+ /** SKU property values for the current SKU. */
274
+ activeVariations?: Maybe<Scalars['ActiveVariations']>;
275
+ /** All available options for each SKU variant property, indexed by their name. */
276
+ allVariantsByName?: Maybe<Scalars['VariantsByName']>;
277
+ /**
278
+ * Available options for each varying SKU property, taking into account the
279
+ * `dominantVariantName` property. Returns all available options for the
280
+ * dominant property, and only options that can be combined with its current
281
+ * value for other properties.
282
+ */
283
+ availableVariations?: Maybe<Scalars['FormattedVariants']>;
284
+ /**
285
+ * Maps property value combinations to their respective SKU's slug. Enables
286
+ * us to retrieve the slug for the SKU that matches the currently selected
287
+ * variations in O(1) time.
288
+ */
289
+ slugsMap?: Maybe<Scalars['SlugsMap']>;
290
+ };
291
+
292
+
293
+ export type SkuVariantsAvailableVariationsArgs = {
294
+ dominantVariantName: Scalars['String'];
295
+ };
296
+
297
+
298
+ export type SkuVariantsSlugsMapArgs = {
299
+ dominantVariantName: Scalars['String'];
300
+ };
301
+
209
302
  /** Aggregate offer information, for a given SKU that is available to be fulfilled by multiple sellers. */
210
303
  export type StoreAggregateOffer = {
211
304
  __typename?: 'StoreAggregateOffer';
@@ -556,6 +649,12 @@ export type StoreProductGroup = {
556
649
  name: Scalars['String'];
557
650
  /** Product group ID. */
558
651
  productGroupID: Scalars['String'];
652
+ /**
653
+ * Object containing data structures to facilitate handling different SKU
654
+ * variant properties. Specially useful for implementing SKU selection
655
+ * components.
656
+ */
657
+ skuVariants?: Maybe<SkuVariants>;
559
658
  };
560
659
 
561
660
  /** Properties that can be associated with products and products groups. */
@@ -20,6 +20,7 @@ import { Query } from './resolvers/query'
20
20
  import { StoreReview } from './resolvers/review'
21
21
  import { StoreSearchResult } from './resolvers/searchResult'
22
22
  import { StoreSeo } from './resolvers/seo'
23
+ import { SkuVariants } from './resolvers/skuVariations'
23
24
  import ChannelMarshal from './utils/channel'
24
25
  import type { Loaders } from './loaders'
25
26
  import type { Clients } from './clients'
@@ -80,6 +81,7 @@ const Resolvers = {
80
81
  StoreProductGroup,
81
82
  StoreSearchResult,
82
83
  StorePropertyValue,
84
+ SkuVariants,
83
85
  ObjectOrString,
84
86
  Query,
85
87
  Mutation,
@@ -11,8 +11,11 @@ const isBrand = (x: any): x is Brand | CollectionPageType =>
11
11
  x.type === 'brand' ||
12
12
  (isCollectionPageType(x) && x.pageType.toLowerCase() === 'brand')
13
13
 
14
+ const isCollection = (x: Root): x is CollectionPageType =>
15
+ isCollectionPageType(x) && x.pageType.toLowerCase() === 'collection'
16
+
14
17
  const slugifyRoot = (root: Root) => {
15
- if (isBrand(root)) {
18
+ if (isBrand(root) || isCollection(root)) {
16
19
  return slugify(root.name)
17
20
  }
18
21
 
@@ -12,15 +12,16 @@ export const StoreProductGroup: Record<string, Resolver<Root>> = {
12
12
  hasVariant: (root) =>
13
13
  root.isVariantOf.items.map((item) => enhanceSku(item, root.isVariantOf)),
14
14
  productGroupID: ({ isVariantOf }) => isVariantOf.productId,
15
- name: ({ isVariantOf }) => isVariantOf.productName,
15
+ name: (root) => root.isVariantOf.productName,
16
+ skuVariants: (root) => root,
16
17
  additionalProperty: ({ isVariantOf: { specificationGroups } }) =>
17
18
  specificationGroups
18
- // filter sku specifications so we dont mess sku with product specs
19
+ // Filter sku specifications so we don't mix them with product specs.
19
20
  .filter(
20
21
  (specificationGroup) =>
21
22
  !BLOCKED_SPECIFICATIONS.has(specificationGroup.name)
22
23
  )
23
- // Transform specs back into product specs
24
+ // Transform specs back into product specs.
24
25
  .flatMap(({ specifications }) =>
25
26
  specifications.flatMap(({ name, values }) =>
26
27
  values.map((value) => ({
@@ -0,0 +1,47 @@
1
+ import type { Resolver } from '..'
2
+ import type { PromiseType } from '../../../typings'
3
+ import type { StoreProduct } from './product'
4
+ import {
5
+ createSlugsMap,
6
+ getActiveSkuVariations,
7
+ getFormattedVariations,
8
+ getVariantsByName,
9
+ } from '../utils/skuVariants'
10
+
11
+ type Root = PromiseType<ReturnType<typeof StoreProduct.isVariantOf>>
12
+
13
+ type SlugsMapArgs = {
14
+ dominantVariantName: string
15
+ }
16
+
17
+ export const SkuVariants: Record<string, Resolver<Root>> = {
18
+ activeVariations: (root) => getActiveSkuVariations(root.variations),
19
+ allVariantsByName: (root) =>
20
+ getVariantsByName(root.isVariantOf.skuSpecifications),
21
+
22
+ slugsMap: (root, args) =>
23
+ createSlugsMap(
24
+ root.isVariantOf.items,
25
+ // Since `dominantVariantProperty` is a required argument, we can safely
26
+ // access it.
27
+ (args as SlugsMapArgs).dominantVariantName,
28
+ root.isVariantOf.linkText
29
+ ),
30
+
31
+ availableVariations: (root, args) => {
32
+ // Since `dominantVariantProperty` is a required argument, we can safely
33
+ // access it.
34
+ const dominantVariantName = (args as SlugsMapArgs).dominantVariantName
35
+ const activeVariations = getActiveSkuVariations(root.variations)
36
+
37
+ const activeDominantVariationValue = activeVariations[dominantVariantName]
38
+
39
+ const filteredFormattedVariations = getFormattedVariations(
40
+ root.isVariantOf.items,
41
+ dominantVariantName,
42
+ activeDominantVariationValue
43
+ )
44
+
45
+ return filteredFormattedVariations
46
+ },
47
+ }
@@ -0,0 +1,206 @@
1
+ import { StoreProduct as StoreProductType } from '../../..'
2
+ import type { Product, Item } from '../clients/search/types/ProductSearchResult'
3
+
4
+ export type SkuVariants = StoreProductType[]
5
+
6
+ export type SkuVariantsByName = Record<string, Array<FormattedSkuVariant>>
7
+
8
+ type FormattedSkuVariant = {
9
+ alt: string
10
+ src: string
11
+ label: string
12
+ value: string
13
+ }
14
+
15
+ function findSkuVariantImage(availableImages: Item['images']) {
16
+ return (
17
+ availableImages.find(
18
+ (imageProperties) => imageProperties.imageLabel === 'skuvariation'
19
+ ) ?? availableImages[0]
20
+ )
21
+ }
22
+
23
+ export function createSlugsMap(
24
+ variants: Item[],
25
+ dominantVariantName: string,
26
+ baseSlug: string
27
+ ) {
28
+ /**
29
+ * Maps property value combinations to their respective SKU's slug. Enables
30
+ * us to retrieve the slug for the SKU that matches the currently selected
31
+ * variations in O(1) time.
32
+ *
33
+ * Example: `'Color-Red-Size-40': 'classic-shoes-37'`
34
+ */
35
+ const slugsMap: Record<string, string> = {}
36
+
37
+ variants.forEach((variant) => {
38
+ const skuSpecificationProperties = variant.variations
39
+
40
+ if (skuSpecificationProperties.length === 0) {
41
+ return
42
+ }
43
+
44
+ // Make sure that the 'name-value' pair for the dominant variation
45
+ // is always the first one.
46
+ const dominantNameValue = `${dominantVariantName}-${
47
+ skuSpecificationProperties.find(
48
+ (variationDetails) => variationDetails.name === dominantVariantName
49
+ )?.values[0] ?? ''
50
+ }`
51
+
52
+ const skuVariantKey = skuSpecificationProperties.reduce((acc, property) => {
53
+ const shouldIgnore = property.name === dominantVariantName
54
+
55
+ if (shouldIgnore) {
56
+ return acc
57
+ }
58
+
59
+ return acc + `-${property.name}-${property.values[0]}`
60
+ }, dominantNameValue)
61
+
62
+ slugsMap[skuVariantKey] = `${baseSlug}-${variant.itemId}`
63
+ })
64
+
65
+ return slugsMap
66
+ }
67
+
68
+ export function getActiveSkuVariations(variations: Item['variations']) {
69
+ const activeVariations: Record<string, string> = {}
70
+
71
+ variations.forEach((variation) => {
72
+ activeVariations[variation.name] = variation.values[0]
73
+ })
74
+
75
+ return activeVariations
76
+ }
77
+
78
+ export function getVariantsByName(
79
+ skuSpecifications: Product['skuSpecifications']
80
+ ) {
81
+ const variants: Record<string, string[]> = {}
82
+
83
+ skuSpecifications?.forEach((specification) => {
84
+ variants[specification.field.originalName ?? specification.field.name] =
85
+ specification.values.map((value) => value.originalName ?? value.name)
86
+ })
87
+
88
+ return variants
89
+ }
90
+
91
+ function compare(a: string, b: string) {
92
+ // Values are always represented as Strings, so we need to handle numbers
93
+ // in this special case.
94
+ if (!Number.isNaN(Number(a) - Number(b))) {
95
+ return Number(a) - Number(b)
96
+ }
97
+
98
+ if (a < b) {
99
+ return -1
100
+ }
101
+
102
+ if (a > b) {
103
+ return 1
104
+ }
105
+
106
+ return 0
107
+ }
108
+
109
+ function sortVariants(variantsByName: SkuVariantsByName) {
110
+ const sortedVariants = variantsByName
111
+
112
+ for (const variantProperty in variantsByName) {
113
+ variantsByName[variantProperty].sort((a, b) => compare(a.value, b.value))
114
+ }
115
+
116
+ return sortedVariants
117
+ }
118
+
119
+ export function getFormattedVariations(
120
+ variants: Item[],
121
+ dominantVariantName: string,
122
+ dominantVariantValue: string
123
+ ) {
124
+ /**
125
+ * SKU options already formatted and indexed by their property name.
126
+ *
127
+ * Ex: {
128
+ * `Size`: [
129
+ * { label: '42', value: '42' },
130
+ * { label: '41', value: '41' },
131
+ * { label: '39', value: '39' },
132
+ * ]
133
+ * }
134
+ */
135
+ const variantsByName: SkuVariantsByName = {}
136
+
137
+ const previouslySeenPropertyValues = new Set<string>()
138
+
139
+ variants.forEach((variant) => {
140
+ if (variant.variations.length === 0) {
141
+ return
142
+ }
143
+
144
+ const variantImageToUse = findSkuVariantImage(variant.images)
145
+
146
+ const dominantVariantEntry = variant.variations.find(
147
+ (variation) => variation.name === dominantVariantName
148
+ )
149
+
150
+ const matchesDominantVariant =
151
+ dominantVariantEntry?.values[0] === dominantVariantValue
152
+
153
+ if (!matchesDominantVariant) {
154
+ const nameValueIdentifier = `${dominantVariantName}-${dominantVariantEntry?.values[0]}`
155
+
156
+ if (
157
+ !dominantVariantEntry ||
158
+ previouslySeenPropertyValues.has(nameValueIdentifier)
159
+ ) {
160
+ return
161
+ }
162
+
163
+ previouslySeenPropertyValues.add(nameValueIdentifier)
164
+
165
+ const formattedVariant = {
166
+ src: variantImageToUse.imageUrl,
167
+ alt: variantImageToUse.imageLabel ?? '',
168
+ label: `${dominantVariantName}: ${dominantVariantEntry.values[0]}`,
169
+ value: dominantVariantEntry.values[0],
170
+ }
171
+
172
+ if (variantsByName[dominantVariantEntry.name]) {
173
+ variantsByName[dominantVariantEntry.name].push(formattedVariant)
174
+ } else {
175
+ variantsByName[dominantVariantEntry.name] = [formattedVariant]
176
+ }
177
+
178
+ return
179
+ }
180
+
181
+ variant.variations.forEach((variationProperty) => {
182
+ const nameValueIdentifier = `${variationProperty.name}-${variationProperty.values[0]}`
183
+
184
+ if (previouslySeenPropertyValues.has(nameValueIdentifier)) {
185
+ return
186
+ }
187
+
188
+ previouslySeenPropertyValues.add(nameValueIdentifier)
189
+
190
+ const formattedVariant = {
191
+ src: variantImageToUse.imageUrl,
192
+ alt: variantImageToUse.imageLabel ?? '',
193
+ label: `${variationProperty.name}: ${variationProperty.values[0]}`,
194
+ value: variationProperty.values[0],
195
+ }
196
+
197
+ if (variantsByName[variationProperty.name]) {
198
+ variantsByName[variationProperty.name].push(formattedVariant)
199
+ } else {
200
+ variantsByName[variationProperty.name] = [formattedVariant]
201
+ }
202
+ })
203
+ })
204
+
205
+ return sortVariants(variantsByName)
206
+ }
@@ -25,6 +25,7 @@ import Person from './person.graphql'
25
25
  import ObjectOrString from './objectOrString.graphql'
26
26
  import Session from './session.graphql'
27
27
  import Newsletter from './newsletter.graphql'
28
+ import SkuVariants from './skuVariants.graphql'
28
29
 
29
30
  export const typeDefs = [
30
31
  Query,
@@ -51,7 +52,8 @@ export const typeDefs = [
51
52
  Person,
52
53
  ObjectOrString,
53
54
  Session,
54
- Newsletter
55
+ Newsletter,
56
+ SkuVariants,
55
57
  ]
56
58
  .map(print)
57
59
  .join('\n')
@@ -18,4 +18,10 @@ type StoreProductGroup {
18
18
  Array of additional properties.
19
19
  """
20
20
  additionalProperty: [StorePropertyValue!]!
21
+ """
22
+ Object containing data structures to facilitate handling different SKU
23
+ variant properties. Specially useful for implementing SKU selection
24
+ components.
25
+ """
26
+ skuVariants: SkuVariants
21
27
  }
@@ -0,0 +1,87 @@
1
+ type SkuVariants {
2
+ """
3
+ SKU property values for the current SKU.
4
+ """
5
+ activeVariations: ActiveVariations
6
+ """
7
+ All available options for each SKU variant property, indexed by their name.
8
+ """
9
+ allVariantsByName: VariantsByName
10
+ """
11
+ Maps property value combinations to their respective SKU's slug. Enables
12
+ us to retrieve the slug for the SKU that matches the currently selected
13
+ variations in O(1) time.
14
+ """
15
+ slugsMap(dominantVariantName: String!): SlugsMap
16
+ """
17
+ Available options for each varying SKU property, taking into account the
18
+ `dominantVariantName` property. Returns all available options for the
19
+ dominant property, and only options that can be combined with its current
20
+ value for other properties.
21
+ """
22
+ availableVariations(dominantVariantName: String!): FormattedVariants
23
+ }
24
+
25
+ """
26
+ Example:
27
+
28
+ ```json
29
+ {
30
+ 'Color-Red-Size-40': 'classic-shoes-37'
31
+ }
32
+ ```
33
+ """
34
+ scalar SlugsMap
35
+ """
36
+ Example:
37
+
38
+ ```json
39
+ {
40
+ Color: 'Red', Size: '42'
41
+ }
42
+ ```
43
+ """
44
+ scalar ActiveVariations
45
+ """
46
+ Example:
47
+
48
+ ```json
49
+ {
50
+ Color: [ "Red", "Blue", "Green" ],
51
+ Size: [ "40", "41" ]
52
+ }
53
+ ```
54
+ """
55
+ scalar VariantsByName
56
+ """
57
+ Example:
58
+
59
+ ```json
60
+ {
61
+ Color: [
62
+ {
63
+ src: "https://storecomponents.vtexassets.com/...",
64
+ alt: "...",
65
+ label: "...",
66
+ value: "..."
67
+ },
68
+ {
69
+ src: "https://storecomponents.vtexassets.com/...",
70
+ alt: "...",
71
+ label: "...",
72
+ value: "..."
73
+ }
74
+ ],
75
+ Size: [
76
+ {
77
+ src: "https://storecomponents.vtexassets.com/...",
78
+ alt: "...",
79
+ label: "...",
80
+ value: "..."
81
+ }
82
+ ]
83
+ }
84
+ ```
85
+ """
86
+ scalar FormattedVariants
87
+