@labdigital/commercetools-mock 2.65.1 → 3.0.0-beta.0

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 (281) hide show
  1. package/README.md +31 -8
  2. package/dist/abstract-BKFcva6S.mjs +1044 -0
  3. package/dist/abstract-BKFcva6S.mjs.map +1 -0
  4. package/dist/config-BcNSzPZz.d.mts +1718 -0
  5. package/dist/index.d.mts +50 -1633
  6. package/dist/index.mjs +3771 -2654
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/storage/sqlite.d.mts +59 -0
  9. package/dist/storage/sqlite.mjs +234 -0
  10. package/dist/storage/sqlite.mjs.map +1 -0
  11. package/package.json +26 -29
  12. package/src/ctMock.ts +125 -136
  13. package/src/helpers.ts +14 -6
  14. package/src/index.ts +5 -0
  15. package/src/lib/masking.ts +4 -5
  16. package/src/lib/product-review-statistics.test.ts +257 -294
  17. package/src/lib/review-statistics.ts +17 -4
  18. package/src/oauth/helpers.ts +7 -4
  19. package/src/oauth/server.test.ts +102 -62
  20. package/src/oauth/server.ts +215 -213
  21. package/src/oauth/store.ts +20 -6
  22. package/src/orderSearch.ts +3 -3
  23. package/src/product-projection-search.ts +38 -20
  24. package/src/product-search-availability.test.ts +31 -52
  25. package/src/product-search.ts +19 -10
  26. package/src/projectAPI.ts +6 -22
  27. package/src/repositories/abstract.ts +182 -48
  28. package/src/repositories/as-associate.test.ts +19 -19
  29. package/src/repositories/associate-role.ts +12 -23
  30. package/src/repositories/attribute-group.test.ts +23 -23
  31. package/src/repositories/attribute-group.ts +6 -4
  32. package/src/repositories/business-unit.test.ts +63 -57
  33. package/src/repositories/business-unit.ts +107 -55
  34. package/src/repositories/cart/actions.ts +96 -65
  35. package/src/repositories/cart/helpers.ts +15 -11
  36. package/src/repositories/cart/index.test.ts +136 -30
  37. package/src/repositories/cart/index.ts +76 -59
  38. package/src/repositories/cart-discount/actions.ts +12 -44
  39. package/src/repositories/cart-discount/index.ts +20 -8
  40. package/src/repositories/category/actions.ts +27 -27
  41. package/src/repositories/category/index.test.ts +13 -9
  42. package/src/repositories/category/index.ts +40 -23
  43. package/src/repositories/channel.test.ts +53 -51
  44. package/src/repositories/channel.ts +12 -22
  45. package/src/repositories/custom-object.ts +34 -25
  46. package/src/repositories/customer/actions.ts +47 -25
  47. package/src/repositories/customer/index.test.ts +11 -11
  48. package/src/repositories/customer/index.ts +65 -35
  49. package/src/repositories/customer-group.test.ts +44 -42
  50. package/src/repositories/customer-group.ts +12 -22
  51. package/src/repositories/discount-code/actions.ts +3 -19
  52. package/src/repositories/discount-code/index.ts +9 -4
  53. package/src/repositories/discount-group/index.ts +8 -3
  54. package/src/repositories/extension.test.ts +27 -27
  55. package/src/repositories/extension.ts +10 -5
  56. package/src/repositories/helpers.ts +126 -47
  57. package/src/repositories/inventory-entry/actions.ts +3 -24
  58. package/src/repositories/inventory-entry/index.ts +19 -11
  59. package/src/repositories/my-customer.ts +13 -12
  60. package/src/repositories/my-order.ts +5 -2
  61. package/src/repositories/order/actions.ts +89 -56
  62. package/src/repositories/order/index.test.ts +36 -31
  63. package/src/repositories/order/index.ts +83 -49
  64. package/src/repositories/order-edit.ts +8 -3
  65. package/src/repositories/payment/actions.ts +64 -44
  66. package/src/repositories/payment/helpers.ts +3 -3
  67. package/src/repositories/payment/index.ts +28 -12
  68. package/src/repositories/product/actions.ts +133 -98
  69. package/src/repositories/product/helpers.ts +29 -16
  70. package/src/repositories/product/index.ts +42 -25
  71. package/src/repositories/product-discount.ts +6 -4
  72. package/src/repositories/product-projection.ts +41 -21
  73. package/src/repositories/product-selection.ts +8 -15
  74. package/src/repositories/product-tailoring.ts +22 -3
  75. package/src/repositories/product-type.ts +45 -4
  76. package/src/repositories/project.ts +57 -13
  77. package/src/repositories/quote/actions.ts +5 -28
  78. package/src/repositories/quote/index.ts +29 -6
  79. package/src/repositories/quote-request/actions.ts +5 -28
  80. package/src/repositories/quote-request/index.test.ts +3 -3
  81. package/src/repositories/quote-request/index.ts +31 -11
  82. package/src/repositories/quote-staged/actions.ts +5 -28
  83. package/src/repositories/quote-staged/index.ts +22 -8
  84. package/src/repositories/recurrence-policy/index.ts +6 -4
  85. package/src/repositories/recurring-order/actions.ts +7 -32
  86. package/src/repositories/recurring-order/index.ts +8 -6
  87. package/src/repositories/review.test.ts +147 -142
  88. package/src/repositories/review.ts +31 -37
  89. package/src/repositories/shipping-method/actions.ts +11 -28
  90. package/src/repositories/shipping-method/index.ts +26 -15
  91. package/src/repositories/shopping-list/actions.ts +21 -31
  92. package/src/repositories/shopping-list/index.ts +44 -22
  93. package/src/repositories/standalone-price.ts +6 -4
  94. package/src/repositories/state.ts +15 -9
  95. package/src/repositories/store.ts +21 -32
  96. package/src/repositories/subscription.test.ts +22 -22
  97. package/src/repositories/subscription.ts +8 -3
  98. package/src/repositories/tax-category/index.ts +8 -3
  99. package/src/repositories/type/actions.ts +21 -3
  100. package/src/repositories/type/index.ts +5 -3
  101. package/src/repositories/zone.test.ts +112 -77
  102. package/src/repositories/zone.ts +5 -3
  103. package/src/schemas/generated/associate-role.ts +13 -0
  104. package/src/schemas/generated/attribute-group.ts +12 -0
  105. package/src/schemas/generated/business-unit.ts +38 -0
  106. package/src/schemas/generated/cart-discount.ts +33 -0
  107. package/src/schemas/generated/cart.ts +61 -0
  108. package/src/schemas/generated/category.ts +25 -0
  109. package/src/schemas/generated/channel.ts +21 -0
  110. package/src/schemas/generated/common.ts +1372 -0
  111. package/src/schemas/generated/custom-object.ts +11 -0
  112. package/src/schemas/generated/customer-group.ts +11 -0
  113. package/src/schemas/generated/customer.ts +47 -0
  114. package/src/schemas/generated/discount-code.ts +25 -0
  115. package/src/schemas/generated/discount-group.ts +13 -0
  116. package/src/schemas/generated/extension.ts +15 -0
  117. package/src/schemas/generated/index.ts +42 -0
  118. package/src/schemas/generated/inventory-entry.ts +20 -0
  119. package/src/schemas/generated/my-quote-request.ts +10 -0
  120. package/src/schemas/generated/order-edit.ts +18 -0
  121. package/src/schemas/generated/order-from-cart.ts +25 -0
  122. package/src/schemas/generated/payment.ts +30 -0
  123. package/src/schemas/generated/product-discount.ts +20 -0
  124. package/src/schemas/generated/product-selection.ts +18 -0
  125. package/src/schemas/generated/product-tailoring.ts +26 -0
  126. package/src/schemas/generated/product-type.ts +12 -0
  127. package/src/schemas/generated/product.ts +37 -0
  128. package/src/schemas/generated/quote-request.ts +19 -0
  129. package/src/schemas/generated/quote.ts +18 -0
  130. package/src/schemas/generated/recurrence-policy.ts +15 -0
  131. package/src/schemas/generated/recurring-order.ts +19 -0
  132. package/src/schemas/generated/review.ts +24 -0
  133. package/src/schemas/generated/shipping-method.ts +24 -0
  134. package/src/schemas/generated/shopping-list.ts +28 -0
  135. package/src/schemas/generated/staged-quote.ts +18 -0
  136. package/src/schemas/generated/standalone-price.ts +32 -0
  137. package/src/schemas/generated/state.ts +20 -0
  138. package/src/schemas/generated/store.ts +23 -0
  139. package/src/schemas/generated/subscription.ts +20 -0
  140. package/src/schemas/generated/tax-category.ts +12 -0
  141. package/src/schemas/generated/type.ts +17 -0
  142. package/src/schemas/generated/zone.ts +12 -0
  143. package/src/schemas/update-request.ts +3 -5
  144. package/src/server.ts +32 -4
  145. package/src/services/abstract.ts +207 -101
  146. package/src/services/as-associate-cart.test.ts +28 -36
  147. package/src/services/as-associate-cart.ts +15 -12
  148. package/src/services/as-associate-order.test.ts +33 -40
  149. package/src/services/as-associate-order.ts +15 -12
  150. package/src/services/as-associate-quote-request.ts +15 -12
  151. package/src/services/as-associate-shopping-list.test.ts +25 -35
  152. package/src/services/as-associate-shopping-list.ts +15 -12
  153. package/src/services/as-associate.test.ts +21 -15
  154. package/src/services/as-associate.ts +23 -22
  155. package/src/services/associate-roles.test.ts +16 -22
  156. package/src/services/associate-roles.ts +2 -2
  157. package/src/services/attribute-group.test.ts +40 -44
  158. package/src/services/attribute-group.ts +2 -2
  159. package/src/services/business-units.test.ts +227 -163
  160. package/src/services/business-units.ts +2 -2
  161. package/src/services/cart-discount.test.ts +253 -187
  162. package/src/services/cart-discount.ts +2 -2
  163. package/src/services/cart.test.ts +833 -832
  164. package/src/services/cart.ts +31 -12
  165. package/src/services/category.test.ts +208 -130
  166. package/src/services/category.ts +2 -2
  167. package/src/services/channel.test.ts +39 -44
  168. package/src/services/channel.ts +2 -2
  169. package/src/services/custom-object.test.ts +103 -79
  170. package/src/services/custom-object.ts +106 -38
  171. package/src/services/customer-group.test.ts +39 -44
  172. package/src/services/customer-group.ts +2 -2
  173. package/src/services/customer.test.ts +357 -292
  174. package/src/services/customer.ts +70 -23
  175. package/src/services/discount-code.test.ts +57 -68
  176. package/src/services/discount-code.ts +2 -2
  177. package/src/services/discount-group.test.ts +111 -134
  178. package/src/services/discount-group.ts +2 -2
  179. package/src/services/draft-validation.test.ts +255 -0
  180. package/src/services/extension.test.ts +39 -44
  181. package/src/services/extension.ts +2 -2
  182. package/src/services/inventory-entry.test.ts +106 -87
  183. package/src/services/inventory-entry.ts +2 -2
  184. package/src/services/my-business-unit.test.ts +82 -112
  185. package/src/services/my-business-unit.ts +25 -19
  186. package/src/services/my-cart.test.ts +46 -41
  187. package/src/services/my-cart.ts +32 -28
  188. package/src/services/my-customer.test.ts +153 -88
  189. package/src/services/my-customer.ts +130 -61
  190. package/src/services/my-order.ts +15 -12
  191. package/src/services/my-payment.test.ts +30 -24
  192. package/src/services/my-payment.ts +2 -2
  193. package/src/services/my-shopping-list.ts +2 -2
  194. package/src/services/order.test.ts +332 -276
  195. package/src/services/order.ts +45 -27
  196. package/src/services/payment.test.ts +31 -29
  197. package/src/services/payment.ts +2 -2
  198. package/src/services/product-discount.test.ts +39 -46
  199. package/src/services/product-discount.ts +2 -2
  200. package/src/services/product-projection.test.ts +176 -166
  201. package/src/services/product-projection.ts +31 -15
  202. package/src/services/product-selection.test.ts +17 -9
  203. package/src/services/product-selection.ts +2 -2
  204. package/src/services/product-type.test.ts +80 -21
  205. package/src/services/product-type.ts +2 -2
  206. package/src/services/product.test.ts +569 -534
  207. package/src/services/product.ts +14 -7
  208. package/src/services/project.test.ts +22 -12
  209. package/src/services/project.ts +28 -13
  210. package/src/services/quote-request.test.ts +36 -39
  211. package/src/services/quote-request.ts +2 -2
  212. package/src/services/quote-staged.ts +2 -2
  213. package/src/services/quote.ts +2 -2
  214. package/src/services/recurrence-policy.test.ts +114 -139
  215. package/src/services/recurrence-policy.ts +2 -2
  216. package/src/services/recurring-order.test.ts +149 -194
  217. package/src/services/recurring-order.ts +2 -2
  218. package/src/services/reviews.test.ts +127 -106
  219. package/src/services/reviews.ts +2 -2
  220. package/src/services/shipping-method.test.ts +96 -125
  221. package/src/services/shipping-method.ts +24 -12
  222. package/src/services/shopping-list.test.ts +183 -141
  223. package/src/services/shopping-list.ts +2 -2
  224. package/src/services/standalone-price.test.ts +60 -46
  225. package/src/services/standalone-price.ts +2 -2
  226. package/src/services/state.test.ts +20 -25
  227. package/src/services/state.ts +2 -2
  228. package/src/services/store.test.ts +26 -45
  229. package/src/services/store.ts +2 -2
  230. package/src/services/subscription.test.ts +39 -44
  231. package/src/services/subscription.ts +2 -2
  232. package/src/services/tax-category.test.ts +33 -36
  233. package/src/services/tax-category.ts +2 -2
  234. package/src/services/type.test.ts +45 -44
  235. package/src/services/type.ts +2 -2
  236. package/src/services/zone.test.ts +40 -44
  237. package/src/services/zone.ts +2 -2
  238. package/src/shipping.ts +41 -11
  239. package/src/storage/abstract.ts +248 -17
  240. package/src/storage/in-memory.ts +147 -290
  241. package/src/storage/sqlite.ts +429 -0
  242. package/src/storage/storage-map.ts +75 -0
  243. package/src/storage/storage.test-helpers.ts +97 -0
  244. package/src/storage/storage.test.ts +802 -0
  245. package/src/testing/associate-role.ts +28 -0
  246. package/src/testing/attribute-group.ts +27 -0
  247. package/src/testing/business-unit.ts +9 -8
  248. package/src/testing/cart-discount.ts +34 -0
  249. package/src/testing/cart.ts +20 -0
  250. package/src/testing/category.ts +25 -0
  251. package/src/testing/channel.ts +23 -0
  252. package/src/testing/custom-object.ts +27 -0
  253. package/src/testing/customer-group.ts +26 -0
  254. package/src/testing/customer.ts +36 -33
  255. package/src/testing/discount-code.ts +29 -0
  256. package/src/testing/discount-group.ts +27 -0
  257. package/src/testing/extension.ts +32 -0
  258. package/src/testing/index.ts +33 -0
  259. package/src/testing/inventory-entry.ts +26 -0
  260. package/src/testing/order.ts +27 -0
  261. package/src/testing/payment.ts +23 -0
  262. package/src/testing/product-discount.ts +33 -0
  263. package/src/testing/product-selection.ts +28 -0
  264. package/src/testing/product-type.ts +27 -0
  265. package/src/testing/product.ts +38 -0
  266. package/src/testing/quote-request.ts +29 -0
  267. package/src/testing/recurrence-policy.ts +33 -0
  268. package/src/testing/recurring-order.ts +32 -0
  269. package/src/testing/review.ts +24 -0
  270. package/src/testing/shipping-method.ts +31 -0
  271. package/src/testing/shopping-list.ts +25 -0
  272. package/src/testing/standalone-price.ts +31 -0
  273. package/src/testing/state.ts +21 -0
  274. package/src/testing/store.ts +26 -0
  275. package/src/testing/subscription.ts +38 -0
  276. package/src/testing/tax-category.ts +27 -0
  277. package/src/testing/type.ts +9 -6
  278. package/src/testing/zone.ts +22 -0
  279. package/src/validate.test.ts +122 -0
  280. package/src/validate.ts +78 -7
  281. package/src/.env +0 -0
