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

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 (31) 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.d.ts +22 -3
  5. package/dist/CfgProduct.js +172 -162
  6. package/dist/io/CfgIOManager.js +2 -11
  7. package/dist/io/CfgIOProdConfConnector.js +14 -25
  8. package/dist/io/CfgObservableStateManager.js +1 -1
  9. package/dist/productConfiguration/CfgFeature.d.ts +5 -0
  10. package/dist/productConfiguration/CfgFeature.js +68 -53
  11. package/dist/productConfiguration/CfgOption.d.ts +8 -0
  12. package/dist/productConfiguration/CfgOption.js +68 -73
  13. package/dist/productConfiguration/CfgProductConfiguration.js +22 -34
  14. package/dist/productConfiguration/productParamsGenerator.js +43 -57
  15. package/dist/productLoader.js +2 -13
  16. package/dist/syncGroups/SyncGroupsHandler.js +60 -83
  17. package/dist/syncGroups/SyncGroupsPathHelper.js +5 -5
  18. package/dist/syncGroups/SyncGroupsState.js +4 -5
  19. package/dist/syncGroups/SyncGroupsTransaction.d.ts +19 -1
  20. package/dist/syncGroups/SyncGroupsTransaction.js +352 -303
  21. package/dist/tasks/TaskHandler.js +53 -64
  22. package/dist/tests/testData/dummyProductForTest.js +4 -1
  23. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +18 -21
  24. package/dist/tests/testData/testDataCachedGetProduct.js +20 -20
  25. package/dist/tests/testData/testDataCachedPostValidate.js +6 -15
  26. package/dist/tests/testData/testDataProductAggregatedPrice.js +21 -21
  27. package/dist/tests/testData/testDataUpcharge.js +31 -22
  28. package/dist/utilitiesCatalogueData.js +21 -9
  29. package/dist/utilitiesCataloguePermission.js +5 -2
  30. package/dist/utilitiesConfiguration.js +21 -23
  31. package/package.json +3 -3
@@ -1,28 +1,19 @@
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
  var _a;
11
2
  import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
12
3
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
13
- import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
4
+ import { ProductConfigurationBubbleMode, } from "./productConfiguration/CfgOption.js";
14
5
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
15
6
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
16
7
  import { wrapWithCache } from "./productLoader.js";
17
8
  import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
