@faststore/api 4.5.0-dev.3 → 4.5.0-dev.5

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 (59) hide show
  1. package/dist/cjs/index.js +105 -44
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/es/index.mjs +1164 -901
  4. package/dist/es/index.mjs.map +1 -1
  5. package/dist/src/__generated__/schema.d.ts +52 -3
  6. package/dist/src/__generated__/schema.d.ts.map +1 -1
  7. package/dist/src/platforms/vtex/clients/catalog/index.d.ts +34 -0
  8. package/dist/src/platforms/vtex/clients/catalog/index.d.ts.map +1 -0
  9. package/dist/src/platforms/vtex/clients/commerce/index.d.ts +6 -0
  10. package/dist/src/platforms/vtex/clients/commerce/index.d.ts.map +1 -1
  11. package/dist/src/platforms/vtex/clients/commerce/types/ByLinkId.d.ts +45 -0
  12. package/dist/src/platforms/vtex/clients/commerce/types/ByLinkId.d.ts.map +1 -0
  13. package/dist/src/platforms/vtex/clients/commerce/types/OrderForm.d.ts +5 -0
  14. package/dist/src/platforms/vtex/clients/commerce/types/OrderForm.d.ts.map +1 -1
  15. package/dist/src/platforms/vtex/clients/index.d.ts +8 -0
  16. package/dist/src/platforms/vtex/clients/index.d.ts.map +1 -1
  17. package/dist/src/platforms/vtex/clients/search/index.d.ts.map +1 -1
  18. package/dist/src/platforms/vtex/clients/search/types/ProductSearchResult.d.ts +7 -0
  19. package/dist/src/platforms/vtex/clients/search/types/ProductSearchResult.d.ts.map +1 -1
  20. package/dist/src/platforms/vtex/index.d.ts +12 -13
  21. package/dist/src/platforms/vtex/index.d.ts.map +1 -1
  22. package/dist/src/platforms/vtex/loaders/collection.d.ts +32 -3
  23. package/dist/src/platforms/vtex/loaders/collection.d.ts.map +1 -1
  24. package/dist/src/platforms/vtex/loaders/index.d.ts +2 -2
  25. package/dist/src/platforms/vtex/loaders/index.d.ts.map +1 -1
  26. package/dist/src/platforms/vtex/resolvers/collection.d.ts +20 -2
  27. package/dist/src/platforms/vtex/resolvers/collection.d.ts.map +1 -1
  28. package/dist/src/platforms/vtex/resolvers/getOrderEntryOperation.d.ts.map +1 -1
  29. package/dist/src/platforms/vtex/resolvers/index.d.ts +3 -13
  30. package/dist/src/platforms/vtex/resolvers/index.d.ts.map +1 -1
  31. package/dist/src/platforms/vtex/resolvers/offer.d.ts.map +1 -1
  32. package/dist/src/platforms/vtex/resolvers/product.d.ts +13 -1
  33. package/dist/src/platforms/vtex/resolvers/product.d.ts.map +1 -1
  34. package/dist/src/platforms/vtex/resolvers/query.d.ts +8 -17
  35. package/dist/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
  36. package/dist/src/platforms/vtex/resolvers/validateCart.d.ts.map +1 -1
  37. package/dist/src/platforms/vtex/utils/localization.d.ts +28 -0
  38. package/dist/src/platforms/vtex/utils/localization.d.ts.map +1 -0
  39. package/package.json +3 -3
  40. package/src/__generated__/schema.ts +54 -3
  41. package/src/platforms/vtex/clients/catalog/index.ts +52 -0
  42. package/src/platforms/vtex/clients/commerce/index.ts +71 -0
  43. package/src/platforms/vtex/clients/commerce/types/ByLinkId.ts +46 -0
  44. package/src/platforms/vtex/clients/commerce/types/OrderForm.ts +5 -0
  45. package/src/platforms/vtex/clients/index.ts +3 -0
  46. package/src/platforms/vtex/clients/search/index.ts +16 -4
  47. package/src/platforms/vtex/clients/search/types/ProductSearchResult.ts +7 -0
  48. package/src/platforms/vtex/index.ts +17 -2
  49. package/src/platforms/vtex/loaders/collection.ts +99 -25
  50. package/src/platforms/vtex/loaders/index.ts +2 -1
  51. package/src/platforms/vtex/resolvers/collection.ts +173 -66
  52. package/src/platforms/vtex/resolvers/offer.ts +7 -0
  53. package/src/platforms/vtex/resolvers/product.ts +160 -11
  54. package/src/platforms/vtex/resolvers/query.ts +148 -56
  55. package/src/platforms/vtex/resolvers/validateCart.ts +6 -1
  56. package/src/platforms/vtex/typeDefs/collection.graphql +33 -1
  57. package/src/platforms/vtex/typeDefs/offer.graphql +10 -0
  58. package/src/platforms/vtex/typeDefs/product.graphql +19 -0
  59. package/src/platforms/vtex/utils/localization.ts +45 -0
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,uBAAuB,EACvB,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EAEd,MAAM,+BAA+B,CAAA;AAWtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAA;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAkC9C,eAAO,MAAM,KAAK;iBAEX,OAAO,eACG,gBAAgB,OACxB,cAAc;oBAgFhB,OAAO,YACA,mBAAmB,OACxB,cAAc;gBAShB,OAAO,6EAQP,eAAe,OACb,cAAc;;;;qBA4EhB,OAAO,gCACoB,oBAAoB,OAC7C,cAAc;;;;;;;;;;;;;kBAkChB,OAAO,kBACM,iBAAiB,OAC5B,cAAc;wBAwBhB,OAAO,gCACoB,uBAAuB,OAChD,cAAc;;;;;;;;;;;;;;;;;;uBAa6B,MAAM;;;;;kBA2CnD,OAAO,kCACsB,iBAAiB,OAC5C,cAAc;;;;;;;;;;;;;;;;;kBAkBhB,OAAO,4BACgB,iBAAiB,OACtC,cAAc;;;iBAqBhB,OAAO,yDAC6C,gBAAgB,OAClE,cAAc;;;;iBAqBhB,OAAO,UACF,gBAAgB,OACnB,cAAc;;;sBAyBhB,OAAO,YACA,qBAAqB,OAC1B,cAAc;mBAahB,OAAO,eACG,kBAAkB,OAC1B,cAAc;;;;;;;;;;;;;;;;;;;;;;;wBAqGhB,OAAO,WACD,uBAAuB,OAC3B,cAAc;;;;;;;;;;;;;;;wBAyBhB,OAAO,WACD;QACP,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;QACjB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,OACI,cAAc;;;;;;;;;;;;;;;;sBAoDG,OAAO,MAAM,OAAO,QAAQ,cAAc;;;8BAQ7D,OAAO,MACN,OAAO,OACN,cAAc;qBAWE,OAAO,MAAM,OAAO,OAAO,cAAc;;;;;;;;wBAqCtC,OAAO,MAAM,OAAO,OAAO,cAAc;;;;;4BAmD9D,OAAO,iBACK,2BAA2B,OACrC,cAAc,KAClB,OAAO,CAAC,aAAa,EAAE,CAAC;sBA4CtB,OAAO,sBACU,qBAAqB,OACpC,cAAc;;;;;;;oBAgBy1O,CAAC;;;;;;;;;;;;;;;;;;;;CADh3O,CAAA"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,uBAAuB,EACvB,oBAAoB,EACpB,2BAA2B,EAC3B,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EAEd,MAAM,+BAA+B,CAAA;AAUtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAA;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,uBAAuB,CAAA;AAU9B,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AA4HlE,eAAO,MAAM,KAAK;iBAEX,OAAO,eACG,gBAAgB,OACxB,cAAc;oBAyChB,OAAO,YACA,mBAAmB,OACxB,cAAc;gBAgBhB,OAAO,6EAQP,eAAe,OACb,cAAc;;;;qBA4EhB,OAAO,gCACoB,oBAAoB,OAC7C,cAAc;;;;;;;;;;;;;kBAkChB,OAAO,kBACM,iBAAiB,OAC5B,cAAc;wBAwBhB,OAAO,gCACoB,uBAAuB,OAChD,cAAc;;;;;;;;;;;;;kBA8EhB,OAAO,kCACsB,iBAAiB,OAC5C,cAAc;;;;;;;;;;;;;;;;;kBAkBhB,OAAO,4BACgB,iBAAiB,OACtC,cAAc;;;iBAqBhB,OAAO,yDAC6C,gBAAgB,OAClE,cAAc;;;;iBAqBhB,OAAO,UACF,gBAAgB,OACnB,cAAc;;;sBAyBhB,OAAO,YACA,qBAAqB,OAC1B,cAAc;mBAahB,OAAO,eACG,kBAAkB,OAC1B,cAAc;;;;;;;;;;;;;;;;;;;;;;;wBAqGhB,OAAO,WACD,uBAAuB,OAC3B,cAAc;;;;;;;;;;;;;;;wBAyBhB,OAAO,WACD;QACP,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;QACjB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,OACI,cAAc;;;;;;;;;;;;;;;;sBAoDG,OAAO,MAAM,OAAO,QAAQ,cAAc;;;8BAQ7D,OAAO,MACN,OAAO,OACN,cAAc;qBAWE,OAAO,MAAM,OAAO,OAAO,cAAc;;;;;;;;wBAqCtC,OAAO,MAAM,OAAO,OAAO,cAAc;;;;;4BAmD9D,OAAO,iBACK,2BAA2B,OACrC,cAAc,KAClB,OAAO,CAAC,aAAa,EAAE,CAAC;sBA4CtB,OAAO,sBACU,qBAAqB,OACpC,cAAc;;;;;;;oBAgB01N,CAAC;;;;;;;;;;;;;;;;;;;;CADj3N,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"validateCart.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/validateCart.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AACxC,OAAO,KAAK,EAMV,wBAAwB,EACzB,MAAM,+BAA+B,CAAA;AA0TtC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GACvB,GAAG,OAAO,EACV,8BAA8B,wBAAwB,EACtD,KAAK,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAkMpB,CAAA"}
1
+ {"version":3,"file":"validateCart.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/validateCart.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AACxC,OAAO,KAAK,EAMV,wBAAwB,EACzB,MAAM,+BAA+B,CAAA;AA+TtC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GACvB,GAAG,OAAO,EACV,8BAA8B,wBAAwB,EACtD,KAAK,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAkMpB,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { GraphqlContext } from '..';
2
+ export interface LocalizationConfig {
3
+ enabled?: boolean;
4
+ defaultLocale?: string;
5
+ locales?: Record<string, unknown>;
6
+ }
7
+ export declare const getLocalizationConfig: (ctx: GraphqlContext) => LocalizationConfig;
8
+ export declare const isLocalizationEnabled: (ctx: GraphqlContext) => boolean;
9
+ export declare const getConfiguredLocales: (ctx: GraphqlContext) => string[];
10
+ export declare const getDefaultLocale: (ctx: GraphqlContext) => string | undefined;
11
+ /**
12
+ * Whether `locale` is one of the store's configured locales. Used to validate
13
+ * client-supplied locale input before propagating it to downstream platform
14
+ * APIs. Returns false when localization is disabled (no configured locales).
15
+ */
16
+ export declare const isConfiguredLocale: (ctx: GraphqlContext, locale: string) => boolean;
17
+ /**
18
+ * Locale to forward to Catalog by-linkid endpoints via the Accept-Language
19
+ * header so they return localized name/title/description. Returns the active
20
+ * locale only when localization is enabled; otherwise `undefined`, so
21
+ * non-localized stores send no header and falls back to the store's default
22
+ * registered language (legacy behavior).
23
+ *
24
+ * Read at request time (not client construction) because `ctx.storage.locale`
25
+ * is mutated later by the collection resolver.
26
+ */
27
+ export declare const getCatalogLocale: (ctx: GraphqlContext) => string | undefined;
28
+ //# sourceMappingURL=localization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localization.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/utils/localization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AAExC,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED,eAAO,MAAM,qBAAqB,GAChC,KAAK,cAAc,KAClB,kBAEqB,CAAA;AAExB,eAAO,MAAM,qBAAqB,GAAI,KAAK,cAAc,KAAG,OACf,CAAA;AAE7C,eAAO,MAAM,oBAAoB,GAAI,KAAK,cAAc,KAAG,MAAM,EACV,CAAA;AAEvD,eAAO,MAAM,gBAAgB,GAAI,KAAK,cAAc,KAAG,MAAM,GAAG,SACtB,CAAA;AAE1C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,KAAK,cAAc,EACnB,QAAQ,MAAM,KACb,OAAqD,CAAA;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,cAAc,KAAG,MAAM,GAAG,SACU,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/api",
3
- "version": "4.5.0-dev.3",
3
+ "version": "4.5.0-dev.5",
4
4
  "license": "MIT",
5
5
  "main": "dist/cjs/index.js",
6
6
  "typings": "dist/src/index.d.ts",
@@ -58,11 +58,11 @@
58
58
  "vite": "7.1.5",
59
59
  "vite-plugin-dts": "4.5.4",
60
60
  "vitest": "^4.0.7",
61
- "@faststore/diagnostics": "^4.5.0-dev.3"
61
+ "@faststore/diagnostics": "^4.5.0-dev.5"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "graphql": "16.11.0",
65
- "@faststore/diagnostics": "4.5.0-dev.3"
65
+ "@faststore/diagnostics": "4.5.0-dev.5"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"
@@ -510,6 +510,8 @@ export type IStoreOffer = {
510
510
  listPrice: Scalars['Float']['input'];
511
511
  /** Also known as spot price. */
512
512
  price: Scalars['Float']['input'];
513
+ /** Signed price token (JWT) from the Pricing Fallback feature, forwarded to Checkout on add-to-cart. */
514
+ priceToken?: InputMaybe<Scalars['String']['input']>;
513
515
  /** Number of items offered. */
514
516
  quantity: Scalars['Int']['input'];
515
517
  /** Seller responsible for the offer. */
@@ -1347,6 +1349,11 @@ export type StoreCollection = {
1347
1349
  id: Scalars['ID']['output'];
1348
1350
  /** Collection meta information. Used for search. */
1349
1351
  meta: StoreCollectionMeta;
1352
+ /**
1353
+ * Localized versions of this collection for all available locales.
1354
+ * Only populated when localization is enabled.
1355
+ */
1356
+ otherLocales?: Maybe<Array<StoreCollectionLocale>>;
1350
1357
  /** Meta tag data. */
1351
1358
  seo: StoreSeo;
1352
1359
  /** Corresponding collection URL slug, with which to retrieve this entity. */
@@ -1382,6 +1389,15 @@ export type StoreCollectionFacet = {
1382
1389
  value: Scalars['String']['output'];
1383
1390
  };
1384
1391
 
1392
+ /** Localized collection data for a specific locale. */
1393
+ export type StoreCollectionLocale = {
1394
+ __typename?: 'StoreCollectionLocale';
1395
+ /** Locale code (e.g. "pt-BR", "it-IT"). */
1396
+ locale: Scalars['String']['output'];
1397
+ /** Localized collection slug (e.g. "vestuario/camisetas"). */
1398
+ slug: Scalars['String']['output'];
1399
+ };
1400
+
1385
1401
  /** Collection meta information. Used for search. */
1386
1402
  export type StoreCollectionMeta = {
1387
1403
  __typename?: 'StoreCollectionMeta';
@@ -1389,19 +1405,34 @@ export type StoreCollectionMeta = {
1389
1405
  selectedFacets: Array<StoreCollectionFacet>;
1390
1406
  };
1391
1407
 
1392
- /** Product collection type. Possible values are `Department`, `Category`, `Brand`, `Cluster`, `SubCategory` or `Collection`. */
1408
+ /**
1409
+ * Product collection type. Possible values are `Department`, `Category`, `Brand` or `Collection`.
1410
+ *
1411
+ * `SubCategory` and `Cluster` are still declared for backward compatibility but are
1412
+ * deprecated and never returned.
1413
+ */
1393
1414
  export const enum StoreCollectionType {
1394
1415
  /** Product brand. */
1395
1416
  Brand = 'Brand',
1396
1417
  /** Second level of product categorization. */
1397
1418
  Category = 'Category',
1398
- /** Product cluster. */
1419
+ /**
1420
+ * Product cluster.
1421
+ *
1422
+ * Deprecated: never returned — clusters resolve as `Collection`.
1423
+ * @deprecated Never returned since the by-linkid migration: clusters and curated collections are both served by `collection/by-linkid`, whose response carries no discriminator between them, so both resolve as `Collection`. Scheduled for removal in the next major.
1424
+ */
1399
1425
  Cluster = 'Cluster',
1400
1426
  /** Product collection. */
1401
1427
  Collection = 'Collection',
1402
1428
  /** First level of product categorization. */
1403
1429
  Department = 'Department',
1404
- /** Third level of product categorization. */
1430
+ /**
1431
+ * Third level of product categorization.
1432
+ *
1433
+ * Deprecated: never returned — third-level categories resolve as `Category`.
1434
+ * @deprecated Never returned since the by-linkid migration: the category response only exposes `fatherCategoryId`, which distinguishes root from non-root but not tree depth, so third-level categories resolve as `Category`. Scheduled for removal in the next major.
1435
+ */
1405
1436
  SubCategory = 'SubCategory'
1406
1437
  };
1407
1438
 
@@ -1560,6 +1591,12 @@ export type StoreOffer = {
1560
1591
  price: Scalars['Float']['output'];
1561
1592
  /** ISO code of the currency used for the offer prices. */
1562
1593
  priceCurrency: Scalars['String']['output'];
1594
+ /**
1595
+ * Signed price token (JWT) used by the Pricing Fallback feature. When present, it should be
1596
+ * forwarded to Checkout on add-to-cart so the platform can trust the offer's price even if
1597
+ * the Pricing System is unavailable. May be null when the signing step fails or the feature is disabled.
1598
+ */
1599
+ priceToken?: Maybe<Scalars['String']['output']>;
1563
1600
  /** Next date in which price is scheduled to change. If there is no scheduled change, this will be set a year in the future from current time. */
1564
1601
  priceValidUntil: Scalars['String']['output'];
1565
1602
  /** Also known as spot price with taxes. */
@@ -1688,6 +1725,11 @@ export type StoreProduct = {
1688
1725
  name: Scalars['String']['output'];
1689
1726
  /** Aggregate offer information. */
1690
1727
  offers: StoreAggregateOffer;
1728
+ /**
1729
+ * Localized versions of this product for all available locales.
1730
+ * Only populated when localization is enabled.
1731
+ */
1732
+ otherLocales?: Maybe<Array<StoreProductLocale>>;
1691
1733
  /** Product ID, such as [ISBN](https://www.isbn-international.org/content/what-isbn) or similar global IDs. */
1692
1734
  productID: Scalars['String']['output'];
1693
1735
  /** The product's release date. Formatted using https://en.wikipedia.org/wiki/ISO_8601 */
@@ -1752,6 +1794,15 @@ export type StoreProductGroup = {
1752
1794
  skuVariants?: Maybe<SkuVariants>;
1753
1795
  };
1754
1796
 
1797
+ /** Localized product data for a specific locale. */
1798
+ export type StoreProductLocale = {
1799
+ __typename?: 'StoreProductLocale';
1800
+ /** Locale code (e.g. "pt-BR", "it-IT"). */
1801
+ locale: Scalars['String']['output'];
1802
+ /** Localized product slug including the SKU ID suffix (e.g. "adidas-polo-uomo-65"). */
1803
+ slug: Scalars['String']['output'];
1804
+ };
1805
+
1755
1806
  /** Properties that can be associated with products and products groups. */
1756
1807
  export type StorePropertyValue = {
1757
1808
  __typename?: 'StorePropertyValue';
@@ -0,0 +1,52 @@
1
+ import { fetchAPI } from '../fetch'
2
+
3
+ export interface LocalizedCategoryEntry {
4
+ id: number
5
+ name: string
6
+ /** Slash-separated IDs from root to this node, e.g. "9281/9285". Used to determine depth. */
7
+ fullPath: string
8
+ /** Slash-separated localized slugs from root to this node, e.g. "apparel/t-shirts". */
9
+ fullPathUriName: string
10
+ }
11
+
12
+ export interface LocalizedProductEntry {
13
+ linkId: string
14
+ /** All categories the product belongs to across all trees, as returned by Catalog Dataplane. */
15
+ categories: LocalizedCategoryEntry[]
16
+ /** Localized linkIds keyed by locale, covering all available locales in a single response. */
17
+ availableLinkIds: Record<string, string>
18
+ }
19
+
20
+ export interface LocalizedProductResponse {
21
+ id: number
22
+ linkId: string
23
+ name: string
24
+ /** Leaf category (deepest level) the product is registered under. */
25
+ category: LocalizedCategoryEntry | null
26
+ /** Full ancestry chain for every category tree the product belongs to. */
27
+ categories: LocalizedCategoryEntry[]
28
+ /** Localized linkIds keyed by locale, covering all available locales in a single response. */
29
+ availableLinkIds: Record<string, string>
30
+ }
31
+
32
+ /**
33
+ * Client for the VTEX Catalog Dataplane API.
34
+ * Uses Accept-Language header to return locale-specific product data.
35
+ */
36
+ export const CatalogDataplane = ({ account, environment }: Options) => {
37
+ const base = `https://${account}.${environment}.com.br`
38
+
39
+ return {
40
+ getLocalizedProduct: (
41
+ productId: string,
42
+ locale: string
43
+ ): Promise<LocalizedProductResponse> =>
44
+ fetchAPI(`${base}/api/catalog-dataplane/product/${productId}`, {
45
+ method: 'GET',
46
+ headers: {
47
+ 'Accept-Language': locale,
48
+ 'Content-Type': 'application/json',
49
+ },
50
+ }),
51
+ }
52
+ }
@@ -14,6 +14,7 @@ import {
14
14
  type UserOrderCancel,
15
15
  type UserOrderListResult,
16
16
  } from '../../../..'
17
+ import { isNotFoundError } from '../../../errors'
17
18
  import type { GraphqlContext } from '../../index'
18
19
  import { getWithAppKeyAndToken } from '../../utils/auth'
19
20
  import type { Channel } from '../../utils/channel'
@@ -26,6 +27,11 @@ import {
26
27
  import type { ContractResponse } from './Contract'
27
28
  import type { Address, AddressInput } from './types/Address'
28
29
  import type { Brand } from './types/Brand'
30
+ import type {
31
+ ByLinkIdBrandResponse,
32
+ ByLinkIdCategoryResponse,
33
+ ByLinkIdCollectionResponse,
34
+ } from './types/ByLinkId'
29
35
  import type { CategoryTree } from './types/CategoryTree'
30
36
  import type { MasterDataResponse } from './types/Newsletter'
31
37
  import type {
@@ -74,6 +80,24 @@ const BASE_INIT = {
74
80
  },
75
81
  }
76
82
 
83
+ /**
84
+ * Encode a by-linkid path for the category endpoint. Category link paths can be
85
+ * multi-segment (e.g. "computer---software/eletronicos"); the Catalog endpoint
86
+ * expects the "/" separators to stay literal so it can validate each level,
87
+ * while each segment is individually URL-encoded. Running encodeURIComponent on
88
+ * the whole string would turn "/" into "%2F" and break multi-segment resolution.
89
+ */
90
+ const encodeLinkIdPath = (linkId: string): string =>
91
+ linkId.split('/').map(encodeURIComponent).join('/')
92
+
93
+ /**
94
+ * Build the fetch init that forwards a locale to a by-linkid endpoint. When no
95
+ * locale is provided the endpoint falls back to the store's default registered
96
+ * language (non-localized stores behavior).
97
+ */
98
+ const byLinkIdInit = (locale?: string): RequestInit | undefined =>
99
+ locale ? { headers: { 'Accept-Language': locale } } : undefined
100
+
77
101
  const QUOTE_ISO_DATE = /^\d{4}-\d{2}-\d{2}$/
78
102
  const QUOTE_VALID_STATUSES = new Set([
79
103
  'Draft',
@@ -222,6 +246,53 @@ export const VtexCommerce = (
222
246
  { storeCookies }
223
247
  ),
224
248
  },
249
+ byLinkId: {
250
+ // The by-linkid endpoints resolve a slug to a single catalog entity
251
+ // (or 404 when there is no match). We surface a 404 as `null` so the
252
+ // loader can cascade category → brand → collection.
253
+ category: async (
254
+ linkId: string,
255
+ locale?: string
256
+ ): Promise<ByLinkIdCategoryResponse | null> => {
257
+ try {
258
+ return await fetchAPI(
259
+ `${base}/api/catalog_system/pub/category/by-linkid/${encodeLinkIdPath(linkId)}`,
260
+ byLinkIdInit(locale)
261
+ )
262
+ } catch (error) {
263
+ if (isNotFoundError(error)) return null
264
+ throw error
265
+ }
266
+ },
267
+ brand: async (
268
+ linkId: string,
269
+ locale?: string
270
+ ): Promise<ByLinkIdBrandResponse | null> => {
271
+ try {
272
+ return await fetchAPI(
273
+ `${base}/api/catalog_system/pub/brand/by-linkid/${encodeURIComponent(linkId)}`,
274
+ byLinkIdInit(locale)
275
+ )
276
+ } catch (error) {
277
+ if (isNotFoundError(error)) return null
278
+ throw error
279
+ }
280
+ },
281
+ collection: async (
282
+ linkId: string,
283
+ locale?: string
284
+ ): Promise<ByLinkIdCollectionResponse | null> => {
285
+ try {
286
+ return await fetchAPI(
287
+ `${base}/api/catalog_system/pub/collection/by-linkid/${encodeURIComponent(linkId)}`,
288
+ byLinkIdInit(locale)
289
+ )
290
+ } catch (error) {
291
+ if (isNotFoundError(error)) return null
292
+ throw error
293
+ }
294
+ },
295
+ },
225
296
  products: {
226
297
  crossselling: ({
227
298
  type,
@@ -0,0 +1,46 @@
1
+ export interface ByLinkIdCategoryResponse {
2
+ id: number
3
+ fatherCategoryId: number | null
4
+ name: string
5
+ linkId: string
6
+ title: string | null
7
+ description: string | null
8
+ /**
9
+ * Optional SEO meta description. pagetype.metaTagDescription
10
+ * was backed by the same source as `description`; prefer `description` for
11
+ * parity when this field is absent.
12
+ */
13
+ metaTagDescription: string | null
14
+ /** Localized linkIds keyed by locale. Null when no multilanguage entries are registered. */
15
+ availableLinkIds: Record<string, string> | null
16
+ }
17
+
18
+ export interface ByLinkIdBrandResponse {
19
+ id: number
20
+ name: string
21
+ linkId: string
22
+ title: string | null
23
+ description: string | null
24
+ /**
25
+ * Optional SEO meta description. Catalog confirmed pagetype.metaTagDescription
26
+ * was backed by the same source as `description`; prefer `description` for
27
+ * parity when this field is absent.
28
+ */
29
+ metaTagDescription: string | null
30
+ availableLinkIds: Record<string, string> | null
31
+ }
32
+
33
+ export interface ByLinkIdCollectionResponse {
34
+ id: number
35
+ name: string
36
+ linkId: string | null
37
+ title: string | null
38
+ description: string | null
39
+ /**
40
+ * Optional SEO meta description. pagetype.metaTagDescription
41
+ * was backed by the same source as `description`; prefer `description` for
42
+ * parity when this field is absent.
43
+ */
44
+ metaTagDescription: string | null
45
+ availableLinkIds: Record<string, string> | null
46
+ }
@@ -4,6 +4,11 @@ export interface OrderFormInputItem {
4
4
  seller: string
5
5
  index?: number
6
6
  attachments?: Attachment[]
7
+ /**
8
+ * Signed price token (JWT) from the Pricing Fallback feature. Forwarded to Checkout so it can
9
+ * trust the offer's price even if the Pricing System is down. Omitted when not available.
10
+ */
11
+ priceToken?: string
7
12
  }
8
13
 
9
14
  export interface Attachment {
@@ -1,4 +1,5 @@
1
1
  import type { GraphqlContext } from '..'
2
+ import { CatalogDataplane } from './catalog'
2
3
  import { VtexCommerce } from './commerce'
3
4
  import { IntelligentSearch } from './search'
4
5
 
@@ -7,9 +8,11 @@ export type Clients = ReturnType<typeof getClients>
7
8
  export const getClients = (options: Options, ctx: GraphqlContext) => {
8
9
  const search = IntelligentSearch(options, ctx)
9
10
  const commerce = VtexCommerce(options, ctx)
11
+ const catalog = CatalogDataplane(options)
10
12
 
11
13
  return {
12
14
  search,
13
15
  commerce,
16
+ catalog,
14
17
  }
15
18
  }
@@ -3,6 +3,7 @@ import pLimit from 'p-limit'
3
3
  import type { GraphqlContext } from '../../'
4
4
  import { getWithCookie } from '../../utils/cookies'
5
5
  import type { SelectedFacet } from '../../utils/facets'
6
+ import { isLocalizationEnabled } from '../../utils/localization'
6
7
  import {
7
8
  buildIntelligentSearchRequest,
8
9
  parseSegmentCookie,
@@ -80,10 +81,21 @@ function getRegionIdFromContext(ctx: GraphqlContext): string | undefined {
80
81
 
81
82
  function getSegmentLocale(ctx: GraphqlContext): string {
82
83
  const segment = parseSegmentCookie(ctx.headers?.cookie)
83
-
84
- // Prefer ctx.storage.locale (set from trusted selectedFacets) over the
85
- // vtex_segment cookie, which can lag on hard locale-switch navigation.
86
- return ctx.storage.locale || (segment.cultureInfo as string | undefined) || ''
84
+ const cultureInfo = (segment.cultureInfo as string | undefined) || ''
85
+
86
+ // Locale flows from the URL into IS in one chain:
87
+ // router.locale (Next.js)
88
+ // → selectedFacets locale facet (useLocalizedVariables in @faststore/core)
89
+ // → ctx.storage.locale (mutateLocaleContext in query.ts)
90
+ // → IS locale query param (here)
91
+ //
92
+ // For localized stores: prefer storage.locale over the vtex_segment cookie.
93
+ // The cookie can lag after locale navigation, while storage.locale is derived
94
+ // from the trusted selectedFacets locale facet in the same request.
95
+
96
+ return isLocalizationEnabled(ctx)
97
+ ? ctx.storage.locale || cultureInfo
98
+ : cultureInfo || ctx.storage.locale || ''
87
99
  }
88
100
 
89
101
  export const IntelligentSearch = (
@@ -175,6 +175,13 @@ export interface CommertialOffer {
175
175
  }>
176
176
  GetInfoErrorMessage: any | null
177
177
  CacheVersionUsedToCallCheckout: string
178
+ /**
179
+ * Signed price (JWT) used by the Pricing Fallback feature. When present, it is
180
+ * forwarded to Checkout on add-to-cart so the platform can trust the offer's
181
+ * price even if the Pricing System is unavailable. It may be absent when the
182
+ * signing step fails or the feature is disabled.
183
+ */
184
+ PriceToken?: string
178
185
  }
179
186
 
180
187
  export interface Seller {
@@ -32,10 +32,22 @@ export interface GraphqlContext {
32
32
  flags: FeatureFlags
33
33
  searchArgs?: Omit<SearchArgs, 'type'>
34
34
  cookies: Map<string, Record<string, string>>
35
+ /**
36
+ * Cached in-flight localized product lookups keyed by "productId:locale".
37
+ * Stores the promise (not just the resolved value) so concurrent sibling
38
+ * resolvers (slug validation, otherLocales, breadcrumb) dedupe to a single
39
+ * Catalog Dataplane request within the same request.
40
+ */
41
+ productTranslationsCache?: Map<
42
+ string,
43
+ Promise<import('./clients/catalog').LocalizedProductEntry | null>
44
+ >
35
45
  }
36
46
  headers: Record<string, string>
37
47
  account: string
38
48
  OTEL: Record<string, unknown>
49
+ /** Discovery config passed from @faststore/core, including localization settings. */
50
+ discoveryConfig?: Record<string, unknown>
39
51
  }
40
52
 
41
53
  export const GraphqlVtexContextFactory = async (options: Options) => {
@@ -49,11 +61,14 @@ export const GraphqlVtexContextFactory = async (options: Options) => {
49
61
  locale: options.locale,
50
62
  cookies: new Map<string, Record<string, string>>(),
51
63
  }
52
- ctx.clients = getClients(options, ctx)
53
- ctx.loaders = getLoaders(options, ctx)
54
64
  ctx.account = options.account
55
65
  ctx.OTEL = options.OTEL
56
66
  ctx.discoveryConfig = options.discoveryConfig
67
+ // Build clients/loaders last: they capture `ctx` and read storage,
68
+ // discoveryConfig, etc. at request time, so everything they may depend on
69
+ // must already be assigned.
70
+ ctx.clients = getClients(options, ctx)
71
+ ctx.loaders = getLoaders(options, ctx)
57
72
 
58
73
  return ctx
59
74
  }
@@ -3,49 +3,123 @@ import pLimit from 'p-limit'
3
3
 
4
4
  import { NotFoundError } from '../../errors'
5
5
  import type { Clients } from '../clients'
6
- import type { CollectionPageType } from '../clients/commerce/types/Portal'
6
+ import type {
7
+ ByLinkIdBrandResponse,
8
+ ByLinkIdCategoryResponse,
9
+ ByLinkIdCollectionResponse,
10
+ } from '../clients/commerce/types/ByLinkId'
7
11
 
8
- // Limits concurrent requests to 20 so that they don't timeout
9
12
  const CONCURRENT_REQUESTS_MAX = 20
10
13
 
11
- const collectionPageTypes = new Set([
12
- 'brand',
13
- 'category',
14
- 'department',
15
- 'subcategory',
16
- 'collection',
17
- 'cluster',
18
- ] as const)
14
+ /**
15
+ * Load key for the collection DataLoader. `locale` is captured at the call
16
+ * site (not re-read from mutable `ctx.storage`) so concurrent aliased
17
+ * `collection` fields with different locales cannot share a cache entry or
18
+ * race on Accept-Language.
19
+ */
20
+ export type CollectionLoadKey = {
21
+ slug: string
22
+ /** Catalog Accept-Language; `undefined` when localization is disabled. */
23
+ locale?: string
24
+ }
25
+
26
+ export type ByLinkIdCategoryRoot = ByLinkIdCategoryResponse & {
27
+ entityType: 'category'
28
+ /**
29
+ * Full accumulated input slug injected by the loader (e.g. "vestuario/camisetas").
30
+ * Used by slugifyRoot so that meta.selectedFacets builds the correct facet keys
31
+ * without relying on root.url (which is absent in the by-linkid response).
32
+ */
33
+ slug: string
34
+ }
35
+
36
+ export type ByLinkIdBrandRoot = ByLinkIdBrandResponse & {
37
+ entityType: 'brand'
38
+ }
39
+
40
+ export type ByLinkIdCollectionRoot = ByLinkIdCollectionResponse & {
41
+ entityType: 'collection'
42
+ }
43
+
44
+ export type Root =
45
+ | ByLinkIdCategoryRoot
46
+ | ByLinkIdBrandRoot
47
+ | ByLinkIdCollectionRoot
48
+
49
+ export const isCategory = (root: Root): root is ByLinkIdCategoryRoot =>
50
+ root.entityType === 'category'
19
51
 
20
- export const isCollectionPageType = (x: any): x is CollectionPageType =>
21
- typeof x.pageType === 'string' &&
22
- collectionPageTypes.has(x.pageType.toLowerCase())
52
+ export const isBrand = (root: Root): root is ByLinkIdBrandRoot =>
53
+ root.entityType === 'brand'
54
+
55
+ export const isCollection = (root: Root): root is ByLinkIdCollectionRoot =>
56
+ root.entityType === 'collection'
23
57
 
24
58
  export const getCollectionLoader = (_: Options, clients: Clients) => {
25
59
  const limit = pLimit(CONCURRENT_REQUESTS_MAX)
26
60
 
27
- const loader = async (
28
- slugs: readonly string[]
29
- ): Promise<CollectionPageType[]> => {
30
- return Promise.all(
31
- slugs.map((slug: string) =>
61
+ const loader = async (keys: readonly CollectionLoadKey[]): Promise<Root[]> =>
62
+ Promise.all(
63
+ keys.map((key) =>
32
64
  limit(async () => {
33
- const page = await clients.commerce.catalog.portal.pagetype(slug)
65
+ // Normalize to lowercase for DataLoader cache-key consistency
66
+ // (e.g. "Sporting" and "sporting" are the same). The by-linkid API is
67
+ // case-insensitive by design (it preserves the legacy pagetype behavior).
68
+ // Accents are significant and are preserved by toLowerCase() (e.g. "vestuário").
69
+ const normalizedSlug = key.slug.toLowerCase()
70
+ // Use the locale captured on the load key — do not re-read
71
+ // ctx.storage.locale here (it can change mid-request).
72
+ const locale = key.locale
73
+
74
+ // Step 1: category
75
+ // Pass the full path so the API validates each segment and returns only
76
+ // the unambiguous leaf category. E.g. "vestuario/camisetas" resolves to
77
+ // the "camisetas" that is a child of "vestuario", not any other category
78
+ // that happens to share the linkId "camisetas".
79
+ // The full slug is also injected into the result for meta.selectedFacets
80
+ // and breadcrumb URL construction.
81
+ const category = await clients.commerce.catalog.byLinkId.category(
82
+ normalizedSlug,
83
+ locale
84
+ )
85
+ if (category) {
86
+ return {
87
+ ...category,
88
+ entityType: 'category' as const,
89
+ slug: normalizedSlug,
90
+ }
91
+ }
34
92
 
35
- if (isCollectionPageType(page)) {
36
- return page
93
+ // Step 2: brand (always single-segment)
94
+ const brand = await clients.commerce.catalog.byLinkId.brand(
95
+ normalizedSlug,
96
+ locale
97
+ )
98
+ if (brand) {
99
+ return { ...brand, entityType: 'brand' as const }
100
+ }
101
+
102
+ // Step 3: collection cluster (always single-segment)
103
+ const collection = await clients.commerce.catalog.byLinkId.collection(
104
+ normalizedSlug,
105
+ locale
106
+ )
107
+ if (collection) {
108
+ return { ...collection, entityType: 'collection' as const }
37
109
  }
38
110
 
39
111
  throw new NotFoundError(
40
- `Catalog returned ${page.pageType} for slug: ${slug}. This usually happens when there is more than one category with the same name in the same category tree level.`
112
+ `No catalog entity found for slug: ${key.slug}. Cascade exhausted (category → brand → collection).`
41
113
  )
42
114
  })
43
115
  )
44
116
  )
45
- }
46
117
 
47
- return new DataLoader<string, CollectionPageType>(loader, {
48
- // DataLoader is being used to cache requests, not to batch them
118
+ return new DataLoader<CollectionLoadKey, Root, string>(loader, {
119
+ // DataLoader is used for caching, not batching
49
120
  batch: false,
121
+ // Dedup by lowercased slug + captured locale so casing variants share an
122
+ // entry while same-slug / different-locale loads stay isolated.
123
+ cacheKeyFn: ({ slug, locale }) => `${slug.toLowerCase()}::${locale ?? ''}`,
50
124
  })
51
125
  }