@configura/web-api 2.2.0-alpha.1 → 2.2.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 (102) hide show
  1. package/.eslintrc.json +5 -5
  2. package/LICENSE +201 -201
  3. package/README.md +1 -1
  4. package/dist/CatalogueAPI.d.ts +633 -633
  5. package/dist/CatalogueAPI.js +312 -312
  6. package/dist/CfgMeasure.d.ts +32 -32
  7. package/dist/CfgMeasure.js +30 -30
  8. package/dist/CfgProduct.d.ts +359 -344
  9. package/dist/CfgProduct.js +1005 -992
  10. package/dist/CfgReferencePathHelper.d.ts +26 -26
  11. package/dist/CfgReferencePathHelper.js +26 -26
  12. package/dist/index.d.ts +24 -24
  13. package/dist/index.js +24 -24
  14. package/dist/io/CfgHistoryManager.d.ts +83 -83
  15. package/dist/io/CfgHistoryManager.js +144 -144
  16. package/dist/io/CfgHistoryToProdConfConnector.d.ts +21 -21
  17. package/dist/io/CfgHistoryToProdConfConnector.js +50 -50
  18. package/dist/io/CfgIOManager.d.ts +53 -53
  19. package/dist/io/CfgIOManager.js +134 -134
  20. package/dist/io/CfgIOProdConfConnector.d.ts +54 -54
  21. package/dist/io/CfgIOProdConfConnector.js +139 -139
  22. package/dist/io/CfgIOWarningSupplier.d.ts +3 -3
  23. package/dist/io/CfgIOWarningSupplier.js +1 -1
  24. package/dist/io/CfgObservableStateManager.d.ts +25 -25
  25. package/dist/io/CfgObservableStateManager.js +69 -69
  26. package/dist/io/CfgObservableStateToProdConfConnector.d.ts +15 -15
  27. package/dist/io/CfgObservableStateToProdConfConnector.js +17 -17
  28. package/dist/io/CfgWindowEventManager.d.ts +21 -21
  29. package/dist/io/CfgWindowEventManager.js +38 -38
  30. package/dist/io/CfgWindowMessageManager.d.ts +40 -40
  31. package/dist/io/CfgWindowMessageManager.js +91 -91
  32. package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +17 -17
  33. package/dist/io/CfgWindowMessageToProdConfConnector.js +19 -19
  34. package/dist/io/index.d.ts +8 -8
  35. package/dist/io/index.js +8 -8
  36. package/dist/material/CfgMaterialMapping.d.ts +7 -7
  37. package/dist/material/CfgMaterialMapping.js +181 -181
  38. package/dist/material/CfgMtrlApplication.d.ts +18 -18
  39. package/dist/material/CfgMtrlApplication.js +43 -43
  40. package/dist/material/CfgMtrlApplicationSource.d.ts +7 -7
  41. package/dist/material/CfgMtrlApplicationSource.js +8 -8
  42. package/dist/material/CfgMtrlSource.d.ts +19 -19
  43. package/dist/material/CfgMtrlSource.js +40 -40
  44. package/dist/material/CfgMtrlSourceWithMetaData.d.ts +7 -7
  45. package/dist/material/CfgMtrlSourceWithMetaData.js +1 -1
  46. package/dist/productConfiguration/CfgFeature.d.ts +199 -199
  47. package/dist/productConfiguration/CfgFeature.js +691 -691
  48. package/dist/productConfiguration/CfgOption.d.ts +160 -160
  49. package/dist/productConfiguration/CfgOption.js +464 -464
  50. package/dist/productConfiguration/CfgProductConfiguration.d.ts +136 -129
  51. package/dist/productConfiguration/CfgProductConfiguration.js +355 -346
  52. package/dist/productConfiguration/filters.d.ts +17 -17
  53. package/dist/productConfiguration/filters.js +141 -141
  54. package/dist/productConfiguration/productParamsGenerator.d.ts +15 -15
  55. package/dist/productConfiguration/productParamsGenerator.js +65 -65
  56. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +17 -17
  57. package/dist/productConfiguration/utilitiesProductConfiguration.js +89 -87
  58. package/dist/productLoader.d.ts +33 -33
  59. package/dist/productLoader.js +49 -49
  60. package/dist/syncGroups/SyncGroupsApplyMode.d.ts +20 -20
  61. package/dist/syncGroups/SyncGroupsApplyMode.js +21 -21
  62. package/dist/syncGroups/SyncGroupsHandler.d.ts +47 -47
  63. package/dist/syncGroups/SyncGroupsHandler.js +370 -370
  64. package/dist/syncGroups/SyncGroupsPathHelper.d.ts +26 -26
  65. package/dist/syncGroups/SyncGroupsPathHelper.js +90 -90
  66. package/dist/syncGroups/SyncGroupsState.d.ts +39 -39
  67. package/dist/syncGroups/SyncGroupsState.js +167 -167
  68. package/dist/syncGroups/SyncGroupsTransaction.d.ts +154 -154
  69. package/dist/syncGroups/SyncGroupsTransaction.js +589 -589
  70. package/dist/tasks/TaskHandler.d.ts +77 -77
  71. package/dist/tasks/TaskHandler.js +276 -276
  72. package/dist/tasks/formats.d.ts +4 -4
  73. package/dist/tasks/formats.js +7 -7
  74. package/dist/tests/testData/collectorForTest.d.ts +73 -73
  75. package/dist/tests/testData/collectorForTest.js +194 -194
  76. package/dist/tests/testData/dummyProductForTest.d.ts +4 -4
  77. package/dist/tests/testData/dummyProductForTest.js +32 -32
  78. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.d.ts +11 -11
  79. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +282 -282
  80. package/dist/tests/testData/testDataCachedGetProduct.d.ts +5 -5
  81. package/dist/tests/testData/testDataCachedGetProduct.js +187 -187
  82. package/dist/tests/testData/testDataCachedPostValidate.d.ts +7 -7
  83. package/dist/tests/testData/testDataCachedPostValidate.js +185 -185
  84. package/dist/tests/testData/testDataConstraints.d.ts +3 -3
  85. package/dist/tests/testData/testDataConstraints.js +174 -174
  86. package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.d.ts +3 -3
  87. package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.js +1099 -1099
  88. package/dist/tests/testData/testDataOptions.d.ts +12 -12
  89. package/dist/tests/testData/testDataOptions.js +60 -60
  90. package/dist/tests/testData/testDataProductAggregatedPrice.d.ts +6 -6
  91. package/dist/tests/testData/testDataProductAggregatedPrice.js +189 -189
  92. package/dist/tests/testData/testDataUpcharge.d.ts +8 -8
  93. package/dist/tests/testData/testDataUpcharge.js +121 -121
  94. package/dist/utilitiesCatalogueData.d.ts +47 -47
  95. package/dist/utilitiesCatalogueData.js +180 -180
  96. package/dist/utilitiesCataloguePermission.d.ts +38 -38
  97. package/dist/utilitiesCataloguePermission.js +79 -79
  98. package/dist/utilitiesConfiguration.d.ts +28 -28
  99. package/dist/utilitiesConfiguration.js +200 -200
  100. package/dist/utilitiesNumericValues.d.ts +24 -24
  101. package/dist/utilitiesNumericValues.js +114 -114
  102. package/package.json +3 -3
