@fluidframework/core-utils 2.0.0-internal.5.2.0 → 2.0.0-internal.5.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @fluidframework/core-utils
2
2
 
3
+ ## 2.0.0-internal.5.3.0
4
+
5
+ Dependency updates only.
6
+
3
7
  ## 2.0.0-internal.5.2.0
4
8
 
5
9
  Dependency updates only.
package/dist/index.d.ts CHANGED
@@ -3,4 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export { compareArrays } from "./compare";
6
+ export { Lazy, LazyPromise } from "./lazy";
7
+ export { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from "./promiseCache";
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -4,7 +4,12 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.compareArrays = void 0;
7
+ exports.PromiseCache = exports.LazyPromise = exports.Lazy = exports.compareArrays = void 0;
8
8
  var compare_1 = require("./compare");
9
9
  Object.defineProperty(exports, "compareArrays", { enumerable: true, get: function () { return compare_1.compareArrays; } });
10
+ var lazy_1 = require("./lazy");
11
+ Object.defineProperty(exports, "Lazy", { enumerable: true, get: function () { return lazy_1.Lazy; } });
12
+ Object.defineProperty(exports, "LazyPromise", { enumerable: true, get: function () { return lazy_1.LazyPromise; } });
13
+ var promiseCache_1 = require("./promiseCache");
14
+ Object.defineProperty(exports, "PromiseCache", { enumerable: true, get: function () { return promiseCache_1.PromiseCache; } });
10
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qCAA0C;AAAjC,wGAAA,aAAa,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qCAA0C;AAAjC,wGAAA,aAAa,OAAA;AACtB,+BAA2C;AAAlC,4FAAA,IAAI,OAAA;AAAE,mGAAA,WAAW,OAAA;AAC1B,+CAAuF;AAA9E,4GAAA,YAAY,OAAA","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\nexport { Lazy, LazyPromise } from \"./lazy\";\nexport { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from \"./promiseCache\";\n"]}
package/dist/lazy.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
7
+ */
8
+ export declare class Lazy<T> {
9
+ private readonly valueGenerator;
10
+ private _value;
11
+ private _evaluated;
12
+ /**
13
+ * Instantiates an instance of Lazy<T>.
14
+ * @param valueGenerator - The function that will generate the value when value is accessed the first time.
15
+ */
16
+ constructor(valueGenerator: () => T);
17
+ /**
18
+ * Return true if the value as been generated, otherwise false.
19
+ */
20
+ get evaluated(): boolean;
21
+ /**
22
+ * Get the value. If this is the first call the value will be generated.
23
+ */
24
+ get value(): T;
25
+ }
26
+ /**
27
+ * A lazy evaluated promise. The execute function is delayed until
28
+ * the promise is used, e.g. await, then, catch ...
29
+ * The execute function is only called once.
30
+ * All calls are then proxied to the promise returned by the execute method.
31
+ */
32
+ export declare class LazyPromise<T> implements Promise<T> {
33
+ private readonly execute;
34
+ get [Symbol.toStringTag](): string;
35
+ private result;
36
+ constructor(execute: () => Promise<T>);
37
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
38
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
39
+ finally(onfinally?: (() => void) | null | undefined): Promise<T>;
40
+ private getPromise;
41
+ }
42
+ //# sourceMappingURL=lazy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,qBAAa,IAAI,CAAC,CAAC;IAON,OAAO,CAAC,QAAQ,CAAC,cAAc;IAN3C,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAkB;IACpC;;;OAGG;gBAC0B,cAAc,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,IAAW,KAAK,IAAI,CAAC,CAOpB;CACD;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,OAAO,CAAC,CAAC,CAAC;IAOpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAExC;IAED,OAAO,CAAC,MAAM,CAAyB;gBAEV,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;IAEzC,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK,EAE/C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAEjF,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GACjF,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAKlB,KAAK,CAAC,OAAO,GAAG,KAAK,EAEjC,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC;IAMV,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;YAK/D,UAAU;CAMxB"}
package/dist/lazy.js ADDED
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.LazyPromise = exports.Lazy = void 0;
8
+ /**
9
+ * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
10
+ */
11
+ class Lazy {
12
+ /**
13
+ * Instantiates an instance of Lazy<T>.
14
+ * @param valueGenerator - The function that will generate the value when value is accessed the first time.
15
+ */
16
+ constructor(valueGenerator) {
17
+ this.valueGenerator = valueGenerator;
18
+ this._evaluated = false;
19
+ }
20
+ /**
21
+ * Return true if the value as been generated, otherwise false.
22
+ */
23
+ get evaluated() {
24
+ return this._evaluated;
25
+ }
26
+ /**
27
+ * Get the value. If this is the first call the value will be generated.
28
+ */
29
+ get value() {
30
+ if (!this._evaluated) {
31
+ this._evaluated = true;
32
+ this._value = this.valueGenerator();
33
+ }
34
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
35
+ return this._value;
36
+ }
37
+ }
38
+ exports.Lazy = Lazy;
39
+ /**
40
+ * A lazy evaluated promise. The execute function is delayed until
41
+ * the promise is used, e.g. await, then, catch ...
42
+ * The execute function is only called once.
43
+ * All calls are then proxied to the promise returned by the execute method.
44
+ */
45
+ class LazyPromise {
46
+ constructor(execute) {
47
+ this.execute = execute;
48
+ }
49
+ get [Symbol.toStringTag]() {
50
+ return this.getPromise()[Symbol.toStringTag];
51
+ }
52
+ async then(
53
+ // eslint-disable-next-line @rushstack/no-new-null
54
+ onfulfilled,
55
+ // eslint-disable-next-line @rushstack/no-new-null
56
+ onrejected) {
57
+ // eslint-disable-next-line prefer-rest-params
58
+ return this.getPromise().then(...arguments);
59
+ }
60
+ async catch(
61
+ // eslint-disable-next-line @rushstack/no-new-null
62
+ onrejected) {
63
+ // eslint-disable-next-line prefer-rest-params
64
+ return this.getPromise().catch(...arguments);
65
+ }
66
+ // eslint-disable-next-line @rushstack/no-new-null
67
+ async finally(onfinally) {
68
+ // eslint-disable-next-line prefer-rest-params
69
+ return this.getPromise().finally(...arguments);
70
+ }
71
+ async getPromise() {
72
+ if (this.result === undefined) {
73
+ this.result = this.execute();
74
+ }
75
+ return this.result;
76
+ }
77
+ }
78
+ exports.LazyPromise = LazyPromise;
79
+ //# sourceMappingURL=lazy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH;;GAEG;AACH,MAAa,IAAI;IAGhB;;;OAGG;IACH,YAA6B,cAAuB;QAAvB,mBAAc,GAAd,cAAc,CAAS;QAL5C,eAAU,GAAY,KAAK,CAAC;IAKmB,CAAC;IAExD;;OAEG;IACH,IAAW,SAAS;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,IAAW,KAAK;QACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;SACpC;QACD,oEAAoE;QACpE,OAAO,IAAI,CAAC,MAAO,CAAC;IACrB,CAAC;CACD;AA3BD,oBA2BC;AAED;;;;;GAKG;AACH,MAAa,WAAW;IAOvB,YAA6B,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;IAAG,CAAC;IAN1D,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC9B,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IAMM,KAAK,CAAC,IAAI;IAChB,kDAAkD;IAClD,WAAiF;IACjF,kDAAkD;IAClD,UAAmF;QAEnF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAqB,GAAG,SAAS,CAAC,CAAC;IACjE,CAAC;IAEM,KAAK,CAAC,KAAK;IACjB,kDAAkD;IAClD,UAAiF;QAEjF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAU,GAAG,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,kDAAkD;IAC3C,KAAK,CAAC,OAAO,CAAC,SAA2C;QAC/D,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;YAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;SAC7B;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD;AAvCD,kCAuCC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.\n */\nexport class Lazy<T> {\n\tprivate _value: T | undefined;\n\tprivate _evaluated: boolean = false;\n\t/**\n\t * Instantiates an instance of Lazy<T>.\n\t * @param valueGenerator - The function that will generate the value when value is accessed the first time.\n\t */\n\tconstructor(private readonly valueGenerator: () => T) {}\n\n\t/**\n\t * Return true if the value as been generated, otherwise false.\n\t */\n\tpublic get evaluated(): boolean {\n\t\treturn this._evaluated;\n\t}\n\n\t/**\n\t * Get the value. If this is the first call the value will be generated.\n\t */\n\tpublic get value(): T {\n\t\tif (!this._evaluated) {\n\t\t\tthis._evaluated = true;\n\t\t\tthis._value = this.valueGenerator();\n\t\t}\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\treturn this._value!;\n\t}\n}\n\n/**\n * A lazy evaluated promise. The execute function is delayed until\n * the promise is used, e.g. await, then, catch ...\n * The execute function is only called once.\n * All calls are then proxied to the promise returned by the execute method.\n */\nexport class LazyPromise<T> implements Promise<T> {\n\tpublic get [Symbol.toStringTag](): string {\n\t\treturn this.getPromise()[Symbol.toStringTag];\n\t}\n\n\tprivate result: Promise<T> | undefined;\n\n\tconstructor(private readonly execute: () => Promise<T>) {}\n\n\tpublic async then<TResult1 = T, TResult2 = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,\n\t): Promise<TResult1 | TResult2> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().then<TResult1, TResult2>(...arguments);\n\t}\n\n\tpublic async catch<TResult = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,\n\t): Promise<T | TResult> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().catch<TResult>(...arguments);\n\t}\n\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpublic async finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().finally(...arguments);\n\t}\n\n\tprivate async getPromise(): Promise<T> {\n\t\tif (this.result === undefined) {\n\t\t\tthis.result = this.execute();\n\t\t}\n\t\treturn this.result;\n\t}\n}\n"]}
@@ -0,0 +1,82 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Three supported expiry policies:
7
+ * - indefinite: entries don't expire and must be explicitly removed
8
+ * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time
9
+ * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)
10
+ */
11
+ export declare type PromiseCacheExpiry = {
12
+ policy: "indefinite";
13
+ } | {
14
+ policy: "absolute" | "sliding";
15
+ durationMs: number;
16
+ };
17
+ /**
18
+ * Options for configuring the {@link PromiseCache}
19
+ */
20
+ export interface PromiseCacheOptions {
21
+ /** Common expiration policy for all items added to this cache */
22
+ expiry?: PromiseCacheExpiry;
23
+ /** If the stored Promise is rejected with a particular error, should the given key be removed? */
24
+ removeOnError?: (e: any) => boolean;
25
+ }
26
+ /**
27
+ * A specialized cache for async work, allowing you to safely cache the promised result of some async work
28
+ * without fear of running it multiple times or losing track of errors.
29
+ */
30
+ export declare class PromiseCache<TKey, TResult> {
31
+ private readonly cache;
32
+ private readonly gc;
33
+ private readonly removeOnError;
34
+ /**
35
+ * Create the PromiseCache with the given options, with the following defaults:
36
+ *
37
+ * expiry: indefinite, removeOnError: true for all errors
38
+ */
39
+ constructor({ expiry, removeOnError, }?: PromiseCacheOptions);
40
+ /**
41
+ * Check if there's anything cached at the given key
42
+ */
43
+ has(key: TKey): boolean;
44
+ /**
45
+ * Get the Promise for the given key, or undefined if it's not found.
46
+ * Extend expiry if applicable.
47
+ */
48
+ get(key: TKey): Promise<TResult> | undefined;
49
+ /**
50
+ * Remove the Promise for the given key, returning true if it was found and removed
51
+ */
52
+ remove(key: TKey): boolean;
53
+ /**
54
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
55
+ * Returns a Promise for the added or existing async work being done at that key.
56
+ * @param key - key name where to store the async work
57
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
58
+ */
59
+ addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult>;
60
+ /**
61
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
62
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
63
+ * @param key - key name where to store the async work
64
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
65
+ */
66
+ add(key: TKey, asyncFn: () => Promise<TResult>): boolean;
67
+ /**
68
+ * Try to add the given value, without overwriting an existing cache entry at that key.
69
+ * Returns a Promise for the added or existing async work being done at that key.
70
+ * @param key - key name where to store the async work
71
+ * @param value - value to store
72
+ */
73
+ addValueOrGet(key: TKey, value: TResult): Promise<TResult>;
74
+ /**
75
+ * Try to add the given value, without overwriting an existing cache entry at that key.
76
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
77
+ * @param key - key name where to store the value
78
+ * @param value - value to store
79
+ */
80
+ addValue(key: TKey, value: TResult): boolean;
81
+ }
82
+ //# sourceMappingURL=promiseCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promiseCache.d.ts","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,oBAAY,kBAAkB,GAC3B;IACA,MAAM,EAAE,YAAY,CAAC;CACpB,GACD;IACA,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CAClB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,iEAAiE;IACjE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,kGAAkG;IAClG,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACpC;AAoDD;;;GAGG;AACH,qBAAa,YAAY,CAAC,IAAI,EAAE,OAAO;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAyB;IAE5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IAExD;;;;OAIG;gBACS,EACX,MAAiC,EACjC,aAAmC,GACnC,GAAE,mBAAwB;IAK3B;;OAEG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAI9B;;;OAGG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS;IAOnD;;OAEG;IACI,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAKjC;;;;;OAKG;IACU,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBnF;;;;;OAKG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAU/D;;;;;OAKG;IACU,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvE;;;;;OAKG;IACI,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;CAGnD"}
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PromiseCache = void 0;
8
+ /**
9
+ * Handles garbage collection of expiring cache entries.
10
+ * Not exported.
11
+ */
12
+ class GarbageCollector {
13
+ constructor(expiry, cleanup) {
14
+ this.expiry = expiry;
15
+ this.cleanup = cleanup;
16
+ this.gcTimeouts = new Map();
17
+ }
18
+ /**
19
+ * Schedule GC for the given key, as applicable
20
+ */
21
+ schedule(key) {
22
+ if (this.expiry.policy !== "indefinite") {
23
+ this.gcTimeouts.set(key, setTimeout(() => {
24
+ this.cleanup(key);
25
+ this.cancel(key);
26
+ }, this.expiry.durationMs));
27
+ }
28
+ }
29
+ /**
30
+ * Cancel any pending GC for the given key
31
+ */
32
+ cancel(key) {
33
+ const timeout = this.gcTimeouts.get(key);
34
+ if (timeout !== undefined) {
35
+ clearTimeout(timeout);
36
+ this.gcTimeouts.delete(key);
37
+ }
38
+ }
39
+ /**
40
+ * Update any pending GC for the given key, as applicable
41
+ */
42
+ update(key) {
43
+ // Cancel/reschedule new GC if the policy is sliding
44
+ if (this.expiry.policy === "sliding") {
45
+ this.cancel(key);
46
+ this.schedule(key);
47
+ }
48
+ }
49
+ }
50
+ /**
51
+ * A specialized cache for async work, allowing you to safely cache the promised result of some async work
52
+ * without fear of running it multiple times or losing track of errors.
53
+ */
54
+ class PromiseCache {
55
+ /**
56
+ * Create the PromiseCache with the given options, with the following defaults:
57
+ *
58
+ * expiry: indefinite, removeOnError: true for all errors
59
+ */
60
+ constructor({ expiry = { policy: "indefinite" }, removeOnError = () => true, } = {}) {
61
+ this.cache = new Map();
62
+ this.removeOnError = removeOnError;
63
+ this.gc = new GarbageCollector(expiry, (key) => this.remove(key));
64
+ }
65
+ /**
66
+ * Check if there's anything cached at the given key
67
+ */
68
+ has(key) {
69
+ return this.cache.has(key);
70
+ }
71
+ /**
72
+ * Get the Promise for the given key, or undefined if it's not found.
73
+ * Extend expiry if applicable.
74
+ */
75
+ get(key) {
76
+ if (this.has(key)) {
77
+ this.gc.update(key);
78
+ }
79
+ return this.cache.get(key);
80
+ }
81
+ /**
82
+ * Remove the Promise for the given key, returning true if it was found and removed
83
+ */
84
+ remove(key) {
85
+ this.gc.cancel(key);
86
+ return this.cache.delete(key);
87
+ }
88
+ /**
89
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
90
+ * Returns a Promise for the added or existing async work being done at that key.
91
+ * @param key - key name where to store the async work
92
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
93
+ */
94
+ async addOrGet(key, asyncFn) {
95
+ // NOTE: Do not await the Promise returned by asyncFn!
96
+ // Let the caller do so once we return or after a subsequent call to get
97
+ let promise = this.get(key);
98
+ if (promise === undefined) {
99
+ // Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async
100
+ const safeAsyncFn = async () => asyncFn();
101
+ // Start the async work and put the Promise in the cache
102
+ promise = safeAsyncFn();
103
+ this.cache.set(key, promise);
104
+ // If asyncFn throws, we may remove the Promise from the cache
105
+ promise.catch((error) => {
106
+ if (this.removeOnError(error)) {
107
+ this.remove(key);
108
+ }
109
+ });
110
+ this.gc.schedule(key);
111
+ }
112
+ return promise;
113
+ }
114
+ /**
115
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
116
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
117
+ * @param key - key name where to store the async work
118
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
119
+ */
120
+ add(key, asyncFn) {
121
+ const alreadyPresent = this.has(key);
122
+ // We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.
123
+ // Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.
124
+ this.addOrGet(key, asyncFn).catch(() => { });
125
+ return !alreadyPresent;
126
+ }
127
+ /**
128
+ * Try to add the given value, without overwriting an existing cache entry at that key.
129
+ * Returns a Promise for the added or existing async work being done at that key.
130
+ * @param key - key name where to store the async work
131
+ * @param value - value to store
132
+ */
133
+ async addValueOrGet(key, value) {
134
+ return this.addOrGet(key, async () => value);
135
+ }
136
+ /**
137
+ * Try to add the given value, without overwriting an existing cache entry at that key.
138
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
139
+ * @param key - key name where to store the value
140
+ * @param value - value to store
141
+ */
142
+ addValue(key, value) {
143
+ return this.add(key, async () => value);
144
+ }
145
+ }
146
+ exports.PromiseCache = PromiseCache;
147
+ //# sourceMappingURL=promiseCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promiseCache.js","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Three supported expiry policies:\n * - indefinite: entries don't expire and must be explicitly removed\n * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time\n * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)\n */\nexport type PromiseCacheExpiry =\n\t| {\n\t\t\tpolicy: \"indefinite\";\n\t }\n\t| {\n\t\t\tpolicy: \"absolute\" | \"sliding\";\n\t\t\tdurationMs: number;\n\t };\n\n/**\n * Options for configuring the {@link PromiseCache}\n */\nexport interface PromiseCacheOptions {\n\t/** Common expiration policy for all items added to this cache */\n\texpiry?: PromiseCacheExpiry;\n\t/** If the stored Promise is rejected with a particular error, should the given key be removed? */\n\tremoveOnError?: (e: any) => boolean;\n}\n\n/**\n * Handles garbage collection of expiring cache entries.\n * Not exported.\n */\nclass GarbageCollector<TKey> {\n\tprivate readonly gcTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();\n\n\tconstructor(\n\t\tprivate readonly expiry: PromiseCacheExpiry,\n\t\tprivate readonly cleanup: (key: TKey) => void,\n\t) {}\n\n\t/**\n\t * Schedule GC for the given key, as applicable\n\t */\n\tpublic schedule(key: TKey): void {\n\t\tif (this.expiry.policy !== \"indefinite\") {\n\t\t\tthis.gcTimeouts.set(\n\t\t\t\tkey,\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.cleanup(key);\n\t\t\t\t\tthis.cancel(key);\n\t\t\t\t}, this.expiry.durationMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel any pending GC for the given key\n\t */\n\tpublic cancel(key: TKey): void {\n\t\tconst timeout = this.gcTimeouts.get(key);\n\t\tif (timeout !== undefined) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.gcTimeouts.delete(key);\n\t\t}\n\t}\n\n\t/**\n\t * Update any pending GC for the given key, as applicable\n\t */\n\tpublic update(key: TKey): void {\n\t\t// Cancel/reschedule new GC if the policy is sliding\n\t\tif (this.expiry.policy === \"sliding\") {\n\t\t\tthis.cancel(key);\n\t\t\tthis.schedule(key);\n\t\t}\n\t}\n}\n\n/**\n * A specialized cache for async work, allowing you to safely cache the promised result of some async work\n * without fear of running it multiple times or losing track of errors.\n */\nexport class PromiseCache<TKey, TResult> {\n\tprivate readonly cache = new Map<TKey, Promise<TResult>>();\n\tprivate readonly gc: GarbageCollector<TKey>;\n\n\tprivate readonly removeOnError: (error: any) => boolean;\n\n\t/**\n\t * Create the PromiseCache with the given options, with the following defaults:\n\t *\n\t * expiry: indefinite, removeOnError: true for all errors\n\t */\n\tconstructor({\n\t\texpiry = { policy: \"indefinite\" },\n\t\tremoveOnError = (): boolean => true,\n\t}: PromiseCacheOptions = {}) {\n\t\tthis.removeOnError = removeOnError;\n\t\tthis.gc = new GarbageCollector<TKey>(expiry, (key) => this.remove(key));\n\t}\n\n\t/**\n\t * Check if there's anything cached at the given key\n\t */\n\tpublic has(key: TKey): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Get the Promise for the given key, or undefined if it's not found.\n\t * Extend expiry if applicable.\n\t */\n\tpublic get(key: TKey): Promise<TResult> | undefined {\n\t\tif (this.has(key)) {\n\t\t\tthis.gc.update(key);\n\t\t}\n\t\treturn this.cache.get(key);\n\t}\n\n\t/**\n\t * Remove the Promise for the given key, returning true if it was found and removed\n\t */\n\tpublic remove(key: TKey): boolean {\n\t\tthis.gc.cancel(key);\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic async addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult> {\n\t\t// NOTE: Do not await the Promise returned by asyncFn!\n\t\t// Let the caller do so once we return or after a subsequent call to get\n\t\tlet promise = this.get(key);\n\t\tif (promise === undefined) {\n\t\t\t// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async\n\t\t\tconst safeAsyncFn = async (): Promise<TResult> => asyncFn();\n\n\t\t\t// Start the async work and put the Promise in the cache\n\t\t\tpromise = safeAsyncFn();\n\t\t\tthis.cache.set(key, promise);\n\n\t\t\t// If asyncFn throws, we may remove the Promise from the cache\n\t\t\tpromise.catch((error) => {\n\t\t\t\tif (this.removeOnError(error)) {\n\t\t\t\t\tthis.remove(key);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.gc.schedule(key);\n\t\t}\n\n\t\treturn promise;\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic add(key: TKey, asyncFn: () => Promise<TResult>): boolean {\n\t\tconst alreadyPresent = this.has(key);\n\n\t\t// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.\n\t\t// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.\n\t\tthis.addOrGet(key, asyncFn).catch(() => {});\n\n\t\treturn !alreadyPresent;\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param value - value to store\n\t */\n\tpublic async addValueOrGet(key: TKey, value: TResult): Promise<TResult> {\n\t\treturn this.addOrGet(key, async () => value);\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the value\n\t * @param value - value to store\n\t */\n\tpublic addValue(key: TKey, value: TResult): boolean {\n\t\treturn this.add(key, async () => value);\n\t}\n}\n"]}
package/lib/index.d.ts CHANGED
@@ -3,4 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export { compareArrays } from "./compare";
6
+ export { Lazy, LazyPromise } from "./lazy";
7
+ export { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from "./promiseCache";
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
package/lib/index.js CHANGED
@@ -3,4 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  export { compareArrays } from "./compare";
6
+ export { Lazy, LazyPromise } from "./lazy";
7
+ export { PromiseCache } from "./promiseCache";
6
8
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,YAAY,EAA2C,MAAM,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport { compareArrays } from \"./compare\";\nexport { Lazy, LazyPromise } from \"./lazy\";\nexport { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from \"./promiseCache\";\n"]}
package/lib/lazy.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
7
+ */
8
+ export declare class Lazy<T> {
9
+ private readonly valueGenerator;
10
+ private _value;
11
+ private _evaluated;
12
+ /**
13
+ * Instantiates an instance of Lazy<T>.
14
+ * @param valueGenerator - The function that will generate the value when value is accessed the first time.
15
+ */
16
+ constructor(valueGenerator: () => T);
17
+ /**
18
+ * Return true if the value as been generated, otherwise false.
19
+ */
20
+ get evaluated(): boolean;
21
+ /**
22
+ * Get the value. If this is the first call the value will be generated.
23
+ */
24
+ get value(): T;
25
+ }
26
+ /**
27
+ * A lazy evaluated promise. The execute function is delayed until
28
+ * the promise is used, e.g. await, then, catch ...
29
+ * The execute function is only called once.
30
+ * All calls are then proxied to the promise returned by the execute method.
31
+ */
32
+ export declare class LazyPromise<T> implements Promise<T> {
33
+ private readonly execute;
34
+ get [Symbol.toStringTag](): string;
35
+ private result;
36
+ constructor(execute: () => Promise<T>);
37
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): Promise<TResult1 | TResult2>;
38
+ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined): Promise<T | TResult>;
39
+ finally(onfinally?: (() => void) | null | undefined): Promise<T>;
40
+ private getPromise;
41
+ }
42
+ //# sourceMappingURL=lazy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,qBAAa,IAAI,CAAC,CAAC;IAON,OAAO,CAAC,QAAQ,CAAC,cAAc;IAN3C,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,UAAU,CAAkB;IACpC;;;OAGG;gBAC0B,cAAc,EAAE,MAAM,CAAC;IAEpD;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,IAAW,KAAK,IAAI,CAAC,CAOpB;CACD;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,YAAW,OAAO,CAAC,CAAC,CAAC;IAOpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAExC;IAED,OAAO,CAAC,MAAM,CAAyB;gBAEV,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;IAEzC,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK,EAE/C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAEjF,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GACjF,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAKlB,KAAK,CAAC,OAAO,GAAG,KAAK,EAEjC,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC;IAMV,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;YAK/D,UAAU;CAMxB"}
package/lib/lazy.js ADDED
@@ -0,0 +1,74 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
7
+ */
8
+ export class Lazy {
9
+ /**
10
+ * Instantiates an instance of Lazy<T>.
11
+ * @param valueGenerator - The function that will generate the value when value is accessed the first time.
12
+ */
13
+ constructor(valueGenerator) {
14
+ this.valueGenerator = valueGenerator;
15
+ this._evaluated = false;
16
+ }
17
+ /**
18
+ * Return true if the value as been generated, otherwise false.
19
+ */
20
+ get evaluated() {
21
+ return this._evaluated;
22
+ }
23
+ /**
24
+ * Get the value. If this is the first call the value will be generated.
25
+ */
26
+ get value() {
27
+ if (!this._evaluated) {
28
+ this._evaluated = true;
29
+ this._value = this.valueGenerator();
30
+ }
31
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
32
+ return this._value;
33
+ }
34
+ }
35
+ /**
36
+ * A lazy evaluated promise. The execute function is delayed until
37
+ * the promise is used, e.g. await, then, catch ...
38
+ * The execute function is only called once.
39
+ * All calls are then proxied to the promise returned by the execute method.
40
+ */
41
+ export class LazyPromise {
42
+ constructor(execute) {
43
+ this.execute = execute;
44
+ }
45
+ get [Symbol.toStringTag]() {
46
+ return this.getPromise()[Symbol.toStringTag];
47
+ }
48
+ async then(
49
+ // eslint-disable-next-line @rushstack/no-new-null
50
+ onfulfilled,
51
+ // eslint-disable-next-line @rushstack/no-new-null
52
+ onrejected) {
53
+ // eslint-disable-next-line prefer-rest-params
54
+ return this.getPromise().then(...arguments);
55
+ }
56
+ async catch(
57
+ // eslint-disable-next-line @rushstack/no-new-null
58
+ onrejected) {
59
+ // eslint-disable-next-line prefer-rest-params
60
+ return this.getPromise().catch(...arguments);
61
+ }
62
+ // eslint-disable-next-line @rushstack/no-new-null
63
+ async finally(onfinally) {
64
+ // eslint-disable-next-line prefer-rest-params
65
+ return this.getPromise().finally(...arguments);
66
+ }
67
+ async getPromise() {
68
+ if (this.result === undefined) {
69
+ this.result = this.execute();
70
+ }
71
+ return this.result;
72
+ }
73
+ }
74
+ //# sourceMappingURL=lazy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../src/lazy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,OAAO,IAAI;IAGhB;;;OAGG;IACH,YAA6B,cAAuB;QAAvB,mBAAc,GAAd,cAAc,CAAS;QAL5C,eAAU,GAAY,KAAK,CAAC;IAKmB,CAAC;IAExD;;OAEG;IACH,IAAW,SAAS;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,IAAW,KAAK;QACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;SACpC;QACD,oEAAoE;QACpE,OAAO,IAAI,CAAC,MAAO,CAAC;IACrB,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAOvB,YAA6B,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;IAAG,CAAC;IAN1D,IAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC9B,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IAMM,KAAK,CAAC,IAAI;IAChB,kDAAkD;IAClD,WAAiF;IACjF,kDAAkD;IAClD,UAAmF;QAEnF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAqB,GAAG,SAAS,CAAC,CAAC;IACjE,CAAC;IAEM,KAAK,CAAC,KAAK;IACjB,kDAAkD;IAClD,UAAiF;QAEjF,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAU,GAAG,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,kDAAkD;IAC3C,KAAK,CAAC,OAAO,CAAC,SAA2C;QAC/D,8CAA8C;QAC9C,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,UAAU;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;YAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;SAC7B;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.\n */\nexport class Lazy<T> {\n\tprivate _value: T | undefined;\n\tprivate _evaluated: boolean = false;\n\t/**\n\t * Instantiates an instance of Lazy<T>.\n\t * @param valueGenerator - The function that will generate the value when value is accessed the first time.\n\t */\n\tconstructor(private readonly valueGenerator: () => T) {}\n\n\t/**\n\t * Return true if the value as been generated, otherwise false.\n\t */\n\tpublic get evaluated(): boolean {\n\t\treturn this._evaluated;\n\t}\n\n\t/**\n\t * Get the value. If this is the first call the value will be generated.\n\t */\n\tpublic get value(): T {\n\t\tif (!this._evaluated) {\n\t\t\tthis._evaluated = true;\n\t\t\tthis._value = this.valueGenerator();\n\t\t}\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\treturn this._value!;\n\t}\n}\n\n/**\n * A lazy evaluated promise. The execute function is delayed until\n * the promise is used, e.g. await, then, catch ...\n * The execute function is only called once.\n * All calls are then proxied to the promise returned by the execute method.\n */\nexport class LazyPromise<T> implements Promise<T> {\n\tpublic get [Symbol.toStringTag](): string {\n\t\treturn this.getPromise()[Symbol.toStringTag];\n\t}\n\n\tprivate result: Promise<T> | undefined;\n\n\tconstructor(private readonly execute: () => Promise<T>) {}\n\n\tpublic async then<TResult1 = T, TResult2 = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,\n\t): Promise<TResult1 | TResult2> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().then<TResult1, TResult2>(...arguments);\n\t}\n\n\tpublic async catch<TResult = never>(\n\t\t// eslint-disable-next-line @rushstack/no-new-null\n\t\tonrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,\n\t): Promise<T | TResult> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().catch<TResult>(...arguments);\n\t}\n\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpublic async finally(onfinally?: (() => void) | null | undefined): Promise<T> {\n\t\t// eslint-disable-next-line prefer-rest-params\n\t\treturn this.getPromise().finally(...arguments);\n\t}\n\n\tprivate async getPromise(): Promise<T> {\n\t\tif (this.result === undefined) {\n\t\t\tthis.result = this.execute();\n\t\t}\n\t\treturn this.result;\n\t}\n}\n"]}
@@ -0,0 +1,82 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Three supported expiry policies:
7
+ * - indefinite: entries don't expire and must be explicitly removed
8
+ * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time
9
+ * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)
10
+ */
11
+ export declare type PromiseCacheExpiry = {
12
+ policy: "indefinite";
13
+ } | {
14
+ policy: "absolute" | "sliding";
15
+ durationMs: number;
16
+ };
17
+ /**
18
+ * Options for configuring the {@link PromiseCache}
19
+ */
20
+ export interface PromiseCacheOptions {
21
+ /** Common expiration policy for all items added to this cache */
22
+ expiry?: PromiseCacheExpiry;
23
+ /** If the stored Promise is rejected with a particular error, should the given key be removed? */
24
+ removeOnError?: (e: any) => boolean;
25
+ }
26
+ /**
27
+ * A specialized cache for async work, allowing you to safely cache the promised result of some async work
28
+ * without fear of running it multiple times or losing track of errors.
29
+ */
30
+ export declare class PromiseCache<TKey, TResult> {
31
+ private readonly cache;
32
+ private readonly gc;
33
+ private readonly removeOnError;
34
+ /**
35
+ * Create the PromiseCache with the given options, with the following defaults:
36
+ *
37
+ * expiry: indefinite, removeOnError: true for all errors
38
+ */
39
+ constructor({ expiry, removeOnError, }?: PromiseCacheOptions);
40
+ /**
41
+ * Check if there's anything cached at the given key
42
+ */
43
+ has(key: TKey): boolean;
44
+ /**
45
+ * Get the Promise for the given key, or undefined if it's not found.
46
+ * Extend expiry if applicable.
47
+ */
48
+ get(key: TKey): Promise<TResult> | undefined;
49
+ /**
50
+ * Remove the Promise for the given key, returning true if it was found and removed
51
+ */
52
+ remove(key: TKey): boolean;
53
+ /**
54
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
55
+ * Returns a Promise for the added or existing async work being done at that key.
56
+ * @param key - key name where to store the async work
57
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
58
+ */
59
+ addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult>;
60
+ /**
61
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
62
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
63
+ * @param key - key name where to store the async work
64
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
65
+ */
66
+ add(key: TKey, asyncFn: () => Promise<TResult>): boolean;
67
+ /**
68
+ * Try to add the given value, without overwriting an existing cache entry at that key.
69
+ * Returns a Promise for the added or existing async work being done at that key.
70
+ * @param key - key name where to store the async work
71
+ * @param value - value to store
72
+ */
73
+ addValueOrGet(key: TKey, value: TResult): Promise<TResult>;
74
+ /**
75
+ * Try to add the given value, without overwriting an existing cache entry at that key.
76
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
77
+ * @param key - key name where to store the value
78
+ * @param value - value to store
79
+ */
80
+ addValue(key: TKey, value: TResult): boolean;
81
+ }
82
+ //# sourceMappingURL=promiseCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promiseCache.d.ts","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,oBAAY,kBAAkB,GAC3B;IACA,MAAM,EAAE,YAAY,CAAC;CACpB,GACD;IACA,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CAClB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,iEAAiE;IACjE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,kGAAkG;IAClG,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACpC;AAoDD;;;GAGG;AACH,qBAAa,YAAY,CAAC,IAAI,EAAE,OAAO;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAyB;IAE5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IAExD;;;;OAIG;gBACS,EACX,MAAiC,EACjC,aAAmC,GACnC,GAAE,mBAAwB;IAK3B;;OAEG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAI9B;;;OAGG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS;IAOnD;;OAEG;IACI,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO;IAKjC;;;;;OAKG;IACU,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBnF;;;;;OAKG;IACI,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAU/D;;;;;OAKG;IACU,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvE;;;;;OAKG;IACI,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;CAGnD"}
@@ -0,0 +1,143 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /**
6
+ * Handles garbage collection of expiring cache entries.
7
+ * Not exported.
8
+ */
9
+ class GarbageCollector {
10
+ constructor(expiry, cleanup) {
11
+ this.expiry = expiry;
12
+ this.cleanup = cleanup;
13
+ this.gcTimeouts = new Map();
14
+ }
15
+ /**
16
+ * Schedule GC for the given key, as applicable
17
+ */
18
+ schedule(key) {
19
+ if (this.expiry.policy !== "indefinite") {
20
+ this.gcTimeouts.set(key, setTimeout(() => {
21
+ this.cleanup(key);
22
+ this.cancel(key);
23
+ }, this.expiry.durationMs));
24
+ }
25
+ }
26
+ /**
27
+ * Cancel any pending GC for the given key
28
+ */
29
+ cancel(key) {
30
+ const timeout = this.gcTimeouts.get(key);
31
+ if (timeout !== undefined) {
32
+ clearTimeout(timeout);
33
+ this.gcTimeouts.delete(key);
34
+ }
35
+ }
36
+ /**
37
+ * Update any pending GC for the given key, as applicable
38
+ */
39
+ update(key) {
40
+ // Cancel/reschedule new GC if the policy is sliding
41
+ if (this.expiry.policy === "sliding") {
42
+ this.cancel(key);
43
+ this.schedule(key);
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ * A specialized cache for async work, allowing you to safely cache the promised result of some async work
49
+ * without fear of running it multiple times or losing track of errors.
50
+ */
51
+ export class PromiseCache {
52
+ /**
53
+ * Create the PromiseCache with the given options, with the following defaults:
54
+ *
55
+ * expiry: indefinite, removeOnError: true for all errors
56
+ */
57
+ constructor({ expiry = { policy: "indefinite" }, removeOnError = () => true, } = {}) {
58
+ this.cache = new Map();
59
+ this.removeOnError = removeOnError;
60
+ this.gc = new GarbageCollector(expiry, (key) => this.remove(key));
61
+ }
62
+ /**
63
+ * Check if there's anything cached at the given key
64
+ */
65
+ has(key) {
66
+ return this.cache.has(key);
67
+ }
68
+ /**
69
+ * Get the Promise for the given key, or undefined if it's not found.
70
+ * Extend expiry if applicable.
71
+ */
72
+ get(key) {
73
+ if (this.has(key)) {
74
+ this.gc.update(key);
75
+ }
76
+ return this.cache.get(key);
77
+ }
78
+ /**
79
+ * Remove the Promise for the given key, returning true if it was found and removed
80
+ */
81
+ remove(key) {
82
+ this.gc.cancel(key);
83
+ return this.cache.delete(key);
84
+ }
85
+ /**
86
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
87
+ * Returns a Promise for the added or existing async work being done at that key.
88
+ * @param key - key name where to store the async work
89
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
90
+ */
91
+ async addOrGet(key, asyncFn) {
92
+ // NOTE: Do not await the Promise returned by asyncFn!
93
+ // Let the caller do so once we return or after a subsequent call to get
94
+ let promise = this.get(key);
95
+ if (promise === undefined) {
96
+ // Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async
97
+ const safeAsyncFn = async () => asyncFn();
98
+ // Start the async work and put the Promise in the cache
99
+ promise = safeAsyncFn();
100
+ this.cache.set(key, promise);
101
+ // If asyncFn throws, we may remove the Promise from the cache
102
+ promise.catch((error) => {
103
+ if (this.removeOnError(error)) {
104
+ this.remove(key);
105
+ }
106
+ });
107
+ this.gc.schedule(key);
108
+ }
109
+ return promise;
110
+ }
111
+ /**
112
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
113
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
114
+ * @param key - key name where to store the async work
115
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
116
+ */
117
+ add(key, asyncFn) {
118
+ const alreadyPresent = this.has(key);
119
+ // We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.
120
+ // Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.
121
+ this.addOrGet(key, asyncFn).catch(() => { });
122
+ return !alreadyPresent;
123
+ }
124
+ /**
125
+ * Try to add the given value, without overwriting an existing cache entry at that key.
126
+ * Returns a Promise for the added or existing async work being done at that key.
127
+ * @param key - key name where to store the async work
128
+ * @param value - value to store
129
+ */
130
+ async addValueOrGet(key, value) {
131
+ return this.addOrGet(key, async () => value);
132
+ }
133
+ /**
134
+ * Try to add the given value, without overwriting an existing cache entry at that key.
135
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
136
+ * @param key - key name where to store the value
137
+ * @param value - value to store
138
+ */
139
+ addValue(key, value) {
140
+ return this.add(key, async () => value);
141
+ }
142
+ }
143
+ //# sourceMappingURL=promiseCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promiseCache.js","sourceRoot":"","sources":["../src/promiseCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * Three supported expiry policies:\n * - indefinite: entries don't expire and must be explicitly removed\n * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time\n * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)\n */\nexport type PromiseCacheExpiry =\n\t| {\n\t\t\tpolicy: \"indefinite\";\n\t }\n\t| {\n\t\t\tpolicy: \"absolute\" | \"sliding\";\n\t\t\tdurationMs: number;\n\t };\n\n/**\n * Options for configuring the {@link PromiseCache}\n */\nexport interface PromiseCacheOptions {\n\t/** Common expiration policy for all items added to this cache */\n\texpiry?: PromiseCacheExpiry;\n\t/** If the stored Promise is rejected with a particular error, should the given key be removed? */\n\tremoveOnError?: (e: any) => boolean;\n}\n\n/**\n * Handles garbage collection of expiring cache entries.\n * Not exported.\n */\nclass GarbageCollector<TKey> {\n\tprivate readonly gcTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();\n\n\tconstructor(\n\t\tprivate readonly expiry: PromiseCacheExpiry,\n\t\tprivate readonly cleanup: (key: TKey) => void,\n\t) {}\n\n\t/**\n\t * Schedule GC for the given key, as applicable\n\t */\n\tpublic schedule(key: TKey): void {\n\t\tif (this.expiry.policy !== \"indefinite\") {\n\t\t\tthis.gcTimeouts.set(\n\t\t\t\tkey,\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.cleanup(key);\n\t\t\t\t\tthis.cancel(key);\n\t\t\t\t}, this.expiry.durationMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel any pending GC for the given key\n\t */\n\tpublic cancel(key: TKey): void {\n\t\tconst timeout = this.gcTimeouts.get(key);\n\t\tif (timeout !== undefined) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.gcTimeouts.delete(key);\n\t\t}\n\t}\n\n\t/**\n\t * Update any pending GC for the given key, as applicable\n\t */\n\tpublic update(key: TKey): void {\n\t\t// Cancel/reschedule new GC if the policy is sliding\n\t\tif (this.expiry.policy === \"sliding\") {\n\t\t\tthis.cancel(key);\n\t\t\tthis.schedule(key);\n\t\t}\n\t}\n}\n\n/**\n * A specialized cache for async work, allowing you to safely cache the promised result of some async work\n * without fear of running it multiple times or losing track of errors.\n */\nexport class PromiseCache<TKey, TResult> {\n\tprivate readonly cache = new Map<TKey, Promise<TResult>>();\n\tprivate readonly gc: GarbageCollector<TKey>;\n\n\tprivate readonly removeOnError: (error: any) => boolean;\n\n\t/**\n\t * Create the PromiseCache with the given options, with the following defaults:\n\t *\n\t * expiry: indefinite, removeOnError: true for all errors\n\t */\n\tconstructor({\n\t\texpiry = { policy: \"indefinite\" },\n\t\tremoveOnError = (): boolean => true,\n\t}: PromiseCacheOptions = {}) {\n\t\tthis.removeOnError = removeOnError;\n\t\tthis.gc = new GarbageCollector<TKey>(expiry, (key) => this.remove(key));\n\t}\n\n\t/**\n\t * Check if there's anything cached at the given key\n\t */\n\tpublic has(key: TKey): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Get the Promise for the given key, or undefined if it's not found.\n\t * Extend expiry if applicable.\n\t */\n\tpublic get(key: TKey): Promise<TResult> | undefined {\n\t\tif (this.has(key)) {\n\t\t\tthis.gc.update(key);\n\t\t}\n\t\treturn this.cache.get(key);\n\t}\n\n\t/**\n\t * Remove the Promise for the given key, returning true if it was found and removed\n\t */\n\tpublic remove(key: TKey): boolean {\n\t\tthis.gc.cancel(key);\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic async addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult> {\n\t\t// NOTE: Do not await the Promise returned by asyncFn!\n\t\t// Let the caller do so once we return or after a subsequent call to get\n\t\tlet promise = this.get(key);\n\t\tif (promise === undefined) {\n\t\t\t// Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async\n\t\t\tconst safeAsyncFn = async (): Promise<TResult> => asyncFn();\n\n\t\t\t// Start the async work and put the Promise in the cache\n\t\t\tpromise = safeAsyncFn();\n\t\t\tthis.cache.set(key, promise);\n\n\t\t\t// If asyncFn throws, we may remove the Promise from the cache\n\t\t\tpromise.catch((error) => {\n\t\t\t\tif (this.removeOnError(error)) {\n\t\t\t\t\tthis.remove(key);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.gc.schedule(key);\n\t\t}\n\n\t\treturn promise;\n\t}\n\n\t/**\n\t * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the async work\n\t * @param asyncFn - the async work to do and store, if not already in progress under the given key\n\t */\n\tpublic add(key: TKey, asyncFn: () => Promise<TResult>): boolean {\n\t\tconst alreadyPresent = this.has(key);\n\n\t\t// We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.\n\t\t// Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.\n\t\tthis.addOrGet(key, asyncFn).catch(() => {});\n\n\t\treturn !alreadyPresent;\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns a Promise for the added or existing async work being done at that key.\n\t * @param key - key name where to store the async work\n\t * @param value - value to store\n\t */\n\tpublic async addValueOrGet(key: TKey, value: TResult): Promise<TResult> {\n\t\treturn this.addOrGet(key, async () => value);\n\t}\n\n\t/**\n\t * Try to add the given value, without overwriting an existing cache entry at that key.\n\t * Returns false if the cache already contained an entry at that key, and true otherwise.\n\t * @param key - key name where to store the value\n\t * @param value - value to store\n\t */\n\tpublic addValue(key: TKey, value: TResult): boolean {\n\t\treturn this.add(key, async () => value);\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/core-utils",
3
- "version": "2.0.0-internal.5.2.0",
3
+ "version": "2.0.0-internal.5.3.0",
4
4
  "description": "Not intended for use outside the Fluid client repo.",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -36,14 +36,15 @@
36
36
  },
37
37
  "devDependencies": {
38
38
  "@fluid-tools/benchmark": "^0.48.0",
39
- "@fluid-tools/build-cli": "^0.20.0",
39
+ "@fluid-tools/build-cli": "^0.21.0",
40
40
  "@fluidframework/build-common": "^1.2.0",
41
- "@fluidframework/build-tools": "^0.20.0",
41
+ "@fluidframework/build-tools": "^0.21.0",
42
42
  "@fluidframework/eslint-config-fluid": "^2.0.0",
43
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.5.2.0 <2.0.0-internal.5.3.0",
43
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.5.3.0 <2.0.0-internal.5.4.0",
44
44
  "@microsoft/api-extractor": "^7.34.4",
45
45
  "@types/mocha": "^9.1.1",
46
46
  "@types/node": "^14.18.38",
47
+ "@types/sinon": "^7.0.13",
47
48
  "concurrently": "^7.6.0",
48
49
  "copyfiles": "^2.4.1",
49
50
  "cross-env": "^7.0.3",
@@ -56,6 +57,7 @@
56
57
  "nyc": "^15.1.0",
57
58
  "prettier": "~2.6.2",
58
59
  "rimraf": "^4.4.0",
60
+ "sinon": "^7.4.2",
59
61
  "source-map-support": "^0.5.16",
60
62
  "typescript": "~4.5.5"
61
63
  },
@@ -89,6 +91,6 @@
89
91
  "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
90
92
  "tsc": "tsc",
91
93
  "typetests:gen": "fluid-type-test-generator",
92
- "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
94
+ "typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
93
95
  }
94
96
  }
package/src/index.ts CHANGED
@@ -4,3 +4,5 @@
4
4
  */
5
5
 
6
6
  export { compareArrays } from "./compare";
7
+ export { Lazy, LazyPromise } from "./lazy";
8
+ export { PromiseCache, PromiseCacheExpiry, PromiseCacheOptions } from "./promiseCache";
package/src/lazy.ts ADDED
@@ -0,0 +1,83 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Helper class for lazy initialized values. Ensures the value is only generated once, and remain immutable.
8
+ */
9
+ export class Lazy<T> {
10
+ private _value: T | undefined;
11
+ private _evaluated: boolean = false;
12
+ /**
13
+ * Instantiates an instance of Lazy<T>.
14
+ * @param valueGenerator - The function that will generate the value when value is accessed the first time.
15
+ */
16
+ constructor(private readonly valueGenerator: () => T) {}
17
+
18
+ /**
19
+ * Return true if the value as been generated, otherwise false.
20
+ */
21
+ public get evaluated(): boolean {
22
+ return this._evaluated;
23
+ }
24
+
25
+ /**
26
+ * Get the value. If this is the first call the value will be generated.
27
+ */
28
+ public get value(): T {
29
+ if (!this._evaluated) {
30
+ this._evaluated = true;
31
+ this._value = this.valueGenerator();
32
+ }
33
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
34
+ return this._value!;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * A lazy evaluated promise. The execute function is delayed until
40
+ * the promise is used, e.g. await, then, catch ...
41
+ * The execute function is only called once.
42
+ * All calls are then proxied to the promise returned by the execute method.
43
+ */
44
+ export class LazyPromise<T> implements Promise<T> {
45
+ public get [Symbol.toStringTag](): string {
46
+ return this.getPromise()[Symbol.toStringTag];
47
+ }
48
+
49
+ private result: Promise<T> | undefined;
50
+
51
+ constructor(private readonly execute: () => Promise<T>) {}
52
+
53
+ public async then<TResult1 = T, TResult2 = never>(
54
+ // eslint-disable-next-line @rushstack/no-new-null
55
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
56
+ // eslint-disable-next-line @rushstack/no-new-null
57
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined,
58
+ ): Promise<TResult1 | TResult2> {
59
+ // eslint-disable-next-line prefer-rest-params
60
+ return this.getPromise().then<TResult1, TResult2>(...arguments);
61
+ }
62
+
63
+ public async catch<TResult = never>(
64
+ // eslint-disable-next-line @rushstack/no-new-null
65
+ onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined,
66
+ ): Promise<T | TResult> {
67
+ // eslint-disable-next-line prefer-rest-params
68
+ return this.getPromise().catch<TResult>(...arguments);
69
+ }
70
+
71
+ // eslint-disable-next-line @rushstack/no-new-null
72
+ public async finally(onfinally?: (() => void) | null | undefined): Promise<T> {
73
+ // eslint-disable-next-line prefer-rest-params
74
+ return this.getPromise().finally(...arguments);
75
+ }
76
+
77
+ private async getPromise(): Promise<T> {
78
+ if (this.result === undefined) {
79
+ this.result = this.execute();
80
+ }
81
+ return this.result;
82
+ }
83
+ }
@@ -0,0 +1,196 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Three supported expiry policies:
8
+ * - indefinite: entries don't expire and must be explicitly removed
9
+ * - absolute: entries expire after the given duration in MS, even if accessed multiple times in the mean time
10
+ * - sliding: entries expire after the given duration in MS of inactivity (i.e. get resets the clock)
11
+ */
12
+ export type PromiseCacheExpiry =
13
+ | {
14
+ policy: "indefinite";
15
+ }
16
+ | {
17
+ policy: "absolute" | "sliding";
18
+ durationMs: number;
19
+ };
20
+
21
+ /**
22
+ * Options for configuring the {@link PromiseCache}
23
+ */
24
+ export interface PromiseCacheOptions {
25
+ /** Common expiration policy for all items added to this cache */
26
+ expiry?: PromiseCacheExpiry;
27
+ /** If the stored Promise is rejected with a particular error, should the given key be removed? */
28
+ removeOnError?: (e: any) => boolean;
29
+ }
30
+
31
+ /**
32
+ * Handles garbage collection of expiring cache entries.
33
+ * Not exported.
34
+ */
35
+ class GarbageCollector<TKey> {
36
+ private readonly gcTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();
37
+
38
+ constructor(
39
+ private readonly expiry: PromiseCacheExpiry,
40
+ private readonly cleanup: (key: TKey) => void,
41
+ ) {}
42
+
43
+ /**
44
+ * Schedule GC for the given key, as applicable
45
+ */
46
+ public schedule(key: TKey): void {
47
+ if (this.expiry.policy !== "indefinite") {
48
+ this.gcTimeouts.set(
49
+ key,
50
+ setTimeout(() => {
51
+ this.cleanup(key);
52
+ this.cancel(key);
53
+ }, this.expiry.durationMs),
54
+ );
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Cancel any pending GC for the given key
60
+ */
61
+ public cancel(key: TKey): void {
62
+ const timeout = this.gcTimeouts.get(key);
63
+ if (timeout !== undefined) {
64
+ clearTimeout(timeout);
65
+ this.gcTimeouts.delete(key);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Update any pending GC for the given key, as applicable
71
+ */
72
+ public update(key: TKey): void {
73
+ // Cancel/reschedule new GC if the policy is sliding
74
+ if (this.expiry.policy === "sliding") {
75
+ this.cancel(key);
76
+ this.schedule(key);
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * A specialized cache for async work, allowing you to safely cache the promised result of some async work
83
+ * without fear of running it multiple times or losing track of errors.
84
+ */
85
+ export class PromiseCache<TKey, TResult> {
86
+ private readonly cache = new Map<TKey, Promise<TResult>>();
87
+ private readonly gc: GarbageCollector<TKey>;
88
+
89
+ private readonly removeOnError: (error: any) => boolean;
90
+
91
+ /**
92
+ * Create the PromiseCache with the given options, with the following defaults:
93
+ *
94
+ * expiry: indefinite, removeOnError: true for all errors
95
+ */
96
+ constructor({
97
+ expiry = { policy: "indefinite" },
98
+ removeOnError = (): boolean => true,
99
+ }: PromiseCacheOptions = {}) {
100
+ this.removeOnError = removeOnError;
101
+ this.gc = new GarbageCollector<TKey>(expiry, (key) => this.remove(key));
102
+ }
103
+
104
+ /**
105
+ * Check if there's anything cached at the given key
106
+ */
107
+ public has(key: TKey): boolean {
108
+ return this.cache.has(key);
109
+ }
110
+
111
+ /**
112
+ * Get the Promise for the given key, or undefined if it's not found.
113
+ * Extend expiry if applicable.
114
+ */
115
+ public get(key: TKey): Promise<TResult> | undefined {
116
+ if (this.has(key)) {
117
+ this.gc.update(key);
118
+ }
119
+ return this.cache.get(key);
120
+ }
121
+
122
+ /**
123
+ * Remove the Promise for the given key, returning true if it was found and removed
124
+ */
125
+ public remove(key: TKey): boolean {
126
+ this.gc.cancel(key);
127
+ return this.cache.delete(key);
128
+ }
129
+
130
+ /**
131
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
132
+ * Returns a Promise for the added or existing async work being done at that key.
133
+ * @param key - key name where to store the async work
134
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
135
+ */
136
+ public async addOrGet(key: TKey, asyncFn: () => Promise<TResult>): Promise<TResult> {
137
+ // NOTE: Do not await the Promise returned by asyncFn!
138
+ // Let the caller do so once we return or after a subsequent call to get
139
+ let promise = this.get(key);
140
+ if (promise === undefined) {
141
+ // Wrap in an async lambda in case asyncFn disabled @typescript-eslint/promise-function-async
142
+ const safeAsyncFn = async (): Promise<TResult> => asyncFn();
143
+
144
+ // Start the async work and put the Promise in the cache
145
+ promise = safeAsyncFn();
146
+ this.cache.set(key, promise);
147
+
148
+ // If asyncFn throws, we may remove the Promise from the cache
149
+ promise.catch((error) => {
150
+ if (this.removeOnError(error)) {
151
+ this.remove(key);
152
+ }
153
+ });
154
+
155
+ this.gc.schedule(key);
156
+ }
157
+
158
+ return promise;
159
+ }
160
+
161
+ /**
162
+ * Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
163
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
164
+ * @param key - key name where to store the async work
165
+ * @param asyncFn - the async work to do and store, if not already in progress under the given key
166
+ */
167
+ public add(key: TKey, asyncFn: () => Promise<TResult>): boolean {
168
+ const alreadyPresent = this.has(key);
169
+
170
+ // We are blindly adding the Promise to the cache here, which introduces a Promise in this scope.
171
+ // Swallow Promise rejections here, since whoever gets this out of the cache to use it will await/catch.
172
+ this.addOrGet(key, asyncFn).catch(() => {});
173
+
174
+ return !alreadyPresent;
175
+ }
176
+
177
+ /**
178
+ * Try to add the given value, without overwriting an existing cache entry at that key.
179
+ * Returns a Promise for the added or existing async work being done at that key.
180
+ * @param key - key name where to store the async work
181
+ * @param value - value to store
182
+ */
183
+ public async addValueOrGet(key: TKey, value: TResult): Promise<TResult> {
184
+ return this.addOrGet(key, async () => value);
185
+ }
186
+
187
+ /**
188
+ * Try to add the given value, without overwriting an existing cache entry at that key.
189
+ * Returns false if the cache already contained an entry at that key, and true otherwise.
190
+ * @param key - key name where to store the value
191
+ * @param value - value to store
192
+ */
193
+ public addValue(key: TKey, value: TResult): boolean {
194
+ return this.add(key, async () => value);
195
+ }
196
+ }