@configura/web-api 3.0.0-alpha.1 → 3.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/CatalogueAPI.d.ts +68 -1
  2. package/dist/CatalogueAPI.js +184 -219
  3. package/dist/CfgMeasure.js +1 -2
  4. package/dist/CfgProduct.js +131 -159
  5. package/dist/io/CfgIOManager.js +2 -11
  6. package/dist/io/CfgIOProdConfConnector.js +14 -25
  7. package/dist/io/CfgObservableStateManager.js +1 -1
  8. package/dist/productConfiguration/CfgFeature.d.ts +5 -0
  9. package/dist/productConfiguration/CfgFeature.js +62 -52
  10. package/dist/productConfiguration/CfgOption.d.ts +8 -0
  11. package/dist/productConfiguration/CfgOption.js +67 -72
  12. package/dist/productConfiguration/CfgProductConfiguration.js +22 -34
  13. package/dist/productConfiguration/productParamsGenerator.js +43 -57
  14. package/dist/productLoader.js +2 -13
  15. package/dist/syncGroups/SyncGroupsHandler.js +60 -83
  16. package/dist/syncGroups/SyncGroupsPathHelper.js +5 -5
  17. package/dist/syncGroups/SyncGroupsState.js +4 -5
  18. package/dist/syncGroups/SyncGroupsTransaction.js +259 -303
  19. package/dist/tasks/TaskHandler.js +53 -64
  20. package/dist/tests/testData/dummyProductForTest.js +4 -1
  21. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +18 -21
  22. package/dist/tests/testData/testDataCachedGetProduct.js +20 -20
  23. package/dist/tests/testData/testDataCachedPostValidate.js +6 -15
  24. package/dist/tests/testData/testDataProductAggregatedPrice.js +21 -21
  25. package/dist/tests/testData/testDataUpcharge.js +31 -22
  26. package/dist/utilitiesCatalogueData.js +21 -9
  27. package/dist/utilitiesCataloguePermission.js +5 -2
  28. package/dist/utilitiesConfiguration.js +21 -23
  29. package/package.json +3 -3
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import { assert } from "@configura/web-utilities";
11
2
  import { CfgProductBubbleMode } from "../CfgProduct.js";
12
3
  import { SelectionType, } from "../productConfiguration/CfgFeature.js";
@@ -57,11 +48,9 @@ export class SyncGroupsTransaction {
57
48
  this.target = target;
58
49
  this.initial = initial;
59
50
  }
