@faststore/api 1.9.6 → 1.9.9

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 (45) hide show
  1. package/CHANGELOG.md +519 -1691
  2. package/README.md +5 -2
  3. package/dist/__generated__/schema.d.ts +49 -33
  4. package/dist/api.cjs.development.js +129 -50
  5. package/dist/api.cjs.development.js.map +1 -1
  6. package/dist/api.cjs.production.min.js +1 -1
  7. package/dist/api.cjs.production.min.js.map +1 -1
  8. package/dist/api.esm.js +129 -50
  9. package/dist/api.esm.js.map +1 -1
  10. package/dist/index.d.ts +5 -4
  11. package/dist/platforms/vtex/clients/commerce/types/Portal.d.ts +1 -1
  12. package/dist/platforms/vtex/index.d.ts +5 -4
  13. package/dist/platforms/vtex/loaders/index.d.ts +1 -1
  14. package/dist/platforms/vtex/loaders/sku.d.ts +1 -2
  15. package/dist/platforms/vtex/resolvers/mutation.d.ts +3 -3
  16. package/dist/platforms/vtex/resolvers/offer.d.ts +1 -1
  17. package/dist/platforms/vtex/resolvers/seo.d.ts +1 -0
  18. package/dist/platforms/vtex/resolvers/validateCart.d.ts +3 -3
  19. package/dist/platforms/vtex/utils/canonical.d.ts +2 -0
  20. package/dist/platforms/vtex/utils/facets.d.ts +2 -0
  21. package/dist/platforms/vtex/utils/orderStatistics.d.ts +4 -0
  22. package/dist/platforms/vtex/utils/sku.d.ts +8 -0
  23. package/package.json +3 -2
  24. package/src/__generated__/schema.ts +49 -33
  25. package/src/platforms/vtex/clients/commerce/index.ts +1 -1
  26. package/src/platforms/vtex/clients/commerce/types/Portal.ts +1 -1
  27. package/src/platforms/vtex/index.ts +12 -10
  28. package/src/platforms/vtex/loaders/sku.ts +3 -16
  29. package/src/platforms/vtex/resolvers/offer.ts +6 -3
  30. package/src/platforms/vtex/resolvers/product.ts +6 -4
  31. package/src/platforms/vtex/resolvers/query.ts +44 -1
  32. package/src/platforms/vtex/resolvers/seo.ts +2 -2
  33. package/src/platforms/vtex/resolvers/validateCart.ts +3 -3
  34. package/src/platforms/vtex/utils/canonical.ts +3 -0
  35. package/src/platforms/vtex/utils/facets.ts +6 -0
  36. package/src/platforms/vtex/utils/orderStatistics.ts +16 -0
  37. package/src/platforms/vtex/utils/sku.ts +26 -0
  38. package/src/typeDefs/cart.graphql +1 -1
  39. package/src/typeDefs/collection.graphql +12 -0
  40. package/src/typeDefs/mutation.graphql +2 -2
  41. package/src/typeDefs/order.graphql +1 -1
  42. package/src/typeDefs/pageInfo.graphql +5 -5
  43. package/src/typeDefs/query.graphql +59 -23
  44. package/src/typeDefs/session.graphql +4 -4
  45. package/src/typeDefs/status.graphql +1 -1
