@reactionary/source 0.0.41 → 0.0.48
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/.claude/settings.local.json +28 -0
- package/.env-template +8 -5
- package/.vscode/settings.json +5 -0
- package/README.md +41 -0
- package/core/package.json +3 -1
- package/core/src/cache/cache.interface.ts +14 -18
- package/core/src/cache/memory-cache.ts +56 -0
- package/core/src/cache/noop-cache.ts +5 -23
- package/core/src/cache/redis-cache.ts +28 -38
- package/core/src/client/client-builder.ts +3 -3
- package/core/src/client/client.ts +11 -9
- package/core/src/decorators/reactionary.decorator.ts +80 -8
- package/core/src/index.ts +5 -29
- package/core/src/initialization.ts +43 -0
- package/core/src/providers/analytics.provider.ts +1 -1
- package/core/src/providers/base.provider.ts +61 -25
- package/core/src/providers/cart-payment.provider.ts +57 -0
- package/core/src/providers/cart.provider.ts +131 -8
- package/core/src/providers/category.provider.ts +9 -9
- package/core/src/providers/identity.provider.ts +8 -7
- package/core/src/providers/index.ts +12 -0
- package/core/src/providers/inventory.provider.ts +4 -4
- package/core/src/providers/price.provider.ts +7 -7
- package/core/src/providers/product.provider.ts +17 -5
- package/core/src/providers/profile.provider.ts +22 -0
- package/core/src/providers/search.provider.ts +4 -4
- package/core/src/providers/store.provider.ts +14 -0
- package/core/src/schemas/capabilities.schema.ts +3 -1
- package/core/src/schemas/models/analytics.model.ts +1 -1
- package/core/src/schemas/models/cart.model.ts +16 -3
- package/core/src/schemas/models/identifiers.model.ts +90 -22
- package/core/src/schemas/models/identity.model.ts +23 -7
- package/core/src/schemas/models/index.ts +15 -0
- package/core/src/schemas/models/payment.model.ts +41 -0
- package/core/src/schemas/models/profile.model.ts +35 -0
- package/core/src/schemas/models/shipping-method.model.ts +14 -0
- package/core/src/schemas/models/store.model.ts +11 -0
- package/core/src/schemas/mutations/cart-payment.mutation.ts +21 -0
- package/core/src/schemas/mutations/cart.mutation.ts +62 -3
- package/core/src/schemas/mutations/identity.mutation.ts +8 -1
- package/core/src/schemas/mutations/index.ts +10 -0
- package/core/src/schemas/mutations/profile.mutation.ts +9 -0
- package/core/src/schemas/queries/cart-payment.query.ts +12 -0
- package/core/src/schemas/queries/cart.query.ts +1 -1
- package/core/src/schemas/queries/identity.query.ts +1 -1
- package/core/src/schemas/queries/index.ts +3 -0
- package/core/src/schemas/queries/inventory.query.ts +4 -12
- package/core/src/schemas/queries/price.query.ts +1 -1
- package/core/src/schemas/queries/profile.query.ts +7 -0
- package/core/src/schemas/queries/search.query.ts +1 -1
- package/core/src/schemas/queries/store.query.ts +11 -0
- package/core/src/schemas/session.schema.ts +31 -6
- package/eslint.config.mjs +7 -0
- package/examples/next/src/app/page.tsx +4 -12
- package/examples/node/package.json +1 -3
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +9 -8
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +4 -3
- package/examples/node/src/basic/basic-node-setup.spec.ts +4 -5
- package/nx.json +1 -0
- package/otel/src/metrics.ts +2 -1
- package/otel/src/provider-instrumentation.ts +2 -1
- package/otel/src/tracer.ts +7 -6
- package/otel/src/trpc-middleware.ts +3 -2
- package/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +4 -3
- package/providers/algolia/src/providers/product.provider.ts +15 -13
- package/providers/algolia/src/providers/search.provider.ts +9 -9
- package/providers/algolia/src/schema/capabilities.schema.ts +1 -1
- package/providers/algolia/src/test/search.provider.spec.ts +10 -10
- package/providers/algolia/src/test/test-utils.ts +9 -4
- package/providers/commercetools/README.md +27 -0
- package/providers/commercetools/src/core/client.ts +164 -117
- package/providers/commercetools/src/core/initialize.ts +24 -14
- package/providers/commercetools/src/providers/cart-payment.provider.ts +193 -0
- package/providers/commercetools/src/providers/cart.provider.ts +402 -125
- package/providers/commercetools/src/providers/category.provider.ts +35 -35
- package/providers/commercetools/src/providers/identity.provider.ts +23 -75
- package/providers/commercetools/src/providers/index.ts +2 -0
- package/providers/commercetools/src/providers/inventory.provider.ts +69 -40
- package/providers/commercetools/src/providers/price.provider.ts +79 -47
- package/providers/commercetools/src/providers/product.provider.ts +36 -30
- package/providers/commercetools/src/providers/profile.provider.ts +61 -0
- package/providers/commercetools/src/providers/search.provider.ts +16 -12
- package/providers/commercetools/src/providers/store.provider.ts +78 -0
- package/providers/commercetools/src/schema/capabilities.schema.ts +3 -1
- package/providers/commercetools/src/schema/commercetools.schema.ts +18 -0
- package/providers/commercetools/src/schema/configuration.schema.ts +2 -1
- package/providers/commercetools/src/test/cart-payment.provider.spec.ts +145 -0
- package/providers/commercetools/src/test/cart.provider.spec.ts +82 -22
- package/providers/commercetools/src/test/category.provider.spec.ts +18 -17
- package/providers/commercetools/src/test/identity.provider.spec.ts +88 -0
- package/providers/commercetools/src/test/inventory.provider.spec.ts +41 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +9 -8
- package/providers/commercetools/src/test/product.provider.spec.ts +33 -5
- package/providers/commercetools/src/test/profile.provider.spec.ts +49 -0
- package/providers/commercetools/src/test/search.provider.spec.ts +8 -7
- package/providers/commercetools/src/test/store.provider.spec.ts +37 -0
- package/providers/commercetools/src/test/test-utils.ts +7 -31
- package/providers/fake/src/core/initialize.ts +96 -38
- package/providers/fake/src/providers/analytics.provider.ts +6 -5
- package/providers/fake/src/providers/cart.provider.ts +66 -19
- package/providers/fake/src/providers/category.provider.ts +12 -12
- package/providers/fake/src/providers/identity.provider.ts +22 -14
- package/providers/fake/src/providers/index.ts +1 -0
- package/providers/fake/src/providers/inventory.provider.ts +13 -13
- package/providers/fake/src/providers/price.provider.ts +13 -13
- package/providers/fake/src/providers/product.provider.ts +13 -10
- package/providers/fake/src/providers/search.provider.ts +7 -5
- package/providers/fake/src/providers/store.provider.ts +47 -0
- package/providers/fake/src/schema/capabilities.schema.ts +4 -1
- package/providers/fake/src/test/cart.provider.spec.ts +18 -18
- package/providers/fake/src/test/category.provider.spec.ts +55 -37
- package/providers/fake/src/test/price.provider.spec.ts +9 -14
- package/providers/fake/src/test/product.provider.spec.ts +27 -0
- package/providers/fake/src/test/test-utils.ts +2 -28
- package/providers/posthog/src/core/initialize.ts +3 -3
- package/providers/posthog/src/schema/capabilities.schema.ts +1 -1
- package/trpc/src/client.ts +42 -41
- package/trpc/src/index.ts +4 -3
- package/trpc/src/integration.spec.ts +11 -11
- package/trpc/src/server.ts +26 -24
- package/trpc/src/test-utils.ts +9 -4
- package/trpc/src/types.ts +24 -22
- package/core/src/cache/cache-evaluation.interface.ts +0 -19
- package/examples/node/src/test-utils.ts +0 -26
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { CategoryProvider,
|
|
2
|
-
import type { Session, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories } from "@reactionary/core";
|
|
3
|
-
import z from "zod";
|
|
4
|
-
import { CommercetoolsConfiguration } from "../schema/configuration.schema";
|
|
1
|
+
import { CategoryProvider, createPaginatedResponseSchema } from "@reactionary/core";
|
|
2
|
+
import type { Session, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories, RequestContext , Cache, Category} from "@reactionary/core";
|
|
3
|
+
import type z from "zod";
|
|
4
|
+
import type { CommercetoolsConfiguration } from "../schema/configuration.schema";
|
|
5
5
|
import { CommercetoolsClient } from "../core/client";
|
|
6
|
-
import { ByProjectKeyCategoriesRequestBuilder, CategoryPagedQueryResponse, Category as CTCategory } from "@commercetools/platform-sdk";
|
|
6
|
+
import type { ByProjectKeyCategoriesRequestBuilder, CategoryPagedQueryResponse, Category as CTCategory } from "@commercetools/platform-sdk";
|
|
7
7
|
import { traced } from "@reactionary/otel";
|
|
8
8
|
|
|
9
9
|
export class CommercetoolsCategoryProvider<
|
|
@@ -18,9 +18,9 @@ export class CommercetoolsCategoryProvider<
|
|
|
18
18
|
this.config = config;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
protected getClient(
|
|
22
|
-
const client = new CommercetoolsClient(this.config).getClient(
|
|
23
|
-
return client;
|
|
21
|
+
protected async getClient(reqCtx: RequestContext): Promise<ByProjectKeyCategoriesRequestBuilder> {
|
|
22
|
+
const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
|
|
23
|
+
return client.withProjectKey({ projectKey: this.config.projectKey }).categories();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -30,11 +30,11 @@ export class CommercetoolsCategoryProvider<
|
|
|
30
30
|
* @returns
|
|
31
31
|
*/
|
|
32
32
|
@traced()
|
|
33
|
-
public override async getById(payload: CategoryQueryById,
|
|
34
|
-
const client = this.getClient(
|
|
33
|
+
public override async getById(payload: CategoryQueryById, reqCtx: RequestContext): Promise<T> {
|
|
34
|
+
const client = await this.getClient(reqCtx);
|
|
35
35
|
try {
|
|
36
36
|
const response = await client.withKey({ key: payload.id.key }).get().execute();
|
|
37
|
-
return this.parseSingle(response.body,
|
|
37
|
+
return this.parseSingle(response.body, reqCtx);
|
|
38
38
|
} catch (error) {
|
|
39
39
|
const dummyCategory = this.newModel();
|
|
40
40
|
dummyCategory.meta.placeholder = true;
|
|
@@ -50,14 +50,14 @@ export class CommercetoolsCategoryProvider<
|
|
|
50
50
|
* @returns
|
|
51
51
|
*/
|
|
52
52
|
@traced()
|
|
53
|
-
public override async getBySlug(payload: CategoryQueryBySlug,
|
|
54
|
-
const client = this.getClient(
|
|
53
|
+
public override async getBySlug(payload: CategoryQueryBySlug, reqCtx: RequestContext): Promise<T | null> {
|
|
54
|
+
const client = await this.getClient(reqCtx);
|
|
55
55
|
try {
|
|
56
56
|
const response = await client.get({
|
|
57
57
|
queryArgs: {
|
|
58
|
-
where: `slug(${
|
|
58
|
+
where: `slug(${reqCtx.languageContext.locale}=:slug)`,
|
|
59
59
|
'var.slug': payload.slug,
|
|
60
|
-
storeProjection:
|
|
60
|
+
storeProjection: reqCtx.storeIdentifier.key ,
|
|
61
61
|
limit: 1,
|
|
62
62
|
withTotal: false,
|
|
63
63
|
}
|
|
@@ -65,7 +65,7 @@ export class CommercetoolsCategoryProvider<
|
|
|
65
65
|
if (response.body.results.length === 0) {
|
|
66
66
|
return null;
|
|
67
67
|
}
|
|
68
|
-
return this.parseSingle(response.body.results[0],
|
|
68
|
+
return this.parseSingle(response.body.results[0], reqCtx);
|
|
69
69
|
} catch (error) {
|
|
70
70
|
console.error(`Error fetching category by slug:`, error);
|
|
71
71
|
return null;
|
|
@@ -80,8 +80,8 @@ export class CommercetoolsCategoryProvider<
|
|
|
80
80
|
* @returns
|
|
81
81
|
*/
|
|
82
82
|
@traced()
|
|
83
|
-
public override async getBreadcrumbPathToCategory(payload: CategoryQueryForBreadcrumb,
|
|
84
|
-
const client = this.getClient(
|
|
83
|
+
public override async getBreadcrumbPathToCategory(payload: CategoryQueryForBreadcrumb, reqCtx: RequestContext): Promise<T[]> {
|
|
84
|
+
const client = await this.getClient(reqCtx);
|
|
85
85
|
const path: T[] = [];
|
|
86
86
|
try {
|
|
87
87
|
const response = await client.withKey({ key: payload.id.key }).get({
|
|
@@ -90,10 +90,10 @@ export class CommercetoolsCategoryProvider<
|
|
|
90
90
|
}
|
|
91
91
|
}).execute();
|
|
92
92
|
|
|
93
|
-
const category = this.parseSingle(response.body,
|
|
93
|
+
const category = this.parseSingle(response.body, reqCtx);
|
|
94
94
|
for(const anc of response.body.ancestors || []) {
|
|
95
95
|
if (anc.obj) {
|
|
96
|
-
const parsedAnc = this.parseSingle(anc.obj,
|
|
96
|
+
const parsedAnc = this.parseSingle(anc.obj, reqCtx);
|
|
97
97
|
path.push(parsedAnc);
|
|
98
98
|
}
|
|
99
99
|
};
|
|
@@ -117,12 +117,12 @@ export class CommercetoolsCategoryProvider<
|
|
|
117
117
|
* @returns
|
|
118
118
|
*/
|
|
119
119
|
@traced()
|
|
120
|
-
public override async findChildCategories(payload: CategoryQueryForChildCategories,
|
|
120
|
+
public override async findChildCategories(payload: CategoryQueryForChildCategories, reqCtx: RequestContext) {
|
|
121
121
|
|
|
122
122
|
// ok, so for Commercetools we can't actually query by the parents key, so we have to first resolve the key to an ID, then query by that.
|
|
123
123
|
// This is a bit of a pain, but we can cache the result of the first lookup for a short period to mitigate it.
|
|
124
124
|
|
|
125
|
-
const client = this.getClient(
|
|
125
|
+
const client = await this.getClient(reqCtx);
|
|
126
126
|
|
|
127
127
|
try {
|
|
128
128
|
const parentCategory = await client.withKey({ key: payload.parentId.key }).get().execute();
|
|
@@ -138,14 +138,14 @@ export class CommercetoolsCategoryProvider<
|
|
|
138
138
|
limit: payload.paginationOptions.pageSize,
|
|
139
139
|
offset: (payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize,
|
|
140
140
|
sort: 'orderHint asc',
|
|
141
|
-
storeProjection:
|
|
141
|
+
storeProjection: reqCtx.storeIdentifier.key ,
|
|
142
142
|
},
|
|
143
143
|
})
|
|
144
144
|
.execute();
|
|
145
145
|
|
|
146
|
-
const result = this.parsePaginatedResult(response.body,
|
|
146
|
+
const result = this.parsePaginatedResult(response.body, reqCtx);
|
|
147
147
|
result.meta = {
|
|
148
|
-
cache: { hit: false, key: this.generateCacheKeyPaginatedResult('children-of-' + payload.parentId.key, result,
|
|
148
|
+
cache: { hit: false, key: this.generateCacheKeyPaginatedResult('children-of-' + payload.parentId.key, result, reqCtx) },
|
|
149
149
|
placeholder: false
|
|
150
150
|
};
|
|
151
151
|
return result;
|
|
@@ -156,9 +156,9 @@ export class CommercetoolsCategoryProvider<
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
@traced()
|
|
159
|
-
public override async findTopCategories(payload: CategoryQueryForTopCategories,
|
|
159
|
+
public override async findTopCategories(payload: CategoryQueryForTopCategories, reqCtx: RequestContext) {
|
|
160
160
|
|
|
161
|
-
const client = this.getClient(
|
|
161
|
+
const client = await this.getClient(reqCtx);
|
|
162
162
|
try {
|
|
163
163
|
const response = await client.get({
|
|
164
164
|
queryArgs: {
|
|
@@ -166,14 +166,14 @@ export class CommercetoolsCategoryProvider<
|
|
|
166
166
|
limit: payload.paginationOptions.pageSize,
|
|
167
167
|
offset: (payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize,
|
|
168
168
|
sort: 'orderHint asc',
|
|
169
|
-
storeProjection:
|
|
169
|
+
storeProjection: reqCtx.storeIdentifier.key ,
|
|
170
170
|
},
|
|
171
171
|
})
|
|
172
172
|
.execute();
|
|
173
173
|
|
|
174
|
-
const result = this.parsePaginatedResult(response.body,
|
|
174
|
+
const result = this.parsePaginatedResult(response.body, reqCtx);
|
|
175
175
|
result.meta = {
|
|
176
|
-
cache: { hit: false, key: this.generateCacheKeyPaginatedResult('top', result,
|
|
176
|
+
cache: { hit: false, key: this.generateCacheKeyPaginatedResult('top', result, reqCtx) },
|
|
177
177
|
placeholder: false
|
|
178
178
|
};
|
|
179
179
|
return result;
|
|
@@ -191,9 +191,9 @@ export class CommercetoolsCategoryProvider<
|
|
|
191
191
|
* into the typed domain model.
|
|
192
192
|
*/
|
|
193
193
|
@traced()
|
|
194
|
-
protected override parseSingle(_body: unknown,
|
|
194
|
+
protected override parseSingle(_body: unknown, reqCtx: RequestContext): T {
|
|
195
195
|
const body = _body as CTCategory;
|
|
196
|
-
const languageContext =
|
|
196
|
+
const languageContext = reqCtx.languageContext;
|
|
197
197
|
|
|
198
198
|
const model = this.newModel();
|
|
199
199
|
|
|
@@ -214,7 +214,7 @@ export class CommercetoolsCategoryProvider<
|
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
model.meta = {
|
|
217
|
-
cache: { hit: false, key: this.generateCacheKeySingle(model.identifier,
|
|
217
|
+
cache: { hit: false, key: this.generateCacheKeySingle(model.identifier, reqCtx) },
|
|
218
218
|
placeholder: false
|
|
219
219
|
};
|
|
220
220
|
|
|
@@ -223,10 +223,10 @@ export class CommercetoolsCategoryProvider<
|
|
|
223
223
|
|
|
224
224
|
|
|
225
225
|
@traced()
|
|
226
|
-
protected override parsePaginatedResult(_body: unknown,
|
|
226
|
+
protected override parsePaginatedResult(_body: unknown, reqCtx: RequestContext) {
|
|
227
227
|
const body = _body as CategoryPagedQueryResponse;
|
|
228
228
|
|
|
229
|
-
const items = body.results.map(x => this.parseSingle(x,
|
|
229
|
+
const items = body.results.map(x => this.parseSingle(x, reqCtx));
|
|
230
230
|
|
|
231
231
|
const result = createPaginatedResponseSchema(this.schema).parse({
|
|
232
232
|
meta: {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Identity,
|
|
3
|
-
IdentityMutationLogin,
|
|
2
|
+
type Identity,
|
|
3
|
+
type IdentityMutationLogin,
|
|
4
|
+
type IdentityQuerySelf,
|
|
5
|
+
type RequestContext,
|
|
6
|
+
type Cache,
|
|
4
7
|
IdentityProvider,
|
|
5
|
-
|
|
6
|
-
Session,
|
|
7
|
-
Cache,
|
|
8
|
+
type IdentityMutationRegister,
|
|
8
9
|
} from '@reactionary/core';
|
|
9
|
-
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
10
|
-
import z from 'zod';
|
|
10
|
+
import type { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
11
|
+
import type z from 'zod';
|
|
11
12
|
import { CommercetoolsClient } from '../core/client';
|
|
12
13
|
|
|
13
14
|
export class CommercetoolsIdentityProvider<
|
|
@@ -27,88 +28,35 @@ export class CommercetoolsIdentityProvider<
|
|
|
27
28
|
|
|
28
29
|
public override async getSelf(
|
|
29
30
|
payload: IdentityQuerySelf,
|
|
30
|
-
|
|
31
|
+
reqCtx: RequestContext
|
|
31
32
|
): Promise<T> {
|
|
32
|
-
|
|
33
|
-
const base = this.newModel();
|
|
34
|
-
|
|
35
|
-
if (session.identity.token) {
|
|
36
|
-
const remote = await client.introspect(session.identity.token);
|
|
37
|
-
|
|
38
|
-
if (remote.active) {
|
|
39
|
-
const current = this.schema.safeParse(session.identity);
|
|
40
|
-
|
|
41
|
-
if (current.success) {
|
|
42
|
-
current.data.meta = {
|
|
43
|
-
cache: { hit: false, key: session.identity.id || 'anonymous' },
|
|
44
|
-
placeholder: false
|
|
45
|
-
};
|
|
46
|
-
return current.data;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
base.meta = {
|
|
52
|
-
cache: { hit: false, key: 'anonymous' },
|
|
53
|
-
placeholder: false
|
|
54
|
-
};
|
|
55
|
-
session.identity = base;
|
|
56
|
-
|
|
57
|
-
return this.assert(base);
|
|
33
|
+
return this.assert(reqCtx.identity as T);
|
|
58
34
|
}
|
|
59
35
|
|
|
60
36
|
public override async login(
|
|
61
37
|
payload: IdentityMutationLogin,
|
|
62
|
-
|
|
38
|
+
reqCtx: RequestContext
|
|
63
39
|
): Promise<T> {
|
|
64
|
-
|
|
65
|
-
const remote = await client.login(payload.username, payload.password);
|
|
66
|
-
const base = this.newModel();
|
|
67
|
-
|
|
68
|
-
if (remote && remote.access_token) {
|
|
69
|
-
base.issued = new Date();
|
|
70
|
-
base.expiry = new Date();
|
|
71
|
-
base.expiry.setSeconds(base.expiry.getSeconds() + remote.expires_in);
|
|
72
|
-
base.id = this.extractCustomerIdFromScopes(remote.scope);
|
|
73
|
-
base.token = remote.access_token;
|
|
74
|
-
base.type = 'Registered';
|
|
75
|
-
}
|
|
40
|
+
await new CommercetoolsClient(this.config).login(payload.username, payload.password, reqCtx);
|
|
76
41
|
|
|
77
|
-
|
|
78
|
-
cache: { hit: false, key: base.id || 'anonymous' },
|
|
79
|
-
placeholder: false
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
session.identity = base;
|
|
83
|
-
|
|
84
|
-
return this.assert(base);
|
|
42
|
+
return this.getSelf({}, reqCtx);
|
|
85
43
|
}
|
|
86
44
|
|
|
87
45
|
public override async logout(
|
|
88
46
|
payload: Record<string, never>,
|
|
89
|
-
|
|
47
|
+
reqCtx: RequestContext
|
|
90
48
|
): Promise<T> {
|
|
91
|
-
|
|
92
|
-
const base = this.newModel();
|
|
93
|
-
|
|
94
|
-
if (session.identity.token) {
|
|
95
|
-
await client.logout(session.identity.token);
|
|
96
|
-
}
|
|
49
|
+
await new CommercetoolsClient(this.config).logout(reqCtx);
|
|
97
50
|
|
|
98
|
-
|
|
99
|
-
cache: { hit: false, key: 'anonymous' },
|
|
100
|
-
placeholder: false
|
|
101
|
-
};
|
|
102
|
-
session.identity = base;
|
|
103
|
-
|
|
104
|
-
return this.assert(base);
|
|
51
|
+
return this.getSelf({}, reqCtx);
|
|
105
52
|
}
|
|
106
53
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
54
|
+
public override async register(
|
|
55
|
+
payload: IdentityMutationRegister,
|
|
56
|
+
reqCtx: RequestContext
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
await new CommercetoolsClient(this.config).register(payload.username, payload.password, reqCtx);
|
|
111
59
|
|
|
112
|
-
return
|
|
60
|
+
return this.getSelf({}, reqCtx);
|
|
113
61
|
}
|
|
114
|
-
}
|
|
62
|
+
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
Inventory,
|
|
3
|
-
|
|
4
|
-
InventoryQuery,
|
|
5
|
-
Session,
|
|
3
|
+
RequestContext,
|
|
6
4
|
Cache,
|
|
7
|
-
|
|
5
|
+
InventoryQueryBySKU,
|
|
8
6
|
} from '@reactionary/core';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
7
|
+
import { InventoryProvider } from '@reactionary/core';
|
|
8
|
+
import type z from 'zod';
|
|
9
|
+
import type { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
11
10
|
import { CommercetoolsClient } from '../core/client';
|
|
12
|
-
import {
|
|
11
|
+
import type {
|
|
12
|
+
InventoryEntry,
|
|
13
|
+
ProductVariant,
|
|
14
|
+
ProductVariantAvailability,
|
|
15
|
+
} from '@commercetools/platform-sdk';
|
|
13
16
|
export class CommercetoolsInventoryProvider<
|
|
14
17
|
T extends Inventory = Inventory
|
|
15
18
|
> extends InventoryProvider<T> {
|
|
@@ -25,53 +28,79 @@ export class CommercetoolsInventoryProvider<
|
|
|
25
28
|
this.config = config;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
protected async getClient(reqCtx: RequestContext) {
|
|
32
|
+
const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
|
|
33
|
+
return client
|
|
34
|
+
.withProjectKey({ projectKey: this.config.projectKey })
|
|
35
|
+
.inventory();
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
public override async getBySKU(
|
|
29
|
-
payload:
|
|
30
|
-
|
|
39
|
+
payload: InventoryQueryBySKU,
|
|
40
|
+
reqCtx: RequestContext
|
|
31
41
|
): Promise<T> {
|
|
32
|
-
const client = new CommercetoolsClient(this.config).getClient(
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
|
|
43
|
+
|
|
44
|
+
// TODO: We can't query by supplyChannel.key, so we have to resolve it first.
|
|
45
|
+
// This is probably a good candidate for internal data caching at some point.
|
|
46
|
+
const channel = await client
|
|
47
|
+
.withProjectKey({ projectKey: this.config.projectKey })
|
|
48
|
+
.channels()
|
|
49
|
+
.withKey({ key: payload.fulfilmentCenter.key })
|
|
50
|
+
.get()
|
|
51
|
+
.execute();
|
|
52
|
+
|
|
53
|
+
const channelId = channel.body.id;
|
|
35
54
|
|
|
36
55
|
const remote = await client
|
|
37
56
|
.withProjectKey({ projectKey: this.config.projectKey })
|
|
38
57
|
.inventory()
|
|
39
58
|
.get({
|
|
40
59
|
queryArgs: {
|
|
41
|
-
where:
|
|
60
|
+
where: 'sku=:sku AND supplyChannel(id=:channel)',
|
|
61
|
+
'var.sku': payload.sku.key,
|
|
62
|
+
'var.channel': channelId,
|
|
63
|
+
expand: 'supplyChannel'
|
|
42
64
|
},
|
|
43
65
|
})
|
|
44
66
|
.execute();
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
}
|
|
68
|
+
const result = remote.body.results[0];
|
|
48
69
|
|
|
49
|
-
|
|
50
|
-
const body = _body as CTInventory;
|
|
51
|
-
const model = this.newModel();
|
|
70
|
+
const model = this.parseSingle(result, reqCtx);
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
sku: { key: body.sku },
|
|
55
|
-
channelId: {
|
|
56
|
-
key: body.supplyChannel?.id || 'online'
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
model.sku = body.sku;
|
|
60
|
-
model.quantity = body.availableQuantity;
|
|
61
|
-
|
|
62
|
-
if (model.quantity > 0 ) {
|
|
63
|
-
model.status = 'inStock';
|
|
64
|
-
} else {
|
|
65
|
-
model.status = 'outOfStock';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
model.meta = {
|
|
69
|
-
cache: { hit: false, key: this.generateCacheKeySingle(model.identifier, session) },
|
|
70
|
-
placeholder: false
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
return this.assert(model);
|
|
72
|
+
return model;
|
|
74
73
|
}
|
|
75
74
|
|
|
75
|
+
protected override parseSingle(
|
|
76
|
+
body: InventoryEntry,
|
|
77
|
+
reqCtx: RequestContext
|
|
78
|
+
): T {
|
|
79
|
+
const model = this.newModel();
|
|
80
|
+
|
|
81
|
+
model.identifier = {
|
|
82
|
+
sku: { key: body.sku || '' },
|
|
83
|
+
fulfillmentCenter: {
|
|
84
|
+
key: body.supplyChannel?.obj?.key || '',
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
model.quantity = body.availableQuantity || 0;
|
|
76
89
|
|
|
90
|
+
if (model.quantity > 0) {
|
|
91
|
+
model.status = 'inStock';
|
|
92
|
+
} else {
|
|
93
|
+
model.status = 'outOfStock';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
model.meta = {
|
|
97
|
+
cache: {
|
|
98
|
+
hit: false,
|
|
99
|
+
key: this.generateCacheKeySingle(model.identifier, reqCtx),
|
|
100
|
+
},
|
|
101
|
+
placeholder: false,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return this.assert(model);
|
|
105
|
+
}
|
|
77
106
|
}
|
|
@@ -1,47 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { PriceQueryBySku,
|
|
3
|
-
import z from 'zod';
|
|
4
|
-
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
1
|
+
import { PriceProvider, TieredPriceSchema } from '@reactionary/core';
|
|
2
|
+
import type { PriceQueryBySku, RequestContext , Price, Cache, Currency, TieredPrice } from '@reactionary/core';
|
|
3
|
+
import type z from 'zod';
|
|
4
|
+
import type { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
5
5
|
import { CommercetoolsClient } from '../core/client';
|
|
6
|
-
import {
|
|
6
|
+
import type { Price as CTPrice, ProductVariant as CTProductVariant } from '@commercetools/platform-sdk';
|
|
7
7
|
export class CommercetoolsPriceProvider<
|
|
8
8
|
T extends Price = Price
|
|
9
9
|
> extends PriceProvider<T> {
|
|
10
10
|
protected config: CommercetoolsConfiguration;
|
|
11
11
|
|
|
12
|
+
|
|
13
|
+
|
|
12
14
|
constructor(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
|
|
13
15
|
super(schema, cache);
|
|
14
16
|
|
|
15
17
|
this.config = config;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
protected async getClient(reqCtx: RequestContext) {
|
|
22
|
+
const client = await new CommercetoolsClient(this.config).getClient(
|
|
23
|
+
reqCtx
|
|
24
|
+
);
|
|
25
|
+
return client.withProjectKey({ projectKey: this.config.projectKey }).productProjections()
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
|
|
23
|
-
public override async getBySKUs(payload: PriceQueryBySku[], session: Session): Promise<T[]> {
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
public override async getBySKUs(payload: PriceQueryBySku[], reqCtx: RequestContext): Promise<T[]> {
|
|
31
|
+
|
|
32
|
+
const client = await this.getClient(reqCtx);
|
|
26
33
|
|
|
27
34
|
// AND (validFrom is not defined OR validFrom <= now()) AND (validUntil is not defined OR validUntil >= now())
|
|
28
|
-
const queryArgs = {
|
|
29
|
-
where: 'sku in (:skus)',
|
|
30
|
-
'var.skus': payload.map(p => p.sku.key),
|
|
31
|
-
};
|
|
32
35
|
|
|
36
|
+
const channels = await this.getChannels(reqCtx);
|
|
33
37
|
|
|
34
38
|
const response = await client.get({
|
|
35
|
-
queryArgs
|
|
39
|
+
queryArgs: {
|
|
40
|
+
staged: false,
|
|
41
|
+
priceCountry: 'US',
|
|
42
|
+
priceCustomerGroup: undefined,
|
|
43
|
+
priceChannel: channels.offerChannelGUID,
|
|
44
|
+
priceCurrency: reqCtx.languageContext.currencyCode,
|
|
45
|
+
// storeProjection: reqCtx.storeIdentifier?.key || undefined,
|
|
46
|
+
where: 'variants(sku in (:skus)) OR (masterVariant(sku in (:skus))) ',
|
|
47
|
+
'var.skus': payload.map(p => p.sku.key),
|
|
48
|
+
limit: payload.length,
|
|
49
|
+
},
|
|
36
50
|
}).execute();
|
|
37
51
|
|
|
38
52
|
const result = [];
|
|
53
|
+
const allReturnedVariants = [...response.body.results.map(x => x.variants).flat(), ...response.body.results.map(x => x.masterVariant).flat()];
|
|
54
|
+
// Now we need to match the skus requested with the prices returned.
|
|
39
55
|
for(const p of payload) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
const foundSku = allReturnedVariants.find(v => v.sku === p.sku.key);
|
|
57
|
+
|
|
58
|
+
if (!foundSku) {
|
|
59
|
+
result.push(this.createEmptyPriceResult(p.sku.key, reqCtx.languageContext.currencyCode ));
|
|
43
60
|
} else {
|
|
44
|
-
result.push(this.
|
|
61
|
+
result.push(this.parseSingle(foundSku, reqCtx));
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
64
|
|
|
@@ -51,43 +68,29 @@ export class CommercetoolsPriceProvider<
|
|
|
51
68
|
|
|
52
69
|
public override async getBySKU(
|
|
53
70
|
payload: PriceQueryBySku,
|
|
54
|
-
|
|
71
|
+
reqCtx: RequestContext
|
|
55
72
|
): Promise<T> {
|
|
56
|
-
|
|
57
|
-
// AND (validFrom is not defined OR validFrom <= now()) AND (validUntil is not defined OR validUntil >= now())
|
|
58
|
-
const queryArgs = {
|
|
59
|
-
where: 'sku=:sku',
|
|
60
|
-
'var.sku': payload.sku.key,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const remote = await client
|
|
64
|
-
.get({
|
|
65
|
-
queryArgs,
|
|
66
|
-
})
|
|
67
|
-
.execute();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const matched = remote.body.results.filter(x => x.value.currencyCode === session.languageContext.currencyCode);
|
|
71
|
-
if (matched && matched.length > 0) {
|
|
72
|
-
return this.parseSingle(matched[0], session);
|
|
73
|
-
}
|
|
74
|
-
return this.getEmptyPriceResult(payload.sku.key, session.languageContext.currencyCode );
|
|
73
|
+
return this.getBySKUs([payload], reqCtx).then(r => r[0]);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
|
|
79
|
-
protected override parseSingle(_body: unknown,
|
|
80
|
-
const body = _body as
|
|
78
|
+
protected override parseSingle(_body: unknown, reqCtx: RequestContext): T {
|
|
79
|
+
const body = _body as CTProductVariant;
|
|
80
|
+
const price = body.price as CTPrice | undefined;
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
if (!price) {
|
|
83
|
+
return this.createEmptyPriceResult(body.sku!, reqCtx.languageContext.currencyCode);
|
|
84
|
+
}
|
|
83
85
|
|
|
86
|
+
const base = this.newModel();
|
|
84
87
|
base.unitPrice = {
|
|
85
|
-
value: (
|
|
86
|
-
currency:
|
|
88
|
+
value: (price.value.centAmount / 100),
|
|
89
|
+
currency: price.value.currencyCode as Currency,
|
|
87
90
|
};
|
|
88
91
|
|
|
89
|
-
if (
|
|
90
|
-
const p =
|
|
92
|
+
if (price.tiers && price.tiers.length > 0) {
|
|
93
|
+
const p = price.tiers.map(x => {
|
|
91
94
|
const tp: TieredPrice = TieredPriceSchema.parse({});
|
|
92
95
|
tp.minimumQuantity = x.minimumQuantity;
|
|
93
96
|
tp.price = {
|
|
@@ -101,12 +104,12 @@ export class CommercetoolsPriceProvider<
|
|
|
101
104
|
|
|
102
105
|
base.identifier = {
|
|
103
106
|
sku: {
|
|
104
|
-
key: body.sku
|
|
107
|
+
key: body.sku!
|
|
105
108
|
}
|
|
106
109
|
};
|
|
107
110
|
|
|
108
111
|
base.meta = {
|
|
109
|
-
cache: { hit: false, key: this.generateCacheKeySingle(base.identifier,
|
|
112
|
+
cache: { hit: false, key: this.generateCacheKeySingle(base.identifier, reqCtx) },
|
|
110
113
|
placeholder: false
|
|
111
114
|
};
|
|
112
115
|
|
|
@@ -114,5 +117,34 @@ export class CommercetoolsPriceProvider<
|
|
|
114
117
|
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
protected async getChannels(reqCtx: RequestContext) {
|
|
121
|
+
|
|
122
|
+
if (!(reqCtx.session['commercetools'] && reqCtx.session['commercetools'].offerChannelGUID && reqCtx.session['commercetools'].listChannelGUID)) {
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Bah - have to be an admin to call these....
|
|
126
|
+
* So either we cache them in the session, or we make the user provide them in the config.
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
const configClient = await new CommercetoolsClient(this.config).getClient(reqCtx);
|
|
131
|
+
const offerPriceChannelPromise = configClient.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key: 'Offer Price'}).get().execute();
|
|
132
|
+
const listPriceChannelPromise = configClient.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key: 'List Price'}).get().execute();
|
|
133
|
+
|
|
134
|
+
const [offerChannel, listChannel] = await Promise.all([offerPriceChannelPromise, listPriceChannelPromise]);
|
|
135
|
+
*/
|
|
117
136
|
|
|
137
|
+
reqCtx.session['commercetools'] = {
|
|
138
|
+
...reqCtx.session['commercetools'],
|
|
139
|
+
offerChannelGUID: undefined,
|
|
140
|
+
listChannelGUID: undefined
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
offerChannelGUID: reqCtx.session['commercetools'].offerChannelGUID,
|
|
147
|
+
listChannelGUID: reqCtx.session['commercetools'].listChannelGUID
|
|
148
|
+
}
|
|
149
|
+
}
|
|
118
150
|
}
|