@configura/web-api 1.6.1-alpha.7 → 1.6.1

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 (43) hide show
  1. package/.eslintrc.json +18 -0
  2. package/dist/CatalogueAPI.d.ts +4 -9
  3. package/dist/CatalogueAPI.js +6 -3
  4. package/dist/CfgProduct.d.ts +31 -12
  5. package/dist/CfgProduct.js +124 -45
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +2 -0
  8. package/dist/productConfiguration/CfgFeature.d.ts +13 -3
  9. package/dist/productConfiguration/CfgFeature.js +77 -52
  10. package/dist/productConfiguration/CfgOption.d.ts +30 -8
  11. package/dist/productConfiguration/CfgOption.js +49 -17
  12. package/dist/productConfiguration/CfgProductConfiguration.d.ts +1 -1
  13. package/dist/productConfiguration/CfgProductConfiguration.js +4 -2
  14. package/dist/productConfiguration/filters.js +7 -7
  15. package/dist/syncGroups/SyncGroupsApplyMode.d.ts +21 -0
  16. package/dist/syncGroups/SyncGroupsApplyMode.js +21 -0
  17. package/dist/syncGroups/SyncGroupsHandler.d.ts +41 -0
  18. package/dist/syncGroups/SyncGroupsHandler.js +358 -0
  19. package/dist/syncGroups/SyncGroupsPathHelper.d.ts +27 -0
  20. package/dist/syncGroups/SyncGroupsPathHelper.js +90 -0
  21. package/dist/syncGroups/SyncGroupsState.d.ts +36 -0
  22. package/dist/syncGroups/SyncGroupsState.js +125 -0
  23. package/dist/syncGroups/SyncGroupsTransaction.d.ts +155 -0
  24. package/dist/syncGroups/SyncGroupsTransaction.js +576 -0
  25. package/dist/tasks/TaskHandler.d.ts +1 -1
  26. package/dist/tasks/TaskHandler.js +20 -9
  27. package/dist/tests/testData/collectorForTest.d.ts +1 -1
  28. package/dist/tests/testData/collectorForTest.js +1 -2
  29. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.d.ts +3 -24
  30. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +30 -101
  31. package/dist/tests/testData/testDataCachedGetProduct.d.ts +1 -1
  32. package/dist/tests/testData/testDataCachedGetProduct.js +16 -27
  33. package/dist/tests/testData/testDataCachedPostValidate.js +5 -5
  34. package/dist/tests/testData/testDataOptions.d.ts +13 -0
  35. package/dist/tests/testData/testDataOptions.js +60 -0
  36. package/dist/tests/testData/testDataProductAggregatedPrice.js +19 -30
  37. package/dist/tests/testData/testDataUpcharge.d.ts +3 -24
  38. package/dist/tests/testData/testDataUpcharge.js +17 -49
  39. package/dist/utilitiesCatalogueData.d.ts +8 -2
  40. package/dist/utilitiesCatalogueData.js +105 -9
  41. package/dist/utilitiesCataloguePermission.d.ts +1 -3
  42. package/dist/utilitiesCataloguePermission.js +10 -14
  43. package/package.json +3 -3
package/.eslintrc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "parser": "@typescript-eslint/parser",
3
+ "plugins": ["@typescript-eslint"],
4
+ "extends": [
5
+ "eslint:recommended",
6
+ "plugin:@typescript-eslint/recommended",
7
+ "prettier"
8
+
9
+ // TODO: Type-checking rules require a proper tsconfig-file to work,
10
+ // pointed at by the parserOption.project setting.
11
+ //
12
+ //"plugin:@typescript-eslint/recommended-requiring-type-checking"
13
+ ]
14
+ //"rules": { "@typescript-eslint/no-floating-promises": "error" }
15
+ //"parserOptions": {
16
+ // "project": "./tsconfig.json"
17
+ //}
18
+ }
@@ -66,14 +66,6 @@ export interface CodeRange {
66
66
  maxValue: number;
67
67
  increment?: number;
68
68
  }
69
- /** ConnectorRef */
70
- export interface ConnectorRef {
71
- code: string;
72
- connectRule: string;
73
- visible: boolean;
74
- anchor?: MeasureParam;
75
- transform: Transform;
76
- }
77
69
  /** ErrorResponse */
