@reactionary/magento 0.10.25 → 0.10.26

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.
@@ -19,6 +19,8 @@ import {
19
19
  } from "@reactionary/core";
20
20
  import createDebug from "debug";
21
21
  const debug = createDebug("reactionary:magento:search");
22
+ const CATEGORY_FACET_KEY = "categories";
23
+ const CATEGORY_FIELD = "category_id";
22
24
  class MagentoProductSearchCapability extends ProductSearchCapability {
23
25
  constructor(config, cache, context, magentoApi, factory) {
24
26
  super(cache, context);
@@ -34,38 +36,26 @@ class MagentoProductSearchCapability extends ProductSearchCapability {
34
36
  const pageSize = payload.search.paginationOptions.pageSize;
35
37
  const currentPage = payload.search.paginationOptions.pageNumber;
36
38
  const params = new URLSearchParams();
37
- let filterGroupIndex = 0;
39
+ const filterGroups = [];
38
40
  if (finalSearch) {
39
- params.set(
40
- `searchCriteria[filterGroups][${filterGroupIndex}][filters][0][field]`,
41
- "name"
42
- );
43
- params.set(
44
- `searchCriteria[filterGroups][${filterGroupIndex}][filters][0][value]`,
45
- `%${finalSearch}%`
46
- );
47
- params.set(
48
- `searchCriteria[filterGroups][${filterGroupIndex}][filters][0][condition_type]`,
49
- "like"
50
- );
51
- filterGroupIndex++;
41
+ filterGroups.push([{ field: "name", value: `%${finalSearch}%`, conditionType: "like" }]);
52
42
  }
53
43
  if (payload.search.categoryFilter?.key) {
54
44
  debug(`Applying category filter: ${payload.search.categoryFilter.key}`);
55
- params.set(
56
- `searchCriteria[filterGroups][${filterGroupIndex}][filters][0][field]`,
57
- "category_id"
58
- );
59
- params.set(
60
- `searchCriteria[filterGroups][${filterGroupIndex}][filters][0][value]`,
61
- payload.search.categoryFilter.key
62
- );
63
- params.set(
64
- `searchCriteria[filterGroups][${filterGroupIndex}][filters][0][condition_type]`,
65
- "eq"
66
- );
67
- filterGroupIndex++;
45
+ filterGroups.push([
46
+ { field: CATEGORY_FIELD, value: payload.search.categoryFilter.key, conditionType: "eq" }
47
+ ]);
68
48
  }
49
+ filterGroups.push(...this.getFacetFilterGroups(payload.search.facets));
50
+ filterGroups.push(...this.getStorefrontScopeFilterGroups());
51
+ filterGroups.forEach((filters, groupIndex) => {
52
+ filters.forEach((filter, filterIndex) => {
53
+ const prefix = `searchCriteria[filterGroups][${groupIndex}][filters][${filterIndex}]`;
54
+ params.set(`${prefix}[field]`, filter.field);
55
+ params.set(`${prefix}[value]`, filter.value);
56
+ params.set(`${prefix}[condition_type]`, filter.conditionType);
57
+ });
58
+ });
69
59
  params.set("searchCriteria[pageSize]", String(pageSize));
70
60
  params.set("searchCriteria[currentPage]", String(currentPage));
71
61
  const client = await this.magentoApi.getClient();
@@ -78,9 +68,50 @@ class MagentoProductSearchCapability extends ProductSearchCapability {
78
68
  }
79
69
  return success(result);
80
70
  }
71
+ /**
72
+ * One filter group per facet code: Magento ORs the filters within a group and ANDs
73
+ * the groups, so values of one facet widen the result and distinct facets narrow it.
74
+ * The facet code is the Magento attribute code and the facet value key its (option) value.
75
+ *
76
+ * The category navigation facet created by {@link createCategoryNavigationFilter} is the
77
+ * exception: Magento's `category_id` filter (ProductCategoryFilter) ANDs the filters of a
78
+ * group, so its values are OR-ed through a single `category_id in (...)` filter instead.
79
+ */
80
+ getFacetFilterGroups(facets) {
81
+ const groups = /* @__PURE__ */ new Map();
82
+ const categoryIds = [];
83
+ for (const facetValue of facets) {
84
+ if (facetValue.facet.key === CATEGORY_FACET_KEY) {
85
+ categoryIds.push(facetValue.key);
86
+ continue;
87
+ }
88
+ const field = facetValue.facet.key;
89
+ const group = groups.get(field) ?? [];
90
+ group.push({ field, value: facetValue.key, conditionType: "eq" });
91
+ groups.set(field, group);
92
+ }
93
+ if (categoryIds.length > 0) {
94
+ groups.set(CATEGORY_FIELD, [
95
+ { field: CATEGORY_FIELD, value: categoryIds.join(","), conditionType: "in" }
96
+ ]);
97
+ }
98
+ return [...groups.values()];
99
+ }
100
+ /**
101
+ * Restricts results to what a storefront may show: enabled products that are visible in
102
+ * the catalog (visibility 2 = Catalog, 4 = Catalog, Search). The admin REST product search
103
+ * does not apply this itself, so without it disabled products and "Not Visible
104
+ * Individually" (1) configurable children would be listed.
105
+ */
106
+ getStorefrontScopeFilterGroups() {
107
+ return [
108
+ [{ field: "status", value: "1", conditionType: "eq" }],
109
+ [{ field: "visibility", value: "2,4", conditionType: "in" }]
110
+ ];
111
+ }
81
112
  async createCategoryNavigationFilter(payload) {
82
113
  const facetIdentifier = FacetIdentifierSchema.parse({
83
- key: "categories"
114
+ key: CATEGORY_FACET_KEY
84
115
  });
85
116
  const facetValueIdentifier = FacetValueIdentifierSchema.parse({
86
117
  facet: facetIdentifier,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  CategoryIdentifierSchema,
3
+ ProductComplianceDataSchema,
3
4
  ProductIdentifierSchema
4
5
  } from "@reactionary/core";
5
6
  import { getProductKey, parseDamImages } from "../../utils/magento-product.js";
@@ -27,6 +28,9 @@ class MagentoProductFactory {
27
28
  }
28
29
  config;
29
30
  productSchema;
31
+ parseComplianceData(context, data) {
32
+ return ProductComplianceDataSchema.parse({});
33
+ }
30
34
  parseProduct(_context, data) {
31
35
  const sku = data.sku;
32
36
  const identifier = ProductIdentifierSchema.parse({ key: getProductKey(data) });
@@ -50,7 +54,8 @@ class MagentoProductFactory {
50
54
  published: true,
51
55
  sharedAttributes,
52
56
  slug,
53
- variants: []
57
+ variants: [],
58
+ complianceData: this.parseComplianceData(_context, data)
54
59
  };
55
60
  return this.productSchema.parse(result);
56
61
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ImageSchema,
3
+ ProductSearchResultItemComplianceDataSchema,
3
4
  ProductSearchResultItemVariantSchema,
4
5
  ProductVariantIdentifierSchema
5
6
  } from "@reactionary/core";
@@ -48,6 +49,9 @@ class MagentoProductSearchFactory {
48
49
  };
49
50
  return this.productSearchResultSchema.parse(result);
50
51
  }
52
+ parseComplianceData(product) {
53
+ return ProductSearchResultItemComplianceDataSchema.parse({});
54
+ }
51
55
  parseProductSearchResultItem(_context, product) {
52
56
  const idKey = product.id !== void 0 ? String(product.id) : product.sku;
53
57
  const identifier = { key: idKey };
@@ -61,7 +65,8 @@ class MagentoProductSearchFactory {
61
65
  identifier,
62
66
  name,
63
67
  slug,
64
- variants
68
+ variants,
69
+ complianceData: this.parseComplianceData(product)
65
70
  };
66
71
  }
67
72
  parseVariant(variant, product) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reactionary/magento",
3
- "version": "0.10.25",
3
+ "version": "0.10.26",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "types": "./src/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/Solteq/reactionary.git"
10
10
  },
11
11
  "dependencies": {
12
- "@reactionary/core": "0.10.25",
12
+ "@reactionary/core": "0.10.26",
13
13
  "zod": "4.1.9",
14
14
  "debug": "^4.4.3"
15
15
  },
@@ -2,14 +2,37 @@ import { ProductSearchCapability, type Cache, type FacetIdentifier, type FacetVa
2
2
  import type { MagentoConfiguration } from '../schema/configuration.schema.js';
3
3
  import type { MagentoClient } from '../core/client.js';
4
4
  import type { MagentoProductSearchFactory } from '../factories/product-search/product-search.factory.js';
5
+ interface MagentoSearchFilter {
6
+ field: string;
7
+ value: string;
8
+ conditionType: 'eq' | 'in' | 'like';
9
+ }
5
10
  export declare class MagentoProductSearchCapability<TFactory extends ProductSearchFactory = MagentoProductSearchFactory> extends ProductSearchCapability<ProductSearchFactoryOutput<TFactory>> {
6
11
  magentoApi: MagentoClient;
7
12
  protected config: MagentoConfiguration;
8
13
  protected factory: ProductSearchFactoryWithOutput<TFactory>;
9
14
  constructor(config: MagentoConfiguration, cache: Cache, context: RequestContext, magentoApi: MagentoClient, factory: ProductSearchFactoryWithOutput<TFactory>);
10
15
  queryByTerm(payload: ProductSearchQueryByTerm): Promise<Result<ProductSearchFactoryOutput<TFactory>>>;
16
+ /**
17
+ * One filter group per facet code: Magento ORs the filters within a group and ANDs
18
+ * the groups, so values of one facet widen the result and distinct facets narrow it.
19
+ * The facet code is the Magento attribute code and the facet value key its (option) value.
20
+ *
21
+ * The category navigation facet created by {@link createCategoryNavigationFilter} is the
22
+ * exception: Magento's `category_id` filter (ProductCategoryFilter) ANDs the filters of a
23
+ * group, so its values are OR-ed through a single `category_id in (...)` filter instead.
24
+ */
25
+ protected getFacetFilterGroups(facets: FacetValueIdentifier[]): MagentoSearchFilter[][];
26
+ /**
27
+ * Restricts results to what a storefront may show: enabled products that are visible in
28
+ * the catalog (visibility 2 = Catalog, 4 = Catalog, Search). The admin REST product search
29
+ * does not apply this itself, so without it disabled products and "Not Visible
30
+ * Individually" (1) configurable children would be listed.
31
+ */
32
+ protected getStorefrontScopeFilterGroups(): MagentoSearchFilter[][];
11
33
  createCategoryNavigationFilter(payload: ProductSearchQueryCreateNavigationFilter): Promise<Result<FacetValueIdentifier>>;
12
34
  protected parseFacetValue(_facetValueIdentifier: FacetValueIdentifier, _label: string, _count: number): ProductSearchResultFacetValue;
13
35
  protected parseFacet(_facetIdentifier: FacetIdentifier, _facetValue: unknown): ProductSearchResultFacet;
14
36
  protected parseVariant(): never;
15
37
  }
38
+ export {};
@@ -1,4 +1,4 @@
1
- import { type AnyProductSchema, type Image, type ProductAttribute, type ProductFactory, type ProductSchema, type ProductVariant, type RequestContext } from '@reactionary/core';
1
+ import { type AnyProductSchema, type Image, type ProductAttribute, type ProductComplianceData, type ProductFactory, type ProductSchema, type ProductVariant, type RequestContext } from '@reactionary/core';
2
2
  import type * as z from 'zod';
3
3
  import type { MagentoConfiguration } from '../../schema/configuration.schema.js';
4
4
  import type { MagentoProduct } from '../../schema/magento.types.js';
@@ -6,6 +6,7 @@ export declare class MagentoProductFactory<TProductSchema extends AnyProductSche
6
6
  protected config: MagentoConfiguration;
7
7
  readonly productSchema: TProductSchema;
8
8
  constructor(productSchema: TProductSchema, config: MagentoConfiguration);
9
+ protected parseComplianceData(context: RequestContext, data: MagentoProduct): ProductComplianceData;
9
10
  parseProduct(_context: RequestContext, data: MagentoProduct): z.output<TProductSchema>;
10
11
  protected parseVariant(product: MagentoProduct): ProductVariant;
11
12
  protected parseImages(product: MagentoProduct): Image[];
@@ -1,4 +1,4 @@
1
- import { type AnyProductSearchResultSchema, type ProductSearchFactory, type ProductSearchQueryByTerm, type ProductSearchResultItem, type ProductSearchResultItemVariant, type ProductSearchResultSchema, type RequestContext } from '@reactionary/core';
1
+ import { type AnyProductSearchResultSchema, type ProductSearchFactory, type ProductSearchQueryByTerm, type ProductSearchResultItem, type ProductSearchResultItemComplianceData, type ProductSearchResultItemVariant, type ProductSearchResultSchema, type RequestContext } from '@reactionary/core';
2
2
  import type * as z from 'zod';
3
3
  import type { MagentoConfiguration } from '../../schema/configuration.schema.js';
4
4
  import type { MagentoProduct, MagentoProductSearchResult } from '../../schema/magento.types.js';
@@ -7,6 +7,7 @@ export declare class MagentoProductSearchFactory<TProductSearchResultSchema exte
7
7
  readonly productSearchResultSchema: TProductSearchResultSchema;
8
8
  constructor(productSearchResultSchema: TProductSearchResultSchema, config: MagentoConfiguration);
9
9
  parseSearchResult(context: RequestContext, data: MagentoProductSearchResult, query: ProductSearchQueryByTerm): z.output<TProductSearchResultSchema>;
10
+ protected parseComplianceData(product: MagentoProduct): ProductSearchResultItemComplianceData;
10
11
  protected parseProductSearchResultItem(_context: RequestContext, product: MagentoProduct): ProductSearchResultItem;
11
12
  protected parseVariant(variant: MagentoProduct, product: MagentoProduct): ProductSearchResultItemVariant;
12
13
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ImageSchema,
3
+ ProductSearchResultItemComplianceDataSchema,
3
4
  ProductSearchResultItemVariantSchema,
4
5
  ProductVariantIdentifierSchema
5
6
  } from "@reactionary/core";
@@ -96,9 +97,13 @@ function buildProductSearchResultItem(config, product) {
96
97
  identifier: { key: getProductKey(product) },
97
98
  name: product.name || product.sku,
98
99
  slug: getCustomAttribute(product, "url_key") ?? getCustomAttribute(product, "url_path") ?? "",
99
- variants
100
+ variants,
101
+ complianceData: buildProductComplianceData(product)
100
102
  };
101
103
  }
104
+ function buildProductComplianceData(product) {
105
+ return ProductSearchResultItemComplianceDataSchema.parse({});
106
+ }
102
107
  export {
103
108
  buildMagentoImageUrl,
104
109
  buildProductSearchResultItem,