9
+ import { generateSelectionDiff } from "./syncGroups/SyncGroupsTransaction.js";
18
10
  import { compareCfgProductData, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
19
11
  import { CfgProdConfParts, convertDtoProductConfToV1, isAdditionalProductConfiguration, isProductConf, } from "./utilitiesConfiguration.js";
20
12
  function completeSettings(incompleteSettings) {
21
- var _b, _c;
22
13
  return {
23
- strictSelectOneSelectionCount: (_b = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSelectOneSelectionCount) !== null && _b !== void 0 ? _b : false,
24
- syncGroupsApplyMode: incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.syncGroupsApplyMode,
25
- disableMatchOnDescription: (_c = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.disableMatchOnDescription) !== null && _c !== void 0 ? _c : false,
14
+ strictSelectOneSelectionCount: incompleteSettings?.strictSelectOneSelectionCount ?? false,
15
+ syncGroupsApplyMode: incompleteSettings?.syncGroupsApplyMode,
16
+ disableMatchOnDescription: incompleteSettings?.disableMatchOnDescription ?? false,
26
17
  };
27
18
  }
28
19
  /**
@@ -55,7 +46,6 @@ function isDescriptionMatch(l, r) {
55
46
  */
56
47
  export class _CfgProductInternal {
57
48
  constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, rawFeatures, notes, uuid, _rawUnit, _rawProductData, apiSelection, apiConstraints, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
58
- var _b;
59
49
  this._productLoaderRaw = _productLoaderRaw;
60
50
  this.prodParams = prodParams;
61
51
  this.settings = settings;
@@ -70,25 +60,31 @@ export class _CfgProductInternal {
70
60
  this.additionalProducts = [];
71
61
  this._notes = new Map();
72
62
  this.changeObservable = new Observable();
63
+ /**
64
+ * The last diff that was generated during the last validate call.
65
+ *
66
+ * This is used to keep track of the changes that were made to the
67
+ * configuration as a result of apply constraints.
68
+ */
69
+ this._lastDiff = undefined;
73
70
  /** Mark this and its descendants as destroyed and remove all listeners */
74
71
  this.destroy = () => {
75
- var _b;
76
72
  this._destroyed = true;
77
73
  this.changeObservable.stopAllListen();
78
74
  this.configuration.stopAllListenForChange();
79
- for (const additionalProduct of (_b = this.additionalProducts) !== null && _b !== void 0 ? _b : []) {
75
+ for (const additionalProduct of this.additionalProducts ?? []) {
80
76
  additionalProduct.destroy();
81
77
  }
82
78
  };
83
79
  /**
84
80
  * Reset will reset the product to its initial state
85
81
  */
86
- this.reset = () => __awaiter(this, void 0, void 0, function* () {
82
+ this.reset = async () => {
87
83
  if (this._initialClone !== undefined) {
88
- yield this.copyFrom(this._initialClone, true);
84
+ await this.copyFrom(this._initialClone, true);
89
85
  }
90
- });
91
- this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
86
+ };
87
+ this._notifyAllOfChange = async (bubbleMode, committed) => {
92
88
  if (bubbleMode === CfgProductBubbleMode.Stop) {
93
89
  return;
94
90
  }
@@ -96,23 +92,23 @@ export class _CfgProductInternal {
96
92
  const freshRef = CfgProduct._makeNewRefFrom(this);
97
93
  this.changeObservable.notifyAll({ freshRef, committed });
98
94
  if (parent !== undefined) {
99
- yield parent._additionalProductHasChanged(freshRef, bubbleMode === CfgProductBubbleMode.OneLevel
95
+ await parent._additionalProductHasChanged(freshRef, bubbleMode === CfgProductBubbleMode.OneLevel
100
96
  ? CfgProductBubbleMode.Stop
101
97
  : bubbleMode, committed);
102
98
  }
103
- });
99
+ };
104
100
  /** Called when a child (additional product or the configuration) has changed. */
105
- this._childHasChanged = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
101
+ this._childHasChanged = async (bubbleMode, committed) => {
106
102
  if (bubbleMode === CfgProductBubbleMode.ToRootAndBubbleSelected &&
107
103
  this.optional &&
108
104
  !this.selected) {
109
- yield this.setSelected(true, bubbleMode, false);
105
+ await this.setSelected(true, bubbleMode, false);
110
106
  return;
111
107
  }
112
- yield this._notifyAllOfChange(bubbleMode, committed);
113
- });
108
+ await this._notifyAllOfChange(bubbleMode, committed);
109
+ };
114
110
  /** Called by child to tell its parent that it has changed. */
115
- this._additionalProductHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
111
+ this._additionalProductHasChanged = async (freshRef, bubbleMode, committed) => {
116
112
  const i = this.additionalProducts.findIndex((a) => a.isBackedBySame(freshRef));
117
113
  if (i !== -1) {
118
114
  // Child additional product might not be found. This probably means that propagate
@@ -122,39 +118,38 @@ export class _CfgProductInternal {
122
118
  // C no longer is part of the tree. Odd, but fully permitted.
123
119
  this.additionalProducts[i] = freshRef;
124
120
  }
125
- yield this._childHasChanged(bubbleMode, committed);
126
- });
121
+ await this._childHasChanged(bubbleMode, committed);
122
+ };
127
123
  /** Called by the configuration to tell its parent that it has changed. */
128
- this._configurationHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
124
+ this._configurationHasChanged = async (freshRef, bubbleMode, committed) => {
129
125
  this._configuration = freshRef;
130
126
  switch (bubbleMode) {
131
127
  case ProductConfigurationBubbleMode.ValidateAndBubbleSelected:
132
128
  // The revalidate call will continue the bubble
133
- yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw, committed);
129
+ await this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw, committed);
134
130
  return;
135
131
  case ProductConfigurationBubbleMode.BubbleSelected:
136
- yield this._childHasChanged(CfgProductBubbleMode.ToRootAndBubbleSelected, committed);
132
+ await this._childHasChanged(CfgProductBubbleMode.ToRootAndBubbleSelected, committed);
137
133
  return;
138
134
  case ProductConfigurationBubbleMode.Validate:
139
135
  // The revalidate call will continue the bubble
140
- yield this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw, committed);
136
+ await this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw, committed);
141
137
  return;
142
138
  case ProductConfigurationBubbleMode.ToParentProduct:
143
139
  // Do not continue bubble as we have reached the parent CfgProduct
144
140
  return;
145
141
  case ProductConfigurationBubbleMode.OneLevel:
146
- yield this._childHasChanged(CfgProductBubbleMode.OneLevel, committed);
142
+ await this._childHasChanged(CfgProductBubbleMode.OneLevel, committed);
147
143
  return;
148
144
  case ProductConfigurationBubbleMode.Stop:
149
- yield this._childHasChanged(CfgProductBubbleMode.Stop, committed);
145
+ await this._childHasChanged(CfgProductBubbleMode.Stop, committed);
150
146
  return;
151
147
  case ProductConfigurationBubbleMode.ToRoot:
152
- yield this._childHasChanged(CfgProductBubbleMode.ToRoot, committed);
148
+ await this._childHasChanged(CfgProductBubbleMode.ToRoot, committed);
153
149
  return;
154
150
  }
