@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.
Files changed (125) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/.env-template +8 -5
  3. package/.vscode/settings.json +5 -0
  4. package/README.md +41 -0
  5. package/core/package.json +3 -1
  6. package/core/src/cache/cache.interface.ts +14 -18
  7. package/core/src/cache/memory-cache.ts +56 -0
  8. package/core/src/cache/noop-cache.ts +5 -23
  9. package/core/src/cache/redis-cache.ts +28 -38
  10. package/core/src/client/client-builder.ts +3 -3
  11. package/core/src/client/client.ts +11 -9
  12. package/core/src/decorators/reactionary.decorator.ts +80 -8
  13. package/core/src/index.ts +5 -29
  14. package/core/src/initialization.ts +43 -0
  15. package/core/src/providers/analytics.provider.ts +1 -1
  16. package/core/src/providers/base.provider.ts +61 -25
  17. package/core/src/providers/cart-payment.provider.ts +57 -0
  18. package/core/src/providers/cart.provider.ts +131 -8
  19. package/core/src/providers/category.provider.ts +9 -9
  20. package/core/src/providers/identity.provider.ts +8 -7
  21. package/core/src/providers/index.ts +12 -0
  22. package/core/src/providers/inventory.provider.ts +4 -4
  23. package/core/src/providers/price.provider.ts +7 -7
  24. package/core/src/providers/product.provider.ts +17 -5
  25. package/core/src/providers/profile.provider.ts +22 -0
  26. package/core/src/providers/search.provider.ts +4 -4
  27. package/core/src/providers/store.provider.ts +14 -0
  28. package/core/src/schemas/capabilities.schema.ts +3 -1
  29. package/core/src/schemas/models/analytics.model.ts +1 -1
  30. package/core/src/schemas/models/cart.model.ts +16 -3
  31. package/core/src/schemas/models/identifiers.model.ts +90 -22
  32. package/core/src/schemas/models/identity.model.ts +23 -7
  33. package/core/src/schemas/models/index.ts +15 -0
  34. package/core/src/schemas/models/payment.model.ts +41 -0
  35. package/core/src/schemas/models/profile.model.ts +35 -0
  36. package/core/src/schemas/models/shipping-method.model.ts +14 -0
  37. package/core/src/schemas/models/store.model.ts +11 -0
  38. package/core/src/schemas/mutations/cart-payment.mutation.ts +21 -0
  39. package/core/src/schemas/mutations/cart.mutation.ts +62 -3
  40. package/core/src/schemas/mutations/identity.mutation.ts +8 -1
  41. package/core/src/schemas/mutations/index.ts +10 -0
  42. package/core/src/schemas/mutations/profile.mutation.ts +9 -0
  43. package/core/src/schemas/queries/cart-payment.query.ts +12 -0
  44. package/core/src/schemas/queries/cart.query.ts +1 -1
  45. package/core/src/schemas/queries/identity.query.ts +1 -1
  46. package/core/src/schemas/queries/index.ts +3 -0
  47. package/core/src/schemas/queries/inventory.query.ts +4 -12
  48. package/core/src/schemas/queries/price.query.ts +1 -1
  49. package/core/src/schemas/queries/profile.query.ts +7 -0
  50. package/core/src/schemas/queries/search.query.ts +1 -1
  51. package/core/src/schemas/queries/store.query.ts +11 -0
  52. package/core/src/schemas/session.schema.ts +31 -6
  53. package/eslint.config.mjs +7 -0
  54. package/examples/next/src/app/page.tsx +4 -12
  55. package/examples/node/package.json +1 -3
  56. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +9 -8
  57. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +4 -3
  58. package/examples/node/src/basic/basic-node-setup.spec.ts +4 -5
  59. package/nx.json +1 -0
  60. package/otel/src/metrics.ts +2 -1
  61. package/otel/src/provider-instrumentation.ts +2 -1
  62. package/otel/src/tracer.ts +7 -6
  63. package/otel/src/trpc-middleware.ts +3 -2
  64. package/package.json +2 -1
  65. package/providers/algolia/src/core/initialize.ts +4 -3
  66. package/providers/algolia/src/providers/product.provider.ts +15 -13
  67. package/providers/algolia/src/providers/search.provider.ts +9 -9
  68. package/providers/algolia/src/schema/capabilities.schema.ts +1 -1
  69. package/providers/algolia/src/test/search.provider.spec.ts +10 -10
  70. package/providers/algolia/src/test/test-utils.ts +9 -4
  71. package/providers/commercetools/README.md +27 -0
  72. package/providers/commercetools/src/core/client.ts +164 -117
  73. package/providers/commercetools/src/core/initialize.ts +24 -14
  74. package/providers/commercetools/src/providers/cart-payment.provider.ts +193 -0
  75. package/providers/commercetools/src/providers/cart.provider.ts +402 -125
  76. package/providers/commercetools/src/providers/category.provider.ts +35 -35
  77. package/providers/commercetools/src/providers/identity.provider.ts +23 -75
  78. package/providers/commercetools/src/providers/index.ts +2 -0
  79. package/providers/commercetools/src/providers/inventory.provider.ts +69 -40
  80. package/providers/commercetools/src/providers/price.provider.ts +79 -47
  81. package/providers/commercetools/src/providers/product.provider.ts +36 -30
  82. package/providers/commercetools/src/providers/profile.provider.ts +61 -0
  83. package/providers/commercetools/src/providers/search.provider.ts +16 -12
  84. package/providers/commercetools/src/providers/store.provider.ts +78 -0
  85. package/providers/commercetools/src/schema/capabilities.schema.ts +3 -1
  86. package/providers/commercetools/src/schema/commercetools.schema.ts +18 -0
  87. package/providers/commercetools/src/schema/configuration.schema.ts +2 -1
  88. package/providers/commercetools/src/test/cart-payment.provider.spec.ts +145 -0
  89. package/providers/commercetools/src/test/cart.provider.spec.ts +82 -22
  90. package/providers/commercetools/src/test/category.provider.spec.ts +18 -17
  91. package/providers/commercetools/src/test/identity.provider.spec.ts +88 -0
  92. package/providers/commercetools/src/test/inventory.provider.spec.ts +41 -0
  93. package/providers/commercetools/src/test/price.provider.spec.ts +9 -8
  94. package/providers/commercetools/src/test/product.provider.spec.ts +33 -5
  95. package/providers/commercetools/src/test/profile.provider.spec.ts +49 -0
  96. package/providers/commercetools/src/test/search.provider.spec.ts +8 -7
  97. package/providers/commercetools/src/test/store.provider.spec.ts +37 -0
  98. package/providers/commercetools/src/test/test-utils.ts +7 -31
  99. package/providers/fake/src/core/initialize.ts +96 -38
  100. package/providers/fake/src/providers/analytics.provider.ts +6 -5
  101. package/providers/fake/src/providers/cart.provider.ts +66 -19
  102. package/providers/fake/src/providers/category.provider.ts +12 -12
  103. package/providers/fake/src/providers/identity.provider.ts +22 -14
  104. package/providers/fake/src/providers/index.ts +1 -0
  105. package/providers/fake/src/providers/inventory.provider.ts +13 -13
  106. package/providers/fake/src/providers/price.provider.ts +13 -13
  107. package/providers/fake/src/providers/product.provider.ts +13 -10
  108. package/providers/fake/src/providers/search.provider.ts +7 -5
  109. package/providers/fake/src/providers/store.provider.ts +47 -0
  110. package/providers/fake/src/schema/capabilities.schema.ts +4 -1
  111. package/providers/fake/src/test/cart.provider.spec.ts +18 -18
  112. package/providers/fake/src/test/category.provider.spec.ts +55 -37
  113. package/providers/fake/src/test/price.provider.spec.ts +9 -14
  114. package/providers/fake/src/test/product.provider.spec.ts +27 -0
  115. package/providers/fake/src/test/test-utils.ts +2 -28
  116. package/providers/posthog/src/core/initialize.ts +3 -3
  117. package/providers/posthog/src/schema/capabilities.schema.ts +1 -1
  118. package/trpc/src/client.ts +42 -41
  119. package/trpc/src/index.ts +4 -3
  120. package/trpc/src/integration.spec.ts +11 -11
  121. package/trpc/src/server.ts +26 -24
  122. package/trpc/src/test-utils.ts +9 -4
  123. package/trpc/src/types.ts +24 -22
  124. package/core/src/cache/cache-evaluation.interface.ts +0 -19
  125. package/examples/node/src/test-utils.ts +0 -26
