@faststore/api 4.10.1-dev.6 → 4.10.1-dev.7
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 +49 -46
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.mjs +1115 -1058
- package/dist/es/index.mjs.map +1 -1
- package/dist/src/__generated__/schema.d.ts +1 -0
- package/dist/src/__generated__/schema.d.ts.map +1 -1
- package/dist/src/platforms/vtex/clients/index.d.ts +1 -1
- package/dist/src/platforms/vtex/clients/search/index.d.ts +7 -1
- package/dist/src/platforms/vtex/clients/search/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/index.d.ts +1 -1
- package/dist/src/platforms/vtex/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/loaders/sku.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/index.d.ts +1 -1
- package/dist/src/platforms/vtex/resolvers/index.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/query.d.ts +1 -1
- package/dist/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
- package/dist/src/platforms/vtex/resolvers/validateSession.d.ts.map +1 -1
- package/dist/src/platforms/vtex/utils/contractAssortment.d.ts +22 -0
- package/dist/src/platforms/vtex/utils/contractAssortment.d.ts.map +1 -0
- package/dist/src/platforms/vtex/utils/intelligentSearchRequest.d.ts +5 -0
- package/dist/src/platforms/vtex/utils/intelligentSearchRequest.d.ts.map +1 -1
- package/dist/src/platforms/vtex/utils/validateSessionHelpers.d.ts +29 -2
- package/dist/src/platforms/vtex/utils/validateSessionHelpers.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__generated__/schema.ts +1 -0
- package/src/platforms/vtex/clients/search/index.ts +13 -1
- package/src/platforms/vtex/loaders/sku.ts +1 -0
- package/src/platforms/vtex/resolvers/query.ts +51 -37
- package/src/platforms/vtex/resolvers/validateSession.ts +3 -3
- package/src/platforms/vtex/typeDefs/session.graphql +3 -0
- package/src/platforms/vtex/utils/contractAssortment.ts +87 -0
- package/src/platforms/vtex/utils/intelligentSearchRequest.ts +10 -0
- package/src/platforms/vtex/utils/validateSessionHelpers.ts +47 -5
|
@@ -199,7 +199,7 @@ export declare const getResolvers: () => {
|
|
|
199
199
|
accountProfile: (_: unknown, __: unknown, ctx: import('..').GraphqlContext) => Promise<{
|
|
200
200
|
name: string;
|
|
201
201
|
email: string;
|
|
202
|
-
id:
|
|
202
|
+
id: string;
|
|
203
203
|
} | null>;
|
|
204
204
|
availableContracts: (_: unknown, { orgUnitId }: import('../../..').QueryAvailableContractsArgs, ctx: import('..').GraphqlContext) => Promise<import('../../..').StoreContract[]>;
|
|
205
205
|
pickupPoints: (_: unknown, { geoCoordinates, postalCode, country, channel }: import('../../..').QueryPickupPointsArgs, ctx: import('..').GraphqlContext) => Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/index.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,YAAY;;;;;;oJAC6uG,mCAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/index.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,YAAY;;;;;;oJAC6uG,mCAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAAqyiB,CAAC;mBAAsB,CAAC;kBAAqB,CAAC;yBAA8B,CAAC;uBAA0B,CAAC;yBAA4B,CAAC;uBAA0B,CAAC;iBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAAznnB,CAAC;yBAAoB,CAAC;0BAAqB,CAAC;0BAAqB,CAAC;8BAAyB,CAAC;gCAA2B,CAAC;;;6BAA0D,CAAC;+BAA0B,CAAC;+BAA0B,CAAC;;;;;;;;;;;wBAAmxmC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CADpkpC,CAAA"}
|
|
@@ -159,7 +159,7 @@ export declare const Query: {
|
|
|
159
159
|
accountProfile: (_: unknown, __: unknown, ctx: GraphqlContext) => Promise<{
|
|
160
160
|
name: string;
|
|
161
161
|
email: string;
|
|
162
|
-
id:
|
|
162
|
+
id: string;
|
|
163
163
|
} | null>;
|
|
164
164
|
availableContracts: (_: unknown, { orgUnitId }: QueryAvailableContractsArgs, ctx: GraphqlContext) => Promise<StoreContract[]>;
|
|
165
165
|
pickupPoints: (_: unknown, { geoCoordinates, postalCode, country, channel }: QueryPickupPointsArgs, ctx: GraphqlContext) => Promise<{
|
|
@@ -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,EAElB,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;
|
|
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,EAElB,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;AAY9B,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAwIlE,eAAO,MAAM,KAAK;iBAEX,OAAO,eACG,gBAAgB,OACxB,cAAc;oBAmDhB,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;;;;;;;;;;;;;;;yBAwBM,OAAO,MAAM,OAAO,OAAO,cAAc;;;;;;;;;;;;;4BAqBtC,OAAO,MAAM,OAAO,OAAO,cAAc;wBA0BlE,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;;;;;4BA0E9D,OAAO,iBACK,2BAA2B,OACrC,cAAc,KAClB,OAAO,CAAC,aAAa,EAAE,CAAC;sBA4DtB,OAAO,oDACwC,qBAAqB,OAClE,cAAc;;;;;;;oBA57BmB,CAAC;qBAAoB,CAAC;sBAC3D,CAAC;sBAAqB,CAAC;0BAAyB,CAAC;4BAClD,CAAC;;;yBAA0D,CAAC;2BACrD,CAAC;2BAA2B,CAAA;;;;;;;;;;;oBAu9BguQ,CAAC;;;;;;;;;;;;;;;;;;;;CADvwQ,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateSession.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/validateSession.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AACxC,OAAO,KAAK,EACV,2BAA2B,EAC3B,YAAY,EACb,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"validateSession.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/resolvers/validateSession.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AACxC,OAAO,KAAK,EACV,2BAA2B,EAC3B,YAAY,EACb,MAAM,+BAA+B,CAAA;AAuItC,eAAO,MAAM,eAAe,GAC1B,GAAG,GAAG,EACN,iCAAiC,2BAA2B,EAC5D,yDAAyD,cAAc,KACtE,OAAO,CAAC,YAAY,GAAG,IAAI,CA+I7B,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ProductCluster } from '../clients/search/types/ProductSearchResult';
|
|
2
|
+
import { GraphqlContext } from '../index';
|
|
3
|
+
import { EnhancedSku } from './enhanceSku';
|
|
4
|
+
export interface ProductClusterRestriction {
|
|
5
|
+
allowed: Set<string>;
|
|
6
|
+
excluded: Set<string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Collections the shopper's assortment is restricted to, as carried by the
|
|
10
|
+
* `vtex_segment` cookie (`productClusterIds=X` allows, `productClusterIds=not:Y`
|
|
11
|
+
* excludes). Returns `null` when the segment carries no restriction.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getProductClusterRestriction(segment: Record<string, unknown>): ProductClusterRestriction | null;
|
|
14
|
+
export declare function isProductInAssortment(productClusters: ProductCluster[] | undefined, { allowed, excluded }: ProductClusterRestriction): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Listing endpoints get the assortment applied by Intelligent Search through
|
|
17
|
+
* the segment facets, but the `products` endpoint used to fetch a single
|
|
18
|
+
* product does not, so a product outside the shopper's assortment must be
|
|
19
|
+
* rejected here.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertProductInContractAssortment(ctx: GraphqlContext, sku: EnhancedSku): void;
|
|
22
|
+
//# sourceMappingURL=contractAssortment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contractAssortment.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/utils/contractAssortment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAA;AACjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAS/C,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACpB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACtB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,yBAAyB,GAAG,IAAI,CAwBlC;AAED,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,cAAc,EAAE,GAAG,SAAS,EAC7C,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,yBAAyB,GAC/C,OAAO,CAQT;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,WAAW,GACf,IAAI,CAcN"}
|
|
@@ -50,6 +50,11 @@ export interface IntelligentSearchRequest {
|
|
|
50
50
|
* Returns an empty object when the cookie is absent or invalid.
|
|
51
51
|
*/
|
|
52
52
|
export declare function parseSegmentCookie(cookieHeader?: string): Record<string, unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Non-shipping facets carried by the `vtex_segment` cookie (e.g. the B2B
|
|
55
|
+
* contract's `productClusterIds`), in the order they appear in the cookie.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getSegmentExtraFacets(segment: Record<string, unknown>): IntelligentSearchFacet[];
|
|
53
58
|
/**
|
|
54
59
|
* Pure builder for intelligent-search v1 path + query params.
|
|
55
60
|
* Callers assemble the full URL: `/api/intelligent-search/v1/${endpoint}/${request.path}?${request.params}`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligentSearchRequest.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/utils/intelligentSearchRequest.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AA6BD,MAAM,MAAM,IAAI,GACZ,YAAY,GACZ,WAAW,GACX,aAAa,GACb,WAAW,GACX,UAAU,GACV,cAAc,GACd,eAAe,GACf,EAAE,GAIF,GAAG,MAAM,IAAI,KAAK,GAAG,MAAM,EAAE,CAAA;AAEjC,MAAM,MAAM,sBAAsB,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAA;AAEhF,MAAM,MAAM,yBAAyB,GACjC,gBAAgB,GAChB,QAAQ,GACR,eAAe,GACf,UAAU,GACV,oBAAoB,GACpB,cAAc,GACd,2BAA2B,CAAA;AAE/B,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,kBAAkB,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;IAClD,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,cAAc,CAAC,EAAE,sBAAsB,EAAE,CAAA;IACzC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,KAAK,CAAC,EAAE,sBAAsB,CAAA;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,yBAAyB,CAAA;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,QAAQ,CAAC,EAAE,yBAAyB,CAAA;IACpC,IAAI,CAAC,EAAE,4BAA4B,CAAA;CACpC;AAED,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,IAAI,MAAM,CAAA;CACnB;AAwCD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;
|
|
1
|
+
{"version":3,"file":"intelligentSearchRequest.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/utils/intelligentSearchRequest.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AA6BD,MAAM,MAAM,IAAI,GACZ,YAAY,GACZ,WAAW,GACX,aAAa,GACb,WAAW,GACX,UAAU,GACV,cAAc,GACd,eAAe,GACf,EAAE,GAIF,GAAG,MAAM,IAAI,KAAK,GAAG,MAAM,EAAE,CAAA;AAEjC,MAAM,MAAM,sBAAsB,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAA;AAEhF,MAAM,MAAM,yBAAyB,GACjC,gBAAgB,GAChB,QAAQ,GACR,eAAe,GACf,UAAU,GACV,oBAAoB,GACpB,cAAc,GACd,2BAA2B,CAAA;AAE/B,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,kBAAkB,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;IAClD,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,cAAc,CAAC,EAAE,sBAAsB,EAAE,CAAA;IACzC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,KAAK,CAAC,EAAE,sBAAsB,CAAA;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,yBAAyB,CAAA;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,QAAQ,CAAC,EAAE,yBAAyB,CAAA;IACpC,IAAI,CAAC,EAAE,4BAA4B,CAAA;CACpC;AAED,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,IAAI,MAAM,CAAA;CACnB;AAwCD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;AA6DD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,sBAAsB,EAAE,CAE1B;AAqbD;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,6BAA6B,GACnC,wBAAwB,CAkE1B"}
|
|
@@ -12,13 +12,37 @@ export declare function getPreciseLocationData(clients: GraphqlContext['clients'
|
|
|
12
12
|
export declare function buildSessionSearchParams(search: string, channel: Required<Channel>, postalCode: string, country: string, geoCoordinates: StoreSession['geoCoordinates'], locale: string): URLSearchParams;
|
|
13
13
|
export declare function buildMarketingData(params: URLSearchParams, oldMarketingData: StoreSession['marketingData']): StoreMarketingData;
|
|
14
14
|
export declare function resolveJwtClaims(clients: GraphqlContext['clients'], cookie: string | undefined, account: string): Promise<{
|
|
15
|
-
isRepresentative: boolean;
|
|
16
15
|
customerId: any;
|
|
17
16
|
unitId: any;
|
|
18
17
|
}>;
|
|
19
18
|
export declare function resolveSellerInRegion(clients: GraphqlContext['clients'], channel: Required<Channel>, postalCode: string, geoCoordinates: StoreSession['geoCoordinates'], country: string, salesChannel: string): Promise<string | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* First value that is a non-blank string, trimmed. Session values and JWT
|
|
21
|
+
* claims are only nominally typed, so anything else is skipped.
|
|
22
|
+
*/
|
|
23
|
+
export declare function firstNonBlank(...values: unknown[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* Organizational unit of the shopper, from the session or the JWT claim.
|
|
26
|
+
* Buyer Portal identifies its buyers by the unit; `isRepresentative` alone is
|
|
27
|
+
* not enough, since it can be true outside B2B.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveB2bUnitId(authentication: {
|
|
30
|
+
unitId?: {
|
|
31
|
+
value?: string | null;
|
|
32
|
+
} | null;
|
|
33
|
+
} | null, jwtUnitId?: unknown): string;
|
|
34
|
+
/**
|
|
35
|
+
* Returns `null` unless the shopper has both an organizational unit and a
|
|
36
|
+
* contract, the rule Buyer Portal uses for a B2B session.
|
|
37
|
+
*
|
|
38
|
+
* The contract falls back from the session's active contract to the session's
|
|
39
|
+
* authentication namespace and then to the JWT claim. Blank values fall
|
|
40
|
+
* through, so an empty session value never hides the claim.
|
|
41
|
+
*
|
|
42
|
+
* The deprecated `isRepresentative` field stays `true` whenever `b2b` is
|
|
43
|
+
* returned, so consumers that still read it keep working.
|
|
44
|
+
*/
|
|
20
45
|
export declare function buildB2bSession(args: {
|
|
21
|
-
isRepresentative: boolean;
|
|
22
46
|
authentication: {
|
|
23
47
|
customerId?: {
|
|
24
48
|
value?: string | null;
|
|
@@ -43,6 +67,9 @@ export declare function buildB2bSession(args: {
|
|
|
43
67
|
organizationManager?: {
|
|
44
68
|
value?: boolean | null;
|
|
45
69
|
} | null;
|
|
70
|
+
activeContractId?: {
|
|
71
|
+
value?: string | null;
|
|
72
|
+
} | null;
|
|
46
73
|
} | null;
|
|
47
74
|
publicData: {
|
|
48
75
|
postalCode?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateSessionHelpers.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/utils/validateSessionHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AACxC,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACb,MAAM,+BAA+B,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,gCAAgC,EAAE,MAAM,YAAY,CAAA;AAG7D,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM;;;;;;GAuBnB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC1B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAC9C,MAAM,EAAE,MAAM,mBA8Bf;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EACvB,gBAAgB,EAAE,YAAY,CAAC,eAAe,CAAC,GAC9C,kBAAkB,CAUpB;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAClC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"validateSessionHelpers.d.ts","sourceRoot":"","sources":["../../../../../src/platforms/vtex/utils/validateSessionHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,IAAI,CAAA;AACxC,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACb,MAAM,+BAA+B,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,gCAAgC,EAAE,MAAM,YAAY,CAAA;AAG7D,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM;;;;;;GAuBnB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC1B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAC9C,MAAM,EAAE,MAAM,mBA8Bf;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,eAAe,EACvB,gBAAgB,EAAE,YAAY,CAAC,eAAe,CAAC,GAC9C,kBAAkB,CAUpB;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAClC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM;;;GAmBhB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC1B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAC9C,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,+BAerB;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAO1D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE;IAAE,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,EACpE,SAAS,CAAC,EAAE,OAAO,GAClB,MAAM,CAER;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,cAAc,EAAE;QACd,UAAU,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;QAC7C,QAAQ,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;QAC3C,MAAM,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;QACzC,cAAc,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;KAClD,GAAG,IAAI,CAAA;IACR,OAAO,EAAE;QACP,SAAS,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAA;QACtC,QAAQ,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAA;QACrC,mBAAmB,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;QACvD,gBAAgB,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;KACpD,GAAG,IAAI,CAAA;IACR,UAAU,EAAE;QAAE,UAAU,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IACpE,OAAO,EAAE,UAAU,CAAC,OAAO,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/D,QAAQ,EAAE,UAAU,CAAC,OAAO,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAA;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;;;;;;;;;;;;SA4BA;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE;IACP,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IACrC,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IACxC,SAAS,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5C,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CAC5C,GAAG,IAAI;;;;;SAYT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/api",
|
|
3
|
-
"version": "4.10.1-dev.
|
|
3
|
+
"version": "4.10.1-dev.7",
|
|
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.18",
|
|
61
|
-
"@faststore/diagnostics": "^4.10.1-dev.
|
|
61
|
+
"@faststore/diagnostics": "^4.10.1-dev.7"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"graphql": "16.11.0",
|
|
65
|
-
"@faststore/diagnostics": "4.10.1-dev.
|
|
65
|
+
"@faststore/diagnostics": "4.10.1-dev.7"
|
|
66
66
|
},
|
|
67
67
|
"publishConfig": {
|
|
68
68
|
"access": "public"
|
|
@@ -1360,6 +1360,7 @@ export type StoreB2B = {
|
|
|
1360
1360
|
contractName?: Maybe<Scalars['String']['output']>;
|
|
1361
1361
|
customerId: Scalars['String']['output'];
|
|
1362
1362
|
firstName?: Maybe<Scalars['String']['output']>;
|
|
1363
|
+
/** @deprecated Always true when `b2b` is set: a B2B session is identified by `unitId` and `customerId`. Check that `b2b` is not null instead. Scheduled for removal in the next major. */
|
|
1363
1364
|
isRepresentative?: Maybe<Scalars['Boolean']['output']>;
|
|
1364
1365
|
lastName?: Maybe<Scalars['String']['output']>;
|
|
1365
1366
|
organizationManager?: Maybe<Scalars['Boolean']['output']>;
|
|
@@ -2,6 +2,7 @@ import pLimit from 'p-limit'
|
|
|
2
2
|
import type { Options } from '../../../../typings/globals'
|
|
3
3
|
|
|
4
4
|
import type { GraphqlContext } from '../../'
|
|
5
|
+
import { isNotFoundError } from '../../../errors'
|
|
5
6
|
import { getWithCookie } from '../../utils/cookies'
|
|
6
7
|
import type { SelectedFacet } from '../../utils/facets'
|
|
7
8
|
import { isLocalizationEnabled } from '../../utils/localization'
|
|
@@ -65,6 +66,12 @@ export interface ProductsByIdentifierArgs {
|
|
|
65
66
|
values: string[]
|
|
66
67
|
showInvisibleItems?: boolean
|
|
67
68
|
hideUnavailableItems?: boolean
|
|
69
|
+
/**
|
|
70
|
+
* Rethrow upstream failures (5xx, timeouts, network) instead of dropping the
|
|
71
|
+
* product, so callers can tell a failure from a product that does not exist.
|
|
72
|
+
* A not-found answer is still dropped.
|
|
73
|
+
*/
|
|
74
|
+
throwOnUpstreamError?: boolean
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
export interface PickupPointAvailabilityArgs {
|
|
@@ -214,6 +221,7 @@ export const IntelligentSearch = (
|
|
|
214
221
|
values,
|
|
215
222
|
showInvisibleItems,
|
|
216
223
|
hideUnavailableItems: hideUnavailable,
|
|
224
|
+
throwOnUpstreamError = false,
|
|
217
225
|
}: ProductsByIdentifierArgs): Promise<Product[]> => {
|
|
218
226
|
const limit = pLimit(PRODUCTS_BY_IDENTIFIER_CONCURRENCY)
|
|
219
227
|
|
|
@@ -227,7 +235,11 @@ export const IntelligentSearch = (
|
|
|
227
235
|
hideUnavailableItems: hideUnavailable,
|
|
228
236
|
showInvisibleItems,
|
|
229
237
|
})
|
|
230
|
-
} catch {
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (throwOnUpstreamError && !isNotFoundError(error)) {
|
|
240
|
+
throw error
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
return null
|
|
232
244
|
}
|
|
233
245
|
})
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
resolveActiveContractIdFromSession,
|
|
45
45
|
resolveDefaultContractId,
|
|
46
46
|
} from '../utils/contract'
|
|
47
|
+
import { assertProductInContractAssortment } from '../utils/contractAssortment'
|
|
47
48
|
import { mutateChannelContext, mutateLocaleContext } from '../utils/contex'
|
|
48
49
|
import { getAuthCookie, parseJwt } from '../utils/cookies'
|
|
49
50
|
import { enhanceSku, type EnhancedSku } from '../utils/enhanceSku'
|
|
@@ -59,6 +60,10 @@ import { getCatalogLocale, isLocalizationEnabled } from '../utils/localization'
|
|
|
59
60
|
import { isValidSkuId, pickBestSku } from '../utils/sku'
|
|
60
61
|
import { slugify } from '../utils/slugify'
|
|
61
62
|
import { resolveSort } from '../utils/sort'
|
|
63
|
+
import {
|
|
64
|
+
firstNonBlank,
|
|
65
|
+
resolveB2bUnitId,
|
|
66
|
+
} from '../utils/validateSessionHelpers'
|
|
62
67
|
import { FACET_CROSS_SELLING_MAP } from './../utils/facets'
|
|
63
68
|
import { StoreCollection } from './collection'
|
|
64
69
|
import { getOrderEntryOperation } from './getOrderEntryOperation'
|
|
@@ -202,25 +207,35 @@ export const Query = {
|
|
|
202
207
|
loaders: { skuLoader },
|
|
203
208
|
} = ctx
|
|
204
209
|
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
const loadSku = async () => {
|
|
211
|
+
try {
|
|
212
|
+
const skuId = id ?? slug?.split('-').pop() ?? ''
|
|
207
213
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
214
|
+
if (!isValidSkuId(skuId)) {
|
|
215
|
+
throw new Error(INVALID_SKU_ID_ERROR)
|
|
216
|
+
}
|
|
211
217
|
|
|
212
|
-
|
|
218
|
+
const sku = await skuLoader.load(skuId)
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
await assertSkuMatchesSlug(ctx, sku, slug, locale)
|
|
215
221
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
return sku
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (!shouldFallbackToProductRoute(err)) {
|
|
225
|
+
throw err
|
|
226
|
+
}
|
|
221
227
|
|
|
222
|
-
|
|
228
|
+
return fetchProductBySlugFallback(ctx, slug)
|
|
229
|
+
}
|
|
223
230
|
}
|
|
231
|
+
|
|
232
|
+
const sku = await loadSku()
|
|
233
|
+
|
|
234
|
+
// Runs outside `loadSku` so its NotFoundError is not swallowed by the
|
|
235
|
+
// slug fallback, which would refetch and return the same product.
|
|
236
|
+
assertProductInContractAssortment(ctx, sku)
|
|
237
|
+
|
|
238
|
+
return sku
|
|
224
239
|
},
|
|
225
240
|
collection: (
|
|
226
241
|
_: unknown,
|
|
@@ -871,8 +886,8 @@ export const Query = {
|
|
|
871
886
|
orgUnit: authentication?.unitName?.value ?? '',
|
|
872
887
|
}
|
|
873
888
|
},
|
|
874
|
-
//
|
|
875
|
-
//
|
|
889
|
+
// Buyers with an organizational unit and a contract get the contract
|
|
890
|
+
// information. Everyone else gets the b2c user information.
|
|
876
891
|
accountProfile: async (_: unknown, __: unknown, ctx: GraphqlContext) => {
|
|
877
892
|
const {
|
|
878
893
|
account,
|
|
@@ -886,7 +901,7 @@ export const Query = {
|
|
|
886
901
|
return null
|
|
887
902
|
}
|
|
888
903
|
|
|
889
|
-
if (jwt
|
|
904
|
+
if (resolveB2bUnitId(null, jwt.unitId)) {
|
|
890
905
|
const sessionData = await commerce.session('').catch(() => null)
|
|
891
906
|
|
|
892
907
|
if (!sessionData) {
|
|
@@ -897,13 +912,14 @@ export const Query = {
|
|
|
897
912
|
const shopper = sessionData.namespaces.shopper ?? null
|
|
898
913
|
const authentication = sessionData.namespaces.authentication ?? null
|
|
899
914
|
|
|
900
|
-
const contractId =
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
915
|
+
const contractId = firstNonBlank(
|
|
916
|
+
shopper?.activeContractId?.value,
|
|
917
|
+
authentication?.customerId?.value,
|
|
918
|
+
jwt.customerId
|
|
919
|
+
)
|
|
904
920
|
|
|
905
|
-
let contract = null
|
|
906
921
|
if (contractId) {
|
|
922
|
+
let contract = null
|
|
907
923
|
try {
|
|
908
924
|
contract = await commerce.masterData.getContractById({ contractId })
|
|
909
925
|
} catch (err) {
|
|
@@ -912,23 +928,21 @@ export const Query = {
|
|
|
912
928
|
err
|
|
913
929
|
)
|
|
914
930
|
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
const shopperName =
|
|
918
|
-
`${(shopper?.firstName?.value ?? '').trim()} ${(shopper?.lastName?.value ?? '').trim()}`.trim()
|
|
919
|
-
const name =
|
|
920
|
-
resolveActiveContractDisplayName(contract, profile) || shopperName
|
|
921
931
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
profile
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
+
const shopperName =
|
|
933
|
+
`${(shopper?.firstName?.value ?? '').trim()} ${(shopper?.lastName?.value ?? '').trim()}`.trim()
|
|
934
|
+
const name =
|
|
935
|
+
resolveActiveContractDisplayName(contract, profile) || shopperName
|
|
936
|
+
|
|
937
|
+
return {
|
|
938
|
+
name: name || '',
|
|
939
|
+
email:
|
|
940
|
+
profile?.email?.value ||
|
|
941
|
+
authentication?.storeUserEmail?.value ||
|
|
942
|
+
'',
|
|
943
|
+
id: profile?.id?.value || contractId,
|
|
944
|
+
// createdAt: '',
|
|
945
|
+
}
|
|
932
946
|
}
|
|
933
947
|
}
|
|
934
948
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
buildPersonFromProfile,
|
|
23
23
|
buildSessionSearchParams,
|
|
24
24
|
getPreciseLocationData,
|
|
25
|
+
resolveB2bUnitId,
|
|
25
26
|
resolveJwtClaims,
|
|
26
27
|
resolveSellerInRegion,
|
|
27
28
|
} from '../utils/validateSessionHelpers'
|
|
@@ -187,7 +188,7 @@ export const validateSession = async (
|
|
|
187
188
|
oldSession.locale
|
|
188
189
|
)
|
|
189
190
|
const marketingData = buildMarketingData(params, oldSession.marketingData)
|
|
190
|
-
const {
|
|
191
|
+
const { customerId, unitId } = await resolveJwtClaims(
|
|
191
192
|
clients,
|
|
192
193
|
headers?.cookie,
|
|
193
194
|
account
|
|
@@ -211,7 +212,7 @@ export const validateSession = async (
|
|
|
211
212
|
const publicData = sessionData?.namespaces.public ?? null
|
|
212
213
|
|
|
213
214
|
let contract = null
|
|
214
|
-
if (
|
|
215
|
+
if (resolveB2bUnitId(authentication, unitId) && profile?.id?.value) {
|
|
215
216
|
try {
|
|
216
217
|
contract = await clients.commerce.masterData.getContractById({
|
|
217
218
|
contractId: profile.id.value,
|
|
@@ -262,7 +263,6 @@ export const validateSession = async (
|
|
|
262
263
|
* - Contract data: `profile` namespace (those info will be available inside Faststore's Session `person` object)
|
|
263
264
|
*/
|
|
264
265
|
b2b: buildB2bSession({
|
|
265
|
-
isRepresentative,
|
|
266
266
|
authentication,
|
|
267
267
|
shopper,
|
|
268
268
|
publicData,
|
|
@@ -191,6 +191,9 @@ type StoreSession {
|
|
|
191
191
|
type StoreB2B {
|
|
192
192
|
customerId: String!
|
|
193
193
|
isRepresentative: Boolean
|
|
194
|
+
@deprecated(
|
|
195
|
+
reason: "Always true when `b2b` is set: a B2B session is identified by `unitId` and `customerId`. Check that `b2b` is not null instead. Scheduled for removal in the next major."
|
|
196
|
+
)
|
|
194
197
|
unitName: String
|
|
195
198
|
unitId: String
|
|
196
199
|
firstName: String
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { NotFoundError } from '../../errors'
|
|
2
|
+
import type { ProductCluster } from '../clients/search/types/ProductSearchResult'
|
|
3
|
+
import type { GraphqlContext } from '../index'
|
|
4
|
+
import type { EnhancedSku } from './enhanceSku'
|
|
5
|
+
import {
|
|
6
|
+
getSegmentExtraFacets,
|
|
7
|
+
parseSegmentCookie,
|
|
8
|
+
} from './intelligentSearchRequest'
|
|
9
|
+
|
|
10
|
+
const PRODUCT_CLUSTER_IDS_KEY = 'productClusterIds'
|
|
11
|
+
const EXCLUDED_PREFIX = 'not:'
|
|
12
|
+
|
|
13
|
+
export interface ProductClusterRestriction {
|
|
14
|
+
allowed: Set<string>
|
|
15
|
+
excluded: Set<string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Collections the shopper's assortment is restricted to, as carried by the
|
|
20
|
+
* `vtex_segment` cookie (`productClusterIds=X` allows, `productClusterIds=not:Y`
|
|
21
|
+
* excludes). Returns `null` when the segment carries no restriction.
|
|
22
|
+
*/
|
|
23
|
+
export function getProductClusterRestriction(
|
|
24
|
+
segment: Record<string, unknown>
|
|
25
|
+
): ProductClusterRestriction | null {
|
|
26
|
+
const allowed = new Set<string>()
|
|
27
|
+
const excluded = new Set<string>()
|
|
28
|
+
|
|
29
|
+
for (const { key, value } of getSegmentExtraFacets(segment)) {
|
|
30
|
+
if (key !== PRODUCT_CLUSTER_IDS_KEY) continue
|
|
31
|
+
|
|
32
|
+
for (const rawId of value.split(',')) {
|
|
33
|
+
const id = rawId.trim()
|
|
34
|
+
|
|
35
|
+
if (id.startsWith(EXCLUDED_PREFIX)) {
|
|
36
|
+
const excludedId = id.slice(EXCLUDED_PREFIX.length)
|
|
37
|
+
if (excludedId) excluded.add(excludedId)
|
|
38
|
+
} else if (id) {
|
|
39
|
+
allowed.add(id)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (allowed.size === 0 && excluded.size === 0) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { allowed, excluded }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isProductInAssortment(
|
|
52
|
+
productClusters: ProductCluster[] | undefined,
|
|
53
|
+
{ allowed, excluded }: ProductClusterRestriction
|
|
54
|
+
): boolean {
|
|
55
|
+
const clusterIds = (productClusters ?? []).map(({ id }) => String(id))
|
|
56
|
+
|
|
57
|
+
if (clusterIds.some((id) => excluded.has(id))) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return allowed.size === 0 || clusterIds.some((id) => allowed.has(id))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Listing endpoints get the assortment applied by Intelligent Search through
|
|
66
|
+
* the segment facets, but the `products` endpoint used to fetch a single
|
|
67
|
+
* product does not, so a product outside the shopper's assortment must be
|
|
68
|
+
* rejected here.
|
|
69
|
+
*/
|
|
70
|
+
export function assertProductInContractAssortment(
|
|
71
|
+
ctx: GraphqlContext,
|
|
72
|
+
sku: EnhancedSku
|
|
73
|
+
): void {
|
|
74
|
+
const restriction = getProductClusterRestriction(
|
|
75
|
+
parseSegmentCookie(ctx.headers?.cookie)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (!restriction) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!isProductInAssortment(sku.isVariantOf.productClusters, restriction)) {
|
|
83
|
+
throw new NotFoundError(
|
|
84
|
+
`Product ${sku.isVariantOf.productId} is not in the shopper's assortment`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -225,6 +225,16 @@ function extractSegmentData(segment: Record<string, unknown>): {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Non-shipping facets carried by the `vtex_segment` cookie (e.g. the B2B
|
|
230
|
+
* contract's `productClusterIds`), in the order they appear in the cookie.
|
|
231
|
+
*/
|
|
232
|
+
export function getSegmentExtraFacets(
|
|
233
|
+
segment: Record<string, unknown>
|
|
234
|
+
): IntelligentSearchFacet[] {
|
|
235
|
+
return extractSegmentData(segment).extraFacets
|
|
236
|
+
}
|
|
237
|
+
|
|
228
238
|
function appendSegmentParams(
|
|
229
239
|
params: URLSearchParams,
|
|
230
240
|
segmentParams: SegmentParams
|
|
@@ -107,7 +107,6 @@ export async function resolveJwtClaims(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
return {
|
|
110
|
-
isRepresentative: isValidJwt ? Boolean(jwt?.isRepresentative) : false,
|
|
111
110
|
customerId: isValidJwt ? jwt?.customerId : undefined,
|
|
112
111
|
unitId: isValidJwt ? jwt?.unitId : undefined,
|
|
113
112
|
}
|
|
@@ -140,8 +139,43 @@ function shopperString(value: { value?: unknown } | null | undefined): string {
|
|
|
140
139
|
return typeof value?.value === 'string' ? value.value : ''
|
|
141
140
|
}
|
|
142
141
|
|
|
142
|
+
/**
|
|
143
|
+
* First value that is a non-blank string, trimmed. Session values and JWT
|
|
144
|
+
* claims are only nominally typed, so anything else is skipped.
|
|
145
|
+
*/
|
|
146
|
+
export function firstNonBlank(...values: unknown[]): string {
|
|
147
|
+
for (const value of values) {
|
|
148
|
+
const trimmed = typeof value === 'string' ? value.trim() : ''
|
|
149
|
+
if (trimmed) return trimmed
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return ''
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Organizational unit of the shopper, from the session or the JWT claim.
|
|
157
|
+
* Buyer Portal identifies its buyers by the unit; `isRepresentative` alone is
|
|
158
|
+
* not enough, since it can be true outside B2B.
|
|
159
|
+
*/
|
|
160
|
+
export function resolveB2bUnitId(
|
|
161
|
+
authentication: { unitId?: { value?: string | null } | null } | null,
|
|
162
|
+
jwtUnitId?: unknown
|
|
163
|
+
): string {
|
|
164
|
+
return firstNonBlank(authentication?.unitId?.value, jwtUnitId)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Returns `null` unless the shopper has both an organizational unit and a
|
|
169
|
+
* contract, the rule Buyer Portal uses for a B2B session.
|
|
170
|
+
*
|
|
171
|
+
* The contract falls back from the session's active contract to the session's
|
|
172
|
+
* authentication namespace and then to the JWT claim. Blank values fall
|
|
173
|
+
* through, so an empty session value never hides the claim.
|
|
174
|
+
*
|
|
175
|
+
* The deprecated `isRepresentative` field stays `true` whenever `b2b` is
|
|
176
|
+
* returned, so consumers that still read it keep working.
|
|
177
|
+
*/
|
|
143
178
|
export function buildB2bSession(args: {
|
|
144
|
-
isRepresentative: boolean
|
|
145
179
|
authentication: {
|
|
146
180
|
customerId?: { value?: string | null } | null
|
|
147
181
|
unitName?: { value?: string | null } | null
|
|
@@ -152,6 +186,7 @@ export function buildB2bSession(args: {
|
|
|
152
186
|
firstName?: { value?: unknown } | null
|
|
153
187
|
lastName?: { value?: unknown } | null
|
|
154
188
|
organizationManager?: { value?: boolean | null } | null
|
|
189
|
+
activeContractId?: { value?: string | null } | null
|
|
155
190
|
} | null
|
|
156
191
|
publicData: { postalCode?: { value?: string | null } | null } | null
|
|
157
192
|
profile: Parameters<typeof resolveActiveContractDisplayName>[1]
|
|
@@ -159,7 +194,14 @@ export function buildB2bSession(args: {
|
|
|
159
194
|
customerId?: string
|
|
160
195
|
unitId?: string
|
|
161
196
|
}) {
|
|
162
|
-
|
|
197
|
+
const unitId = resolveB2bUnitId(args.authentication, args.unitId)
|
|
198
|
+
const customerId = firstNonBlank(
|
|
199
|
+
args.shopper?.activeContractId?.value,
|
|
200
|
+
args.authentication?.customerId?.value,
|
|
201
|
+
args.customerId
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if (!unitId || !customerId) {
|
|
163
205
|
return null
|
|
164
206
|
}
|
|
165
207
|
|
|
@@ -168,9 +210,9 @@ export function buildB2bSession(args: {
|
|
|
168
210
|
|
|
169
211
|
return {
|
|
170
212
|
isRepresentative: true,
|
|
171
|
-
customerId
|
|
213
|
+
customerId,
|
|
172
214
|
unitName: args.authentication?.unitName?.value ?? '',
|
|
173
|
-
unitId
|
|
215
|
+
unitId,
|
|
174
216
|
firstName,
|
|
175
217
|
lastName,
|
|
176
218
|
userName: `${firstName} ${lastName}`.trim(),
|