package/dist/index.d.ts CHANGED
@@ -49,6 +49,7 @@ export declare const getResolvers: (options: Options) => {
49
49
  StoreSeo: Record<string, import("./platforms/vtex").Resolver<{
50
50
  title?: string | undefined;
51
51
  description?: string | undefined;
52
+ canonical?: string | undefined;
52
53
  }, unknown, any>>;
53
54
  StoreFacet: Record<string, import("./platforms/vtex").Resolver<import("./platforms/vtex/clients/search/types/FacetSearchResult").Facet, unknown, any>>;
54
55
  StoreFacetValue: Record<string, import("./platforms/vtex").Resolver<import("./platforms/vtex/clients/search/types/FacetSearchResult").FacetValue, unknown, any>>;
@@ -57,7 +58,7 @@ export declare const getResolvers: (options: Options) => {
57
58
  } & {
58
59
  attachmentsValues?: import("./platforms/vtex/clients/commerce/types/OrderForm").Attachment[] | undefined;
59
60
  }> | (import("./platforms/vtex/clients/commerce/types/OrderForm").OrderFormItem & {
60
- product: Promise<import("./platforms/vtex/utils/enhanceSku").EnhancedSku>;
61
+ product: import("./platforms/vtex/utils/enhanceSku").EnhancedSku;
61
62
  }), unknown, any>>;
62
63
  StoreAggregateRating: Record<string, import("./platforms/vtex").Resolver<unknown, unknown, any>>;
63
64
  StoreReview: Record<string, import("./platforms/vtex").Resolver<unknown, unknown, any>>;
@@ -125,8 +126,8 @@ export declare const getResolvers: (options: Options) => {
125
126
  }, ctx: import("./platforms/vtex").Context) => Promise<{
126
127
  order: {
127
128
  orderNumber: string;
128
- acceptedOffer: {
129
- product: Promise<import("./platforms/vtex/utils/enhanceSku").EnhancedSku>;
129
+ acceptedOffer: Promise<{
130
+ product: import("./platforms/vtex/utils/enhanceSku").EnhancedSku;
130
131
  id: string;
131
132
  name: string;
132
133
  detailUrl: string;
@@ -170,7 +171,7 @@ export declare const getResolvers: (options: Options) => {
170
171
  total: number;
171
172
  };
172
173
  attachments: import("./platforms/vtex/clients/commerce/types/OrderForm").Attachment[];
173
- }[];
174
+ }>[];
174
175
  };
175
176
  messages: {
176
177
  text: any;
@@ -5,7 +5,7 @@ export interface CollectionPageType {
5
5
  url: string;
6
6
  title: string;
7
7
  metaTagDescription: string;
8
- pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory';
8
+ pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory' | 'Product';
9
9
  }
10
10
  export interface FallbackPageType {
11
11
  id: null;
@@ -77,6 +77,7 @@ export declare const getResolvers: (_: Options) => {
77
77
  StoreSeo: Record<string, Resolver<{
78
78
  title?: string | undefined;
79
79
  description?: string | undefined;
80
+ canonical?: string | undefined;
80
81
  }, unknown, any>>;
81
82
  StoreFacet: Record<string, Resolver<import("./clients/search/types/FacetSearchResult").Facet, unknown, any>>;
82
83
  StoreFacetValue: Record<string, Resolver<import("./clients/search/types/FacetSearchResult").FacetValue, unknown, any>>;
@@ -85,7 +86,7 @@ export declare const getResolvers: (_: Options) => {
85
86
  } & {
86
87
  attachmentsValues?: import("./clients/commerce/types/OrderForm").Attachment[] | undefined;
87
88
  }> | (import("./clients/commerce/types/OrderForm").OrderFormItem & {
88
- product: Promise<import("./utils/enhanceSku").EnhancedSku>;
89
+ product: import("./utils/enhanceSku").EnhancedSku;
89
90
  }), unknown, any>>;
90
91
  StoreAggregateRating: Record<string, Resolver<unknown, unknown, any>>;
91
92
  StoreReview: Record<string, Resolver<unknown, unknown, any>>;
@@ -153,8 +154,8 @@ export declare const getResolvers: (_: Options) => {
153
154
  }, ctx: Context) => Promise<{
154
155
  order: {
155
156
  orderNumber: string;
156
- acceptedOffer: {
157
- product: Promise<import("./utils/enhanceSku").EnhancedSku>;
157
+ acceptedOffer: Promise<{
158
+ product: import("./utils/enhanceSku").EnhancedSku;
158
159
  id: string;
159
160
  name: string;
160
161
  detailUrl: string;
@@ -198,7 +199,7 @@ export declare const getResolvers: (_: Options) => {
198
199
  total: number;
199
200
  };
200
201
  attachments: import("./clients/commerce/types/OrderForm").Attachment[];
201
- }[];
202
+ }>[];
202
203
  };
203
204
  messages: {
204
205
  text: any;
@@ -1,7 +1,7 @@
1
1
  import type { Context, Options } from '..';
2
2
  export declare type Loaders = ReturnType<typeof getLoaders>;
3
3
  export declare const getLoaders: (options: Options, { clients }: Context) => {
4
- skuLoader: import("dataloader")<import("../utils/facets").SelectedFacet[], import("../utils/enhanceSku").EnhancedSku, import("../utils/facets").SelectedFacet[]>;
4
+ skuLoader: import("dataloader")<string, import("../utils/enhanceSku").EnhancedSku, string>;
5
5
  simulationLoader: import("dataloader")<import("../clients/commerce/types/Simulation").PayloadItem[], import("../clients/commerce/types/Simulation").Simulation, import("../clients/commerce/types/Simulation").PayloadItem[]>;
6
6
  collectionLoader: import("dataloader")<string, import("../clients/commerce/types/Portal").CollectionPageType, string>;
7
7
  };
@@ -2,5 +2,4 @@ import DataLoader from 'dataloader';
2
2
  import type { EnhancedSku } from '../utils/enhanceSku';
3
3
  import type { Options } from '..';
4
4
  import type { Clients } from '../clients';
5
- import type { SelectedFacet } from '../utils/facets';
6
- export declare const getSkuLoader: (_: Options, clients: Clients) => DataLoader<SelectedFacet[], EnhancedSku, SelectedFacet[]>;
5
+ export declare const getSkuLoader: (_: Options, clients: Clients) => DataLoader<string, EnhancedSku, string>;
@@ -4,8 +4,8 @@ export declare const Mutation: {
4
4
  }, ctx: import("..").Context) => Promise<{
5
5
  order: {
6
6
  orderNumber: string;
7
- acceptedOffer: {
8
- product: Promise<import("../utils/enhanceSku").EnhancedSku>;
7
+ acceptedOffer: Promise<{
8
+ product: import("../utils/enhanceSku").EnhancedSku;
9
9
  id: string;
10
10
  name: string;
11
11
  detailUrl: string;
@@ -49,7 +49,7 @@ export declare const Mutation: {
49
49
  total: number;
50
50
  };
51
51
  attachments: import("../clients/commerce/types/OrderForm").Attachment[];
52
- }[];
52
+ }>[];
53
53
  };
54
54
  messages: {
55
55
  text: any;
@@ -4,7 +4,7 @@ import type { ArrayElementType } from '../../../typings';
4
4
  import type { EnhancedSku } from '../utils/enhanceSku';
5
5
  import type { OrderFormItem } from '../clients/commerce/types/OrderForm';
6
6
  declare type OrderFormProduct = OrderFormItem & {
7
- product: Promise<EnhancedSku>;
7
+ product: EnhancedSku;
8
8
  };
9
9
  declare type SearchProduct = ArrayElementType<ReturnType<typeof StoreAggregateOffer.offers>>;
10
10
  declare type Root = SearchProduct | OrderFormProduct;
@@ -2,6 +2,7 @@ import type { Resolver } from '..';
2
2
  declare type Root = {
3
3
  title?: string;
4
4
  description?: string;
5
+ canonical?: string;
5
6
  };
6
7
  export declare const StoreSeo: Record<string, Resolver<Root>>;
7
8
  export {};
@@ -18,8 +18,8 @@ export declare const validateCart: (_: unknown, { cart: { order } }: {
18
18
  }, ctx: Context) => Promise<{
19
19
  order: {
20
20
  orderNumber: string;
21
- acceptedOffer: {
22
- product: Promise<import("../utils/enhanceSku").EnhancedSku>;
21
+ acceptedOffer: Promise<{
22
+ product: import("../utils/enhanceSku").EnhancedSku;
23
23
  id: string;
24
24
  name: string;
25
25
  detailUrl: string;
@@ -63,7 +63,7 @@ export declare const validateCart: (_: unknown, { cart: { order } }: {
63
63
  total: number;
64
64
  };
65
65
  attachments: import("../clients/commerce/types/OrderForm").Attachment[];
66
- }[];
66
+ }>[];
67
67
  };
68
68
  messages: {
69
69
  text: any;
@@ -0,0 +1,2 @@
1
+ import type { Product } from '../clients/search/types/ProductSearchResult';
2
+ export declare const canonicalFromProduct: ({ linkText }: Product) => string;
@@ -13,5 +13,7 @@ export declare const transformSelectedFacet: ({ key, value }: SelectedFacet) =>
13
13
  key: string;
14
14
  value: string;
15
15
  };
16
+ export declare const findSlug: (facets?: SelectedFacet[] | null | undefined) => string | null;
17
+ export declare const findSkuId: (facets?: SelectedFacet[] | null | undefined) => string | null;
16
18
  export declare const findLocale: (facets?: SelectedFacet[] | null | undefined) => string | null;
17
19
  export declare const findChannel: (facets?: SelectedFacet[] | null | undefined) => string | null;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * More info at: https://en.wikipedia.org/wiki/Order_statistic
3
+ */
4
+ export declare const min: <T>(array: T[], cmp: (a: T, b: T) => number) => T;
@@ -0,0 +1,8 @@
1
+ import type { Item } from '../clients/search/types/ProductSearchResult';
2
+ /**
3
+ * This function implements Portal heuristics for returning the best sku for a product.
4
+ *
5
+ * The best sku is the one with the best (cheapest available) offer
6
+ * */
7
+ export declare const pickBestSku: (skus: Item[]) => Item;
8
+ export declare const isValidSkuId: (skuId: string) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/api",
3
- "version": "1.9.6",
3
+ "version": "1.9.9",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -17,6 +17,7 @@
17
17
  "develop": "tsdx watch --entry ./local/server.ts --target node --onSuccess \"node ./dist\"",
18
18
  "develop:no-server": "concurrently \"yarn generate -w\" \"tsdx watch\"",
19
19
  "build": "graphql-codegen --config codegen.yml && tsdx build",
20
+ "lint": "eslint src/**/*.ts",
20
21
  "test": "tsdx test",
21
22
  "generate": "graphql-codegen --config codegen.yml"
22
23
  },
@@ -44,5 +45,5 @@
44
45
  "peerDependencies": {
45
46
  "graphql": "^15.6.0"
46
47
  },
47
- "gitHead": "69e237e43d8fc3940fffdd2a9fa8d5ed43f40181"
48
+ "gitHead": "1f1d2c97e399fb44987cdbd7980157242f193abf"
48
49
  }
@@ -19,9 +19,9 @@ export type IStoreCart = {
19
19
  };
20
20
 
21
21
  export type IStoreCurrency = {
22
- /** Currency code, e.g: USD */
22
+ /** Currency code (e.g: USD). */
23
23
  code: Scalars['String'];
24
- /** Currency symbol, e.g: $ */
24
+ /** Currency symbol (e.g: $). */
25
25
  symbol: Scalars['String'];
26
26
  };
27
27
 
@@ -47,7 +47,7 @@ export type IStoreOffer = {
47
47
  seller: IStoreOrganization;
48
48
  };
49
49
 
50
- /** Offer input. */
50
+ /** Order input. */
51
51
  export type IStoreOrder = {
52
52
  /** Array with information on each accepted offer. */
53
53
  acceptedOffer: Array<IStoreOffer>;
@@ -96,9 +96,11 @@ export type IStorePropertyValue = {
96
96
  valueReference: Scalars['String'];
97
97
  };
98
98
 
99
- /** Selected facet input. */
99
+ /** Selected search facet input. */
100
100
  export type IStoreSelectedFacet = {
101
+ /** Selected search facet key. */
101
102
  key: Scalars['String'];
103
+ /** Selected search facet value. */
102
104
  value: Scalars['String'];
103
105
  };
104
106
 
@@ -120,9 +122,9 @@ export type IStoreSession = {
120
122
 
121
123
  export type Mutation = {
122
124
  __typename?: 'Mutation';
123
- /** Returns the order if anything has changed in it, or `null` if the order is valid. */
125
+ /** Checks for changes between the cart presented in the UI and the cart stored in the ecommerce platform. If changes are detected, it returns the cart stored on the platform. Otherwise, it returns `null`. */
124
126
  validateCart?: Maybe<StoreCart>;
125
- /** Validate session information. */
127
+ /** Updates a web session with the specified values. */
126
128
  validateSession?: Maybe<StoreSession>;
127
129
  };
128
130
 
@@ -139,15 +141,15 @@ export type MutationValidateSessionArgs = {
139
141
 
140
142
  export type Query = {
141
143
  __typename?: 'Query';
142
- /** All collections query. */
144
+ /** Returns information about all collections. */
143
145
  allCollections: StoreCollectionConnection;
144
- /** All products query. */
146
+ /** Returns information about all products. */
145
147
  allProducts: StoreProductConnection;
146
- /** Collection query. */
148
+ /** Returns the details of a collection based on the collection slug. */
147
149
  collection: StoreCollection;
148
- /** Product query. */
150
+ /** Returns the details of a product based on the specified locator. */
149
151
  product: StoreProduct;
150
- /** Search query. */
152
+ /** Returns the result of a product, facet, or suggestion search. */
151
153
  search: StoreSearchResult;
152
154
  };
153
155
 
@@ -241,7 +243,7 @@ export type StoreCart = {
241
243
  /** Shopping cart message. */
242
244
  export type StoreCartMessage = {
243
245
  __typename?: 'StoreCartMessage';
244
- /** Shopping cart message status, which can be `INFO`, `WARNING` OR `ERROR`. */
246
+ /** Shopping cart message status, which can be `INFO`, `WARNING` or `ERROR`. */
245
247
  status: StoreStatus;
246
248
  /** Shopping cart message text. */
247
249
  text: Scalars['String'];
@@ -264,21 +266,21 @@ export type StoreCollection = {
264
266
  type: StoreCollectionType;
265
267
  };
266
268
 
267
- /** Collection connection pagination information. */
269
+ /** Collection connections, including pagination information and collections returned by the query. */
268
270
  export type StoreCollectionConnection = {
269
271
  __typename?: 'StoreCollectionConnection';
270
- /** Array with collection connection page edges. */
272
+ /** Array with collection connection page edges, each containing a collection and a corresponding cursor.. */
271
273
  edges: Array<StoreCollectionEdge>;
272
- /** Collection connection page information. */
274
+ /** Collection pagination information. */
273
275
  pageInfo: StorePageInfo;
274
276
  };
275
277
 
276
- /** Collection pagination edge. */
278
+ /** Each collection edge contains a `node`, with product collection information, and a `cursor`, that can be used as a reference for pagination. */
277
279
  export type StoreCollectionEdge = {
278
280
  __typename?: 'StoreCollectionEdge';
279
- /** Collection pagination cursor. */
281
+ /** Collection cursor. Used as pagination reference. */
280
282
  cursor: Scalars['String'];
281
- /** Collection pagination node. */
283
+ /** Each collection node contains the information of a product collection returned by the query. */
282
284
  node: StoreCollection;
283
285
  };
284
286
 
@@ -300,18 +302,22 @@ export type StoreCollectionMeta = {
300
302
 
301
303
  /** Product collection type. Possible values are `Department`, `Category`, `Brand` or `Cluster`. */
302
304
  export const enum StoreCollectionType {
305
+ /** Product brand. */
303
306
  Brand = 'Brand',
307
+ /** Second level of product categorization. */
304
308
  Category = 'Category',
309
+ /** Product cluster. */
305
310
  Cluster = 'Cluster',
311
+ /** First level of product categorization. */
306
312
  Department = 'Department'
307
313
  };
308
314
 
309
315
  /** Currency information. */
310
316
  export type StoreCurrency = {
311
317
  __typename?: 'StoreCurrency';
312
- /** Currency code, e.g: USD */
318
+ /** Currency code (e.g: USD). */
313
319
  code: Scalars['String'];
314
- /** Currency symbol, e.g: $ */
320
+ /** Currency symbol (e.g: $). */
315
321
  symbol: Scalars['String'];
316
322
  };
317
323
 
@@ -330,7 +336,9 @@ export type StoreFacet = {
330
336
 
331
337
  /** Search facet type. */
332
338
  export const enum StoreFacetType {
339
+ /** Indicates boolean search facet. */
333
340
  Boolean = 'BOOLEAN',
341
+ /** Indicates range type search facet. */
334
342
  Range = 'RANGE'
335
343
  };
336
344
 
@@ -408,16 +416,16 @@ export type StoreOrganization = {
408
416
  identifier: Scalars['String'];
409
417
  };
410
418
 
411
- /** Page information. */
419
+ /** Whenever you make a query that allows for pagination, such as `allProducts` or `allCollections`, you can check `StorePageInfo` to learn more about the complete set of items and use it to paginate your queries. */
412
420
  export type StorePageInfo = {
413
421
  __typename?: 'StorePageInfo';
414
- /** Page cursor end. */
422
+ /** Cursor corresponding to the last possible item. */
415
423
  endCursor: Scalars['String'];
416
- /** Indicates whether next page exists. */
424
+ /** Indicates whether there is at least one more page with items after the ones returned in the current query. */
417
425
  hasNextPage: Scalars['Boolean'];
418
- /** Indicates whether previous page exists. */
426
+ /** Indicates whether there is at least one more page with items before the ones returned in the current query. */
419
427
  hasPreviousPage: Scalars['Boolean'];
420
- /** Page cursor start. */
428
+ /** Cursor corresponding to the first possible item. */
421
429
  startCursor: Scalars['String'];
422
430
  /** Total number of items (products or collections), not pages. */
423
431
  totalCount: Scalars['Int'];
@@ -471,21 +479,21 @@ export type StoreProduct = {
471
479
  slug: Scalars['String'];
472
480
  };
473
481
 
474
- /** Product connection pagination information. */
482
+ /** Product connections, including pagination information and products returned by the query. */
475
483
  export type StoreProductConnection = {
476
484
  __typename?: 'StoreProductConnection';
477
- /** Array with product connection page edges. */
485
+ /** Array with product connection edges, each containing a product and a corresponding cursor. */
478
486
  edges: Array<StoreProductEdge>;
479
- /** Product connection page information. */
487
+ /** Product pagination information. */
480
488
  pageInfo: StorePageInfo;
481
489
  };
482
490
 
483
- /** Product pagination edge. */
491
+ /** Each product edge contains a `node`, with product information, and a `cursor`, that can be used as a reference for pagination. */
484
492
  export type StoreProductEdge = {
485
493
  __typename?: 'StoreProductEdge';
486
- /** Product pagination cursor. */
494
+ /** Product cursor. Used as pagination reference. */
487
495
  cursor: Scalars['String'];
488
- /** Product pagination node. */
496
+ /** Each product node contains the information of a product returned by the query. */
489
497
  node: StoreProduct;
490
498
  };
491
499
 
@@ -574,19 +582,27 @@ export type StoreSession = {
574
582
  postalCode?: Maybe<Scalars['String']>;
575
583
  };
576
584
 
577
- /** Product sorting options used in search. */
585
+ /** Product search results sorting options. */
578
586
  export const enum StoreSort {
587
+ /** Sort by discount value, from highest to lowest. */
579
588
  DiscountDesc = 'discount_desc',
589
+ /** Sort by name, in alphabetical order. */
580
590
  NameAsc = 'name_asc',
591
+ /** Sort by name, in reverse alphabetical order. */
581
592
  NameDesc = 'name_desc',
593
+ /** Sort by orders, from highest to lowest. */
582
594
  OrdersDesc = 'orders_desc',
595
+ /** Sort by price, from lowest to highest. */
583
596
  PriceAsc = 'price_asc',
597
+ /** Sort by price, from highest to lowest. */
584
598
  PriceDesc = 'price_desc',
599
+ /** Sort by release date, from highest to lowest. */
585
600
  ReleaseDesc = 'release_desc',
601
+ /** Sort by product score, from highest to lowest. */
586
602
  ScoreDesc = 'score_desc'
587
603
  };
588
604
 
589
- /** Status used to indicate type of message. For instance, in shopping cart messages. */
605
+ /** Status used to indicate a message type. For instance, a shopping cart informative or error message. */
590
606
  export const enum StoreStatus {
591
607
  Error = 'ERROR',
592
608
  Info = 'INFO',
@@ -1,5 +1,5 @@
1
- import type { Context, Options } from '../../index'
2
1
  import { fetchAPI } from '../fetch'
2
+ import type { Context, Options } from '../../index'
3
3
  import type { Brand } from './types/Brand'
4
4
  import type { CategoryTree } from './types/CategoryTree'
5
5
  import type { OrderForm, OrderFormInputItem } from './types/OrderForm'
@@ -6,7 +6,7 @@ export interface CollectionPageType {
6
6
  url: string
7
7
  title: string
8
8
  metaTagDescription: string
9
- pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory'
9
+ pageType: 'Brand' | 'Category' | 'Department' | 'Subcategory' | 'Product'
10
10
  }
11
11
 
12
12
  export interface FallbackPageType {
@@ -77,16 +77,18 @@ const Resolvers = {
77
77
  Mutation,
78
78
  }
79
79
 
80
- export const getContextFactory = (options: Options) => (ctx: any): Context => {
81
- ctx.storage = {
82
- channel: ChannelMarshal.parse(options.channel),
83
- flags: options.flags ?? {},
84
- locale: options.locale,
85
- }
86
- ctx.clients = getClients(options, ctx)
87
- ctx.loaders = getLoaders(options, ctx)
80
+ export const getContextFactory =
81
+ (options: Options) =>
82
+ (ctx: any): Context => {
83
+ ctx.storage = {
84
+ channel: ChannelMarshal.parse(options.channel),
85
+ flags: options.flags ?? {},
86
+ locale: options.locale,
87
+ }
88
+ ctx.clients = getClients(options, ctx)
89
+ ctx.loaders = getLoaders(options, ctx)
88
90
 
89
- return ctx
90
- }
91
+ return ctx
92
+ }
91
93
 
92
94
  export const getResolvers = (_: Options) => Resolvers
@@ -1,26 +1,13 @@
1
1
  import DataLoader from 'dataloader'
2
2
 
3
3
  import { enhanceSku } from '../utils/enhanceSku'
4
- import { BadRequestError, NotFoundError } from '../../errors'
4
+ import { NotFoundError } from '../../errors'
5
5
  import type { EnhancedSku } from '../utils/enhanceSku'
6
6
  import type { Options } from '..'
7
7
  import type { Clients } from '../clients'
8
- import type { SelectedFacet } from '../utils/facets'
9
8
 
10
9
  export const getSkuLoader = (_: Options, clients: Clients) => {
11
- const loader = async (facetsList: readonly SelectedFacet[][]) => {
12
- const skuIds = facetsList.map((facets) => {
13
- const maybeFacet = facets.find(({ key }) => key === 'id')
14
-
15
- if (!maybeFacet) {
16
- throw new BadRequestError(
17
- 'Error while loading SKU. Needs to pass an id to selected facets'
18
- )
19
- }
20
-
21
- return maybeFacet.value
22
- })
23
-
10
+ const loader = async (skuIds: readonly string[]) => {
24
11
  const { products } = await clients.search.products({
25
12
  query: `sku:${skuIds.join(';')}`,
26
13
  page: 0,
@@ -47,7 +34,7 @@ export const getSkuLoader = (_: Options, clients: Clients) => {
47
34
  return skus
48
35
  }
49
36
 
50
- return new DataLoader<SelectedFacet[], EnhancedSku>(loader, {
37
+ return new DataLoader<string, EnhancedSku>(loader, {
51
38
  maxBatchSize: 99, // Max allowed batch size of Search API
52
39
  })
53
40
  }
@@ -11,7 +11,7 @@ import type { ArrayElementType } from '../../../typings'
11
11
  import type { EnhancedSku } from '../utils/enhanceSku'
12
12
  import type { OrderFormItem } from '../clients/commerce/types/OrderForm'
13
13
 
14
- type OrderFormProduct = OrderFormItem & { product: Promise<EnhancedSku> }
14
+ type OrderFormProduct = OrderFormItem & { product: EnhancedSku }
15
15
  type SearchProduct = ArrayElementType<
16
16
  ReturnType<typeof StoreAggregateOffer.offers>
17
17
  >
@@ -96,13 +96,16 @@ export const StoreOffer: Record<string, Resolver<Root>> = {
96
96
 
97
97
  return null
98
98
  },
99
- itemOffered: async (root) => {
99
+ itemOffered: (root) => {
100
100
  if (isSearchItem(root)) {
101
101
  return root.product
102
102
  }
103
103
 
104
104
  if (isOrderFormItem(root)) {
105
- return { ...(await root.product), attachmentsValues: root.attachments }
105
+ return {
106
+ ...root.product,
107
+ attachmentsValues: root.attachments,
108
+ }
106
109
  }
107
110
 
108
111
  return null
@@ -1,3 +1,4 @@
1
+ import { canonicalFromProduct } from '../utils/canonical'
1
2
  import { enhanceCommercialOffer } from '../utils/enhanceCommercialOffer'
2
3
  import { bestOfferFirst } from '../utils/productStock'
3
4
  import { slugify } from '../utils/slugify'
@@ -41,9 +42,10 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
41
42
  name: ({ isVariantOf, name }) => name ?? isVariantOf.productName,
42
43
  slug: ({ isVariantOf: { linkText }, itemId }) => getSlug(linkText, itemId),
43
44
  description: ({ isVariantOf: { description } }) => description,
44
- seo: ({ isVariantOf: { description, productName } }) => ({
45
- title: productName,
46
- description,
45
+ seo: ({ isVariantOf }) => ({
46
+ title: isVariantOf.productName,
47
+ description: isVariantOf.description,
48
+ canonical: canonicalFromProduct(isVariantOf),
47
49
  }),
48
50
  brand: ({ isVariantOf: { brand } }) => ({ name: brand }),
49
51
  breadcrumbList: ({
@@ -85,7 +87,7 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
85
87
  aggregateRating: () => ({}),
86
88
  offers: (root) =>
87
89
  root.sellers
88
- .flatMap((seller) =>
90
+ .map((seller) =>
89
91
  enhanceCommercialOffer({
90
92
  offer: seller.commertialOffer,
91
93
  seller,
@@ -1,8 +1,11 @@
1
+ import { NotFoundError, BadRequestError } from '../../errors'
1
2
  import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
2
3
  import { enhanceSku } from '../utils/enhanceSku'
3
4
  import {
4
5
  findChannel,
5
6
  findLocale,
7
+ findSkuId,
8
+ findSlug,
6
9
  transformSelectedFacet,
7
10
  } from '../utils/facets'
8
11
  import { SORT_MAP } from '../utils/sort'
@@ -16,12 +19,15 @@ import type {
16
19
  } from '../../../__generated__/schema'
17
20
  import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
18
21
  import type { Context } from '../index'
22
+ import { isValidSkuId, pickBestSku } from '../utils/sku'
19
23
 
20
24
  export const Query = {
21
25
  product: async (_: unknown, { locator }: QueryProductArgs, ctx: Context) => {
22
26
  // Insert channel in context for later usage
23
27
  const channel = findChannel(locator)
24
28
  const locale = findLocale(locator)
29
+ const id = findSkuId(locator)
30
+ const slug = findSlug(locator)
25
31
 
26
32
  if (channel) {
27
33
  mutateChannelContext(ctx, channel)
@@ -33,9 +39,46 @@ export const Query = {
33
39
 
34
40
  const {
35
41
  loaders: { skuLoader },
42
+ clients: { commerce, search },
36
43
  } = ctx
37
44
 
38
- return skuLoader.load(locator)
45
+ try {
46
+ const skuId = id ?? slug?.split('-').pop() ?? ''
47
+
48
+ if (!isValidSkuId(skuId)) {
49
+ throw new Error('Invalid SkuId')
50
+ }
51
+
52
+ const sku = await skuLoader.load(skuId)
53
+
54
+ return sku
55
+ } catch (err) {
56
+ if (slug == null) {
57
+ throw new BadRequestError('Missing slug or id')
58
+ }
59
+
60
+ const route = await commerce.catalog.portal.pagetype(`${slug}/p`)
61
+
62
+ if (route.pageType !== 'Product' || !route.id) {
63
+ throw new NotFoundError(`No product found for slug ${slug}`)
64
+ }
65
+
66
+ const {
67
+ products: [product],
68
+ } = await search.products({
69
+ page: 0,
70
+ count: 1,
71
+ query: `product:${route.id}`,
72
+ })
73
+
74
+ if (!product) {
75
+ throw new NotFoundError(`No product found for id ${route.id}`)
76
+ }
77
+
78
+ const sku = pickBestSku(product.items)
79
+
80
+ return enhanceSku(sku, product)
81
+ }
39
82
  },
40
83
  collection: (_: unknown, { slug }: QueryCollectionArgs, ctx: Context) => {
41
84
  const {