@@ -1,12 +1,17 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
2
  import type {
3
3
  BaseResource,
4
+ CustomFields,
5
+ FieldContainer,
4
6
  InvalidInputError,
7
+ InvalidOperationError,
5
8
  Project,
6
9
  QueryParam,
7
10
  ResourceNotFoundError,
11
+ TypeResourceIdentifier,
8
12
  UpdateAction,
9
13
  } from "@commercetools/platform-sdk";
14
+ import type { z } from "zod";
10
15
  import type { Config } from "#src/config.ts";
11
16
  import { CommercetoolsError } from "#src/exceptions.ts";
12
17
  import { cloneObject } from "../helpers.ts";
@@ -18,6 +23,7 @@ import type {
18
23
  Writable,
19
24
  } from "./../types.ts";
20
25
  import { checkConcurrentModification } from "./errors.ts";
26
+ import { createCustomFields } from "./helpers.ts";
21
27
 
22
28
  export type QueryParams = {
23
29
  expand?: string[];
@@ -36,6 +42,7 @@ export type GetParams = {
36
42
  export type RepositoryContext = {
37
43
  projectKey: string;
38
44
  storeKey?: string;
45
+ clientId?: string;
39
46
  };
40
47
 
41
48
  export abstract class AbstractRepository<R extends BaseResource | Project> {
@@ -50,27 +57,36 @@ export abstract class AbstractRepository<R extends BaseResource | Project> {
50
57
  this._storage = config.storage;
51
58
  }
52
59
 
53
- abstract saveNew({ projectKey }: RepositoryContext, resource: R): void;
60
+ async saveNew({ projectKey }: RepositoryContext, resource: R): Promise<R> {
61
+ throw new Error("Not implemented");
62
+ }
54
63
 
55
- abstract saveUpdate(
64
+ async saveUpdate(
56
65
  { projectKey }: RepositoryContext,
57
66
  version: number,
58
67
  resource: R,
59
- ): void;
68
+ ): Promise<R> {
69
+ throw new Error("Not implemented");
70
+ }
60
71
 
61
- abstract postProcessResource(context: RepositoryContext, resource: any): any;
72
+ async postProcessResource(
73
+ context: RepositoryContext,
74
+ resource: any,
75
+ ): Promise<any> {
76
+ throw new Error("Not implemented");
77
+ }
62
78
 
63
- processUpdateActions(
79
+ async processUpdateActions(
64
80
  context: RepositoryContext,
65
81
  resource: R,
66
82
  version: number,
67
83
  actions: UpdateAction[],
68
- ): R {
84
+ ): Promise<R> {
69
85
  if (!this.actions) {
70
86
  throw new Error("No actions defined");
71
87
  }
72
88
 
73
- const updatedResource = this.actions.apply(
89
+ const updatedResource = await this.actions.apply(
74
90
  context,
75
91
  resource,
76
92
  version,
@@ -80,10 +96,10 @@ export abstract class AbstractRepository<R extends BaseResource | Project> {
80
96
  // If all actions succeeded we write the new version
81
97
  // to the storage.
82
98
  if (resource.version !== updatedResource.version) {
83
- this.saveUpdate(context, version, updatedResource);
99
+ await this.saveUpdate(context, version, updatedResource);
84
100
  }
85
101
 
86
- const result = this.postProcessResource(context, updatedResource);
102
+ const result = await this.postProcessResource(context, updatedResource);
87
103
  if (!result) {
88
104
  throw new Error("invalid post process action");
89
105
  }
@@ -96,82 +112,120 @@ export abstract class AbstractResourceRepository<
96
112
  > extends AbstractRepository<ResourceMap[T]> {
97
113
  protected _typeId: T;
98
114
 
115
+ /**
116
+ * Optional Zod schema for validating creation drafts.
117
+ * When set and strict mode is enabled, the service layer will
118
+ * validate incoming request bodies against this schema before
119
+ * passing them to create().
120
+ */
121
+ draftSchema?: z.ZodType;
122
+
99
123
  constructor(typeId: T, config: Config) {
100
124
  super(config);
101
125
  this._typeId = typeId;
102
126
  }
103
127
 
104
- abstract create(context: RepositoryContext, draft: any): ResourceMap[T];
128
+ /**
129
+ * Whether strict validation is enabled.
130
+ */
131
+ get strict(): boolean {
132
+ return this.config.strict;
133
+ }
134
+
135
+ abstract create(
136
+ context: RepositoryContext,
137
+ draft: any,
138
+ ): Promise<ResourceMap[T]>;
105
139
 
106
140
  protected getTypeId(): T {
107
141
  return this._typeId;
108
142
  }
109
143
 
110
- delete(
144
+ /**
145
+ * Apply expand clauses to a resource without re-fetching from storage.
146
+ */
147
+ async expand(
148
+ context: RepositoryContext,
149
+ resource: ResourceMap[T],
150
+ expand: string[] | undefined,
151
+ ): Promise<ResourceMap[T]> {
152
+ if (!expand || expand.length === 0) {
153
+ return resource;
154
+ }
155
+ return this._storage.expand(context.projectKey, resource, expand);
156
+ }
157
+
158
+ async delete(
111
159
  context: RepositoryContext,
112
160
  id: string,
113
161
  params: GetParams = {},
114
- ): ResourceMap[T] | null {
115
- const resource = this._storage.delete(
162
+ ): Promise<ResourceMap[T] | null> {
163
+ const resource = await this._storage.delete(
116
164
  context.projectKey,
117
165
  this.getTypeId(),
118
166
  id,
119
167
  params,
120
168
  );
121
169
  return resource
122
- ? this.postProcessResource(context, resource, params)
170
+ ? await this.postProcessResource(context, resource, params)
123
171
  : null;
124
172
  }
125
173
 
126
- get(
174
+ async get(
127
175
  context: RepositoryContext,
128
176
  id: string,
129
177
  params: GetParams = {},
130
- ): ResourceMap[T] | null {
131
- const resource = this._storage.get(
178
+ ): Promise<ResourceMap[T] | null> {
179
+ const resource = await this._storage.get(
132
180
  context.projectKey,
133
181
  this.getTypeId(),
134
182
  id,
135
183
  params,
136
184
  );
137
185
  return resource
138
- ? this.postProcessResource(context, resource, params)
186
+ ? await this.postProcessResource(context, resource, params)
139
187
  : null;
140
188
  }
141
189
 
142
- getByKey(
190
+ async getByKey(
143
191
  context: RepositoryContext,
144
192
  key: string,
145
193
  params: GetParams = {},
146
- ): ResourceMap[T] | null {
147
- const resource = this._storage.getByKey(
194
+ ): Promise<ResourceMap[T] | null> {
195
+ const resource = await this._storage.getByKey(
148
196
  context.projectKey,
149
197
  this.getTypeId(),
150
198
  key,
151
199
  params,
152
200
  );
153
201
  return resource
154
- ? this.postProcessResource(context, resource, params)
202
+ ? await this.postProcessResource(context, resource, params)
155
203
  : null;
156
204
  }
157
205
 
158
- postProcessResource(
206
+ async postProcessResource(
159
207
  context: RepositoryContext,
160
208
  resource: ResourceMap[T],
161
209
  params?: GetParams,
162
- ): ResourceMap[T] {
210
+ ): Promise<ResourceMap[T]> {
163
211
  return resource;
164
212
  }
165
213
 
166
- query(context: RepositoryContext, params: QueryParams = {}) {
167
- const result = this._storage.query(context.projectKey, this.getTypeId(), {
168
- ...params,
169
- });
214
+ async query(context: RepositoryContext, params: QueryParams = {}) {
215
+ const result = await this._storage.query(
216
+ context.projectKey,
217
+ this.getTypeId(),
218
+ {
219
+ ...params,
220
+ },
221
+ );
170
222
 
171
- const data = result.results.map((r) =>
172
- this.postProcessResource(context, r as ResourceMap[T], {
173
- expand: params.expand,
174
- }),
223
+ const data = await Promise.all(
224
+ result.results.map((r) =>
225
+ this.postProcessResource(context, r as ResourceMap[T], {
226
+ expand: params.expand,
227
+ }),
228
+ ),
175
229
  );
176
230
  return {
177
231
  ...result,
@@ -179,25 +233,25 @@ export abstract class AbstractResourceRepository<
179
233
  };
180
234
  }
181
235
 
182
- saveNew(
236
+ async saveNew(
183
237
  context: RepositoryContext,
184
238
  resource: ShallowWritable<ResourceMap[T]>,
185
- ): ResourceMap[T] {
239
+ ): Promise<ResourceMap[T]> {
186
240
  resource.version = 1;
187
- return this._storage.add(
241
+ return await this._storage.add(
188
242
  context.projectKey,
189
243
  this.getTypeId(),
190
244
  resource as any,
191
245
  );
192
246
  }
193
247
 
194
- saveUpdate(
248
+ async saveUpdate(
195
249
  context: RepositoryContext,
196
250
  version: number,
197
251
  resource: ShallowWritable<ResourceMap[T]>,
198
- ) {
252
+ ): Promise<ResourceMap[T]> {
199
253
  // Check if the resource still exists.
200
- const current = this._storage.get(
254
+ const current = await this._storage.get(
201
255
  context.projectKey,
202
256
  this.getTypeId(),
203
257
  resource.id,
@@ -218,10 +272,18 @@ export abstract class AbstractResourceRepository<
218
272
  throw new Error("Internal error: no changes to save");
219
273
  }
220
274
  resource.lastModifiedAt = new Date().toISOString();
275
+ (resource as any).lastModifiedBy = {
276
+ clientId: context.clientId ?? "",
277
+ isPlatformClient: false,
278
+ };
221
279
 
222
- this._storage.add(context.projectKey, this.getTypeId(), resource as any);
280
+ await this._storage.add(
281
+ context.projectKey,
282
+ this.getTypeId(),
283
+ resource as any,
284
+ );
223
285
 
224
- return resource;
286
+ return resource as ResourceMap[T];
225
287
  }
226
288
  }
227
289
 
@@ -229,7 +291,7 @@ type UpdateActionHandlerMethod<A, T> = (
229
291
  context: RepositoryContext,
230
292
  resource: Writable<A>,
231
293
  action: T,
232
- ) => void;
294
+ ) => void | Promise<void>;
233
295
 
234
296
  export type UpdateHandlerInterface<
235
297
  A extends BaseResource | Project,
@@ -239,19 +301,85 @@ export type UpdateHandlerInterface<
239
301
  };
240
302
 
241
303
  export class AbstractUpdateHandler {
242
- constructor(protected _storage: AbstractStorage) {
304
+ _storage: AbstractStorage;
305
+ constructor(_storage: AbstractStorage) {
243
306
  if (!_storage) {
244
307
  throw new Error("No storage provided");
245
308
  }
246
309
  this._storage = _storage;
247
310
  }
248
311
 
249
- apply<R extends BaseResource | Project>(
312
+ /**
313
+ * Shared implementation for setCustomField update actions.
314
+ *
315
+ * Throws InvalidOperation if the resource has no custom type set.
316
+ * When `value` is `null`, the field is removed; otherwise it is set.
317
+ */
318
+ protected _setCustomFieldValues(
319
+ resource: { custom?: CustomFields },
320
+ { name, value }: { name: string; value?: unknown },
321
+ ): void {
322
+ if (!resource.custom) {
323
+ throw new CommercetoolsError<InvalidOperationError>(
324
+ {
325
+ code: "InvalidOperation",
326
+ message: "Resource has no custom type",
327
+ },
328
+ 400,
329
+ );
330
+ }
331
+ if (value === null) {
332
+ if (!(name in resource.custom.fields)) {
333
+ throw new CommercetoolsError<InvalidOperationError>(
334
+ {
335
+ code: "InvalidOperation",
336
+ message: `Cannot remove custom field ${name} because it does not exist.`,
337
+ },
338
+ 400,
339
+ );
340
+ }
341
+ delete resource.custom.fields[name];
342
+ } else {
343
+ resource.custom.fields[name] = value;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Shared implementation for setCustomType update actions.
349
+ *
350
+ * When `type` is provided, resolves the type reference and sets the
351
+ * custom fields on the resource. When `type` is not provided, removes
352
+ * the custom fields entirely.
353
+ */
354
+ protected async _setCustomType(
355
+ context: RepositoryContext,
356
+ resource: { custom?: CustomFields },
357
+ {
358
+ type,
359
+ fields,
360
+ }: { type?: TypeResourceIdentifier; fields?: FieldContainer },
361
+ ): Promise<void> {
362
+ if (type) {
363
+ resource.custom = await createCustomFields(
364
+ { type, fields },
365
+ context.projectKey,
366
+ this._storage,
367
+ );
368
+ } else {
369
+ resource.custom = undefined;
370
+ }
371
+ }
372
+
373
+ async apply<R extends BaseResource | Project>(
250
374
  context: RepositoryContext,
251
375
  resource: R,
252
376
  version: number,
253
377
  actions: UpdateAction[],
254
- ): R {
378
+ ): Promise<R> {
379
+ // We need a separate working copy because the caller (processUpdateActions)
380
+ // compares resource.version with updatedResource.version to detect changes.
381
+ // The resource parameter is already a clone from storage, but we still need
382
+ // two distinct references.
255
383
  const updatedResource = cloneObject(resource) as ShallowWritable<R>;
256
384
  const identifier = (resource as BaseResource).id
257
385
  ? (resource as BaseResource).id
@@ -277,13 +405,19 @@ export class AbstractUpdateHandler {
277
405
  const updateFunc = this[action.action].bind(this);
278
406
 
279
407
  if (!updateFunc) {
280
- throw new Error(
281
- `No mock implemented for update action ${action.action}`,
408
+ throw new CommercetoolsError<InvalidInputError>(
409
+ {
410
+ code: "InvalidInput",
411
+ message: `No mock implemented for update action ${action.action}`,
412
+ },
413
+ 400,
282
414
  );
283
415
  }
284
416
 
285
- const beforeUpdate = cloneObject(resource);
286
- updateFunc(context, updatedResource, action);
417
+ // Snapshot the current state before applying the action so we can
418
+ // detect whether it actually changed anything.
419
+ const beforeUpdate = cloneObject(updatedResource);
420
+ await updateFunc(context, updatedResource, action);
287
421
 
288
422
  // Check if the object is updated. We need to increase the version of
289
423
  // an object per action which does an actual modification.
@@ -14,7 +14,7 @@ describe("As Associate Repositories", () => {
14
14
  const storage = new InMemoryStorage();
15
15
  const config: Config = { storage, strict: false };
16
16
 
17
- test("AsAssociateCartRepository can create and retrieve carts", () => {
17
+ test("AsAssociateCartRepository can create and retrieve carts", async () => {
18
18
  const repository = new AsAssociateCartRepository(config);
19
19
  const ctx = { projectKey: "test-project" };
20
20
 
@@ -26,23 +26,23 @@ describe("As Associate Repositories", () => {
26
26
  taxCalculationMode: "UnitPriceLevel" as const,
27
27
  };
28
28
 
29
- const cart = repository.create(ctx, cartDraft);
29
+ const cart = await repository.create(ctx, cartDraft);
30
30
  expect(cart.id).toBeDefined();
31
31
  expect(cart.version).toBe(1);
32
32
  expect(cart.totalPrice.currencyCode).toBe("EUR");
33
33
 
34
34
  // Test query
35
- const result = repository.query(ctx);
35
+ const result = await repository.query(ctx);
36
36
  expect(result.count).toBe(1);
37
37
  expect(result.results[0].id).toBe(cart.id);
38
38
 
39
39
  // Test get
40
- const retrieved = repository.get(ctx, cart.id);
40
+ const retrieved = await repository.get(ctx, cart.id);
41
41
  expect(retrieved).toBeDefined();
42
42
  expect(retrieved?.id).toBe(cart.id);
43
43
  });
44
44
 
45
- test("AsAssociateOrderRepository can create and retrieve orders", () => {
45
+ test("AsAssociateOrderRepository can create and retrieve orders", async () => {
46
46
  const repository = new AsAssociateOrderRepository(config);
47
47
  const ctx = { projectKey: "test-project" };
48
48
 
@@ -55,7 +55,7 @@ describe("As Associate Repositories", () => {
55
55
  taxRoundingMode: "HalfEven" as const,
56
56
  taxCalculationMode: "UnitPriceLevel" as const,
57
57
  };
58
- const cart = cartRepository.create(ctx, cartDraft);
58
+ const cart = await cartRepository.create(ctx, cartDraft);
59
59
 
60
60
  const orderDraft = {
61
61
  cart: {
@@ -65,29 +65,29 @@ describe("As Associate Repositories", () => {
65
65
  version: cart.version,
66
66
  };
67
67
 
68
- const order = repository.create(ctx, orderDraft);
68
+ const order = await repository.create(ctx, orderDraft);
69
69
  expect(order.id).toBeDefined();
70
70
  expect(order.version).toBe(1);
71
71
  expect(order.cart?.id).toBe(cart.id);
72
72
 
73
73
  // Test query
74
- const result = repository.query(ctx);
74
+ const result = await repository.query(ctx);
75
75
  expect(result.count).toBe(1);
76
76
  expect(result.results[0].id).toBe(order.id);
77
77
 
78
78
  // Test get
79
- const retrieved = repository.get(ctx, order.id);
79
+ const retrieved = await repository.get(ctx, order.id);
80
80
  expect(retrieved).toBeDefined();
81
81
  expect(retrieved?.id).toBe(order.id);
82
82
  });
83
83
 
84
- test("AsAssociateQuoteRequestRepository can create and retrieve quote requests", () => {
84
+ test("AsAssociateQuoteRequestRepository can create and retrieve quote requests", async () => {
85
85
  const repository = new AsAssociateQuoteRequestRepository(config);
86
86
  const ctx = { projectKey: "test-project" };
87
87
 
88
88
  // Create a customer using the customer repository
89
89
  const customerRepository = new CustomerRepository(config);
90
- const customer = customerRepository.create(ctx, {
90
+ const customer = await customerRepository.create(ctx, {
91
91
  email: "test@example.com",
92
92
  password: "password123",
93
93
  firstName: "John",
@@ -100,7 +100,7 @@ describe("As Associate Repositories", () => {
100
100
  currency: "EUR",
101
101
  customerId: customer.id,
102
102
  };
103
- const cart = cartRepository.create(ctx, cartDraft);
103
+ const cart = await cartRepository.create(ctx, cartDraft);
104
104
 
105
105
  const quoteRequestDraft = {
106
106
  cart: {
@@ -110,23 +110,23 @@ describe("As Associate Repositories", () => {
110
110
  cartVersion: cart.version,
111
111
  };
112
112
 
113
- const quoteRequest = repository.create(ctx, quoteRequestDraft);
113
+ const quoteRequest = await repository.create(ctx, quoteRequestDraft);
114
114
  expect(quoteRequest.id).toBeDefined();
115
115
  expect(quoteRequest.version).toBe(1);
116
116
  expect(quoteRequest.cart?.id).toBe(cart.id);
117
117
 
118
118
  // Test query
119
- const result = repository.query(ctx);
119
+ const result = await repository.query(ctx);
120
120
  expect(result.count).toBe(1);
121
121
  expect(result.results[0].id).toBe(quoteRequest.id);
122
122
 
123
123
  // Test get
124
- const retrieved = repository.get(ctx, quoteRequest.id);
124
+ const retrieved = await repository.get(ctx, quoteRequest.id);
125
125
  expect(retrieved).toBeDefined();
126
126
  expect(retrieved?.id).toBe(quoteRequest.id);
127
127
  });
128
128
 
129
- test("AsAssociateShoppingListRepository can create and retrieve shopping lists", () => {
129
+ test("AsAssociateShoppingListRepository can create and retrieve shopping lists", async () => {
130
130
  const repository = new AsAssociateShoppingListRepository(config);
131
131
  const ctx = { projectKey: "test-project" };
132
132
 
@@ -134,17 +134,17 @@ describe("As Associate Repositories", () => {
134
134
  name: { "en-US": "My Shopping List" },
135
135
  };
136
136
 
137
- const shoppingList = repository.create(ctx, shoppingListDraft);
137
+ const shoppingList = await repository.create(ctx, shoppingListDraft);
138
138
  expect(shoppingList.id).toBeDefined();
139
139
  expect(shoppingList.version).toBe(1);
140
140
 
141
141
  // Test query
142
- const result = repository.query(ctx);
142
+ const result = await repository.query(ctx);
143
143
  expect(result.count).toBe(1);
144
144
  expect(result.results[0].id).toBe(shoppingList.id);
145
145
 
146
146
  // Test get
147
- const retrieved = repository.get(ctx, shoppingList.id);
147
+ const retrieved = await repository.get(ctx, shoppingList.id);
148
148
  expect(retrieved).toBeDefined();
149
149
  expect(retrieved?.id).toBe(shoppingList.id);
150
150
  });
@@ -11,6 +11,7 @@ import type {
11
11
  AssociateRoleUpdateAction,
12
12
  } from "@commercetools/platform-sdk";
13
13
  import type { Config } from "#src/config.ts";
14
+ import { AssociateRoleDraftSchema } from "#src/schemas/generated/associate-role.ts";
14
15
  import { getBaseResourceProperties } from "../helpers.ts";
15
16
  import type { Writable } from "../types.ts";
16
17
  import type { UpdateHandlerInterface } from "./abstract.ts";
@@ -25,23 +26,27 @@ export class AssociateRoleRepository extends AbstractResourceRepository<"associa
25
26
  constructor(config: Config) {
26
27
  super("associate-role", config);
27
28
  this.actions = new AssociateRoleUpdateHandler(this._storage);
29
+ this.draftSchema = AssociateRoleDraftSchema;
28
30
  }
29
31
 
30
- create(context: RepositoryContext, draft: AssociateRoleDraft): AssociateRole {
32
+ async create(
33
+ context: RepositoryContext,
34
+ draft: AssociateRoleDraft,
35
+ ): Promise<AssociateRole> {
31
36
  const resource: AssociateRole = {
32
- ...getBaseResourceProperties(),
37
+ ...getBaseResourceProperties(context.clientId),
33
38
  key: draft.key,
34
39
  name: draft.name,
35
40
  buyerAssignable: draft.buyerAssignable || false,
36
41
  permissions: draft.permissions || [],
37
- custom: createCustomFields(
42
+ custom: await createCustomFields(
38
43
  draft.custom,
39
44
  context.projectKey,
40
45
  this._storage,
41
46
  ),
42
47
  };
43
48
 
44
- return this.saveNew(context, resource);
49
+ return await this.saveNew(context, resource);
45
50
  }
46
51
  }
47
52
 
@@ -95,31 +100,15 @@ class AssociateRoleUpdateHandler
95
100
  resource: Writable<AssociateRole>,
96
101
  { name, value }: AssociateRoleSetCustomFieldAction,
97
102
  ) {
98
- if (!resource.custom) {
99
- return;
100
- }
101
-
102
- if (value === null) {
103
- delete resource.custom.fields[name];
104
- } else {
105
- resource.custom.fields[name] = value;
106
- }
103
+ this._setCustomFieldValues(resource, { name, value });
107
104
  }
108
105
 
109
- setCustomType(
106
+ async setCustomType(
110
107
  context: RepositoryContext,
111
108
  resource: Writable<AssociateRole>,
112
109
  { type, fields }: AssociateRoleSetCustomTypeAction,
113
110
  ) {
114
- if (type) {
115
- resource.custom = createCustomFields(
116
- { type, fields },
117
- context.projectKey,
118
- this._storage,
119
- );
120
- } else {
121
- resource.custom = undefined;
122
- }
111
+ await this._setCustomType(context, resource, { type, fields });
123
112
  }
124
113
 
125
114
  setName(