155
- });
151
+ };
156
152
  this.getDtoConf = (include) => {
157
- var _b;
158
153
  const conf = {};
159
154
  const features = this.configuration._internal.getDtoConf((include & CfgProdConfParts.ExtendedData) === CfgProdConfParts.ExtendedData);
160
155
  if (0 < features.length) {
@@ -168,7 +163,7 @@ export class _CfgProductInternal {
168
163
  // so no need to store it for additional products.
169
164
  if ((include & CfgProdConfParts.SyncGroupState) === CfgProdConfParts.SyncGroupState &&
170
165
  this.parent === undefined) {
171
- conf.syncGroupState = (_b = this.syncGroupHandler) === null || _b === void 0 ? void 0 : _b.getCompactSyncGroupState();
166
+ conf.syncGroupState = this.syncGroupHandler?.getCompactSyncGroupState();
172
167
  }
173
168
  const additionalProducts = this.additionalProducts;
174
169
  if (0 < additionalProducts.length) {
@@ -185,7 +180,7 @@ export class _CfgProductInternal {
185
180
  }
186
181
  return conf;
187
182
  };
188
- this.setDtoConf = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
183
+ this.setDtoConf = async (s, doValidate, productLoaderForGroupedLoad) => {
189
184
  // The DtoProdConf format can contain a sync group state, but
190
185
  // not the DtoAdditionalProductConfiguration format. So we apply
191
186
  // any passed state here.
@@ -196,15 +191,11 @@ export class _CfgProductInternal {
196
191
  syncGroupHandler.setCompactSyncGroupState(newSyncGroupState);
197
192
  }
198
193
  }
199
- return yield this.setApiSelection(convertDtoProductConfToV1(s), doValidate, productLoaderForGroupedLoad);
200
- });
201
- this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
202
- return yield this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
203
- });
204
- this.copyFrom = (source, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
205
- return yield this._setApiSelectionWithOtherProduct(convertDtoProductConfToV1(source.getDtoConf(CfgProdConfParts.NoExtra)), doValidate, productLoaderForGroupedLoad, source);
206
- });
207
- this._setApiSelectionWithOtherProduct = (productConfiguration, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
194
+ return await this.setApiSelection(convertDtoProductConfToV1(s), doValidate, productLoaderForGroupedLoad);
195
+ };
196
+ this.setApiSelection = async (s, doValidate, productLoaderForGroupedLoad) => await this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
197
+ this.copyFrom = async (source, doValidate, productLoaderForGroupedLoad) => await this._setApiSelectionWithOtherProduct(convertDtoProductConfToV1(source.getDtoConf(CfgProdConfParts.NoExtra)), doValidate, productLoaderForGroupedLoad, source);
198
+ this._setApiSelectionWithOtherProduct = async (productConfiguration, doValidate, productLoaderForGroupedLoad, sourceProduct) => {
208
199
  // Wrap with cache will make getProduct for this function call use the same server call
209
200
  // for the same product with the same params. Used for getProduct (when a new additional
210
201
  // product is loaded) and postValidate.
@@ -237,16 +228,20 @@ export class _CfgProductInternal {
237
228
  }
238
229
  }
239
230
  }
240
- const configurationChange = yield this.configuration._internal.setApiSelection(productConfiguration.selOptions, sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct._rawProductData.partsData.constrOptions, false);
231
+ const configurationChange = await this.configuration._internal.setApiSelection(productConfiguration.selOptions, sourceProduct?._rawProductData.partsData.constrOptions, false);
241
232
  if (configurationChange) {
242
233
  change = true;
243
234
  }
244
235
  if (this.optional) {
245
- if (yield this.setSelected(productConfiguration.selected !== false, CfgProductBubbleMode.Stop, false)) {
236
+ if (await this.setSelected(productConfiguration.selected !== false, CfgProductBubbleMode.Stop, false)) {
246
237
  change = true;
247
238
  }
248
239
  }
249
- yield this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, productConfiguration);
240
+ if (sourceProduct?._lastDiff) {
241
+ this._lastDiff = sourceProduct._lastDiff;
242
+ change = true;
243
+ }
244
+ await this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, productConfiguration);
250
245
  const apiSelectionAdditionalProducts = productConfiguration.additionalProducts || [];
251
246
  const additionalProducts = this.additionalProducts.slice();
252
247
  const additionalProductsCount = additionalProducts.length;
@@ -254,11 +249,10 @@ export class _CfgProductInternal {
254
249
  if (apiSelectionAdditionalProductsCount !== additionalProductsCount) {
255
250
  throw new Error(`Additional products are not same length. This product: "${this.key}". This product additional products count: ${additionalProductsCount}. Passed apiSelection additional products count: ${apiSelectionAdditionalProductsCount}`);
256
251
  }
257
- const sourceProductAdditionalProducts = sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct.additionalProducts;
252
+ const sourceProductAdditionalProducts = sourceProduct?.additionalProducts;
258
253
  assert(!sourceProductAdditionalProducts ||
259
254
  additionalProductsCount === sourceProductAdditionalProducts.length, `Passed sourceProduct does not have the same number of additional products as this.`);
260
- if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct) => __awaiter(this, void 0, void 0, function* () {
261
- var _c;
255
+ if ((await Promise.all(apiSelectionAdditionalProducts.map(async (apiSelectionAdditionalProduct) => {
262
256
  const refKey = apiSelectionAdditionalProduct.refKey;
263
257
  assertDefined(refKey, "Additional product api configurations must have refKey.");
264
258
  const i = additionalProducts.findIndex((a) => refKey === a.refKey);
@@ -266,25 +260,25 @@ export class _CfgProductInternal {
266
260
  let sourceProductAdditionalProduct = undefined;
267
261
  if (sourceProductAdditionalProducts !== undefined) {
268
262
  sourceProductAdditionalProduct =
269
- (_c = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _c === void 0 ? void 0 : _c._internal;
263
+ sourceProductAdditionalProducts.find((a) => refKey === a.refKey)?._internal;
270
264
  assertDefined(sourceProductAdditionalProduct, "Additional product not found in sourceProduct");
271
265
  }
272
266
  const additionalProduct = additionalProducts.splice(i, 1)[0]; // Splicing like this is okay because this is done synchronous. The setCon. is what is async.
273
- return yield additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
274
- })))).some((b) => b)) {
267
+ return await additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
268
+ }))).some((b) => b)) {
275
269
  change = true;
276
270
  }
