runtry 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,8 @@ Run async functions and return a typed `Result` **instead of throwing**.
5
5
  - ✅ No repetitive `try/catch` in UI code
6
6
  - ✅ Typed success/error handling
7
7
  - ✅ Pluggable error normalization (matchers / adapters)
8
+ - ✅ **Automatic Retries** with backoff & jitter
9
+ - ✅ **Concurrency control** with `runAll`
8
10
 
9
11
  ---
10
12
 
@@ -52,9 +54,50 @@ declare function run<T, E extends AppError = AppError>(
52
54
  ): Promise<RunResult<T, E>>;
53
55
  ```
54
56
 
55
- - Returns `{ ok: true, data, error: null }` on success
56
- - Returns `{ ok: false, data: null, error }` on failure
57
- - Never throws (unless your callbacks throw)
57
+ #### Options
58
+
59
+ - `toError`: Custom error normalizer.
60
+ - `onError`: Callback for final failure.
61
+ - `onSuccess`: Callback for success.
62
+ - `onFinally`: Callback after completion (success or fail).
63
+ - `ignoreAbort`: Don't trigger onError for Abort/Cancellation (default: true).
64
+
65
+ #### Retries
66
+
67
+ You can configure automatic retries for failed operations:
68
+
69
+ ```ts
70
+ await run(fetchData, {
71
+ retries: 3,
72
+ retryDelay: 1000,
73
+ retryBackoff: "exponential", // "fixed" | "linear" | "exponential"
74
+ jitter: true, // adds randomization to delay
75
+ shouldRetry: (err) => err.status === 503, // optional filter
76
+ });
77
+ ```
78
+
79
+ ---
80
+
81
+ ### `runAll(tasks, options?)`
82
+
83
+ Executes multiple tasks with **concurrency control**.
84
+
85
+ ```ts
86
+ import { runAll } from "runtry";
87
+
88
+ const tasks = [
89
+ () => fetch("/api/1"),
90
+ () => fetch("/api/2"),
91
+ () => fetch("/api/3"),
92
+ ];
93
+
94
+ // Run max 2 at a time
95
+ const results = await runAll(tasks, {
96
+ concurrency: 2,
97
+ onSettled: (res, index) => console.log(`Task ${index} done`),
98
+ retries: 2, // Inherits all run() options including retries!
99
+ });
100
+ ```
58
101
 
59
102
  ---
60
103
 
@@ -2,5 +2,9 @@ import type { AppError } from "./types";
2
2
  import type { Matcher } from "./normalize";
3
3
  export declare const abortMatcher: Matcher;
4
4
  export declare const timeoutMatcher: Matcher;
5
+ export declare const messageMatcher: Matcher;
6
+ export declare const stringMatcher: Matcher;
7
+ export declare const statusMatcher: Matcher;
8
+ export declare const aggregateMatcher: Matcher;
5
9
  export declare function matchInstance<T extends Error, Meta = unknown>(ErrorCtor: new (...args: any[]) => T, map: (e: T) => AppError<Meta>): Matcher<AppError<Meta>>;
6
10
  //# sourceMappingURL=matchers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/error/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,eAAO,MAAM,YAAY,EAAE,OAK1B,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,OAa5B,CAAC;AAGF,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,GAAG,OAAO,EAC3D,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EACpC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAEzB"}
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/error/matchers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,eAAO,MAAM,YAAY,EAAE,OAK1B,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,OAa5B,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,OAc5B,CAAC;AAGF,eAAO,MAAM,aAAa,EAAE,OAS3B,CAAC;AAGF,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,OAU9B,CAAC;AAGF,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,GAAG,OAAO,EAC3D,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EACpC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAEzB"}
@@ -20,6 +20,59 @@ export const timeoutMatcher = (err) => {
20
20
  }
21
21
  return null;
22
22
  };
23
+ // Matcher para objetos con propiedad "message" (ej: { message: "Error X" })
24
+ export const messageMatcher = (err) => {
25
+ if (typeof err === "object" &&
26
+ err !== null &&
27
+ "message" in err &&
28
+ typeof err.message === "string") {
29
+ return {
30
+ code: "UNKNOWN",
31
+ message: err.message,
32
+ cause: err,
33
+ };
34
+ }
35
+ return null;
36
+ };
37
+ // Matcher para errores lanzados como string literal (ej: throw "Error X")
38
+ export const stringMatcher = (err) => {
39
+ if (typeof err === "string") {
40
+ return {
41
+ code: "UNKNOWN",
42
+ message: err,
43
+ cause: err,
44
+ };
45
+ }
46
+ return null;
47
+ };
48
+ // Matcher para errores con status/statusCode (común en clientes HTTP y APIs)
49
+ export const statusMatcher = (err) => {
50
+ if (typeof err === "object" && err !== null) {
51
+ // Comprobamos status o statusCode
52
+ const status = err.status ?? err.statusCode;
53
+ if (typeof status === "number") {
54
+ return {
55
+ code: "HTTP",
56
+ message: err.message || `HTTP Error ${status}`,
57
+ status,
58
+ cause: err,
59
+ };
60
+ }
61
+ }
62
+ return null;
63
+ };
64
+ // Matcher para AggregateError (Promise.any, etc)
65
+ export const aggregateMatcher = (err) => {
66
+ if (typeof AggregateError !== "undefined" && err instanceof AggregateError) {
67
+ return {
68
+ code: "UNKNOWN",
69
+ message: err.message || "Multiple errors occurred",
70
+ cause: err,
71
+ meta: { errors: err.errors },
72
+ };
73
+ }
74
+ return null;
75
+ };
23
76
  // Helper para crear matcher basado en "instanceof" (plugin-friendly)
24
77
  export function matchInstance(ErrorCtor, map) {
25
78
  return (err) => (err instanceof ErrorCtor ? map(err) : null);
@@ -1 +1 @@
1
- {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../../src/error/matchers.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAY,CAAC,GAAG,EAAE,EAAE;IAC3C,IAAI,GAAG,YAAY,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACvE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,wFAAwF;AACxF,MAAM,CAAC,MAAM,cAAc,GAAY,CAAC,GAAG,EAAE,EAAE;IAC7C,IAAI,GAAG,YAAY,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAC/D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACvE,CAAC;IACD,qDAAqD;IACrD,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACxD,OAAO;YACL,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,mBAAmB;YAC3C,KAAK,EAAE,GAAG;SACX,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAC3B,SAAoC,EACpC,GAA6B;IAE7B,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,YAAY,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../../src/error/matchers.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { run } from "./run";
2
- export type { RunOptions, RunResult } from "./types";
2
+ export { runAll } from "./runAll";
3
+ export type { RunAllOptions } from "./runAll";
4
+ export type { RunOptions, RunResult, RetryOptions } from "./types";
3
5
  export type { AppError, AppErrorCode } from "./error/types";
4
6
  export { toAppError, defaultFallback, createNormalizer, } from "./error/normalize";
5
- export { abortMatcher, timeoutMatcher, matchInstance } from "./error/matchers";
7
+ export { abortMatcher, timeoutMatcher, matchInstance, messageMatcher, stringMatcher, statusMatcher, aggregateMatcher, } from "./error/matchers";
6
8
  export { createClient } from "./client";
7
9
  export type { CreateClientOptions } from "./client";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE/E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACL,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { run } from "./run";
2
+ export { runAll } from "./runAll";
2
3
  export { toAppError, defaultFallback, createNormalizer, } from "./error/normalize";
3
- export { abortMatcher, timeoutMatcher, matchInstance } from "./error/matchers";
4
+ export { abortMatcher, timeoutMatcher, matchInstance, messageMatcher, stringMatcher, statusMatcher, aggregateMatcher, } from "./error/matchers";
4
5
  export { createClient } from "./client";
5
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAI5B,OAAO,EACL,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE/E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKlC,OAAO,EACL,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
package/dist/run.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAsB,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACxD,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAM,GAC7B,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CA4B1B"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAsB,GAAG,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACxD,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAM,GAC7B,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CA4D1B"}
package/dist/run.js CHANGED
@@ -9,25 +9,49 @@ import { toAppError as defaultToAppError } from "./error/normalize";
9
9
  * React effects, and any async context.
10
10
  */
11
11
  export async function run(fn, options = {}) {
12
- const { toError = defaultToAppError, mapError, onError, onSuccess, onFinally, ignoreAbort = true, } = options;
13
- try {
14
- const data = await fn();
15
- onSuccess?.(data);
16
- return { ok: true, data, error: null };
17
- }
18
- catch (e) {
19
- let err = toError(e);
20
- if (mapError)
21
- err = mapError(err);
22
- if (ignoreAbort && err.code === "ABORTED") {
23
- // decisión de diseño: devolvemos ok:false pero sin llamar onError (para no toastear)
12
+ const { toError = defaultToAppError, mapError, onError, onSuccess, onFinally, ignoreAbort = true, retries = 0, retryDelay = 0, retryBackoff = "fixed", shouldRetry = () => true, jitter = false, } = options;
13
+ let attempt = 0;
14
+ while (true) {
15
+ try {
16
+ const data = await fn();
17
+ onSuccess?.(data);
18
+ onFinally?.(); // Always call finally on success (last attempt)
19
+ return { ok: true, data, error: null };
20
+ }
21
+ catch (e) {
22
+ let err = toError(e);
23
+ if (mapError)
24
+ err = mapError(err);
25
+ // Abort is critical: stop immediately, no retries
26
+ const isAborted = err.code === "ABORTED";
27
+ if (isAborted) {
28
+ if (ignoreAbort) {
29
+ onFinally?.();
30
+ return { ok: false, data: null, error: err };
31
+ }
32
+ // If not ignoring abort, we fall through to onError and return
33
+ }
34
+ else if (attempt < retries && shouldRetry(err)) {
35
+ attempt++;
36
+ let delay = retryDelay;
37
+ if (retryBackoff === "linear")
38
+ delay *= attempt;
39
+ if (retryBackoff === "exponential")
40
+ delay *= Math.pow(2, attempt - 1); // 1st retry: 2^0=1x, 2nd: 2^1=2x
41
+ if (jitter) {
42
+ delay = delay * (0.5 + Math.random()); // +/- 50% randomization or similar
43
+ }
44
+ // Wait before retrying
45
+ if (delay > 0) {
46
+ await new Promise((resolve) => setTimeout(resolve, delay));
47
+ }
48
+ continue; // Retry loop
49
+ }
50
+ // Final failure or aborted
51
+ onError?.(err);
52
+ onFinally?.();
24
53
  return { ok: false, data: null, error: err };
25
54
  }
26
- onError?.(err);
27
- return { ok: false, data: null, error: err };
28
- }
29
- finally {
30
- onFinally?.();
31
55
  }
32
56
  }
33
57
  //# sourceMappingURL=run.js.map
package/dist/run.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGpE;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,EAAoB,EACpB,UAA4B,EAAE;IAE9B,MAAM,EACJ,OAAO,GAAG,iBAAmD,EAC7D,QAAQ,EACR,OAAO,EACP,SAAS,EACT,SAAS,EACT,WAAW,GAAG,IAAI,GACnB,GAAG,OAAO,CAAC;IAEZ,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,CAAC;QACxB,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,QAAQ;YAAE,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,WAAW,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC1C,qFAAqF;YACrF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC/C,CAAC;QAED,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAC/C,CAAC;YAAS,CAAC;QACT,SAAS,EAAE,EAAE,CAAC;IAChB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGpE;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,EAAoB,EACpB,UAA4B,EAAE;IAE9B,MAAM,EACJ,OAAO,GAAG,iBAAmD,EAC7D,QAAQ,EACR,OAAO,EACP,SAAS,EACT,SAAS,EACT,WAAW,GAAG,IAAI,EAClB,OAAO,GAAG,CAAC,EACX,UAAU,GAAG,CAAC,EACd,YAAY,GAAG,OAAO,EACtB,WAAW,GAAG,GAAG,EAAE,CAAC,IAAI,EACxB,MAAM,GAAG,KAAK,GACf,GAAG,OAAO,CAAC;IAEZ,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,CAAC;YACxB,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC;YAClB,SAAS,EAAE,EAAE,CAAC,CAAC,gDAAgD;YAC/D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACrB,IAAI,QAAQ;gBAAE,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAElC,kDAAkD;YAClD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC;YAEzC,IAAI,SAAS,EAAE,CAAC;gBACb,IAAI,WAAW,EAAE,CAAC;oBACf,SAAS,EAAE,EAAE,CAAC;oBACd,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;gBAChD,CAAC;gBACD,+DAA+D;YAClE,CAAC;iBAAM,IAAI,OAAO,GAAG,OAAO,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjD,OAAO,EAAE,CAAC;gBAEV,IAAI,KAAK,GAAG,UAAU,CAAC;gBACvB,IAAI,YAAY,KAAK,QAAQ;oBAAE,KAAK,IAAI,OAAO,CAAC;gBAChD,IAAI,YAAY,KAAK,aAAa;oBAAE,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,iCAAiC;gBAExG,IAAI,MAAM,EAAE,CAAC;oBACV,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,mCAAmC;gBAC7E,CAAC;gBAED,uBAAuB;gBACvB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;oBACZ,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC/D,CAAC;gBACD,SAAS,CAAC,aAAa;YACzB,CAAC;YAED,2BAA2B;YAC3B,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;YACf,SAAS,EAAE,EAAE,CAAC;YACd,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAC/C,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { AppError } from "./error/types";
2
+ import type { RunOptions, RunResult } from "./types";
3
+ export type RunAllOptions<T, E extends AppError = AppError> = RunOptions<T, E> & {
4
+ /**
5
+ * Maximum number of concurrent tasks to run.
6
+ * @default Infinity
7
+ */
8
+ concurrency?: number;
9
+ /**
10
+ * Called after each task finishes (success or error).
11
+ */
12
+ onSettled?: (result: RunResult<T, E>, index: number) => void;
13
+ };
14
+ export declare function runAll<T, E extends AppError = AppError>(tasks: Array<() => Promise<T>>, options?: RunAllOptions<T, E>): Promise<RunResult<T, E>[]>;
15
+ //# sourceMappingURL=runAll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runAll.d.ts","sourceRoot":"","sources":["../src/runAll.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,UAAU,CACtE,CAAC,EACD,CAAC,CACF,GAAG;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF,wBAAsB,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAC3D,KAAK,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,EAC9B,OAAO,GAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAM,GAChC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAuC5B"}
package/dist/runAll.js ADDED
@@ -0,0 +1,33 @@
1
+ import { run } from "./run";
2
+ export async function runAll(tasks, options = {}) {
3
+ const { concurrency = Infinity, onSettled, ...runOptions } = options;
4
+ if (tasks.length === 0)
5
+ return [];
6
+ const limit = Number.isFinite(concurrency)
7
+ ? Math.max(1, Math.floor(concurrency))
8
+ : Infinity;
9
+ // Run all in parallel if unlimited or limit >= count
10
+ if (limit >= tasks.length) {
11
+ const results = await Promise.all(tasks.map((t) => run(t, runOptions)));
12
+ results.forEach((r, i) => onSettled?.(r, i));
13
+ return results;
14
+ }
15
+ const results = new Array(tasks.length);
16
+ let nextIndex = 0;
17
+ const worker = async () => {
18
+ while (true) {
19
+ const i = nextIndex++;
20
+ if (i >= tasks.length)
21
+ return;
22
+ const task = tasks[i];
23
+ if (!task)
24
+ continue;
25
+ const res = await run(task, runOptions);
26
+ results[i] = res;
27
+ onSettled?.(res, i);
28
+ }
29
+ };
30
+ await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, worker));
31
+ return results;
32
+ }
33
+ //# sourceMappingURL=runAll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runAll.js","sourceRoot":"","sources":["../src/runAll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAoB5B,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,KAA8B,EAC9B,UAA+B,EAAE;IAEjC,MAAM,EAAE,WAAW,GAAG,QAAQ,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE,GAAG,OAAO,CAAC;IAErE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC,CAAC,QAAQ,CAAC;IAEb,qDAAqD;IACrD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAC3C,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,OAAO,GAAsB,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE;QACxB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO;YAE9B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAO,IAAI,EAAE,UAAU,CAAC,CAAC;YAC9C,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACjB,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAC9D,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/types.d.ts CHANGED
@@ -1,5 +1,36 @@
1
1
  import type { AppError } from "./error/types";
2
- export type RunOptions<T, E extends AppError = AppError> = {
2
+ export type RetryOptions<E extends AppError = AppError> = {
3
+ /**
4
+ * Number of retries to attempt.
5
+ * @default 0
6
+ */
7
+ retries?: number;
8
+ /**
9
+ * Delay in milliseconds between retries.
10
+ * @default 0
11
+ */
12
+ retryDelay?: number;
13
+ /**
14
+ * Type of backoff to apply to the delay.
15
+ * - "fixed": Always waits `retryDelay`.
16
+ * - "linear": Waits `retryDelay * attempt`.
17
+ * - "exponential": Waits `retryDelay * (2 ** attempt)`.
18
+ * @default "fixed"
19
+ */
20
+ retryBackoff?: "fixed" | "linear" | "exponential";
21
+ /**
22
+ * Function to determine if a retry should be attempted.
23
+ * Returns true to retry, false to stop.
24
+ * @default () => true
25
+ */
26
+ shouldRetry?: (error: E) => boolean;
27
+ /**
28
+ * Adds random jitter to the delay to prevent thundering herd.
29
+ * @default false
30
+ */
31
+ jitter?: boolean;
32
+ };
33
+ export type RunOptions<T, E extends AppError = AppError> = RetryOptions<E> & {
3
34
  /**
4
35
  * Converts an unknown thrown value into your normalized error type `E`.
5
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IACzD;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC;IAE9B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;IAE3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE7B;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAE9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAClD;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IACxD;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,CAAC;IAElD;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAEpC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG;IAC3E;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC;IAE9B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;IAE3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE7B;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAE9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAClD;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtry",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Run async functions and return a typed Result instead of throwing.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",