@@ -1,370 +1,370 @@
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
- import { SelectionType } from "../productConfiguration/CfgFeature.js";
11
- import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
12
- import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
13
- import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
14
- import { SyncGroupsState } from "./SyncGroupsState.js";
15
- import { SyncGroupsTransaction } from "./SyncGroupsTransaction.js";
16
- /* SyncGroup Concepts
17
- * ==================
18
- *
19
- * SyncGroups are a concept in Catalogues that gives the creator the option to attempt to
20
- * synchronize selections between otherwise independent Features.
21
- *
22
- * Each Feature can optionally specify a "Sync Group Code" in the Catalogue. All features with the
23
- * same sync group code is said to belong to the same Sync Group.
24
- *
25
- * In addition, each Feature that is part of a Sync Group should have a "Sync Mode" set which can
26
- * be either "Read", "Write" or "Read and Write". The code in the SDK refers to them as "pull",
27
- * "push" and "twoWay" respectively.
28
- *
29
- * The current state of all the SyncGroups is stored in a SyncState. The sync state keeps track of
30
- * each sync group even if no features are currently visible for that sync group code. In that way,
31
- * the sync state acts like a kind of short-term memory when a user is configuring a product.
32
- *
33
- * The sync state is always discarded when you leave or reload a product and is thus always an
34
- * empty slate when a product is loaded.
35
- *
36
- *
37
- * Best Effort and Conflicts
38
- * =========================
39
- *
40
- * Catalogues had been along for a long time when the Sync Group functionality was added in 2021,
41
- * and as a result, it is sort of a layer on top off the normal handling of selecting Options on
42
- * Features and it is applied in a "best effort"-manner.
43
- *
44
- * Two or more Features belonging to the same SyncGroup shows the intent that they should be
45
- * synchronized, with how being controlled by their respective sync mode.
46
- *
47
- * However, the Catalogue format is not strict on how you define the features you want tp keep
48
- * synchronized. They can for example have only partially overlapping domains (i.e. what Options
49
- * can be selected) or even no overlap at all which means there is no guarantee that they can
50
- * always stay in sync.
51
- *
52
- * Using different sync modes and bringing features into scope also adds complexity as well as
53
- * power to create "creative" solutions to design problems.
54
- *
55
- * There is also a use case for adding a unique sync group code to single features, which signals
56
- * the intent that the creator wants those features to keep their previously selected values even
57
- * when they are not currently visible in the current configuration.
58
- *
59
- * In the end, it's up to the Catalogue creator to build the products inside their Catalogues so
60
- * that his or her intentions are met without unexpected side effects.
61
- *
62
- *
63
- * Technical Details
64
- * =================
65
- *
66
- * Feature types
67
- * -------------
68
- *
69
- * Features can be of three types. (Or that is the model we use in Stage, it seems to hold.)
70
- *
71
- * 1. SelectOne Features are like radio buttons, one Option is selected at a time.
72
- * 2. SelectMany Features ("optional" in Catalogues) are like check boxes, any number of Options
73
- * are selected at a time.
74
- * 3. Group Features ("multiple" in Catalogues) act like a grouping mechanism where all the Options
75
- * are permanently selected. No user interaction allowed.
76
- *
77
- * Group Features work like any other Feature without SyncGroups. They are just transparent.
78
- *
79
- * SelectOne and SelectMany have fully separated sync states. This means that there is never a sync
80
- * connection between SelectOne and SelectMany Features even if they have the same Sync Group Code.
81
- *
82
- * In other words, SelectOne Features only sync with other SelectOne features and vice versa.
83
- *
84
- * On what level sync happens is different between SelectOne and SelectMany Features. For SelectOne
85
- * we store an option code per SyncGroup. For SelectMany we store "On" or "Off" per option code per
86
- * SyncGroup.
87
- *
88
- * You can say that SelectOne is synced on Feature level, and SelectMany is synced on Option level.
89
- *
90
- * Note that the same Feature (as in feature code) can exist in multiple places in a configuration
91
- * tree. In this context, they are treated as individual Features that just happens to have an
92
- * identical set of settings in the Catalogue.
93
- *
94
- *
95
- * Transactions
96
- * ------------
97
- *
98
- * Selecting an Option in a Feature can trigger a bunch of changes, each of them cascading into new
99
- * features coming into scope and in turn changing other SyncGroups. As far as the user goes these
100
- * changes is under the hood and is part of a single change triggered by the first selection.
101
- *
102
- * This is also how it is implemented. All the changes created by the initial selection are
103
- * validated and propagated inside a single "transaction". The transaction is considered complete
104
- * when the last round of changes did not trigger any new changes in the sync state.
105
- *
106
- * In every transaction, each SyncGroup is allowed to be updated only once for SelectOne features or
107
- * once per unique option code in the case of SelectMany features. This is done to ensure that the
108
- * propagated changes is guaranteed to stabilize over time and eliminates the risk of loops.
109
- *
110
- * Since a Feature can only have a single sync group code, this also means that a Feature will be
111
- * changed at most once during a single transaction.
112
- *
113
- *
114
- * Applying SyncGroups => Features/Options
115
- * ---------------------------------------
116
- *
117
- * The state of the SyncGroups (as stored in the SyncState) is applied to a Feature/Option under
118
- * the following conditions:
119
- * A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
120
- * B) The feature's sync mode is set to "pull" or "twoWay".
121
- *
122
- * ...for SelectOne:
123
- * C) The SyncState has an option code for this SyncGroup.
124
- * D) The option code in the SyncState for the SyncGroup is not the Option selected.
125
- * E) The Feature has an Option with the right option code.
126
- * F) The Feature has not previously been affected in this transaction, unless it was affected
127
- * at Feature initialization.
128
- *
129
- * ...for SelectMany (done for every Option):
130
- * C) The SyncState has a value (on or off) for this SyncGroup and option code.
131
- * D) The Option is on when it should be off or the other way around.
132
- * E) The Option has not previously been affected in this transaction.
133
- *
134
- * Applying the sync state typically happen when:
135
- * - A user action on a Feature has changed the SyncState.
136
- * - Features "comes into scope"
137
- *
138
- * A feature "coming into scope" refers features "appearing" during for example a Product load, a
139
- * parent feature changing selected options to expose new children or an Additional Product coming
140
- * into scope.
141
- *
142
- *
143
- * Applying Features/Options => SyncGroups
144
- * ---------------------------------------
145
- *
146
- * The Feature/Option is applied to the SyncState under the following conditions:
147
- * A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
148
- * B) The feature's sync mode is set to "push" or "twoWay".
149
- *
150
- * ...for SelectOne:
151
- * C) The Option selected is not the option code currently in the SyncState for the SyncGroup.
152
- * D) The SyncState has not been affected for this SyncGroup in this transaction.
153
- * E) Any one of the below:
154
- * - A user actively selects an Option.
155
- * - There is no option code in the SyncState for this SyncGroup.
156
- * - The Feature did just come into scope, and the option code in the SyncState for this
157
- * SyncGroup is not one of the Options in the Feature. The option code is not in the Feature
158
- * Domain for this Feature that is. So, a Feature appearing which cannot take it's selection
159
- * from the SyncState will instead write it back.
160
- *
161
- * ...for SelectMany (for each Option):
162
- * C) The SyncState has not been affected for this SyncGroup and option code in this transaction.
163
- * D) Any one of the below:
164
- * - A user actively selects or deselects an Option, and the on or off status differs from what
165
- * is in the SyncState for the SyncGroup and option code.
166
- * - There is no entry for SyncGroup and option code in the SyncState and the Option is
167
- * selected. We only implicitly initialize the SyncState for "on" as there is no way to
168
- * explicitly chose what is default of in Catalogues.
169
- *
170
- * This typically happens when:
171
- * - A user selects or deselects an option.
172
- * - Features comes into scope, like at Product load, a parent feature changing selected options to
173
- * expose new children or an Additional Product coming into scope.
174
- *
175
- *
176
- * Implementation Details
177
- * ======================
178
- *
179
- * There are two entry points into the process. One for when a new Product is loaded, when we want
180
- * to move it into a synced state. The other is when a user selects or deselects an Option.
181
- *
182
- * The process can be thought of as a state machine with three states or stages:
183
- * A) Select/deselect Option and force to SyncState
184
- * B) Apply the SyncState onto the Product
185
- * C) Apply the Product onto the SyncState
186
- *
187
- * The entry point for a new Product is C. This makes sense as a new Product has no SyncState at
188
- * all, so applying the Product onto the SyncState is good start.
189
- *
190
- * The entry point for user selection is A.
191
- *
192
- * Let's look at how we move between states starting with A.
193
- *
194
- * State A will apply the selection onto the Option. It will write to the SyncState. It will then
195
- * transition to state B.
196
- *
197
- * State A is only run once for each user interaction, and not at all for Product Load.
198
- *
199
- * State B will apply the SyncState onto Features by recursively going through the configuration
200
- * tree. Once it finds a Feature/Option which should be changed by the state it:
201
- * - Checks if it can change it (it can't if the belongs to a so far uninitialized SyncGroup) and
202
- * if it can then:
203
- * - Changes the selection on the Feature.
204
- * - Add the parent Product for the Feature to a list of Products for which a validate call
205
- * must later be made.
206
- * - Stop recursing down this branch as the validation call might actually change the structure
207
- * and children in the branch.
208
- *
209
- * Once we have recursed the entire tree the result can be either:
210
- * - There are Products to validate.
211
- * Then we do the validations, and once they are done we move back into state B. We run it again
212
- * as we did not recurse all the way down the branches that changed value. With each run, we will
213
- * get further out on the branches until finally reaching the end.
214
- * - There are no Products to validate.
215
- * This means that there were no Features which we could apply the SyncState onto. We now move
216
- * into state C as the selection changes may have brought new Features into scope, Features which
217
- * might write to the SyncState.
218
- *
219
- * State C will apply Features onto the SyncState. It will recursively go through the configuration
220
- * tree. When it's done one of two has happened:
221
- * - There was a write to the SyncState.
222
- * We then move back to state B, as this change in the SyncState could open up things in B. For
223
- * example a Feature that could not pull from the SyncState before might be able to do that now.
224
- * - There was no write to the SyncState.
225
- * All is calm. We are done. The settling of the SyncState vs configuration is done. Things are
226
- * as in sync as they will be. This is the exit.
227
- *
228
- *
229
- * Side Notes
230
- * ==========
231
- *
232
- * _CfgProductInternal, _CfgFeatureInternal and _CfgOptionInternal are used in Sets and Maps in the
233
- * code. These objects are only created once for the data they represent. That is, even if a Feature
234
- * goes in or out of scope the object is the same. This is not true for their wrapper classer
235
- * CfgProduct, CfgFeature and CfgOption. Those are frequently replaced while running.
236
- *
237
- */
238
- /**
239
- * Send to root for the passed option and any sibling which is selected, provided the Feature
240
- * is SelectOne. (As that one (should only be one) can be assumed to be affected).
241
- */
242
- function notifyOptionAndSelectedSiblings(option) {
243
- return __awaiter(this, void 0, void 0, function* () {
244
- const committed = false;
245
- const parentFeature = option.parent;
246
- if (parentFeature.selectionType === SelectionType.SelectOne) {
247
- // These only need to be OneLevel, as the final is ToRoot and they share their parent.
248
- yield Promise.all(option.parent.selectedOptions.map((o) => parentFeature._childHasChanged(o._internal, ProductConfigurationBubbleMode.OneLevel, committed)));
249
- }
250
- yield parentFeature._childHasChanged(option, ProductConfigurationBubbleMode.ToRoot, committed);
251
- });
252
- }
253
- /**
254
- * Is used to apply the SyncGroups functionality on the Configuration and the other way around.
255
- * It also keeps the SyncState.
256
- */
257
- export class SyncGroupsHandler {
258
- constructor(_syncState, updateMode, _loadingObservable) {
259
- this._syncState = _syncState;
260
- this.updateMode = updateMode;
261
- this._loadingObservable = _loadingObservable;
262
- }
263
- /**
264
- * @param verboseLogging Set to true to get verbose sync state changes logged to the console.
265
- */
266
- static make(updateMode = SyncGroupsApplyMode.Strict, loadingObservable, initial, verboseLogging = false) {
267
- return new SyncGroupsHandler(new SyncGroupsState(verboseLogging, initial), updateMode, loadingObservable);
268
- }
269
- /** Please note that clones will use the same loadingObservable as their source. */
270
- clone() {
271
- return new SyncGroupsHandler(this._syncState.clone(), this.updateMode, this._loadingObservable);
272
- }
273
- getCompactSyncGroupState() {
274
- return this._syncState.getCompact();
275
- }
276
- /** Overwrites the sync state */
277
- setCompactSyncGroupState(s) {
278
- this._syncState.setCompact(s);
279
- }
280
- get verboseLogging() {
281
- return this._syncState.verboseLogging;
282
- }
283
- set verboseLogging(v) {
284
- this._syncState.verboseLogging = v;
285
- }
286
- /**
287
- * Used to initially apply the sync state onto a new product so that it is "in sync"
288
- * and to reapply the sync state when an optional additional product is selected.
289
- */
290
- init(product, productLoader) {
291
- return __awaiter(this, void 0, void 0, function* () {
292
- const transaction = yield this.newTransaction(product, productLoader, true);
293
- try {
294
- yield transaction.applyRootProduct();
295
- yield this.commitTransaction(transaction);
296
- }
297
- finally {
298
- this.closeTransaction(transaction);
299
- }
300
- });
301
- }
302
- /**
303
- * Used when an Option is selected or deselected to apply all consequences of the sync groups.
304
- * Can cause multiple extra validation calls to the server.
305
- */
306
- selectOption(product, option, on, productLoader) {
307
- return __awaiter(this, void 0, void 0, function* () {
308
- if (product.parent !== undefined) {
309
- console.info("Normally the product passed shall be the root product");
310
- }
311
- yield this.setPending(option);
312
- const transaction = yield this.newTransaction(product, productLoader, false);
313
- try {
314
- const change = yield transaction.selectOption(SyncGroupsPathHelper.getPath(option), on);
315
- // Always commit the transaction. The change-result above only tells if the
316
- // configuration has changed. The SyncState may however also have changed.
317
- yield this.commitTransaction(transaction);
318
- return change;
319
- }
320
- finally {
321
- if (this._pending === option) {
322
- yield this.setPending(undefined);
323
- }
324
- this.closeTransaction(transaction);
325
- }
326
- });
327
- }
328
- setPending(newPending) {
329
- return __awaiter(this, void 0, void 0, function* () {
330
- const oldPending = this._pending;
331
- this._pending = newPending;
332
- if (oldPending !== undefined) {
333
- yield notifyOptionAndSelectedSiblings(oldPending);
334
- }
335
- if (newPending !== undefined) {
336
- yield notifyOptionAndSelectedSiblings(newPending);
337
- }
338
- });
339
- }
340
- get pending() {
341
- return this._pending;
342
- }
343
- newTransaction(product, productLoader, assumeNoStartProductState) {
344
- var _a;
345
- return __awaiter(this, void 0, void 0, function* () {
346
- if (this._currentTransaction !== undefined) {
347
- this.closeTransaction(this._currentTransaction);
348
- }
349
- const transaction = yield SyncGroupsTransaction.make(this._syncState, this.updateMode, product, productLoader, assumeNoStartProductState);
350
- this._currentTransaction = transaction;
351
- // The transaction object is used as loading token
352
- (_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.startChildLoading(transaction);
353
- return transaction;
354
- });
355
- }
356
- closeTransaction(transaction) {
357
- var _a;
358
- transaction.close();
359
- (_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.stopChildLoading(transaction);
360
- }
361
- commitTransaction(transaction) {
362
- return __awaiter(this, void 0, void 0, function* () {
363
- if (transaction.isClosed) {
364
- return;
365
- }
366
- yield transaction.commit();
367
- this.closeTransaction(transaction);
368
- });
369
- }
370
- }
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
+ import { SelectionType } from "../productConfiguration/CfgFeature.js";
11
+ import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
12
+ import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
13
+ import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
14
+ import { SyncGroupsState } from "./SyncGroupsState.js";
15
+ import { SyncGroupsTransaction } from "./SyncGroupsTransaction.js";
16
+ /* SyncGroup Concepts
17
+ * ==================
18
+ *
19
+ * SyncGroups are a concept in Catalogues that gives the creator the option to attempt to
20
+ * synchronize selections between otherwise independent Features.
21
+ *
22
+ * Each Feature can optionally specify a "Sync Group Code" in the Catalogue. All features with the
23
+ * same sync group code is said to belong to the same Sync Group.
24
+ *
25
+ * In addition, each Feature that is part of a Sync Group should have a "Sync Mode" set which can
26
+ * be either "Read", "Write" or "Read and Write". The code in the SDK refers to them as "pull",
27
+ * "push" and "twoWay" respectively.
28
+ *
29
+ * The current state of all the SyncGroups is stored in a SyncState. The sync state keeps track of
30
+ * each sync group even if no features are currently visible for that sync group code. In that way,
31
+ * the sync state acts like a kind of short-term memory when a user is configuring a product.
32
+ *
33
+ * The sync state is always discarded when you leave or reload a product and is thus always an
34
+ * empty slate when a product is loaded.
35
+ *
36
+ *
37
+ * Best Effort and Conflicts
38
+ * =========================
39
+ *
40
+ * Catalogues had been along for a long time when the Sync Group functionality was added in 2021,
41
+ * and as a result, it is sort of a layer on top off the normal handling of selecting Options on
42
+ * Features and it is applied in a "best effort"-manner.
43
+ *
44
+ * Two or more Features belonging to the same SyncGroup shows the intent that they should be
45
+ * synchronized, with how being controlled by their respective sync mode.
46
+ *
47
+ * However, the Catalogue format is not strict on how you define the features you want tp keep
48
+ * synchronized. They can for example have only partially overlapping domains (i.e. what Options
49
+ * can be selected) or even no overlap at all which means there is no guarantee that they can
50
+ * always stay in sync.
51
+ *
52
+ * Using different sync modes and bringing features into scope also adds complexity as well as
53
+ * power to create "creative" solutions to design problems.
54
+ *
55
+ * There is also a use case for adding a unique sync group code to single features, which signals
56
+ * the intent that the creator wants those features to keep their previously selected values even
57
+ * when they are not currently visible in the current configuration.
58
+ *
59
+ * In the end, it's up to the Catalogue creator to build the products inside their Catalogues so
60
+ * that his or her intentions are met without unexpected side effects.
61
+ *
62
+ *
63
+ * Technical Details
64
+ * =================
65
+ *
66
+ * Feature types
67
+ * -------------
68
+ *
69
+ * Features can be of three types. (Or that is the model we use in Stage, it seems to hold.)
70
+ *
71
+ * 1. SelectOne Features are like radio buttons, one Option is selected at a time.
72
+ * 2. SelectMany Features ("optional" in Catalogues) are like check boxes, any number of Options
73
+ * are selected at a time.
74
+ * 3. Group Features ("multiple" in Catalogues) act like a grouping mechanism where all the Options
75
+ * are permanently selected. No user interaction allowed.
76
+ *
77
+ * Group Features work like any other Feature without SyncGroups. They are just transparent.
78
+ *
79
+ * SelectOne and SelectMany have fully separated sync states. This means that there is never a sync
80
+ * connection between SelectOne and SelectMany Features even if they have the same Sync Group Code.
81
+ *
82
+ * In other words, SelectOne Features only sync with other SelectOne features and vice versa.
83
+ *
84
+ * On what level sync happens is different between SelectOne and SelectMany Features. For SelectOne
85
+ * we store an option code per SyncGroup. For SelectMany we store "On" or "Off" per option code per
86
+ * SyncGroup.
87
+ *
88
+ * You can say that SelectOne is synced on Feature level, and SelectMany is synced on Option level.
89
+ *
90
+ * Note that the same Feature (as in feature code) can exist in multiple places in a configuration
91
+ * tree. In this context, they are treated as individual Features that just happens to have an
92
+ * identical set of settings in the Catalogue.
93
+ *
94
+ *
95
+ * Transactions
96
+ * ------------
97
+ *
98
+ * Selecting an Option in a Feature can trigger a bunch of changes, each of them cascading into new
99
+ * features coming into scope and in turn changing other SyncGroups. As far as the user goes these
100
+ * changes is under the hood and is part of a single change triggered by the first selection.
101
+ *
102
+ * This is also how it is implemented. All the changes created by the initial selection are
103
+ * validated and propagated inside a single "transaction". The transaction is considered complete
104
+ * when the last round of changes did not trigger any new changes in the sync state.
105
+ *
106
+ * In every transaction, each SyncGroup is allowed to be updated only once for SelectOne features or
107
+ * once per unique option code in the case of SelectMany features. This is done to ensure that the
108
+ * propagated changes is guaranteed to stabilize over time and eliminates the risk of loops.
109
+ *
110
+ * Since a Feature can only have a single sync group code, this also means that a Feature will be
111
+ * changed at most once during a single transaction.
112
+ *
113
+ *
114
+ * Applying SyncGroups => Features/Options
115
+ * ---------------------------------------
116
+ *
117
+ * The state of the SyncGroups (as stored in the SyncState) is applied to a Feature/Option under
118
+ * the following conditions:
119
+ * A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
120
+ * B) The feature's sync mode is set to "pull" or "twoWay".
121
+ *
122
+ * ...for SelectOne:
123
+ * C) The SyncState has an option code for this SyncGroup.
124
+ * D) The option code in the SyncState for the SyncGroup is not the Option selected.
125
+ * E) The Feature has an Option with the right option code.
126
+ * F) The Feature has not previously been affected in this transaction, unless it was affected
127
+ * at Feature initialization.
128
+ *
129
+ * ...for SelectMany (done for every Option):
130
+ * C) The SyncState has a value (on or off) for this SyncGroup and option code.
131
+ * D) The Option is on when it should be off or the other way around.
132
+ * E) The Option has not previously been affected in this transaction.
133
+ *
134
+ * Applying the sync state typically happen when:
135
+ * - A user action on a Feature has changed the SyncState.
136
+ * - Features "comes into scope"
137
+ *
138
+ * A feature "coming into scope" refers features "appearing" during for example a Product load, a
139
+ * parent feature changing selected options to expose new children or an Additional Product coming
140
+ * into scope.
141
+ *
142
+ *
143
+ * Applying Features/Options => SyncGroups
144
+ * ---------------------------------------
145
+ *
146
+ * The Feature/Option is applied to the SyncState under the following conditions:
147
+ * A) The Feature belongs to a SyncGroup, i.e. has a Sync Group Code.
148
+ * B) The feature's sync mode is set to "push" or "twoWay".
149
+ *
150
+ * ...for SelectOne:
151
+ * C) The Option selected is not the option code currently in the SyncState for the SyncGroup.
152
+ * D) The SyncState has not been affected for this SyncGroup in this transaction.
153
+ * E) Any one of the below:
154
+ * - A user actively selects an Option.
155
+ * - There is no option code in the SyncState for this SyncGroup.
156
+ * - The Feature did just come into scope, and the option code in the SyncState for this
157
+ * SyncGroup is not one of the Options in the Feature. The option code is not in the Feature
158
+ * Domain for this Feature that is. So, a Feature appearing which cannot take it's selection
159
+ * from the SyncState will instead write it back.
160
+ *
161
+ * ...for SelectMany (for each Option):
162
+ * C) The SyncState has not been affected for this SyncGroup and option code in this transaction.
163
+ * D) Any one of the below:
164
+ * - A user actively selects or deselects an Option, and the on or off status differs from what
165
+ * is in the SyncState for the SyncGroup and option code.
166
+ * - There is no entry for SyncGroup and option code in the SyncState and the Option is
167
+ * selected. We only implicitly initialize the SyncState for "on" as there is no way to
168
+ * explicitly chose what is default of in Catalogues.
169
+ *
170
+ * This typically happens when:
171
+ * - A user selects or deselects an option.
172
+ * - Features comes into scope, like at Product load, a parent feature changing selected options to
173
+ * expose new children or an Additional Product coming into scope.
174
+ *
175
+ *
176
+ * Implementation Details
177
+ * ======================
178
+ *
179
+ * There are two entry points into the process. One for when a new Product is loaded, when we want
180
+ * to move it into a synced state. The other is when a user selects or deselects an Option.
181
+ *
182
+ * The process can be thought of as a state machine with three states or stages:
183
+ * A) Select/deselect Option and force to SyncState
184
+ * B) Apply the SyncState onto the Product
185
+ * C) Apply the Product onto the SyncState
186
+ *
187
+ * The entry point for a new Product is C. This makes sense as a new Product has no SyncState at
188
+ * all, so applying the Product onto the SyncState is good start.
189
+ *
190
+ * The entry point for user selection is A.
191
+ *
192
+ * Let's look at how we move between states starting with A.
193
+ *
194
+ * State A will apply the selection onto the Option. It will write to the SyncState. It will then
195
+ * transition to state B.
196
+ *
197
+ * State A is only run once for each user interaction, and not at all for Product Load.
198
+ *
199
+ * State B will apply the SyncState onto Features by recursively going through the configuration
200
+ * tree. Once it finds a Feature/Option which should be changed by the state it:
201
+ * - Checks if it can change it (it can't if the belongs to a so far uninitialized SyncGroup) and
202
+ * if it can then:
203
+ * - Changes the selection on the Feature.
204
+ * - Add the parent Product for the Feature to a list of Products for which a validate call
205
+ * must later be made.
206
+ * - Stop recursing down this branch as the validation call might actually change the structure
207
+ * and children in the branch.
208
+ *
209
+ * Once we have recursed the entire tree the result can be either:
210
+ * - There are Products to validate.
211
+ * Then we do the validations, and once they are done we move back into state B. We run it again
212
+ * as we did not recurse all the way down the branches that changed value. With each run, we will
213
+ * get further out on the branches until finally reaching the end.
214
+ * - There are no Products to validate.
215
+ * This means that there were no Features which we could apply the SyncState onto. We now move
216
+ * into state C as the selection changes may have brought new Features into scope, Features which
217
+ * might write to the SyncState.
218
+ *
219
+ * State C will apply Features onto the SyncState. It will recursively go through the configuration
220
+ * tree. When it's done one of two has happened:
221
+ * - There was a write to the SyncState.
222
+ * We then move back to state B, as this change in the SyncState could open up things in B. For
223
+ * example a Feature that could not pull from the SyncState before might be able to do that now.
224
+ * - There was no write to the SyncState.
225
+ * All is calm. We are done. The settling of the SyncState vs configuration is done. Things are
226
+ * as in sync as they will be. This is the exit.
227
+ *
228
+ *
229
+ * Side Notes
230
+ * ==========
231
+ *
232
+ * _CfgProductInternal, _CfgFeatureInternal and _CfgOptionInternal are used in Sets and Maps in the
233
+ * code. These objects are only created once for the data they represent. That is, even if a Feature
234
+ * goes in or out of scope the object is the same. This is not true for their wrapper classer
235
+ * CfgProduct, CfgFeature and CfgOption. Those are frequently replaced while running.
236
+ *
237
+ */
238
+ /**
239
+ * Send to root for the passed option and any sibling which is selected, provided the Feature
240
+ * is SelectOne. (As that one (should only be one) can be assumed to be affected).
241
+ */
242
+ function notifyOptionAndSelectedSiblings(option) {
243
+ return __awaiter(this, void 0, void 0, function* () {
244
+ const committed = false;
245
+ const parentFeature = option.parent;
246
+ if (parentFeature.selectionType === SelectionType.SelectOne) {
247
+ // These only need to be OneLevel, as the final is ToRoot and they share their parent.
248
+ yield Promise.all(option.parent.selectedOptions.map((o) => parentFeature._childHasChanged(o._internal, ProductConfigurationBubbleMode.OneLevel, committed)));
249
+ }
250
+ yield parentFeature._childHasChanged(option, ProductConfigurationBubbleMode.ToRoot, committed);
251
+ });
252
+ }
253
+ /**
254
+ * Is used to apply the SyncGroups functionality on the Configuration and the other way around.
255
+ * It also keeps the SyncState.
256
+ */
257
+ export class SyncGroupsHandler {
258
+ constructor(_syncState, updateMode, _loadingObservable) {
259
+ this._syncState = _syncState;
260
+ this.updateMode = updateMode;
261
+ this._loadingObservable = _loadingObservable;
262
+ }
263
+ /**
264
+ * @param verboseLogging Set to true to get verbose sync state changes logged to the console.
265
+ */
266
+ static make(updateMode = SyncGroupsApplyMode.Strict, loadingObservable, initial, verboseLogging = false) {
267
+ return new SyncGroupsHandler(new SyncGroupsState(verboseLogging, initial), updateMode, loadingObservable);
268
+ }
269
+ /** Please note that clones will use the same loadingObservable as their source. */
270
+ clone() {
271
+ return new SyncGroupsHandler(this._syncState.clone(), this.updateMode, this._loadingObservable);
272
+ }
273
+ getCompactSyncGroupState() {
274
+ return this._syncState.getCompact();
275
+ }
276
+ /** Overwrites the sync state */
277
+ setCompactSyncGroupState(s) {
278
+ this._syncState.setCompact(s);
279
+ }
280
+ get verboseLogging() {
281
+ return this._syncState.verboseLogging;
282
+ }
283
+ set verboseLogging(v) {
284
+ this._syncState.verboseLogging = v;
285
+ }
286
+ /**
287
+ * Used to initially apply the sync state onto a new product so that it is "in sync"
288
+ * and to reapply the sync state when an optional additional product is selected.
289
+ */
290
+ init(product, productLoader) {
291
+ return __awaiter(this, void 0, void 0, function* () {
292
+ const transaction = yield this.newTransaction(product, productLoader, true);
293
+ try {
294
+ yield transaction.applyRootProduct();
295
+ yield this.commitTransaction(transaction);
296
+ }
297
+ finally {
298
+ this.closeTransaction(transaction);
299
+ }
300
+ });
301
+ }
302
+ /**
303
+ * Used when an Option is selected or deselected to apply all consequences of the sync groups.
304
+ * Can cause multiple extra validation calls to the server.
305
+ */
306
+ selectOption(product, option, on, productLoader) {
307
+ return __awaiter(this, void 0, void 0, function* () {
308
+ if (product.parent !== undefined) {
309
+ console.info("Normally the product passed shall be the root product");
310
+ }
311
+ yield this.setPending(option);
312
+ const transaction = yield this.newTransaction(product, productLoader, false);
313
+ try {
314
+ const change = yield transaction.selectOption(SyncGroupsPathHelper.getPath(option), on);
315
+ // Always commit the transaction. The change-result above only tells if the
316
+ // configuration has changed. The SyncState may however also have changed.
317
+ yield this.commitTransaction(transaction);
318
+ return change;
319
+ }
320
+ finally {
321
+ if (this._pending === option) {
322
+ yield this.setPending(undefined);
323
+ }
324
+ this.closeTransaction(transaction);
325
+ }
326
+ });
327
+ }
328
+ setPending(newPending) {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ const oldPending = this._pending;
331
+ this._pending = newPending;
332
+ if (oldPending !== undefined) {
333
+ yield notifyOptionAndSelectedSiblings(oldPending);
334
+ }
335
+ if (newPending !== undefined) {
336
+ yield notifyOptionAndSelectedSiblings(newPending);
337
+ }
338
+ });
339
+ }
340
+ get pending() {
341
+ return this._pending;
342
+ }
343
+ newTransaction(product, productLoader, assumeNoStartProductState) {
344
+ var _a;
345
+ return __awaiter(this, void 0, void 0, function* () {
346
+ if (this._currentTransaction !== undefined) {
347
+ this.closeTransaction(this._currentTransaction);
348
+ }
349
+ const transaction = yield SyncGroupsTransaction.make(this._syncState, this.updateMode, product, productLoader, assumeNoStartProductState);
350
+ this._currentTransaction = transaction;
351
+ // The transaction object is used as loading token
352
+ (_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.startChildLoading(transaction);
353
+ return transaction;
354
+ });
355
+ }
356
+ closeTransaction(transaction) {
357
+ var _a;
358
+ transaction.close();
359
+ (_a = this._loadingObservable) === null || _a === void 0 ? void 0 : _a.stopChildLoading(transaction);
360
+ }
361
+ commitTransaction(transaction) {
362
+ return __awaiter(this, void 0, void 0, function* () {
363
+ if (transaction.isClosed) {
364
+ return;
365
+ }
366
+ yield transaction.commit();
367
+ this.closeTransaction(transaction);
368
+ });
369
+ }
370
+ }