@@ -1,9 +1,9 @@
1
- import { CategoryProvider, Cache, Category, createPaginatedResponseSchema } from "@reactionary/core";
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(session: Session): ByProjectKeyCategoriesRequestBuilder {
22
- const client = new CommercetoolsClient(this.config).getClient(session.identity.token).withProjectKey({ projectKey: this.config.projectKey }).categories();
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, session: Session): Promise<T> {
34
- const client = this.getClient(session);
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, session);
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, session: Session): Promise<T | null> {
54
- const client = this.getClient(session);
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(${session.languageContext.locale}=:slug)`,
58
+ where: `slug(${reqCtx.languageContext.locale}=:slug)`,
59
59
  'var.slug': payload.slug,
60
- storeProjection: session.storeIdentifier.key ,
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], session);
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, session: Session): Promise<T[]> {
84
- const client = this.getClient(session);
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, session);
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, session);
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, session: Session) {
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(session);
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: session.storeIdentifier.key ,
141
+ storeProjection: reqCtx.storeIdentifier.key ,
142
142
  },
143
143
  })
144
144
  .execute();
145
145
 
146
- const result = this.parsePaginatedResult(response.body, session);
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, session) },
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, session: Session) {
159
+ public override async findTopCategories(payload: CategoryQueryForTopCategories, reqCtx: RequestContext) {
160
160
 
161
- const client = this.getClient(session);
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: session.storeIdentifier.key ,
169
+ storeProjection: reqCtx.storeIdentifier.key ,
170
170
  },
171
171
  })
172
172
  .execute();
173
173
 
174
- const result = this.parsePaginatedResult(response.body, session);
174
+ const result = this.parsePaginatedResult(response.body, reqCtx);
175
175
  result.meta = {
176
- cache: { hit: false, key: this.generateCacheKeyPaginatedResult('top', result, session) },
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, session: Session): T {
194
+ protected override parseSingle(_body: unknown, reqCtx: RequestContext): T {
195
195
  const body = _body as CTCategory;
196
- const languageContext = session.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, session) },
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, session: Session) {
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, session));
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
- IdentityQuerySelf,
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
- session: Session
31
+ reqCtx: RequestContext
31
32
  ): Promise<T> {
32
- const client = new CommercetoolsClient(this.config);
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
- session: Session
38
+ reqCtx: RequestContext
63
39
  ): Promise<T> {
64
- const client = new CommercetoolsClient(this.config);
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
- base.meta = {
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
- session: Session
47
+ reqCtx: RequestContext
90
48
  ): Promise<T> {
91
- const client = new CommercetoolsClient(this.config);
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
- base.meta = {
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
- protected extractCustomerIdFromScopes(scopes: string) {
108
- const scopeList = scopes.split(' ');
109
- const customerScope = scopeList.find((x) => x.startsWith('customer_id'));
110
- const id = customerScope?.split(':')[1];
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 id || '';
60
+ return this.getSelf({}, reqCtx);
113
61
  }
114
- }
62
+ }
@@ -4,4 +4,6 @@ export * from './identity.provider';
4
4
  export * from './inventory.provider';
5
5
  export * from './price.provider';
6
6
  export * from './product.provider';
7
+ export * from './profile.provider';
7
8
  export * from './search.provider';
9
+ export * from './store.provider';
@@ -1,15 +1,18 @@
1
- import {
1
+ import type {
2
2
  Inventory,
3
- InventoryProvider,
4
- InventoryQuery,
5
- Session,
3
+ RequestContext,
6
4
  Cache,
7
- LanguageContext,
5
+ InventoryQueryBySKU,
8
6
  } from '@reactionary/core';
9
- import z from 'zod';
10
- import { CommercetoolsConfiguration } from '../schema/configuration.schema';
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 { InventoryEntry as CTInventory } from '@commercetools/platform-sdk';
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: InventoryQuery,
30
- session: Session
39
+ payload: InventoryQueryBySKU,
40
+ reqCtx: RequestContext
31
41
  ): Promise<T> {
32
- const client = new CommercetoolsClient(this.config).getClient(
33
- session.identity?.token
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: `sku=${payload.sku}`,
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
- return this.parseSingle(remote.body, session);
47
- }
68
+ const result = remote.body.results[0];
48
69
 
49
- protected override parseSingle(_body: unknown, session: Session): T {
50
- const body = _body as CTInventory;
51
- const model = this.newModel();
70
+ const model = this.parseSingle(result, reqCtx);
52
71
 
53
- model.identifier = {
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 { Price, PriceProvider, Cache, Currency, TieredPriceSchema, TieredPrice } from '@reactionary/core';
2
- import type { PriceQueryBySku, Session } from '@reactionary/core';
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 { StandalonePrice as CTPrice } from '@commercetools/platform-sdk';
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
- public getClient(session: Session) {
19
- return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).standalonePrices();
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
- const client = this.getClient(session);
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 matched = response.body.results.filter(x => x.sku === p.sku.key && x.value.currencyCode === session.languageContext.currencyCode);
41
- if (matched && matched.length > 0) {
42
- result.push(this.parseSingle(matched[0], session));
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.getEmptyPriceResult(p.sku.key, session.languageContext.currencyCode ));
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
- session: Session
71
+ reqCtx: RequestContext
55
72
  ): Promise<T> {
56
- const client = this.getClient(session);
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, session: Session): T {
80
- const body = _body as CTPrice;
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
- const base = this.newModel();
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: (body.value.centAmount / 100),
86
- currency: body.value.currencyCode as Currency,
88
+ value: (price.value.centAmount / 100),
89
+ currency: price.value.currencyCode as Currency,
87
90
  };
88
91
 
89
- if (body.tiers && body.tiers.length > 0) {
90
- const p = body.tiers.map(x => {
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, session) },
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
  }