@icure/form 2.1.1 → 2.1.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 (51) hide show
  1. package/.yarn/cache/{libphonenumber-js-npm-1.12.36-92f3728071-f39b5ed57b.zip → libphonenumber-js-npm-1.12.38-a5180a8a02-2a2eeb122d.zip} +0 -0
  2. package/.yarn/cache/{markdown-it-npm-14.1.0-e337d75bfe-07296b45eb.zip → markdown-it-npm-14.1.1-45c173274d-d6d55865c6.zip} +0 -0
  3. package/.yarn/install-state.gz +0 -0
  4. package/components/common/field.d.ts +14 -2
  5. package/components/common/field.js +95 -2
  6. package/components/common/field.js.map +1 -1
  7. package/components/common/utils.js +11 -1
  8. package/components/common/utils.js.map +1 -1
  9. package/components/icure-form/fields/button-group/checkbox.js +3 -4
  10. package/components/icure-form/fields/button-group/checkbox.js.map +1 -1
  11. package/components/icure-form/fields/button-group/radio-button.js +3 -4
  12. package/components/icure-form/fields/button-group/radio-button.js.map +1 -1
  13. package/components/icure-form/fields/date-picker/date-picker.js +3 -4
  14. package/components/icure-form/fields/date-picker/date-picker.js.map +1 -1
  15. package/components/icure-form/fields/date-picker/date-time-picker.js +3 -4
  16. package/components/icure-form/fields/date-picker/date-time-picker.js.map +1 -1
  17. package/components/icure-form/fields/date-picker/time-picker.js +3 -4
  18. package/components/icure-form/fields/date-picker/time-picker.js.map +1 -1
  19. package/components/icure-form/fields/dropdown/dropdown-field.js +3 -4
  20. package/components/icure-form/fields/dropdown/dropdown-field.js.map +1 -1
  21. package/components/icure-form/fields/measure-field/measure-field.js +3 -4
  22. package/components/icure-form/fields/measure-field/measure-field.js.map +1 -1
  23. package/components/icure-form/fields/number-field/number-field.js +3 -4
  24. package/components/icure-form/fields/number-field/number-field.js.map +1 -1
  25. package/components/icure-form/fields/text-field/text-field.js +3 -4
  26. package/components/icure-form/fields/text-field/text-field.js.map +1 -1
  27. package/components/icure-form/fields/utils/index.d.ts +0 -5
  28. package/components/icure-form/fields/utils/index.js +1 -12
  29. package/components/icure-form/fields/utils/index.js.map +1 -1
  30. package/components/icure-form/index.js +14 -10
  31. package/components/icure-form/index.js.map +1 -1
  32. package/components/icure-form/renderer/form/form.js +6 -4
  33. package/components/icure-form/renderer/form/form.js.map +1 -1
  34. package/components/icure-text-field/index.js +0 -2
  35. package/components/icure-text-field/index.js.map +1 -1
  36. package/components/icure-text-field/schema/measure-schema.js +0 -1
  37. package/components/icure-text-field/schema/measure-schema.js.map +1 -1
  38. package/components/model/index.d.ts +16 -16
  39. package/components/model/index.js +34 -30
  40. package/components/model/index.js.map +1 -1
  41. package/generic/model.d.ts +10 -5
  42. package/generic/model.js.map +1 -1
  43. package/icure/form-values-container.d.ts +24 -18
  44. package/icure/form-values-container.js +310 -115
  45. package/icure/form-values-container.js.map +1 -1
  46. package/icure/icure-utils.js +48 -18
  47. package/icure/icure-utils.js.map +1 -1
  48. package/package.json +1 -1
  49. package/utils/fields-values-provider.d.ts +1 -1
  50. package/utils/fields-values-provider.js +6 -4
  51. package/utils/fields-values-provider.js.map +1 -1
@@ -15,10 +15,30 @@ const no_lodash_1 = require("../utils/no-lodash");
15
15
  const icure_utils_1 = require("./icure-utils");
16
16
  const primitive_1 = require("../utils/primitive");
17
17
  const dates_1 = require("../utils/dates");
18
- const uuid_1 = require("uuid");
19
18
  const code_utils_1 = require("../utils/code-utils");
