@reactionary/source 0.3.0 → 0.3.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.
Files changed (103) hide show
  1. package/core/src/client/client-builder.ts +3 -7
  2. package/core/src/client/client.ts +2 -3
  3. package/core/src/decorators/reactionary.decorator.ts +2 -2
  4. package/core/src/initialization.ts +11 -3
  5. package/core/src/providers/analytics.provider.ts +75 -0
  6. package/core/src/providers/cart.provider.ts +3 -0
  7. package/core/src/providers/category.provider.ts +1 -0
  8. package/core/src/providers/identity.provider.ts +5 -0
  9. package/core/src/schemas/errors/invalid-input.error.ts +1 -1
  10. package/core/src/schemas/errors/invalid-output.error.ts +1 -1
  11. package/core/src/schemas/models/identifiers.model.ts +3 -0
  12. package/core/src/schemas/models/order.model.ts +2 -2
  13. package/core/src/schemas/mutations/analytics/index.ts +23 -0
  14. package/core/src/schemas/mutations/analytics/product-add-to-cart.mutation.ts +25 -0
  15. package/core/src/schemas/mutations/analytics/product-details-view.mutation.ts +14 -0
  16. package/core/src/schemas/mutations/analytics/product-summary-click.mutation.ts +26 -0
  17. package/core/src/schemas/mutations/analytics/product-summary-view.mutation.ts +25 -0
  18. package/core/src/schemas/mutations/analytics/purchase.mutation.ts +14 -0
  19. package/core/src/schemas/mutations/index.ts +1 -1
  20. package/core/src/schemas/queries/order-search.query.ts +3 -0
  21. package/core/src/schemas/session.schema.ts +21 -9
  22. package/core/src/test/client-builder.spec.ts +60 -0
  23. package/core/src/zod-utils.ts +3 -1
  24. package/documentation/{1-purpose.md → docs/1-purpose.md} +4 -0
  25. package/documentation/docs/8-tracking.md +9 -0
  26. package/documentation/docs/providers/analytics.provider.md +297 -0
  27. package/documentation/docs/providers/base.provider.md +118 -0
  28. package/documentation/docs/providers/cart.provider.md +305 -0
  29. package/documentation/docs/providers/category.provider.md +244 -0
  30. package/documentation/docs/providers/checkout.provider.md +315 -0
  31. package/documentation/docs/providers/identity.provider.md +194 -0
  32. package/documentation/docs/providers/inventory.provider.md +162 -0
  33. package/documentation/docs/providers/order-search.provider.md +155 -0
  34. package/documentation/docs/providers/order.provider.md +160 -0
  35. package/documentation/docs/providers/price.provider.md +197 -0
  36. package/documentation/docs/providers/product-search.provider.md +265 -0
  37. package/documentation/docs/providers/product.provider.md +204 -0
  38. package/documentation/docs/providers/profile.provider.md +283 -0
  39. package/documentation/docs/providers/store.provider.md +146 -0
  40. package/documentation/docs/schemas/schemas.md +1862 -0
  41. package/documentation/docusaurus.config.js +33 -0
  42. package/documentation/scripts/generate.ts +52 -0
  43. package/documentation/sidebars.js +8 -0
  44. package/documentation/src/css/custom.css +3 -0
  45. package/documentation/src/pages/index.js +12 -0
  46. package/eslint.config.mjs +1 -1
  47. package/examples/node/package.json +6 -6
  48. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -2
  49. package/examples/node/src/basic/client-creation.spec.ts +2 -2
  50. package/package.json +19 -5
  51. package/providers/algolia/README.md +12 -4
  52. package/providers/algolia/project.json +1 -1
  53. package/providers/algolia/src/core/initialize.ts +7 -2
  54. package/providers/algolia/src/providers/analytics.provider.ts +114 -0
  55. package/providers/algolia/src/providers/index.ts +1 -0
  56. package/providers/algolia/src/providers/product-search.provider.ts +5 -4
  57. package/providers/algolia/src/test/analytics.spec.ts +138 -0
  58. package/providers/commercetools/project.json +1 -1
  59. package/providers/commercetools/src/providers/identity.provider.ts +8 -1
  60. package/providers/commercetools/src/providers/profile.provider.ts +1 -4
  61. package/providers/commercetools/src/test/caching.spec.ts +3 -3
  62. package/providers/commercetools/src/test/identity.spec.ts +2 -2
  63. package/providers/fake/project.json +1 -1
  64. package/providers/fake/src/providers/analytics.provider.ts +5 -0
  65. package/providers/fake/src/providers/checkout.provider.ts +5 -2
  66. package/providers/fake/src/providers/product.provider.ts +18 -8
  67. package/providers/fake/src/test/cart.provider.spec.ts +0 -2
  68. package/providers/fake/src/test/category.provider.spec.ts +3 -3
  69. package/providers/fake/src/test/checkout.provider.spec.ts +3 -7
  70. package/providers/google-analytics/README.md +11 -0
  71. package/providers/google-analytics/eslint.config.mjs +25 -0
  72. package/providers/google-analytics/package.json +12 -0
  73. package/providers/google-analytics/project.json +33 -0
  74. package/providers/google-analytics/src/core/initialize.ts +16 -0
  75. package/providers/google-analytics/src/index.ts +4 -0
  76. package/providers/google-analytics/src/providers/analytics.provider.ts +162 -0
  77. package/providers/google-analytics/src/schema/capabilities.schema.ts +10 -0
  78. package/providers/google-analytics/src/schema/configuration.schema.ts +9 -0
  79. package/providers/google-analytics/src/test/analytics.provider.spec.ts +93 -0
  80. package/providers/google-analytics/tsconfig.json +24 -0
  81. package/providers/google-analytics/tsconfig.lib.json +23 -0
  82. package/providers/google-analytics/tsconfig.spec.json +28 -0
  83. package/providers/google-analytics/vite.config.ts +26 -0
  84. package/providers/google-analytics/vitest.config.mts +21 -0
  85. package/providers/medusa/package.json +3 -10
  86. package/providers/medusa/project.json +1 -1
  87. package/providers/medusa/src/providers/identity.provider.ts +34 -10
  88. package/providers/medusa/src/providers/profile.provider.ts +5 -15
  89. package/providers/medusa/src/test/test-utils.ts +0 -1
  90. package/providers/medusa/tsconfig.json +3 -0
  91. package/providers/medusa/tsconfig.lib.json +16 -1
  92. package/providers/meilisearch/project.json +1 -1
  93. package/providers/posthog/project.json +1 -1
  94. package/tsconfig.base.json +4 -1
  95. package/.claude/settings.local.json +0 -28
  96. package/core/src/schemas/mutations/analytics.mutation.ts +0 -23
  97. package/providers/algolia/src/test/test-utils.ts +0 -31
  98. /package/documentation/{2-getting-started.md → docs/2-getting-started.md} +0 -0
  99. /package/documentation/{3-querying-and-changing-data.md → docs/3-querying-and-changing-data.md} +0 -0
  100. /package/documentation/{4-product-data.md → docs/4-product-data.md} +0 -0
  101. /package/documentation/{5-cart-and-checkout.md → docs/5-cart-and-checkout.md} +0 -0
  102. /package/documentation/{6-product-search.md → docs/6-product-search.md} +0 -0
  103. /package/documentation/{7-marketing.md → docs/7-marketing.md} +0 -0
