ccqa 1.37.0 → 1.38.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.
@@ -1239,6 +1239,15 @@ interface HubClient {
1239
1239
  }): Promise<void>;
1240
1240
  deletePerspectives(project: string): Promise<void>;
1241
1241
  }
1242
+ /**
1243
+ * One round trip against a hub, with the client's shared policy: bearer auth,
1244
+ * per-attempt timeout, retries for idempotent methods, `HubApiError` on the
1245
+ * final non-ok answer. Exported for the one caller outside the client
1246
+ * (`CoverageInbox`), whose POSTs are appends a duplicate cannot corrupt —
1247
+ * unlike the client's own POSTs — so it may opt into `retry: "post-once"`,
1248
+ * one extra attempt after a 5xx or network error.
1249
+ */
1250
+ declare function hubRequest(opts: HubClientOptions, path: string, init?: RequestInit, retry?: "post-once"): Promise<Response>;
1242
1251
  declare function createHubClient(opts: HubClientOptions): HubClient;
1243
1252
  //#endregion
1244
- export { HubApiError, HubClient, HubClientOptions, HubPromptMeta, HubVariable, PatchRunRequest, createHubClient };
1253
+ export { HubApiError, HubClient, HubClientOptions, HubPromptMeta, HubVariable, PatchRunRequest, createHubClient, hubRequest };
@@ -27,50 +27,61 @@ const RETRY_BACKOFF_MS = [
27
27
  function sleep(ms) {
28
28
  return new Promise((resolve) => setTimeout(resolve, ms));
29
29
  }
30
- function createHubClient(opts) {
30
+ async function throwHubApiError(res) {
31
+ let code = "unknown_error";
32
+ let message = res.statusText;
33
+ try {
34
+ const body = await res.json();
35
+ if (body.error?.code) code = body.error.code;
36
+ if (body.error?.message) message = body.error.message;
37
+ } catch {}
38
+ throw new HubApiError(res.status, code, message);
39
+ }
40
+ /**
41
+ * One round trip against a hub, with the client's shared policy: bearer auth,
42
+ * per-attempt timeout, retries for idempotent methods, `HubApiError` on the
43
+ * final non-ok answer. Exported for the one caller outside the client
44
+ * (`CoverageInbox`), whose POSTs are appends a duplicate cannot corrupt —
45
+ * unlike the client's own POSTs — so it may opt into `retry: "post-once"`,
46
+ * one extra attempt after a 5xx or network error.
47
+ */
48
+ async function hubRequest(opts, path, init = {}, retry) {
31
49
  const baseUrl = opts.baseUrl.replace(/\/+$/, "");
32
50
  const doFetch = opts.fetchImpl ?? fetch;
33
- async function throwHubApiError(res) {
34
- let code = "unknown_error";
35
- let message = res.statusText;
51
+ const method = (init.method ?? "GET").toUpperCase();
52
+ const maxAttempts = retry === "post-once" ? 2 : RETRYABLE_METHODS.has(method) ? RETRY_BACKOFF_MS.length + 1 : 1;
53
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
54
+ const signal = init.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS);
55
+ let res;
36
56
  try {
37
- const body = await res.json();
38
- if (body.error?.code) code = body.error.code;
39
- if (body.error?.message) message = body.error.message;
40
- } catch {}
41
- throw new HubApiError(res.status, code, message);
42
- }
43
- async function request(path, init = {}) {
44
- const method = (init.method ?? "GET").toUpperCase();
45
- const maxAttempts = RETRYABLE_METHODS.has(method) ? RETRY_BACKOFF_MS.length + 1 : 1;
46
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
47
- const signal = init.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS);
48
- let res;
49
- try {
50
- res = await doFetch(`${baseUrl}${path}`, {
51
- ...init,
52
- signal,
53
- headers: {
54
- ...opts.headers,
55
- ...init.headers,
56
- Authorization: `Bearer ${opts.token}`
57
- }
58
- });
59
- } catch (err) {
60
- if (attempt < maxAttempts - 1) {
61
- await sleep(RETRY_BACKOFF_MS[attempt]);
62
- continue;
57
+ res = await doFetch(`${baseUrl}${path}`, {
58
+ ...init,
59
+ signal,
60
+ headers: {
61
+ ...opts.headers,
62
+ ...init.headers,
63
+ Authorization: `Bearer ${opts.token}`
63
64
  }
64
- throw err;
65
- }
66
- if (res.ok) return res;
67
- if (res.status >= 500 && attempt < maxAttempts - 1) {
65
+ });
66
+ } catch (err) {
67
+ if (attempt < maxAttempts - 1) {
68
68
  await sleep(RETRY_BACKOFF_MS[attempt]);
69
69
  continue;
70
70
  }
71
- return throwHubApiError(res);
71
+ throw err;
72
+ }
73
+ if (res.ok) return res;
74
+ if (res.status >= 500 && attempt < maxAttempts - 1) {
75
+ await sleep(RETRY_BACKOFF_MS[attempt]);
76
+ continue;
72
77
  }
73
- throw new Error("unreachable");
78
+ return throwHubApiError(res);
79
+ }
80
+ throw new Error("unreachable");
81
+ }
82
+ function createHubClient(opts) {
83
+ function request(path, init = {}) {
84
+ return hubRequest(opts, path, init);
74
85
  }
75
86
  async function json(path, init) {
76
87
  return (await request(path, init)).json();
@@ -363,4 +374,4 @@ function toBodyInit(bytes) {
363
374
  return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
364
375
  }
365
376
  //#endregion
366
- export { HubApiError, createHubClient };
377
+ export { HubApiError, createHubClient, hubRequest };
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.37.0",
3
+ "version": "1.38.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.37.0",
3
+ "version": "1.38.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {