@inseefr/lunatic 3.6.11 → 3.6.12
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.
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.d.ts +30 -3
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.js +104 -26
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.js.map +1 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js +1 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/use-lunatic/commons/variables/lunatic-variables-store.spec.ts +1 -1
- package/src/use-lunatic/commons/variables/lunatic-variables-store.ts +128 -39
- package/tsconfig.build.tsbuildinfo +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.d.ts +30 -3
- package/use-lunatic/commons/variables/lunatic-variables-store.js +109 -24
- package/use-lunatic/commons/variables/lunatic-variables-store.js.map +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.spec.js +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
|
@@ -41,15 +41,30 @@ export type EventArgs = {
|
|
|
41
41
|
[extra: string]: unknown;
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
|
+
|
|
44
45
|
export type LunaticVariablesStoreEvent<T extends keyof EventArgs> = {
|
|
45
46
|
detail: EventArgs[T];
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Represent a point in time (more precise than Date)
|
|
51
|
+
*/
|
|
52
|
+
class Timekey {
|
|
53
|
+
private time = performance.now();
|
|
54
|
+
getTime() {
|
|
55
|
+
return this.time;
|
|
56
|
+
}
|
|
57
|
+
touch() {
|
|
58
|
+
this.time = performance.now();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
export class LunaticVariablesStore {
|
|
49
63
|
private dictionary = new Map<string, LunaticVariable>();
|
|
50
64
|
private eventTarget = new EventTarget();
|
|
51
65
|
private queue = new Map<string, () => void>();
|
|
52
66
|
public autoCommit = false; // Commit change instantly (used in tests)
|
|
67
|
+
public updatedAt = new Timekey();
|
|
53
68
|
|
|
54
69
|
constructor() {
|
|
55
70
|
interpretCount = 0;
|
|
@@ -65,6 +80,7 @@ export class LunaticVariablesStore {
|
|
|
65
80
|
autoCommit?: boolean
|
|
66
81
|
) {
|
|
67
82
|
const store = new LunaticVariablesStore();
|
|
83
|
+
(window as any).lunaticStore = store; // Allow access to the store from the console
|
|
68
84
|
if (!source.variables) {
|
|
69
85
|
return store;
|
|
70
86
|
}
|
|
@@ -107,6 +123,7 @@ export class LunaticVariablesStore {
|
|
|
107
123
|
}
|
|
108
124
|
resizingBehaviour(store, source.resizing);
|
|
109
125
|
missingBehaviour(store, source.missingBlock);
|
|
126
|
+
store.updatedAt.touch();
|
|
110
127
|
return store;
|
|
111
128
|
}
|
|
112
129
|
|
|
@@ -176,20 +193,14 @@ export class LunaticVariablesStore {
|
|
|
176
193
|
'iteration' | 'cause' | 'ignoreIterationOnScalar'
|
|
177
194
|
> = {}
|
|
178
195
|
): LunaticVariable {
|
|
196
|
+
this.updatedAt.touch();
|
|
179
197
|
if (!this.dictionary.has(name)) {
|
|
180
198
|
this.dictionary.set(
|
|
181
199
|
name,
|
|
182
200
|
new LunaticVariable({
|
|
183
201
|
name,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
this.eventTarget.dispatchEvent(
|
|
187
|
-
new CustomEvent('change', {
|
|
188
|
-
detail: {
|
|
189
|
-
...args,
|
|
190
|
-
name: name,
|
|
191
|
-
value: value,
|
|
192
|
-
} satisfies EventArgs['change'],
|
|
202
|
+
dependencies: [],
|
|
203
|
+
storeUpdatedAt: this.updatedAt,
|
|
193
204
|
})
|
|
194
205
|
);
|
|
195
206
|
}
|
|
@@ -218,10 +229,12 @@ export class LunaticVariablesStore {
|
|
|
218
229
|
dependencies,
|
|
219
230
|
iterationDepth,
|
|
220
231
|
shapeFrom,
|
|
232
|
+
dimension,
|
|
221
233
|
}: {
|
|
222
234
|
dependencies?: string[];
|
|
223
235
|
iterationDepth?: number;
|
|
224
236
|
shapeFrom?: string | string[];
|
|
237
|
+
dimension?: number;
|
|
225
238
|
} = {}
|
|
226
239
|
): LunaticVariable {
|
|
227
240
|
if (this.dictionary.has(name)) {
|
|
@@ -234,8 +247,11 @@ export class LunaticVariablesStore {
|
|
|
234
247
|
dependencies,
|
|
235
248
|
iterationDepth,
|
|
236
249
|
name,
|
|
250
|
+
dimension,
|
|
251
|
+
storeUpdatedAt: this.updatedAt,
|
|
237
252
|
});
|
|
238
253
|
this.dictionary.set(name, variable);
|
|
254
|
+
this.updatedAt.touch();
|
|
239
255
|
return variable;
|
|
240
256
|
}
|
|
241
257
|
|
|
@@ -302,12 +318,16 @@ export class LunaticVariablesStore {
|
|
|
302
318
|
class LunaticVariable {
|
|
303
319
|
/** Last time the value was updated (changed). */
|
|
304
320
|
public updatedAt = new Map<undefined | string, number>();
|
|
321
|
+
/** Last time the store was updated (changed). */
|
|
322
|
+
private storeUpdatedAt: Timekey;
|
|
305
323
|
/** Last time "calculation" was run (for calculated variable). */
|
|
306
324
|
private calculatedAt = new Map<undefined | string, number>();
|
|
307
325
|
/** Internal value for the variable. */
|
|
308
326
|
private value: unknown;
|
|
309
|
-
/** List of dependencies, ex: ['FIRSTNAME', 'LASTNAME']. */
|
|
327
|
+
/** List of direct dependencies, ex: ['FULLNAME', 'FIRSTNAME', 'LASTNAME']. */
|
|
310
328
|
private dependencies?: string[];
|
|
329
|
+
/** List of deep dependencies, exploring the variables used in calculated variables, ex: ['FIRSTNAME', 'LASTNAME']. */
|
|
330
|
+
private baseDependencies?: string[];
|
|
311
331
|
/** Expression for calculated variable. */
|
|
312
332
|
public readonly expression?: string;
|
|
313
333
|
/** Dictionary holding all the available variables. */
|
|
@@ -320,17 +340,19 @@ class LunaticVariable {
|
|
|
320
340
|
public readonly name?: string;
|
|
321
341
|
/** Count the number of calculation. */
|
|
322
342
|
public calculatedCount = 0;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
/** Dimension **/
|
|
344
|
+
public dimension?: number;
|
|
345
|
+
|
|
346
|
+
constructor(args: {
|
|
347
|
+
expression?: string;
|
|
348
|
+
dependencies?: string[];
|
|
349
|
+
dictionary?: Map<string, LunaticVariable>;
|
|
350
|
+
iterationDepth?: number;
|
|
351
|
+
shapeFrom?: string | string[];
|
|
352
|
+
name?: string;
|
|
353
|
+
dimension?: number;
|
|
354
|
+
storeUpdatedAt: Timekey;
|
|
355
|
+
}) {
|
|
334
356
|
if (args.expression && !args.dictionary) {
|
|
335
357
|
throw new Error(
|
|
336
358
|
`A calculated variable needs a dictionary to retrieve his deps`
|
|
@@ -343,6 +365,8 @@ class LunaticVariable {
|
|
|
343
365
|
this.shapeFrom =
|
|
344
366
|
typeof args.shapeFrom === 'string' ? [args.shapeFrom] : args.shapeFrom;
|
|
345
367
|
this.name = args.name ?? args.expression;
|
|
368
|
+
this.dimension = args.dimension;
|
|
369
|
+
this.storeUpdatedAt = args.storeUpdatedAt;
|
|
346
370
|
}
|
|
347
371
|
|
|
348
372
|
getValue(iteration?: IterationLevel): unknown {
|
|
@@ -370,22 +394,29 @@ class LunaticVariable {
|
|
|
370
394
|
iteration = undefined;
|
|
371
395
|
}
|
|
372
396
|
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
const hasNoBinding = Object.keys(bindings).length === 0;
|
|
397
|
+
const deps = this.getDependencies();
|
|
398
|
+
const hasNoBindings = deps.length === 0;
|
|
376
399
|
|
|
377
400
|
// A static expression should not be reevaluated
|
|
378
|
-
if (
|
|
401
|
+
if (hasNoBindings && this.value) {
|
|
379
402
|
return this.value;
|
|
380
403
|
}
|
|
381
404
|
|
|
382
405
|
// A variable without binding is a primitive (string, boolean...)
|
|
383
406
|
// it yields the same results for every iteration, so we can ignore iteration
|
|
384
|
-
if (
|
|
407
|
+
if (hasNoBindings) {
|
|
385
408
|
iteration = undefined;
|
|
386
409
|
}
|
|
387
410
|
|
|
411
|
+
// The store did not change since the last calculation, skip further checks
|
|
412
|
+
if (this.getCalculatedAt(iteration) > this.storeUpdatedAt.getTime()) {
|
|
413
|
+
return this.getSavedValue(iteration);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Calculate bindings first to refresh "updatedAt" on calculated dependencies
|
|
417
|
+
const bindings = this.getDependenciesValues(iteration);
|
|
388
418
|
if (this.shapeFrom && !this.isOutdated(iteration)) {
|
|
419
|
+
this.updateTimestamps(iteration, 'calculatedAt');
|
|
389
420
|
return this.getSavedValue(iteration);
|
|
390
421
|
}
|
|
391
422
|
if (isTestEnv()) {
|
|
@@ -492,6 +523,31 @@ class LunaticVariable {
|
|
|
492
523
|
return current;
|
|
493
524
|
}
|
|
494
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Get a list of transitive dependencies (leaf of the dependency tree)
|
|
528
|
+
*/
|
|
529
|
+
private getBaseDependencies(): string[] {
|
|
530
|
+
// Find the dependencies of the dependencies
|
|
531
|
+
const reducer = (acc: Set<string>, variableName: string) => {
|
|
532
|
+
if (acc.has(variableName)) {
|
|
533
|
+
return acc;
|
|
534
|
+
}
|
|
535
|
+
const deps = this.dictionary?.get(variableName)?.getDependencies();
|
|
536
|
+
if (!deps || deps.length === 0) {
|
|
537
|
+
acc.add(variableName);
|
|
538
|
+
} else {
|
|
539
|
+
deps?.reduce(reducer, acc);
|
|
540
|
+
}
|
|
541
|
+
return acc;
|
|
542
|
+
};
|
|
543
|
+
if (this.baseDependencies === undefined) {
|
|
544
|
+
this.baseDependencies = [
|
|
545
|
+
...this.getDependencies().reduce(reducer, new Set<string>()),
|
|
546
|
+
];
|
|
547
|
+
}
|
|
548
|
+
return this.baseDependencies;
|
|
549
|
+
}
|
|
550
|
+
|
|
495
551
|
private getDependencies(): string[] {
|
|
496
552
|
// Calculate dependencies from expression on the fly if necessary
|
|
497
553
|
if (this.dependencies === undefined) {
|
|
@@ -534,22 +590,55 @@ class LunaticVariable {
|
|
|
534
590
|
}
|
|
535
591
|
}
|
|
536
592
|
|
|
593
|
+
/**
|
|
594
|
+
* Check if the variable should be updated (comparing calculatedAt to dependency updated time)
|
|
595
|
+
*/
|
|
537
596
|
private isOutdated(iteration?: IterationLevel): boolean {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
597
|
+
const deps = this.getDependencies();
|
|
598
|
+
const lastCalculatedAt = this.calculatedAt.get(iteration?.join('.'));
|
|
599
|
+
// Variable was never calculated
|
|
600
|
+
if (!lastCalculatedAt) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Look for an outdated dependency
|
|
605
|
+
for (const dep of deps) {
|
|
606
|
+
const depUpdatedAt =
|
|
607
|
+
this.dictionary?.get(dep)?.getUpdatedAt(iteration) ?? 0;
|
|
608
|
+
if (depUpdatedAt > lastCalculatedAt) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
public getUpdatedAt(iteration?: IterationLevel): number {
|
|
616
|
+
if (this.dimension === 0) {
|
|
617
|
+
return this.updatedAt.get(undefined) ?? 0;
|
|
618
|
+
}
|
|
619
|
+
// The value is an array, do not look at the root updatedAt if an iteration is provided
|
|
620
|
+
if (Array.isArray(this.value)) {
|
|
621
|
+
return this.updatedAt.get(iteration?.join('.')) ?? 0;
|
|
622
|
+
}
|
|
623
|
+
return (
|
|
624
|
+
this.updatedAt.get(iteration?.join('.')) ??
|
|
625
|
+
this.updatedAt.get(undefined) ??
|
|
626
|
+
0
|
|
549
627
|
);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
public getCalculatedAt(iteration?: IterationLevel): number {
|
|
631
|
+
if (this.dimension === 0) {
|
|
632
|
+
return this.calculatedAt.get(undefined) ?? 0;
|
|
633
|
+
}
|
|
634
|
+
// The value is an array, do not look at the root updatedAt if an iteration is provided
|
|
635
|
+
if (Array.isArray(this.value)) {
|
|
636
|
+
return this.calculatedAt.get(iteration?.join('.')) ?? 0;
|
|
637
|
+
}
|
|
550
638
|
return (
|
|
551
|
-
|
|
552
|
-
|
|
639
|
+
this.calculatedAt.get(iteration?.join('.')) ??
|
|
640
|
+
this.calculatedAt.get(undefined) ??
|
|
641
|
+
0
|
|
553
642
|
);
|
|
554
643
|
}
|
|
555
644
|
}
|