277
271
  if (doValidate && configurationChange) {
278
- yield this._revalidate(CfgProductBubbleMode.ToRoot, productLoaderForGroupedLoad, true);
272
+ await this._revalidate(CfgProductBubbleMode.ToRoot, productLoaderForGroupedLoad, true);
279
273
  }
280
274
  else if (change) {
281
275
  // As setApiSelection is done recursively each level takes care of its own notifications
282
276
  // so we only need to bubble one level to notify this and swap out the reference in the
283
277
  // parent.
284
- yield this._notifyAllOfChange(CfgProductBubbleMode.OneLevel, true);
278
+ await this._notifyAllOfChange(CfgProductBubbleMode.OneLevel, true);
285
279
  }
286
280
  return change;
287
- });
281
+ };
288
282
  this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => {
289
283
  if (!this.configuration.structureCompare(other.configuration, strictOrder, descriptionMatch)) {
290
284
  return false;
@@ -292,25 +286,25 @@ export class _CfgProductInternal {
292
286
  return compareArrays(this.additionalProducts, other.additionalProducts, (l, r) => (!descriptionMatch || (descriptionMatch && isDescriptionMatch(l, r))) &&
293
287
  l.structureCompare(r, strictOrder, descriptionMatch), !descriptionMatch);
294
288
  };
295
- this.tryMatchSelection = (other, descriptionMatch = false, // Match on case insensitive description, not code
296
- productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
289
+ this.tryMatchSelection = async (other, descriptionMatch = false, // Match on case insensitive description, not code
290
+ productLoaderForGroupedLoad) => {
297
291
  // wrap with cache will make getProduct for this function call use the same server call
298
292
  // for the same product with the same params
299
293
  productLoaderForGroupedLoad =
300
294
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
301
295
  let change = false;
302
296
  if (this.optional && other.optional) {
303
- if (yield this.setSelected(other.selected, CfgProductBubbleMode.Stop, false)) {
297
+ if (await this.setSelected(other.selected, CfgProductBubbleMode.Stop, false)) {
304
298
  change = true;
305
299
  }
306
300
  }
307
- const configurationChange = yield this.configuration._internal.tryMatchSelection(other.configuration._internal, descriptionMatch, false);
301
+ const configurationChange = await this.configuration._internal.tryMatchSelection(other.configuration._internal, descriptionMatch, false);
308
302
  if (configurationChange) {
309
303
  change = true;
310
- yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, productLoaderForGroupedLoad, true);
304
+ await this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, productLoaderForGroupedLoad, true);
311
305
  }
312
306
  else if (change) {
313
- yield this._notifyAllOfChange(CfgProductBubbleMode.ToRootAndBubbleSelected, true);
307
+ await this._notifyAllOfChange(CfgProductBubbleMode.ToRootAndBubbleSelected, true);
314
308
  }
315
309
  const thisAdditionalProducts = this.additionalProducts;
316
310
  const otherAdditionalProducts = other.additionalProducts;
@@ -342,11 +336,11 @@ export class _CfgProductInternal {
342
336
  promises.push(thisAdditionalProducts[i]._internal.tryMatchSelection(otherAdditionalProducts[i]._internal, descriptionMatch, productLoaderForGroupedLoad));
343
337
  }
344
338
  }
345
- if ((yield Promise.all(promises)).some((b) => b)) {
339
+ if ((await Promise.all(promises)).some((b) => b)) {
346
340
  change = true;
347
341
  }
348
342
  return change;
349
- });
343
+ };
350
344
  /** Only features in selected options and selected additional products. */