20
- function notify(l, fvc) {
21
- l(fvc);
19
+ let _ordinal = 0;
20
+ function notify(l, fvc, changedKeys, force = false) {
21
+ l(fvc, changedKeys, force);
22
+ }
23
+ function primitiveTypeToString(pt) {
24
+ switch (pt.type) {
25
+ case 'string':
26
+ return pt.value;
27
+ case 'number':
28
+ return pt.value.toString();
29
+ case 'boolean':
30
+ return pt.value.toString();
31
+ case 'measure':
32
+ return pt.value !== undefined ? `${pt.value}${pt.unit ? ' ' + pt.unit : ''}` : '';
33
+ case 'timestamp':
34
+ return new Date(pt.value).toISOString();
35
+ case 'datetime':
36
+ return pt.value.toString();
37
+ case 'compound':
38
+ return Object.entries(pt.value)
39
+ .map(([k, v]) => `${k}: ${primitiveTypeToString(v)}`)
40
+ .join(', ');
41
+ }
22
42
  }
23
43
  /** This class is a bridge between the ICure API and the generic FormValuesContainer interface.
24
44
  * It wraps around a ContactFormValuesContainer and provides a series of services:
@@ -55,8 +75,9 @@ class BridgedFormValuesContainer {
55
75
  * @param language The language in which the values are displayed
56
76
  * @param changeListeners The listeners that will be notified when the values change
57
77
  * @param interpreterContext A map with keys that are the names of the variables and values that are the functions that return the values of the variables
78
+ * @param dependenciesCache
58
79
  */
59
- constructor(responsible, contactFormValuesContainer, interpreter, contact, initialValuesProvider = () => [], dependentValuesProvider = () => [], validatorsProvider = () => [], language = 'en', changeListeners = [], interpreterContext = {}) {
80
+ constructor(responsible, contactFormValuesContainer, interpreter, contact, initialValuesProvider = () => [], dependentValuesProvider = () => [], validatorsProvider = () => [], language = 'en', changeListeners = [], interpreterContext = {}, dependenciesCache = {}) {
60
81
  this.responsible = responsible;
61
82
  this.interpreter = interpreter;
62
83
  this.initialValuesProvider = initialValuesProvider;
@@ -65,25 +86,54 @@ class BridgedFormValuesContainer {
65
86
  this.language = language;
66
87
  this.changeListeners = changeListeners;
67
88
  this.interpreterContext = interpreterContext;
68
- this._id = (0, uuid_1.v4)();
69
- console.log(`Creating bridge FVC (${contactFormValuesContainer.rootForm.formTemplateId}) with ${contactFormValuesContainer.children.length} children [${this._id}]`);
89
+ this.dependenciesCache = dependenciesCache;
90
+ this._id = '';
91
+ this.dependentValuesComputationIteration = undefined;
70
92
  //Before start to broadcast changes, we need to fill in the contactFormValuesContainer with the dependent values
71
93
  this.contactFormValuesContainer = contactFormValuesContainer;
72
- this.mutateAndNotify = (newContactFormValuesContainer) => __awaiter(this, void 0, void 0, function* () {
94
+ this._id = `[${contactFormValuesContainer._id}]`;
95
+ console.log(`+ BFVC ${this._id} created`);
96
+ this.mutateAndNotify = (newContactFormValuesContainer, changedKeys, force = false) => {
97
+ if (this.changeListeners.length === 0) {
98
+ return;
99
+ }
100
+ if (!force && this.contactFormValuesContainer === newContactFormValuesContainer) {
101
+ return;
102
+ }
103
+ this.contactFormValuesContainer.unregisterChangeListener(this.mutateAndNotify);
73
104
  newContactFormValuesContainer.unregisterChangeListener(this.mutateAndNotify);
74
- const newBridgedFormValueContainer = yield new BridgedFormValuesContainer(this.responsible, newContactFormValuesContainer, this.interpreter, this.contact === this.contactFormValuesContainer.currentContact ? newContactFormValuesContainer.currentContact : this.contact, this.initialValuesProvider, this.dependentValuesProvider, this.validatorsProvider, this.language, this.changeListeners, this.interpreterContext).init();
75
- this.changeListeners.forEach((l) => notify(l, newBridgedFormValueContainer));
76
- return newBridgedFormValueContainer;
77
- });
105
+ const newBridgedFormValueContainer = new BridgedFormValuesContainer(this.responsible, newContactFormValuesContainer, this.interpreter, this.contact === this.contactFormValuesContainer.currentContact ? newContactFormValuesContainer.currentContact : this.contact, this.initialValuesProvider, this.dependentValuesProvider, this.validatorsProvider, this.language, this.changeListeners, this.interpreterContext, this.dependenciesCache);
106
+ const initPromise = newBridgedFormValueContainer.init(changedKeys, this.dependentValuesComputationIteration);
107
+ this.changeListeners.forEach((l) => notify(l, newBridgedFormValueContainer, changedKeys));
108
+ initPromise.catch((e) => {
109
+ console.log('Error while initializing new BridgedFormValuesContainer', e);
110
+ });
111
+ };
78
112
  this.contactFormValuesContainer.registerChangeListener(this.mutateAndNotify);
79
113
  this.contact = contact !== null && contact !== void 0 ? contact : contactFormValuesContainer.currentContact;
80
114
  }
115
+ /* init can be called several times on the same instance but the initialisation will only be done once */
81
116
  init() {
82
- return __awaiter(this, void 0, void 0, function* () {
117
+ return __awaiter(this, arguments, void 0, function* (changedKeys = null, pendingComputationIteration) {
83
118
  if (this.contactFormValuesContainer.mustBeInitialised()) {
84
119
  yield this.computeInitialValues();
85
120
  }
86
- yield this.computeDependentValues();
121
+ let changedKeysByInit = [];
122
+ if (pendingComputationIteration) {
123
+ const changes = yield pendingComputationIteration;
124
+ if (changes.length > 0) {
125
+ this.quietlyApplyChangesOnContactFormValueContainer(changes);
126
+ changedKeysByInit = yield this.computeDependentValues([...(changedKeys !== null && changedKeys !== void 0 ? changedKeys : []), ...changes.map(([, metadata]) => metadata.label)]);
127
+ }
128
+ else {
129
+ changedKeysByInit = yield this.computeDependentValues(changedKeys);
130
+ }
131
+ }
132
+ else {
133
+ changedKeysByInit = yield this.computeDependentValues(changedKeys);
134
+ }
135
+ //After all the silent updates, we need a last one that causes a new BridgedFormValuesContainer to be created and broadcasted to the listeners, so that they get the final values with all the dependencies computed
136
+ this.contactFormValuesContainer.changeListeners.forEach((l) => notify(l, this.contactFormValuesContainer, changedKeysByInit, changedKeysByInit.length > 0));
87
137
  return this;
88
138
  });
89
139
  }
@@ -199,7 +249,8 @@ class BridgedFormValuesContainer {
199
249
  try {
200
250
  const currentValue = this.getValues(revisionsFilter);
201
251
  if (!currentValue || !Object.keys(currentValue).length) {
202
- const newValue = this.convertRawValue((yield this.compute(formula)));
252
+ const computedValue = (yield this.compute(formula)).value;
253
+ const newValue = computedValue ? this.convertRawValue(computedValue) : undefined;
203
254
  if (newValue !== undefined) {
204
255
  const lng = (_b = this.language) !== null && _b !== void 0 ? _b : 'en';
205
256
  if (newValue && !newValue.content[lng] && newValue.content['*']) {
@@ -226,38 +277,80 @@ class BridgedFormValuesContainer {
226
277
  });
227
278
  }
228
279
  //This method mutates the BridgedFormValuesContainer but can only be called from the constructor
229
- computeDependentValues() {
280
+ computeDependentValues(changedKeys) {
230
281
  return __awaiter(this, void 0, void 0, function* () {
231
- if (this.contactFormValuesContainer.rootForm.formTemplateId) {
232
- yield Promise.all(this.dependentValuesProvider(this.contactFormValuesContainer.rootForm.descr, this.contactFormValuesContainer.rootForm.formTemplateId).map((_a) => __awaiter(this, [_a], void 0, function* ({ metadata, revisionsFilter, formula }) {
233
- var _b;
234
- try {
235
- const currentValue = this.getValues(revisionsFilter);
236
- const newValue = this.convertRawValue((yield this.compute(formula)));
237
- if (newValue !== undefined || currentValue != undefined) {
238
- const lng = (_b = this.language) !== null && _b !== void 0 ? _b : 'en';
239
- if (newValue && !newValue.content[lng] && newValue.content['*']) {
240
- newValue.content[lng] = newValue.content['*'];
241
- }
242
- if (newValue) {
243
- delete newValue.content['*'];
244
- }
245
- const interceptor = (fvc) => {
246
- const currentContact = this.contactFormValuesContainer.currentContact;
247
- this.contactFormValuesContainer = fvc;
248
- if (this.contact === currentContact) {
249
- this.contact = fvc.currentContact;
282
+ const iterate = (changedKeys, iterationCount) => __awaiter(this, void 0, void 0, function* () {
283
+ if (this.contactFormValuesContainer.rootForm.formTemplateId) {
284
+ console.log(`% ${iterationCount}, keys: ${changedKeys ? changedKeys.join(', ') : 'all'}`);
285
+ return yield Promise.all(this.dependentValuesProvider(this.contactFormValuesContainer.rootForm.descr, this.contactFormValuesContainer.rootForm.formTemplateId).map((_a) => __awaiter(this, [_a], void 0, function* ({ metadata, revisionsFilter, formula }) {
286
+ var _b, _c;
287
+ try {
288
+ if (!changedKeys || !this.dependenciesCache[formula] || this.dependenciesCache[formula].some((d) => changedKeys.includes(d))) {
289
+ const currentValue = this.getValues(revisionsFilter);
290
+ try {
291
+ const computationResult = yield this.compute(formula);
292
+ this.dependenciesCache[formula] = [...((_b = this.dependenciesCache[formula]) !== null && _b !== void 0 ? _b : []), ...computationResult.dependencies].filter((d, idx, array) => array.indexOf(d) === idx);
293
+ const newValue = computationResult.value ? this.convertRawValue(computationResult.value) : undefined;
294
+ if (newValue !== undefined || (currentValue != undefined && Object.keys(currentValue).length > 0 && currentValue[Object.keys(currentValue)[0]][0].value)) {
295
+ const lng = (_c = this.language) !== null && _c !== void 0 ? _c : 'en';
296
+ if (newValue && !newValue.content[lng] && newValue.content['*']) {
297
+ newValue.content[lng] = newValue.content['*'];
298
+ }
299
+ if (newValue) {
300
+ delete newValue.content['*'];
301
+ }
302
+ return [Object.keys(currentValue !== null && currentValue !== void 0 ? currentValue : {})[0], metadata, lng, newValue];
303
+ }
304
+ }
305
+ catch (e) {
306
+ console.log(`Error while computing formula : ${formula}`, e);
250
307
  }
251
- };
252
- setValueOnContactFormValuesContainer(this.contactFormValuesContainer, metadata.label, lng, newValue, Object.keys(currentValue !== null && currentValue !== void 0 ? currentValue : {})[0], metadata, interceptor);
308
+ }
253
309
  }
254
- }
255
- catch (e) {
256
- console.log(`Error while computing formula : ${formula}`, e);
257
- }
258
- })));
310
+ catch (e) {
311
+ console.log(`Error while computing formula : ${formula}`, e);
312
+ }
313
+ return null;
314
+ }))).then((results) => results.filter((r) => !!r));
315
+ }
316
+ else {
317
+ return [];
318
+ }
319
+ });
320
+ let latestKeys = changedKeys;
321
+ let iterationCount = 0;
322
+ const allChangedKeys = [];
323
+ while (true) {
324
+ if (latestKeys != null && latestKeys.length === 0) {
325
+ this.dependentValuesComputationIteration = undefined;
326
+ break;
327
+ }
328
+ const changes = yield (this.dependentValuesComputationIteration = iterate(latestKeys, iterationCount++));
329
+ latestKeys = this.quietlyApplyChangesOnContactFormValueContainer(changes);
330
+ allChangedKeys.push(...latestKeys);
331
+ }
332
+ return allChangedKeys;
333
+ });
334
+ }
335
+ quietlyApplyChangesOnContactFormValueContainer(changes) {
336
+ const allChangedKeys = [];
337
+ const interceptor = (fvc, changedKeys) => {
338
+ const currentContact = this.contactFormValuesContainer.currentContact;
339
+ this.contactFormValuesContainer = fvc;
340
+ if (this.contact === currentContact) {
341
+ this.contact = fvc.currentContact;
342
+ }
343
+ allChangedKeys.push(...(changedKeys !== null && changedKeys !== void 0 ? changedKeys : []));
344
+ };
345
+ changes.forEach(([id, metadata, language, newValue]) => {
346
+ try {
347
+ setValueOnContactFormValuesContainer(this.contactFormValuesContainer, metadata.label, language, newValue !== null && newValue !== void 0 ? newValue : undefined, id, metadata, interceptor);
348
+ }
349
+ catch (e) {
350
+ console.log(`Error while applying dependent value change for ${metadata.label} (${id})`, e);
259
351
  }
260
352
  });
353
+ return allChangedKeys;
261
354
  }
262
355
  setValue(label, language, fv, id, metadata) {
263
356
  setValueOnContactFormValuesContainer(this.contactFormValuesContainer, label, language, fv, id, metadata);
@@ -276,9 +369,10 @@ class BridgedFormValuesContainer {
276
369
  getVersionedValuesForKey(key) {
277
370
  return this.getValues((id, history) => { var _a, _b, _c; return (((_b = (_a = history === null || history === void 0 ? void 0 : history[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.label) && key === history[0].value.label ? [(_c = history === null || history === void 0 ? void 0 : history[0]) === null || _c === void 0 ? void 0 : _c.revision] : []); });
278
371
  }
279
- compute(formula, sandbox) {
372
+ compute(formula) {
280
373
  return __awaiter(this, void 0, void 0, function* () {
281
374
  var _a;
375
+ const dependencies = new Set();
282
376
  // noinspection JSUnusedGlobalSymbols
283
377
  const parseContent = (content, toString = false) => {
284
378
  var _a, _b;
@@ -299,6 +393,7 @@ class BridgedFormValuesContainer {
299
393
  .join(', ');
300
394
  };
301
395
  const log = console.log;
396
+ // noinspection JSUnusedGlobalSymbols
302
397
  const native = {
303
398
  parseInt: parseInt,
304
399
  parseFloat: parseFloat,
@@ -346,42 +441,40 @@ class BridgedFormValuesContainer {
346
441
  if (!!nativeValue) {
347
442
  return nativeValue;
348
443
  }
349
- return key === 'self' ? proxy : this.interpreterContext[key] ? this.interpreterContext[key]() : Object.values(this.getVersionedValuesForKey(key)).map((v) => { var _a; return (_a = v[0]) === null || _a === void 0 ? void 0 : _a.value; });
444
+ if (key === 'self') {
445
+ return proxy;
446
+ }
447
+ else {
448
+ dependencies.add(key.toString());
449
+ return this.interpreterContext[key] ? this.interpreterContext[key]() : Object.values(this.getVersionedValuesForKey(key)).map((v) => { var _a; return (_a = v[0]) === null || _a === void 0 ? void 0 : _a.value; });
450
+ }
350
451
  },
351
452
  });
352
- return (_a = this.interpreter) === null || _a === void 0 ? void 0 : _a.call(this, formula, sandbox !== null && sandbox !== void 0 ? sandbox : proxy);
453
+ return { value: yield ((_a = this.interpreter) === null || _a === void 0 ? void 0 : _a.call(this, formula, proxy)), dependencies: Array.from(dependencies) };
353
454
  });
354
455
  }
355
456
  getChildren() {
356
457
  return __awaiter(this, void 0, void 0, function* () {
357
- const children = yield Promise.all((yield this.contactFormValuesContainer.getChildren()).map((fvc) => new BridgedFormValuesContainer(this.responsible, fvc, this.interpreter, this.contact, this.initialValuesProvider, this.dependentValuesProvider, this.validatorsProvider, this.language, [], this.interpreterContext).init()));
358
- console.log(`${children.length} children found in ${this.contactFormValuesContainer.rootForm.formTemplateId} initialised with `, this.initialValuesProvider);
359
- return children;
458
+ return yield Promise.all((yield this.contactFormValuesContainer.getChildren()).map((fvc) => new BridgedFormValuesContainer(this.responsible, fvc, this.interpreter, this.contact, this.initialValuesProvider, this.dependentValuesProvider, this.validatorsProvider, this.language, [], this.interpreterContext).init()));
360
459
  });
361
460
  }
362
461
  getValidationErrors() {
363
- return __awaiter(this, void 0, void 0, function* () {
364
- if (this.contactFormValuesContainer.rootForm.formTemplateId) {
365
- // noinspection ES6MissingAwait
366
- return yield this.validatorsProvider(this.contactFormValuesContainer.rootForm.descr, this.contactFormValuesContainer.rootForm.formTemplateId).reduce((resPromise_1, _a) => __awaiter(this, [resPromise_1, _a], void 0, function* (resPromise, { metadata, validators }) {
367
- return yield validators.reduce((resPromise_2, _a) => __awaiter(this, [resPromise_2, _a], void 0, function* (resPromise, { validation, message }) {
368
- const res = yield resPromise;
369
- try {
370
- if (!(yield this.compute(validation))) {
371
- res.push([metadata, message]);
372
- }
373
- }
374
- catch (e) {
375
- console.log(`Error while computing validation : ${validation}`, e);
376
- }
377
- return res;
378
- }), resPromise);
379
- }), Promise.resolve([]));
380
- }
381
- else {
382
- return [];
383
- }
384
- });
462
+ if (this.contactFormValuesContainer.rootForm.formTemplateId) {
463
+ return this.validatorsProvider(this.contactFormValuesContainer.rootForm.descr, this.contactFormValuesContainer.rootForm.formTemplateId).flatMap(({ metadata, validators }) => validators.map((_a) => __awaiter(this, [_a], void 0, function* ({ validation, message }) {
464
+ try {
465
+ if (!(yield this.compute(validation))) {
466
+ return [metadata, message];
467
+ }
468
+ }
469
+ catch (e) {
470
+ console.log(`Error while computing validation : ${validation}`, e);
471
+ }
472
+ return null;
473
+ })));
474
+ }
475
+ else {
476
+ return [];
477
+ }
385
478
  }
386
479
  addChild(anchorId, templateId, label) {
387
480
  return __awaiter(this, void 0, void 0, function* () {
@@ -431,17 +524,20 @@ class ContactFormValuesContainer {
431
524
  allForms() {
432
525
  return [this.rootForm].concat(this.children.flatMap((c) => c.allForms()));
433
526
  }
434
- constructor(rootForm, currentContact, contactsHistory, serviceFactory, children, formFactory, formRecycler, changeListeners = [], initialised = true) {
435
- this._id = (0, uuid_1.v4)();
527
+ constructor(rootForm, currentContact, contactsHistory, serviceFactory, children, formFactory, formRecycler, changeListeners = [], anchorId, initialised = true) {
528
+ var _a;
529
+ this._id = '';
436
530
  this._initialised = false;
437
- console.log(`Creating contact FVC (${rootForm.formTemplateId}) with ${children.length} children [${this._id}]`);
531
+ this.rootForm = rootForm;
532
+ this._id = `${((_a = rootForm.formTemplateId) !== null && _a !== void 0 ? _a : '').substring(0, 4)}.${++_ordinal}`;
533
+ console.log(`+ CFVC: ${this._id} with children { ${children.map((c) => c._id).join(', ')} }`);
438
534
  if (contactsHistory.includes(currentContact)) {
439
535
  throw new Error('Illegal argument, the history must not contain the currentContact');
440
536
  }
441
- this.rootForm = rootForm;
442
537
  this.currentContact = currentContact;
443
538
  this.contactsHistory = (0, no_lodash_1.sortedBy)(contactsHistory, 'created', 'desc');
444
539
  this.children = children;
540
+ this.anchorId = anchorId;
445
541
  this.serviceFactory = serviceFactory;
446
542
  this.formFactory = formFactory;
447
543
  this.formRecycler = formRecycler;
@@ -449,7 +545,7 @@ class ContactFormValuesContainer {
449
545
  this._initialised = initialised;
450
546
  this.indexedServices = [this.currentContact].concat(this.contactsHistory).reduce((acc, ctc) => {
451
547
  var _a, _b, _c;
452
- const services = (_c = (_b = (_a = ctc.services) === null || _a === void 0 ? void 0 : _a.filter((s) => { var _a; return (_a = ctc.subContacts) === null || _a === void 0 ? void 0 : _a.some((sc) => { var _a; return sc.formId === this.rootForm.id && ((_a = sc.services) === null || _a === void 0 ? void 0 : _a.some((sss) => sss.serviceId === s.id)); }); })) === null || _b === void 0 ? void 0 : _b.reduce((acc, s) => {
548
+ return ((_c = (_b = (_a = ctc.services) === null || _a === void 0 ? void 0 : _a.filter((s) => { var _a; return (_a = ctc.subContacts) === null || _a === void 0 ? void 0 : _a.some((sc) => { var _a; return sc.formId === this.rootForm.id && ((_a = sc.services) === null || _a === void 0 ? void 0 : _a.some((sss) => sss.serviceId === s.id)); }); })) === null || _b === void 0 ? void 0 : _b.reduce((acc, s) => {
453
549
  var _a, _b;
454
550
  return s.id
455
551
  ? Object.assign(Object.assign({}, acc), { [s.id]: ((_a = acc[s.id]) !== null && _a !== void 0 ? _a : (acc[s.id] = [])).concat({
@@ -457,26 +553,29 @@ class ContactFormValuesContainer {
457
553
  modified: ctc.created,
458
554
  value: s,
459
555
  }) }) : acc;
460
- }, acc)) !== null && _c !== void 0 ? _c : acc;
461
- return services;
556
+ }, acc)) !== null && _c !== void 0 ? _c : acc);
462
557
  }, {});
463
558
  this.synchronise();
464
559
  }
465
560
  synchronise() {
466
561
  this.children.forEach((childFVC) => {
467
- this.registerChildFormValuesContainer(childFVC.synchronise());
562
+ var _a;
563
+ this.registerChildFormValuesContainer(childFVC.synchronise(), (_a = childFVC.anchorId) !== null && _a !== void 0 ? _a : '');
468
564
  });
469
565
  return this;
470
566
  }
471
567
  //Make sure that when a child is changed, a new version of this is created with the updated child
472
- registerChildFormValuesContainer(childFormValueContainer) {
568
+ registerChildFormValuesContainer(childFormValueContainer, anchorId) {
473
569
  childFormValueContainer.changeListeners = [
474
- (newValue) => {
570
+ (newValue, changedKeys, force) => {
571
+ if ((changedKeys === null || changedKeys === void 0 ? void 0 : changedKeys.length) === 0 && !force) {
572
+ return;
573
+ }
475
574
  console.log(`Child ${newValue._id} ${childFormValueContainer.rootForm.formTemplateId} changed, updating parent ${this._id} ${this.rootForm.formTemplateId}`);
476
575
  const newContactFormValuesContainer = new ContactFormValuesContainer(this.rootForm, this.currentContact, this.contactsHistory, this.serviceFactory, this.children.map((c) => {
477
576
  return c.rootForm.id === childFormValueContainer.rootForm.id ? newValue : c;
478
- }), this.formFactory, this.formRecycler);
479
- this.changeListeners.forEach((l) => notify(l, newContactFormValuesContainer));
577
+ }), this.formFactory, this.formRecycler, [], this.anchorId, true);
578
+ this.changeListeners.forEach((l) => notify(l, newContactFormValuesContainer, null));
480
579
  },
481
580
  ];
482
581
  }
@@ -486,8 +585,8 @@ class ContactFormValuesContainer {
486
585
  ? yield Promise.all((yield formChildrenProvider(rootForm.id)).map((f) => __awaiter(this, void 0, void 0, function* () {
487
586
  // eslint-disable-next-line max-len
488
587
  return yield ContactFormValuesContainer.fromFormsHierarchy(f, currentContact, contactsHistory, serviceFactory, formChildrenProvider, formFactory, formRecycler); })))
489
- : [], formFactory, formRecycler, changeListeners, false);
490
- contactFormValuesContainer.children.forEach((childFVC) => contactFormValuesContainer.registerChildFormValuesContainer(childFVC));
588
+ : [], formFactory, formRecycler, changeListeners, rootForm.descr, false);
589
+ contactFormValuesContainer.children.forEach((childFVC) => { var _a; return contactFormValuesContainer.registerChildFormValuesContainer(childFVC, (_a = childFVC.anchorId) !== null && _a !== void 0 ? _a : 'all'); });
491
590
  return contactFormValuesContainer;
492
591
  });
493
592
  }
@@ -511,9 +610,7 @@ class ContactFormValuesContainer {
511
610
  });
512
611
  }
513
612
  getValidationErrors() {
514
- return __awaiter(this, void 0, void 0, function* () {
515
- throw new Error('Validation not supported at contact level');
516
- });
613
+ throw new Error('Validation not supported at contact level');
517
614
  }
518
615
  getDefaultValueProvider() {
519
616
  return undefined;
@@ -561,20 +658,20 @@ class ContactFormValuesContainer {
561
658
  meta.valueDate && (newService.valueDate = meta.valueDate);
562
659
  meta.codes && (newService.codes = (0, code_utils_1.normalizeCodes)(meta.codes));
563
660
  meta.tags && (newService.tags = (0, code_utils_1.normalizeCodes)(meta.tags));
564
- const newFormValuesContainer = new ContactFormValuesContainer(this.rootForm, Object.assign(Object.assign({}, this.currentContact), { services: (_a = this.currentContact.services) === null || _a === void 0 ? void 0 : _a.map((s) => (s.id === service.id ? newService : s)) }), this.contactsHistory, this.serviceFactory, this.children, this.formFactory, this.formRecycler, this.changeListeners);
565
- this.changeListeners.forEach((l) => notify(l, newFormValuesContainer));
661
+ const newFormValuesContainer = new ContactFormValuesContainer(this.rootForm, Object.assign(Object.assign({}, this.currentContact), { services: (_a = this.currentContact.services) === null || _a === void 0 ? void 0 : _a.map((s) => (s.id === service.id ? newService : s)) }), this.contactsHistory, this.serviceFactory, this.children, this.formFactory, this.formRecycler, this.changeListeners, this.anchorId, true);
662
+ this.changeListeners.forEach((l) => notify(l, newFormValuesContainer, [meta.label]));
566
663
  }
567
664
  }
568
665
  setValue(label, language, value, id, metadata, changeListenersOverrider) {
569
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
666
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
570
667
  const service = (id && ((_b = (_a = this.getServicesInHistory((sid, history) => (sid === id ? history.map((x) => x.revision) : []))[id]) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value)) || this.serviceFactory(label, id);
571
668
  if (!service.id) {
572
669
  throw new Error('Service id must be defined');
573
670
  }
574
- console.log('Setting value of service', service.id, 'with', value, 'and metadata', metadata);
575
- const newContent = (_c = value === null || value === void 0 ? void 0 : value.content) === null || _c === void 0 ? void 0 : _c[language];
671
+ console.log(`Setting value of service ${service.label} [${service.id}] to ${Object.entries((_c = value === null || value === void 0 ? void 0 : value.content) !== null && _c !== void 0 ? _c : {}).map(([k, c]) => `${k}: ${JSON.stringify((0, icure_utils_1.contentToPrimitiveType)(k, c))}`)} and md`, metadata);
672
+ const newContent = (_d = value === null || value === void 0 ? void 0 : value.content) === null || _d === void 0 ? void 0 : _d[language];
576
673
  const newCodes = (value === null || value === void 0 ? void 0 : value.codes) ? (0, code_utils_1.normalizeCodes)(value.codes) : [];
577
- if (!(0, icure_utils_1.isContentEqual)((_d = service.content) === null || _d === void 0 ? void 0 : _d[language], newContent) || (newCodes && !(0, icure_utils_1.areCodesEqual)(newCodes, (_e = service.codes) !== null && _e !== void 0 ? _e : []))) {
674
+ if (!(0, icure_utils_1.isContentEqual)((_e = service.content) === null || _e === void 0 ? void 0 : _e[language], newContent) || (newCodes && !(0, icure_utils_1.areCodesEqual)(newCodes, (_f = service.codes) !== null && _f !== void 0 ? _f : []))) {
578
675
  const newService = new api_1.Service(Object.assign(Object.assign({}, service), { modified: Date.now() }));
579
676
  const newContents = newContent
580
677
  ? Object.assign(Object.assign({}, (service.content || {})), { [language]: newContent }) : Object.assign({}, (service.content || {}));
@@ -583,8 +680,8 @@ class ContactFormValuesContainer {
583
680
  }
584
681
  let newCurrentContact;
585
682
  if (!Object.entries(newContents).filter(([, cnt]) => cnt !== undefined).length) {
586
- newCurrentContact = Object.assign(Object.assign({}, this.currentContact), { subContacts: ((_f = this.currentContact.subContacts) !== null && _f !== void 0 ? _f : []).some((sc) => sc.formId === this.rootForm.id)
587
- ? ((_g = this.currentContact.subContacts) !== null && _g !== void 0 ? _g : []).map((sc) => {
683
+ newCurrentContact = Object.assign(Object.assign({}, this.currentContact), { subContacts: ((_g = this.currentContact.subContacts) !== null && _g !== void 0 ? _g : []).some((sc) => sc.formId === this.rootForm.id)
684
+ ? ((_h = this.currentContact.subContacts) !== null && _h !== void 0 ? _h : []).map((sc) => {
588
685
  var _a;
589
686
  if (sc.formId === this.rootForm.id) {
590
687
  return Object.assign(Object.assign({}, sc), { services: ((_a = sc.services) !== null && _a !== void 0 ? _a : []).filter((s) => s.serviceId !== service.id).concat([{ serviceId: service.id }]) });
@@ -593,21 +690,21 @@ class ContactFormValuesContainer {
593
690
  return sc;
594
691
  }
595
692
  })
596
- : ((_h = this.currentContact.subContacts) !== null && _h !== void 0 ? _h : []).concat({ formId: this.rootForm.id, services: [{ serviceId: service.id }] }), services: ((_j = this.currentContact.services) !== null && _j !== void 0 ? _j : []).some((s) => s.id === service.id)
597
- ? ((_k = this.currentContact.services) !== null && _k !== void 0 ? _k : []).filter((s) => s.id !== service.id)
598
- : [...((_l = this.currentContact.services) !== null && _l !== void 0 ? _l : [])] });
693
+ : ((_j = this.currentContact.subContacts) !== null && _j !== void 0 ? _j : []).concat({ formId: this.rootForm.id, services: [{ serviceId: service.id }] }), services: ((_k = this.currentContact.services) !== null && _k !== void 0 ? _k : []).some((s) => s.id === service.id)
694
+ ? ((_l = this.currentContact.services) !== null && _l !== void 0 ? _l : []).filter((s) => s.id !== service.id)
695
+ : [...((_m = this.currentContact.services) !== null && _m !== void 0 ? _m : [])] });
599
696
  }
600
697
  else {
601
698
  newService.content = newContents;
602
699
  newService.codes = newCodes;
603
700
  if (metadata) {
604
- newService.responsible = (_m = metadata.responsible) !== null && _m !== void 0 ? _m : newService.responsible;
605
- newService.valueDate = (_o = metadata.valueDate) !== null && _o !== void 0 ? _o : newService.valueDate;
701
+ newService.responsible = (_o = metadata.responsible) !== null && _o !== void 0 ? _o : newService.responsible;
702
+ newService.valueDate = (_p = metadata.valueDate) !== null && _p !== void 0 ? _p : newService.valueDate;
606
703
  newService.tags = metadata.tags ? (0, code_utils_1.normalizeCodes)(metadata.tags) : newService.tags;
607
- newService.label = (_p = metadata.label) !== null && _p !== void 0 ? _p : newService.label;
704
+ newService.label = (_q = metadata.label) !== null && _q !== void 0 ? _q : newService.label;
608
705
  }
609
- newCurrentContact = Object.assign(Object.assign({}, this.currentContact), { subContacts: ((_q = this.currentContact.subContacts) !== null && _q !== void 0 ? _q : []).some((sc) => sc.formId === this.rootForm.id)
610
- ? ((_r = this.currentContact.subContacts) !== null && _r !== void 0 ? _r : []).map((sc) => {
706
+ newCurrentContact = Object.assign(Object.assign({}, this.currentContact), { subContacts: ((_r = this.currentContact.subContacts) !== null && _r !== void 0 ? _r : []).some((sc) => sc.formId === this.rootForm.id)
707
+ ? ((_s = this.currentContact.subContacts) !== null && _s !== void 0 ? _s : []).map((sc) => {
611
708
  var _a;
612
709
  if (sc.formId === this.rootForm.id) {
613
710
  return Object.assign(Object.assign({}, sc), { services: ((_a = sc.services) !== null && _a !== void 0 ? _a : []).filter((s) => s.serviceId !== service.id).concat([{ serviceId: service.id }]) });
@@ -616,12 +713,12 @@ class ContactFormValuesContainer {
616
713
  return sc;
617
714
  }
618
715
  })
619
- : ((_s = this.currentContact.subContacts) !== null && _s !== void 0 ? _s : []).concat({ formId: this.rootForm.id, services: [{ serviceId: service.id }] }), services: ((_t = this.currentContact.services) !== null && _t !== void 0 ? _t : []).some((s) => s.id === service.id)
620
- ? ((_u = this.currentContact.services) !== null && _u !== void 0 ? _u : []).map((s) => (s.id === service.id ? newService : s))
621
- : [...((_v = this.currentContact.services) !== null && _v !== void 0 ? _v : []), newService] });
716
+ : ((_t = this.currentContact.subContacts) !== null && _t !== void 0 ? _t : []).concat({ formId: this.rootForm.id, services: [{ serviceId: service.id }] }), services: ((_u = this.currentContact.services) !== null && _u !== void 0 ? _u : []).some((s) => s.id === service.id)
717
+ ? ((_v = this.currentContact.services) !== null && _v !== void 0 ? _v : []).map((s) => (s.id === service.id ? newService : s))
718
+ : [...((_w = this.currentContact.services) !== null && _w !== void 0 ? _w : []), newService] });
622
719
  }
623
- const newFormValuesContainer = new ContactFormValuesContainer(this.rootForm, newCurrentContact, this.contactsHistory.map((c) => (c === this.currentContact ? newCurrentContact : c)), this.serviceFactory, this.children, this.formFactory, this.formRecycler, this.changeListeners);
624
- changeListenersOverrider ? changeListenersOverrider(newFormValuesContainer) : this.changeListeners.forEach((l) => notify(l, newFormValuesContainer));
720
+ const newFormValuesContainer = new ContactFormValuesContainer(this.rootForm, newCurrentContact, this.contactsHistory.map((c) => (c === this.currentContact ? newCurrentContact : c)), this.serviceFactory, this.children, this.formFactory, this.formRecycler, this.changeListeners, this.anchorId, true);
721
+ changeListenersOverrider ? changeListenersOverrider(newFormValuesContainer, [label]) : this.changeListeners.forEach((l) => notify(l, newFormValuesContainer, [label]));
625
722
  }
626
723
  }
627
724
  delete(serviceId) {
@@ -630,10 +727,35 @@ class ContactFormValuesContainer {
630
727
  if (service) {
631
728
  const newFormValuesContainer = new ContactFormValuesContainer(this.rootForm, Object.assign(Object.assign({}, this.currentContact), { services: (_a = this.currentContact.services) === null || _a === void 0 ? void 0 : _a.map((s) => s.id === serviceId
632
729
  ? new api_1.Service(Object.assign(Object.assign({}, service), { endOfLife: Date.now() }))
633
- : s) }), this.contactsHistory, this.serviceFactory, this.children, this.formFactory, this.formRecycler, this.changeListeners);
634
- this.changeListeners.forEach((l) => notify(l, newFormValuesContainer));
730
+ : s) }), this.contactsHistory, this.serviceFactory, this.children, this.formFactory, this.formRecycler, this.changeListeners, this.anchorId, true);
731
+ this.changeListeners.forEach((l) => { var _a, _b; return notify(l, newFormValuesContainer, [(_b = (_a = service.label) !== null && _a !== void 0 ? _a : service.id) !== null && _b !== void 0 ? _b : ''] /* only the label is going to propagate the changes for the formulas */); });
635
732
  }
636
733
  }
734
+ toMarkdownTable() {
735
+ var _a, _b, _c;
736
+ const tableRows = [];
737
+ for (const [, versions] of Object.entries(this.indexedServices)) {
738
+ if (!versions.length)
739
+ continue;
740
+ const service = versions[0].value;
741
+ const label = (_b = (_a = service.label) !== null && _a !== void 0 ? _a : service.id) !== null && _b !== void 0 ? _b : '';
742
+ const modifiedStr = service.modified ? new Date(service.modified).toISOString() : '';
743
+ const contentParts = [];
744
+ for (const [lng, cnt] of Object.entries((_c = service.content) !== null && _c !== void 0 ? _c : {})) {
745
+ const primitive = (0, icure_utils_1.contentToPrimitiveType)(lng, cnt);
746
+ if (primitive) {
747
+ contentParts.push(primitiveTypeToString(primitive));
748
+ }
749
+ }
750
+ tableRows.push([label, modifiedStr, contentParts.join(', ')]);
751
+ }
752
+ const headers = ['Label', 'Modified', 'Content'];
753
+ const colWidths = headers.map((h, i) => Math.max(h.length, ...tableRows.map((r) => r[i].length)));
754
+ const pad = (s, w) => s + ' '.repeat(w - s.length);
755
+ const formatRow = (cells) => '| ' + cells.map((c, i) => pad(c, colWidths[i])).join(' | ') + ' |';
756
+ const separator = '|' + colWidths.map((w) => '-'.repeat(w + 2)).join('|') + '|';
757
+ return [formatRow(headers), separator, ...tableRows.map(formatRow)].join('\n');
758
+ }
637
759
  compute() {
638
760
  return __awaiter(this, void 0, void 0, function* () {
639
761
  throw new Error('Compute not supported at contact level');
@@ -672,10 +794,12 @@ class ContactFormValuesContainer {
672
794
  if (!parentId)
673
795
  return;
674
796
  const newForm = yield this.formFactory(parentId, anchorId, templateId, label);
675
- const childFVC = new ContactFormValuesContainer(newForm, this.currentContact, this.contactsHistory, this.serviceFactory, [], this.formFactory, this.formRecycler, [], false);
676
- const newContactFormValuesContainer = new ContactFormValuesContainer(this.rootForm, this.currentContact, this.contactsHistory, this.serviceFactory, [...this.children, childFVC], this.formFactory, this.formRecycler, this.changeListeners);
677
- newContactFormValuesContainer.registerChildFormValuesContainer(childFVC);
678
- this.changeListeners.forEach((l) => notify(l, newContactFormValuesContainer));
797
+ const childFVC = new ContactFormValuesContainer(newForm, this.currentContact, this.contactsHistory, this.serviceFactory, [], this.formFactory, this.formRecycler, [], anchorId, false);
798
+ const newContactFormValuesContainer = new ContactFormValuesContainer(this.rootForm, this.currentContact, this.contactsHistory, this.serviceFactory, [...this.children, childFVC], this.formFactory, this.formRecycler, this.changeListeners, this.anchorId, true);
799
+ newContactFormValuesContainer.registerChildFormValuesContainer(childFVC, anchorId);
800
+ this.changeListeners.forEach((l) => {
801
+ notify(l, newContactFormValuesContainer, null);
802
+ });
679
803
  });
680
804
  }
681
805
  getServiceInCurrentContact(id) {
@@ -685,14 +809,85 @@ class ContactFormValuesContainer {
685
809
  }
686
810
  removeChild(container) {
687
811
  return __awaiter(this, void 0, void 0, function* () {
688
- const newContactFormValuesContainer = new ContactFormValuesContainer(this.rootForm, this.currentContact, this.contactsHistory, this.serviceFactory, this.children.filter((c) => c.rootForm.id !== container.rootForm.id), this.formFactory, this.formRecycler, this.changeListeners);
689
- this.changeListeners.forEach((l) => notify(l, newContactFormValuesContainer));
812
+ const newContactFormValuesContainer = new ContactFormValuesContainer(this.rootForm, this.currentContact, this.contactsHistory, this.serviceFactory, this.children.filter((c) => c.rootForm.id !== container.rootForm.id), this.formFactory, this.formRecycler, this.changeListeners, this.anchorId, true);
813
+ this.changeListeners.forEach((l) => { var _a; return notify(l, newContactFormValuesContainer, [`subForms_${(_a = container.anchorId) !== null && _a !== void 0 ? _a : 'all'}`]); });
690
814
  });
691
815
  }
692
816
  }
693
817
  exports.ContactFormValuesContainer = ContactFormValuesContainer;
818
+ // Runtime validation for FieldValue — guards against mistyped data coming from
819
+ // external callers (formulas, SDK bridges, etc.) where the TypeScript types may
820
+ // not match the actual runtime values.
821
+ const validatePrimitiveType = (pt, path) => {
822
+ const v = pt.value;
823
+ switch (pt.type) {
824
+ case 'string':
825
+ if (typeof v !== 'string') {
826
+ throw new Error(`${path}: expected string value, got ${typeof v}`);
827
+ }
828
+ break;
829
+ case 'number':
830
+ if (typeof v !== 'number') {
831
+ throw new Error(`${path}: expected number value, got ${typeof v}`);
832
+ }
833
+ break;
834
+ case 'boolean':
835
+ if (typeof v !== 'boolean') {
836
+ throw new Error(`${path}: expected boolean value, got ${typeof v}`);
837
+ }
838
+ break;
839
+ case 'timestamp':
840
+ if (typeof v !== 'number') {
841
+ throw new Error(`${path}: expected number value for timestamp, got ${typeof v}`);
842
+ }
843
+ break;
844
+ case 'datetime':
845
+ if (typeof v !== 'number') {
846
+ throw new Error(`${path}: expected number value for datetime, got ${typeof v}`);
847
+ }
848
+ break;
849
+ case 'measure': {
850
+ const m = pt;
851
+ if (m.value !== undefined && typeof m.value !== 'number') {
852
+ throw new Error(`${path}: expected number or undefined value for measure, got ${typeof m.value}`);
853
+ }
854
+ if (m.unit !== undefined && typeof m.unit !== 'string') {
855
+ throw new Error(`${path}: expected string or undefined unit for measure, got ${typeof m.unit}`);
856
+ }
857
+ break;
858
+ }
859
+ case 'compound':
860
+ if (typeof v !== 'object' || v === null || Array.isArray(v)) {
861
+ throw new Error(`${path}: expected object value for compound, got ${Array.isArray(v) ? 'array' : typeof v}`);
862
+ }
863
+ for (const [key, subValue] of Object.entries(v)) {
864
+ validatePrimitiveType(subValue, `${path}.compound[${key}]`);
865
+ }
866
+ break;
867
+ default:
868
+ throw new Error(`${path}: unknown PrimitiveType type: ${pt.type}`);
869
+ }
870
+ };
871
+ const validateCode = (code, path) => {
872
+ const id = code.id;
873
+ if (!id || typeof id !== 'string') {
874
+ throw new Error(`${path}: code must have a non-empty string id, got ${JSON.stringify(id)}`);
875
+ }
876
+ };
877
+ const validateFieldValue = (fv, language, label) => {
878
+ const pt = fv.content[language];
879
+ if (pt) {
880
+ validatePrimitiveType(pt, `FieldValue[${label}].content[${language}]`);
881
+ }
882
+ if (fv.codes) {
883
+ fv.codes.forEach((code, i) => validateCode(code, `FieldValue[${label}].codes[${i}]`));
884
+ }
885
+ };
694
886
  const setValueOnContactFormValuesContainer = (cfvc, label, language, fv, id, metadata, changeListenersOverrider) => {
695
887
  var _a, _b;
888
+ if (fv) {
889
+ validateFieldValue(fv, language, label);
890
+ }
696
891
  const value = fv === null || fv === void 0 ? void 0 : fv.content[language];
697
892
  cfvc.setValue(label, language, {
698
893
  id: id,