78
70
  export interface ErrorResponse {
79
71
  error: string;
@@ -349,7 +341,6 @@ export interface ProductData {
349
341
  navImage?: string;
350
342
  sku: string;
351
343
  measurements?: Array<Measurement>;
352
- connectorRefs?: Array<ConnectorRef>;
353
344
  tags?: Array<{
354
345
  [index: string]: string;
355
346
  }>;
@@ -491,8 +482,11 @@ export declare class APIError<T> extends Error {
491
482
  parsed?: T;
492
483
  status?: number;
493
484
  }
485
+ declare type FetchFunc = (input: RequestInfo, init?: RequestInit) => Promise<Response>;
494
486
  export declare class CatalogueAPI {
487
+ private readonly _fetch;
495
488
  auth: AuthorizeResponse | undefined;
489
+ constructor(_fetch?: FetchFunc);
496
490
  private _alternativeReferer;
497
491
  _saveAlternativeReferer(): void;
498
492
  hasFeature(feature: string): boolean;
@@ -510,4 +504,5 @@ export declare class CatalogueAPI {
510
504
  getRenderById(params: GetRenderByIdParams): Promise<RenderResponse>;
511
505
  postSessionTokenRefresh(): Promise<RefreshSessionTokenResponse>;
512
506
  }
507
+ export {};
513
508
  //# sourceMappingURL=CatalogueAPI.d.ts.map
@@ -1,5 +1,5 @@
1
- // WARNING: This file was auto generated by the code in web-rnd/tygen.
2
- // Do not commit manual changes to this file.
1
+ // TODO: Fix the linter issue in Tygen and regenerate this file.
2
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
3
3
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
4
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
5
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -35,6 +35,9 @@ export const syncGroupMethodsNames = ["pull", "push", "twoWay"];
35
35
  export class APIError extends Error {
36
36
  }
37
37
  export class CatalogueAPI {
38
+ constructor(_fetch = window.fetch.bind(window)) {
39
+ this._fetch = _fetch;
40
+ }
38
41
  // For internal use
39
42
  _saveAlternativeReferer() {
40
43
  if (typeof document === "undefined") {
@@ -59,7 +62,7 @@ export class CatalogueAPI {
59
62
  options.headers["Content-Type"] = "application/json;charset=utf-8";
60
63
  }
61
64
  try {
62
- response = yield fetch(url, options);
65
+ response = yield this._fetch(url, options);
63
66
  body = yield response.text();
64
67
  const parsed = JSON.parse(body);
65
68
  if ("error" in parsed) {
@@ -1,10 +1,12 @@
1
1
  import { AggregatedLoadingObservable, LengthUnit, Observable, SingleArgCallback } from "@configura/web-utilities";
2
- import { AdditionalProductConfiguration, CatalogueParams, MeasureParam, MtrlApplication, Prices, Transform } from "./CatalogueAPI.js";
2
+ import { AdditionalProductConfiguration, AdditionalProductRef, CatalogueParams, MeasureParam, MtrlApplication, Prices, Transform } from "./CatalogueAPI.js";
3
3
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
4
4
  import { _CfgFeatureInternal } from "./productConfiguration/CfgFeature.js";
5
5
  import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
6
6
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
7
7
  import { ProductLoader } from "./productLoader.js";
8
+ import { SyncGroupsApplyMode } from "./syncGroups/SyncGroupsApplyMode.js";
9
+ import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
8
10
  import { CfgProductData, RootNodeSource } from "./utilitiesCatalogueData.js";
9
11
  export declare type CfgProductChangeNotification = {
10
12
  freshRef: CfgProduct;
@@ -18,12 +20,19 @@ export declare type CfgProductSettings = {
18
20
  */
19
21
  strictSelectOneSelectionCount: boolean;
20
22
  /**
21
- * Activating this will make setAPI throw an error if the number of actually selected options
22
- * on Features (excluding Group) are not exactly equal to the number of options passed in.
23
- * Note: This check is not always reliable for Options with multiple Features each, which we
24
- * believe is a rare use case.
23
+ * Controls if SyncGroups are applied Faster or Stricter.
24
+ *
25
+ * Fast - Tries to minimize the number of validates calls to the AI lowering the response times
26
+ * for selecting options. Might not always give the expected result for complex products.
27
+ *
28
+ * Strict - Apply the SyncGroups rules in a stricter fashion to be as close to CET as possible,
29
+ * which might result in longer response times and more validation calls to the API when
30
+ * selecting options.
31
+ *
32
+ * The SDK will default to Strict, but we recommend that you try out Fast since cases where the
33
+ * results differ should be rare in most real uses cases and the speedup can be quite large.
25
34
  */
26
- strictSetApiSelectionMatch: boolean;
35
+ syncGroupsApplyMode: SyncGroupsApplyMode | undefined;
27
36
  };
28
37
  /**
29
38
  * This enum is used internally in the SDK and is not expected by be used directly by integrators.
@@ -63,12 +72,10 @@ export declare class _CfgProductInternal {
63
72
  private readonly _rawUnit;
64
73
  private _rawProductData;
65
74
  readonly loadingObservable: AggregatedLoadingObservable;
66
- readonly refKey: string | undefined;
67
- readonly refDescription: string | undefined;
68
75
  readonly parent: _CfgProductInternal | undefined;
69
- readonly transform: Transform | undefined;
70
- anchor: MeasureParam | undefined;
71
- static make: (productLoaderRaw: ProductLoader, productLoaderForGroupedLoad: ProductLoader | undefined, lang: string, catId: CatalogueParams, partNumber: string, settings: CfgProductSettings, optional: boolean, loadingObservable: AggregatedLoadingObservable, refKey: string | undefined, refDescription: string | undefined, parent: _CfgProductInternal | undefined, root: _CfgProductInternal | undefined, transform: Transform | undefined, anchor: MeasureParam | undefined) => Promise<_CfgProductInternal>;
76
+ private _additionalProductRef;
77
+ private readonly _syncGroupHandler;
78
+ static make: (productLoaderRaw: ProductLoader, productLoaderForGroupedLoad: ProductLoader | undefined, lang: string, catId: CatalogueParams, partNumber: string, settings: CfgProductSettings, optional: boolean, loadingObservable: AggregatedLoadingObservable, parent: _CfgProductInternal | undefined, root: _CfgProductInternal | undefined, additionalProductRef: AdditionalProductRef | undefined) => Promise<_CfgProductInternal>;
72
79
  private constructor();
73
80
  readonly root: _CfgProductInternal;
74
81
  private _destroyed;
@@ -81,6 +88,7 @@ export declare class _CfgProductInternal {
81
88
  readonly isAdditionalProduct: boolean;
82
89
  clone(parent?: _CfgProductInternal, root?: _CfgProductInternal): Promise<_CfgProductInternal>;
83
90
  destroy: () => void;
91
+ _updateAdditionalProdRef(p: AdditionalProductRef): void;
84
92
  get description(): string | undefined;
85
93
  get rootNodeSources(): RootNodeSource[] | undefined;
86
94
  get mtrlApplications(): MtrlApplication[] | undefined;
@@ -90,6 +98,9 @@ export declare class _CfgProductInternal {
90
98
  private _measureDefinitions;
91
99
  get measureDefinitions(): CfgMeasureDefinition[];
92
100
  private _unit;
101
+ get refKey(): string | undefined;
102
+ get transform(): Transform | undefined;
103
+ get anchor(): MeasureParam | undefined;
93
104
  /** @throws an error if the actual unit sent by the server was not a LengthUnit */
94
105
  get unit(): LengthUnit;
95
106
  get aggregatedPrice(): CfgPrice;
@@ -121,6 +132,14 @@ export declare class _CfgProductInternal {
121
132
  _configurationHasChanged: (freshRef: CfgProductConfiguration, bubbleMode: ProductConfigurationBubbleMode) => Promise<void>;
122
133
  getApiSelection: () => AdditionalProductConfiguration;
123
134
  setApiSelection: (s: AdditionalProductConfiguration, doValidate: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
135
+ copyFrom: (source: _CfgProductInternal, doValidate: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
136
+ private _setApiSelectionWithOtherProduct;
137
+ get syncGroupHandler(): SyncGroupsHandler | undefined;
138
+ get syncGroupsVerboseLogging(): boolean;
139
+ /**
140
+ * Set to true to get verbose sync state changes logged to the console.
141
+ */
142
+ set syncGroupsVerboseLogging(v: boolean);
124
143
  structureCompare: (other: _CfgProductInternal, strictOrder?: boolean, descriptionMatch?: boolean) => boolean;
125
144
  tryMatchSelection: (other: _CfgProductInternal, descriptionMatch?: boolean, productLoaderForGroupedLoad?: ProductLoader | undefined) => Promise<boolean>;
126
145
  /** Only features in selected options and selected additional products. */
@@ -143,7 +162,7 @@ export declare class CfgProduct {
143
162
  static make(productLoader: ProductLoader, lang: string, catId: CatalogueParams, partNumber: string, settings?: Partial<CfgProductSettings>): Promise<CfgProduct>;
144
163
  /**
145
164
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to
146
- * make a new outer reference. Like a shallow copy./ We use this to help frameworks that are
165
+ * make a new outer reference. Like a shallow copy. We use this to help frameworks that are
147
166
  * build around using equals to detect change.
148
167
  */
149
168
  static _makeNewRefFrom(source: _CfgProductInternal): CfgProduct;
@@ -7,18 +7,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { AggregatedLoadingObservable, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
10
+ import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
12
12
  import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
13
13
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
14
14
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
15
15
  import { wrapWithCache } from "./productLoader.js";
16
- import { comparePricesObjects, correctDefaultsOnCatalogueParams, makeProductKey, } from "./utilitiesCatalogueData.js";
16
+ import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
17
+ import { comparePricesObjects, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
17
18
  function completeSettings(incompleteSettings) {
18
- var _a, _b;
19
+ var _a;
19
20
  return {
20
21
  strictSelectOneSelectionCount: (_a = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSelectOneSelectionCount) !== null && _a !== void 0 ? _a : false,
21
- strictSetApiSelectionMatch: (_b = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSetApiSelectionMatch) !== null && _b !== void 0 ? _b : false,
22
+ syncGroupsApplyMode: incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.syncGroupsApplyMode,
22
23
  };
23
24
  }
24
25
  /**
@@ -50,7 +51,8 @@ function isDescriptionMatch(l, r) {
50
51
  * the class that should be used and interacted with.
51
52
  */
52
53
  export class _CfgProductInternal {
53
- constructor(initSuccess, initFail, _productLoaderRaw, lang, catId, partNumber, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, refKey, refDescription, parent, root, transform, anchor) {
54
+ constructor(initSuccess, initFail, _productLoaderRaw, lang, catId, partNumber, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
55
+ var _a;
54
56
  this._productLoaderRaw = _productLoaderRaw;
55
57
  this.lang = lang;
56
58
  this.catId = catId;
@@ -60,11 +62,9 @@ export class _CfgProductInternal {
60
62
  this._rawUnit = _rawUnit;
61
63
  this._rawProductData = _rawProductData;
62
64
  this.loadingObservable = loadingObservable;
63
- this.refKey = refKey;
64
- this.refDescription = refDescription;
65
65
  this.parent = parent;
66
- this.transform = transform;
67
- this.anchor = anchor;
66
+ this._additionalProductRef = _additionalProductRef;
67
+ this._syncGroupHandler = _syncGroupHandler;
68
68
  this._destroyed = false;
69
69
  this.additionalProducts = [];
70
70
  this.changeObservable = new Observable();
@@ -120,6 +120,9 @@ export class _CfgProductInternal {
120
120
  // The revalidate call will continue the bubble
121
121
  yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw);
122
122
  return;
123
+ case ProductConfigurationBubbleMode.BubbleSelected:
124
+ yield this._childHasChanged(CfgProductBubbleMode.ToRootAndBubbleSelected);
125
+ return;
123
126
  case ProductConfigurationBubbleMode.Validate:
124
127
  // The revalidate call will continue the bubble
125
128
  yield this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw);
@@ -145,13 +148,27 @@ export class _CfgProductInternal {
145
148
  additionalProducts: this.additionalProducts.map((additionalProduct) => additionalProduct.getApiSelection()),
146
149
  });
147
150
  this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
151
+ return this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
152
+ });
153
+ this.copyFrom = (source, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
154
+ return yield this._setApiSelectionWithOtherProduct(source.getApiSelection(), doValidate, productLoaderForGroupedLoad, source);
155
+ });
156
+ this._setApiSelectionWithOtherProduct = (s, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
148
157
  // Wrap with cache will make getProduct for this function call use the same server call
149
158
  // for the same product with the same params. Used for getProduct (when a new additional
150
159
  // product is loaded) and postValidate.
151
160
  productLoaderForGroupedLoad =
152
161
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
162
+ let change = false;
163
+ if (sourceProduct !== undefined) {
164
+ this._rawProductData = sourceProduct.rawProductData;
165
+ this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs);
166
+ change = true; // We can not know if this is an actual change, so we assume it is
167
+ }
153
168
  const configurationChange = yield this.configuration._internal.setApiSelection(s.selOptions, false);
154
- let change = configurationChange;
169
+ if (configurationChange) {
170
+ change = true;
171
+ }
155
172
  if (this.optional) {
156
173
  if (yield this.setSelected(s.selected !== false, CfgProductBubbleMode.Stop)) {
157
174
  change = true;
@@ -165,17 +182,23 @@ export class _CfgProductInternal {
165
182
  if (apiSelectionAdditionalProductsCount !== additionalProductsCount) {
166
183
  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}`);
167
184
  }
168
- if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionChild) => {
169
- const refKey = apiSelectionChild.refKey;
170
- if (refKey === undefined) {
171
- throw new Error("Additional product api configurations must have refKey.");
172
- }
185
+ const sourceProductAdditionalProducts = sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct.additionalProducts;
186
+ assert(!sourceProductAdditionalProducts ||
187
+ additionalProductsCount === sourceProductAdditionalProducts.length, `Passed sourceProduct does not have the same number of additional products as this.`);
188
+ if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct, index) => {
189
+ var _a;
190
+ const refKey = apiSelectionAdditionalProduct.refKey;
191
+ assertDefined(refKey, "Additional product api configurations must have refKey.");
173
192
  const i = additionalProducts.findIndex((a) => refKey === a.refKey);
174
- if (i === -1) {
175
- throw new Error(`Additional product not found. This product: "${this.key}". refKey of not found additional product: "${refKey}"`);
193
+ assert(i !== -1, `Additional product not found. This product: "${this.key}". refKey of not found additional product: "${refKey}"`);
194
+ let sourceProductAdditionalProduct = undefined;
195
+ if (sourceProductAdditionalProducts !== undefined) {
196
+ sourceProductAdditionalProduct =
197
+ (_a = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _a === void 0 ? void 0 : _a._internal;
198
+ assertDefined(sourceProductAdditionalProduct, "Additional product not found in sourceProduct");
176
199
  }
177
200
  const additionalProduct = additionalProducts.splice(i, 1)[0]; // Splicing like this is okay because this is done synchronous. The setCon. is what is async.
178
- return additionalProduct._internal.setApiSelection(apiSelectionChild, doValidate, productLoaderForGroupedLoad);
201
+ return additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
179
202
  }))).some((b) => b)) {
180
203
  change = true;
181
204
  }
@@ -307,7 +330,7 @@ export class _CfgProductInternal {
307
330
  return true;
308
331
  }
309
332
  catch (e) {
310
- throw e;
333
+ throw augmentErrorMessage(e, "Validate product configuration request failure");
311
334
  }
312
335
  finally {
313
336
  this.loadingObservable.stopChildLoading(token);
@@ -322,7 +345,14 @@ export class _CfgProductInternal {
322
345
  const additionalProductRefs = [
323
346
  ...(productData.additionalProductRefs || []),
324
347
  ...collectAdditionalProductRefs(configuration),
325
- ].map((prodRef, originalIndex) => ({
348
+ ]
349
+ .reduce((a, c) => {
350
+ if (a.every((p) => !isSameProductRef(p, c))) {
351
+ a.push(c);
352
+ }
353
+ return a;
354
+ }, [])
355
+ .map((prodRef, originalIndex) => ({
326
356
  prodRef,
327
357
  originalIndex,
328
358
  }));
@@ -330,14 +360,19 @@ export class _CfgProductInternal {
330
360
  let i = currentAdditionalProducts.length;
331
361
  while (i--) {
332
362
  const currentAdditionalProduct = currentAdditionalProducts[i];
333
- const refKey = currentAdditionalProduct.refKey;
334
- const j = additionalProductRefs.findIndex((p) => p.prodRef.refKey === refKey);
363
+ const j = additionalProductRefs.findIndex((p) => {
364
+ const prodRef = p.prodRef;
365
+ return (prodRef.refKey === currentAdditionalProduct.refKey &&
366
+ prodRef.partNumber === currentAdditionalProduct.partNumber &&
367
+ isSameCatalogueParams(prodRef.catId, currentAdditionalProduct.catId));
368
+ });
335
369
  if (j === -1) {
336
370
  currentAdditionalProduct.destroy();
337
371
  currentAdditionalProducts.splice(i, 1);
338
372
  change = true;
339
373
  }
340
374
  else {
375
+ currentAdditionalProduct._internal._updateAdditionalProdRef(additionalProductRefs[j].prodRef);
341
376
  additionalProductRefs.splice(j, 1);
342
377
  }
343
378
  }
@@ -350,7 +385,7 @@ export class _CfgProductInternal {
350
385
  const additionalProductRef = p.prodRef;
351
386
  return {
352
387
  originalIndex: p.originalIndex,
353
- product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, lang, additionalProductRef.catId, additionalProductRef.partNumber, this.settings, additionalProductRef.optional === true, this.loadingObservable, additionalProductRef.refKey, additionalProductRef.refDescription, this, this.root, additionalProductRef.transform, additionalProductRef.anchor)),
388
+ product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, lang, additionalProductRef.catId, additionalProductRef.partNumber, this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef)),
354
389
  };
355
390
  }))()));
356
391
  if (this._destroyed) {
@@ -361,15 +396,12 @@ export class _CfgProductInternal {
361
396
  }
362
397
  return change;
363
398
  }
364
- catch (e) {
365
- throw e;
366
- }
367
399
  finally {
368
400
  this.loadingObservable.stopChildLoading(token);
369
401
  }
370
402
  });
371
- this.root = root || this;
372
- this.key = makeProductKey(catId, refKey || partNumber);
403
+ this.root = root !== null && root !== void 0 ? root : this;
404
+ this.key = makeProductKey(catId, (_a = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _a !== void 0 ? _a : partNumber);
373
405
  this._selected = optional ? selected : undefined;
374
406
  this.isAdditionalProduct = parent !== undefined;
375
407
  this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, apiSelection, this, this.root);
@@ -380,9 +412,10 @@ export class _CfgProductInternal {
380
412
  clone(parent, root) {
381
413
  return __awaiter(this, void 0, void 0, function* () {
382
414
  const product = yield new Promise((initSuccess, initFail) => {
415
+ var _a;
383
416
  const p = new _CfgProductInternal(() => {
384
417
  initSuccess(p);
385
- }, initFail, this._productLoaderRaw, this.lang, this.catId, this.partNumber, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.allRawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration.getApiSelection(), new AggregatedLoadingObservable(), this.refKey, this.refDescription, parent, root, this.transform, this.anchor);
418
+ }, initFail, this._productLoaderRaw, this.lang, this.catId, this.partNumber, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.allRawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration.getApiSelection(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
386
419
  });
387
420
  for (const additionalProduct of this.additionalProducts) {
388
421
  product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
@@ -390,8 +423,15 @@ export class _CfgProductInternal {
390
423
  return product;
391
424
  });
392
425
  }
426
+ _updateAdditionalProdRef(p) {
427
+ this._additionalProductRef = p;
428
+ if (p.optional !== this.optional) {
429
+ this._selected = p.optional ? false : undefined;
430
+ }
431
+ }
393
432
  get description() {
394
- return this.refDescription || this._rawProductData.description;
433
+ var _a, _b;
434
+ return (_b = (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.refDescription) !== null && _b !== void 0 ? _b : this._rawProductData.description;
395
435
  }
396
436
  get rootNodeSources() {
397
437
  return this._rawProductData.models;
@@ -417,6 +457,18 @@ export class _CfgProductInternal {
417
457
  }
418
458
  return this._measureDefinitions;
419
459
  }
460
+ get refKey() {
461
+ var _a;
462
+ return (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.refKey;
463
+ }
464
+ get transform() {
465
+ var _a;
466
+ return (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.transform;
467
+ }
468
+ get anchor() {
469
+ var _a;
470
+ return (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.anchor;
471
+ }
420
472
  /** @throws an error if the actual unit sent by the server was not a LengthUnit */
421
473
  get unit() {
422
474
  if (this._unit === undefined) {
@@ -494,26 +546,53 @@ export class _CfgProductInternal {
494
546
  ? this.visibleIfAdditionalProduct
495
547
  : this.visibleIfMainProduct;
496
548
  }
549
+ get syncGroupHandler() {
550
+ return this.root._syncGroupHandler;
551
+ }
552
+ get syncGroupsVerboseLogging() {
553
+ var _a, _b;
554
+ return (_b = (_a = this.syncGroupHandler) === null || _a === void 0 ? void 0 : _a.verboseLogging) !== null && _b !== void 0 ? _b : false;
555
+ }
556
+ /**
557
+ * Set to true to get verbose sync state changes logged to the console.
558
+ */
559
+ set syncGroupsVerboseLogging(v) {
560
+ const syncGroupHandler = this.syncGroupHandler;
561
+ if (syncGroupHandler === undefined) {
562
+ throw new Error("No syncGroupHandler, so can not change log verbosity");
563
+ }
564
+ syncGroupHandler.verboseLogging = v;
565
+ }
497
566
  }
498
567
  _CfgProductInternal.make = (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
499
- lang, catId, partNumber, settings, optional, loadingObservable, refKey, refDescription, parent, root, transform, anchor) => __awaiter(void 0, void 0, void 0, function* () {
568
+ lang, catId, partNumber, settings, optional, loadingObservable, parent, root, additionalProductRef) => __awaiter(void 0, void 0, void 0, function* () {
500
569
  // Wrap with cache will make getProduct for this function call use the same server call
501
570
  // for the same product with the same params. Not retained for future calls, only used
502
571
  // at this initial load.
503
572
  productLoaderForGroupedLoad =
504
573
  productLoaderForGroupedLoad || wrapWithCache(productLoaderRaw);
505
- const productResponse = yield productLoaderForGroupedLoad.getProduct(Object.assign(Object.assign({ lang }, correctDefaultsOnCatalogueParams(catId)), { partNumber }));
506
- const { productData, rootFeatureRefs, features: allRawFeatures, uuid, unit, } = productResponse;
507
- const product = yield new Promise((initSuccess, initFail) => {
508
- const p = new _CfgProductInternal(() => {
509
- // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
510
- // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
511
- // Feel free to find a nicer more readable solution :)
512
- initSuccess(p);
513
- }, initFail, productLoaderRaw, lang, catId, partNumber, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, refKey, refDescription, parent, root, transform, anchor);
514
- });
515
- yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
516
- return product;
574
+ const syncGroupHandler = root === undefined
575
+ ? SyncGroupsHandler.make(settings.syncGroupsApplyMode, loadingObservable)
576
+ : undefined;
577
+ try {
578
+ const productResponse = yield productLoaderForGroupedLoad.getProduct(Object.assign(Object.assign({ lang }, correctDefaultsOnCatalogueParams(catId)), { partNumber }));
579
+ const { productData, rootFeatureRefs, features: allRawFeatures, uuid, unit, } = productResponse;
580
+ const product = yield new Promise((initSuccess, initFail) => {
581
+ const p = new _CfgProductInternal(() => {
582
+ // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
583
+ // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
584
+ // Feel free to find a nicer more readable solution :)
585
+ initSuccess(p);
586
+ }, initFail, productLoaderRaw, lang, catId, partNumber, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
587
+ });
588
+ yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
589
+ // Product is guaranteed to be root
590
+ yield (syncGroupHandler === null || syncGroupHandler === void 0 ? void 0 : syncGroupHandler.init(product, productLoaderForGroupedLoad));
591
+ return product;
592
+ }
593
+ catch (e) {
594
+ throw augmentErrorMessage(e, "Load product request failure");
595
+ }
517
596
  });
518
597
  export class CfgProduct {
519
598
  constructor(_internal) {
@@ -563,12 +642,12 @@ export class CfgProduct {
563
642
  }
564
643
  static make(productLoader, lang, catId, partNumber, settings) {
565
644
  return __awaiter(this, void 0, void 0, function* () {
566
- return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, lang, catId, partNumber, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined, undefined, undefined, undefined));
645
+ return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, lang, catId, partNumber, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined));
567
646
  });
568
647
  }
569
648
  /**
570
649
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to
571
- * make a new outer reference. Like a shallow copy./ We use this to help frameworks that are
650
+ * make a new outer reference. Like a shallow copy. We use this to help frameworks that are
572
651
  * build around using equals to detect change.
573
652
  */
574
653
  static _makeNewRefFrom(source) {
package/dist/index.d.ts CHANGED
@@ -11,6 +11,8 @@ export * from "./productConfiguration/CfgProductConfiguration.js";
11
11
  export * from "./productConfiguration/filters.js";
12
12
  export * from "./productConfiguration/productParamsGenerator.js";
13
13
  export * from "./productLoader.js";
14
+ export * from "./syncGroups/SyncGroupsApplyMode.js";
15
+ export * from "./syncGroups/SyncGroupsHandler.js";
14
16
  export * from "./tasks/formats.js";
15
17
  export * from "./tasks/TaskHandler.js";
16
18
  export * from "./utilitiesCatalogueData.js";
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ export * from "./productConfiguration/CfgProductConfiguration.js";
11
11
  export * from "./productConfiguration/filters.js";
12
12
  export * from "./productConfiguration/productParamsGenerator.js";
13
13
  export * from "./productLoader.js";
14
+ export * from "./syncGroups/SyncGroupsApplyMode.js";
15
+ export * from "./syncGroups/SyncGroupsHandler.js";
14
16
  export * from "./tasks/formats.js";
15
17
  export * from "./tasks/TaskHandler.js";
16
18
  export * from "./utilitiesCatalogueData.js";
@@ -1,7 +1,8 @@
1
1
  import { LengthUnit, Observable, SingleArgCallback } from "@configura/web-utilities";
2
- import { Feature, SelectedOption } from "../CatalogueAPI.js";
2
+ import { Feature, SelectedOption, SyncGroup, SyncGroupMethods } from "../CatalogueAPI.js";
3
3
  import { CfgProduct, _CfgProductInternal } from "../CfgProduct.js";
4
4
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
5
+ import { SyncCode } from "../syncGroups/SyncGroupsHandler.js";
5
6
  import { CfgOption, ProductConfigurationBubbleMode, _CfgOptionInternal } from "./CfgOption.js";
6
7
  import { _CfgProductConfigurationInternal } from "./CfgProductConfiguration.js";
7
8
  export declare enum SelectionType {
@@ -50,6 +51,14 @@ export declare class _CfgFeatureInternal {
50
51
  get numericValue(): number | undefined;
51
52
  setNumericValue: (val: number) => Promise<boolean>;
52
53
  get description(): string;
54
+ get syncGroup(): SyncGroup | undefined;
55
+ /**
56
+ * @return one of the following, in order:
57
+ * - undefined if the Feature lacks a syncGroup.
58
+ * - false if the syncGroup doesn't fulfill the optional mustSupport requirement.
59
+ * - syncCode from the syncGroup.
60
+ */
61
+ getSyncCode(mustSupport?: SyncGroupMethods): undefined | false | SyncCode;
53
62
  /**
54
63
  * The MeasureParam class is re-used for different purposes. In Features it is used
55
64
  * to indicate which stretch measures inside Models shall be affected by this state
@@ -100,8 +109,9 @@ export declare class _CfgFeatureInternal {
100
109
  structureCompare: (other: _CfgFeatureInternal, strictOrder?: boolean, descriptionMatch?: boolean) => boolean;
101
110
  tryMatchSelection: (other: _CfgFeatureInternal, descriptionMatch?: boolean) => Promise<boolean>;
102
111
  /**
103
- * Normally this is used through methods on CfgFeature and CfgOption. Use this
104
- * internal version if you need to control the bubbleMode.
112
+ * Normally this is used through methods on CfgFeature and CfgOption. Use this internal version
113
+ * if you need to control the bubbleMode.
114
+ *
105
115
  * Using a validate bubbleMode will cause validation calls to the server.
106
116
  */
107
117
  selectOption: (optionInternal: _CfgOptionInternal, on: boolean, bubbleMode: ProductConfigurationBubbleMode) => Promise<boolean>;