unwrapped 0.1.2 → 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.
@@ -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
- console.error(this.toString(), this.thrownError);
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
- static errTag(code, message) {
38
- return _Result.err(new ErrorBase(code, message));
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
- isSuccess() {
59
- return this._state.status === "success";
60
- }
61
- isError() {
62
- return this._state.status === "error";
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- set state(newState) {
109
- this._state = newState;
110
- this._listeners.forEach((listener) => listener(this));
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
- listen(listener, immediate = true) {
128
- this._listeners.add(listener);
129
- if (immediate) {
130
- listener(this);
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
- listenUntilSettled(listener, immediate = true) {
137
- const unsub = this.listen((result) => {
138
- listener(result);
139
- if (result.state.status === "success" || result.state.status === "error") {
140
- unsub();
141
- }
142
- }, immediate);
143
- return unsub;
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
- copyOnceSettled(other) {
149
- this.updateFromResultPromise(other.toResultPromise());
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
- update(newState) {
152
- this.state = newState;
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
- updateToValue(value) {
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
- updateToError(error) {
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
- updateFromResultPromise(promise) {
161
- this.state = { status: "loading", promise };
162
- promise.then((res) => {
163
- this.state = res.state;
164
- }).catch((error) => {
165
- this.state = { status: "error", error };
166
- });
167
- }
168
- static fromResultPromise(promise) {
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.updateFromResultPromise(promise);
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
- async waitForSettledResult() {
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
- async toResultPromise() {
211
- if (this._state.status === "idle") {
212
- throw new Error("Cannot convert idle AsyncResult to ResultState");
213
- }
214
- if (this._state.status === "loading") {
215
- try {
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
- unwrapOrNull() {
233
- if (this._state.status === "success") {
234
- return this._state.value;
235
- }
236
- return null;
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
- async unwrapOrNullOnceSettled() {
239
- return (await this.waitForSettled()).unwrapOrNull();
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
- unwrapOrThrow() {
242
- if (this._state.status === "success") {
243
- return this._state.value;
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
- throw new Error("Tried to unwrap an AsyncResult that is not successful");
462
+ return () => {
463
+ this._listeners.delete(listener);
464
+ };
246
465
  }
247
- async unwrapOrThrowOnceSettled() {
248
- return (await this.waitForSettled()).unwrapOrThrow();
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.waitForSettledResult();
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.waitForSettledResult();
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
- mirror(other) {
278
- return other.listen((newState) => {
279
- this.setState(newState.state);
280
- }, true);
281
- }
282
- mirrorUntilSettled(other) {
283
- return other.listenUntilSettled((newState) => {
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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.waitForSettledResult();
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- constructor(fetcher, paramsToKey = defaultParamsToKey) {
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
- makeCacheItem(result, fetcherParams) {
355
- return { result, fetcherParams };
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 cacheItem = this._cache.get(key);
370
- if (!this.shouldRefetch(cacheItem, refetch)) {
371
- return cacheItem.result;
741
+ const cacheItem2 = this._cache.get(key);
742
+ if (!this.shouldRefetch(cacheItem2, refetch)) {
743
+ return cacheItem2.result;
372
744
  } else {
373
- cacheItem.result.updateFromResultPromise(Promise.resolve(this._fetcher(cacheItem.fetcherParams)));
374
- return cacheItem.result;
745
+ this.updateOrCreateCacheItemFromParams(params, cacheItem2);
746
+ return cacheItem2.result;
375
747
  }
376
748
  }
377
- const asyncResult = AsyncResult.fromResultPromise(Promise.resolve(this._fetcher(params)));
378
- this._cache.set(key, this.makeCacheItem(asyncResult, params));
379
- return asyncResult;
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,