@reactionary/hcl 0.9.2
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/README.md +11 -0
- package/capabilities/category.capability.js +170 -0
- package/capabilities/product-search.capability.js +89 -0
- package/capabilities/product.capability.js +157 -0
- package/core/client.js +120 -0
- package/core/initialize.js +97 -0
- package/core/initialize.types.js +8 -0
- package/core/locale-params.js +9 -0
- package/factories/category/category.factory.js +60 -0
- package/factories/index.js +3 -0
- package/factories/product/product.factory.js +154 -0
- package/factories/product-search/product-search.factory.js +71 -0
- package/index.js +11 -0
- package/package.json +14 -0
- package/schema/capabilities.schema.js +27 -0
- package/schema/category.schema.js +9 -0
- package/schema/configuration.schema.js +33 -0
- package/schema/hcl.schema.js +0 -0
- package/src/capabilities/category.capability.d.ts +15 -0
- package/src/capabilities/product-search.capability.d.ts +14 -0
- package/src/capabilities/product.capability.d.ts +20 -0
- package/src/core/client.d.ts +19 -0
- package/src/core/initialize.d.ts +5 -0
- package/src/core/initialize.types.d.ts +42 -0
- package/src/core/locale-params.d.ts +6 -0
- package/src/factories/category/category.factory.d.ts +12 -0
- package/src/factories/index.d.ts +3 -0
- package/src/factories/product/product.factory.d.ts +8 -0
- package/src/factories/product-search/product-search.factory.d.ts +11 -0
- package/src/index.d.ts +11 -0
- package/src/schema/capabilities.schema.d.ts +63 -0
- package/src/schema/category.schema.d.ts +27 -0
- package/src/schema/configuration.schema.d.ts +18 -0
- package/src/schema/hcl.schema.d.ts +312 -0
- package/src/test/test-utils.d.ts +241 -0
- package/test/test-utils.js +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
CategoryCapability,
|
|
14
|
+
CategoryPaginatedResultSchema,
|
|
15
|
+
CategoryQueryByIdSchema,
|
|
16
|
+
CategoryQueryBySlugSchema,
|
|
17
|
+
CategoryQueryForBreadcrumbSchema,
|
|
18
|
+
CategoryQueryForChildCategoriesSchema,
|
|
19
|
+
CategoryQueryForTopCategoriesSchema,
|
|
20
|
+
CategorySchema,
|
|
21
|
+
Reactionary,
|
|
22
|
+
error,
|
|
23
|
+
success
|
|
24
|
+
} from "@reactionary/core";
|
|
25
|
+
import * as z from "zod";
|
|
26
|
+
import { getLocaleParams } from "../core/locale-params.js";
|
|
27
|
+
class HclCategoryCapability extends CategoryCapability {
|
|
28
|
+
constructor(cache, context, config, client, factory) {
|
|
29
|
+
super(cache, context);
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.client = client;
|
|
32
|
+
this.factory = factory;
|
|
33
|
+
}
|
|
34
|
+
async getById(payload) {
|
|
35
|
+
const { langId } = getLocaleParams(this.config, this.context);
|
|
36
|
+
const response = await this.client.findCategories({
|
|
37
|
+
identifier: [payload.id.key],
|
|
38
|
+
langId
|
|
39
|
+
});
|
|
40
|
+
const data = response.contents?.[0];
|
|
41
|
+
if (!data) {
|
|
42
|
+
return error({ type: "NotFound", identifier: payload.id });
|
|
43
|
+
}
|
|
44
|
+
return success(this.factory.parseCategory(this.context, data));
|
|
45
|
+
}
|
|
46
|
+
async getBySlug(payload) {
|
|
47
|
+
const { langId } = getLocaleParams(this.config, this.context);
|
|
48
|
+
const token = await this.client.resolveSlug(payload.slug, langId);
|
|
49
|
+
if (!token || token.tokenName !== "CategoryToken" || !token.tokenExternalValue) {
|
|
50
|
+
return error({
|
|
51
|
+
type: "NotFound",
|
|
52
|
+
identifier: payload.slug
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const response = await this.client.findCategories({
|
|
56
|
+
// tokenExternalValue is the external identifier (e.g. "LivingRoom").
|
|
57
|
+
// Use identifier= param so the key returned by the factory is consistent.
|
|
58
|
+
identifier: [token.tokenExternalValue],
|
|
59
|
+
langId
|
|
60
|
+
});
|
|
61
|
+
const data = response.contents?.[0];
|
|
62
|
+
if (!data) {
|
|
63
|
+
return error({
|
|
64
|
+
type: "NotFound",
|
|
65
|
+
identifier: payload.slug
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return success(this.factory.parseCategory(this.context, data));
|
|
69
|
+
}
|
|
70
|
+
async getBreadcrumbPathToCategory(payload) {
|
|
71
|
+
const { langId } = getLocaleParams(this.config, this.context);
|
|
72
|
+
const leafResponse = await this.client.findCategories({
|
|
73
|
+
identifier: [payload.id.key],
|
|
74
|
+
langId
|
|
75
|
+
});
|
|
76
|
+
const leafData = leafResponse.contents?.[0];
|
|
77
|
+
if (!leafData) {
|
|
78
|
+
return success([]);
|
|
79
|
+
}
|
|
80
|
+
const parentId = leafData.parentCatalogGroupID;
|
|
81
|
+
const pathSegments = (typeof parentId === "string" ? parentId : parentId?.[0] ?? "").split("/").filter(Boolean);
|
|
82
|
+
const ancestorIds = pathSegments.slice(0, -1);
|
|
83
|
+
const ancestorsResp = ancestorIds.length > 0 ? await this.client.findCategories({ id: ancestorIds, langId }) : { contents: [] };
|
|
84
|
+
const byUniqueId = new Map(
|
|
85
|
+
(ancestorsResp.contents ?? []).map((c) => [c.uniqueID, c])
|
|
86
|
+
);
|
|
87
|
+
const path = [];
|
|
88
|
+
for (const id of ancestorIds) {
|
|
89
|
+
const data = byUniqueId.get(id);
|
|
90
|
+
if (data) {
|
|
91
|
+
path.push(this.factory.parseCategory(this.context, data));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
path.push(this.factory.parseCategory(this.context, leafData));
|
|
95
|
+
return success(path);
|
|
96
|
+
}
|
|
97
|
+
async findChildCategories(payload) {
|
|
98
|
+
const { langId } = getLocaleParams(this.config, this.context);
|
|
99
|
+
const parentResp = await this.client.findCategories({
|
|
100
|
+
identifier: [payload.parentId.key],
|
|
101
|
+
langId
|
|
102
|
+
});
|
|
103
|
+
const parentUniqueId = parentResp.contents?.[0]?.uniqueID;
|
|
104
|
+
if (!parentUniqueId) {
|
|
105
|
+
return error({
|
|
106
|
+
type: "NotFound",
|
|
107
|
+
identifier: payload.parentId
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const response = await this.client.findCategories({
|
|
111
|
+
parentCategoryId: parentUniqueId,
|
|
112
|
+
depthAndLimit: "1,0",
|
|
113
|
+
langId
|
|
114
|
+
});
|
|
115
|
+
const items = response.contents ?? [];
|
|
116
|
+
const paginated = this.factory.parseCategoryPaginatedResult(
|
|
117
|
+
this.context,
|
|
118
|
+
items,
|
|
119
|
+
payload
|
|
120
|
+
);
|
|
121
|
+
return success(paginated);
|
|
122
|
+
}
|
|
123
|
+
async findTopCategories(payload) {
|
|
124
|
+
const { langId } = getLocaleParams(this.config, this.context);
|
|
125
|
+
const response = await this.client.findCategories({
|
|
126
|
+
depthAndLimit: "1,0",
|
|
127
|
+
langId
|
|
128
|
+
});
|
|
129
|
+
const items = response.contents ?? [];
|
|
130
|
+
const paginated = this.factory.parseCategoryPaginatedResult(
|
|
131
|
+
this.context,
|
|
132
|
+
items,
|
|
133
|
+
payload
|
|
134
|
+
);
|
|
135
|
+
return success(paginated);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
__decorateClass([
|
|
139
|
+
Reactionary({
|
|
140
|
+
inputSchema: CategoryQueryByIdSchema,
|
|
141
|
+
outputSchema: CategorySchema
|
|
142
|
+
})
|
|
143
|
+
], HclCategoryCapability.prototype, "getById", 1);
|
|
144
|
+
__decorateClass([
|
|
145
|
+
Reactionary({
|
|
146
|
+
inputSchema: CategoryQueryBySlugSchema,
|
|
147
|
+
outputSchema: CategorySchema
|
|
148
|
+
})
|
|
149
|
+
], HclCategoryCapability.prototype, "getBySlug", 1);
|
|
150
|
+
__decorateClass([
|
|
151
|
+
Reactionary({
|
|
152
|
+
inputSchema: CategoryQueryForBreadcrumbSchema,
|
|
153
|
+
outputSchema: z.array(CategorySchema)
|
|
154
|
+
})
|
|
155
|
+
], HclCategoryCapability.prototype, "getBreadcrumbPathToCategory", 1);
|
|
156
|
+
__decorateClass([
|
|
157
|
+
Reactionary({
|
|
158
|
+
inputSchema: CategoryQueryForChildCategoriesSchema,
|
|
159
|
+
outputSchema: CategoryPaginatedResultSchema
|
|
160
|
+
})
|
|
161
|
+
], HclCategoryCapability.prototype, "findChildCategories", 1);
|
|
162
|
+
__decorateClass([
|
|
163
|
+
Reactionary({
|
|
164
|
+
inputSchema: CategoryQueryForTopCategoriesSchema,
|
|
165
|
+
outputSchema: CategoryPaginatedResultSchema
|
|
166
|
+
})
|
|
167
|
+
], HclCategoryCapability.prototype, "findTopCategories", 1);
|
|
168
|
+
export {
|
|
169
|
+
HclCategoryCapability
|
|
170
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
FacetValueIdentifierSchema,
|
|
14
|
+
ProductSearchCapability,
|
|
15
|
+
ProductSearchQueryByTermSchema,
|
|
16
|
+
ProductSearchQueryCreateNavigationFilterSchema,
|
|
17
|
+
ProductSearchResultSchema,
|
|
18
|
+
Reactionary,
|
|
19
|
+
success
|
|
20
|
+
} from "@reactionary/core";
|
|
21
|
+
import { getLocaleParams } from "../core/locale-params.js";
|
|
22
|
+
class HclProductSearchCapability extends ProductSearchCapability {
|
|
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
|
+
queryByTermPayload(payload) {
|
|
30
|
+
const { term, paginationOptions, categoryFilter, facets } = payload.search;
|
|
31
|
+
const { pageNumber, pageSize } = paginationOptions;
|
|
32
|
+
const { langId, currency } = getLocaleParams(this.config, this.context);
|
|
33
|
+
const categoryId = categoryFilter?.key || void 0;
|
|
34
|
+
return {
|
|
35
|
+
searchTerm: term || void 0,
|
|
36
|
+
categoryId,
|
|
37
|
+
limit: pageSize,
|
|
38
|
+
offset: (pageNumber - 1) * pageSize,
|
|
39
|
+
profileName: categoryId ? this.config.profiles.categoryBrowse : this.config.profiles.productSearch,
|
|
40
|
+
facets: facets.length > 0 ? facets.map((f) => f.key) : void 0,
|
|
41
|
+
langId,
|
|
42
|
+
currency
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async queryByTerm(payload) {
|
|
46
|
+
const response = await this.client.findProducts(
|
|
47
|
+
this.queryByTermPayload(payload)
|
|
48
|
+
);
|
|
49
|
+
const value = this.factory.parseSearchResult(
|
|
50
|
+
this.context,
|
|
51
|
+
response,
|
|
52
|
+
payload
|
|
53
|
+
);
|
|
54
|
+
return success(value);
|
|
55
|
+
}
|
|
56
|
+
async createCategoryNavigationFilter(payload) {
|
|
57
|
+
const leaf = payload.categoryPath.at(-1);
|
|
58
|
+
const externalKey = leaf?.identifier.key ?? "";
|
|
59
|
+
let uniqueId = leaf?.uniqueId;
|
|
60
|
+
if (!uniqueId) {
|
|
61
|
+
const { langId } = getLocaleParams(this.config, this.context);
|
|
62
|
+
const catResp = await this.client.findCategories({
|
|
63
|
+
identifier: [externalKey],
|
|
64
|
+
langId
|
|
65
|
+
});
|
|
66
|
+
uniqueId = catResp.contents?.[0]?.uniqueID ?? externalKey;
|
|
67
|
+
}
|
|
68
|
+
const filter = {
|
|
69
|
+
facet: { key: "categories" },
|
|
70
|
+
key: uniqueId
|
|
71
|
+
};
|
|
72
|
+
return success(filter);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
__decorateClass([
|
|
76
|
+
Reactionary({
|
|
77
|
+
inputSchema: ProductSearchQueryByTermSchema,
|
|
78
|
+
outputSchema: ProductSearchResultSchema
|
|
79
|
+
})
|
|
80
|
+
], HclProductSearchCapability.prototype, "queryByTerm", 1);
|
|
81
|
+
__decorateClass([
|
|
82
|
+
Reactionary({
|
|
83
|
+
inputSchema: ProductSearchQueryCreateNavigationFilterSchema,
|
|
84
|
+
outputSchema: FacetValueIdentifierSchema
|
|
85
|
+
})
|
|
86
|
+
], HclProductSearchCapability.prototype, "createCategoryNavigationFilter", 1);
|
|
87
|
+
export {
|
|
88
|
+
HclProductSearchCapability
|
|
89
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
ProductCapability,
|
|
14
|
+
ProductQueryByIdSchema,
|
|
15
|
+
ProductQueryBySKUSchema,
|
|
16
|
+
ProductQueryBySlugSchema,
|
|
17
|
+
ProductSchema,
|
|
18
|
+
Reactionary,
|
|
19
|
+
error,
|
|
20
|
+
success
|
|
21
|
+
} from "@reactionary/core";
|
|
22
|
+
import { getLocaleParams } from "../core/locale-params.js";
|
|
23
|
+
class HclProductCapability extends ProductCapability {
|
|
24
|
+
constructor(cache, context, config, client, factory) {
|
|
25
|
+
super(cache, context);
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.client = client;
|
|
28
|
+
this.factory = factory;
|
|
29
|
+
}
|
|
30
|
+
async getById(payload) {
|
|
31
|
+
const { langId, currency } = getLocaleParams(this.config, this.context);
|
|
32
|
+
const response = await this.client.findProducts({
|
|
33
|
+
partNumber: [payload.identifier.key],
|
|
34
|
+
profileName: this.config.profiles.product,
|
|
35
|
+
langId,
|
|
36
|
+
currency
|
|
37
|
+
});
|
|
38
|
+
const products = response.contents ?? response.catalogEntryView ?? [];
|
|
39
|
+
const data = products[0];
|
|
40
|
+
if (!data) {
|
|
41
|
+
return error({
|
|
42
|
+
type: "NotFound",
|
|
43
|
+
identifier: payload.identifier
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const value = this.factory.parseProduct(
|
|
47
|
+
this.context,
|
|
48
|
+
await this.withResolvedParentCategories(data, langId)
|
|
49
|
+
);
|
|
50
|
+
return success(value);
|
|
51
|
+
}
|
|
52
|
+
async getBySlug(payload) {
|
|
53
|
+
const { langId, currency } = getLocaleParams(this.config, this.context);
|
|
54
|
+
const token = await this.client.resolveSlug(payload.slug, langId);
|
|
55
|
+
if (!token || token.tokenName !== "ProductToken" || !token.tokenExternalValue) {
|
|
56
|
+
return error({
|
|
57
|
+
type: "NotFound",
|
|
58
|
+
identifier: payload.slug
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const response = await this.client.findProducts({
|
|
62
|
+
partNumber: [token.tokenExternalValue],
|
|
63
|
+
profileName: this.config.profiles.product,
|
|
64
|
+
langId,
|
|
65
|
+
currency
|
|
66
|
+
});
|
|
67
|
+
const products = response.contents ?? response.catalogEntryView ?? [];
|
|
68
|
+
const data = products[0];
|
|
69
|
+
if (!data) {
|
|
70
|
+
return error({
|
|
71
|
+
type: "NotFound",
|
|
72
|
+
identifier: payload.slug
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const value = this.factory.parseProduct(
|
|
76
|
+
this.context,
|
|
77
|
+
await this.withResolvedParentCategories(data, langId)
|
|
78
|
+
);
|
|
79
|
+
return success(value);
|
|
80
|
+
}
|
|
81
|
+
async getBySKU(payload) {
|
|
82
|
+
const { langId, currency } = getLocaleParams(this.config, this.context);
|
|
83
|
+
const response = await this.client.findProducts({
|
|
84
|
+
partNumber: [payload.variant.sku],
|
|
85
|
+
profileName: this.config.profiles.product,
|
|
86
|
+
langId,
|
|
87
|
+
currency
|
|
88
|
+
});
|
|
89
|
+
const products = response.contents ?? response.catalogEntryView ?? [];
|
|
90
|
+
const data = products[0];
|
|
91
|
+
if (!data) {
|
|
92
|
+
return error({
|
|
93
|
+
type: "NotFound",
|
|
94
|
+
identifier: payload.variant
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const value = this.factory.parseProduct(
|
|
98
|
+
this.context,
|
|
99
|
+
await this.withResolvedParentCategories(data, langId)
|
|
100
|
+
);
|
|
101
|
+
return success(value);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Resolves `parentCatalogGroupID` path strings (e.g. "/10505/10507") to external
|
|
105
|
+
* category identifiers (e.g. "LivingRoomFurniture") so the factory receives
|
|
106
|
+
* human-readable keys rather than internal uniqueIDs.
|
|
107
|
+
*/
|
|
108
|
+
async withResolvedParentCategories(data, langId) {
|
|
109
|
+
const rawIds = Array.isArray(data.parentCatalogGroupID) ? data.parentCatalogGroupID : data.parentCatalogGroupID ? [data.parentCatalogGroupID] : [];
|
|
110
|
+
const uniqueIds = [
|
|
111
|
+
...new Set(
|
|
112
|
+
rawIds.map((p) => p.split("/").filter(Boolean).at(-1)).filter((id) => id !== void 0)
|
|
113
|
+
)
|
|
114
|
+
];
|
|
115
|
+
if (uniqueIds.length === 0)
|
|
116
|
+
return data;
|
|
117
|
+
const catResp = await this.client.findCategories({ id: uniqueIds, langId });
|
|
118
|
+
const idToIdentifier = new Map(
|
|
119
|
+
(catResp.contents ?? []).map((c) => [c.uniqueID, c.identifier])
|
|
120
|
+
);
|
|
121
|
+
const resolvedIds = uniqueIds.map((id) => idToIdentifier.get(id) ?? id);
|
|
122
|
+
return { ...data, parentCatalogGroupID: resolvedIds };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
__decorateClass([
|
|
126
|
+
Reactionary({
|
|
127
|
+
inputSchema: ProductQueryByIdSchema,
|
|
128
|
+
outputSchema: ProductSchema,
|
|
129
|
+
cache: true,
|
|
130
|
+
cacheTimeToLiveInSeconds: 300,
|
|
131
|
+
currencyDependentCaching: false,
|
|
132
|
+
localeDependentCaching: false
|
|
133
|
+
})
|
|
134
|
+
], HclProductCapability.prototype, "getById", 1);
|
|
135
|
+
__decorateClass([
|
|
136
|
+
Reactionary({
|
|
137
|
+
inputSchema: ProductQueryBySlugSchema,
|
|
138
|
+
outputSchema: ProductSchema,
|
|
139
|
+
cache: true,
|
|
140
|
+
cacheTimeToLiveInSeconds: 300,
|
|
141
|
+
currencyDependentCaching: false,
|
|
142
|
+
localeDependentCaching: false
|
|
143
|
+
})
|
|
144
|
+
], HclProductCapability.prototype, "getBySlug", 1);
|
|
145
|
+
__decorateClass([
|
|
146
|
+
Reactionary({
|
|
147
|
+
inputSchema: ProductQueryBySKUSchema,
|
|
148
|
+
outputSchema: ProductSchema,
|
|
149
|
+
cache: true,
|
|
150
|
+
cacheTimeToLiveInSeconds: 300,
|
|
151
|
+
currencyDependentCaching: false,
|
|
152
|
+
localeDependentCaching: false
|
|
153
|
+
})
|
|
154
|
+
], HclProductCapability.prototype, "getBySKU", 1);
|
|
155
|
+
export {
|
|
156
|
+
HclProductCapability
|
|
157
|
+
};
|
package/core/client.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
class HclClient {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.config = config;
|
|
4
|
+
const origin = config.apiUrl.replace(/\/+$/, "");
|
|
5
|
+
const apiPath = "/search/resources/api/v2";
|
|
6
|
+
this.baseUrl = `${origin}${apiPath}`;
|
|
7
|
+
}
|
|
8
|
+
baseUrl;
|
|
9
|
+
async findProducts(query) {
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
params.set("storeId", query.storeId ?? this.config.storeId);
|
|
12
|
+
const catalogId = query.catalogId ?? this.config.catalogId;
|
|
13
|
+
if (catalogId)
|
|
14
|
+
params.set("catalogId", catalogId);
|
|
15
|
+
if (query.langId)
|
|
16
|
+
params.set("langId", query.langId);
|
|
17
|
+
if (query.currency)
|
|
18
|
+
params.set("currency", query.currency);
|
|
19
|
+
if (query.categoryId)
|
|
20
|
+
params.set("categoryId", query.categoryId);
|
|
21
|
+
if (query.searchTerm)
|
|
22
|
+
params.set("searchTerm", query.searchTerm);
|
|
23
|
+
if (query.contractId)
|
|
24
|
+
params.set("contractId", query.contractId);
|
|
25
|
+
if (query.profileName)
|
|
26
|
+
params.set("profileName", query.profileName);
|
|
27
|
+
if (query.limit !== void 0)
|
|
28
|
+
params.set("limit", String(query.limit));
|
|
29
|
+
if (query.offset !== void 0)
|
|
30
|
+
params.set("offset", String(query.offset));
|
|
31
|
+
if (query.checkEntitlement !== void 0) {
|
|
32
|
+
params.set("checkEntitlement", String(query.checkEntitlement));
|
|
33
|
+
}
|
|
34
|
+
for (const id of query.id ?? []) {
|
|
35
|
+
params.append("id", id);
|
|
36
|
+
}
|
|
37
|
+
for (const partNumber of query.partNumber ?? []) {
|
|
38
|
+
params.append("partNumber", partNumber);
|
|
39
|
+
}
|
|
40
|
+
for (const facet of query.facets ?? []) {
|
|
41
|
+
params.append("facet", facet);
|
|
42
|
+
}
|
|
43
|
+
const url = `${this.baseUrl}/products?${params.toString()}`;
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers: { Accept: "application/json" }
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`HCL API error ${response.status} ${response.statusText} for URL: ${url}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return response.json();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a URL slug to an HCL token (product partNumber, category ID, etc.).
|
|
57
|
+
* Calls GET /api/v2/urls?storeId=X&identifier=<slug>
|
|
58
|
+
* Returns undefined when the slug is not found (404).
|
|
59
|
+
*/
|
|
60
|
+
async resolveSlug(slug, langId) {
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
params.set("storeId", this.config.storeId);
|
|
63
|
+
params.append("identifier", slug);
|
|
64
|
+
if (langId)
|
|
65
|
+
params.set("langId", langId);
|
|
66
|
+
const url = `${this.baseUrl}/urls?${params.toString()}`;
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
method: "GET",
|
|
69
|
+
headers: { Accept: "application/json" }
|
|
70
|
+
});
|
|
71
|
+
if (response.status === 404)
|
|
72
|
+
return void 0;
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`HCL URL resolve error ${response.status} ${response.statusText} for URL: ${url}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const data = await response.json();
|
|
79
|
+
return data.contents?.[0];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Query categories from the HCL Commerce Query Service.
|
|
83
|
+
* Calls GET /api/v2/categories with the given query parameters.
|
|
84
|
+
*/
|
|
85
|
+
async findCategories(query) {
|
|
86
|
+
const params = new URLSearchParams();
|
|
87
|
+
params.set("storeId", query.storeId ?? this.config.storeId);
|
|
88
|
+
const catalogId = query.catalogId ?? this.config.catalogId;
|
|
89
|
+
if (catalogId)
|
|
90
|
+
params.set("catalogId", catalogId);
|
|
91
|
+
if (query.langId)
|
|
92
|
+
params.set("langId", query.langId);
|
|
93
|
+
if (query.parentCategoryId)
|
|
94
|
+
params.set("parentCategoryId", query.parentCategoryId);
|
|
95
|
+
if (query.depthAndLimit)
|
|
96
|
+
params.set("depthAndLimit", query.depthAndLimit);
|
|
97
|
+
if (query.profileName)
|
|
98
|
+
params.set("profileName", query.profileName);
|
|
99
|
+
for (const id of query.id ?? []) {
|
|
100
|
+
params.append("id", id);
|
|
101
|
+
}
|
|
102
|
+
for (const identifier of query.identifier ?? []) {
|
|
103
|
+
params.append("identifier", identifier);
|
|
104
|
+
}
|
|
105
|
+
const url = `${this.baseUrl}/categories?${params.toString()}`;
|
|
106
|
+
const response = await fetch(url, {
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: { Accept: "application/json" }
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`HCL API error ${response.status} ${response.statusText} for URL: ${url}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return response.json();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
HclClient
|
|
120
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CategoryPaginatedResultSchema,
|
|
3
|
+
ProductSchema
|
|
4
|
+
} from "@reactionary/core";
|
|
5
|
+
import { HclCategorySchema } from "../schema/category.schema.js";
|
|
6
|
+
import {
|
|
7
|
+
HclCapabilitiesSchema
|
|
8
|
+
} from "../schema/capabilities.schema.js";
|
|
9
|
+
import {
|
|
10
|
+
HclConfigurationSchema
|
|
11
|
+
} from "../schema/configuration.schema.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveCapabilityWithFactory
|
|
14
|
+
} from "./initialize.types.js";
|
|
15
|
+
import { HclClient } from "./client.js";
|
|
16
|
+
import {
|
|
17
|
+
HclCategoryFactory,
|
|
18
|
+
HclProductFactory,
|
|
19
|
+
HclProductSearchFactory
|
|
20
|
+
} from "../factories/index.js";
|
|
21
|
+
import { HclProductCapability } from "../capabilities/product.capability.js";
|
|
22
|
+
import { HclCategoryCapability } from "../capabilities/category.capability.js";
|
|
23
|
+
import { HclProductSearchCapability } from "../capabilities/product-search.capability.js";
|
|
24
|
+
import { ProductSearchResultSchema } from "@reactionary/core";
|
|
25
|
+
function withHclCapabilities(configuration, capabilities) {
|
|
26
|
+
return (cache, context) => {
|
|
27
|
+
const client = {};
|
|
28
|
+
const config = HclConfigurationSchema.parse(configuration);
|
|
29
|
+
const caps = HclCapabilitiesSchema.parse(capabilities);
|
|
30
|
+
const hclClient = new HclClient(config);
|
|
31
|
+
const buildCapabilityArgs = (factory) => ({
|
|
32
|
+
cache,
|
|
33
|
+
context,
|
|
34
|
+
config,
|
|
35
|
+
hclClient,
|
|
36
|
+
factory
|
|
37
|
+
});
|
|
38
|
+
if (caps.product?.enabled) {
|
|
39
|
+
client.product = resolveCapabilityWithFactory(
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
capabilities.product,
|
|
42
|
+
{
|
|
43
|
+
factory: new HclProductFactory(ProductSchema),
|
|
44
|
+
capability: (args) => new HclProductCapability(
|
|
45
|
+
args.cache,
|
|
46
|
+
args.context,
|
|
47
|
+
args.config,
|
|
48
|
+
args.hclClient,
|
|
49
|
+
args.factory
|
|
50
|
+
)
|
|
51
|
+
},
|
|
52
|
+
buildCapabilityArgs
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (caps.category?.enabled) {
|
|
56
|
+
client.category = resolveCapabilityWithFactory(
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
capabilities.category,
|
|
59
|
+
{
|
|
60
|
+
factory: new HclCategoryFactory(
|
|
61
|
+
HclCategorySchema,
|
|
62
|
+
CategoryPaginatedResultSchema
|
|
63
|
+
),
|
|
64
|
+
capability: (args) => new HclCategoryCapability(
|
|
65
|
+
args.cache,
|
|
66
|
+
args.context,
|
|
67
|
+
args.config,
|
|
68
|
+
args.hclClient,
|
|
69
|
+
args.factory
|
|
70
|
+
)
|
|
71
|
+
},
|
|
72
|
+
buildCapabilityArgs
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (caps.productSearch?.enabled) {
|
|
76
|
+
client.productSearch = resolveCapabilityWithFactory(
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
capabilities.productSearch,
|
|
79
|
+
{
|
|
80
|
+
factory: new HclProductSearchFactory(ProductSearchResultSchema),
|
|
81
|
+
capability: (args) => new HclProductSearchCapability(
|
|
82
|
+
args.cache,
|
|
83
|
+
args.context,
|
|
84
|
+
args.config,
|
|
85
|
+
args.hclClient,
|
|
86
|
+
args.factory
|
|
87
|
+
)
|
|
88
|
+
},
|
|
89
|
+
buildCapabilityArgs
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return client;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
withHclCapabilities
|
|
97
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function resolveCapabilityWithFactory(capability, defaults, buildCapabilityArgs) {
|
|
2
|
+
const factory = capability?.factory ?? defaults.factory;
|
|
3
|
+
const capabilityFactory = capability?.capability ?? defaults.capability;
|
|
4
|
+
return capabilityFactory(buildCapabilityArgs(factory));
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
resolveCapabilityWithFactory
|
|
8
|
+
};
|