@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.
- package/dist/cjs/index.js +105 -44
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +1164 -901
- package/dist/es/index.mjs.map +1 -1
- package/dist/src/__generated__/schema.d.ts +52 -3
- package/dist/src/__generated__/schema.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/catalog/index.d.ts +34 -0
- package/dist/src/platforms/vtex/clients/catalog/index.d.ts.map +1 -0
- package/dist/src/platforms/vtex/clients/commerce/index.d.ts +6 -0
- package/dist/src/platforms/vtex/clients/commerce/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/commerce/types/ByLinkId.d.ts +45 -0
- package/dist/src/platforms/vtex/clients/commerce/types/ByLinkId.d.ts.map +1 -0
- package/dist/src/platforms/vtex/clients/commerce/types/OrderForm.d.ts +5 -0
- package/dist/src/platforms/vtex/clients/commerce/types/OrderForm.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/index.d.ts +8 -0
- package/dist/src/platforms/vtex/clients/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/search/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/search/types/ProductSearchResult.d.ts +7 -0
- package/dist/src/platforms/vtex/clients/search/types/ProductSearchResult.d.ts.map +1 -1
- package/dist/src/platforms/vtex/index.d.ts +12 -13
- package/dist/src/platforms/vtex/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/loaders/collection.d.ts +32 -3
- package/dist/src/platforms/vtex/loaders/collection.d.ts.map +1 -1
- package/dist/src/platforms/vtex/loaders/index.d.ts +2 -2
- package/dist/src/platforms/vtex/loaders/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/collection.d.ts +20 -2
- package/dist/src/platforms/vtex/resolvers/collection.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/getOrderEntryOperation.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/index.d.ts +3 -13
- package/dist/src/platforms/vtex/resolvers/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/offer.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/product.d.ts +13 -1
- package/dist/src/platforms/vtex/resolvers/product.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/query.d.ts +8 -17
- package/dist/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/validateCart.d.ts.map +1 -1
- package/dist/src/platforms/vtex/utils/localization.d.ts +28 -0
- package/dist/src/platforms/vtex/utils/localization.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/__generated__/schema.ts +54 -3
- package/src/platforms/vtex/clients/catalog/index.ts +52 -0
- package/src/platforms/vtex/clients/commerce/index.ts +71 -0
- package/src/platforms/vtex/clients/commerce/types/ByLinkId.ts +46 -0
- package/src/platforms/vtex/clients/commerce/types/OrderForm.ts +5 -0
- package/src/platforms/vtex/clients/index.ts +3 -0
- package/src/platforms/vtex/clients/search/index.ts +16 -4
- package/src/platforms/vtex/clients/search/types/ProductSearchResult.ts +7 -0
- package/src/platforms/vtex/index.ts +17 -2
- package/src/platforms/vtex/loaders/collection.ts +99 -25
- package/src/platforms/vtex/loaders/index.ts +2 -1
- package/src/platforms/vtex/resolvers/collection.ts +173 -66
- package/src/platforms/vtex/resolvers/offer.ts +7 -0
- package/src/platforms/vtex/resolvers/product.ts +160 -11
- package/src/platforms/vtex/resolvers/query.ts +148 -56
- package/src/platforms/vtex/resolvers/validateCart.ts +6 -1
- package/src/platforms/vtex/typeDefs/collection.graphql +33 -1
- package/src/platforms/vtex/typeDefs/offer.graphql +10 -0
- package/src/platforms/vtex/typeDefs/product.graphql +19 -0
- 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;
|
|
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;
|
|
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
|
+
"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.
|
|
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.
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
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 {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
`
|
|
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<
|
|
48
|
-
// DataLoader is
|
|
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
|
}
|