@opencode/util 2.0.4 → 2.0.6

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/dist/retry.js CHANGED
@@ -8,12 +8,19 @@ const transientMessages = [
8
8
  "etimedout",
9
9
  "socket hang up",
10
10
  ];
11
- function isTransientError(error) {
11
+ // Generated clients wrap fetch failures as ClientError("Transport", { cause }) and Bun/Node put the
12
+ // network detail in `code`, so the transient signal may sit anywhere along the cause chain.
13
+ function isTransientError(error, depth = 0) {
12
14
  if (!error)
13
15
  return false;
16
+ const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "";
14
17
  // oxlint-disable-next-line no-base-to-string -- Error input is intentionally normalized for message matching.
15
- const message = String(error instanceof Error ? error.message : error).toLowerCase();
16
- return transientMessages.some((item) => message.includes(item));
18
+ const message = `${String(error instanceof Error ? error.message : error)} ${code}`.toLowerCase();
19
+ if (transientMessages.some((item) => message.includes(item)))
20
+ return true;
21
+ if (!(error instanceof Error) || depth >= 5)
22
+ return false;
23
+ return isTransientError(error.cause, depth + 1);
17
24
  }
18
25
  export async function retry(operation, options = {}) {
19
26
  const attempts = options.attempts ?? 3;
@@ -12,6 +12,38 @@ describe("client retry", () => {
12
12
  expect(value).toBe("ready");
13
13
  expect(failures).toHaveLength(3);
14
14
  });
15
+ test("retries a real network failure wrapped as the cause of a transport error", async () => {
16
+ const server = Bun.listen({
17
+ hostname: "127.0.0.1",
18
+ port: 0,
19
+ socket: {
20
+ open(socket) {
21
+ socket.terminate();
22
+ },
23
+ data() { },
24
+ },
25
+ });
26
+ const failures = [];
27
+ try {
28
+ await expect(retry(() => fetch(`http://127.0.0.1:${server.port}/api/project`).catch((cause) => {
29
+ failures.push(cause);
30
+ throw new Error("Transport", { cause });
31
+ }), { delay: 0 })).rejects.toThrow("Transport");
32
+ }
33
+ finally {
34
+ server.stop(true);
35
+ }
36
+ expect(failures).toHaveLength(3);
37
+ expect(failures.every((cause) => cause instanceof TypeError)).toBe(true);
38
+ });
39
+ test("does not retry a transport error whose cause is not transient", async () => {
40
+ const failures = [];
41
+ await expect(retry(async () => {
42
+ failures.push("attempt");
43
+ throw new Error("Transport", { cause: new Error("invalid response") });
44
+ })).rejects.toThrow("Transport");
45
+ expect(failures).toHaveLength(1);
46
+ });
15
47
  test("does not retry other failures", async () => {
16
48
  const failures = [];
17
49
  await expect(retry(async () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/util",
4
- "version": "2.0.4",
4
+ "version": "2.0.6",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {