@zajno/common 2.8.1 → 2.8.2
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/cjs/lazy/lazy.js +8 -0
- package/cjs/lazy/lazy.js.map +1 -1
- package/cjs/lazy/promise.js +118 -89
- package/cjs/lazy/promise.js.map +1 -1
- package/esm/lazy/lazy.js +8 -0
- package/esm/lazy/lazy.js.map +1 -1
- package/esm/lazy/promise.js +118 -89
- package/esm/lazy/promise.js.map +1 -1
- package/package.json +1 -1
- package/types/lazy/lazy.d.ts +8 -0
- package/types/lazy/promise.d.ts +39 -30
- package/types/lazy/types.d.ts +63 -54
package/cjs/lazy/lazy.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Lazy = void 0;
|
|
4
4
|
const disposer_js_1 = require("../functions/disposer.js");
|
|
5
|
+
/**
|
|
6
|
+
* Synchronous lazy-loading container that initializes a value on first access.
|
|
7
|
+
* The value is cached until reset or expired. Supports custom disposal and cache expiration.
|
|
8
|
+
*/
|
|
5
9
|
class Lazy {
|
|
6
10
|
_factory;
|
|
7
11
|
_instance = undefined;
|
|
@@ -31,18 +35,22 @@ class Lazy {
|
|
|
31
35
|
}
|
|
32
36
|
return true;
|
|
33
37
|
}
|
|
38
|
+
/** Provides custom cleanup logic when the instance is reset or disposed. */
|
|
34
39
|
withDisposer(disposer) {
|
|
35
40
|
this._disposer = disposer;
|
|
36
41
|
return this;
|
|
37
42
|
}
|
|
43
|
+
/** Configures automatic cache expiration using an expire tracker. */
|
|
38
44
|
withExpire(tracker) {
|
|
39
45
|
this._expireTracker = tracker;
|
|
40
46
|
return this;
|
|
41
47
|
}
|
|
48
|
+
/** Eagerly loads the value without accessing it. Useful for preloading. */
|
|
42
49
|
prewarm() {
|
|
43
50
|
this.ensureInstance();
|
|
44
51
|
return this;
|
|
45
52
|
}
|
|
53
|
+
/** Manually sets the cached value. */
|
|
46
54
|
setInstance(instance) {
|
|
47
55
|
this._instance = instance;
|
|
48
56
|
if (this._instance !== undefined && this._expireTracker) {
|
package/cjs/lazy/lazy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../src/lazy/lazy.ts"],"names":[],"mappings":";;;AAAA,0DAAwE;AAKxE,MAAa,IAAI;IAOkB;IALrB,SAAS,GAAkB,SAAS,CAAC;IACvC,cAAc,CAA6B;IAC3C,SAAS,CAAqB;IAC9B,MAAM,GAAkB,IAAI,CAAC;IAErC,YAA+B,QAAmB;QAAnB,aAAQ,GAAR,QAAQ,CAAW;IAAI,CAAC;IAEvD,IAAW,QAAQ,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC;IAE9D,IAAW,KAAK;QACZ,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,SAAc,CAAC;IAC/B,CAAC;IAED,IAAW,YAAY,KAAK,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACpD,IAAW,KAAK,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAE1C,iEAAiE;IACjE,IAAc,OAAO;QACjB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC;gBAChC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,OAAO,KAAK,CAAC;YACjB,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;
|
|
1
|
+
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../src/lazy/lazy.ts"],"names":[],"mappings":"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"}
|
package/cjs/lazy/promise.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LazyPromise = void 0;
|
|
4
4
|
const disposer_js_1 = require("../functions/disposer.js");
|
|
5
|
+
/**
|
|
6
|
+
* Asynchronous lazy-loading container that initializes via a promise-based factory.
|
|
7
|
+
* Handles concurrent operations with "latest wins" semantics: multiple refreshes are automatically
|
|
8
|
+
* coordinated so all awaiting promises receive the final value. Supports extensions for custom behavior.
|
|
9
|
+
*/
|
|
5
10
|
class LazyPromise {
|
|
6
11
|
_factory;
|
|
7
12
|
_initial;
|
|
@@ -9,8 +14,10 @@ class LazyPromise {
|
|
|
9
14
|
_isLoading = null;
|
|
10
15
|
_promise;
|
|
11
16
|
_expireTracker;
|
|
12
|
-
|
|
17
|
+
// Track the active factory promise to determine "latest wins"
|
|
18
|
+
_activeFactoryPromise = null;
|
|
13
19
|
_error = null;
|
|
20
|
+
_ownDisposer;
|
|
14
21
|
constructor(factory, initial) {
|
|
15
22
|
this._factory = factory;
|
|
16
23
|
this._initial = initial;
|
|
@@ -27,99 +34,72 @@ class LazyPromise {
|
|
|
27
34
|
this.ensureInstanceLoading();
|
|
28
35
|
return this._instance;
|
|
29
36
|
}
|
|
30
|
-
/**
|
|
37
|
+
/** Returns current value without triggering loading. */
|
|
31
38
|
get currentValue() {
|
|
32
39
|
return this._instance;
|
|
33
40
|
}
|
|
41
|
+
/** Configures automatic cache expiration using an expire tracker. */
|
|
34
42
|
withExpire(tracker) {
|
|
35
43
|
this._expireTracker = tracker;
|
|
36
44
|
return this;
|
|
37
45
|
}
|
|
38
46
|
/**
|
|
39
|
-
* Extends this instance with additional functionality
|
|
47
|
+
* Extends this instance with additional functionality via in-place mutation.
|
|
40
48
|
*
|
|
41
49
|
* **Capabilities:**
|
|
42
|
-
* - `overrideFactory`: Wrap the factory
|
|
43
|
-
* - `extendShape`: Add custom properties/methods
|
|
50
|
+
* - `overrideFactory`: Wrap the factory (logging, retry, caching, etc.)
|
|
51
|
+
* - `extendShape`: Add custom properties/methods
|
|
52
|
+
* - `dispose`: Cleanup resources when disposed
|
|
44
53
|
*
|
|
45
54
|
* **Type Safety:**
|
|
46
55
|
* - Use `ILazyPromiseExtension<any>` for universal extensions
|
|
47
|
-
* - Use `ILazyPromiseExtension<ConcreteType>` for type-specific extensions
|
|
56
|
+
* - Use `ILazyPromiseExtension<ConcreteType>` for type-specific extensions
|
|
48
57
|
*
|
|
49
|
-
* **Note:** Extensions mutate the instance and can be chained.
|
|
58
|
+
* **Note:** Extensions mutate the instance and can be chained.
|
|
50
59
|
*
|
|
51
|
-
* @param extension -
|
|
52
|
-
* @returns The same instance
|
|
60
|
+
* @param extension - Extension configuration
|
|
61
|
+
* @returns The same instance with applied extensions
|
|
53
62
|
*
|
|
54
63
|
* @example
|
|
55
64
|
* ```typescript
|
|
56
|
-
* // Universal logging extension
|
|
57
65
|
* const logged = lazy.extend({
|
|
58
66
|
* overrideFactory: (factory) => async (refreshing) => {
|
|
59
67
|
* console.log('Loading...');
|
|
60
68
|
* return await factory(refreshing);
|
|
61
69
|
* }
|
|
62
70
|
* });
|
|
63
|
-
*
|
|
64
|
-
* // Type-specific extension with custom methods
|
|
65
|
-
* const enhanced = lazyNumber.extend<{ double: () => number | undefined }>({
|
|
66
|
-
* extendShape: (instance) => Object.assign(instance, {
|
|
67
|
-
* double: () => instance.currentValue !== undefined
|
|
68
|
-
* ? instance.currentValue * 2
|
|
69
|
-
* : undefined
|
|
70
|
-
* })
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
|
-
* // Chaining multiple extensions
|
|
74
|
-
* const composed = lazy
|
|
75
|
-
* .extend(cacheExtension)
|
|
76
|
-
* .extend(loggingExtension);
|
|
77
71
|
* ```
|
|
78
72
|
*/
|
|
79
73
|
extend(
|
|
80
74
|
// Partial allows extensions with extra properties beyond the interface
|
|
81
75
|
// 'any' type parameter doesn't affect return type since we return 'this'
|
|
82
76
|
extension) {
|
|
83
|
-
|
|
84
|
-
if (extension.overrideFactory) {
|
|
85
|
-
this._factory = extension.overrideFactory(this._factory, this);
|
|
86
|
-
}
|
|
77
|
+
let extended = this;
|
|
87
78
|
// Apply shape extension if provided
|
|
88
79
|
if (extension.extendShape) {
|
|
89
|
-
|
|
80
|
+
extended = extension.extendShape(this);
|
|
90
81
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (this.isLoading === false && this._instance !== undefined && this._expireTracker?.isExpired) {
|
|
95
|
-
// do not reset the instance, just make sure it will be reloaded
|
|
96
|
-
this._isLoading = null;
|
|
82
|
+
// Override the factory if provided
|
|
83
|
+
if (extension.overrideFactory) {
|
|
84
|
+
this._factory = extension.overrideFactory(this._factory, extended);
|
|
97
85
|
}
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
if (extension.dispose) {
|
|
87
|
+
const previousDisposer = this._ownDisposer;
|
|
88
|
+
const nextDisposer = extension.dispose;
|
|
89
|
+
this._ownDisposer = () => {
|
|
90
|
+
nextDisposer(extended);
|
|
91
|
+
previousDisposer?.();
|
|
92
|
+
};
|
|
101
93
|
}
|
|
94
|
+
return extended;
|
|
102
95
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (!this._isLoading && this._instance !== undefined) {
|
|
111
|
-
return this._instance;
|
|
112
|
-
}
|
|
113
|
-
this.setInstance(res);
|
|
114
|
-
return res;
|
|
115
|
-
}
|
|
116
|
-
onRejected(e) {
|
|
117
|
-
this._isLoading = false;
|
|
118
|
-
this._instance = this._initial;
|
|
119
|
-
this._promise = Promise.resolve(this._initial);
|
|
120
|
-
this.setError(e);
|
|
121
|
-
return this._initial;
|
|
122
|
-
}
|
|
96
|
+
/**
|
|
97
|
+
* Manually sets the value and marks loading as complete.
|
|
98
|
+
* Clears any errors and restarts the expiration tracker if configured.
|
|
99
|
+
*
|
|
100
|
+
* @param res - The value to set
|
|
101
|
+
* @returns The value that was set
|
|
102
|
+
*/
|
|
123
103
|
setInstance(res) {
|
|
124
104
|
this._isLoading = false;
|
|
125
105
|
this.clearError(); // clear error on successful set
|
|
@@ -127,41 +107,24 @@ class LazyPromise {
|
|
|
127
107
|
// + make sure it's resolved with the freshest value
|
|
128
108
|
// also do this before setting the instance... just in case :)
|
|
129
109
|
this._promise = Promise.resolve(res);
|
|
110
|
+
this._activeFactoryPromise = null;
|
|
130
111
|
this._instance = res;
|
|
131
|
-
|
|
132
|
-
this._expireTracker.restart();
|
|
133
|
-
}
|
|
112
|
+
this._expireTracker?.restart();
|
|
134
113
|
return res;
|
|
135
114
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Re-executes the factory to get fresh data.
|
|
117
|
+
*
|
|
118
|
+
* **Concurrency handling:**
|
|
119
|
+
* - Supersedes any in-progress load or refresh
|
|
120
|
+
* - Multiple concurrent refreshes: latest wins
|
|
121
|
+
* - All awaiting promises receive the final refreshed value
|
|
122
|
+
*
|
|
123
|
+
* @returns Promise resolving to the refreshed value
|
|
124
|
+
*/
|
|
142
125
|
async refresh() {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
myPromise = this._factory(true);
|
|
146
|
-
// every new refresh overrides the previous one
|
|
147
|
-
// so this one becomes "last"
|
|
148
|
-
// and previous becomes stale and won't update the value when it resolves
|
|
149
|
-
this._lastRefreshingPromise = myPromise;
|
|
150
|
-
const fresh = await myPromise;
|
|
151
|
-
if (this._lastRefreshingPromise === myPromise) {
|
|
152
|
-
this.setInstance(fresh);
|
|
153
|
-
}
|
|
154
|
-
return fresh;
|
|
155
|
-
}
|
|
156
|
-
catch (e) {
|
|
157
|
-
this.setError(e);
|
|
158
|
-
return this._instance;
|
|
159
|
-
}
|
|
160
|
-
finally {
|
|
161
|
-
if (myPromise != null && this._lastRefreshingPromise === myPromise) {
|
|
162
|
-
this._lastRefreshingPromise = null;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
126
|
+
this.startLoading(true);
|
|
127
|
+
return this._promise;
|
|
165
128
|
}
|
|
166
129
|
reset() {
|
|
167
130
|
this._isLoading = null;
|
|
@@ -170,6 +133,7 @@ class LazyPromise {
|
|
|
170
133
|
this._instance = this._initial;
|
|
171
134
|
const p = this._promise;
|
|
172
135
|
this._promise = undefined;
|
|
136
|
+
this._activeFactoryPromise = null; // Clear active promise reference
|
|
173
137
|
// check if loading is still in progress
|
|
174
138
|
// need to dispose abandoned value
|
|
175
139
|
if (p && !wasDisposed) {
|
|
@@ -179,8 +143,73 @@ class LazyPromise {
|
|
|
179
143
|
}
|
|
180
144
|
}
|
|
181
145
|
dispose() {
|
|
146
|
+
this._ownDisposer?.();
|
|
182
147
|
this.reset();
|
|
183
148
|
}
|
|
149
|
+
ensureInstanceLoading() {
|
|
150
|
+
if (this.isLoading === false && this._instance !== undefined && this._expireTracker?.isExpired) {
|
|
151
|
+
// do not reset the instance, just make sure it will be reloaded
|
|
152
|
+
this._isLoading = null;
|
|
153
|
+
}
|
|
154
|
+
if (this._isLoading === null) {
|
|
155
|
+
this._isLoading = true;
|
|
156
|
+
this.startLoading(false);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
startLoading(refreshing) {
|
|
160
|
+
if (!refreshing && this._activeFactoryPromise) {
|
|
161
|
+
// Case when refreshing already is happening - we have an active promise
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const factoryPromise = this._factory(refreshing)
|
|
165
|
+
.then(res => {
|
|
166
|
+
if (!this._activeFactoryPromise) {
|
|
167
|
+
// this promise was abandoned: was superseded or reset called
|
|
168
|
+
return this._instance ?? this._initial;
|
|
169
|
+
}
|
|
170
|
+
if (this._activeFactoryPromise === factoryPromise) {
|
|
171
|
+
// case: during the promise `setInstance` was called manually
|
|
172
|
+
if (!refreshing && !this._isLoading && this._instance !== undefined) {
|
|
173
|
+
return this._instance;
|
|
174
|
+
}
|
|
175
|
+
this.setInstance(res);
|
|
176
|
+
return res;
|
|
177
|
+
}
|
|
178
|
+
// Stale promise - return the latest active promise instead
|
|
179
|
+
// This ensures anyone awaiting this old promise gets the fresh value
|
|
180
|
+
return this._activeFactoryPromise;
|
|
181
|
+
})
|
|
182
|
+
.catch(err => {
|
|
183
|
+
if (!this._activeFactoryPromise || this._activeFactoryPromise === factoryPromise) {
|
|
184
|
+
return this.onRejected(err);
|
|
185
|
+
}
|
|
186
|
+
throw err;
|
|
187
|
+
});
|
|
188
|
+
const hadActive = !!this._activeFactoryPromise;
|
|
189
|
+
// This is now the active promise - any previous one is superseded
|
|
190
|
+
this._activeFactoryPromise = factoryPromise;
|
|
191
|
+
// don't overwrite an existing promise (e.g., from refresh)
|
|
192
|
+
// it should pick up the new active promise automatically
|
|
193
|
+
if (!this._promise || !hadActive) {
|
|
194
|
+
this._promise = factoryPromise;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
onRejected(e) {
|
|
198
|
+
this._isLoading = false;
|
|
199
|
+
// Keep the current instance on error (don't reset to initial)
|
|
200
|
+
// This allows retaining the last successful value
|
|
201
|
+
const currentInstance = this._instance !== undefined ? this._instance : this._initial;
|
|
202
|
+
this._promise = Promise.resolve(currentInstance);
|
|
203
|
+
this._activeFactoryPromise = null;
|
|
204
|
+
this.setError(e);
|
|
205
|
+
return currentInstance;
|
|
206
|
+
}
|
|
207
|
+
setError(err) {
|
|
208
|
+
this._error = this.parseError(err);
|
|
209
|
+
}
|
|
210
|
+
clearError() {
|
|
211
|
+
this._error = null;
|
|
212
|
+
}
|
|
184
213
|
parseError(err) {
|
|
185
214
|
if (typeof err === 'string') {
|
|
186
215
|
return err;
|
package/cjs/lazy/promise.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise.js","sourceRoot":"","sources":["../../../src/lazy/promise.ts"],"names":[],"mappings":";;;AAAA,0DAAwE;AAKxE,MAAa,WAAW;IAEZ,QAAQ,CAAiB;IAChB,QAAQ,CAAW;IAE5B,SAAS,CAAe;IACxB,UAAU,GAAmB,IAAI,CAAC;IAElC,QAAQ,CAAyB;IACjC,cAAc,CAA6B;
|
|
1
|
+
{"version":3,"file":"promise.js","sourceRoot":"","sources":["../../../src/lazy/promise.ts"],"names":[],"mappings":"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"}
|
package/esm/lazy/lazy.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { tryDispose } from '../functions/disposer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Synchronous lazy-loading container that initializes a value on first access.
|
|
4
|
+
* The value is cached until reset or expired. Supports custom disposal and cache expiration.
|
|
5
|
+
*/
|
|
2
6
|
export class Lazy {
|
|
3
7
|
_factory;
|
|
4
8
|
_instance = undefined;
|
|
@@ -28,18 +32,22 @@ export class Lazy {
|
|
|
28
32
|
}
|
|
29
33
|
return true;
|
|
30
34
|
}
|
|
35
|
+
/** Provides custom cleanup logic when the instance is reset or disposed. */
|
|
31
36
|
withDisposer(disposer) {
|
|
32
37
|
this._disposer = disposer;
|
|
33
38
|
return this;
|
|
34
39
|
}
|
|
40
|
+
/** Configures automatic cache expiration using an expire tracker. */
|
|
35
41
|
withExpire(tracker) {
|
|
36
42
|
this._expireTracker = tracker;
|
|
37
43
|
return this;
|
|
38
44
|
}
|
|
45
|
+
/** Eagerly loads the value without accessing it. Useful for preloading. */
|
|
39
46
|
prewarm() {
|
|
40
47
|
this.ensureInstance();
|
|
41
48
|
return this;
|
|
42
49
|
}
|
|
50
|
+
/** Manually sets the cached value. */
|
|
43
51
|
setInstance(instance) {
|
|
44
52
|
this._instance = instance;
|
|
45
53
|
if (this._instance !== undefined && this._expireTracker) {
|
package/esm/lazy/lazy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../src/lazy/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAoB,MAAM,0BAA0B,CAAC;AAKxE,MAAM,OAAO,IAAI;IAOkB;IALrB,SAAS,GAAkB,SAAS,CAAC;IACvC,cAAc,CAA6B;IAC3C,SAAS,CAAqB;IAC9B,MAAM,GAAkB,IAAI,CAAC;IAErC,YAA+B,QAAmB;QAAnB,aAAQ,GAAR,QAAQ,CAAW;IAAI,CAAC;IAEvD,IAAW,QAAQ,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC;IAE9D,IAAW,KAAK;QACZ,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,SAAc,CAAC;IAC/B,CAAC;IAED,IAAW,YAAY,KAAK,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACpD,IAAW,KAAK,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAE1C,iEAAiE;IACjE,IAAc,OAAO;QACjB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC;gBAChC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,OAAO,KAAK,CAAC;YACjB,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;
|
|
1
|
+
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../src/lazy/lazy.ts"],"names":[],"mappings":"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"}
|
package/esm/lazy/promise.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { tryDispose } from '../functions/disposer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Asynchronous lazy-loading container that initializes via a promise-based factory.
|
|
4
|
+
* Handles concurrent operations with "latest wins" semantics: multiple refreshes are automatically
|
|
5
|
+
* coordinated so all awaiting promises receive the final value. Supports extensions for custom behavior.
|
|
6
|
+
*/
|
|
2
7
|
export class LazyPromise {
|
|
3
8
|
_factory;
|
|
4
9
|
_initial;
|
|
@@ -6,8 +11,10 @@ export class LazyPromise {
|
|
|
6
11
|
_isLoading = null;
|
|
7
12
|
_promise;
|
|
8
13
|
_expireTracker;
|
|
9
|
-
|
|
14
|
+
// Track the active factory promise to determine "latest wins"
|
|
15
|
+
_activeFactoryPromise = null;
|
|
10
16
|
_error = null;
|
|
17
|
+
_ownDisposer;
|
|
11
18
|
constructor(factory, initial) {
|
|
12
19
|
this._factory = factory;
|
|
13
20
|
this._initial = initial;
|
|
@@ -24,99 +31,72 @@ export class LazyPromise {
|
|
|
24
31
|
this.ensureInstanceLoading();
|
|
25
32
|
return this._instance;
|
|
26
33
|
}
|
|
27
|
-
/**
|
|
34
|
+
/** Returns current value without triggering loading. */
|
|
28
35
|
get currentValue() {
|
|
29
36
|
return this._instance;
|
|
30
37
|
}
|
|
38
|
+
/** Configures automatic cache expiration using an expire tracker. */
|
|
31
39
|
withExpire(tracker) {
|
|
32
40
|
this._expireTracker = tracker;
|
|
33
41
|
return this;
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
|
-
* Extends this instance with additional functionality
|
|
44
|
+
* Extends this instance with additional functionality via in-place mutation.
|
|
37
45
|
*
|
|
38
46
|
* **Capabilities:**
|
|
39
|
-
* - `overrideFactory`: Wrap the factory
|
|
40
|
-
* - `extendShape`: Add custom properties/methods
|
|
47
|
+
* - `overrideFactory`: Wrap the factory (logging, retry, caching, etc.)
|
|
48
|
+
* - `extendShape`: Add custom properties/methods
|
|
49
|
+
* - `dispose`: Cleanup resources when disposed
|
|
41
50
|
*
|
|
42
51
|
* **Type Safety:**
|
|
43
52
|
* - Use `ILazyPromiseExtension<any>` for universal extensions
|
|
44
|
-
* - Use `ILazyPromiseExtension<ConcreteType>` for type-specific extensions
|
|
53
|
+
* - Use `ILazyPromiseExtension<ConcreteType>` for type-specific extensions
|
|
45
54
|
*
|
|
46
|
-
* **Note:** Extensions mutate the instance and can be chained.
|
|
55
|
+
* **Note:** Extensions mutate the instance and can be chained.
|
|
47
56
|
*
|
|
48
|
-
* @param extension -
|
|
49
|
-
* @returns The same instance
|
|
57
|
+
* @param extension - Extension configuration
|
|
58
|
+
* @returns The same instance with applied extensions
|
|
50
59
|
*
|
|
51
60
|
* @example
|
|
52
61
|
* ```typescript
|
|
53
|
-
* // Universal logging extension
|
|
54
62
|
* const logged = lazy.extend({
|
|
55
63
|
* overrideFactory: (factory) => async (refreshing) => {
|
|
56
64
|
* console.log('Loading...');
|
|
57
65
|
* return await factory(refreshing);
|
|
58
66
|
* }
|
|
59
67
|
* });
|
|
60
|
-
*
|
|
61
|
-
* // Type-specific extension with custom methods
|
|
62
|
-
* const enhanced = lazyNumber.extend<{ double: () => number | undefined }>({
|
|
63
|
-
* extendShape: (instance) => Object.assign(instance, {
|
|
64
|
-
* double: () => instance.currentValue !== undefined
|
|
65
|
-
* ? instance.currentValue * 2
|
|
66
|
-
* : undefined
|
|
67
|
-
* })
|
|
68
|
-
* });
|
|
69
|
-
*
|
|
70
|
-
* // Chaining multiple extensions
|
|
71
|
-
* const composed = lazy
|
|
72
|
-
* .extend(cacheExtension)
|
|
73
|
-
* .extend(loggingExtension);
|
|
74
68
|
* ```
|
|
75
69
|
*/
|
|
76
70
|
extend(
|
|
77
71
|
// Partial allows extensions with extra properties beyond the interface
|
|
78
72
|
// 'any' type parameter doesn't affect return type since we return 'this'
|
|
79
73
|
extension) {
|
|
80
|
-
|
|
81
|
-
if (extension.overrideFactory) {
|
|
82
|
-
this._factory = extension.overrideFactory(this._factory, this);
|
|
83
|
-
}
|
|
74
|
+
let extended = this;
|
|
84
75
|
// Apply shape extension if provided
|
|
85
76
|
if (extension.extendShape) {
|
|
86
|
-
|
|
77
|
+
extended = extension.extendShape(this);
|
|
87
78
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (this.isLoading === false && this._instance !== undefined && this._expireTracker?.isExpired) {
|
|
92
|
-
// do not reset the instance, just make sure it will be reloaded
|
|
93
|
-
this._isLoading = null;
|
|
79
|
+
// Override the factory if provided
|
|
80
|
+
if (extension.overrideFactory) {
|
|
81
|
+
this._factory = extension.overrideFactory(this._factory, extended);
|
|
94
82
|
}
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
if (extension.dispose) {
|
|
84
|
+
const previousDisposer = this._ownDisposer;
|
|
85
|
+
const nextDisposer = extension.dispose;
|
|
86
|
+
this._ownDisposer = () => {
|
|
87
|
+
nextDisposer(extended);
|
|
88
|
+
previousDisposer?.();
|
|
89
|
+
};
|
|
98
90
|
}
|
|
91
|
+
return extended;
|
|
99
92
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!this._isLoading && this._instance !== undefined) {
|
|
108
|
-
return this._instance;
|
|
109
|
-
}
|
|
110
|
-
this.setInstance(res);
|
|
111
|
-
return res;
|
|
112
|
-
}
|
|
113
|
-
onRejected(e) {
|
|
114
|
-
this._isLoading = false;
|
|
115
|
-
this._instance = this._initial;
|
|
116
|
-
this._promise = Promise.resolve(this._initial);
|
|
117
|
-
this.setError(e);
|
|
118
|
-
return this._initial;
|
|
119
|
-
}
|
|
93
|
+
/**
|
|
94
|
+
* Manually sets the value and marks loading as complete.
|
|
95
|
+
* Clears any errors and restarts the expiration tracker if configured.
|
|
96
|
+
*
|
|
97
|
+
* @param res - The value to set
|
|
98
|
+
* @returns The value that was set
|
|
99
|
+
*/
|
|
120
100
|
setInstance(res) {
|
|
121
101
|
this._isLoading = false;
|
|
122
102
|
this.clearError(); // clear error on successful set
|
|
@@ -124,41 +104,24 @@ export class LazyPromise {
|
|
|
124
104
|
// + make sure it's resolved with the freshest value
|
|
125
105
|
// also do this before setting the instance... just in case :)
|
|
126
106
|
this._promise = Promise.resolve(res);
|
|
107
|
+
this._activeFactoryPromise = null;
|
|
127
108
|
this._instance = res;
|
|
128
|
-
|
|
129
|
-
this._expireTracker.restart();
|
|
130
|
-
}
|
|
109
|
+
this._expireTracker?.restart();
|
|
131
110
|
return res;
|
|
132
111
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Re-executes the factory to get fresh data.
|
|
114
|
+
*
|
|
115
|
+
* **Concurrency handling:**
|
|
116
|
+
* - Supersedes any in-progress load or refresh
|
|
117
|
+
* - Multiple concurrent refreshes: latest wins
|
|
118
|
+
* - All awaiting promises receive the final refreshed value
|
|
119
|
+
*
|
|
120
|
+
* @returns Promise resolving to the refreshed value
|
|
121
|
+
*/
|
|
139
122
|
async refresh() {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
myPromise = this._factory(true);
|
|
143
|
-
// every new refresh overrides the previous one
|
|
144
|
-
// so this one becomes "last"
|
|
145
|
-
// and previous becomes stale and won't update the value when it resolves
|
|
146
|
-
this._lastRefreshingPromise = myPromise;
|
|
147
|
-
const fresh = await myPromise;
|
|
148
|
-
if (this._lastRefreshingPromise === myPromise) {
|
|
149
|
-
this.setInstance(fresh);
|
|
150
|
-
}
|
|
151
|
-
return fresh;
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
this.setError(e);
|
|
155
|
-
return this._instance;
|
|
156
|
-
}
|
|
157
|
-
finally {
|
|
158
|
-
if (myPromise != null && this._lastRefreshingPromise === myPromise) {
|
|
159
|
-
this._lastRefreshingPromise = null;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
123
|
+
this.startLoading(true);
|
|
124
|
+
return this._promise;
|
|
162
125
|
}
|
|
163
126
|
reset() {
|
|
164
127
|
this._isLoading = null;
|
|
@@ -167,6 +130,7 @@ export class LazyPromise {
|
|
|
167
130
|
this._instance = this._initial;
|
|
168
131
|
const p = this._promise;
|
|
169
132
|
this._promise = undefined;
|
|
133
|
+
this._activeFactoryPromise = null; // Clear active promise reference
|
|
170
134
|
// check if loading is still in progress
|
|
171
135
|
// need to dispose abandoned value
|
|
172
136
|
if (p && !wasDisposed) {
|
|
@@ -176,8 +140,73 @@ export class LazyPromise {
|
|
|
176
140
|
}
|
|
177
141
|
}
|
|
178
142
|
dispose() {
|
|
143
|
+
this._ownDisposer?.();
|
|
179
144
|
this.reset();
|
|
180
145
|
}
|
|
146
|
+
ensureInstanceLoading() {
|
|
147
|
+
if (this.isLoading === false && this._instance !== undefined && this._expireTracker?.isExpired) {
|
|
148
|
+
// do not reset the instance, just make sure it will be reloaded
|
|
149
|
+
this._isLoading = null;
|
|
150
|
+
}
|
|
151
|
+
if (this._isLoading === null) {
|
|
152
|
+
this._isLoading = true;
|
|
153
|
+
this.startLoading(false);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
startLoading(refreshing) {
|
|
157
|
+
if (!refreshing && this._activeFactoryPromise) {
|
|
158
|
+
// Case when refreshing already is happening - we have an active promise
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const factoryPromise = this._factory(refreshing)
|
|
162
|
+
.then(res => {
|
|
163
|
+
if (!this._activeFactoryPromise) {
|
|
164
|
+
// this promise was abandoned: was superseded or reset called
|
|
165
|
+
return this._instance ?? this._initial;
|
|
166
|
+
}
|
|
167
|
+
if (this._activeFactoryPromise === factoryPromise) {
|
|
168
|
+
// case: during the promise `setInstance` was called manually
|
|
169
|
+
if (!refreshing && !this._isLoading && this._instance !== undefined) {
|
|
170
|
+
return this._instance;
|
|
171
|
+
}
|
|
172
|
+
this.setInstance(res);
|
|
173
|
+
return res;
|
|
174
|
+
}
|
|
175
|
+
// Stale promise - return the latest active promise instead
|
|
176
|
+
// This ensures anyone awaiting this old promise gets the fresh value
|
|
177
|
+
return this._activeFactoryPromise;
|
|
178
|
+
})
|
|
179
|
+
.catch(err => {
|
|
180
|
+
if (!this._activeFactoryPromise || this._activeFactoryPromise === factoryPromise) {
|
|
181
|
+
return this.onRejected(err);
|
|
182
|
+
}
|
|
183
|
+
throw err;
|
|
184
|
+
});
|
|
185
|
+
const hadActive = !!this._activeFactoryPromise;
|
|
186
|
+
// This is now the active promise - any previous one is superseded
|
|
187
|
+
this._activeFactoryPromise = factoryPromise;
|
|
188
|
+
// don't overwrite an existing promise (e.g., from refresh)
|
|
189
|
+
// it should pick up the new active promise automatically
|
|
190
|
+
if (!this._promise || !hadActive) {
|
|
191
|
+
this._promise = factoryPromise;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
onRejected(e) {
|
|
195
|
+
this._isLoading = false;
|
|
196
|
+
// Keep the current instance on error (don't reset to initial)
|
|
197
|
+
// This allows retaining the last successful value
|
|
198
|
+
const currentInstance = this._instance !== undefined ? this._instance : this._initial;
|
|
199
|
+
this._promise = Promise.resolve(currentInstance);
|
|
200
|
+
this._activeFactoryPromise = null;
|
|
201
|
+
this.setError(e);
|
|
202
|
+
return currentInstance;
|
|
203
|
+
}
|
|
204
|
+
setError(err) {
|
|
205
|
+
this._error = this.parseError(err);
|
|
206
|
+
}
|
|
207
|
+
clearError() {
|
|
208
|
+
this._error = null;
|
|
209
|
+
}
|
|
181
210
|
parseError(err) {
|
|
182
211
|
if (typeof err === 'string') {
|
|
183
212
|
return err;
|
package/esm/lazy/promise.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise.js","sourceRoot":"","sources":["../../../src/lazy/promise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAoB,MAAM,0BAA0B,CAAC;AAKxE,MAAM,OAAO,WAAW;IAEZ,QAAQ,CAAiB;IAChB,QAAQ,CAAW;IAE5B,SAAS,CAAe;IACxB,UAAU,GAAmB,IAAI,CAAC;IAElC,QAAQ,CAAyB;IACjC,cAAc,CAA6B;
|
|
1
|
+
{"version":3,"file":"promise.js","sourceRoot":"","sources":["../../../src/lazy/promise.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/types/lazy/lazy.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { type IDisposable } from '../functions/disposer.js';
|
|
|
2
2
|
import type { IResettableModel } from '../models/types.js';
|
|
3
3
|
import type { IExpireTracker } from '../structures/expire.js';
|
|
4
4
|
import type { ILazy } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Synchronous lazy-loading container that initializes a value on first access.
|
|
7
|
+
* The value is cached until reset or expired. Supports custom disposal and cache expiration.
|
|
8
|
+
*/
|
|
5
9
|
export declare class Lazy<T> implements ILazy<T>, IDisposable, IResettableModel {
|
|
6
10
|
protected readonly _factory: (() => T);
|
|
7
11
|
protected _instance: T | undefined;
|
|
@@ -15,9 +19,13 @@ export declare class Lazy<T> implements ILazy<T>, IDisposable, IResettableModel
|
|
|
15
19
|
get error(): string | null;
|
|
16
20
|
/** Override me: additional way to make sure instance is valid */
|
|
17
21
|
protected get isValid(): boolean;
|
|
22
|
+
/** Provides custom cleanup logic when the instance is reset or disposed. */
|
|
18
23
|
withDisposer(disposer: (prev: T) => void): this;
|
|
24
|
+
/** Configures automatic cache expiration using an expire tracker. */
|
|
19
25
|
withExpire(tracker: IExpireTracker | undefined): this;
|
|
26
|
+
/** Eagerly loads the value without accessing it. Useful for preloading. */
|
|
20
27
|
prewarm(): this;
|
|
28
|
+
/** Manually sets the cached value. */
|
|
21
29
|
setInstance(instance: T | undefined): void;
|
|
22
30
|
reset(): void;
|
|
23
31
|
dispose(): void;
|
package/types/lazy/promise.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { type IDisposable } from '../functions/disposer.js';
|
|
|
2
2
|
import type { IResettableModel } from '../models/types.js';
|
|
3
3
|
import type { IExpireTracker } from '../structures/expire.js';
|
|
4
4
|
import type { IControllableLazyPromise, ILazyPromiseExtension, LazyFactory } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Asynchronous lazy-loading container that initializes via a promise-based factory.
|
|
7
|
+
* Handles concurrent operations with "latest wins" semantics: multiple refreshes are automatically
|
|
8
|
+
* coordinated so all awaiting promises receive the final value. Supports extensions for custom behavior.
|
|
9
|
+
*/
|
|
5
10
|
export declare class LazyPromise<T, TInitial extends T | undefined = undefined> implements IControllableLazyPromise<T, TInitial>, IDisposable, IResettableModel {
|
|
6
11
|
private _factory;
|
|
7
12
|
private readonly _initial;
|
|
@@ -9,68 +14,72 @@ export declare class LazyPromise<T, TInitial extends T | undefined = undefined>
|
|
|
9
14
|
private _isLoading;
|
|
10
15
|
private _promise;
|
|
11
16
|
private _expireTracker;
|
|
12
|
-
private
|
|
17
|
+
private _activeFactoryPromise;
|
|
13
18
|
private _error;
|
|
19
|
+
private _ownDisposer?;
|
|
14
20
|
constructor(factory: LazyFactory<T>, initial?: TInitial);
|
|
15
21
|
get isLoading(): boolean | null;
|
|
16
22
|
get hasValue(): boolean;
|
|
17
23
|
get error(): string | null;
|
|
18
24
|
get promise(): Promise<T>;
|
|
19
25
|
get value(): T | TInitial;
|
|
20
|
-
/**
|
|
26
|
+
/** Returns current value without triggering loading. */
|
|
21
27
|
get currentValue(): T | TInitial;
|
|
28
|
+
/** Configures automatic cache expiration using an expire tracker. */
|
|
22
29
|
withExpire(tracker: IExpireTracker | undefined): this;
|
|
23
30
|
/**
|
|
24
|
-
* Extends this instance with additional functionality
|
|
31
|
+
* Extends this instance with additional functionality via in-place mutation.
|
|
25
32
|
*
|
|
26
33
|
* **Capabilities:**
|
|
27
|
-
* - `overrideFactory`: Wrap the factory
|
|
28
|
-
* - `extendShape`: Add custom properties/methods
|
|
34
|
+
* - `overrideFactory`: Wrap the factory (logging, retry, caching, etc.)
|
|
35
|
+
* - `extendShape`: Add custom properties/methods
|
|
36
|
+
* - `dispose`: Cleanup resources when disposed
|
|
29
37
|
*
|
|
30
38
|
* **Type Safety:**
|
|
31
39
|
* - Use `ILazyPromiseExtension<any>` for universal extensions
|
|
32
|
-
* - Use `ILazyPromiseExtension<ConcreteType>` for type-specific extensions
|
|
40
|
+
* - Use `ILazyPromiseExtension<ConcreteType>` for type-specific extensions
|
|
33
41
|
*
|
|
34
|
-
* **Note:** Extensions mutate the instance and can be chained.
|
|
42
|
+
* **Note:** Extensions mutate the instance and can be chained.
|
|
35
43
|
*
|
|
36
|
-
* @param extension -
|
|
37
|
-
* @returns The same instance
|
|
44
|
+
* @param extension - Extension configuration
|
|
45
|
+
* @returns The same instance with applied extensions
|
|
38
46
|
*
|
|
39
47
|
* @example
|
|
40
48
|
* ```typescript
|
|
41
|
-
* // Universal logging extension
|
|
42
49
|
* const logged = lazy.extend({
|
|
43
50
|
* overrideFactory: (factory) => async (refreshing) => {
|
|
44
51
|
* console.log('Loading...');
|
|
45
52
|
* return await factory(refreshing);
|
|
46
53
|
* }
|
|
47
54
|
* });
|
|
48
|
-
*
|
|
49
|
-
* // Type-specific extension with custom methods
|
|
50
|
-
* const enhanced = lazyNumber.extend<{ double: () => number | undefined }>({
|
|
51
|
-
* extendShape: (instance) => Object.assign(instance, {
|
|
52
|
-
* double: () => instance.currentValue !== undefined
|
|
53
|
-
* ? instance.currentValue * 2
|
|
54
|
-
* : undefined
|
|
55
|
-
* })
|
|
56
|
-
* });
|
|
57
|
-
*
|
|
58
|
-
* // Chaining multiple extensions
|
|
59
|
-
* const composed = lazy
|
|
60
|
-
* .extend(cacheExtension)
|
|
61
|
-
* .extend(loggingExtension);
|
|
62
55
|
* ```
|
|
63
56
|
*/
|
|
64
57
|
extend<TExtShape extends object = object>(extension: Partial<ILazyPromiseExtension<any, TExtShape>>): object extends TExtShape ? this : this & TExtShape;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Manually sets the value and marks loading as complete.
|
|
60
|
+
* Clears any errors and restarts the expiration tracker if configured.
|
|
61
|
+
*
|
|
62
|
+
* @param res - The value to set
|
|
63
|
+
* @returns The value that was set
|
|
64
|
+
*/
|
|
69
65
|
setInstance(res: T): T;
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Re-executes the factory to get fresh data.
|
|
68
|
+
*
|
|
69
|
+
* **Concurrency handling:**
|
|
70
|
+
* - Supersedes any in-progress load or refresh
|
|
71
|
+
* - Multiple concurrent refreshes: latest wins
|
|
72
|
+
* - All awaiting promises receive the final refreshed value
|
|
73
|
+
*
|
|
74
|
+
* @returns Promise resolving to the refreshed value
|
|
75
|
+
*/
|
|
72
76
|
refresh(): Promise<T>;
|
|
73
77
|
reset(): void;
|
|
74
78
|
dispose(): void;
|
|
79
|
+
protected ensureInstanceLoading(): void;
|
|
80
|
+
private startLoading;
|
|
81
|
+
protected onRejected(e: unknown): T | TInitial;
|
|
82
|
+
protected setError(err: unknown): void;
|
|
83
|
+
protected clearError(): void;
|
|
75
84
|
protected parseError(err: unknown): string;
|
|
76
85
|
}
|
package/types/lazy/types.d.ts
CHANGED
|
@@ -1,58 +1,54 @@
|
|
|
1
1
|
import type { IResettableModel } from '../models/types.js';
|
|
2
|
-
/** Represents a lazily loaded value. */
|
|
2
|
+
/** Represents a lazily loaded value that initializes on first access. */
|
|
3
3
|
export interface ILazy<T> {
|
|
4
|
-
/** Returns current value
|
|
4
|
+
/** Returns current value, triggering loading if not yet loaded. */
|
|
5
5
|
readonly value: T;
|
|
6
|
-
/** Returns
|
|
6
|
+
/** Returns true if value has been loaded. Does not trigger loading. */
|
|
7
7
|
readonly hasValue: boolean;
|
|
8
|
-
/** Returns current value or undefined if not
|
|
8
|
+
/** Returns current value or undefined if not loaded. Does not trigger loading. */
|
|
9
9
|
readonly currentValue: T | undefined;
|
|
10
|
-
/** Returns error message if loading failed, null otherwise.
|
|
10
|
+
/** Returns error message if loading failed, null otherwise. Does not trigger loading. */
|
|
11
11
|
readonly error: string | null;
|
|
12
12
|
}
|
|
13
|
-
/** Represents a lazily asynchronously loaded value. */
|
|
13
|
+
/** Represents a lazily asynchronously loaded value with promise-based access. */
|
|
14
14
|
export interface ILazyPromise<T, TInitial extends T | undefined = undefined> extends ILazy<T | TInitial> {
|
|
15
15
|
/**
|
|
16
|
-
* Returns
|
|
17
|
-
*
|
|
18
|
-
* Accessing this property does not trigger loading.
|
|
16
|
+
* Returns loading state: true (loading), false (loaded), null (not started).
|
|
17
|
+
* Does not trigger loading.
|
|
19
18
|
*/
|
|
20
19
|
readonly isLoading: boolean | null;
|
|
21
|
-
/** Returns the promise for the
|
|
20
|
+
/** Returns the promise for the value, triggering loading if not started. */
|
|
22
21
|
readonly promise: Promise<T>;
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* If multiple refreshes are called concurrently, only the last one will update the instance.
|
|
23
|
+
* Re-executes the factory to get fresh data. If concurrent refreshes occur, the latest wins.
|
|
24
|
+
* All awaiting promises will resolve to the final refreshed value.
|
|
27
25
|
*
|
|
28
|
-
* **⚠️ Use sparingly:**
|
|
29
|
-
* Over-
|
|
26
|
+
* **⚠️ Use sparingly:** Only refresh when explicitly needed for fresh data.
|
|
27
|
+
* Over-use defeats lazy loading and caching benefits.
|
|
30
28
|
*
|
|
31
|
-
* **
|
|
32
|
-
* - User-initiated refresh
|
|
33
|
-
* - Cache invalidation after
|
|
34
|
-
* - Time-based refresh with
|
|
35
|
-
* -
|
|
29
|
+
* **Valid use cases:**
|
|
30
|
+
* - User-initiated refresh (pull-to-refresh, refresh button)
|
|
31
|
+
* - Cache invalidation after mutation
|
|
32
|
+
* - Time-based refresh with throttling
|
|
33
|
+
* - Error recovery
|
|
36
34
|
*
|
|
37
|
-
* **
|
|
38
|
-
* -
|
|
39
|
-
* - Using
|
|
40
|
-
* - Calling
|
|
41
|
-
* - Using refresh as a substitute for real-time updates (consider WebSockets/polling instead)
|
|
35
|
+
* **Avoid:**
|
|
36
|
+
* - Refreshing on every render/mount
|
|
37
|
+
* - Using instead of cache expiration (use `withExpire`)
|
|
38
|
+
* - Calling in loops or high-frequency events without debouncing
|
|
42
39
|
*
|
|
43
|
-
* @returns Promise
|
|
40
|
+
* @returns Promise resolving to the refreshed value
|
|
44
41
|
*/
|
|
45
42
|
refresh(): Promise<T>;
|
|
46
43
|
}
|
|
47
44
|
/**
|
|
48
|
-
*
|
|
49
|
-
* Extends ILazyPromise
|
|
50
|
-
* Use this interface in extensions that need direct control over the lazy value lifecycle.
|
|
45
|
+
* Controllable lazy promise with manual state management.
|
|
46
|
+
* Extends ILazyPromise with methods to manually set values and reset state.
|
|
51
47
|
*/
|
|
52
48
|
export interface IControllableLazyPromise<T, TInitial extends T | undefined = undefined> extends ILazyPromise<T, TInitial>, IResettableModel {
|
|
53
49
|
/**
|
|
54
|
-
* Manually sets the
|
|
55
|
-
* Useful for cache synchronization and manual state
|
|
50
|
+
* Manually sets the value and marks loading as complete.
|
|
51
|
+
* Useful for cache synchronization and manual state updates.
|
|
56
52
|
*
|
|
57
53
|
* @param value - The value to set
|
|
58
54
|
* @returns The value that was set
|
|
@@ -60,28 +56,20 @@ export interface IControllableLazyPromise<T, TInitial extends T | undefined = un
|
|
|
60
56
|
setInstance(value: T): T;
|
|
61
57
|
}
|
|
62
58
|
/**
|
|
63
|
-
* Factory function
|
|
59
|
+
* Factory function that retrieves the value for LazyPromise.
|
|
64
60
|
*
|
|
65
|
-
* @param refreshing -
|
|
66
|
-
|
|
61
|
+
* @param refreshing - True when called via refresh(), false on initial load
|
|
62
|
+
*/
|
|
67
63
|
export type LazyFactory<T> = (refreshing?: boolean) => Promise<T>;
|
|
68
64
|
/**
|
|
69
|
-
* Extension for LazyPromise instances.
|
|
65
|
+
* Extension for LazyPromise instances, enabling factory wrapping and instance augmentation.
|
|
70
66
|
*
|
|
71
|
-
* @template T -
|
|
72
|
-
* @template TExtShape - Additional
|
|
67
|
+
* @template T - Value type the extension is compatible with (use `any` for universal extensions)
|
|
68
|
+
* @template TExtShape - Additional properties/methods added to the instance
|
|
73
69
|
*
|
|
74
70
|
* @example
|
|
75
71
|
* ```typescript
|
|
76
|
-
* //
|
|
77
|
-
* const doublingExtension: ILazyPromiseExtension<number> = {
|
|
78
|
-
* overrideFactory: (original) => async (refreshing) => {
|
|
79
|
-
* const result = await original(refreshing);
|
|
80
|
-
* return result * 2;
|
|
81
|
-
* }
|
|
82
|
-
* };
|
|
83
|
-
*
|
|
84
|
-
* // Universal extension (works with any type)
|
|
72
|
+
* // Universal logging extension
|
|
85
73
|
* const loggingExtension: ILazyPromiseExtension<any> = {
|
|
86
74
|
* overrideFactory: (original) => async (refreshing) => {
|
|
87
75
|
* console.log('Loading...');
|
|
@@ -92,19 +80,40 @@ export type LazyFactory<T> = (refreshing?: boolean) => Promise<T>;
|
|
|
92
80
|
*/
|
|
93
81
|
export interface ILazyPromiseExtension<T = any, TExtShape extends object = object> {
|
|
94
82
|
/**
|
|
95
|
-
*
|
|
83
|
+
* Augment the instance with additional properties/methods.
|
|
84
|
+
* Receives IControllableLazyPromise with setInstance() and reset() for manual control.
|
|
85
|
+
*
|
|
86
|
+
* @param previous - The controllable LazyPromise instance
|
|
87
|
+
* @returns The instance with additional shape
|
|
88
|
+
*/
|
|
89
|
+
extendShape?: <TInitial extends T | undefined = undefined>(previous: IControllableLazyPromise<T, TInitial>) => IControllableLazyPromise<T, TInitial> & TExtShape;
|
|
90
|
+
/**
|
|
91
|
+
* Wrap or replace the factory function.
|
|
92
|
+
*
|
|
96
93
|
* @param original - The original factory function
|
|
97
94
|
* @param target - The LazyPromise instance being extended
|
|
98
95
|
* @returns A new factory function
|
|
99
96
|
*/
|
|
100
|
-
overrideFactory?: <TInitial extends T | undefined = undefined>(original: LazyFactory<T>, target: ILazyPromise<T, TInitial>) => LazyFactory<T>;
|
|
97
|
+
overrideFactory?: <TInitial extends T | undefined = undefined>(original: LazyFactory<T>, target: ILazyPromise<T, TInitial> & TExtShape) => LazyFactory<T>;
|
|
101
98
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
99
|
+
* Cleanup function called when the LazyPromise is disposed.
|
|
100
|
+
* Use for cleaning up resources (timers, subscriptions, listeners).
|
|
101
|
+
* Executes in reverse order: newest extension first, oldest last.
|
|
105
102
|
*
|
|
106
|
-
* @param
|
|
107
|
-
*
|
|
103
|
+
* @param instance - The extended LazyPromise instance being disposed
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const intervalExtension: ILazyPromiseExtension<any, { stopTimer: () => void }> = {
|
|
108
|
+
* extendShape: (instance) => {
|
|
109
|
+
* let intervalId: NodeJS.Timeout | null = null;
|
|
110
|
+
* return Object.assign(instance, {
|
|
111
|
+
* stopTimer: () => { if (intervalId) clearInterval(intervalId); }
|
|
112
|
+
* });
|
|
113
|
+
* },
|
|
114
|
+
* dispose: (instance) => instance.stopTimer()
|
|
115
|
+
* };
|
|
116
|
+
* ```
|
|
108
117
|
*/
|
|
109
|
-
|
|
118
|
+
dispose?: (instance: ILazyPromise<T, any> & TExtShape) => void;
|
|
110
119
|
}
|