@reactionary/hcl 0.9.12 → 0.9.13
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/capabilities/analytics.capability.js +98 -0
- package/capabilities/personalization-profile.capability.js +85 -0
- package/capabilities/product-associations.capability.js +117 -0
- package/capabilities/product-recommendations.capability.js +143 -0
- package/core/initialize.js +77 -0
- package/factories/index.js +3 -0
- package/factories/personalization-profile/personalization-profile.factory.js +16 -0
- package/factories/product-associations/product-associations.factory.js +16 -0
- package/factories/product-recommendations/product-recommendations.factory.js +17 -0
- package/index.js +4 -0
- package/package.json +2 -2
- package/schema/capabilities.schema.js +10 -2
- package/schema/configuration.schema.js +37 -1
- package/src/capabilities/analytics.capability.d.ts +17 -0
- package/src/capabilities/personalization-profile.capability.d.ts +19 -0
- package/src/capabilities/product-associations.capability.d.ts +22 -0
- package/src/capabilities/product-recommendations.capability.d.ts +29 -0
- package/src/factories/index.d.ts +3 -0
- package/src/factories/personalization-profile/personalization-profile.factory.d.ts +11 -0
- package/src/factories/product-associations/product-associations.factory.d.ts +8 -0
- package/src/factories/product-recommendations/product-recommendations.factory.d.ts +12 -0
- package/src/index.d.ts +4 -0
- package/src/schema/capabilities.schema.d.ts +27 -1
- package/src/schema/configuration.schema.d.ts +24 -0
- package/src/schema/hcl.schema.d.ts +59 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
import {
|
|
13
|
+
AnalyticsCapability,
|
|
14
|
+
AnalyticsMutationSchema,
|
|
15
|
+
AnalyticsResultSchema,
|
|
16
|
+
Reactionary
|
|
17
|
+
} from "@reactionary/core";
|
|
18
|
+
import { SESSION_KEY_PERSONALIZATION_ID } from "../core/session-keys.js";
|
|
19
|
+
class HclAnalyticsCapability extends AnalyticsCapability {
|
|
20
|
+
constructor(cache, context, config, client) {
|
|
21
|
+
super(cache, context);
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.client = client;
|
|
24
|
+
}
|
|
25
|
+
async track(event) {
|
|
26
|
+
return super.track(event);
|
|
27
|
+
}
|
|
28
|
+
async processProductSummaryView(event) {
|
|
29
|
+
try {
|
|
30
|
+
for (const product of event.products) {
|
|
31
|
+
await this.fireEvent({ productId: product.key });
|
|
32
|
+
}
|
|
33
|
+
return this.accepted();
|
|
34
|
+
} catch {
|
|
35
|
+
return this.rejected();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async processProductSummaryClick(event) {
|
|
39
|
+
try {
|
|
40
|
+
await this.fireEvent({ productId: event.product.key });
|
|
41
|
+
return this.accepted();
|
|
42
|
+
} catch {
|
|
43
|
+
return this.rejected();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async processProductDetailsView(event) {
|
|
47
|
+
try {
|
|
48
|
+
await this.fireEvent({ productId: event.product.key });
|
|
49
|
+
return this.accepted();
|
|
50
|
+
} catch {
|
|
51
|
+
return this.rejected();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async processProductAddToCart(event) {
|
|
55
|
+
try {
|
|
56
|
+
await this.fireEvent({ productId: event.product.key });
|
|
57
|
+
return this.accepted();
|
|
58
|
+
} catch {
|
|
59
|
+
return this.rejected();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async processPurchase(event) {
|
|
63
|
+
try {
|
|
64
|
+
for (const item of event.order.items) {
|
|
65
|
+
await this.fireEvent({ productId: item.variant.sku });
|
|
66
|
+
}
|
|
67
|
+
return this.accepted();
|
|
68
|
+
} catch {
|
|
69
|
+
return this.rejected();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async fireEvent(extra) {
|
|
73
|
+
await this.client.callPost(
|
|
74
|
+
this.getEventUrl(),
|
|
75
|
+
this.getEventBody(extra)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
getEventUrl() {
|
|
79
|
+
return `${this.client.transactionBaseUrl}/event`;
|
|
80
|
+
}
|
|
81
|
+
getEventBody(extra) {
|
|
82
|
+
const personalizationID = this.context.session[SESSION_KEY_PERSONALIZATION_ID];
|
|
83
|
+
const body = { ...extra };
|
|
84
|
+
if (personalizationID) {
|
|
85
|
+
body["personalizationID"] = personalizationID;
|
|
86
|
+
}
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
__decorateClass([
|
|
91
|
+
Reactionary({
|
|
92
|
+
inputSchema: AnalyticsMutationSchema,
|
|
93
|
+
outputSchema: AnalyticsResultSchema
|
|
94
|
+
})
|
|
95
|
+
], HclAnalyticsCapability.prototype, "track", 1);
|
|
96
|
+
export {
|
|
97
|
+
HclAnalyticsCapability
|
|
98
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
import {
|
|
13
|
+
PersonalizationProfileCapability,
|
|
14
|
+
PersonalizationProfileQueryGetProfileSchema,
|
|
15
|
+
PersonalizationProfileSchema,
|
|
16
|
+
Reactionary,
|
|
17
|
+
success
|
|
18
|
+
} from "@reactionary/core";
|
|
19
|
+
import { SESSION_KEY_PERSONALIZATION_ID } from "../core/session-keys.js";
|
|
20
|
+
class HclPersonalizationProfileCapability extends PersonalizationProfileCapability {
|
|
21
|
+
constructor(cache, context, config, client, factory) {
|
|
22
|
+
super(cache, context);
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.client = client;
|
|
25
|
+
this.factory = factory;
|
|
26
|
+
}
|
|
27
|
+
async getPersonalizationProfile(_payload) {
|
|
28
|
+
const personalizationId = this.context.session[SESSION_KEY_PERSONALIZATION_ID];
|
|
29
|
+
if (!personalizationId) {
|
|
30
|
+
return success(
|
|
31
|
+
this.factory.parsePersonalizationProfile(this.context, {
|
|
32
|
+
personalizationId: "anonymous",
|
|
33
|
+
segments: []
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const segments = await this.fetchSegments(personalizationId);
|
|
38
|
+
return success(
|
|
39
|
+
this.factory.parsePersonalizationProfile(this.context, {
|
|
40
|
+
personalizationId,
|
|
41
|
+
segments
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Calls the WCS segment API to retrieve the customer segments for the given
|
|
47
|
+
* personalization ID. Returns an empty array when the API call fails or
|
|
48
|
+
* returns no segment data.
|
|
49
|
+
*/
|
|
50
|
+
async fetchSegments(personalizationId) {
|
|
51
|
+
try {
|
|
52
|
+
const response = await this.client.callGet(
|
|
53
|
+
this.getSegmentsUrl(),
|
|
54
|
+
this.getSegmentsParams(personalizationId),
|
|
55
|
+
{ allowUndefined: true }
|
|
56
|
+
);
|
|
57
|
+
if (!response?.MemberGroup) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return response.MemberGroup.map((g) => g.displayName?.value).filter(
|
|
61
|
+
(name) => name !== void 0 && name.length > 0
|
|
62
|
+
);
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
getSegmentsUrl() {
|
|
68
|
+
return `${this.client.transactionBaseUrl}/segment`;
|
|
69
|
+
}
|
|
70
|
+
getSegmentsParams(personalizationId) {
|
|
71
|
+
const params = new URLSearchParams();
|
|
72
|
+
params.set("q", "byPersonalizationId");
|
|
73
|
+
params.set("qPersonalizationId", personalizationId);
|
|
74
|
+
return params;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
__decorateClass([
|
|
78
|
+
Reactionary({
|
|
79
|
+
inputSchema: PersonalizationProfileQueryGetProfileSchema,
|
|
80
|
+
outputSchema: PersonalizationProfileSchema
|
|
81
|
+
})
|
|
82
|
+
], HclPersonalizationProfileCapability.prototype, "getPersonalizationProfile", 1);
|
|
83
|
+
export {
|
|
84
|
+
HclPersonalizationProfileCapability
|
|
85
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
import {
|
|
13
|
+
ProductAssociationsCapability,
|
|
14
|
+
ProductAssociationSchema,
|
|
15
|
+
ProductAssociationsGetAccessoriesQuerySchema,
|
|
16
|
+
ProductAssociationsGetSparepartsQuerySchema,
|
|
17
|
+
ProductAssociationsGetReplacementsQuerySchema,
|
|
18
|
+
Reactionary,
|
|
19
|
+
success
|
|
20
|
+
} from "@reactionary/core";
|
|
21
|
+
import * as z from "zod";
|
|
22
|
+
class HclProductAssociationsCapability extends ProductAssociationsCapability {
|
|
23
|
+
constructor(cache, context, config, client, factory) {
|
|
24
|
+
super(cache, context);
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.client = client;
|
|
27
|
+
this.factory = factory;
|
|
28
|
+
}
|
|
29
|
+
async getAccessories(query) {
|
|
30
|
+
const associations = await this.fetchAssociations(
|
|
31
|
+
query.forProduct.key,
|
|
32
|
+
this.config.associationTypes.accessories,
|
|
33
|
+
query.numberOfAccessories
|
|
34
|
+
);
|
|
35
|
+
return success(
|
|
36
|
+
associations.map((a) => this.factory.parseAssociation(this.context, a))
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
async getSpareparts(query) {
|
|
40
|
+
const associations = await this.fetchAssociations(
|
|
41
|
+
query.forProduct.key,
|
|
42
|
+
this.config.associationTypes.spareparts,
|
|
43
|
+
query.numberOfSpareparts
|
|
44
|
+
);
|
|
45
|
+
return success(
|
|
46
|
+
associations.map((a) => this.factory.parseAssociation(this.context, a))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
async getReplacements(query) {
|
|
50
|
+
const associations = await this.fetchAssociations(
|
|
51
|
+
query.forProduct.key,
|
|
52
|
+
this.config.associationTypes.replacements,
|
|
53
|
+
query.numberOfReplacements
|
|
54
|
+
);
|
|
55
|
+
return success(
|
|
56
|
+
associations.map((a) => this.factory.parseAssociation(this.context, a))
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fetches the product detail for the given part number and returns the
|
|
61
|
+
* `merchandisingAssociations` filtered to the requested association types,
|
|
62
|
+
* capped at `maxCount`.
|
|
63
|
+
*/
|
|
64
|
+
async fetchAssociations(partNumber, types, maxCount) {
|
|
65
|
+
const response = await this.client.callGet(
|
|
66
|
+
this.getProductsUrl(),
|
|
67
|
+
this.getProductsParams(partNumber)
|
|
68
|
+
);
|
|
69
|
+
const product = (response.contents ?? response.catalogEntryView ?? [])[0];
|
|
70
|
+
if (!product?.merchandisingAssociations) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
return product.merchandisingAssociations.filter(
|
|
74
|
+
(a) => a.associationCodeType && types.includes(a.associationCodeType)
|
|
75
|
+
).slice(0, maxCount);
|
|
76
|
+
}
|
|
77
|
+
getProductsUrl() {
|
|
78
|
+
return `${this.client.catalogBaseUrl}/api/v2/products`;
|
|
79
|
+
}
|
|
80
|
+
getProductsParams(partNumber) {
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
params.set("storeId", this.config.storeId);
|
|
83
|
+
params.append("partNumber", partNumber);
|
|
84
|
+
params.set("profileName", this.config.profiles.product);
|
|
85
|
+
return params;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
__decorateClass([
|
|
89
|
+
Reactionary({
|
|
90
|
+
inputSchema: ProductAssociationsGetAccessoriesQuerySchema,
|
|
91
|
+
outputSchema: z.array(ProductAssociationSchema),
|
|
92
|
+
cache: true,
|
|
93
|
+
cacheTimeToLiveInSeconds: 300,
|
|
94
|
+
currencyDependentCaching: false
|
|
95
|
+
})
|
|
96
|
+
], HclProductAssociationsCapability.prototype, "getAccessories", 1);
|
|
97
|
+
__decorateClass([
|
|
98
|
+
Reactionary({
|
|
99
|
+
inputSchema: ProductAssociationsGetSparepartsQuerySchema,
|
|
100
|
+
outputSchema: z.array(ProductAssociationSchema),
|
|
101
|
+
cache: true,
|
|
102
|
+
cacheTimeToLiveInSeconds: 300,
|
|
103
|
+
currencyDependentCaching: false
|
|
104
|
+
})
|
|
105
|
+
], HclProductAssociationsCapability.prototype, "getSpareparts", 1);
|
|
106
|
+
__decorateClass([
|
|
107
|
+
Reactionary({
|
|
108
|
+
inputSchema: ProductAssociationsGetReplacementsQuerySchema,
|
|
109
|
+
outputSchema: z.array(ProductAssociationSchema),
|
|
110
|
+
cache: true,
|
|
111
|
+
cacheTimeToLiveInSeconds: 300,
|
|
112
|
+
currencyDependentCaching: false
|
|
113
|
+
})
|
|
114
|
+
], HclProductAssociationsCapability.prototype, "getReplacements", 1);
|
|
115
|
+
export {
|
|
116
|
+
HclProductAssociationsCapability
|
|
117
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
import {
|
|
13
|
+
ProductRecommendationsCapability,
|
|
14
|
+
ProductRecommendationsQuerySchema,
|
|
15
|
+
ProductRecommendationsByCollectionQuerySchema,
|
|
16
|
+
Reactionary,
|
|
17
|
+
success
|
|
18
|
+
} from "@reactionary/core";
|
|
19
|
+
class HclProductRecommendationsCapability extends ProductRecommendationsCapability {
|
|
20
|
+
constructor(cache, context, config, client, factory) {
|
|
21
|
+
super(cache, context);
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.client = client;
|
|
24
|
+
this.factory = factory;
|
|
25
|
+
}
|
|
26
|
+
getRecommendations(...args) {
|
|
27
|
+
return super.getRecommendations(...args);
|
|
28
|
+
}
|
|
29
|
+
async getCollection(query) {
|
|
30
|
+
const params = {
|
|
31
|
+
productId: query.sourceProduct?.[0]?.key,
|
|
32
|
+
categoryId: query.sourceCategory?.key,
|
|
33
|
+
count: String(query.numberOfRecommendations)
|
|
34
|
+
};
|
|
35
|
+
const recommendations = await this.fetchEspot(
|
|
36
|
+
query.collectionName,
|
|
37
|
+
query.collectionName,
|
|
38
|
+
params
|
|
39
|
+
);
|
|
40
|
+
return success(recommendations.slice(0, query.numberOfRecommendations));
|
|
41
|
+
}
|
|
42
|
+
async getFrequentlyBoughtTogetherRecommendations(query) {
|
|
43
|
+
return this.fetchEspot(
|
|
44
|
+
this.config.espotNames.frequentlyBoughtTogether,
|
|
45
|
+
"frequentlyBoughtTogether",
|
|
46
|
+
{
|
|
47
|
+
productId: query.sourceProduct.key,
|
|
48
|
+
count: String(query.numberOfRecommendations)
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
async getSimilarProductsRecommendations(query) {
|
|
53
|
+
return this.fetchEspot(this.config.espotNames.similar, "similar", {
|
|
54
|
+
productId: query.sourceProduct.key,
|
|
55
|
+
count: String(query.numberOfRecommendations)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async getRelatedProductsRecommendations(query) {
|
|
59
|
+
return this.fetchEspot(this.config.espotNames.related, "related", {
|
|
60
|
+
productId: query.sourceProduct.key,
|
|
61
|
+
count: String(query.numberOfRecommendations)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async getTrendingInCategoryRecommendations(query) {
|
|
65
|
+
return this.fetchEspot(
|
|
66
|
+
this.config.espotNames.trendingInCategory,
|
|
67
|
+
"trendingInCategory",
|
|
68
|
+
{
|
|
69
|
+
categoryId: query.sourceCategory.key,
|
|
70
|
+
count: String(query.numberOfRecommendations)
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
async getPopularProductsRecommendations(query) {
|
|
75
|
+
return this.fetchEspot(this.config.espotNames.popular, "popular", {
|
|
76
|
+
count: String(query.numberOfRecommendations)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async getTopPicksProductsRecommendations(query) {
|
|
80
|
+
return this.fetchEspot(this.config.espotNames.topPicks, "topPicks", {
|
|
81
|
+
count: String(query.numberOfRecommendations)
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async getAlsoViewedProductsRecommendations(query) {
|
|
85
|
+
return this.fetchEspot(this.config.espotNames.alsoViewed, "alsoViewed", {
|
|
86
|
+
productId: query.sourceProduct.key,
|
|
87
|
+
count: String(query.numberOfRecommendations)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Calls an HCL marketing spot (espot) by name and maps its catalog entry
|
|
92
|
+
* activity data to `ProductRecommendation[]` using the factory.
|
|
93
|
+
*
|
|
94
|
+
* Returns an empty array when the espot is not found (404) or returns no
|
|
95
|
+
* catalog entry data — allowing teams to configure only the espots they need.
|
|
96
|
+
*/
|
|
97
|
+
async fetchEspot(name, algorithm, params = {}) {
|
|
98
|
+
const response = await this.client.callGet(
|
|
99
|
+
this.getEspotUrl(name),
|
|
100
|
+
this.getEspotParams(params),
|
|
101
|
+
{ allowUndefined: true }
|
|
102
|
+
);
|
|
103
|
+
if (!response) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const activities = response.MarketingSpotData?.[0]?.baseMarketingSpotActivityData ?? [];
|
|
107
|
+
return activities.filter(
|
|
108
|
+
(a) => a.baseMarketingSpotDataType === "CatalogEntry" || a.baseMarketingSpotDataType === "CatalogEntryId"
|
|
109
|
+
).map(
|
|
110
|
+
(a) => this.factory.parseRecommendation(this.context, {
|
|
111
|
+
activityData: a,
|
|
112
|
+
algorithm
|
|
113
|
+
})
|
|
114
|
+
).filter((r) => r.product.key !== "");
|
|
115
|
+
}
|
|
116
|
+
getEspotUrl(name) {
|
|
117
|
+
return `${this.client.transactionBaseUrl}/espot/${encodeURIComponent(name)}`;
|
|
118
|
+
}
|
|
119
|
+
getEspotParams(params) {
|
|
120
|
+
const searchParams = new URLSearchParams();
|
|
121
|
+
const count = params["count"];
|
|
122
|
+
if (count)
|
|
123
|
+
searchParams.set("DM_DisplayProducts", count);
|
|
124
|
+
if (params["productId"])
|
|
125
|
+
searchParams.set("productId", params["productId"]);
|
|
126
|
+
if (params["categoryId"])
|
|
127
|
+
searchParams.set("categoryId", params["categoryId"]);
|
|
128
|
+
return searchParams;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
__decorateClass([
|
|
132
|
+
Reactionary({
|
|
133
|
+
inputSchema: ProductRecommendationsQuerySchema
|
|
134
|
+
})
|
|
135
|
+
], HclProductRecommendationsCapability.prototype, "getRecommendations", 1);
|
|
136
|
+
__decorateClass([
|
|
137
|
+
Reactionary({
|
|
138
|
+
inputSchema: ProductRecommendationsByCollectionQuerySchema
|
|
139
|
+
})
|
|
140
|
+
], HclProductRecommendationsCapability.prototype, "getCollection", 1);
|
|
141
|
+
export {
|
|
142
|
+
HclProductRecommendationsCapability
|
|
143
|
+
};
|
package/core/initialize.js
CHANGED
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
IdentitySchema,
|
|
4
4
|
OrderSchema,
|
|
5
5
|
OrderSearchResultSchema,
|
|
6
|
+
PersonalizationProfileSchema,
|
|
7
|
+
ProductAssociationSchema,
|
|
8
|
+
ProductRecommendationSchema,
|
|
6
9
|
ProductSchema,
|
|
7
10
|
ProfileSchema
|
|
8
11
|
} from "@reactionary/core";
|
|
@@ -25,8 +28,11 @@ import {
|
|
|
25
28
|
HclInventoryFactory,
|
|
26
29
|
HclOrderFactory,
|
|
27
30
|
HclOrderSearchFactory,
|
|
31
|
+
HclPersonalizationProfileFactory,
|
|
28
32
|
HclPriceFactory,
|
|
33
|
+
HclProductAssociationsFactory,
|
|
29
34
|
HclProductFactory,
|
|
35
|
+
HclProductRecommendationsFactory,
|
|
30
36
|
HclProductSearchFactory,
|
|
31
37
|
HclProfileFactory
|
|
32
38
|
} from "../factories/index.js";
|
|
@@ -41,6 +47,10 @@ import { HclIdentityCapability } from "../capabilities/identity.capability.js";
|
|
|
41
47
|
import { HclProfileCapability } from "../capabilities/profile.capability.js";
|
|
42
48
|
import { HclOrderCapability } from "../capabilities/order.capability.js";
|
|
43
49
|
import { HclOrderSearchCapability } from "../capabilities/order-search.capability.js";
|
|
50
|
+
import { HclAnalyticsCapability } from "../capabilities/analytics.capability.js";
|
|
51
|
+
import { HclProductAssociationsCapability } from "../capabilities/product-associations.capability.js";
|
|
52
|
+
import { HclProductRecommendationsCapability } from "../capabilities/product-recommendations.capability.js";
|
|
53
|
+
import { HclPersonalizationProfileCapability } from "../capabilities/personalization-profile.capability.js";
|
|
44
54
|
import {
|
|
45
55
|
CartIdentifierSchema,
|
|
46
56
|
CartPaginatedSearchResultSchema,
|
|
@@ -252,6 +262,73 @@ function withHclCapabilities(configuration, capabilities) {
|
|
|
252
262
|
buildCapabilityArgs
|
|
253
263
|
);
|
|
254
264
|
}
|
|
265
|
+
if (caps.analytics?.enabled) {
|
|
266
|
+
const analyticsCap = capabilities.analytics;
|
|
267
|
+
if (analyticsCap?.capability) {
|
|
268
|
+
client.analytics = analyticsCap.capability(
|
|
269
|
+
buildCapabilityArgs(void 0)
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
client.analytics = new HclAnalyticsCapability(
|
|
273
|
+
cache,
|
|
274
|
+
context,
|
|
275
|
+
config,
|
|
276
|
+
hclClient
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (caps.productAssociations?.enabled) {
|
|
281
|
+
client.productAssociations = resolveCapabilityWithFactory(
|
|
282
|
+
capabilities.productAssociations,
|
|
283
|
+
{
|
|
284
|
+
factory: new HclProductAssociationsFactory(ProductAssociationSchema),
|
|
285
|
+
capability: (args) => new HclProductAssociationsCapability(
|
|
286
|
+
args.cache,
|
|
287
|
+
args.context,
|
|
288
|
+
args.config,
|
|
289
|
+
args.hclClient,
|
|
290
|
+
args.factory
|
|
291
|
+
)
|
|
292
|
+
},
|
|
293
|
+
buildCapabilityArgs
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
if (caps.productRecommendations?.enabled) {
|
|
297
|
+
client.productRecommendations = resolveCapabilityWithFactory(
|
|
298
|
+
capabilities.productRecommendations,
|
|
299
|
+
{
|
|
300
|
+
factory: new HclProductRecommendationsFactory(
|
|
301
|
+
ProductRecommendationSchema
|
|
302
|
+
),
|
|
303
|
+
capability: (args) => new HclProductRecommendationsCapability(
|
|
304
|
+
args.cache,
|
|
305
|
+
args.context,
|
|
306
|
+
args.config,
|
|
307
|
+
args.hclClient,
|
|
308
|
+
args.factory
|
|
309
|
+
)
|
|
310
|
+
},
|
|
311
|
+
buildCapabilityArgs
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (caps.personalizationProfile?.enabled) {
|
|
315
|
+
client.personalizationProfile = resolveCapabilityWithFactory(
|
|
316
|
+
capabilities.personalizationProfile,
|
|
317
|
+
{
|
|
318
|
+
factory: new HclPersonalizationProfileFactory(
|
|
319
|
+
PersonalizationProfileSchema
|
|
320
|
+
),
|
|
321
|
+
capability: (args) => new HclPersonalizationProfileCapability(
|
|
322
|
+
args.cache,
|
|
323
|
+
args.context,
|
|
324
|
+
args.config,
|
|
325
|
+
args.hclClient,
|
|
326
|
+
args.factory
|
|
327
|
+
)
|
|
328
|
+
},
|
|
329
|
+
buildCapabilityArgs
|
|
330
|
+
);
|
|
331
|
+
}
|
|
255
332
|
return client;
|
|
256
333
|
};
|
|
257
334
|
}
|
package/factories/index.js
CHANGED
|
@@ -9,3 +9,6 @@ export * from "./identity/identity.factory.js";
|
|
|
9
9
|
export * from "./profile/profile.factory.js";
|
|
10
10
|
export * from "./order/order.factory.js";
|
|
11
11
|
export * from "./order-search/order-search.factory.js";
|
|
12
|
+
export * from "./product-associations/product-associations.factory.js";
|
|
13
|
+
export * from "./product-recommendations/product-recommendations.factory.js";
|
|
14
|
+
export * from "./personalization-profile/personalization-profile.factory.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class HclPersonalizationProfileFactory {
|
|
2
|
+
personalizationProfileSchema;
|
|
3
|
+
constructor(personalizationProfileSchema) {
|
|
4
|
+
this.personalizationProfileSchema = personalizationProfileSchema;
|
|
5
|
+
}
|
|
6
|
+
parsePersonalizationProfile(_context, data) {
|
|
7
|
+
return this.personalizationProfileSchema.parse({
|
|
8
|
+
identifier: { key: data.personalizationId },
|
|
9
|
+
segments: data.segments,
|
|
10
|
+
blurb: ""
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
HclPersonalizationProfileFactory
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class HclProductAssociationsFactory {
|
|
2
|
+
productAssociationSchema;
|
|
3
|
+
constructor(productAssociationSchema) {
|
|
4
|
+
this.productAssociationSchema = productAssociationSchema;
|
|
5
|
+
}
|
|
6
|
+
parseAssociation(_context, data) {
|
|
7
|
+
return this.productAssociationSchema.parse({
|
|
8
|
+
associationIdentifier: { key: data.partNumber },
|
|
9
|
+
associationReturnType: "idOnly",
|
|
10
|
+
product: { key: data.partNumber }
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
HclProductAssociationsFactory
|
|
16
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class HclProductRecommendationsFactory {
|
|
2
|
+
productRecommendationSchema;
|
|
3
|
+
constructor(productRecommendationSchema) {
|
|
4
|
+
this.productRecommendationSchema = productRecommendationSchema;
|
|
5
|
+
}
|
|
6
|
+
parseRecommendation(_context, data) {
|
|
7
|
+
const key = data.activityData.partNumber ?? data.activityData.contentId ?? "";
|
|
8
|
+
return this.productRecommendationSchema.parse({
|
|
9
|
+
recommendationIdentifier: { key, algorithm: data.algorithm },
|
|
10
|
+
recommendationReturnType: "idOnly",
|
|
11
|
+
product: { key }
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
HclProductRecommendationsFactory
|
|
17
|
+
};
|
package/index.js
CHANGED
|
@@ -17,3 +17,7 @@ export * from "./capabilities/identity.capability.js";
|
|
|
17
17
|
export * from "./capabilities/profile.capability.js";
|
|
18
18
|
export * from "./capabilities/order.capability.js";
|
|
19
19
|
export * from "./capabilities/order-search.capability.js";
|
|
20
|
+
export * from "./capabilities/analytics.capability.js";
|
|
21
|
+
export * from "./capabilities/product-associations.capability.js";
|
|
22
|
+
export * from "./capabilities/product-recommendations.capability.js";
|
|
23
|
+
export * from "./capabilities/personalization-profile.capability.js";
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/hcl",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@reactionary/core": "0.9.
|
|
8
|
+
"@reactionary/core": "0.9.13",
|
|
9
9
|
"debug": "^4.4.3",
|
|
10
10
|
"zod": "4.1.9"
|
|
11
11
|
},
|
|
@@ -16,7 +16,11 @@ const HclCapabilitiesSchema = CapabilitiesSchema.pick({
|
|
|
16
16
|
productSearch: true,
|
|
17
17
|
profile: true,
|
|
18
18
|
order: true,
|
|
19
|
-
orderSearch: true
|
|
19
|
+
orderSearch: true,
|
|
20
|
+
analytics: true,
|
|
21
|
+
productAssociations: true,
|
|
22
|
+
productRecommendations: true,
|
|
23
|
+
personalizationProfile: true
|
|
20
24
|
}).extend({
|
|
21
25
|
cart: OverridableCapabilitySchema.optional(),
|
|
22
26
|
checkout: OverridableCapabilitySchema.optional(),
|
|
@@ -28,7 +32,11 @@ const HclCapabilitiesSchema = CapabilitiesSchema.pick({
|
|
|
28
32
|
productSearch: OverridableCapabilitySchema.optional(),
|
|
29
33
|
profile: OverridableCapabilitySchema.optional(),
|
|
30
34
|
order: OverridableCapabilitySchema.optional(),
|
|
31
|
-
orderSearch: OverridableCapabilitySchema.optional()
|
|
35
|
+
orderSearch: OverridableCapabilitySchema.optional(),
|
|
36
|
+
analytics: OverridableCapabilitySchema.optional(),
|
|
37
|
+
productAssociations: OverridableCapabilitySchema.optional(),
|
|
38
|
+
productRecommendations: OverridableCapabilitySchema.optional(),
|
|
39
|
+
personalizationProfile: OverridableCapabilitySchema.optional()
|
|
32
40
|
}).partial();
|
|
33
41
|
export {
|
|
34
42
|
HclCapabilitiesSchema
|
|
@@ -37,7 +37,43 @@ const HclConfigurationSchema = z.looseObject({
|
|
|
37
37
|
*/
|
|
38
38
|
priceRuleId: z.string().optional().meta({
|
|
39
39
|
description: "Price rule ID for /display_price WCS calls. Optional \u2014 the server uses its default rule when absent."
|
|
40
|
-
})
|
|
40
|
+
}),
|
|
41
|
+
/**
|
|
42
|
+
* Maps Reactionary association types to the HCL `associationCodeType` values
|
|
43
|
+
* returned in `merchandisingAssociations` on product detail responses.
|
|
44
|
+
* Override to match custom HCL store configuration.
|
|
45
|
+
*/
|
|
46
|
+
associationTypes: z.looseObject({
|
|
47
|
+
accessories: z.array(z.string()).default(["ACCESSORY"]),
|
|
48
|
+
spareparts: z.array(z.string()).default(["SPAREPART"]),
|
|
49
|
+
replacements: z.array(z.string()).default(["REPLACEMENT"])
|
|
50
|
+
}).default(() => ({
|
|
51
|
+
accessories: ["ACCESSORY"],
|
|
52
|
+
spareparts: ["SPAREPART"],
|
|
53
|
+
replacements: ["REPLACEMENT"]
|
|
54
|
+
})),
|
|
55
|
+
/**
|
|
56
|
+
* Names of HCL marketing spots used for product recommendation algorithms.
|
|
57
|
+
* Each algorithm calls the corresponding named espot via the WCS Transaction Service.
|
|
58
|
+
* These espots must be configured in HCL Commerce to return catalog entry data.
|
|
59
|
+
*/
|
|
60
|
+
espotNames: z.looseObject({
|
|
61
|
+
frequentlyBoughtTogether: z.string().default("Reactionary_FrequentlyBoughtTogether"),
|
|
62
|
+
similar: z.string().default("Reactionary_SimilarProducts"),
|
|
63
|
+
related: z.string().default("Reactionary_RelatedProducts"),
|
|
64
|
+
trendingInCategory: z.string().default("Reactionary_TrendingInCategory"),
|
|
65
|
+
popular: z.string().default("Reactionary_Popular"),
|
|
66
|
+
topPicks: z.string().default("Reactionary_TopPicks"),
|
|
67
|
+
alsoViewed: z.string().default("Reactionary_AlsoViewed")
|
|
68
|
+
}).default(() => ({
|
|
69
|
+
frequentlyBoughtTogether: "Reactionary_FrequentlyBoughtTogether",
|
|
70
|
+
similar: "Reactionary_SimilarProducts",
|
|
71
|
+
related: "Reactionary_RelatedProducts",
|
|
72
|
+
trendingInCategory: "Reactionary_TrendingInCategory",
|
|
73
|
+
popular: "Reactionary_Popular",
|
|
74
|
+
topPicks: "Reactionary_TopPicks",
|
|
75
|
+
alsoViewed: "Reactionary_AlsoViewed"
|
|
76
|
+
}))
|
|
41
77
|
});
|
|
42
78
|
export {
|
|
43
79
|
HclConfigurationSchema
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AnalyticsCapability, type AnalyticsMutation, type AnalyticsMutationProductAddToCartEvent, type AnalyticsMutationProductDetailsViewEvent, type AnalyticsMutationProductSummaryClickEvent, type AnalyticsMutationProductSummaryViewEvent, type AnalyticsMutationPurchaseEvent, type AnalyticsResult, type Cache, type RequestContext } from '@reactionary/core';
|
|
2
|
+
import type { HclConfiguration } from '../schema/configuration.schema.js';
|
|
3
|
+
import type { HclClient } from '../core/client.js';
|
|
4
|
+
export declare class HclAnalyticsCapability extends AnalyticsCapability {
|
|
5
|
+
protected readonly config: HclConfiguration;
|
|
6
|
+
protected readonly client: HclClient;
|
|
7
|
+
constructor(cache: Cache, context: RequestContext, config: HclConfiguration, client: HclClient);
|
|
8
|
+
track(event: AnalyticsMutation): Promise<AnalyticsResult>;
|
|
9
|
+
protected processProductSummaryView(event: AnalyticsMutationProductSummaryViewEvent): Promise<AnalyticsResult>;
|
|
10
|
+
protected processProductSummaryClick(event: AnalyticsMutationProductSummaryClickEvent): Promise<AnalyticsResult>;
|
|
11
|
+
protected processProductDetailsView(event: AnalyticsMutationProductDetailsViewEvent): Promise<AnalyticsResult>;
|
|
12
|
+
protected processProductAddToCart(event: AnalyticsMutationProductAddToCartEvent): Promise<AnalyticsResult>;
|
|
13
|
+
protected processPurchase(event: AnalyticsMutationPurchaseEvent): Promise<AnalyticsResult>;
|
|
14
|
+
protected fireEvent(extra: Record<string, string>): Promise<void>;
|
|
15
|
+
protected getEventUrl(): string;
|
|
16
|
+
protected getEventBody(extra: Record<string, string>): Record<string, string>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PersonalizationProfileCapability, type Cache, type NotFoundError, type PersonalizationProfileFactory, type PersonalizationProfileFactoryOutput, type PersonalizationProfileFactoryWithOutput, type PersonalizationProfileQueryGetProfile, type RequestContext, type Result } from '@reactionary/core';
|
|
2
|
+
import type { HclConfiguration } from '../schema/configuration.schema.js';
|
|
3
|
+
import type { HclClient } from '../core/client.js';
|
|
4
|
+
import type { HclPersonalizationProfileFactory } from '../factories/personalization-profile/personalization-profile.factory.js';
|
|
5
|
+
export declare class HclPersonalizationProfileCapability<TFactory extends PersonalizationProfileFactory = HclPersonalizationProfileFactory> extends PersonalizationProfileCapability<PersonalizationProfileFactoryOutput<TFactory>> {
|
|
6
|
+
protected readonly config: HclConfiguration;
|
|
7
|
+
protected readonly client: HclClient;
|
|
8
|
+
protected readonly factory: PersonalizationProfileFactoryWithOutput<TFactory>;
|
|
9
|
+
constructor(cache: Cache, context: RequestContext, config: HclConfiguration, client: HclClient, factory: PersonalizationProfileFactoryWithOutput<TFactory>);
|
|
10
|
+
getPersonalizationProfile(_payload: PersonalizationProfileQueryGetProfile): Promise<Result<PersonalizationProfileFactoryOutput<TFactory>, NotFoundError>>;
|
|
11
|
+
/**
|
|
12
|
+
* Calls the WCS segment API to retrieve the customer segments for the given
|
|
13
|
+
* personalization ID. Returns an empty array when the API call fails or
|
|
14
|
+
* returns no segment data.
|
|
15
|
+
*/
|
|
16
|
+
protected fetchSegments(personalizationId: string): Promise<string[]>;
|
|
17
|
+
protected getSegmentsUrl(): string;
|
|
18
|
+
protected getSegmentsParams(personalizationId: string): URLSearchParams;
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ProductAssociationsCapability, type Cache, type ProductAssociationsFactory, type ProductAssociationsFactoryOutput, type ProductAssociationsFactoryWithOutput, type ProductAssociationsGetAccessoriesQuery, type ProductAssociationsGetReplacementsQuery, type ProductAssociationsGetSparepartsQuery, type RequestContext, type Result } from '@reactionary/core';
|
|
2
|
+
import type { HclConfiguration } from '../schema/configuration.schema.js';
|
|
3
|
+
import type { HclClient } from '../core/client.js';
|
|
4
|
+
import type { HclAssociation } from '../schema/hcl.schema.js';
|
|
5
|
+
import type { HclProductAssociationsFactory } from '../factories/product-associations/product-associations.factory.js';
|
|
6
|
+
export declare class HclProductAssociationsCapability<TFactory extends ProductAssociationsFactory = HclProductAssociationsFactory> extends ProductAssociationsCapability<ProductAssociationsFactoryOutput<TFactory>> {
|
|
7
|
+
protected readonly config: HclConfiguration;
|
|
8
|
+
protected readonly client: HclClient;
|
|
9
|
+
protected readonly factory: ProductAssociationsFactoryWithOutput<TFactory>;
|
|
10
|
+
constructor(cache: Cache, context: RequestContext, config: HclConfiguration, client: HclClient, factory: ProductAssociationsFactoryWithOutput<TFactory>);
|
|
11
|
+
getAccessories(query: ProductAssociationsGetAccessoriesQuery): Promise<Result<ProductAssociationsFactoryOutput<TFactory>[]>>;
|
|
12
|
+
getSpareparts(query: ProductAssociationsGetSparepartsQuery): Promise<Result<ProductAssociationsFactoryOutput<TFactory>[]>>;
|
|
13
|
+
getReplacements(query: ProductAssociationsGetReplacementsQuery): Promise<Result<ProductAssociationsFactoryOutput<TFactory>[]>>;
|
|
14
|
+
/**
|
|
15
|
+
* Fetches the product detail for the given part number and returns the
|
|
16
|
+
* `merchandisingAssociations` filtered to the requested association types,
|
|
17
|
+
* capped at `maxCount`.
|
|
18
|
+
*/
|
|
19
|
+
protected fetchAssociations(partNumber: string, types: string[], maxCount: number): Promise<HclAssociation[]>;
|
|
20
|
+
protected getProductsUrl(): string;
|
|
21
|
+
protected getProductsParams(partNumber: string): URLSearchParams;
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ProductRecommendationsCapability, type Cache, type ProductRecommendation, type ProductRecommendationAlgorithmAlsoViewedProductsQuery, type ProductRecommendationAlgorithmFrequentlyBoughtTogetherQuery, type ProductRecommendationAlgorithmPopuplarProductsQuery, type ProductRecommendationAlgorithmRelatedProductsQuery, type ProductRecommendationAlgorithmSimilarProductsQuery, type ProductRecommendationAlgorithmTopPicksProductsQuery, type ProductRecommendationAlgorithmTrendingInCategoryQuery, type ProductRecommendationsByCollectionQuery, type ProductRecommendationsFactory, type ProductRecommendationsFactoryWithOutput, type RequestContext, type Result } from '@reactionary/core';
|
|
2
|
+
import type { HclConfiguration } from '../schema/configuration.schema.js';
|
|
3
|
+
import type { HclClient } from '../core/client.js';
|
|
4
|
+
import type { HclProductRecommendationsFactory } from '../factories/product-recommendations/product-recommendations.factory.js';
|
|
5
|
+
export declare class HclProductRecommendationsCapability<TFactory extends ProductRecommendationsFactory = HclProductRecommendationsFactory> extends ProductRecommendationsCapability {
|
|
6
|
+
protected readonly config: HclConfiguration;
|
|
7
|
+
protected readonly client: HclClient;
|
|
8
|
+
protected readonly factory: ProductRecommendationsFactoryWithOutput<TFactory>;
|
|
9
|
+
constructor(cache: Cache, context: RequestContext, config: HclConfiguration, client: HclClient, factory: ProductRecommendationsFactoryWithOutput<TFactory>);
|
|
10
|
+
getRecommendations(...args: Parameters<ProductRecommendationsCapability['getRecommendations']>): Promise<Result<ProductRecommendation[]>>;
|
|
11
|
+
getCollection(query: ProductRecommendationsByCollectionQuery): Promise<Result<ProductRecommendation[]>>;
|
|
12
|
+
protected getFrequentlyBoughtTogetherRecommendations(query: ProductRecommendationAlgorithmFrequentlyBoughtTogetherQuery): Promise<ProductRecommendation[]>;
|
|
13
|
+
protected getSimilarProductsRecommendations(query: ProductRecommendationAlgorithmSimilarProductsQuery): Promise<ProductRecommendation[]>;
|
|
14
|
+
protected getRelatedProductsRecommendations(query: ProductRecommendationAlgorithmRelatedProductsQuery): Promise<ProductRecommendation[]>;
|
|
15
|
+
protected getTrendingInCategoryRecommendations(query: ProductRecommendationAlgorithmTrendingInCategoryQuery): Promise<ProductRecommendation[]>;
|
|
16
|
+
protected getPopularProductsRecommendations(query: ProductRecommendationAlgorithmPopuplarProductsQuery): Promise<ProductRecommendation[]>;
|
|
17
|
+
protected getTopPicksProductsRecommendations(query: ProductRecommendationAlgorithmTopPicksProductsQuery): Promise<ProductRecommendation[]>;
|
|
18
|
+
protected getAlsoViewedProductsRecommendations(query: ProductRecommendationAlgorithmAlsoViewedProductsQuery): Promise<ProductRecommendation[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Calls an HCL marketing spot (espot) by name and maps its catalog entry
|
|
21
|
+
* activity data to `ProductRecommendation[]` using the factory.
|
|
22
|
+
*
|
|
23
|
+
* Returns an empty array when the espot is not found (404) or returns no
|
|
24
|
+
* catalog entry data — allowing teams to configure only the espots they need.
|
|
25
|
+
*/
|
|
26
|
+
protected fetchEspot(name: string, algorithm: string, params?: Record<string, string | undefined>): Promise<ProductRecommendation[]>;
|
|
27
|
+
protected getEspotUrl(name: string): string;
|
|
28
|
+
protected getEspotParams(params: Record<string, string | undefined>): URLSearchParams;
|
|
29
|
+
}
|
package/src/factories/index.d.ts
CHANGED
|
@@ -9,3 +9,6 @@ export * from './identity/identity.factory.js';
|
|
|
9
9
|
export * from './profile/profile.factory.js';
|
|
10
10
|
export * from './order/order.factory.js';
|
|
11
11
|
export * from './order-search/order-search.factory.js';
|
|
12
|
+
export * from './product-associations/product-associations.factory.js';
|
|
13
|
+
export * from './product-recommendations/product-recommendations.factory.js';
|
|
14
|
+
export * from './personalization-profile/personalization-profile.factory.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AnyPersonalizationProfileSchema, PersonalizationProfileFactory, PersonalizationProfileSchema, RequestContext } from '@reactionary/core';
|
|
2
|
+
import type * as z from 'zod';
|
|
3
|
+
export interface HclPersonalizationProfileData {
|
|
4
|
+
personalizationId: string;
|
|
5
|
+
segments: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare class HclPersonalizationProfileFactory<TPersonalizationProfileSchema extends AnyPersonalizationProfileSchema = typeof PersonalizationProfileSchema> implements PersonalizationProfileFactory<TPersonalizationProfileSchema> {
|
|
8
|
+
readonly personalizationProfileSchema: TPersonalizationProfileSchema;
|
|
9
|
+
constructor(personalizationProfileSchema: TPersonalizationProfileSchema);
|
|
10
|
+
parsePersonalizationProfile(_context: RequestContext, data: HclPersonalizationProfileData): z.output<TPersonalizationProfileSchema>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnyProductAssociationSchema, ProductAssociationSchema, ProductAssociationsFactory, RequestContext } from '@reactionary/core';
|
|
2
|
+
import type * as z from 'zod';
|
|
3
|
+
import type { HclAssociation } from '../../schema/hcl.schema.js';
|
|
4
|
+
export declare class HclProductAssociationsFactory<TProductAssociationSchema extends AnyProductAssociationSchema = typeof ProductAssociationSchema> implements ProductAssociationsFactory<TProductAssociationSchema> {
|
|
5
|
+
readonly productAssociationSchema: TProductAssociationSchema;
|
|
6
|
+
constructor(productAssociationSchema: TProductAssociationSchema);
|
|
7
|
+
parseAssociation(_context: RequestContext, data: HclAssociation): z.output<TProductAssociationSchema>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AnyProductRecommendationSchema, ProductRecommendationSchema, ProductRecommendationsFactory, RequestContext } from '@reactionary/core';
|
|
2
|
+
import type * as z from 'zod';
|
|
3
|
+
import type { HclEspotActivityData } from '../../schema/hcl.schema.js';
|
|
4
|
+
export interface HclEspotRecommendationData {
|
|
5
|
+
activityData: HclEspotActivityData;
|
|
6
|
+
algorithm: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class HclProductRecommendationsFactory<TProductRecommendationSchema extends AnyProductRecommendationSchema = typeof ProductRecommendationSchema> implements ProductRecommendationsFactory<TProductRecommendationSchema> {
|
|
9
|
+
readonly productRecommendationSchema: TProductRecommendationSchema;
|
|
10
|
+
constructor(productRecommendationSchema: TProductRecommendationSchema);
|
|
11
|
+
parseRecommendation(_context: RequestContext, data: HclEspotRecommendationData): z.output<TProductRecommendationSchema>;
|
|
12
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -17,3 +17,7 @@ export * from './capabilities/identity.capability.js';
|
|
|
17
17
|
export * from './capabilities/profile.capability.js';
|
|
18
18
|
export * from './capabilities/order.capability.js';
|
|
19
19
|
export * from './capabilities/order-search.capability.js';
|
|
20
|
+
export * from './capabilities/analytics.capability.js';
|
|
21
|
+
export * from './capabilities/product-associations.capability.js';
|
|
22
|
+
export * from './capabilities/product-recommendations.capability.js';
|
|
23
|
+
export * from './capabilities/personalization-profile.capability.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Cache, RequestContext, CartFactory, CartFactoryWithOutput, CartCapability, CategoryFactory, CategoryFactoryWithOutput, CategoryCapability, CheckoutFactory, CheckoutFactoryWithOutput, CheckoutCapability, IdentityFactory, IdentityFactoryWithOutput, IdentityCapability, InventoryFactory, InventoryFactoryWithOutput, InventoryCapability, OrderFactory, OrderFactoryWithOutput, OrderCapability, OrderSearchFactory, OrderSearchFactoryWithOutput, OrderSearchCapability, PriceFactory, PriceFactoryWithOutput, PriceCapability, ProductFactory, ProductFactoryWithOutput, ProductCapability, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchCapability, ProfileFactory, ProfileFactoryWithOutput, ProfileCapability } from '@reactionary/core';
|
|
1
|
+
import type { Cache, RequestContext, AnalyticsCapability, CartFactory, CartFactoryWithOutput, CartCapability, CategoryFactory, CategoryFactoryWithOutput, CategoryCapability, CheckoutFactory, CheckoutFactoryWithOutput, CheckoutCapability, IdentityFactory, IdentityFactoryWithOutput, IdentityCapability, InventoryFactory, InventoryFactoryWithOutput, InventoryCapability, OrderFactory, OrderFactoryWithOutput, OrderCapability, OrderSearchFactory, OrderSearchFactoryWithOutput, OrderSearchCapability, PersonalizationProfileCapability, PersonalizationProfileFactory, PersonalizationProfileFactoryWithOutput, PriceFactory, PriceFactoryWithOutput, PriceCapability, ProductAssociationsCapability, ProductAssociationsFactory, ProductAssociationsFactoryWithOutput, ProductFactory, ProductFactoryWithOutput, ProductCapability, ProductRecommendationsCapability, ProductRecommendationsFactory, ProductRecommendationsFactoryWithOutput, ProductSearchFactory, ProductSearchFactoryWithOutput, ProductSearchCapability, ProfileFactory, ProfileFactoryWithOutput, ProfileCapability } from '@reactionary/core';
|
|
2
2
|
import type { HclConfiguration } from './configuration.schema.js';
|
|
3
3
|
import type { HclClient } from '../core/client.js';
|
|
4
4
|
import * as z from 'zod';
|
|
@@ -58,6 +58,26 @@ export declare const HclCapabilitiesSchema: z.ZodObject<{
|
|
|
58
58
|
factory: z.ZodOptional<z.ZodUnknown>;
|
|
59
59
|
capability: z.ZodOptional<z.ZodUnknown>;
|
|
60
60
|
}, z.core.$loose>>>;
|
|
61
|
+
analytics: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
62
|
+
enabled: z.ZodBoolean;
|
|
63
|
+
factory: z.ZodOptional<z.ZodUnknown>;
|
|
64
|
+
capability: z.ZodOptional<z.ZodUnknown>;
|
|
65
|
+
}, z.core.$loose>>>;
|
|
66
|
+
productAssociations: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
67
|
+
enabled: z.ZodBoolean;
|
|
68
|
+
factory: z.ZodOptional<z.ZodUnknown>;
|
|
69
|
+
capability: z.ZodOptional<z.ZodUnknown>;
|
|
70
|
+
}, z.core.$loose>>>;
|
|
71
|
+
productRecommendations: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
72
|
+
enabled: z.ZodBoolean;
|
|
73
|
+
factory: z.ZodOptional<z.ZodUnknown>;
|
|
74
|
+
capability: z.ZodOptional<z.ZodUnknown>;
|
|
75
|
+
}, z.core.$loose>>>;
|
|
76
|
+
personalizationProfile: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
77
|
+
enabled: z.ZodBoolean;
|
|
78
|
+
factory: z.ZodOptional<z.ZodUnknown>;
|
|
79
|
+
capability: z.ZodOptional<z.ZodUnknown>;
|
|
80
|
+
}, z.core.$loose>>>;
|
|
61
81
|
}, z.core.$loose>;
|
|
62
82
|
export type HclCapabilities = z.infer<typeof HclCapabilitiesSchema>;
|
|
63
83
|
export interface HclCapabilityFactoryArgs {
|
|
@@ -85,3 +105,9 @@ export type HclIdentityCapabilityConfig = HclCapabilityConfig<IdentityFactoryWit
|
|
|
85
105
|
export type HclProfileCapabilityConfig = HclCapabilityConfig<ProfileFactoryWithOutput<ProfileFactory>, ProfileCapability>;
|
|
86
106
|
export type HclOrderCapabilityConfig = HclCapabilityConfig<OrderFactoryWithOutput<OrderFactory>, OrderCapability>;
|
|
87
107
|
export type HclOrderSearchCapabilityConfig = HclCapabilityConfig<OrderSearchFactoryWithOutput<OrderSearchFactory>, OrderSearchCapability>;
|
|
108
|
+
/** Analytics does not use a factory — the capability writes events directly. */
|
|
109
|
+
export type HclAnalyticsCapabilityConfig = HclCapabilityConfig<never, AnalyticsCapability>;
|
|
110
|
+
export type HclProductAssociationsCapabilityConfig = HclCapabilityConfig<ProductAssociationsFactoryWithOutput<ProductAssociationsFactory>, ProductAssociationsCapability>;
|
|
111
|
+
/** Product recommendations uses a factory to parse espot activity data into recommendations. */
|
|
112
|
+
export type HclProductRecommendationsCapabilityConfig = HclCapabilityConfig<ProductRecommendationsFactoryWithOutput<ProductRecommendationsFactory>, ProductRecommendationsCapability>;
|
|
113
|
+
export type HclPersonalizationProfileCapabilityConfig = HclCapabilityConfig<PersonalizationProfileFactoryWithOutput<PersonalizationProfileFactory>, PersonalizationProfileCapability>;
|
|
@@ -21,5 +21,29 @@ export declare const HclConfigurationSchema: z.ZodObject<{
|
|
|
21
21
|
* In Karkkainen this is '10003'.
|
|
22
22
|
*/
|
|
23
23
|
priceRuleId: z.ZodOptional<z.ZodString>;
|
|
24
|
+
/**
|
|
25
|
+
* Maps Reactionary association types to the HCL `associationCodeType` values
|
|
26
|
+
* returned in `merchandisingAssociations` on product detail responses.
|
|
27
|
+
* Override to match custom HCL store configuration.
|
|
28
|
+
*/
|
|
29
|
+
associationTypes: z.ZodDefault<z.ZodObject<{
|
|
30
|
+
accessories: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
31
|
+
spareparts: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
32
|
+
replacements: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
33
|
+
}, z.core.$loose>>;
|
|
34
|
+
/**
|
|
35
|
+
* Names of HCL marketing spots used for product recommendation algorithms.
|
|
36
|
+
* Each algorithm calls the corresponding named espot via the WCS Transaction Service.
|
|
37
|
+
* These espots must be configured in HCL Commerce to return catalog entry data.
|
|
38
|
+
*/
|
|
39
|
+
espotNames: z.ZodDefault<z.ZodObject<{
|
|
40
|
+
frequentlyBoughtTogether: z.ZodDefault<z.ZodString>;
|
|
41
|
+
similar: z.ZodDefault<z.ZodString>;
|
|
42
|
+
related: z.ZodDefault<z.ZodString>;
|
|
43
|
+
trendingInCategory: z.ZodDefault<z.ZodString>;
|
|
44
|
+
popular: z.ZodDefault<z.ZodString>;
|
|
45
|
+
topPicks: z.ZodDefault<z.ZodString>;
|
|
46
|
+
alsoViewed: z.ZodDefault<z.ZodString>;
|
|
47
|
+
}, z.core.$loose>>;
|
|
24
48
|
}, z.core.$loose>;
|
|
25
49
|
export type HclConfiguration = z.infer<typeof HclConfigurationSchema>;
|
|
@@ -234,7 +234,7 @@ export interface HclProductResponse extends HclProductSearchItem {
|
|
|
234
234
|
components?: HclProductResponse[];
|
|
235
235
|
/** Quantity — only present on components array elements */
|
|
236
236
|
quantity?: string;
|
|
237
|
-
merchandisingAssociations?:
|
|
237
|
+
merchandisingAssociations?: HclAssociation[];
|
|
238
238
|
attachments?: HclAttachment[];
|
|
239
239
|
images?: HclImage[];
|
|
240
240
|
/**
|
|
@@ -694,3 +694,61 @@ export interface HclWcsOrderHistoryResponse {
|
|
|
694
694
|
recordSetStartNumber?: string;
|
|
695
695
|
recordSetComplete?: string;
|
|
696
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* A product item returned within a `merchandisingAssociations` array on a
|
|
699
|
+
* HCL detail-profile product response. Extends the search item shape with
|
|
700
|
+
* an `associationCodeType` that identifies the semantic relationship.
|
|
701
|
+
*
|
|
702
|
+
* Known values: 'ACCESSORY', 'SPAREPART', 'REPLACEMENT', 'CROSSSELL', 'UPSELL', 'UPGRADE'
|
|
703
|
+
*/
|
|
704
|
+
export interface HclAssociation extends HclProductSearchItem {
|
|
705
|
+
/** The association type code. e.g. 'ACCESSORY', 'SPAREPART', 'REPLACEMENT'. */
|
|
706
|
+
associationCodeType?: string;
|
|
707
|
+
/** Display sequence for ordering associations within a type. */
|
|
708
|
+
associationSequence?: string;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* A single activity data entry within a marketing spot response.
|
|
712
|
+
* When `baseMarketingSpotDataType` is 'CatalogEntry' or 'CatalogEntryId',
|
|
713
|
+
* the entry represents a product recommendation from the spot.
|
|
714
|
+
*/
|
|
715
|
+
export interface HclEspotActivityData {
|
|
716
|
+
/** 'CatalogEntry' | 'CatalogEntryId' | 'CatalogGroupId' | 'MarketingContent' */
|
|
717
|
+
baseMarketingSpotDataType?: string;
|
|
718
|
+
/** Internal HCL product uniqueID (numeric string). Present for CatalogEntry/CatalogEntryId types. */
|
|
719
|
+
contentId?: string;
|
|
720
|
+
/** Part number of the product, if returned inline by the spot configuration. */
|
|
721
|
+
partNumber?: string;
|
|
722
|
+
/** Display name of the product or content entry. */
|
|
723
|
+
contentName?: string;
|
|
724
|
+
}
|
|
725
|
+
/** A single marketing spot entry in a WCS espot response. */
|
|
726
|
+
export interface HclEspotMarketingSpot {
|
|
727
|
+
eSpotName?: string;
|
|
728
|
+
baseMarketingSpotActivityData?: HclEspotActivityData[];
|
|
729
|
+
}
|
|
730
|
+
/** Top-level response from GET /wcs/resources/store/{storeId}/espot/{name}. */
|
|
731
|
+
export interface HclEspotResponse {
|
|
732
|
+
MarketingSpotData?: HclEspotMarketingSpot[];
|
|
733
|
+
}
|
|
734
|
+
/** A single customer segment (member group) entry in a WCS segment response. */
|
|
735
|
+
export interface HclSegmentMemberGroup {
|
|
736
|
+
id: string;
|
|
737
|
+
displayName?: {
|
|
738
|
+
language: number;
|
|
739
|
+
value: string;
|
|
740
|
+
};
|
|
741
|
+
description?: {
|
|
742
|
+
language: number;
|
|
743
|
+
value: string;
|
|
744
|
+
};
|
|
745
|
+
usage?: string[];
|
|
746
|
+
}
|
|
747
|
+
/** Top-level response from GET /wcs/resources/store/{storeId}/segment. */
|
|
748
|
+
export interface HclSegmentResponse {
|
|
749
|
+
MemberGroup?: HclSegmentMemberGroup[];
|
|
750
|
+
recordSetTotal?: number;
|
|
751
|
+
recordSetCount?: number;
|
|
752
|
+
recordSetStartNumber?: number;
|
|
753
|
+
recordSetComplete?: boolean;
|
|
754
|
+
}
|