351
345
  this._getDescendantFeaturesWithCode = (code) => this.additionalProducts.reduce((agg, additionalProduct) => {
352
346
  if (additionalProduct.selected) {
@@ -355,16 +349,21 @@ export class _CfgProductInternal {
355
349
  return agg;
356
350
  }, this._configuration._internal._getFeaturesWithCode(code));
357
351
  /**
358
- * Do a validate call for this product. It does not validate additional products, only this
359
- * product in isolation. The validation result is applied on the configuration. Then additional
352
+ * Do a validate call for this product.
353
+ *
354
+ * Does not validate additional products, unless application of sync groups
355
+ * on the result of the validate call results in changes in the additional
356
+ * product.
357
+ *
358
+ * The validation result is applied on the configuration. Then additional
360
359
  * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
361
360
  */
362
- this._revalidate = (bubbleMode, productLoader, committed) => __awaiter(this, void 0, void 0, function* () {
361
+ this._revalidate = async (bubbleMode, productLoader, committed) => {
363
362
  const { _configuration: configuration } = this;
364
363
  const token = this.loadingObservable.startChildLoading();
365
364
  this._revalidateInProgressToken = token;
366
365
  try {
367
- const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), {
366
+ const response = await productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), {
368
367
  selOptions: configuration._internal.getApiSelection(),
369
368
  knownFeatureCodes: configuration.rawFeatures.map((f) => f.code),
370
369
  });
@@ -395,12 +394,22 @@ export class _CfgProductInternal {
395
394
  if (rootFeatureRefs !== undefined) {
396
395
  configuration._internal.populateFeatures(rootFeatureRefs);
397
396
  }
398
- yield configuration._internal.setApiSelection(productData.partsData.selOptions || [], productData.partsData.constrOptions, false);
399
- yield this._syncAndLoadAdditionalProducts(productLoader, undefined);
397
+ const originalProduct = await this.clone();
398
+ await configuration._internal.setApiSelection(productData.partsData.selOptions || [], productData.partsData.constrOptions, false);
399
+ if (this.parent === undefined) {
400
+ const diff = generateSelectionDiff(originalProduct, this);
401
+ if (this._lastDiff && diff) {
402
+ this._lastDiff.push(...diff);
403
+ }
404
+ else if (diff) {
405
+ this._lastDiff = diff;
406
+ }
407
+ }
408
+ await this._syncAndLoadAdditionalProducts(productLoader, undefined);
400
409
  if (this._destroyed) {
401
410
  return false;
402
411
  }
403
- yield this._notifyAllOfChange(bubbleMode, committed);
412
+ await this._notifyAllOfChange(bubbleMode, committed);
404
413
  return true;
405
414
  }
406
415
  catch (e) {
@@ -409,12 +418,12 @@ export class _CfgProductInternal {
409
418
  finally {
410
419
  this.loadingObservable.stopChildLoading(token);
411
420
  }
412
- });
421
+ };
413
422
  /**
414
423
  * Based on this configuration find what additional products should be shown and not, unload
415
424
  * (i.e. destroy) those that should no longer be shown, load the new ones.
416
425
  */
417
- this._syncAndLoadAdditionalProducts = (productLoaderForGroupedLoad, initialProductConfiguration) => __awaiter(this, void 0, void 0, function* () {
426
+ this._syncAndLoadAdditionalProducts = async (productLoaderForGroupedLoad, initialProductConfiguration) => {
418
427
  const { _productLoaderRaw: productLoaderRaw, rawProductData: productData, configuration, additionalProducts: currentAdditionalProducts, } = this;
419
428
  const additionalProductRefs = [
420
429
  ...(productData.additionalProductRefs || []),
@@ -455,20 +464,23 @@ export class _CfgProductInternal {
455
464
  if (additionalProductRefs.length !== 0) {
456
465
  change = true;
457
466
  }
458
- const newAdditionalProducts = yield Promise.all(additionalProductRefs.map((p) => (() => __awaiter(this, void 0, void 0, function* () {
459
- var _d;
467
+ const newAdditionalProducts = await Promise.all(additionalProductRefs.map((p) => (async () => {
460
468
  const additionalProductRef = p.prodRef;
461
469
  const refKey = additionalProductRef.refKey;
462
- const additionalProductInitialProductConfiguration = (_d = initialProductConfiguration === null || initialProductConfiguration === void 0 ? void 0 : initialProductConfiguration.additionalProducts) === null || _d === void 0 ? void 0 : _d.find((aP) => refKey === aP.refKey);
470
+ const additionalProductInitialProductConfiguration = initialProductConfiguration?.additionalProducts?.find((aP) => refKey === aP.refKey);
463
471
  if (initialProductConfiguration !== undefined &&
464
472
  additionalProductInitialProductConfiguration === undefined) {
465
473
  console.warn(`Did not find initial product configuration for additional product with refKey ${refKey}`);
466
474
  }
467
475
  return {
468
476
  originalIndex: p.originalIndex,
469
- product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, Object.assign(Object.assign({}, additionalProductRef.catId), { lang: this.prodParams.lang, partNumber: additionalProductRef.partNumber }), this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef, additionalProductInitialProductConfiguration)),
477
+ product: CfgProduct._makeNewRefFrom(await _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, {
478
+ ...additionalProductRef.catId,
479
+ lang: this.prodParams.lang,
480
+ partNumber: additionalProductRef.partNumber,
481
+ }, this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef, additionalProductInitialProductConfiguration)),
470
482
  };
471
- }))()));
483
+ })()));
472
484
  if (this._destroyed) {
473
485
  return change;
474
486
  }
@@ -480,13 +492,16 @@ export class _CfgProductInternal {
480
492
  finally {
481
493
  this.loadingObservable.stopChildLoading(token);
482
494
  }
483
- });
495
+ };
484
496
  this._setRawProductData = (productData) => {
485
497
  this._rawProductData = productData;
486
498
  this._clearStretchMeasurements();
487
499
  };
488
- this.root = root !== null && root !== void 0 ? root : this;
489
- this.key = makeProductKey(Object.assign(Object.assign({}, prodParams), { partNumber: (_b = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _b !== void 0 ? _b : prodParams.partNumber }));
500
+ this.root = root ?? this;
501
+ this.key = makeProductKey({
502
+ ...prodParams,
503
+ partNumber: _additionalProductRef?.refKey ?? prodParams.partNumber,
504
+ });
490
505
  this._selected = optional ? selected : undefined;
491
506
  this.isAdditionalProduct = _additionalProductRef !== undefined;
492
507
  if (notes !== undefined) {
@@ -503,19 +518,16 @@ export class _CfgProductInternal {
503
518
  * Providing the parent and root of what you clone as arguments is unwise as it will
504
519
  * make changes you do on the clone be propagated up to the original non-clone root product.
505
520
  */
506
- clone(parent, root) {
507
- return __awaiter(this, void 0, void 0, function* () {
508
- const product = yield new Promise((initSuccess, initFail) => {
509
- var _b;
510
- const p = new _CfgProductInternal(() => {
511
- initSuccess(p);
512
- }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, this._notes.values(), this.uuid, this._rawUnit, this._rawProductData, this.configuration._internal.getApiSelection(), this.configuration._internal.getApiConstrained(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_b = this._syncGroupHandler) === null || _b === void 0 ? void 0 : _b.clone());
513
- });
514
- for (const additionalProduct of this.additionalProducts) {
515
- product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
516
- }
517
- return product;
521
+ async clone(parent, root) {
522
+ const product = await new Promise((initSuccess, initFail) => {
523
+ const p = new _CfgProductInternal(() => {
524
+ initSuccess(p);
525
+ }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, this._notes.values(), this.uuid, this._rawUnit, this._rawProductData, this.configuration._internal.getApiSelection(), this.configuration._internal.getApiConstrained(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, this._syncGroupHandler?.clone());
518
526
  });
527
+ for (const additionalProduct of this.additionalProducts) {
528
+ product.additionalProducts.push(CfgProduct._makeNewRefFrom(await additionalProduct._internal.clone(product, root || product)));
529
+ }
530
+ return product;
519
531
  }
520
532
  /**
521
533
  * Internal use. Used when this product is an additional product, and
@@ -556,20 +568,17 @@ export class _CfgProductInternal {
556
568
  }
557
569
  }
558
570
  get notes() {
559
- var _b;
560
- return this.getNotes((_b = this.rawProductData.noteRefs) !== null && _b !== void 0 ? _b : []);
571
+ return this.getNotes(this.rawProductData.noteRefs ?? []);
561
572
  }
562
573
  get miscFiles() {
563
- var _b;
564
- return (_b = this._rawProductData.miscFiles) !== null && _b !== void 0 ? _b : [];
574
+ return this._rawProductData.miscFiles ?? [];
565
575
  }
566
576
  get hasRootFeaturesChanged() {
567
577
  return (this._configuration._internal.hasRootFeaturesChanged ||
568
578
  this.additionalProducts.some((p) => p._internal.hasRootFeaturesChanged));
569
579
  }
570
580
  get description() {
571
- var _b, _c;
572
- return (_c = (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.refDescription) !== null && _c !== void 0 ? _c : this._rawProductData.description;
581
+ return this._additionalProductRef?.refDescription ?? this._rawProductData.description;
573
582
  }
574
583
  get rootNodeSources() {
575
584
  return this._rawProductData.models;
@@ -587,25 +596,21 @@ export class _CfgProductInternal {
587
596
  return this._rawProductData.partsData.prices;
588
597
  }
589
598
  get measureDefinitions() {
590
- var _b;
591
599
  if (this._measureDefinitions === undefined) {
592
- this._measureDefinitions = ((_b = this._rawProductData.measurements) !== null && _b !== void 0 ? _b : [])
600
+ this._measureDefinitions = (this._rawProductData.measurements ?? [])
593
601
  .map((m) => CfgMeasureDefinition.make(m))
594
602
  .filter((m) => m !== undefined);
595
603
  }
596
604
  return this._measureDefinitions;
597
605
  }
598
606
  get refKey() {
599
- var _b;
600
- return (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.refKey;
607
+ return this._additionalProductRef?.refKey;
601
608
  }
602
609
  get transform() {
603
- var _b;
604
- return (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.transform;
610
+ return this._additionalProductRef?.transform;
605
611
  }
606
612
  get anchor() {
607
- var _b;
608
- return (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.anchor;
613
+ return this._additionalProductRef?.anchor;
609
614
  }
610
615
  /** @throws an error if the actual unit sent by the server was not a LengthUnit */
611
616
  get unit() {
@@ -641,34 +646,32 @@ export class _CfgProductInternal {
641
646
  get optional() {
642
647
  return this._selected !== undefined;
643
648
  }
644
- setSelected(selected, bubbleMode, interactive) {
645
- return __awaiter(this, void 0, void 0, function* () {
646
- if (!this.optional) {
647
- console.warn("This product is not optional. Nothing will happen");
648
- return false;
649
- }
650
- if (this._selected === selected) {
651
- return false;
652
- }
653
- // Vitally important that this happens before the call to reset,
654
- // so that the reset won't cause infinite loops
655
- this._selected = selected;
656
- if (interactive) {
657
- if (selected) {
658
- const syncGroupHandler = this.syncGroupHandler;
659
- if (syncGroupHandler !== undefined) {
660
- yield syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
661
- }
662
- }
663
- else {
664
- yield this.reset();
665
- // In case a passed initial configuration has made it reset to selected
666
- this._selected = selected;
649
+ async setSelected(selected, bubbleMode, interactive) {
650
+ if (!this.optional) {
651
+ console.warn("This product is not optional. Nothing will happen");
652
+ return false;
653
+ }
654
+ if (this._selected === selected) {
655
+ return false;
656
+ }
657
+ // Vitally important that this happens before the call to reset,
658
+ // so that the reset won't cause infinite loops
659
+ this._selected = selected;
660
+ if (interactive) {
661
+ if (selected) {
662
+ const syncGroupHandler = this.syncGroupHandler;
663
+ if (syncGroupHandler !== undefined) {
664
+ await syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
667
665
  }
668
666
  }
669
- yield this._notifyAllOfChange(bubbleMode, true);
670
- return true;
671
- });
667
+ else {
668
+ await this.reset();
669
+ // In case a passed initial configuration has made it reset to selected
670
+ this._selected = selected;
671
+ }
672
+ }
673
+ await this._notifyAllOfChange(bubbleMode, true);
674
+ return true;
672
675
  }
673
676
  get configuration() {
674
677
  return this._configuration;
@@ -711,8 +714,7 @@ export class _CfgProductInternal {
711
714
  return this.root._syncGroupHandler;
712
715
  }
713
716
  get syncGroupsVerboseLogging() {
714
- var _b, _c;
715
- return (_c = (_b = this.syncGroupHandler) === null || _b === void 0 ? void 0 : _b.verboseLogging) !== null && _c !== void 0 ? _c : false;
717
+ return this.syncGroupHandler?.verboseLogging ?? false;
716
718
  }
717
719
  /**
718
720
  * Set to true to get verbose sync state changes logged to the console.
@@ -724,10 +726,21 @@ export class _CfgProductInternal {
724
726
  }
725
727
  syncGroupHandler.verboseLogging = v;
726
728
  }
729
+ /**
730
+ * Obtains the differences found in the last validate call.
731
+ * Used to update the sync group state.
732
+ *
733
+ * Removes the diff from the product.
734
+ */
735
+ takeDiff() {
736
+ const diff = this._lastDiff?.filter((option) => option.selected);
737
+ this._lastDiff = undefined;
738
+ return diff;
739
+ }
727
740
  }
728
741
  _a = _CfgProductInternal;
729
- _CfgProductInternal.make = (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
730
- prodParams, settings, optional, loadingObservable, parent, root, additionalProductRef, initialProductConfiguration) => __awaiter(void 0, void 0, void 0, function* () {
742
+ _CfgProductInternal.make = async (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
743
+ prodParams, settings, optional, loadingObservable, parent, root, additionalProductRef, initialProductConfiguration) => {
731
744
  // Wrap with cache will make getProduct for this function call use the same server call
732
745
  // for the same product with the same params. Not retained for future calls, only used
733
746
  // at this initial load.
@@ -756,39 +769,38 @@ prodParams, settings, optional, loadingObservable, parent, root, additionalProdu
756
769
  initialProductConfigurationV1 = initialProductConfiguration;
757
770
  }
758
771
  }
759
- const productResponse = yield (initialProductConfigurationV1
772
+ const productResponse = await (initialProductConfigurationV1
760
773
  ? productLoaderForGroupedLoad.postValidate(defaultCorrectedProdParams, {
761
774
  knownFeatureCodes: [],
762
775
  selOptions: initialProductConfigurationV1.selOptions,
763
776
  })
764
777
  : productLoaderForGroupedLoad.getProduct(defaultCorrectedProdParams));
765
778
  const { productData, rootFeatureRefs, features: rawFeatures, notes, uuid, unit, } = productResponse;
766
- const initiallySelected = !optional || (initialProductConfigurationV1 === null || initialProductConfigurationV1 === void 0 ? void 0 : initialProductConfigurationV1.selected) === true;
767
- const product = yield new Promise((initSuccess, initFail) => {
768
- var _b;
779
+ const initiallySelected = !optional || initialProductConfigurationV1?.selected === true;
780
+ const product = await new Promise((initSuccess, initFail) => {
769
781
  const p = new _CfgProductInternal(() => {
770
782
  // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
771
783
  // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
772
784
  // Feel free to find a nicer more readable solution :)
773
785
  initSuccess(p);
774
- }, initFail, productLoaderRaw, prodParams, settings, optional, initiallySelected, rootFeatureRefs !== null && rootFeatureRefs !== void 0 ? rootFeatureRefs : [], rawFeatures, notes === null || notes === void 0 ? void 0 : notes.values(), uuid, unit, productData, (_b = productData.partsData.selOptions) !== null && _b !== void 0 ? _b : [], productData.partsData.constrOptions, loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
786
+ }, initFail, productLoaderRaw, prodParams, settings, optional, initiallySelected, rootFeatureRefs ?? [], rawFeatures, notes?.values(), uuid, unit, productData, productData.partsData.selOptions ?? [], productData.partsData.constrOptions, loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
775
787
  });
776
- yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, initialProductConfigurationV1);
777
- product._initialClone = yield product.clone();
788
+ await product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, initialProductConfigurationV1);
789
+ product._initialClone = await product.clone();
778
790
  if (syncGroupHandler !== undefined && !initialProductConfiguration) {
779
791
  // As syncGroupHandler is only set for root product we know that we will init with root.
780
792
  // If we have an initialProductConfiguration we do not run init for the syncGroups.
781
793
  // The idea with an initial configuration is that the product should look as it did when
782
794
  // the configuration was saved. Not running init does that. Also, the DtoProductConf
783
795
  // object can contain a sync state which is then used to populate the sync state.
784
- yield syncGroupHandler.init(product, productLoaderForGroupedLoad);
796
+ await syncGroupHandler.init(product, productLoaderForGroupedLoad);
785
797
  }
786
798
  return product;
787
799
  }
788
800
  catch (e) {
789
801
  throw augmentErrorMessage(e, `Load product request (${prodParams.partNumber}) failure`);
790
802
  }
791
- });
803
+ };
792
804
  export class CfgProduct {
793
805
  constructor(_internal) {
794
806
  this._internal = _internal;
@@ -799,13 +811,13 @@ export class CfgProduct {
799
811
  */
800
812
  this.destroy = () => this._internal.destroy();
801
813
  /** Makes a clone of this. It is disconnected from the original. */
802
- this.clone = () => __awaiter(this, void 0, void 0, function* () { return CfgProduct._makeNewRefFrom(yield this._internal.clone()); });
814
+ this.clone = async () => CfgProduct._makeNewRefFrom(await this._internal.clone());
803
815
  /**
804
816
  * Only applicable when this product is optional.
805
817
  * Setting this does not cause a validation call as toggling an optional additional product is
806
818
  * assumed to always be legal.
807
819
  */
808
- this.setSelected = (v) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setSelected(v, CfgProductBubbleMode.ToRootAndBubbleSelected, true); });
820
+ this.setSelected = async (v) => await this._internal.setSelected(v, CfgProductBubbleMode.ToRootAndBubbleSelected, true);
809
821
  /**
810
822
  * Experimental. Additional products lacks descriptions or keys that are suitably for structure
811
823
  * compare, so we use strict-order when trying to match the additional products. This makes
@@ -819,8 +831,8 @@ export class CfgProduct {
819
831
  * This method does not propagate its selections.
820
832
  * This method will cause validation calls if something change.
821
833
  */
822
- this.tryMatchSelection = (other, descriptionMatch = false // Match on case insensitive description, not code
823
- ) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.tryMatchSelection(other._internal, descriptionMatch); });
834
+ this.tryMatchSelection = async (other, descriptionMatch = false // Match on case insensitive description, not code
835
+ ) => await this._internal.tryMatchSelection(other._internal, descriptionMatch);
824
836
  /**
825
837
  * Gets what selections has been made on the product, recursively including product
826
838
  * configuration, optional products and additional products. Used when a full view of all
@@ -836,7 +848,7 @@ export class CfgProduct {
836
848
  * the right models are loaded.
837
849
  * @deprecated setDtoConf uses a newer format.
838
850
  */
839
- this.setApiSelection = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(configuration, doValidate); });
851
+ this.setApiSelection = async (configuration, doValidate = true) => await this._internal.setApiSelection(configuration, doValidate);
840
852
  /**
841
853
  * A newer alternative version of getApiSelection. This returns the configuration (selections)
842
854
  * on the product, recursively including product configuration, optional products and additional
@@ -858,7 +870,7 @@ export class CfgProduct {
858
870
  * @param doValidate Makes a server side validation call. These are necessary to ensure that
859
871
  * the right models are loaded.
860
872
  */
861
- this.setDtoConf = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(configuration, doValidate); });
873
+ this.setDtoConf = async (configuration, doValidate = true) => await this._internal.setDtoConf(configuration, doValidate);
862
874
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
863
875
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
864
876
  this.stopAllListenForChange = () => this._internal.changeObservable.stopAllListen();
@@ -866,10 +878,8 @@ export class CfgProduct {
866
878
  this.stopListenForLoading = (l) => this._internal.loadingObservable.stopListen(l);
867
879
  this.stopAllListenForLoading = () => this._internal.loadingObservable.stopAllListen();
868
880
  }
869
- static make(productLoader, prodParams, settings, initialProductConfiguration) {
870
- return __awaiter(this, void 0, void 0, function* () {
871
- return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, prodParams, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined, initialProductConfiguration));
872
- });
881
+ static async make(productLoader, prodParams, settings, initialProductConfiguration) {
882
+ return this._makeNewRefFrom(await _CfgProductInternal.make(productLoader, undefined, prodParams, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined, initialProductConfiguration));
873
883
  }
874
884
  /**
875
885
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to