@reactionary/provider-algolia 0.0.59 → 0.0.61

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 CHANGED
@@ -1,6 +1,43 @@
1
- # provider-algolia
1
+ # Algolia provider for Reactionary
2
+
3
+ ## Supports
4
+
5
+
6
+ | Feature | Support | Notes |
7
+ | ----------- | ----------- | --------- |
8
+ | product | Full | |
9
+ | productSearch | Full | |
10
+ | identity | N/A | |
11
+ | cart | N/A | |
12
+ | checkout | N/A | |
13
+ | order | N/A | Possibly later |
14
+ | inventory | N/A | |
15
+ | price | N/A | |
16
+ | category | N/A | Possibly later |
17
+ | store | N/A | Possibly later |
18
+
19
+
20
+ ## Notes
21
+ The expected Algolia schema must contain at least these fields
22
+
23
+ ```json
24
+ {
25
+ objectID: string;
26
+ slug:string;
27
+ name: string;
28
+ variants: [
29
+ {
30
+ variantID: string;
31
+ image: string;
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ You can have more, for use with facets, and additional searchable fields, but these must be in the index, and constitutes what we are expecting to get back.
38
+
39
+ The `objectID` corrosponds to your productIdentifier, and `variantID` should match your SKU
2
40
 
3
- This library was generated with [Nx](https://nx.dev).
4
41
 
5
42
  ## Building
6
43
 
@@ -1,6 +1,6 @@
1
- import { ProductSchema } from "@reactionary/core";
1
+ import { ProductSchema, ProductSearchResultItemSchema } from "@reactionary/core";
2
2
  import { AlgoliaProductProvider } from "../providers/product.provider.js";
3
- import { AlgoliaSearchProvider } from "../providers/search.provider.js";
3
+ import { AlgoliaSearchProvider } from "../providers/product-search.provider.js";
4
4
  import { AlgoliaSearchResultSchema } from "../schema/search.schema.js";
5
5
  function withAlgoliaCapabilities(configuration, capabilities) {
6
6
  return (cache) => {
@@ -8,8 +8,8 @@ function withAlgoliaCapabilities(configuration, capabilities) {
8
8
  if (capabilities.product) {
9
9
  client.product = new AlgoliaProductProvider(configuration, ProductSchema, cache);
10
10
  }
11
- if (capabilities.search) {
12
- client.search = new AlgoliaSearchProvider(configuration, AlgoliaSearchResultSchema, cache);
11
+ if (capabilities.productSearch) {
12
+ client.productSearch = new AlgoliaSearchProvider(configuration, ProductSearchResultItemSchema, cache);
13
13
  }
14
14
  return client;
15
15
  };
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./core/initialize.js";
2
2
  export * from "./providers/product.provider.js";
3
- export * from "./providers/search.provider.js";
3
+ export * from "./providers/product-search.provider.js";
4
4
  export * from "./schema/configuration.schema.js";
5
5
  export * from "./schema/capabilities.schema.js";
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@reactionary/provider-algolia",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
4
4
  "main": "index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "dependencies": {
7
- "@reactionary/core": "0.0.59",
7
+ "@reactionary/core": "0.0.61",
8
8
  "algoliasearch": "^5.23.4",
9
9
  "zod": "4.1.9"
10
10
  },
@@ -0,0 +1,2 @@
1
+ export * from "./product-search.provider.js";
2
+ export * from "./product.provider.js";
@@ -0,0 +1,132 @@
1
+ import {
2
+ createPaginatedResponseSchema,
3
+ FacetIdentifierSchema,
4
+ FacetValueIdentifierSchema,
5
+ ImageSchema,
6
+ ProductSearchProvider,
7
+ ProductSearchResultFacetSchema,
8
+ ProductSearchResultFacetValueSchema,
9
+ ProductSearchResultItemVariantSchema
10
+ } from "@reactionary/core";
11
+ import { algoliasearch } from "algoliasearch";
12
+ import { AlgoliaSearchIdentifierSchema } from "../schema/search.schema.js";
13
+ class AlgoliaSearchProvider extends ProductSearchProvider {
14
+ constructor(config, schema, cache) {
15
+ super(schema, cache);
16
+ this.config = config;
17
+ }
18
+ async queryByTerm(payload, reqCtx) {
19
+ const client = algoliasearch(this.config.appId, this.config.apiKey);
20
+ const remote = await client.search({
21
+ requests: [
22
+ {
23
+ indexName: this.config.indexName,
24
+ query: payload.search.term,
25
+ page: payload.search.paginationOptions.pageNumber - 1,
26
+ hitsPerPage: payload.search.paginationOptions.pageSize,
27
+ facets: ["*"],
28
+ analytics: true,
29
+ clickAnalytics: true,
30
+ facetFilters: payload.search.facets.map(
31
+ (x) => `${encodeURIComponent(x.facet.key)}:${x.key}`
32
+ ),
33
+ filters: (payload.search.filters || []).map((x) => `${encodeURIComponent(x)}`).join(" AND ")
34
+ }
35
+ ]
36
+ });
37
+ const input = remote.results[0];
38
+ const identifier = AlgoliaSearchIdentifierSchema.parse({
39
+ ...payload.search,
40
+ index: input.index,
41
+ key: input.queryID
42
+ });
43
+ const result = this.parsePaginatedResult(input, reqCtx);
44
+ result.identifier = identifier;
45
+ for (const selectedFacet of payload.search.facets) {
46
+ const facet = result.facets.find((f) => f.identifier.key === selectedFacet.facet.key);
47
+ if (facet) {
48
+ const value = facet.values.find((v) => v.identifier.key === selectedFacet.key);
49
+ if (value) {
50
+ value.active = true;
51
+ }
52
+ }
53
+ }
54
+ result.meta = {
55
+ cache: { hit: false, key: "" },
56
+ placeholder: false
57
+ };
58
+ return result;
59
+ }
60
+ parseSingle(body, reqCtx) {
61
+ const product = this.newModel();
62
+ product.identifier = { key: body.objectID };
63
+ product.name = body.name || body.objectID;
64
+ product.slug = body.slug || body.objectID;
65
+ product.variants = [...body.variants || []].map((variant) => this.parseVariant(variant, body, reqCtx));
66
+ return this.assert(product);
67
+ }
68
+ parseVariant(variant, product, reqCtx) {
69
+ const result = ProductSearchResultItemVariantSchema.parse({
70
+ variant: {
71
+ sku: variant.variantID
72
+ },
73
+ image: ImageSchema.parse({
74
+ sourceUrl: variant.image,
75
+ altText: product.name || ""
76
+ })
77
+ });
78
+ return result;
79
+ }
80
+ parsePaginatedResult(body, reqCtx) {
81
+ const items = body.hits.map((hit) => this.parseSingle(hit, reqCtx));
82
+ const facets = [];
83
+ for (const id in body.facets) {
84
+ const f = body.facets[id];
85
+ const facetId = FacetIdentifierSchema.parse({
86
+ key: id
87
+ });
88
+ const facet = this.parseFacet(facetId, f, reqCtx);
89
+ facets.push(facet);
90
+ }
91
+ const result = createPaginatedResponseSchema(this.schema).parse({
92
+ meta: {
93
+ cache: { hit: false, key: "unknown" },
94
+ placeholder: false
95
+ },
96
+ pageNumber: (body.page || 0) + 1,
97
+ pageSize: body.hitsPerPage,
98
+ totalCount: body.nbHits,
99
+ totalPages: body.nbPages,
100
+ items
101
+ });
102
+ result.facets = facets;
103
+ return result;
104
+ }
105
+ parseFacet(facetIdentifier, facetValues, reqCtx) {
106
+ const result = ProductSearchResultFacetSchema.parse({
107
+ identifier: facetIdentifier,
108
+ name: facetIdentifier.key,
109
+ values: []
110
+ });
111
+ for (const vid in facetValues) {
112
+ const fv = facetValues[vid];
113
+ const facetValueIdentifier = FacetValueIdentifierSchema.parse({
114
+ facet: facetIdentifier,
115
+ key: vid
116
+ });
117
+ result.values.push(this.parseFacetValue(facetValueIdentifier, vid, fv, reqCtx));
118
+ }
119
+ return result;
120
+ }
121
+ parseFacetValue(facetValueIdentifier, label, count, reqCtx) {
122
+ return ProductSearchResultFacetValueSchema.parse({
123
+ identifier: facetValueIdentifier,
124
+ name: label,
125
+ count,
126
+ active: false
127
+ });
128
+ }
129
+ }
130
+ export {
131
+ AlgoliaSearchProvider
132
+ };
@@ -1,7 +1,7 @@
1
1
  import { CapabilitiesSchema } from "@reactionary/core";
2
2
  const AlgoliaCapabilitiesSchema = CapabilitiesSchema.pick({
3
3
  product: true,
4
- search: true,
4
+ productSearch: true,
5
5
  analytics: true
6
6
  }).partial();
7
7
  export {
@@ -0,0 +1,3 @@
1
+ export * from "./capabilities.schema.js";
2
+ export * from "./configuration.schema.js";
3
+ export * from "./search.schema.js";
@@ -1,10 +1,10 @@
1
- import { SearchIdentifierSchema, SearchResultSchema } from "@reactionary/core";
1
+ import { ProductSearchIdentifierSchema, ProductSearchResultSchema } from "@reactionary/core";
2
2
  import { z } from "zod";
3
- const AlgoliaSearchIdentifierSchema = SearchIdentifierSchema.extend({
3
+ const AlgoliaSearchIdentifierSchema = ProductSearchIdentifierSchema.extend({
4
4
  key: z.string().default(""),
5
5
  index: z.string().default("")
6
6
  });
7
- const AlgoliaSearchResultSchema = SearchResultSchema.extend({
7
+ const AlgoliaSearchResultSchema = ProductSearchResultSchema.extend({
8
8
  identifier: AlgoliaSearchIdentifierSchema.default(() => AlgoliaSearchIdentifierSchema.parse({}))
9
9
  });
10
10
  export {
package/src/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './core/initialize.js';
2
2
  export * from './providers/product.provider.js';
3
- export * from './providers/search.provider.js';
3
+ export * from './providers/product-search.provider.js';
4
4
  export * from './schema/configuration.schema.js';
5
5
  export * from './schema/capabilities.schema.js';
@@ -0,0 +1,2 @@
1
+ export * from './product-search.provider.js';
2
+ export * from './product.provider.js';
@@ -0,0 +1,40 @@
1
+ import { type Cache, type FacetIdentifier, type FacetValueIdentifier, ProductSearchProvider, type ProductSearchQueryByTerm, type ProductSearchResult, type ProductSearchResultFacet, type ProductSearchResultFacetValue, type ProductSearchResultItem, type ProductSearchResultItemVariant, type RequestContext } from '@reactionary/core';
2
+ import { type SearchResponse } from 'algoliasearch';
3
+ import type { z } from 'zod';
4
+ import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
5
+ interface AlgoliaNativeVariant {
6
+ variantID: string;
7
+ image: string;
8
+ }
9
+ interface AlgoliaNativeRecord {
10
+ objectID: string;
11
+ slug?: string;
12
+ name?: string;
13
+ variants: Array<AlgoliaNativeVariant>;
14
+ }
15
+ export declare class AlgoliaSearchProvider<T extends ProductSearchResultItem = ProductSearchResultItem> extends ProductSearchProvider<T> {
16
+ protected config: AlgoliaConfiguration;
17
+ constructor(config: AlgoliaConfiguration, schema: z.ZodType<T>, cache: Cache);
18
+ queryByTerm(payload: ProductSearchQueryByTerm, reqCtx: RequestContext): Promise<ProductSearchResult>;
19
+ protected parseSingle(body: AlgoliaNativeRecord, reqCtx: RequestContext): T;
20
+ protected parseVariant(variant: AlgoliaNativeVariant, product: AlgoliaNativeRecord, reqCtx: RequestContext): ProductSearchResultItemVariant;
21
+ protected parsePaginatedResult(body: SearchResponse<AlgoliaNativeRecord>, reqCtx: RequestContext): {
22
+ meta: {
23
+ [x: string]: unknown;
24
+ cache: {
25
+ [x: string]: unknown;
26
+ hit: boolean;
27
+ key: string;
28
+ };
29
+ placeholder: boolean;
30
+ };
31
+ pageNumber: number;
32
+ pageSize: number;
33
+ totalCount: number;
34
+ totalPages: number;
35
+ items: T[];
36
+ };
37
+ protected parseFacet(facetIdentifier: FacetIdentifier, facetValues: Record<string, number>, reqCtx: RequestContext): ProductSearchResultFacet;
38
+ protected parseFacetValue(facetValueIdentifier: FacetValueIdentifier, label: string, count: number, reqCtx: RequestContext): ProductSearchResultFacetValue;
39
+ }
40
+ export {};
@@ -2,6 +2,6 @@ import type { z } from 'zod';
2
2
  export declare const AlgoliaCapabilitiesSchema: z.ZodObject<{
3
3
  product: z.ZodOptional<z.ZodBoolean>;
4
4
  analytics: z.ZodOptional<z.ZodBoolean>;
5
- search: z.ZodOptional<z.ZodBoolean>;
5
+ productSearch: z.ZodOptional<z.ZodBoolean>;
6
6
  }, z.core.$loose>;
7
7
  export type AlgoliaCapabilities = z.infer<typeof AlgoliaCapabilitiesSchema>;
@@ -0,0 +1,3 @@
1
+ export * from './capabilities.schema.js';
2
+ export * from './configuration.schema.js';
3
+ export * from './search.schema.js';
@@ -1,14 +1,17 @@
1
1
  import { z } from 'zod';
2
2
  export declare const AlgoliaSearchIdentifierSchema: z.ZodObject<{
3
3
  term: z.ZodDefault<z.ZodString>;
4
- page: z.ZodDefault<z.ZodNumber>;
5
- pageSize: z.ZodDefault<z.ZodNumber>;
6
4
  facets: z.ZodDefault<z.ZodArray<z.ZodObject<{
7
5
  facet: z.ZodNonOptional<z.ZodDefault<z.ZodObject<{
8
6
  key: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
9
7
  }, z.core.$loose>>>;
10
8
  key: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
11
- }, z.core.$loose>>>;
9
+ }, z.core.$strip>>>;
10
+ filters: z.ZodDefault<z.ZodArray<z.ZodString>>;
11
+ paginationOptions: z.ZodDefault<z.ZodObject<{
12
+ pageNumber: z.ZodDefault<z.ZodNumber>;
13
+ pageSize: z.ZodDefault<z.ZodNumber>;
14
+ }, z.core.$loose>>;
12
15
  key: z.ZodDefault<z.ZodString>;
13
16
  index: z.ZodDefault<z.ZodString>;
14
17
  }, z.core.$loose>;
@@ -20,15 +23,50 @@ export declare const AlgoliaSearchResultSchema: z.ZodObject<{
20
23
  }, z.core.$loose>>;
21
24
  placeholder: z.ZodDefault<z.ZodBoolean>;
22
25
  }, z.core.$loose>>;
23
- products: z.ZodDefault<z.ZodArray<z.ZodObject<{
26
+ pageNumber: z.ZodNumber;
27
+ pageSize: z.ZodNumber;
28
+ totalCount: z.ZodNumber;
29
+ totalPages: z.ZodNumber;
30
+ items: z.ZodArray<z.ZodObject<{
31
+ meta: z.ZodDefault<z.ZodObject<{
32
+ cache: z.ZodDefault<z.ZodObject<{
33
+ hit: z.ZodDefault<z.ZodBoolean>;
34
+ key: z.ZodDefault<z.ZodString>;
35
+ }, z.core.$loose>>;
36
+ placeholder: z.ZodDefault<z.ZodBoolean>;
37
+ }, z.core.$loose>>;
24
38
  identifier: z.ZodDefault<z.ZodObject<{
25
39
  key: z.ZodDefault<z.ZodString>;
26
40
  }, z.core.$loose>>;
27
41
  name: z.ZodDefault<z.ZodString>;
28
- image: z.ZodDefault<z.ZodString>;
29
42
  slug: z.ZodDefault<z.ZodString>;
30
- }, z.core.$loose>>>;
31
- pages: z.ZodDefault<z.ZodNumber>;
43
+ variants: z.ZodDefault<z.ZodArray<z.ZodObject<{
44
+ variant: z.ZodDefault<z.ZodObject<{
45
+ sku: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
46
+ }, z.core.$loose>>;
47
+ image: z.ZodDefault<z.ZodObject<{
48
+ sourceUrl: z.ZodDefault<z.ZodString>;
49
+ altText: z.ZodDefault<z.ZodString>;
50
+ width: z.ZodOptional<z.ZodNumber>;
51
+ height: z.ZodOptional<z.ZodNumber>;
52
+ }, z.core.$loose>>;
53
+ options: z.ZodOptional<z.ZodObject<{
54
+ identifier: z.ZodDefault<z.ZodObject<{
55
+ key: z.ZodDefault<z.ZodString>;
56
+ }, z.core.$loose>>;
57
+ name: z.ZodString;
58
+ value: z.ZodDefault<z.ZodObject<{
59
+ identifier: z.ZodDefault<z.ZodObject<{
60
+ option: z.ZodDefault<z.ZodObject<{
61
+ key: z.ZodDefault<z.ZodString>;
62
+ }, z.core.$loose>>;
63
+ key: z.ZodDefault<z.ZodString>;
64
+ }, z.core.$loose>>;
65
+ label: z.ZodString;
66
+ }, z.core.$loose>>;
67
+ }, z.core.$loose>>;
68
+ }, z.core.$loose>>>;
69
+ }, z.core.$loose>>;
32
70
  facets: z.ZodDefault<z.ZodArray<z.ZodObject<{
33
71
  identifier: z.ZodDefault<z.ZodObject<{
34
72
  key: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
@@ -40,7 +78,7 @@ export declare const AlgoliaSearchResultSchema: z.ZodObject<{
40
78
  key: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
41
79
  }, z.core.$loose>>;
42
80
  key: z.ZodDefault<z.ZodString>;
43
- }, z.core.$loose>>;
81
+ }, z.core.$strip>>;
44
82
  name: z.ZodDefault<z.ZodString>;
45
83
  count: z.ZodDefault<z.ZodNumber>;
46
84
  active: z.ZodDefault<z.ZodBoolean>;
@@ -48,17 +86,20 @@ export declare const AlgoliaSearchResultSchema: z.ZodObject<{
48
86
  }, z.core.$loose>>>;
49
87
  identifier: z.ZodDefault<z.ZodObject<{
50
88
  term: z.ZodDefault<z.ZodString>;
51
- page: z.ZodDefault<z.ZodNumber>;
52
- pageSize: z.ZodDefault<z.ZodNumber>;
53
89
  facets: z.ZodDefault<z.ZodArray<z.ZodObject<{
54
90
  facet: z.ZodNonOptional<z.ZodDefault<z.ZodObject<{
55
91
  key: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
56
92
  }, z.core.$loose>>>;
57
93
  key: z.ZodNonOptional<z.ZodDefault<z.ZodString>>;
58
- }, z.core.$loose>>>;
94
+ }, z.core.$strip>>>;
95
+ filters: z.ZodDefault<z.ZodArray<z.ZodString>>;
96
+ paginationOptions: z.ZodDefault<z.ZodObject<{
97
+ pageNumber: z.ZodDefault<z.ZodNumber>;
98
+ pageSize: z.ZodDefault<z.ZodNumber>;
99
+ }, z.core.$loose>>;
59
100
  key: z.ZodDefault<z.ZodString>;
60
101
  index: z.ZodDefault<z.ZodString>;
61
102
  }, z.core.$loose>>;
62
- }, z.core.$loose>;
103
+ }, z.core.$strip>;
63
104
  export type AlgoliaSearchResult = z.infer<typeof AlgoliaSearchResultSchema>;
64
105
  export type AlgoliaSearchIdentifier = z.infer<typeof AlgoliaSearchIdentifierSchema>;
@@ -1,78 +0,0 @@
1
- import {
2
- SearchProvider
3
- } from "@reactionary/core";
4
- import { algoliasearch } from "algoliasearch";
5
- class AlgoliaSearchProvider extends SearchProvider {
6
- constructor(config, schema, cache) {
7
- super(schema, cache);
8
- this.config = config;
9
- }
10
- async queryByTerm(payload, _reqCtx) {
11
- const client = algoliasearch(this.config.appId, this.config.apiKey);
12
- const remote = await client.search({
13
- requests: [
14
- {
15
- indexName: this.config.indexName,
16
- query: payload.search.term,
17
- page: payload.search.page,
18
- hitsPerPage: payload.search.pageSize,
19
- facets: ["*"],
20
- analytics: true,
21
- clickAnalytics: true,
22
- facetFilters: payload.search.facets.map(
23
- (x) => `${encodeURIComponent(x.facet.key)}:${x.key}`
24
- )
25
- }
26
- ]
27
- });
28
- return this.parseSearchResult(remote, payload);
29
- }
30
- parseSearchResult(remote, payload) {
31
- const result = this.newModel();
32
- const remoteData = remote;
33
- const remoteProducts = remoteData.results[0];
34
- for (const id in remoteProducts.facets) {
35
- const f = remoteProducts.facets[id];
36
- const facet = {
37
- identifier: { key: id },
38
- name: id,
39
- values: []
40
- };
41
- for (const vid in f) {
42
- const fv = f[vid];
43
- const isActive = payload.search.facets.find(
44
- (x) => x.facet.key === id && x.key === vid
45
- );
46
- facet.values.push({
47
- identifier: { key: vid, facet: { key: id } },
48
- count: fv,
49
- name: vid,
50
- active: !!isActive
51
- });
52
- }
53
- result.facets.push(facet);
54
- }
55
- for (const p of remoteProducts.hits) {
56
- result.products.push({
57
- identifier: { key: p.objectID },
58
- slug: p.slug,
59
- name: p.name,
60
- image: p.image
61
- });
62
- }
63
- result.identifier = {
64
- ...payload.search,
65
- index: remoteProducts.index,
66
- key: remoteProducts.queryID
67
- };
68
- result.pages = remoteProducts.nbPages;
69
- result.meta = {
70
- cache: { hit: false, key: payload.search.term },
71
- placeholder: false
72
- };
73
- return this.assert(result);
74
- }
75
- }
76
- export {
77
- AlgoliaSearchProvider
78
- };
@@ -1,9 +0,0 @@
1
- import { type SearchQueryByTerm, type SearchResult, type RequestContext, type Cache, SearchProvider } from '@reactionary/core';
2
- import type { z } from 'zod';
3
- import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
4
- export declare class AlgoliaSearchProvider<T extends SearchResult = SearchResult> extends SearchProvider<T> {
5
- protected config: AlgoliaConfiguration;
6
- constructor(config: AlgoliaConfiguration, schema: z.ZodType<T>, cache: Cache);
7
- queryByTerm(payload: SearchQueryByTerm, _reqCtx: RequestContext): Promise<SearchResult>;
8
- protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm): T;
9
- }