@@ -0,0 +1,33 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ title: 'My Project',
5
+ tagline: 'Documentation',
6
+ url: 'https://your-site.com',
7
+ baseUrl: '/',
8
+ onBrokenLinks: 'throw',
9
+ staticDirectories: [path.resolve(__dirname, 'static')],
10
+ i18n: {
11
+ defaultLocale: 'en',
12
+ locales: ['en'],
13
+ },
14
+ markdown: {
15
+ format: 'md',
16
+ },
17
+ presets: [
18
+ [
19
+ 'classic',
20
+ {
21
+ docs: {
22
+ path: path.resolve(__dirname, 'docs'),
23
+ routeBasePath: '/',
24
+ sidebarPath: path.resolve(__dirname, 'sidebars.js'),
25
+ },
26
+ blog: false,
27
+ theme: {
28
+ customCss: path.resolve(__dirname, 'src/css/custom.css'),
29
+ },
30
+ },
31
+ ],
32
+ ],
33
+ };
@@ -0,0 +1,52 @@
1
+ import { zod2md } from 'zod2md';
2
+ import { writeFileSync } from 'fs';
3
+ import * as td from 'typedoc';
4
+ import type { TypeDocOptions } from 'typedoc';
5
+ import type { PluginOptions as MarkdownPluginOptions } from 'typedoc-plugin-markdown';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+
9
+ type TypedocWithMarkdownOptions = TypeDocOptions & MarkdownPluginOptions;
10
+
11
+ const projects = [
12
+ {
13
+ entry: 'core/src/schemas/index.ts',
14
+ title: 'Schemas',
15
+ output: 'documentation/docs/schemas/schemas.md',
16
+ },
17
+ ];
18
+
19
+ async function main() {
20
+ for (const project of projects) {
21
+ const markdown = await zod2md({
22
+ entry: project.entry,
23
+ title: `${project.title} Schemas`,
24
+ tsconfig: './core/tsconfig.lib.json',
25
+ });
26
+ writeFileSync(project.output, markdown);
27
+ }
28
+
29
+ const typedocConfig = {
30
+ entryPoints: ['core/src/providers/*.provider.ts'],
31
+ tsconfig: 'core/tsconfig.lib.json',
32
+ plugin: ['typedoc-plugin-markdown'],
33
+ out: 'documentation/docs/providers',
34
+ outputFileStrategy: 'modules',
35
+ categorizeByGroup: true,
36
+ readme: 'none',
37
+ membersWithOwnFile: [],
38
+ modulesFileName: undefined,
39
+ } satisfies TypedocWithMarkdownOptions;
40
+ const app = await td.Application.bootstrapWithPlugins(typedocConfig);
41
+
42
+ const project = await app.convert();
43
+ if (project) {
44
+ await app.generateOutputs(project);
45
+
46
+ await fs.rm(path.join('documentation/docs/providers', 'README.md'), {
47
+ force: true,
48
+ });
49
+ }
50
+ }
51
+
52
+ main();
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ docs: [
3
+ {
4
+ type: 'autogenerated',
5
+ dirName: '.',
6
+ },
7
+ ],
8
+ };
@@ -0,0 +1,3 @@
1
+ :root {
2
+ --ifm-color-primary: #2e8555;
3
+ }
@@ -0,0 +1,12 @@
1
+ import Layout from '@theme/Layout';
2
+
3
+ export default function Home() {
4
+ return (
5
+ <Layout title="Home">
6
+ <main style={{ padding: '2rem' }}>
7
+ <h1>Welcome to My Docs</h1>
8
+ <p>Get started by reading the <a href="/docs/intro">introduction</a>.</p>
9
+ </main>
10
+ </Layout>
11
+ );
12
+ }
package/eslint.config.mjs CHANGED
@@ -9,7 +9,7 @@ export default [
9
9
  '**/dist',
10
10
  '**/vite.config.*.timestamp*',
11
11
  '**/vitest.config.*.timestamp*',
12
- '**/vitest.config.ts'
12
+ '**/vitest.config.ts',
13
13
  ],