60
- static make(syncState, updateMode, product, productLoader, assumeNoStartState) {
61
- return __awaiter(this, void 0, void 0, function* () {
62
- const t = new this(syncState, updateMode, productLoader, product, yield product.clone(), assumeNoStartState ? undefined : yield product.clone());
63
- return t;
64
- });
51
+ static async make(syncState, updateMode, product, productLoader, assumeNoStartState) {
52
+ const t = new this(syncState, updateMode, productLoader, product, await product.clone(), assumeNoStartState ? undefined : await product.clone());
53
+ return t;
65
54
  }
66
55
  /************************************************************************
67
56
  * Public API (intentionally limited)
@@ -79,59 +68,55 @@ export class SyncGroupsTransaction {
79
68
  * but as this method will hand over to applyProduct if it updates the SyncState it can cause
80
69
  * the Features to change. And we need to pass that information back to know when to stop.
81
70
  */
82
- applyRootProduct() {
83
- return __awaiter(this, void 0, void 0, function* () {
84
- const productWithInitial = { target: this.target, initial: this.initial };
85
- if (!(yield this.applyProduct(productWithInitial))) {
86
- // All done!
87
- return false;
88
- }
89
- return yield this.updateRootProduct(undefined);
90
- });
71
+ async applyRootProduct() {
72
+ const productWithInitial = { target: this.target, initial: this.initial };
73
+ if (!(await this.applyProduct(productWithInitial))) {
74
+ // All done!
75
+ return false;
76
+ }
77
+ return await this.updateRootProduct(undefined);
91
78
  }
92
79
  /**
93
80
  * This is the entry point for an active (often user) selection of an option.
94
81
  */
95
- selectOption(optionPath, on) {
96
- return __awaiter(this, void 0, void 0, function* () {
97
- const option = SyncGroupsPathHelper.getOptionFromPath(optionPath, this.target);
98
- const feature = option.parent;
99
- let change = false;
100
- let productToValidate = undefined;
101
- // At this point in handling sync groups we go back to normal selecting.
102
- //
103
- // This is because a normal selection can bring features into scope which should push to or
104
- // (if the SyncGroup is defined) pull from the Sync State.
105
- //
106
- // We use BubbleSelected (without validation) as we can delay the validation until after we
107
- // have applied the SyncGroup (if any).
108
- if (yield feature.selectOption(option, on, ProductConfigurationBubbleMode.BubbleSelected)) {
109
- productToValidate = feature.parentProduct;
110
- change = true;
111
- switch (feature.selectionType) {
112
- case SelectionType.SelectOne:
113
- this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
114
- break;
115
- case SelectionType.SelectMany:
116
- this.affectedSelectManyOptions.add(option);
117
- break;
118
- default:
119
- throw new Error("Should not happen");
120
- }
121
- }
82
+ async selectOption(optionPath, on) {
83
+ const option = SyncGroupsPathHelper.getOptionFromPath(optionPath, this.target);
84
+ const feature = option.parent;
85
+ let change = false;
86
+ let productToValidate = undefined;
87
+ // At this point in handling sync groups we go back to normal selecting.
88
+ //
89
+ // This is because a normal selection can bring features into scope which should push to or
90
+ // (if the SyncGroup is defined) pull from the Sync State.
91
+ //
92
+ // We use BubbleSelected (without validation) as we can delay the validation until after we
93
+ // have applied the SyncGroup (if any).
94
+ if (await feature.selectOption(option, on, ProductConfigurationBubbleMode.BubbleSelected)) {
95
+ productToValidate = feature.parentProduct;
96
+ change = true;
122
97
  switch (feature.selectionType) {
123
98
  case SelectionType.SelectOne:
124
- this.applySelectOneFeature(feature, true, false);
99
+ this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
125
100
  break;
126
101
  case SelectionType.SelectMany:
127
- this.applySelectManyOption(option, true);
102
+ this.affectedSelectManyOptions.add(option);
128
103
  break;
104
+ default:
105
+ throw new Error("Should not happen");
129
106
  }
130
- if (yield this.updateRootProduct(productToValidate)) {
131
- change = true;
132
- }
133
- return change;
134
- });
107
+ }
108
+ switch (feature.selectionType) {
109
+ case SelectionType.SelectOne:
110
+ this.applySelectOneFeature(feature, true, false);
111
+ break;
112
+ case SelectionType.SelectMany:
113
+ this.applySelectManyOption(option, true);
114
+ break;
115
+ }
116
+ if (await this.updateRootProduct(productToValidate)) {
117
+ change = true;
118
+ }
119
+ return change;
135
120
  }
136
121
  /**
137
122
  * Overwrites the original Product and SyncGroupState (supplied when creating the Transaction)
@@ -139,12 +124,10 @@ export class SyncGroupsTransaction {
139
124
  *
140
125
  * @throws error if the transaction has already been closed.
141
126
  */
142
- commit() {
143
- return __awaiter(this, void 0, void 0, function* () {
144
- assert(this.isClosed === false, "Trying to commit a closed Transaction");
145
- this.originalSyncState.copyFrom(this.syncState);
146
- yield this.originalProduct.copyFrom(this.target, false, this.productLoader);
147
- });
127
+ async commit() {
128
+ assert(this.isClosed === false, "Trying to commit a closed Transaction");
129
+ this.originalSyncState.copyFrom(this.syncState);
130
+ await this.originalProduct.copyFrom(this.target, false, this.productLoader);
148
131
  }
149
132
  /************************************************************************
150
133
  * Updating things with the Transaction's SyncState
@@ -153,81 +136,73 @@ export class SyncGroupsTransaction {
153
136
  * Apply current sync groups on those who wants to listen until there is no more to settle.
154
137
  * @returns true if at least one Feature changed selected Option
155
138
  */
156
- updateRootProduct(productToValidate) {
157
- return __awaiter(this, void 0, void 0, function* () {
158
- const productsToValidate = new Set();
159
- if (productToValidate !== undefined) {
160
- productsToValidate.add(productToValidate);
161
- }
162
- const productWithInitial = { target: this.target, initial: this.initial };
163
- yield this.updateProduct(productWithInitial, productsToValidate);
164
- if (productsToValidate.size === 0) {
165
- // All have been settled, continue to pullPhase
166
- return yield this.applyRootProduct();
167
- }
168
- if (this.isClosed) {
169
- // We could exit in more places when the transaction has been aborted, but as
170
- // revalidate is really the only thing that could be expensive time consuming we only
171
- // check here.
172
- return false;
173
- }
174
- const promises = [];
175
- for (const product of productsToValidate) {
176
- promises.push(product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true));
177
- }
178
- const revalidationResults = yield Promise.all(promises);
179
- if (revalidationResults.every((r) => !r)) {
180
- this.close();
181
- return false;
182
- }
183
- // Apply over again, to settle deeper down. Our theory is that the front of "settled" will
184
- // move deeper and deeper into the tree.
185
- yield this.updateRootProduct(undefined);
186
- // We had productsToValidate, and so there must have been a change.
187
- return true;
188
- });
139
+ async updateRootProduct(productToValidate) {
140
+ const productsToValidate = new Set();
141
+ if (productToValidate !== undefined) {
142
+ productsToValidate.add(productToValidate);
143
+ }
144
+ const productWithInitial = { target: this.target, initial: this.initial };
145
+ await this.updateProduct(productWithInitial, productsToValidate);
146
+ if (productsToValidate.size === 0) {
147
+ // All have been settled, continue to pullPhase
148
+ return await this.applyRootProduct();
149
+ }
150
+ if (this.isClosed) {
151
+ // We could exit in more places when the transaction has been aborted, but as
152
+ // revalidate is really the only thing that could be expensive time consuming we only
153
+ // check here.
154
+ return false;
155
+ }
156
+ const promises = [];
157
+ for (const product of productsToValidate) {
158
+ promises.push(product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true));
159
+ }
160
+ const revalidationResults = await Promise.all(promises);
161
+ if (revalidationResults.every((r) => !r)) {
162
+ this.close();
163
+ return false;
164
+ }
165
+ // Apply over again, to settle deeper down. Our theory is that the front of "settled" will
166
+ // move deeper and deeper into the tree.
167
+ await this.updateRootProduct(undefined);
168
+ // We had productsToValidate, and so there must have been a change.
169
+ return true;
189
170
  }
190
171
  /**
191
172
  * Applies the SyncState to the Product and it's AdditionalProducts (sub-products).
192
173
  * @param productsToValidate To this all products that will need validation are added.
193
174
  */
194
- updateProduct(product, productsToValidate) {
195
- return __awaiter(this, void 0, void 0, function* () {
196
- const promises = [];
197
- promises.push(this.updateFeatures(getFeaturesFromProduct(product), productsToValidate));
198
- for (const additionalProduct of getAdditionalProducts(product)) {
199
- promises.push(this.updateProduct(additionalProduct, productsToValidate));
200
- }
201
- yield Promise.all(promises);
202
- });
175
+ async updateProduct(product, productsToValidate) {
176
+ const promises = [];
177
+ promises.push(this.updateFeatures(getFeaturesFromProduct(product), productsToValidate));
178
+ for (const additionalProduct of getAdditionalProducts(product)) {
179
+ promises.push(this.updateProduct(additionalProduct, productsToValidate));
180
+ }
181
+ await Promise.all(promises);
203
182
  }
204
183
  /**
205
184
  * Applies the SyncState to an array of Features.
206
185
  * @param productsToValidate To this all products that will need validation are added.
207
186
  */
208
- updateFeatures(features, productsToValidate) {
209
- return __awaiter(this, void 0, void 0, function* () {
210
- yield Promise.all(features.map((feature) => __awaiter(this, void 0, void 0, function* () {
211
- switch (yield this.updateFeature(feature, productsToValidate)) {
212
- case "stop":
213
- return;
214
- case "recurseDown":
215
- yield this.updateOptions(getSelectedOptions(feature), productsToValidate);
216
- return;
217
- }
218
- })));
219
- });
187
+ async updateFeatures(features, productsToValidate) {
188
+ await Promise.all(features.map(async (feature) => {
189
+ switch (await this.updateFeature(feature, productsToValidate)) {
190
+ case "stop":
191
+ return;
192
+ case "recurseDown":
193
+ await this.updateOptions(getSelectedOptions(feature), productsToValidate);
194
+ return;
195
+ }
196
+ }));
220
197
  }
221
198
  /**
222
199
  * Applies the SyncState to an array of Options.
223
200
  * @param productsToValidate To this all products that will need validation are added.
224
201
  */
225
- updateOptions(options, productsToValidate) {
226
- return __awaiter(this, void 0, void 0, function* () {
227
- yield Promise.all(options.map((option) => {
228
- return this.updateFeatures(getFeaturesFromOption(option), productsToValidate);
229
- }));
230
- });
202
+ async updateOptions(options, productsToValidate) {
203
+ await Promise.all(options.map((option) => {
204
+ return this.updateFeatures(getFeaturesFromOption(option), productsToValidate);
205
+ }));
231
206
  }
232
207
  /**
233
208
  * Applies the SyncState to a Feature
@@ -235,30 +210,28 @@ export class SyncGroupsTransaction {
235
210
  * @returns Whether we shall stop recursing down (because we are in a state which we
236
211
  * expect to be resolved later), we shall continue recursing down.
237
212
  */
238
- updateFeature(featureWithInitial, productsToValidate) {
239
- return __awaiter(this, void 0, void 0, function* () {
240
- const feature = featureWithInitial.target;
241
- const syncCode = feature.getSyncCode("pull");
242
- if (syncCode === undefined) {
243
- // Just continue down, features with no sync groups are always settled
213
+ async updateFeature(featureWithInitial, productsToValidate) {
214
+ const feature = featureWithInitial.target;
215
+ const syncCode = feature.getSyncCode("pull");
216
+ if (syncCode === undefined) {
217
+ // Just continue down, features with no sync groups are always settled
218
+ return "recurseDown";
219
+ }
220
+ if (syncCode === false) {
221
+ // Here we only handle pull. Initializing those missing in SyncMap
222
+ // we do later. We wait until we have settled as much as we can with what
223
+ // we have now, to increase chances of the values being written to the
224
+ // SyncState being the right ones.
225
+ return "stop";
226
+ }
227
+ switch (feature.selectionType) {
228
+ case SelectionType.Group:
244
229
  return "recurseDown";
245
- }
246
- if (syncCode === false) {
247
- // Here we only handle pull. Initializing those missing in SyncMap
248
- // we do later. We wait until we have settled as much as we can with what
249
- // we have now, to increase chances of the values being written to the
250
- // SyncState being the right ones.
251
- return "stop";
252
- }
253
- switch (feature.selectionType) {
254
- case SelectionType.Group:
255
- return "recurseDown";
256
- case SelectionType.SelectOne:
257
- return yield this.updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate);
258
- case SelectionType.SelectMany:
259
- return yield this.updateSelectManyFeature(syncCode, featureWithInitial, productsToValidate);
260
- }
261
- });
230
+ case SelectionType.SelectOne:
231
+ return await this.updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate);
232
+ case SelectionType.SelectMany:
233
+ return await this.updateSelectManyFeature(syncCode, featureWithInitial, productsToValidate);
234
+ }
262
235
  }
263
236
  /**
264
237
  * Decides if the SyncState can be applied to the SelectOne Feature, and then changes
@@ -268,48 +241,46 @@ export class SyncGroupsTransaction {
268
241
  * @returns Whether we shall stop recursing down (because we are in a state which we
269
242
  * expect to be resolved later), we shall continue recursing down.
270
243
  */
271
- updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate) {
272
- return __awaiter(this, void 0, void 0, function* () {
273
- const feature = featureWithInitial.target;
274
- const featureDidJustComeIntoScope = featureWithInitial.initial === undefined;
275
- const hasBeenAffectedLevel = this.affectedSelectOneFeatures.get(feature);
276
- const syncGroupHasBeenUpdated = this.affectedSelectOneSyncGroups.has(syncCode);
277
- if (hasBeenAffectedLevel === AffectedLevel.Set ||
278
- (!syncGroupHasBeenUpdated && hasBeenAffectedLevel === AffectedLevel.Initialized)) {
279
- // A feature can change value for two reasons:
280
- // 1. The feature did just come into scope and loads from the sync group to set its "defaults"
281
- // 2. The sync group has updated with a new value
282
- // The rule is that in one sync group transaction the feature value is allowed to be updated
283
- // once or twice. Once for initialization and once for new sync group value. But they have to
284
- // happen in the right order.
285
- return "recurseDown";
286
- }
287
- if (!featureDidJustComeIntoScope && !syncGroupHasBeenUpdated) {
288
- return "recurseDown";
289
- }
290
- const currentSyncGroupValue = this.syncState.getForSelectOne(syncCode);
291
- if (currentSyncGroupValue === undefined) {
292
- // This branch will have to be settled later. Don't go further down here.
293
- return "stop";
294
- }
295
- const selectedOption = feature.selectedOptions[0];
296
- if (selectedOption !== undefined && selectedOption.code === currentSyncGroupValue) {
297
- // Settled, continue
298
- return "recurseDown";
299
- }
300
- const optionToSelect = feature.options.find((o) => o.code === currentSyncGroupValue);
301
- if (optionToSelect === undefined) {
302
- // No option which can be selected (keep current value and recurse down)
303
- return "recurseDown";
304
- }
305
- // Update the value. Validations are collected so that we do not send more than necessary.
306
- // Do not recurse further as we will change the state and so what is selected now won't be
307
- // selected then.
308
- yield feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
309
- this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
310
- productsToValidate.add(feature.parentProduct);
244
+ async updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate) {
245
+ const feature = featureWithInitial.target;
246
+ const featureDidJustComeIntoScope = featureWithInitial.initial === undefined;
247
+ const hasBeenAffectedLevel = this.affectedSelectOneFeatures.get(feature);
248
+ const syncGroupHasBeenUpdated = this.affectedSelectOneSyncGroups.has(syncCode);
249
+ if (hasBeenAffectedLevel === AffectedLevel.Set ||
250
+ (!syncGroupHasBeenUpdated && hasBeenAffectedLevel === AffectedLevel.Initialized)) {
251
+ // A feature can change value for two reasons:
252
+ // 1. The feature did just come into scope and loads from the sync group to set its "defaults"
253
+ // 2. The sync group has updated with a new value
254
+ // The rule is that in one sync group transaction the feature value is allowed to be updated
255
+ // once or twice. Once for initialization and once for new sync group value. But they have to
256
+ // happen in the right order.
257
+ return "recurseDown";
258
+ }
259
+ if (!featureDidJustComeIntoScope && !syncGroupHasBeenUpdated) {
260
+ return "recurseDown";
261
+ }
262
+ const currentSyncGroupValue = this.syncState.getForSelectOne(syncCode);
263
+ if (currentSyncGroupValue === undefined) {
264
+ // This branch will have to be settled later. Don't go further down here.
311
265
  return "stop";
312
- });
266
+ }
267
+ const selectedOption = feature.selectedOptions[0];
268
+ if (selectedOption !== undefined && selectedOption.code === currentSyncGroupValue) {
269
+ // Settled, continue
270
+ return "recurseDown";
271
+ }
272
+ const optionToSelect = feature.options.find((o) => o.code === currentSyncGroupValue);
273
+ if (optionToSelect === undefined) {
274
+ // No option which can be selected (keep current value and recurse down)
275
+ return "recurseDown";
276
+ }
277
+ // Update the value. Validations are collected so that we do not send more than necessary.
278
+ // Do not recurse further as we will change the state and so what is selected now won't be
279
+ // selected then.
280
+ await feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
281
+ this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
282
+ productsToValidate.add(feature.parentProduct);
283
+ return "stop";
313
284
  }
314
285
  /**
315
286
  * Decides if the SyncState can be applied to Options in the SelectMany Feature, and
@@ -318,22 +289,20 @@ export class SyncGroupsTransaction {
318
289
  * @param productsToValidate To this all products that will need validation are added
319
290
  * @returns Always "stop" as recursion is handled internally. Return for consistency.
320
291
  */
321
- updateSelectManyFeature(syncCode, feature, productsToValidate) {
322
- return __awaiter(this, void 0, void 0, function* () {
323
- const optionsToContinueDown = [];
324
- for (const option of getOptions(feature)) {
325
- switch (yield this.updateSelectManyOption(syncCode, option, productsToValidate)) {
326
- case "stop":
327
- continue;
328
- case "recurseDown":
329
- optionsToContinueDown.push(option);
330
- continue;
331
- }
292
+ async updateSelectManyFeature(syncCode, feature, productsToValidate) {
293
+ const optionsToContinueDown = [];
294
+ for (const option of getOptions(feature)) {
295
+ switch (await this.updateSelectManyOption(syncCode, option, productsToValidate)) {
296
+ case "stop":
297
+ continue;
298
+ case "recurseDown":
299
+ optionsToContinueDown.push(option);
300
+ continue;
332
301
  }
333
- yield this.updateOptions(optionsToContinueDown, productsToValidate);
334
- // stop as the method above handles the recursion down
335
- return "stop";
336
- });
302
+ }
303
+ await this.updateOptions(optionsToContinueDown, productsToValidate);
304
+ // stop as the method above handles the recursion down
305
+ return "stop";
337
306
  }
338
307
  /**
339
308
  * Decides if the SyncState can be applied to the SelectMany Option, and then changes
@@ -343,108 +312,100 @@ export class SyncGroupsTransaction {
343
312
  * @returns Whether we shall stop recursing down (because we are in a state which we
344
313
  * expect to be resolved later), we shall continue recursing down.
345
314
  */
346
- updateSelectManyOption(syncCode, optionWithInitial, productsToValidate) {
347
- return __awaiter(this, void 0, void 0, function* () {
348
- const option = optionWithInitial.target;
349
- const featureDidJustComeIntoScope = optionWithInitial.initial === undefined;
350
- const optionSelected = option.selected;
351
- const recurseOrStopIfNoChange = optionSelected ? "recurseDown" : "stop";
352
- if (this.affectedSelectManyOptions.has(option)) {
353
- // This option has already changed selection once for this transaction. We expect
354
- // this to happen very rarely, as the algorithm should settle the selection tree
355
- // further and further out. Nevertheless, this safeguard is needed to avoid infinite
356
- // looping if for example the server would return the same data over and over.
357
- return recurseOrStopIfNoChange;
358
- }
359
- if (!featureDidJustComeIntoScope &&
360
- !this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
361
- return "recurseDown";
362
- }
363
- const syncGroupValueForOption = this.syncState.getForSelectMany(syncCode, option.code);
364
- if (syncGroupValueForOption === undefined) {
365
- // The sync group has no opinion on this. If it is selected, just continue down.
366
- return recurseOrStopIfNoChange;
367
- }
368
- if (syncGroupValueForOption === optionSelected) {
369
- // We are in sync for this option
370
- return recurseOrStopIfNoChange;
371
- }
372
- const feature = option.parent;
373
- // Update the value. Validations are collected so that we do not
374
- // send more than necessary. Do not recurse further as we will change
375
- // the state and so what is selected now won't be selected then.
376
- yield feature.selectOption(option, syncGroupValueForOption, ProductConfigurationBubbleMode.ToRoot);
377
- this.affectedSelectManyOptions.add(option);
378
- productsToValidate.add(feature.parentProduct);
379
- return "stop";
380
- });
315
+ async updateSelectManyOption(syncCode, optionWithInitial, productsToValidate) {
316
+ const option = optionWithInitial.target;
317
+ const featureDidJustComeIntoScope = optionWithInitial.initial === undefined;
318
+ const optionSelected = option.selected;
319
+ const recurseOrStopIfNoChange = optionSelected ? "recurseDown" : "stop";
320
+ if (this.affectedSelectManyOptions.has(option)) {
321
+ // This option has already changed selection once for this transaction. We expect
322
+ // this to happen very rarely, as the algorithm should settle the selection tree
323
+ // further and further out. Nevertheless, this safeguard is needed to avoid infinite
324
+ // looping if for example the server would return the same data over and over.
325
+ return recurseOrStopIfNoChange;
326
+ }
327
+ if (!featureDidJustComeIntoScope &&
328
+ !this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
329
+ return "recurseDown";
330
+ }
331
+ const syncGroupValueForOption = this.syncState.getForSelectMany(syncCode, option.code);
332
+ if (syncGroupValueForOption === undefined) {
333
+ // The sync group has no opinion on this. If it is selected, just continue down.
334
+ return recurseOrStopIfNoChange;
335
+ }
336
+ if (syncGroupValueForOption === optionSelected) {
337
+ // We are in sync for this option
338
+ return recurseOrStopIfNoChange;
339
+ }
340
+ const feature = option.parent;
341
+ // Update the value. Validations are collected so that we do not
342
+ // send more than necessary. Do not recurse further as we will change
343
+ // the state and so what is selected now won't be selected then.
344
+ await feature.selectOption(option, syncGroupValueForOption, ProductConfigurationBubbleMode.ToRoot);
345
+ this.affectedSelectManyOptions.add(option);
346
+ productsToValidate.add(feature.parentProduct);
347
+ return "stop";
381
348
  }
382
349
  /************************************************************************
383
350
  * Applying things to the Transaction's SyncState
384
351
  ************************************************************************/
385
- applyProduct(product) {
386
- return __awaiter(this, void 0, void 0, function* () {
387
- let change = false;
388
- if (yield this.applyFeatures(getFeaturesFromProduct(product))) {
352
+ async applyProduct(product) {
353
+ let change = false;
354
+ if (await this.applyFeatures(getFeaturesFromProduct(product))) {
355
+ if (this.updateMode === SyncGroupsApplyMode.Strict) {
356
+ return true;
357
+ }
358
+ change = true;
359
+ }
360
+ for (const additionalProduct of getAdditionalProducts(product)) {
361
+ if (await this.applyProduct(additionalProduct)) {
389
362
  if (this.updateMode === SyncGroupsApplyMode.Strict) {
390
363
  return true;
391
364
  }
392
365
  change = true;
393
366
  }
394
- for (const additionalProduct of getAdditionalProducts(product)) {
395
- if (yield this.applyProduct(additionalProduct)) {
396
- if (this.updateMode === SyncGroupsApplyMode.Strict) {
397
- return true;
367
+ }
368
+ return change;
369
+ }
370
+ async applyFeatures(featureWithInitials) {
371
+ let change = false;
372
+ for (const featureWithInitial of featureWithInitials) {
373
+ const feature = featureWithInitial.target;
374
+ switch (feature.selectionType) {
375
+ case SelectionType.SelectOne:
376
+ if (this.applySelectOneFeature(feature, false, featureWithInitial.initial === undefined)) {
377
+ change = true;
398
378
  }
399
- change = true;
400
- }
379
+ break;
380
+ case SelectionType.SelectMany:
381
+ if (this.applySelectManyFeature(featureWithInitial)) {
382
+ change = true;
383
+ }
384
+ break;
401
385
  }
402
- return change;
403
- });
404
- }
405
- applyFeatures(featureWithInitials) {
406
- return __awaiter(this, void 0, void 0, function* () {
407
- let change = false;
408
- for (const featureWithInitial of featureWithInitials) {
409
- const feature = featureWithInitial.target;
410
- switch (feature.selectionType) {
411
- case SelectionType.SelectOne:
412
- if (this.applySelectOneFeature(feature, false, featureWithInitial.initial === undefined)) {
413
- change = true;
414
- }
415
- break;
416
- case SelectionType.SelectMany:
417
- if (this.applySelectManyFeature(featureWithInitial)) {
418
- change = true;
419
- }
420
- break;
421
- }
422
- if (change && this.updateMode === SyncGroupsApplyMode.Strict) {
386
+ if (change && this.updateMode === SyncGroupsApplyMode.Strict) {
387
+ return true;
388
+ }
389
+ if (await this.applyOptions(getSelectedOptions(featureWithInitial))) {
390
+ if (this.updateMode === SyncGroupsApplyMode.Strict) {
423
391
  return true;
424
392
  }
425
- if (yield this.applyOptions(getSelectedOptions(featureWithInitial))) {
426
- if (this.updateMode === SyncGroupsApplyMode.Strict) {
427
- return true;
428
- }
429
- change = true;
430
- }
393
+ change = true;
431
394
  }
432
- return change;
433
- });
395
+ }
396
+ return change;
434
397
  }
435
- applyOptions(options) {
436
- return __awaiter(this, void 0, void 0, function* () {
437
- let change = false;
438
- for (const option of options) {
439
- if (yield this.applyFeatures(getFeaturesFromOption(option))) {
440
- if (this.updateMode === SyncGroupsApplyMode.Strict) {
441
- return true;
442
- }
443
- change = true;
398
+ async applyOptions(options) {
399
+ let change = false;
400
+ for (const option of options) {
401
+ if (await this.applyFeatures(getFeaturesFromOption(option))) {
402
+ if (this.updateMode === SyncGroupsApplyMode.Strict) {
403
+ return true;
444
404
  }
405
+ change = true;
445
406
  }
446
- return change;
447
- });
407
+ }
408
+ return change;
448
409
  }
449
410
  applySelectOneFeature(feature, userInitiated, featureDidJustComeIntoScope) {
450
411
  const selectionType = feature.selectionType;
@@ -534,8 +495,7 @@ export class SyncGroupsTransaction {
534
495
  forSyncCode.add(option.code);
535
496
  }
536
497
  hasSyncGroupAffectedForSelectMany(syncCode, option) {
537
- var _a;
538
- return ((_a = this.affectedSelectManySyncGroupsAndOptions.get(syncCode)) === null || _a === void 0 ? void 0 : _a.has(option.code)) === true;
498
+ return this.affectedSelectManySyncGroupsAndOptions.get(syncCode)?.has(option.code) === true;
539
499
  }
540
500
  }
541
501
  function getAdditionalProducts(product) {
@@ -544,46 +504,42 @@ function getAdditionalProducts(product) {
544
504
  .filter((p) => p.selected)
545
505
  .map((childTarget) => {
546
506
  const refKey = childTarget.refKey;
547
- const childInitial = initial === null || initial === void 0 ? void 0 : initial.additionalProducts.find((p) => refKey === p.refKey);
507
+ const childInitial = initial?.additionalProducts.find((p) => refKey === p.refKey);
548
508
  return {
549
509
  target: childTarget._internal,
550
- initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
510
+ initial: childInitial?._internal,
551
511
  };
552
512
  });
553
513
  }
554
514
  function pairOptions(targets, initials) {
555
515
  return targets.map((childTarget) => {
556
516
  const key = childTarget.key;
557
- const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((o) => key === o.key);
517
+ const childInitial = initials?.find((o) => key === o.key);
558
518
  return {
559
519
  target: childTarget._internal,
560
- initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
520
+ initial: childInitial?._internal,
561
521
  };
562
522
  });
563
523
  }
564
524
  function getSelectedOptions(feature) {
565
- var _a;
566
- return pairOptions(feature.target.selectedOptions, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.selectedOptions);
525
+ return pairOptions(feature.target.selectedOptions, feature.initial?.selectedOptions);
567
526
  }
568
527
  function getOptions(feature) {
569
- var _a;
570
- return pairOptions(feature.target.options, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.options);
528
+ return pairOptions(feature.target.options, feature.initial?.options);
571
529
  }
572
530
  function pairFeatures(targets, initials) {
573
531
  return targets.map((childTarget) => {
574
532
  const code = childTarget.code;
575
- const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((f) => code === f.code);
533
+ const childInitial = initials?.find((f) => code === f.code);
576
534
  return {
577
535
  target: childTarget._internal,
578
- initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
536
+ initial: childInitial?._internal,
579
537
  };
580
538
  });
581
539
  }
582
540
  function getFeaturesFromProduct(product) {
583
- var _a;
584
- return pairFeatures(product.target.configuration.features, (_a = product.initial) === null || _a === void 0 ? void 0 : _a.configuration.features);
541
+ return pairFeatures(product.target.configuration.features, product.initial?.configuration.features);
585
542
  }
586
543
  function getFeaturesFromOption(option) {
587
- var _a;
588
- return pairFeatures(option.target.features, (_a = option.initial) === null || _a === void 0 ? void 0 : _a.getFeaturesOutsideSync());
544
+ return pairFeatures(option.target.features, option.initial?.getFeaturesOutsideSync());
589
545
  }