@reactionary/source 0.3.1 → 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.
- package/core/src/initialization.ts +4 -1
- package/core/src/providers/identity.provider.ts +5 -0
- package/core/src/schemas/session.schema.ts +2 -1
- package/documentation/docs/8-tracking.md +1 -1
- package/examples/node/package.json +6 -6
- package/examples/node/src/basic/client-creation.spec.ts +2 -2
- package/package.json +1 -1
- package/providers/algolia/README.md +12 -4
- package/providers/algolia/src/core/initialize.ts +2 -2
- package/providers/algolia/src/providers/analytics.provider.ts +9 -7
- package/providers/algolia/src/providers/product-search.provider.ts +5 -4
- package/providers/algolia/src/test/analytics.spec.ts +138 -0
- package/providers/commercetools/src/providers/identity.provider.ts +8 -1
- package/providers/commercetools/src/test/caching.spec.ts +3 -3
- package/providers/commercetools/src/test/identity.spec.ts +2 -2
- package/providers/google-analytics/package.json +0 -6
- package/providers/medusa/src/providers/identity.provider.ts +34 -10
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnonymousIdentity } from './schemas/index.js';
|
|
1
2
|
import type { RequestContext } from './schemas/session.schema.js';
|
|
2
3
|
|
|
3
4
|
export function createInitialRequestContext(): RequestContext {
|
|
@@ -17,7 +18,9 @@ export function createInitialRequestContext(): RequestContext {
|
|
|
17
18
|
},
|
|
18
19
|
session: {
|
|
19
20
|
identityContext: {
|
|
20
|
-
|
|
21
|
+
identity: {
|
|
22
|
+
type: 'Anonymous'
|
|
23
|
+
} satisfies AnonymousIdentity,
|
|
21
24
|
lastUpdated: new Date(),
|
|
22
25
|
personalizationKey: crypto.randomUUID(),
|
|
23
26
|
},
|
|
@@ -13,4 +13,9 @@ export abstract class IdentityProvider extends BaseProvider {
|
|
|
13
13
|
protected override getResourceName(): string {
|
|
14
14
|
return 'identity';
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
protected updateIdentityContext(identity: Identity) {
|
|
18
|
+
this.context.session.identityContext.lastUpdated = new Date();
|
|
19
|
+
this.context.session.identityContext.identity = identity;
|
|
20
|
+
}
|
|
16
21
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { IdentityIdentifierSchema, WebStoreIdentifierSchema } from './models/identifiers.model.js';
|
|
3
3
|
import { CurrencySchema } from './models/currency.model.js';
|
|
4
|
+
import { IdentitySchema } from './models/identity.model.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The language and locale context for the current request.
|
|
@@ -11,7 +12,7 @@ export const LanguageContextSchema = z.looseObject( {
|
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
export const IdentityContextSchema = z.looseObject({
|
|
14
|
-
|
|
15
|
+
identity: IdentitySchema,
|
|
15
16
|
personalizationKey: z.string(),
|
|
16
17
|
lastUpdated: z.date()
|
|
17
18
|
});
|
|
@@ -6,4 +6,4 @@ Reactionary takes the approach that tracking customer data should be structured
|
|
|
6
6
|
- Structure: it should be possible to reason about what is tracked on the site, how it is tracked and when it is tracked without having to visit seven different tag managers.
|
|
7
7
|
- Security: pulling in all the embedded and inline scripts from every tag manager is a security incident waiting to happen.
|
|
8
8
|
|
|
9
|
-
To this end the client exposes a single provider in the form of `client.analytics`. This client is internally responsible for delegating events to all relevant subscribers capabilities used to build the client. This means that a single call to record a pageview or attribution will be enough, even if that data internally needs to be multiplexed to GA4, Algolia and Posthog as an example.
|
|
9
|
+
To this end the client exposes a single provider in the form of `client.analytics`. This client is internally responsible for delegating events to all relevant subscribers capabilities used to build the client. This means that a single call to record a pageview or attribution will be enough, even if that data internally needs to be multiplexed to GA4, Algolia and Posthog as an example.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/examples-node",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
|
8
|
-
"@reactionary/provider-commercetools": "0.3.
|
|
9
|
-
"@reactionary/provider-algolia": "0.3.
|
|
10
|
-
"@reactionary/provider-medusa": "0.3.
|
|
11
|
-
"@reactionary/provider-meilisearch": "0.3.
|
|
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
|
}
|
|
@@ -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 {
|
|
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(
|
|
51
|
+
expect(client.productSearch).toBeInstanceOf(AlgoliaProductSearchProvider);
|
|
52
52
|
});
|
|
53
53
|
});
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
43
|
+
The Algolia analytics provider maps the following tracked event types to data tracked in Algolia:
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
- AnalyticsMutationProductSummaryViewEvent => ViewedObjectIDs
|
|
46
|
+
- AnalyticsMutationProductSummaryClickEvent => ClickedObjectIDsAfterSearch / ClickedObjectIDs
|
|
47
|
+
- AnalyticsMutationProductAddToCartEvent => AddedToCartObjectIDsAfterSearch / AddedToCartObjectIDs
|
|
48
|
+
- AnalyticsMutationPurchaseEvent => PurchasedObjectIDs
|
|
45
49
|
|
|
46
|
-
|
|
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
|
-
|
|
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
|
import type { Cache, ClientFromCapabilities, RequestContext } from "@reactionary/core";
|
|
2
|
-
import {
|
|
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
5
|
import { AlgoliaAnalyticsProvider } from "../providers/analytics.provider.js";
|
|
@@ -9,7 +9,7 @@ export function withAlgoliaCapabilities<T extends AlgoliaCapabilities>(configura
|
|
|
9
9
|
const client: any = {};
|
|
10
10
|
|
|
11
11
|
if (capabilities.productSearch) {
|
|
12
|
-
client.productSearch = new
|
|
12
|
+
client.productSearch = new AlgoliaProductSearchProvider(cache, context, configuration);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (capabilities.analytics) {
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
type RequestContext,
|
|
9
9
|
} from '@reactionary/core';
|
|
10
10
|
import {
|
|
11
|
-
insightsClient,
|
|
12
11
|
type InsightsClient,
|
|
13
12
|
type ViewedObjectIDs,
|
|
14
13
|
type ClickedObjectIDsAfterSearch,
|
|
15
14
|
type AddedToCartObjectIDsAfterSearch,
|
|
16
15
|
type PurchasedObjectIDs,
|
|
16
|
+
algoliasearch,
|
|
17
17
|
} from 'algoliasearch';
|
|
18
18
|
import type { AlgoliaConfiguration } from '../schema/configuration.schema.js';
|
|
19
19
|
import type { AlgoliaProductSearchIdentifier } from '../schema/search.schema.js';
|
|
@@ -30,7 +30,7 @@ export class AlgoliaAnalyticsProvider extends AnalyticsProvider {
|
|
|
30
30
|
super(cache, requestContext);
|
|
31
31
|
|
|
32
32
|
this.config = config;
|
|
33
|
-
this.client =
|
|
33
|
+
this.client = algoliasearch(this.config.appId, this.config.apiKey).initInsights({});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
protected override async processProductAddToCart(
|
|
@@ -48,7 +48,7 @@ export class AlgoliaAnalyticsProvider extends AnalyticsProvider {
|
|
|
48
48
|
.key,
|
|
49
49
|
} satisfies AddedToCartObjectIDsAfterSearch;
|
|
50
50
|
|
|
51
|
-
this.client.pushEvents({
|
|
51
|
+
const response = await this.client.pushEvents({
|
|
52
52
|
events: [algoliaEvent],
|
|
53
53
|
});
|
|
54
54
|
}
|
|
@@ -69,7 +69,7 @@ export class AlgoliaAnalyticsProvider extends AnalyticsProvider {
|
|
|
69
69
|
.key,
|
|
70
70
|
} satisfies ClickedObjectIDsAfterSearch;
|
|
71
71
|
|
|
72
|
-
this.client.pushEvents({
|
|
72
|
+
const response = await this.client.pushEvents({
|
|
73
73
|
events: [algoliaEvent],
|
|
74
74
|
});
|
|
75
75
|
}
|
|
@@ -87,7 +87,7 @@ export class AlgoliaAnalyticsProvider extends AnalyticsProvider {
|
|
|
87
87
|
userToken: this.context.session.identityContext.personalizationKey,
|
|
88
88
|
} satisfies ViewedObjectIDs;
|
|
89
89
|
|
|
90
|
-
this.client.pushEvents({
|
|
90
|
+
const response = await this.client.pushEvents({
|
|
91
91
|
events: [algoliaEvent],
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -96,16 +96,18 @@ export class AlgoliaAnalyticsProvider extends AnalyticsProvider {
|
|
|
96
96
|
protected override async processPurchase(
|
|
97
97
|
event: AnalyticsMutationPurchaseEvent
|
|
98
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...
|
|
99
101
|
const algoliaEvent = {
|
|
100
102
|
eventName: 'purchase',
|
|
101
103
|
eventType: 'conversion',
|
|
102
104
|
eventSubtype: 'purchase',
|
|
103
105
|
index: this.config.indexName,
|
|
104
|
-
objectIDs: event.order.items.map((x) => x.
|
|
106
|
+
objectIDs: event.order.items.map((x) => x.variant.sku),
|
|
105
107
|
userToken: this.context.session.identityContext.personalizationKey,
|
|
106
108
|
} satisfies PurchasedObjectIDs;
|
|
107
109
|
|
|
108
|
-
this.client.pushEvents({
|
|
110
|
+
const response = await this.client.pushEvents({
|
|
109
111
|
events: [algoliaEvent],
|
|
110
112
|
});
|
|
111
113
|
}
|
|
@@ -40,10 +40,10 @@ interface AlgoliaNativeRecord {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
export class
|
|
43
|
+
export class AlgoliaProductSearchProvider extends ProductSearchProvider {
|
|
44
44
|
protected config: AlgoliaConfiguration;
|
|
45
45
|
|
|
46
|
-
constructor(
|
|
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
|
|
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
|
+
});
|
|
@@ -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
|
}
|
|
@@ -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 {
|
|
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
|
|
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
|
|
51
|
+
const client = new CommercetoolsAPI(config, context);
|
|
52
52
|
const provider = new CommercetoolsSearchProvider(config, cache, context, client);
|
|
53
53
|
|
|
54
54
|
const query = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import { CommercetoolsAPI } from '../core/client.js';
|
|
4
4
|
import { getCommercetoolsTestConfiguration } from './test-utils.js';
|
|
5
5
|
import {
|
|
6
6
|
createInitialRequestContext,
|
|
@@ -12,7 +12,7 @@ import type { CommercetoolsConfiguration } from '../schema/configuration.schema.
|
|
|
12
12
|
function setup() {
|
|
13
13
|
const config = getCommercetoolsTestConfiguration();
|
|
14
14
|
const context = createInitialRequestContext();
|
|
15
|
-
const root = new
|
|
15
|
+
const root = new CommercetoolsAPI(config, context);
|
|
16
16
|
|
|
17
17
|
return {
|
|
18
18
|
config,
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
success,
|
|
20
20
|
} from '@reactionary/core';
|
|
21
21
|
import type { MedusaConfiguration } from '../schema/configuration.schema.js';
|
|
22
|
-
import type z from 'zod';
|
|
23
22
|
import type { MedusaAPI } from '../core/client.js';
|
|
24
23
|
import createDebug from 'debug';
|
|
25
24
|
|
|
@@ -60,7 +59,10 @@ export class MedusaIdentityProvider extends IdentityProvider {
|
|
|
60
59
|
|
|
61
60
|
if (!token) {
|
|
62
61
|
debug('No active session token found, returning anonymous identity');
|
|
63
|
-
|
|
62
|
+
const identity = this.createAnonymousIdentity();
|
|
63
|
+
this.updateIdentityContext(identity);
|
|
64
|
+
|
|
65
|
+
return success(identity);
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
// Try to fetch customer details to verify authentication
|
|
@@ -68,18 +70,30 @@ export class MedusaIdentityProvider extends IdentityProvider {
|
|
|
68
70
|
|
|
69
71
|
if (customerResponse.customer) {
|
|
70
72
|
debug('Customer authenticated:', customerResponse.customer.email);
|
|
71
|
-
|
|
73
|
+
|
|
74
|
+
const identity = {
|
|
72
75
|
id: {
|
|
73
76
|
userId: customerResponse.customer.id,
|
|
74
77
|
},
|
|
75
78
|
type: 'Registered',
|
|
76
|
-
} satisfies RegisteredIdentity
|
|
79
|
+
} satisfies RegisteredIdentity;
|
|
80
|
+
|
|
81
|
+
this.updateIdentityContext(identity);
|
|
82
|
+
|
|
83
|
+
return success(identity);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
const identity = this.createAnonymousIdentity();
|
|
87
|
+
this.updateIdentityContext(identity);
|
|
88
|
+
|
|
89
|
+
return success(identity);
|
|
80
90
|
} catch (error) {
|
|
81
91
|
debug('getSelf failed, returning anonymous identity:', error);
|
|
82
|
-
|
|
92
|
+
|
|
93
|
+
const identity = this.createAnonymousIdentity();
|
|
94
|
+
this.updateIdentityContext(identity);
|
|
95
|
+
|
|
96
|
+
return success(identity);
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
99
|
|
|
@@ -87,13 +101,17 @@ export class MedusaIdentityProvider extends IdentityProvider {
|
|
|
87
101
|
inputSchema: IdentityMutationLoginSchema,
|
|
88
102
|
outputSchema: IdentitySchema,
|
|
89
103
|
})
|
|
90
|
-
public override async login(
|
|
104
|
+
public override async login(
|
|
105
|
+
payload: IdentityMutationLogin
|
|
106
|
+
): Promise<Result<Identity>> {
|
|
91
107
|
debug('Attempting login for user:', payload.username);
|
|
92
|
-
const identity = await this.medusaApi.login(
|
|
108
|
+
const identity = (await this.medusaApi.login(
|
|
93
109
|
payload.username,
|
|
94
110
|
payload.password,
|
|
95
111
|
this.context
|
|
96
|
-
) satisfies Identity;
|
|
112
|
+
)) satisfies Identity;
|
|
113
|
+
|
|
114
|
+
this.updateIdentityContext(identity);
|
|
97
115
|
|
|
98
116
|
return success(identity);
|
|
99
117
|
}
|
|
@@ -102,10 +120,14 @@ export class MedusaIdentityProvider extends IdentityProvider {
|
|
|
102
120
|
inputSchema: IdentityMutationLogoutSchema,
|
|
103
121
|
outputSchema: IdentitySchema,
|
|
104
122
|
})
|
|
105
|
-
public override async logout(
|
|
123
|
+
public override async logout(
|
|
124
|
+
_payload: IdentityMutationLogout
|
|
125
|
+
): Promise<Result<Identity>> {
|
|
106
126
|
debug('Logging out user');
|
|
107
127
|
const identity = await this.medusaApi.logout(this.context);
|
|
108
128
|
|
|
129
|
+
this.updateIdentityContext(identity);
|
|
130
|
+
|
|
109
131
|
return success(identity);
|
|
110
132
|
}
|
|
111
133
|
|
|
@@ -132,6 +154,8 @@ export class MedusaIdentityProvider extends IdentityProvider {
|
|
|
132
154
|
this.context
|
|
133
155
|
);
|
|
134
156
|
|
|
157
|
+
this.updateIdentityContext(identity);
|
|
158
|
+
|
|
135
159
|
return success(identity);
|
|
136
160
|
}
|
|
137
161
|
}
|