unwrapped 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +553 -2
- package/dist/core/index.d.mts +483 -44
- package/dist/core/index.d.ts +483 -44
- package/dist/core/index.js +529 -117
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +529 -117
- package/dist/core/index.mjs.map +1 -1
- package/dist/vue/index.d.mts +103 -22
- package/dist/vue/index.d.ts +103 -22
- package/dist/vue/index.js +16 -44
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/index.mjs +18 -46
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/core/asyncResult.ts +0 -312
- package/src/core/cache.ts +0 -81
- package/src/core/error.ts +0 -23
- package/src/core/index.ts +0 -4
- package/src/core/result.ts +0 -101
- package/src/vue/components/asyncResultLoader.ts +0 -99
- package/src/vue/composables.ts +0 -106
- package/src/vue/index.ts +0 -2
package/dist/core/index.mjs
CHANGED
|
@@ -11,68 +11,171 @@ var ErrorBase = class {
|
|
|
11
11
|
this.logError();
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Converts the error to a string representation, on the format "Error {code}: {message}".
|
|
16
|
+
* @returns a string representation of the error
|
|
17
|
+
*/
|
|
14
18
|
toString() {
|
|
15
19
|
return `Error ${this.code}: ${this.message ?? ""}`;
|
|
16
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Logs the error to the console, uses console.error and ErrorBase.toString() internally.
|
|
23
|
+
* Logs thrownError if it was provided on creation.
|
|
24
|
+
*/
|
|
17
25
|
logError() {
|
|
18
|
-
|
|
26
|
+
if (this.thrownError) {
|
|
27
|
+
console.error(this.toString(), this.thrownError);
|
|
28
|
+
} else {
|
|
29
|
+
console.error(this.toString());
|
|
30
|
+
}
|
|
19
31
|
}
|
|
20
32
|
};
|
|
21
33
|
|
|
22
34
|
// src/core/result.ts
|
|
23
35
|
var Result = class _Result {
|
|
24
36
|
_state;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new Result instance with the given state.
|
|
39
|
+
* @param state the state of the created Result
|
|
40
|
+
*/
|
|
25
41
|
constructor(state) {
|
|
26
42
|
this._state = state;
|
|
27
43
|
}
|
|
44
|
+
/** Returns the internal state of the Result. */
|
|
28
45
|
get state() {
|
|
29
46
|
return this._state;
|
|
30
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks if the Result is successful.
|
|
50
|
+
* @returns whether or not the result is successful
|
|
51
|
+
*/
|
|
52
|
+
isSuccess() {
|
|
53
|
+
return this._state.status === "success";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Checks if the Result is an error.
|
|
57
|
+
* @returns whether or not the result is an error
|
|
58
|
+
*/
|
|
59
|
+
isError() {
|
|
60
|
+
return this._state.status === "error";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates a successful Result with the given value.
|
|
64
|
+
* @param value the result of the successful operation
|
|
65
|
+
* @returns a successful Result
|
|
66
|
+
*/
|
|
31
67
|
static ok(value) {
|
|
32
68
|
return new _Result({ status: "success", value });
|
|
33
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates an error Result with the given error.
|
|
72
|
+
* @param error the error of the failed operation
|
|
73
|
+
* @returns an error Result
|
|
74
|
+
*/
|
|
34
75
|
static err(error) {
|
|
35
76
|
return new _Result({ status: "error", error });
|
|
36
77
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Creates an error Result (containing an ErrorBase) with the given error code and optional message.
|
|
80
|
+
* @param code the error code
|
|
81
|
+
* @param message an optional error message
|
|
82
|
+
* @returns an error Result
|
|
83
|
+
*/
|
|
84
|
+
static errTag(code, message, thrownError) {
|
|
85
|
+
return _Result.err(new ErrorBase(code, message, thrownError));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the successful value (if the Result is successful) or null (if it is an error).
|
|
89
|
+
* @returns either the successful value or null
|
|
90
|
+
*/
|
|
40
91
|
unwrapOrNull() {
|
|
41
92
|
if (this._state.status === "success") {
|
|
42
93
|
return this._state.value;
|
|
43
94
|
}
|
|
44
95
|
return null;
|
|
45
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns the successful value (if the Result is successful) or throws an error (if it is an error).
|
|
99
|
+
* @returns the successful value
|
|
100
|
+
* @throws an normal JS Error if the result is not successful
|
|
101
|
+
*/
|
|
46
102
|
unwrapOrThrow() {
|
|
47
103
|
if (this._state.status === "success") {
|
|
48
104
|
return this._state.value;
|
|
49
105
|
}
|
|
50
106
|
throw new Error("Tried to unwrap a Result that is not successful");
|
|
51
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns the successful value (if the Result is successful) or a default value (if it is an error).
|
|
110
|
+
* @param defaultValue the default value to return if the Result is an error
|
|
111
|
+
* @returns either the successful value or the default value
|
|
112
|
+
*/
|
|
52
113
|
unwrapOr(defaultValue) {
|
|
53
114
|
if (this._state.status === "success") {
|
|
54
115
|
return this._state.value;
|
|
55
116
|
}
|
|
56
117
|
return defaultValue;
|
|
57
118
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Transforms a Promise of a successful value into a Promise of a Result,
|
|
121
|
+
* catching any thrown errors and mapping them using the provided errorMapper function.
|
|
122
|
+
* @param promise the promise to execute
|
|
123
|
+
* @param errorMapper a function that maps a thrown error to a Result error
|
|
124
|
+
* @returns a Promise resolving to a Result containing either the successful value or the mapped error
|
|
125
|
+
*/
|
|
64
126
|
static tryPromise(promise, errorMapper) {
|
|
65
127
|
return promise.then((value) => _Result.ok(value)).catch((error) => _Result.err(errorMapper(error)));
|
|
66
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Executes an asynchronous function and transforms its result into a Result,
|
|
131
|
+
* catching any thrown errors and mapping them using the provided errorMapper function.
|
|
132
|
+
* Same as Result.tryPromise(fn(), errorMapper).
|
|
133
|
+
* @param fn the asynchronous function to execute
|
|
134
|
+
* @param errorMapper a function that maps a thrown error to a Result error
|
|
135
|
+
* @returns a Promise resolving to a Result containing either the successful value or the mapped error
|
|
136
|
+
*/
|
|
67
137
|
static tryFunction(fn, errorMapper) {
|
|
68
138
|
return _Result.tryPromise(fn(), errorMapper);
|
|
69
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Chains the current Result with another operation that returns a ResultState.
|
|
142
|
+
* If the current Result is successful, applies the provided function to its value.
|
|
143
|
+
* Otherwise, returns the current error.
|
|
144
|
+
* Useful to describe a sequence of operations that can each fail, and short-circuit on the first failure.
|
|
145
|
+
* @param fn a function taking as input the successful value of the result, and returning a ResultState describing the result of its own operation
|
|
146
|
+
* @returns a new Result that has either the successful value of the operation, or either the error of the current result or the error returned by fn
|
|
147
|
+
*/
|
|
70
148
|
chain(fn) {
|
|
71
149
|
if (this._state.status === "success") {
|
|
72
150
|
return new _Result(fn(this._state.value));
|
|
73
151
|
}
|
|
74
152
|
return _Result.err(this._state.error);
|
|
75
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Chains the current Result with another operation that returns a Result.
|
|
156
|
+
* If the current Result is successful, applies the provided function to its value.
|
|
157
|
+
* Otherwise, returns the current error.
|
|
158
|
+
* Useful to describe a sequence of operations that can each fail, and short-circuit on the first failure.
|
|
159
|
+
* Same as chain, but the function returns a Result directly instead of a ResultState.
|
|
160
|
+
* @param fn a function taking as input the successful value of the result, and returning a Result describing the result of its own operation
|
|
161
|
+
* @returns a new Result that has either the successful value of the operation, or either the error of the current result or the error returned by fn
|
|
162
|
+
*/
|
|
163
|
+
flatChain(fn) {
|
|
164
|
+
if (this._state.status === "success") {
|
|
165
|
+
return fn(this._state.value);
|
|
166
|
+
}
|
|
167
|
+
return _Result.err(this._state.error);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @yields the current Result, and if it is successful, returns its value.
|
|
171
|
+
* This allows using Result instances in generator functions to simplify error handling and propagation.
|
|
172
|
+
* @example
|
|
173
|
+
* function* example(): Generator<Result<number>, number, any> {
|
|
174
|
+
* const result1 = yield* Result.ok(5);
|
|
175
|
+
* const result2 = yield* Result.ok(10);
|
|
176
|
+
* return result1 + result2;
|
|
177
|
+
* }
|
|
178
|
+
*/
|
|
76
179
|
*[Symbol.iterator]() {
|
|
77
180
|
yield this;
|
|
78
181
|
if (this._state.status === "success") {
|
|
@@ -80,7 +183,24 @@ var Result = class _Result {
|
|
|
80
183
|
}
|
|
81
184
|
return void 0;
|
|
82
185
|
}
|
|
83
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Runs a generator function that yields Result instances, propagating errors automatically.
|
|
188
|
+
* If any yielded Result is an error, the execution stops and the error is returned.
|
|
189
|
+
* If all yielded Results are successful, returns a successful Result with the final returned value.
|
|
190
|
+
*
|
|
191
|
+
* This serves the same purpose as chain/flatChain, but allows for a more linear and readable style of coding.
|
|
192
|
+
* Think of it as "async/await" but for Result handling in generator functions.
|
|
193
|
+
*
|
|
194
|
+
* @param generator a generator function that yields Result instances
|
|
195
|
+
* @returns a Result containing either the final successful value or the first encountered error
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* const result = Result.run(function* () {
|
|
199
|
+
* const value1 = yield* Result.ok(5);
|
|
200
|
+
* const value2 = yield* Result.ok(10);
|
|
201
|
+
* return value1 + value2;
|
|
202
|
+
* });
|
|
203
|
+
*/
|
|
84
204
|
static run(generator) {
|
|
85
205
|
const iterator = generator();
|
|
86
206
|
let result = iterator.next();
|
|
@@ -102,74 +222,142 @@ var AsyncResult = class _AsyncResult {
|
|
|
102
222
|
constructor(state) {
|
|
103
223
|
this._state = state || { status: "idle" };
|
|
104
224
|
}
|
|
225
|
+
// === Getting current state ===
|
|
226
|
+
/**
|
|
227
|
+
* Returns the internal state of the AsyncResult.
|
|
228
|
+
*/
|
|
105
229
|
get state() {
|
|
106
230
|
return this._state;
|
|
107
231
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
static ok(value) {
|
|
113
|
-
return new _AsyncResult({ status: "success", value });
|
|
114
|
-
}
|
|
115
|
-
static err(error) {
|
|
116
|
-
return new _AsyncResult({ status: "error", error });
|
|
117
|
-
}
|
|
232
|
+
/**
|
|
233
|
+
* Checks if the AsyncResult is successful.
|
|
234
|
+
* @returns whether or not the result is successful
|
|
235
|
+
*/
|
|
118
236
|
isSuccess() {
|
|
119
237
|
return this._state.status === "success";
|
|
120
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Checks if the AsyncResult is an error.
|
|
241
|
+
* @returns whether or not the result is an error
|
|
242
|
+
*/
|
|
121
243
|
isError() {
|
|
122
244
|
return this._state.status === "error";
|
|
123
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Checks if the AsyncResult is idle.
|
|
248
|
+
* @returns whether or not the result is idle
|
|
249
|
+
*/
|
|
250
|
+
isIdle() {
|
|
251
|
+
return this._state.status === "idle";
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Checks if the AsyncResult is loading.
|
|
255
|
+
* @returns whether or not the result is loading
|
|
256
|
+
*/
|
|
124
257
|
isLoading() {
|
|
125
258
|
return this._state.status === "loading";
|
|
126
259
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
260
|
+
// === Unwrapping values ===
|
|
261
|
+
/**
|
|
262
|
+
* Returns the successful value if the AsyncResult is in a success state, otherwise returns null.
|
|
263
|
+
* @returns the successful value or null
|
|
264
|
+
*/
|
|
265
|
+
unwrapOrNull() {
|
|
266
|
+
if (this._state.status === "success") {
|
|
267
|
+
return this._state.value;
|
|
131
268
|
}
|
|
132
|
-
return
|
|
133
|
-
this._listeners.delete(listener);
|
|
134
|
-
};
|
|
269
|
+
return null;
|
|
135
270
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Returns the successful value if the AsyncResult is in a success state, otherwise throws an error.
|
|
273
|
+
* @returns the successful value
|
|
274
|
+
* @throws an normal JS Error if the result is not successful
|
|
275
|
+
*/
|
|
276
|
+
unwrapOrThrow() {
|
|
277
|
+
if (this._state.status === "success") {
|
|
278
|
+
return this._state.value;
|
|
279
|
+
}
|
|
280
|
+
throw new Error("Tried to unwrap an AsyncResult that is not successful");
|
|
281
|
+
}
|
|
282
|
+
// === Creating/updating from settled values ===
|
|
283
|
+
set state(newState) {
|
|
284
|
+
this._state = newState;
|
|
285
|
+
this._listeners.forEach((listener) => listener(this));
|
|
144
286
|
}
|
|
145
287
|
setState(newState) {
|
|
146
288
|
this.state = newState;
|
|
147
289
|
}
|
|
148
|
-
|
|
149
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Creates a successful AsyncResult with the given value.
|
|
292
|
+
* @param value the result of the successful operation
|
|
293
|
+
* @returns a successful AsyncResult
|
|
294
|
+
*/
|
|
295
|
+
static ok(value) {
|
|
296
|
+
return new _AsyncResult({ status: "success", value });
|
|
150
297
|
}
|
|
151
|
-
|
|
152
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Creates an error AsyncResult with the given error.
|
|
300
|
+
* @param error the error of the failed operation
|
|
301
|
+
* @returns an error AsyncResult
|
|
302
|
+
*/
|
|
303
|
+
static err(error) {
|
|
304
|
+
return new _AsyncResult({ status: "error", error });
|
|
153
305
|
}
|
|
154
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Creates an error AsyncResult with a new ErrorBase constructed from the given parameters.
|
|
308
|
+
* @param code the error code
|
|
309
|
+
* @param message the error message (optional)
|
|
310
|
+
* @param thrownError the original error object, if any (optional)
|
|
311
|
+
* @param log whether to log the error upon creation (default is true)
|
|
312
|
+
* @returns an error AsyncResult
|
|
313
|
+
*/
|
|
314
|
+
static errTag(code, message, thrownError, log = true) {
|
|
315
|
+
const error = new ErrorBase(code, message, thrownError, log);
|
|
316
|
+
return _AsyncResult.err(error);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Updates the AsyncResult to a successful state with the given value.
|
|
320
|
+
* Like AsyncResult.ok, but in place.
|
|
321
|
+
* @param value the successful value
|
|
322
|
+
*/
|
|
323
|
+
updateFromValue(value) {
|
|
155
324
|
this.state = { status: "success", value };
|
|
325
|
+
return this;
|
|
156
326
|
}
|
|
157
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Updates the AsyncResult to an error state with the given error.
|
|
329
|
+
* Like AsyncResult.err, but in place.
|
|
330
|
+
* @param error the error
|
|
331
|
+
*/
|
|
332
|
+
updateFromError(error) {
|
|
158
333
|
this.state = { status: "error", error };
|
|
334
|
+
return this;
|
|
159
335
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
336
|
+
// === Creating/updating from promises ===
|
|
337
|
+
/**
|
|
338
|
+
* Creates an AsyncResult from a promise that resolves to a value.
|
|
339
|
+
* The AsyncResult is initially in a loading state, and updates to a successful state once the promise resolves.
|
|
340
|
+
* If the promise rejects, the AsyncResult is updated to an error state with the caught error.
|
|
341
|
+
*
|
|
342
|
+
* Like AsyncResult.fromResultPromise, but for promise that only resolves to a successful value and not a Result.
|
|
343
|
+
*
|
|
344
|
+
* @param promise the promise that resolves to a value
|
|
345
|
+
* @returns an AsyncResult representing the state of the promise
|
|
346
|
+
*/
|
|
347
|
+
static fromValuePromise(promise) {
|
|
169
348
|
const result = new _AsyncResult();
|
|
170
|
-
result.
|
|
349
|
+
result.updateFromValuePromise(promise);
|
|
171
350
|
return result;
|
|
172
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Updates the AsyncResult to a loading state with the given promise.
|
|
354
|
+
* The AsyncResult is initially in a loading state, and updates to a successful state once the promise resolves.
|
|
355
|
+
* If the promise rejects, the AsyncResult is updated to an error state with the caught error.
|
|
356
|
+
*
|
|
357
|
+
* Like AsyncResult.fromValuePromise, but in place.
|
|
358
|
+
*
|
|
359
|
+
* @param promise the promise that resolves to a value
|
|
360
|
+
*/
|
|
173
361
|
updateFromValuePromise(promise) {
|
|
174
362
|
const resultStatePromise = async () => {
|
|
175
363
|
try {
|
|
@@ -179,13 +367,13 @@ var AsyncResult = class _AsyncResult {
|
|
|
179
367
|
return Result.err(error);
|
|
180
368
|
}
|
|
181
369
|
};
|
|
182
|
-
this.updateFromResultPromise(resultStatePromise());
|
|
183
|
-
}
|
|
184
|
-
static fromValuePromise(promise) {
|
|
185
|
-
const result = new _AsyncResult();
|
|
186
|
-
result.updateFromValuePromise(promise);
|
|
187
|
-
return result;
|
|
370
|
+
return this.updateFromResultPromise(resultStatePromise());
|
|
188
371
|
}
|
|
372
|
+
// === Waiting for settled state and get result ===
|
|
373
|
+
/**
|
|
374
|
+
* Waits for the AsyncResult to settle (either success or error) if it is currently loading.
|
|
375
|
+
* @returns itself once settled
|
|
376
|
+
*/
|
|
189
377
|
async waitForSettled() {
|
|
190
378
|
if (this._state.status === "loading") {
|
|
191
379
|
try {
|
|
@@ -197,7 +385,11 @@ var AsyncResult = class _AsyncResult {
|
|
|
197
385
|
}
|
|
198
386
|
return this;
|
|
199
387
|
}
|
|
200
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Waits for the AsyncResult to settle (either success or error) if it is currently loading, and returns a Result representing the settled state.
|
|
390
|
+
* @returns a Result representing the settled state
|
|
391
|
+
*/
|
|
392
|
+
async toResultPromise() {
|
|
201
393
|
const settled = await this.waitForSettled();
|
|
202
394
|
if (settled.state.status === "idle" || settled.state.status === "loading") {
|
|
203
395
|
throw new Error("Cannot convert idle or loading AsyncResult to ResultState");
|
|
@@ -207,46 +399,134 @@ var AsyncResult = class _AsyncResult {
|
|
|
207
399
|
}
|
|
208
400
|
return Result.ok(settled.state.value);
|
|
209
401
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const value = await this._state.promise;
|
|
217
|
-
this._state = value.state;
|
|
218
|
-
} catch (error) {
|
|
219
|
-
this._state = { status: "error", error };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return new Result(this._state);
|
|
223
|
-
}
|
|
224
|
-
async toValuePromiseThrow() {
|
|
402
|
+
/**
|
|
403
|
+
* Waits for the AsyncResult to settle (either success or error) if it is currently loading, and returns the successful value or throws an error.
|
|
404
|
+
* @returns the successful value
|
|
405
|
+
* @throws an normal JS Error if the result is not successful
|
|
406
|
+
*/
|
|
407
|
+
async toValueOrThrowPromise() {
|
|
225
408
|
const settled = await this.waitForSettled();
|
|
226
409
|
return settled.unwrapOrThrow();
|
|
227
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Waits for the AsyncResult to settle (either success or error) if it is currently loading, and returns the successful value or null.
|
|
413
|
+
* @returns either the successful value or null
|
|
414
|
+
*/
|
|
228
415
|
async toValueOrNullPromise() {
|
|
229
416
|
const settled = await this.waitForSettled();
|
|
230
417
|
return settled.unwrapOrNull();
|
|
231
418
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Creates an AsyncResult from a promise that resolves to a Result.
|
|
421
|
+
* The AsyncResult is initially in a loading state, and updates to the settled state once the promise resolves.
|
|
422
|
+
* If the promise rejects, the AsyncResult is updated to an error state with a default ErrorBase.
|
|
423
|
+
*
|
|
424
|
+
* @param promise the promise that resolves to a Result
|
|
425
|
+
* @returns an AsyncResult representing the state of the promise
|
|
426
|
+
*/
|
|
427
|
+
static fromResultPromise(promise) {
|
|
428
|
+
const result = new _AsyncResult();
|
|
429
|
+
result.updateFromResultPromise(promise);
|
|
430
|
+
return result;
|
|
237
431
|
}
|
|
238
|
-
|
|
239
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Updates the AsyncResult to a loading state with the given promise.
|
|
434
|
+
* The promise must produce a Result once settled (meaning it should return the error in the result when possible).
|
|
435
|
+
* If the promise rejects, the AsyncResult is updated to an error state with a default ErrorBase.
|
|
436
|
+
*
|
|
437
|
+
* Like AsyncResult.fromResultPromise, but in place.
|
|
438
|
+
*
|
|
439
|
+
* @param promise the promise that resolves to a Result
|
|
440
|
+
*/
|
|
441
|
+
updateFromResultPromise(promise) {
|
|
442
|
+
this.state = { status: "loading", promise };
|
|
443
|
+
promise.then((res) => {
|
|
444
|
+
this.state = res.state;
|
|
445
|
+
}).catch((error) => {
|
|
446
|
+
this.updateFromError(new ErrorBase("defect_on_updateFromResultPromise", "The promise provided to AsyncResult rejected", error));
|
|
447
|
+
});
|
|
448
|
+
return this;
|
|
240
449
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
450
|
+
// === Listeners ===
|
|
451
|
+
/**
|
|
452
|
+
* Adds a listener that is called whenever the AsyncResult state changes.
|
|
453
|
+
* @param listener the listener function to add
|
|
454
|
+
* @param immediate whether to call the listener immediately with the current state (default is true)
|
|
455
|
+
* @returns a function to remove the listener
|
|
456
|
+
*/
|
|
457
|
+
listen(listener, immediate = true) {
|
|
458
|
+
this._listeners.add(listener);
|
|
459
|
+
if (immediate) {
|
|
460
|
+
listener(this);
|
|
244
461
|
}
|
|
245
|
-
|
|
462
|
+
return () => {
|
|
463
|
+
this._listeners.delete(listener);
|
|
464
|
+
};
|
|
246
465
|
}
|
|
247
|
-
|
|
248
|
-
|
|
466
|
+
/**
|
|
467
|
+
* Adds a listener that is called whenever the AsyncResult state changes, and automatically unsubscribes once it is settled (success or error).
|
|
468
|
+
* @param listener the listener function to add
|
|
469
|
+
* @param immediate whether to call the listener immediately with the current state (default is true)
|
|
470
|
+
* @returns a function to remove the listener
|
|
471
|
+
*/
|
|
472
|
+
listenUntilSettled(listener, immediate = true) {
|
|
473
|
+
const unsub = this.listen((result) => {
|
|
474
|
+
listener(result);
|
|
475
|
+
if (result.state.status === "success" || result.state.status === "error") {
|
|
476
|
+
unsub();
|
|
477
|
+
}
|
|
478
|
+
}, immediate);
|
|
479
|
+
return unsub;
|
|
249
480
|
}
|
|
481
|
+
// === Mirroring ===
|
|
482
|
+
/**
|
|
483
|
+
* Mirrors the state of another AsyncResult into this one.
|
|
484
|
+
* Whenever the other AsyncResult changes state, this AsyncResult is updated to match.
|
|
485
|
+
* @param other the AsyncResult to mirror
|
|
486
|
+
* @returns a function to stop mirroring
|
|
487
|
+
*/
|
|
488
|
+
mirror(other) {
|
|
489
|
+
return other.listen((newState) => {
|
|
490
|
+
this.setState(newState.state);
|
|
491
|
+
}, true);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Mirrors the state of another AsyncResult into this one, until the other AsyncResult is settled (success or error).
|
|
495
|
+
* Whenever the other AsyncResult changes state, this AsyncResult is updated to match, until the other AsyncResult is settled.
|
|
496
|
+
* @param other the AsyncResult to mirror
|
|
497
|
+
* @returns a function to stop mirroring
|
|
498
|
+
*/
|
|
499
|
+
mirrorUntilSettled(other) {
|
|
500
|
+
return other.listenUntilSettled((newState) => {
|
|
501
|
+
this.setState(newState.state);
|
|
502
|
+
}, true);
|
|
503
|
+
}
|
|
504
|
+
// === Actions ===
|
|
505
|
+
/**
|
|
506
|
+
* Creates a LazyAction that can be triggered to run the given Action.
|
|
507
|
+
* @param action the Action to run when triggered
|
|
508
|
+
* @returns an object containing the trigger function and the associated AsyncResult
|
|
509
|
+
*/
|
|
510
|
+
static makeLazyAction(action) {
|
|
511
|
+
const result = new _AsyncResult();
|
|
512
|
+
const trigger = () => {
|
|
513
|
+
result.updateFromResultPromise(action());
|
|
514
|
+
};
|
|
515
|
+
return { trigger, result };
|
|
516
|
+
}
|
|
517
|
+
// === Chaining ===
|
|
518
|
+
/**
|
|
519
|
+
* Chains the current AsyncResult with another operation that returns a Result or a Promise of a Result.
|
|
520
|
+
*
|
|
521
|
+
* If the current AsyncResult is loading, waits for it to settle first, then applies the provided function to its value.
|
|
522
|
+
* If the current AsyncResult is successful, applies the provided function to its value.
|
|
523
|
+
* Otherwise, returns the current error.
|
|
524
|
+
*
|
|
525
|
+
* Useful to describe a sequence of operations that can each fail, and short-circuit on the first failure.
|
|
526
|
+
*
|
|
527
|
+
* @param fn a function taking as input the successful value of the result, and returning a Result or a Promise of a Result describing the result of its own operation
|
|
528
|
+
* @returns a new AsyncResult that has either the successful value of the operation, or either the error of the current result or the error returned by fn
|
|
529
|
+
*/
|
|
250
530
|
chain(fn) {
|
|
251
531
|
const newResultBuilder = async () => {
|
|
252
532
|
const settled = await this.waitForSettled();
|
|
@@ -260,31 +540,41 @@ var AsyncResult = class _AsyncResult {
|
|
|
260
540
|
};
|
|
261
541
|
return _AsyncResult.fromResultPromise(newResultBuilder());
|
|
262
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* Chains the current AsyncResult with another operation that returns an AsyncResult.
|
|
545
|
+
*
|
|
546
|
+
* If the current AsyncResult is loading, waits for it to settle first, then applies the provided function to its value.
|
|
547
|
+
* If the current AsyncResult is successful, applies the provided function to its value.
|
|
548
|
+
* Otherwise, returns the current error.
|
|
549
|
+
*
|
|
550
|
+
* Useful to describe a sequence of operations that can each fail, and short-circuit on the first failure.
|
|
551
|
+
*
|
|
552
|
+
* Like chain, but for functions returning AsyncResult instead of Result.
|
|
553
|
+
*
|
|
554
|
+
* @param fn a function taking as input the successful value of the result, and returning an AsyncResult describing the result of its own operation
|
|
555
|
+
* @returns a new AsyncResult that has either the successful value of the operation, or either the error of the current result or the error returned by fn
|
|
556
|
+
*/
|
|
263
557
|
flatChain(fn) {
|
|
264
558
|
const newResultBuilder = async () => {
|
|
265
|
-
const settled = await this.
|
|
559
|
+
const settled = await this.toResultPromise();
|
|
266
560
|
if (settled.state.status === "error") {
|
|
267
561
|
return Result.err(settled.state.error);
|
|
268
562
|
}
|
|
269
563
|
const nextAsyncResult = fn(settled.state.value);
|
|
270
|
-
const nextSettled = await nextAsyncResult.
|
|
564
|
+
const nextSettled = await nextAsyncResult.toResultPromise();
|
|
271
565
|
return nextSettled;
|
|
272
566
|
};
|
|
273
567
|
return _AsyncResult.fromResultPromise(newResultBuilder());
|
|
274
568
|
}
|
|
275
569
|
// pipeParallel PipeFunction[] -> AsyncResult<T, E>[]
|
|
276
570
|
// pipeParallelAndCollapse PipeFunction[] -> AsyncResult<T[], E>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.setState(newState.state);
|
|
285
|
-
}, true);
|
|
286
|
-
}
|
|
287
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
571
|
+
/**
|
|
572
|
+
* Ensures that all provided AsyncResults are successful.
|
|
573
|
+
* If all are successful, returns an AsyncResult containing an array of their values.
|
|
574
|
+
* If any AsyncResult is an error, returns an AsyncResult with the first encountered error.
|
|
575
|
+
* @param results an array of AsyncResults to check
|
|
576
|
+
* @returns an AsyncResult containing either an array of successful values or the first encountered error
|
|
577
|
+
*/
|
|
288
578
|
static ensureAvailable(results) {
|
|
289
579
|
if (results.length === 0) {
|
|
290
580
|
return _AsyncResult.ok(void 0);
|
|
@@ -302,7 +592,17 @@ var AsyncResult = class _AsyncResult {
|
|
|
302
592
|
);
|
|
303
593
|
return _AsyncResult.fromResultPromise(promise);
|
|
304
594
|
}
|
|
305
|
-
//
|
|
595
|
+
// === Generator support ===
|
|
596
|
+
/**
|
|
597
|
+
* Yields the current AsyncResult, and if it is successful, returns its value.
|
|
598
|
+
* This allows using AsyncResult instances in generator functions to simplify error handling and propagation.
|
|
599
|
+
* @example
|
|
600
|
+
* function* example(): Generator<AsyncResult<number>, number, any> {
|
|
601
|
+
* const result1 = yield* AsyncResult.ok(5);
|
|
602
|
+
* const result2 = yield* AsyncResult.ok(10);
|
|
603
|
+
* return result1 + result2;
|
|
604
|
+
* }
|
|
605
|
+
*/
|
|
306
606
|
*[Symbol.iterator]() {
|
|
307
607
|
yield this;
|
|
308
608
|
if (this._state.status === "success") {
|
|
@@ -315,7 +615,7 @@ var AsyncResult = class _AsyncResult {
|
|
|
315
615
|
let result = iterator.next();
|
|
316
616
|
while (!result.done) {
|
|
317
617
|
const yielded = result.value;
|
|
318
|
-
const settled = await yielded.
|
|
618
|
+
const settled = await yielded.toResultPromise();
|
|
319
619
|
if (settled.state.status === "error") {
|
|
320
620
|
return Result.err(settled.state.error);
|
|
321
621
|
}
|
|
@@ -324,14 +624,49 @@ var AsyncResult = class _AsyncResult {
|
|
|
324
624
|
return Result.ok(result.value);
|
|
325
625
|
};
|
|
326
626
|
}
|
|
327
|
-
|
|
627
|
+
/**
|
|
628
|
+
* Runs a generator function that yields AsyncResult instances, propagating errors automatically.
|
|
629
|
+
* If any yielded AsyncResult is an error, the execution stops and the error is returned.
|
|
630
|
+
* If all yielded AsyncResults are successful, returns a successful AsyncResult with the final returned value.
|
|
631
|
+
*
|
|
632
|
+
* This serves the same purpose as chain/flatChain, but allows for a more linear and readable style of coding.
|
|
633
|
+
* Think of it as "async/await" but for AsyncResult handling in generator functions.
|
|
634
|
+
*
|
|
635
|
+
* @param generatorFunc a generator function that yields AsyncResult instances
|
|
636
|
+
* @returns a AsyncResult containing either the final successful value or the first encountered error
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* const result = AsyncResult.run(function* () {
|
|
640
|
+
* const value1 = yield* AsyncResult.ok(5);
|
|
641
|
+
* const value2 = yield* AsyncResult.ok(10);
|
|
642
|
+
* return value1 + value2;
|
|
643
|
+
* }
|
|
644
|
+
*/
|
|
328
645
|
static run(generatorFunc) {
|
|
329
646
|
const iterator = generatorFunc();
|
|
330
647
|
return _AsyncResult.fromResultPromise(_AsyncResult._runGeneratorProcessor(iterator)());
|
|
331
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Runs a generator function that yields AsyncResult instances, propagating errors automatically, and updates this AsyncResult in place.
|
|
651
|
+
* If any yielded AsyncResult is an error, the execution stops and this AsyncResult is updated to that error.
|
|
652
|
+
* If all yielded AsyncResults are successful, this AsyncResult is updated to a successful state with the final returned value.
|
|
653
|
+
*
|
|
654
|
+
* This serves the same purpose as chain/flatChain, but allows for a more linear and readable style of coding.
|
|
655
|
+
* Think of it as "async/await" but for AsyncResult handling in generator functions.
|
|
656
|
+
*
|
|
657
|
+
* @param generatorFunc a generator function that yields AsyncResult instances
|
|
658
|
+
*/
|
|
332
659
|
runInPlace(generatorFunc) {
|
|
333
660
|
const iterator = generatorFunc();
|
|
334
|
-
this.updateFromResultPromise(_AsyncResult._runGeneratorProcessor(iterator)());
|
|
661
|
+
return this.updateFromResultPromise(_AsyncResult._runGeneratorProcessor(iterator)());
|
|
662
|
+
}
|
|
663
|
+
// === Debuging ===
|
|
664
|
+
log(name) {
|
|
665
|
+
const time = (/* @__PURE__ */ new Date()).toTimeString().slice(0, 7);
|
|
666
|
+
console.log(`${name ?? "<Anonymous AsyncResult>"} ; State at ${time} :`, this.state);
|
|
667
|
+
}
|
|
668
|
+
debug(name) {
|
|
669
|
+
return this.listen((r) => r.log(name));
|
|
335
670
|
}
|
|
336
671
|
};
|
|
337
672
|
|
|
@@ -347,42 +682,90 @@ var KeyedAsyncCache = class {
|
|
|
347
682
|
_cache = /* @__PURE__ */ new Map();
|
|
348
683
|
_fetcher;
|
|
349
684
|
_paramsToKey;
|
|
350
|
-
|
|
685
|
+
_cacheTTL;
|
|
686
|
+
/**
|
|
687
|
+
* Creates a new KeyedAsyncCache instance.
|
|
688
|
+
* @param fetcher the function used to fetch values based on parameters
|
|
689
|
+
* @param paramsToKey a function that converts parameters to a unique string key (default uses JSON.stringify for objects)
|
|
690
|
+
* @param cacheTTL optional time-to-live for cache entries in milliseconds
|
|
691
|
+
*/
|
|
692
|
+
constructor(fetcher, paramsToKey = defaultParamsToKey, cacheTTL = Infinity) {
|
|
351
693
|
this._fetcher = fetcher;
|
|
352
694
|
this._paramsToKey = paramsToKey;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
695
|
+
this._cacheTTL = cacheTTL;
|
|
696
|
+
}
|
|
697
|
+
makeCacheItem(result, fetcherParams, ttl) {
|
|
698
|
+
return {
|
|
699
|
+
result,
|
|
700
|
+
fetcherParams,
|
|
701
|
+
ttl: ttl ?? this._cacheTTL,
|
|
702
|
+
valid: true
|
|
703
|
+
};
|
|
356
704
|
}
|
|
357
705
|
shouldRefetch(existingResult, refetch) {
|
|
706
|
+
if (!existingResult.valid) {
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
358
709
|
if (refetch.policy === "refetch") {
|
|
359
710
|
return true;
|
|
360
711
|
}
|
|
361
712
|
if (refetch.policy === "if-error") {
|
|
362
713
|
return existingResult.result.state.status === "error";
|
|
363
714
|
}
|
|
715
|
+
if (existingResult.ttl !== void 0 && existingResult.lastFetched !== void 0) {
|
|
716
|
+
const now = Date.now();
|
|
717
|
+
if (now - existingResult.lastFetched > existingResult.ttl) {
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
364
721
|
return false;
|
|
365
722
|
}
|
|
723
|
+
updateOrCreateCacheItemFromParams(params, cacheItem) {
|
|
724
|
+
const promise = Promise.resolve(this._fetcher(params));
|
|
725
|
+
let result = cacheItem?.result.updateFromResultPromise(promise) ?? AsyncResult.fromResultPromise(promise);
|
|
726
|
+
cacheItem = cacheItem ?? this.makeCacheItem(result, params);
|
|
727
|
+
promise.then(() => {
|
|
728
|
+
cacheItem.lastFetched = Date.now();
|
|
729
|
+
});
|
|
730
|
+
return cacheItem;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Gets the AsyncResult for the given parameters, fetching it if not cached or if refetching is required.
|
|
734
|
+
* @param params the parameters to fetch the value
|
|
735
|
+
* @param refetch options determining whether to refetch the value
|
|
736
|
+
* @returns the AsyncResult corresponding to the given parameters
|
|
737
|
+
*/
|
|
366
738
|
get(params, refetch = _defaultRefetchOptions) {
|
|
367
739
|
const key = this._paramsToKey(params);
|
|
368
740
|
if (this._cache.has(key)) {
|
|
369
|
-
const
|
|
370
|
-
if (!this.shouldRefetch(
|
|
371
|
-
return
|
|
741
|
+
const cacheItem2 = this._cache.get(key);
|
|
742
|
+
if (!this.shouldRefetch(cacheItem2, refetch)) {
|
|
743
|
+
return cacheItem2.result;
|
|
372
744
|
} else {
|
|
373
|
-
|
|
374
|
-
return
|
|
745
|
+
this.updateOrCreateCacheItemFromParams(params, cacheItem2);
|
|
746
|
+
return cacheItem2.result;
|
|
375
747
|
}
|
|
376
748
|
}
|
|
377
|
-
const
|
|
378
|
-
this._cache.set(key,
|
|
379
|
-
return
|
|
380
|
-
}
|
|
749
|
+
const cacheItem = this.updateOrCreateCacheItemFromParams(params);
|
|
750
|
+
this._cache.set(key, cacheItem);
|
|
751
|
+
return cacheItem.result;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Gets the settled state of the AsyncResult for the given parameters, fetching it if not cached or if refetching is required.
|
|
755
|
+
* Waits for the AsyncResult to settle before returning its state.
|
|
756
|
+
* @param params the parameters to fetch the value
|
|
757
|
+
* @param refetch options determining whether to refetch the value
|
|
758
|
+
* @returns a promise resolving to the settled state of the AsyncResult
|
|
759
|
+
*/
|
|
381
760
|
async getSettledState(params, refetch = _defaultRefetchOptions) {
|
|
382
761
|
const asyncResult = this.get(params, refetch);
|
|
383
762
|
await asyncResult.waitForSettled();
|
|
384
763
|
return asyncResult.state;
|
|
385
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Checks if any cached AsyncResult is currently loading.
|
|
767
|
+
* @returns whether any cached AsyncResult is loading
|
|
768
|
+
*/
|
|
386
769
|
anyLoading() {
|
|
387
770
|
for (const cacheItem of this._cache.values()) {
|
|
388
771
|
if (cacheItem.result.isLoading()) {
|
|
@@ -391,9 +774,38 @@ var KeyedAsyncCache = class {
|
|
|
391
774
|
}
|
|
392
775
|
return false;
|
|
393
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Clears the entire cache.
|
|
779
|
+
*/
|
|
394
780
|
clear() {
|
|
395
781
|
this._cache.clear();
|
|
396
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* Invalidates the cache entry for the given key.
|
|
785
|
+
* @param key the key of the cache entry to invalidate
|
|
786
|
+
*/
|
|
787
|
+
invalidateKey(key) {
|
|
788
|
+
if (this._cache.has(key)) {
|
|
789
|
+
const cacheItem = this._cache.get(key);
|
|
790
|
+
cacheItem.valid = false;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Invalidates the cache entry for the given parameters.
|
|
795
|
+
* @param params the parameters of the cache entry to invalidate
|
|
796
|
+
*/
|
|
797
|
+
invalidateParams(params) {
|
|
798
|
+
const key = this._paramsToKey(params);
|
|
799
|
+
this.invalidateKey(key);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Invalidates all cache entries.
|
|
803
|
+
*/
|
|
804
|
+
invalidateAll() {
|
|
805
|
+
for (const cacheItem of this._cache.values()) {
|
|
806
|
+
cacheItem.valid = false;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
397
809
|
};
|
|
398
810
|
export {
|
|
399
811
|
AsyncResult,
|