@swan-io/lake 15.1.1 → 15.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Future, Result } from "@swan-io/boxed";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { pollUntilOk } from "../polling";
|
|
4
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
5
|
+
describe("pollUntilOk", () => {
|
|
6
|
+
it("resolves Ok and stops after the first successful attempt", async () => {
|
|
7
|
+
let attempts = 0;
|
|
8
|
+
const result = await pollUntilOk(() => {
|
|
9
|
+
attempts += 1;
|
|
10
|
+
return Future.value(Result.Ok("ok"));
|
|
11
|
+
}, { maxAttempts: 5, intervalMs: 1 }).toPromise();
|
|
12
|
+
expect(result.isOk()).toBe(true);
|
|
13
|
+
expect(result.match({ Ok: value => value, Error: () => "" })).toBe("ok");
|
|
14
|
+
expect(attempts).toBe(1);
|
|
15
|
+
});
|
|
16
|
+
it("retries while the attempt returns Error, then resolves Ok", async () => {
|
|
17
|
+
let attempts = 0;
|
|
18
|
+
const result = await pollUntilOk(() => {
|
|
19
|
+
attempts += 1;
|
|
20
|
+
return attempts < 3
|
|
21
|
+
? Future.value(Result.Error("not ready"))
|
|
22
|
+
: Future.value(Result.Ok("ok"));
|
|
23
|
+
}, { maxAttempts: 5, intervalMs: 1 }).toPromise();
|
|
24
|
+
expect(result.isOk()).toBe(true);
|
|
25
|
+
expect(attempts).toBe(3);
|
|
26
|
+
});
|
|
27
|
+
it("gives up after maxAttempts and resolves with the last error", async () => {
|
|
28
|
+
let attempts = 0;
|
|
29
|
+
const result = await pollUntilOk(() => {
|
|
30
|
+
attempts += 1;
|
|
31
|
+
return Future.value(Result.Error(`error-${attempts}`));
|
|
32
|
+
}, { maxAttempts: 4, intervalMs: 1 }).toPromise();
|
|
33
|
+
expect(result.isError()).toBe(true);
|
|
34
|
+
expect(result.match({ Ok: () => "", Error: error => error })).toBe("error-4");
|
|
35
|
+
expect(attempts).toBe(4);
|
|
36
|
+
});
|
|
37
|
+
it("stops the loop when the returned future is cancelled", async () => {
|
|
38
|
+
let attempts = 0;
|
|
39
|
+
const future = pollUntilOk(() => {
|
|
40
|
+
attempts += 1;
|
|
41
|
+
return Future.value(Result.Error(undefined));
|
|
42
|
+
}, { maxAttempts: 5, intervalMs: 50 });
|
|
43
|
+
// Cancel during the first wait, before any attempt runs.
|
|
44
|
+
future.cancel();
|
|
45
|
+
// Wait well past when the first (and subsequent) attempts would have fired.
|
|
46
|
+
await sleep(120);
|
|
47
|
+
expect(attempts).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Future, Result } from "@swan-io/boxed";
|
|
2
|
+
type Options = {
|
|
3
|
+
maxAttempts: number;
|
|
4
|
+
/** Delay before each attempt, including the first. Defaults to 1000ms. */
|
|
5
|
+
intervalMs?: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Waits `intervalMs`, runs `attempt`, repeats while it returns `Error` (up to `maxAttempts`).
|
|
9
|
+
* Cancelling the returned future stops the loop — unlike `Future.retry`, which keeps polling.
|
|
10
|
+
*/
|
|
11
|
+
export declare const pollUntilOk: <Ok, Err>(attempt: () => Future<Result<Ok, Err>>, { maxAttempts, intervalMs }: Options) => Future<Result<Ok, Err>>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Future, Result } from "@swan-io/boxed";
|
|
2
|
+
/**
|
|
3
|
+
* Waits `intervalMs`, runs `attempt`, repeats while it returns `Error` (up to `maxAttempts`).
|
|
4
|
+
* Cancelling the returned future stops the loop — unlike `Future.retry`, which keeps polling.
|
|
5
|
+
*/
|
|
6
|
+
export const pollUntilOk = (attempt, { maxAttempts, intervalMs = 1000 }) => {
|
|
7
|
+
let isStopped = false;
|
|
8
|
+
let pendingWait;
|
|
9
|
+
const loop = (attemptsLeft) => {
|
|
10
|
+
const wait = Future.wait(intervalMs);
|
|
11
|
+
pendingWait = wait;
|
|
12
|
+
return wait.flatMap(() => attempt().flatMapError(error => attemptsLeft > 1 && !isStopped ? loop(attemptsLeft - 1) : Future.value(Result.Error(error))));
|
|
13
|
+
};
|
|
14
|
+
const future = loop(maxAttempts);
|
|
15
|
+
future.onCancel(() => {
|
|
16
|
+
isStopped = true;
|
|
17
|
+
pendingWait === null || pendingWait === void 0 ? void 0 : pendingWait.cancel();
|
|
18
|
+
});
|
|
19
|
+
return future;
|
|
20
|
+
};
|