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