14
14
  },
15
15
  {
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@reactionary/examples-node",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "main": "index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "dependencies": {
7
- "@reactionary/core": "0.3.0",
8
- "@reactionary/provider-commercetools": "0.3.0",
9
- "@reactionary/provider-algolia": "0.3.0",
10
- "@reactionary/provider-medusa": "0.3.0",
11
- "@reactionary/provider-meilisearch": "0.3.0"
7
+ "@reactionary/core": "0.3.2",
8
+ "@reactionary/provider-commercetools": "0.3.2",
9
+ "@reactionary/provider-algolia": "0.3.2",
10
+ "@reactionary/provider-medusa": "0.3.2",
11
+ "@reactionary/provider-meilisearch": "0.3.2"
12
12
  },
13
13
  "type": "module"
14
14
  }
@@ -1,7 +1,5 @@
1
1
  import type {
2
2
  Cache,
3
- ProductQueryById,
4
- ProductQueryBySlug,
5
3
  RequestContext} from '@reactionary/core';
6
4
  import {
7
5
  ClientBuilder,
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import { ClientBuilder, createInitialRequestContext, NoOpCache } from '@reactionary/core';
3
3
  import { FakeProductProvider, withFakeCapabilities } from '@reactionary/provider-fake';
4
4
  import { CommercetoolsCartProvider, withCommercetoolsCapabilities } from '@reactionary/provider-commercetools';
5
- import { AlgoliaSearchProvider, withAlgoliaCapabilities } from '@reactionary/provider-algolia';
5
+ import { AlgoliaProductSearchProvider, withAlgoliaCapabilities } from '@reactionary/provider-algolia';
6
6
 
7
7
  describe('client creation', () => {
8
8
  it('should be able to mix providers and get a valid, typed client', async () => {
@@ -48,6 +48,6 @@ describe('client creation', () => {
48
48
 
49
49
  expect(client.cart).toBeInstanceOf(CommercetoolsCartProvider);
50
50
  expect(client.product).toBeInstanceOf(FakeProductProvider);
51
- expect(client.productSearch).toBeInstanceOf(AlgoliaSearchProvider);
51
+ expect(client.productSearch).toBeInstanceOf(AlgoliaProductSearchProvider);
52
52
  });
53
53
  });
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@reactionary/source",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "dependencies": {
7
7
  "@commercetools/platform-sdk": "^8.16.0",
8
8
  "@commercetools/ts-client": "^4.2.1",
9
+ "@commercetools/ts-sdk-apm": "^4.0.0",
10
+ "@docusaurus/core": "^3.9.2",
11
+ "@docusaurus/preset-classic": "^3.9.2",
9
12
  "@faker-js/faker": "^9.8.0",
10
13
  "@medusajs/js-sdk": "^2.13.0",
11
14
  "@opentelemetry/api": "^1.9.0",
@@ -19,7 +22,6 @@
19
22
  "meilisearch": "^0.55.0",
20
23
  "node-object-hash": "^3.1.1",
21
24
  "posthog-node": "^5.24.0",
22
- "search-insights": "^2.17.3",
23
25
  "zod": "4.1.9"
24
26
  },
25
27
  "devDependencies": {
@@ -44,15 +46,23 @@
44
46
  "@types/debug": "^4.1.12",
45
47
  "@types/node": "^24.0.0",
46
48
  "@typescript-eslint/utils": "^8.33.1",
49
+ "@vitest/coverage-v8": "^4.0.0",
50
+ "docusaurus-plugin-typedoc": "^1.4.2",
51
+ "esbuild": "^0.19.2",
52
+ "jsonc-eslint-parser": "^2.1.0",
47
53
  "nx": "22.4.5",
48
- "prettier": "^2.6.2",
54
+ "prettier": "~3.6.2",
49
55
  "ts-node": "10.9.1",
50
56
  "tslib": "^2.3.0",
57
+ "typedoc": "^0.28.16",
58
+ "typedoc-plugin-markdown": "^4.9.0",
51
59
  "typescript": "5.9.3",
52
60
  "typescript-eslint": "^8.33.1",
61
+ "verdaccio": "^6.0.5",
53
62
  "vite": "7.1.9",
54
63
  "vite-tsconfig-paths": "^5.1.4",
55
- "vitest": "^4.0.9"
64
+ "vitest": "^4.0.9",
65
+ "zod2md": "^0.2.5"
56
66
  },
57
67
  "nx": {
58
68
  "includedScripts": []
@@ -62,5 +72,9 @@
62
72
  "provenance": false
63
73
  },
64
74
  "packageManager": "pnpm@8.15.9+sha512.499434c9d8fdd1a2794ebf4552b3b25c0a633abcee5bb15e7b5de90f32f47b513aca98cd5cfd001c31f0db454bc3804edccd578501e4ca293a6816166bbd9f81",
65
- "scripts": {}
75
+ "scripts": {
76
+ "docs:start": "docusaurus start --config documentation/docusaurus.config.js",
77
+ "docs:build": "docusaurus build --config documentation/docusaurus.config.js",
78
+ "docs:generate": "node ./documentation/scripts/generate.ts"
79
+ }
66
80
  }
@@ -38,11 +38,19 @@ You can have more, for use with facets, and additional searchable fields, but th
38
38
 
39
39
  The `objectID` corrosponds to your productIdentifier, and `variantID` should match your SKU
40
40
 
41
+ ## Analytics
41
42
 
42
- ## Building
43
+ The Algolia analytics provider maps the following tracked event types to data tracked in Algolia:
43
44
 
44
- Run `nx build provider-algolia` to build the library.
45
+ - AnalyticsMutationProductSummaryViewEvent => ViewedObjectIDs
46
+ - AnalyticsMutationProductSummaryClickEvent => ClickedObjectIDsAfterSearch / ClickedObjectIDs
47
+ - AnalyticsMutationProductAddToCartEvent => AddedToCartObjectIDsAfterSearch / AddedToCartObjectIDs
48
+ - AnalyticsMutationPurchaseEvent => PurchasedObjectIDs
45
49
 
46
- ## Running unit tests
50
+ The `AfterSearch` variants are (with the exception of purchase) preferred by the provider in the cases where Algolia is the source of the events. For search or recommendation this would typically be the case, but not necesarily for users arriving on a PDP as a direct target from a search or a link.
47
51
 
48
- Run `nx test provider-algolia` to execute the unit tests via [Jest](https://jestjs.io).
52
+ Note that we do not map `PurchasedObjectIDsAfterSearch` as it would require us to persist the search query ID that lead to the add-to-cart occuring on the cart items. This currently seems like an excess burden to impose on the cart interface.
53
+
54
+ The `ConvertedObjectIDs` and `ConvertedObjectIDsAfterSearch` are not mapped as they seem superfluous by all accounts in a product-purchase based flow. They could likely be used for other types of conversions in a more general setup, such as a customer finishing reading an article.
55
+
56
+ Finally the events that are related to filtering are not mapped, as they are by all accounts deprecated and no longer influence any of the recommendation or personalization features.
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "provider-algolia",
2
+ "name": "algolia",
3
3
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
4
  "sourceRoot": "providers/algolia/src",
5
5
  "projectType": "library",
@@ -1,14 +1,19 @@
1
1
  import type { Cache, ClientFromCapabilities, RequestContext } from "@reactionary/core";
2
- import { AlgoliaSearchProvider } from "../providers/product-search.provider.js";
2
+ import { AlgoliaProductSearchProvider } from "../providers/product-search.provider.js";
3
3
  import type { AlgoliaCapabilities } from "../schema/capabilities.schema.js";
4
4
  import type { AlgoliaConfiguration } from "../schema/configuration.schema.js";
5
+ import { AlgoliaAnalyticsProvider } from "../providers/analytics.provider.js";
5
6
 
6
7
  export function withAlgoliaCapabilities<T extends AlgoliaCapabilities>(configuration: AlgoliaConfiguration, capabilities: T) {
7
8
  return (cache: Cache, context: RequestContext): ClientFromCapabilities<T> => {
8
9
  const client: any = {};
9
10
 
10
11
  if (capabilities.productSearch) {
11
- client.productSearch = new AlgoliaSearchProvider(configuration, cache, context);
12
+ client.productSearch = new AlgoliaProductSearchProvider(cache, context, configuration);
13
+ }
14
+
15
+ if (capabilities.analytics) {
16
+ client.analytics = new AlgoliaAnalyticsProvider(cache, context, configuration);
12
17
  }
13
18
 
14
19
  return client;
@@ -0,0 +1,114 @@
1
+ import {
2
+ AnalyticsProvider,
3
+ type AnalyticsMutationProductAddToCartEvent,
4
+ type AnalyticsMutationProductSummaryClickEvent,
5
+ type AnalyticsMutationProductSummaryViewEvent,
6
+ type AnalyticsMutationPurchaseEvent,
7
+ type Cache,
8
+ type RequestContext,
9
+ } from '@reactionary/core';
10
+ import {
11
+ type InsightsClient,
12
+ type ViewedObjectIDs,
13
+ type ClickedObjectIDsAfterSearch,
14
+ type AddedToCartObjectIDsAfterSearch,
15
+ type PurchasedObjectIDs,
16
+ algoliasearch,
17
+ } from 'algoliasearch';
18
+ import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
19
+ import type { AlgoliaProductSearchIdentifier } from '../schema/search.schema.js';
20
+
21
+ export class AlgoliaAnalyticsProvider extends AnalyticsProvider {
22
+ protected client: InsightsClient;
23
+ protected config: AlgoliaConfiguration;
24
+
25
+ constructor(
26
+ cache: Cache,
27
+ requestContext: RequestContext,
28
+ config: AlgoliaConfiguration
29
+ ) {
30
+ super(cache, requestContext);
31
+
32
+ this.config = config;
33
+ this.client = algoliasearch(this.config.appId, this.config.apiKey).initInsights({});
34
+ }
35
+
36
+ protected override async processProductAddToCart(
37
+ event: AnalyticsMutationProductAddToCartEvent
38
+ ) {
39
+ if (event.source && event.source.type === 'search') {
40
+ const algoliaEvent = {
41
+ eventName: 'addToCart',
42
+ eventType: 'conversion',
43
+ eventSubtype: 'addToCart',
44
+ index: this.config.indexName,
45
+ objectIDs: [event.product.key],
46
+ userToken: this.context.session.identityContext.personalizationKey,
47
+ queryID: (event.source.identifier as AlgoliaProductSearchIdentifier)
48
+ .key,
49
+ } satisfies AddedToCartObjectIDsAfterSearch;
50
+
51
+ const response = await this.client.pushEvents({
52
+ events: [algoliaEvent],
53
+ });
54
+ }
55
+ }
56
+
57
+ protected override async processProductSummaryClick(
58
+ event: AnalyticsMutationProductSummaryClickEvent
59
+ ) {
60
+ if (event.source && event.source.type === 'search') {
61
+ const algoliaEvent = {
62
+ eventName: 'click',
63
+ eventType: 'click',
64
+ index: this.config.indexName,
65
+ objectIDs: [event.product.key],
66
+ userToken: this.context.session.identityContext.personalizationKey,
67
+ positions: [event.position],
68
+ queryID: (event.source.identifier as AlgoliaProductSearchIdentifier)
69
+ .key,
70
+ } satisfies ClickedObjectIDsAfterSearch;
71
+
72
+ const response = await this.client.pushEvents({
73
+ events: [algoliaEvent],
74
+ });
75
+ }
76
+ }
77
+
78
+ protected override async processProductSummaryView(
79
+ event: AnalyticsMutationProductSummaryViewEvent
80
+ ) {
81
+ if (event.source && event.source.type === 'search') {
82
+ const algoliaEvent = {
83
+ eventName: 'view',
84
+ eventType: 'view',
85
+ index: this.config.indexName,
86
+ objectIDs: event.products.map((x) => x.key),
87
+ userToken: this.context.session.identityContext.personalizationKey,
88
+ } satisfies ViewedObjectIDs;
89
+
90
+ const response = await this.client.pushEvents({
91
+ events: [algoliaEvent],
92
+ });
93
+ }
94
+ }
95
+
96
+ protected override async processPurchase(
97
+ event: AnalyticsMutationPurchaseEvent
98
+ ): Promise<void> {
99
+ // TODO: Figure out how to handle the problem below. From the order we have the SKUs,
100
+ // but in Algolia we have the products indexed, and we can't really resolve it here...
101
+ const algoliaEvent = {
102
+ eventName: 'purchase',
103
+ eventType: 'conversion',
104
+ eventSubtype: 'purchase',
105
+ index: this.config.indexName,
106
+ objectIDs: event.order.items.map((x) => x.variant.sku),
107
+ userToken: this.context.session.identityContext.personalizationKey,
108
+ } satisfies PurchasedObjectIDs;
109
+
110
+ const response = await this.client.pushEvents({
111
+ events: [algoliaEvent],
112
+ });
113
+ }
114
+ }
@@ -1 +1,2 @@
1
+ export * from './analytics.provider.js';
1
2
  export * from './product-search.provider.js';
@@ -40,10 +40,10 @@ interface AlgoliaNativeRecord {
40
40
  }
41
41
 
42
42
 
43
- export class AlgoliaSearchProvider extends ProductSearchProvider {
43
+ export class AlgoliaProductSearchProvider extends ProductSearchProvider {
44
44
  protected config: AlgoliaConfiguration;
45
45
 
46
- constructor(config: AlgoliaConfiguration, cache: Cache, context: RequestContext) {
46
+ constructor(cache: Cache, context: RequestContext, config: AlgoliaConfiguration) {
47
47
  super(cache, context);
48
48
  this.config = config;
49
49
  }
@@ -195,7 +195,8 @@ export class AlgoliaSearchProvider extends ProductSearchProvider {
195
195
  facets: query.search.facets,
196
196
  filters: query.search.filters,
197
197
  paginationOptions: query.search.paginationOptions,
198
-
198
+ index: body.index || '',
199
+ key: body.queryID || '',
199
200
  },
200
201
  pageNumber: (body.page || 0) + 1,
201
202
  pageSize: body.hitsPerPage || 0,
@@ -203,7 +204,7 @@ export class AlgoliaSearchProvider extends ProductSearchProvider {
203
204
  totalPages: body.nbPages || 0,
204
205
  items: items,
205
206
  facets,
206
- } satisfies ProductSearchResult;
207
+ } satisfies AlgoliaProductSearchResult;
207
208
 
208
209
  return result;
209
210
  }
@@ -0,0 +1,138 @@
1
+ import { describe, it, assert } from 'vitest';
2
+ import { AlgoliaAnalyticsProvider } from '../providers/analytics.provider.js';
3
+ import { createInitialRequestContext, NoOpCache } from '@reactionary/core';
4
+ import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
5
+ import { AlgoliaProductSearchProvider } from '../providers/product-search.provider.js';
6
+
7
+ describe('Analytics event tracking', async () => {
8
+ const config = {
9
+ apiKey: process.env['ALGOLIA_API_KEY'] || '',
10
+ appId: process.env['ALGOLIA_APP_ID'] || '',
11
+ indexName: process.env['ALGOLIA_INDEX'] || '',
12
+ } satisfies AlgoliaConfiguration;
13
+ const cache = new NoOpCache();
14
+ const context = createInitialRequestContext();
15
+
16
+ const search = new AlgoliaProductSearchProvider(cache, context, config);
17
+ const analytics = new AlgoliaAnalyticsProvider(cache, context, config);
18
+ const searchResult = await search.queryByTerm({
19
+ search: {
20
+ facets: [],
21
+ filters: [],
22
+ paginationOptions: {
23
+ pageNumber: 1,
24
+ pageSize: 10,
25
+ },
26
+ term: 'q',
27
+ },
28
+ });
29
+
30
+ if (!searchResult.success) {
31
+ assert.fail();
32
+ }
33
+
34
+ it('can track summary clicks', async () => {
35
+ await analytics.track({
36
+ event: 'product-summary-click',
37
+ product: searchResult.value.items[0].identifier,
38
+ position: 1,
39
+ source: {
40
+ type: 'search',
41
+ identifier: searchResult.value.identifier,
42
+ },
43
+ });
44
+ });
45
+
46
+ it('can track summary views', async () => {
47
+ await analytics.track({
48
+ event: 'product-summary-view',
49
+ products: searchResult.value.items.map((x) => x.identifier),
50
+ source: {
51
+ type: 'search',
52
+ identifier: searchResult.value.identifier,
53
+ },
54
+ });
55
+ });
56
+
57
+ it('can track add to cart', async () => {
58
+ await analytics.track({
59
+ event: 'product-cart-add',
60
+ product: searchResult.value.items[0].identifier,
61
+ source: {
62
+ type: 'search',
63
+ identifier: searchResult.value.identifier,
64
+ },
65
+ });
66
+ });
67
+
68
+ it('can track purchase', async () => {
69
+ await analytics.track({
70
+ event: 'purchase',
71
+ order: {
72
+ identifier: {
73
+ key: crypto.randomUUID(),
74
+ },
75
+ inventoryStatus: 'Allocated',
76
+ items: [
77
+ {
78
+ identifier: {
79
+ key: crypto.randomUUID(),
80
+ },
81
+ inventoryStatus: 'Allocated',
82
+ price: {
83
+ unitPrice: {
84
+ currency: 'USD',
85
+ value: 50,
86
+ },
87
+ totalDiscount: {
88
+ currency: 'USD',
89
+ value: 0,
90
+ },
91
+ totalPrice: {
92
+ currency: 'USD',
93
+ value: 50,
94
+ },
95
+ unitDiscount: {
96
+ currency: 'USD',
97
+ value: 0,
98
+ },
99
+ },
100
+ quantity: 1,
101
+ variant: searchResult.value.items[0].variants[0].variant,
102
+ },
103
+ ],
104
+ orderStatus: 'Shipped',
105
+ paymentInstructions: [],
106
+ price: {
107
+ grandTotal: {
108
+ currency: 'USD',
109
+ value: 50,
110
+ },
111
+ totalDiscount: {
112
+ currency: 'USD',
113
+ value: 0,
114
+ },
115
+ totalProductPrice: {
116
+ currency: 'USD',
117
+ value: 50,
118
+ },
119
+ totalShipping: {
120
+ currency: 'USD',
121
+ value: 0,
122
+ },
123
+ totalSurcharge: {
124
+ currency: 'USD',
125
+ value: 0,
126
+ },
127
+ totalTax: {
128
+ currency: 'USD',
129
+ value: 0,
130
+ },
131
+ },
132
+ userId: {
133
+ userId: crypto.randomUUID()
134
+ }
135
+ },
136
+ });
137
+ });
138
+ });
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "provider-commercetools",
2
+ "name": "commercetools",
3
3
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
4
  "sourceRoot": "providers/commercetools/src",
5
5
  "projectType": "library",
@@ -15,7 +15,6 @@ import {
15
15
  success,
16
16
  } from '@reactionary/core';
17
17
  import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
18
- import type z from 'zod';
19
18
  import type { CommercetoolsAPI } from '../core/client.js';
20
19
 
21
20
  export class CommercetoolsIdentityProvider extends IdentityProvider {
@@ -41,6 +40,8 @@ export class CommercetoolsIdentityProvider extends IdentityProvider {
41
40
  public override async getSelf(payload: IdentityQuerySelf): Promise<Result<Identity>> {
42
41
  const identity = await this.commercetools.introspect();
43
42
 
43
+ this.updateIdentityContext(identity);
44
+
44
45
  return success(identity);
45
46
  }
46
47
 
@@ -54,6 +55,8 @@ export class CommercetoolsIdentityProvider extends IdentityProvider {
54
55
  payload.password
55
56
  );
56
57
 
58
+ this.updateIdentityContext(identity);
59
+
57
60
  return success(identity);
58
61
  }
59
62
 
@@ -63,6 +66,8 @@ export class CommercetoolsIdentityProvider extends IdentityProvider {
63
66
  public override async logout(payload: Record<string, never>): Promise<Result<Identity>> {
64
67
  const identity = await this.commercetools.logout();
65
68
 
69
+ this.updateIdentityContext(identity);
70
+
66
71
  return success(identity);
67
72
  }
68
73
 
@@ -78,6 +83,8 @@ export class CommercetoolsIdentityProvider extends IdentityProvider {
78
83
  payload.password
79
84
  );
80
85
 
86
+ this.updateIdentityContext(identity);
87
+
81
88
  return success(identity);
82
89
  }
83
90
  }
@@ -237,10 +237,7 @@ export class CommercetoolsProfileProvider extends ProfileProvider {
237
237
  if (addressToMakeDefault.id === customer.defaultBillingAddressId) {
238
238
  return error<InvalidInputError>({
239
239
  type: 'InvalidInput',
240
- error: {
241
- field: 'addressIdentifier',
242
- message: 'Cannot set shipping address as default billing address',
243
- }
240
+ error: 'Cannot set shipping address as default billing address'
244
241
  });
245
242
  }
246
243
 
@@ -8,7 +8,7 @@ import {
8
8
  type ProductSearchQueryByTerm,
9
9
  } from '@reactionary/core';
10
10
  import { CommercetoolsProductProvider } from '../providers/product.provider.js';
11
- import { CommercetoolsClient } from '../core/client.js';
11
+ import { CommercetoolsAPI } from '../core/client.js';
12
12
  import { CommercetoolsSearchProvider } from '../providers/product-search.provider.js';
13
13
 
14
14
  describe('Caching', () => {
@@ -16,7 +16,7 @@ describe('Caching', () => {
16
16
  const config = getCommercetoolsTestConfiguration();
17
17
  const context = createInitialRequestContext();
18
18
  const cache = new MemoryCache();
19
- const client = new CommercetoolsClient(config, context);
19
+ const client = new CommercetoolsAPI(config, context);
20
20
  const provider = new CommercetoolsProductProvider(config, cache, context, client);
21
21
 
22
22
  const identifier = {
@@ -48,7 +48,7 @@ describe('Caching', () => {
48
48
  const config = getCommercetoolsTestConfiguration();
49
49
  const context = createInitialRequestContext();
50
50
  const cache = new MemoryCache();
51
- const client = new CommercetoolsClient(config, context);
51
+ const client = new CommercetoolsAPI(config, context);
52
52
  const provider = new CommercetoolsSearchProvider(config, cache, context, client);
53
53
 
54
54
  const query = {