@gusnips/react 0.3.0 → 0.4.1
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 +10 -2
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +69 -10
- package/dist/api-client.js.map +1 -1
- package/dist/api-error.d.ts +28 -0
- package/dist/api-error.d.ts.map +1 -1
- package/dist/api-error.js +51 -0
- package/dist/api-error.js.map +1 -1
- package/dist/describe-error.d.ts +6 -2
- package/dist/describe-error.d.ts.map +1 -1
- package/dist/describe-error.js +12 -14
- package/dist/describe-error.js.map +1 -1
- package/dist/hydrate.d.ts +5 -0
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -10
- package/dist/index.js.map +1 -1
- package/dist/query.d.ts +17 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +29 -4
- package/dist/query.js.map +1 -1
- package/package.json +8 -1
- package/src/api-client.test.ts +74 -6
- package/src/api-client.ts +73 -10
- package/src/api-error.test.ts +74 -0
- package/src/api-error.ts +57 -1
- package/src/chunk-reload.test.ts +69 -2
- package/src/describe-error.test.ts +37 -18
- package/src/describe-error.ts +19 -13
- package/src/hydrate.ts +5 -0
- package/src/index.ts +20 -10
- package/src/query.test.ts +53 -1
- package/src/query.ts +44 -3
package/README.md
CHANGED
|
@@ -128,14 +128,22 @@ hurry cannot skip.
|
|
|
128
128
|
|
|
129
129
|
## Also here
|
|
130
130
|
|
|
131
|
-
`
|
|
132
|
-
|
|
131
|
+
`ErrorBoundary`, an SSE reader split into a platform-free parser and a stream wrapper,
|
|
132
|
+
`i18nInitOptions`, and `cn`.
|
|
133
133
|
|
|
134
134
|
`createAuthStore` is at `@gusnips/react/store` — a zustand store whose `isLoading` starts `false`
|
|
135
135
|
where there is no window. A session bootstrap can only be in flight in a browser, and `true`
|
|
136
136
|
during a build is a wait that never ends: it once shipped a spinner as the indexable body of a
|
|
137
137
|
page whose whole job was to be found.
|
|
138
138
|
|
|
139
|
+
`hydrateOrMount` is at `@gusnips/react/hydrate`, and the guards are at `@gusnips/react/guards`.
|
|
140
|
+
|
|
141
|
+
## On a phone
|
|
142
|
+
|
|
143
|
+
The main entry works in React Native. It imports `react` and nothing else you would have to go
|
|
144
|
+
find — no `react-dom`, no router, no store — so the fetch client, the retry rule and the SSE
|
|
145
|
+
parser all come along, and the four things that need a browser stay behind the subpaths above.
|
|
146
|
+
|
|
139
147
|
## The Base UI wrappers
|
|
140
148
|
|
|
141
149
|
```ts
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/api-client.js
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
import { isApiError } from "@gusnips/http";
|
|
2
|
-
import { ApiError } from "./api-error.js";
|
|
2
|
+
import { ApiError, parseRetryAfter } from "./api-error.js";
|
|
3
3
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4
|
+
/**
|
|
5
|
+
* One signal that fires on the caller's abort or on the deadline, whichever comes first.
|
|
6
|
+
*
|
|
7
|
+
* `AbortSignal.timeout` plus `AbortSignal.any` is the obvious way to write this, and it is
|
|
8
|
+
* not portable: React Native replaces the global `AbortSignal` with abort-controller@3, which
|
|
9
|
+
* has neither static — so the phone got `AbortSignal.timeout is not a function` on its very
|
|
10
|
+
* first request. This is the same two rules with an `AbortController`, which every runtime
|
|
11
|
+
* this package targets does have, and it is the ONLY path rather than a fallback: one branch
|
|
12
|
+
* cannot drift from the other.
|
|
13
|
+
*
|
|
14
|
+
* Three details that are the whole reason this is not three lines:
|
|
15
|
+
*
|
|
16
|
+
* - **The already-aborted race.** A caller whose signal fired before we attached would never
|
|
17
|
+
* fire the listener, so the request would go out and run to full term. Checked first.
|
|
18
|
+
* - **A timeout must not look like a cancellation.** `isAbortError` exists so a request nobody
|
|
19
|
+
* is waiting for stays silent; a request that ran out of time is a real failure somebody
|
|
20
|
+
* should see. A bare `controller.abort()` raises an `AbortError` and would be swallowed, so
|
|
21
|
+
* the deadline aborts with a plain `Error` — not a `DOMException`, which `isAbortError` is
|
|
22
|
+
* the one thing that checks for.
|
|
23
|
+
* - **The timer is cleared.** Otherwise every completed request leaves a pending timeout for
|
|
24
|
+
* the rest of its budget, which on a phone is a wake-up nobody asked for.
|
|
25
|
+
*/
|
|
26
|
+
function withDeadline(ms, caller) {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
// Named, not typed: callers identify a deadline by `name === "TimeoutError"`, the same
|
|
30
|
+
// way `AbortSignal.timeout` reports it. A `DOMException` would be the faithful copy and is
|
|
31
|
+
// not constructible everywhere this runs.
|
|
32
|
+
const expired = new Error(`Request timed out after ${String(ms)}ms`);
|
|
33
|
+
expired.name = "TimeoutError";
|
|
34
|
+
controller.abort(expired);
|
|
35
|
+
}, ms);
|
|
36
|
+
let detach = () => { };
|
|
37
|
+
if (caller) {
|
|
38
|
+
if (caller.aborted) {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
controller.abort(caller.reason);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const onAbort = () => {
|
|
44
|
+
controller.abort(caller.reason);
|
|
45
|
+
};
|
|
46
|
+
caller.addEventListener("abort", onAbort, { once: true });
|
|
47
|
+
detach = () => {
|
|
48
|
+
caller.removeEventListener("abort", onAbort);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
clear: () => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
detach();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
4
60
|
export function createApiClient({ baseUrl, session, headers: extraHeaders, onSessionDead, onError, timeoutMs = 30_000, maxRefreshAttempts = 2, refreshRetryDelayMs = 500, signOutTimeoutMs = 3_000, requestIdHeader = "x-request-id", }) {
|
|
5
61
|
/**
|
|
6
62
|
* Single-flight refresh.
|
|
@@ -51,15 +107,17 @@ export function createApiClient({ baseUrl, session, headers: extraHeaders, onSes
|
|
|
51
107
|
const budget = perCall === undefined ? timeoutMs : perCall;
|
|
52
108
|
if (budget === null)
|
|
53
109
|
return fetch(`${baseUrl}${path}`, { ...init, signal, headers: buildHeaders(token, headers) });
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
const deadline = withDeadline(budget, signal);
|
|
111
|
+
try {
|
|
112
|
+
return await fetch(`${baseUrl}${path}`, {
|
|
113
|
+
...init,
|
|
114
|
+
signal: deadline.signal,
|
|
115
|
+
headers: buildHeaders(token, headers),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
deadline.clear();
|
|
120
|
+
}
|
|
63
121
|
}
|
|
64
122
|
async function request(path, options = {}) {
|
|
65
123
|
let res = await send(path, await session.getToken(), options);
|
|
@@ -86,6 +144,7 @@ export function createApiClient({ baseUrl, session, headers: extraHeaders, onSes
|
|
|
86
144
|
const body = await res.json().catch(() => null);
|
|
87
145
|
const error = new ApiError(res.status, isApiError(body) ? body.error : null, {
|
|
88
146
|
requestId: res.headers.get(requestIdHeader) ?? undefined,
|
|
147
|
+
retryAfterSecs: parseRetryAfter(res.headers.get("retry-after")),
|
|
89
148
|
});
|
|
90
149
|
// A listener that throws must not become the error the caller sees: the API failure is
|
|
91
150
|
// the real news, and swallowing it for a bug in a side effect would send everyone
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAmB,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA+
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/api-error.d.ts
CHANGED
|
@@ -17,6 +17,17 @@ export declare class ApiError extends Error {
|
|
|
17
17
|
readonly params: Record<string, string | number> | undefined;
|
|
18
18
|
readonly details: unknown;
|
|
19
19
|
readonly requestId: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Seconds until this refusal clears, when the answer said so.
|
|
22
|
+
*
|
|
23
|
+
* Read from the `Retry-After` HEADER, which is where HTTP puts it and where a rate limiter
|
|
24
|
+
* written by anyone puts it. The second migration is why this exists: that API sends
|
|
25
|
+
* `Retry-After: 60` on every 429 and nothing in its body, so the package's only reader —
|
|
26
|
+
* which looked in `details.retryAfterSecs`, the first donor's convention — found nothing, and
|
|
27
|
+
* both the copy and the retry rule were flying blind on the one refusal that states its own
|
|
28
|
+
* expiry. Two conventions, same fact; {@link retryAfterSecs} reads either.
|
|
29
|
+
*/
|
|
30
|
+
readonly retryAfterSecs: number | undefined;
|
|
20
31
|
/**
|
|
21
32
|
* The client raised this itself to stop a caller while it was already handling the
|
|
22
33
|
* situation — today, the 401 thrown after a dead session, where the person is already on
|
|
@@ -31,8 +42,25 @@ export declare class ApiError extends Error {
|
|
|
31
42
|
requestId?: string;
|
|
32
43
|
expected?: boolean;
|
|
33
44
|
message?: string;
|
|
45
|
+
retryAfterSecs?: number;
|
|
34
46
|
});
|
|
35
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Seconds until a refusal clears, from whichever place the API put them.
|
|
50
|
+
*
|
|
51
|
+
* The header first, because it is the standard one and it is what a limiter emits without being
|
|
52
|
+
* asked; then `details.retryAfterSecs`, which one donor's API sends in the body instead. A
|
|
53
|
+
* caller asking "how long" should not have to know which server it is talking to.
|
|
54
|
+
*/
|
|
55
|
+
export declare function retryAfterSecs(error: ApiError): number | null;
|
|
56
|
+
/**
|
|
57
|
+
* `Retry-After` in either spec form: delay-seconds, or an HTTP-date.
|
|
58
|
+
*
|
|
59
|
+
* Both forms are real — RFC 9110 allows either and different servers pick differently — and the
|
|
60
|
+
* date one is the half that gets skipped, which turns a stated wait into no wait at all. Clamped
|
|
61
|
+
* at zero: a date already in the past means "now", not a negative delay.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseRetryAfter(value: string | null | undefined): number | undefined;
|
|
36
64
|
/**
|
|
37
65
|
* An aborted request — the caller's own `AbortController`, or a navigation that unmounted the
|
|
38
66
|
* component waiting on it.
|
package/dist/api-error.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../src/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../src/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC;;;;;;;;;OASG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAE5C;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;gBAGzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,EAClC,IAAI,GAAE;QACJ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;KACpB;CAaT;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAM7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAepF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEpD"}
|
package/dist/api-error.js
CHANGED
|
@@ -16,6 +16,17 @@ export class ApiError extends Error {
|
|
|
16
16
|
params;
|
|
17
17
|
details;
|
|
18
18
|
requestId;
|
|
19
|
+
/**
|
|
20
|
+
* Seconds until this refusal clears, when the answer said so.
|
|
21
|
+
*
|
|
22
|
+
* Read from the `Retry-After` HEADER, which is where HTTP puts it and where a rate limiter
|
|
23
|
+
* written by anyone puts it. The second migration is why this exists: that API sends
|
|
24
|
+
* `Retry-After: 60` on every 429 and nothing in its body, so the package's only reader —
|
|
25
|
+
* which looked in `details.retryAfterSecs`, the first donor's convention — found nothing, and
|
|
26
|
+
* both the copy and the retry rule were flying blind on the one refusal that states its own
|
|
27
|
+
* expiry. Two conventions, same fact; {@link retryAfterSecs} reads either.
|
|
28
|
+
*/
|
|
29
|
+
retryAfterSecs;
|
|
19
30
|
/**
|
|
20
31
|
* The client raised this itself to stop a caller while it was already handling the
|
|
21
32
|
* situation — today, the 401 thrown after a dead session, where the person is already on
|
|
@@ -35,9 +46,49 @@ export class ApiError extends Error {
|
|
|
35
46
|
this.params = body?.params;
|
|
36
47
|
this.details = body?.details;
|
|
37
48
|
this.requestId = opts.requestId;
|
|
49
|
+
this.retryAfterSecs = opts.retryAfterSecs;
|
|
38
50
|
this.expected = opts.expected ?? false;
|
|
39
51
|
}
|
|
40
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Seconds until a refusal clears, from whichever place the API put them.
|
|
55
|
+
*
|
|
56
|
+
* The header first, because it is the standard one and it is what a limiter emits without being
|
|
57
|
+
* asked; then `details.retryAfterSecs`, which one donor's API sends in the body instead. A
|
|
58
|
+
* caller asking "how long" should not have to know which server it is talking to.
|
|
59
|
+
*/
|
|
60
|
+
export function retryAfterSecs(error) {
|
|
61
|
+
if (error.retryAfterSecs !== undefined)
|
|
62
|
+
return error.retryAfterSecs;
|
|
63
|
+
const details = error.details;
|
|
64
|
+
if (typeof details !== "object" || details === null)
|
|
65
|
+
return null;
|
|
66
|
+
const value = details.retryAfterSecs;
|
|
67
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `Retry-After` in either spec form: delay-seconds, or an HTTP-date.
|
|
71
|
+
*
|
|
72
|
+
* Both forms are real — RFC 9110 allows either and different servers pick differently — and the
|
|
73
|
+
* date one is the half that gets skipped, which turns a stated wait into no wait at all. Clamped
|
|
74
|
+
* at zero: a date already in the past means "now", not a negative delay.
|
|
75
|
+
*/
|
|
76
|
+
export function parseRetryAfter(value) {
|
|
77
|
+
const raw = value?.trim();
|
|
78
|
+
if (!raw)
|
|
79
|
+
return undefined;
|
|
80
|
+
// A bare integer is the delay-seconds form, malformed included: `Date.parse` accepts some of
|
|
81
|
+
// them as years, so falling through would read "-5" as a date long past and answer 0 — a
|
|
82
|
+
// wait invented out of a header that never stated one.
|
|
83
|
+
if (/^[+-]?\d+$/.test(raw)) {
|
|
84
|
+
const secs = Number(raw);
|
|
85
|
+
return Number.isSafeInteger(secs) && secs >= 0 ? secs : undefined;
|
|
86
|
+
}
|
|
87
|
+
const at = Date.parse(raw);
|
|
88
|
+
if (Number.isNaN(at))
|
|
89
|
+
return undefined;
|
|
90
|
+
return Math.max(0, Math.round((at - Date.now()) / 1000));
|
|
91
|
+
}
|
|
41
92
|
/**
|
|
42
93
|
* An aborted request — the caller's own `AbortController`, or a navigation that unmounted the
|
|
43
94
|
* component waiting on it.
|
package/dist/api-error.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-error.js","sourceRoot":"","sources":["../src/api-error.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,MAAM,CAAS;IACf,IAAI,CAAqB;IACzB,UAAU,CAAqB;IAC/B,MAAM,CAA8C;IACpD,OAAO,CAAU;IACjB,SAAS,CAAqB;
|
|
1
|
+
{"version":3,"file":"api-error.js","sourceRoot":"","sources":["../src/api-error.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,MAAM,CAAS;IACf,IAAI,CAAqB;IACzB,UAAU,CAAqB;IAC/B,MAAM,CAA8C;IACpD,OAAO,CAAU;IACjB,SAAS,CAAqB;IACvC;;;;;;;;;OASG;IACM,cAAc,CAAqB;IAE5C;;;;;;;;OAQG;IACM,QAAQ,CAAU;IAE3B,YACE,MAAc,EACd,IAAkC,EAClC,OAKI,EAAE;QAEN,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI,mBAAmB,MAAM,GAAG,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,UAAU,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;QAC1C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;IACzC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAe;IAC5C,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,cAAc,CAAC;IACpE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,KAAK,GAAI,OAAwC,CAAC,cAAc,CAAC;IACvE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAgC;IAC9D,MAAM,GAAG,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAE3B,6FAA6F;IAC7F,yFAAyF;IACzF,uDAAuD;IACvD,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;AACtE,CAAC"}
|
package/dist/describe-error.d.ts
CHANGED
|
@@ -38,8 +38,6 @@ export type Translate<Key extends string = string> = (key: Key, params?: Record<
|
|
|
38
38
|
type WaitName = "waitSeconds" | "waitMinutes" | "waitHours";
|
|
39
39
|
/** Every name {@link createErrorDescriber} looks up itself. Your catalog carries all of them. */
|
|
40
40
|
type CopyName = WaitName | "network" | "networkHint" | "unexpected" | "retrySoon";
|
|
41
|
-
/** Seconds until a refusal clears, when the envelope carries them. */
|
|
42
|
-
export declare function retryAfterSecs(error: ApiError): number | null;
|
|
43
41
|
/**
|
|
44
42
|
* "in 4 minutes" / "in 2 hours" — a wait nobody has to convert from seconds.
|
|
45
43
|
*
|
|
@@ -64,6 +62,12 @@ export interface ErrorDescriberOptions<Prefix extends string, ServerPrefix exten
|
|
|
64
62
|
/**
|
|
65
63
|
* The server's own catalog namespace, e.g. `"serverErrors."`. A `messageKey` outside it is
|
|
66
64
|
* ignored — the server names a sentence, it does not get to name any key in the app.
|
|
65
|
+
*
|
|
66
|
+
* The `code` is read under this prefix too, for an API that has no `messageKey` field and
|
|
67
|
+
* lets the code name the sentence — one repo in the fleet has 98 of those, `errors.NOT_FOUND`
|
|
68
|
+
* and its siblings, against another repo's `messageKey`. Both are the same claim ("the server
|
|
69
|
+
* named a sentence this app carries"), both pass the same gate below, and an app that uses
|
|
70
|
+
* neither is unaffected because nothing matches.
|
|
67
71
|
*/
|
|
68
72
|
messageKeyPrefix: ServerPrefix;
|
|
69
73
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-error.d.ts","sourceRoot":"","sources":["../src/describe-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"describe-error.d.ts","sourceRoot":"","sources":["../src/describe-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAE1D;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,CACnD,GAAG,EAAE,GAAG,EACR,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MAAM,CAAC;AAEZ,wFAAwF;AACxF,KAAK,QAAQ,GAAG,aAAa,GAAG,aAAa,GAAG,WAAW,CAAC;AAE5D,iGAAiG;AACjG,KAAK,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,GAAG,YAAY,GAAG,WAAW,CAAC;AA8BlF;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,SAAS,MAAM,EAChD,CAAC,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,MAAM,CAIR;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,2FAA2F;IAC3F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,sFAAsF;IACtF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB,CACpC,MAAM,SAAS,MAAM,EACrB,YAAY,SAAS,MAAM,EAC3B,UAAU,SAAS,MAAM;IAEzB,CAAC,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE,GAAG,GAAG,YAAY,GAAG,UAAU,EAAE,CAAC,CAAC;IACtE,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,gBAAgB,EAAE,YAAY,CAAC;IAC/B;;;;;;;;;OASG;IACH,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,cAAc,CAAC;AAE7D,2BAA2B;AAC3B,wBAAgB,oBAAoB,CAClC,MAAM,SAAS,MAAM,EACrB,YAAY,SAAS,MAAM,EAC3B,UAAU,SAAS,MAAM,EACzB,EACA,CAAC,EACD,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,KAAU,GACX,EAAE,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,cAAc,CAkD9F"}
|
package/dist/describe-error.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import { ApiError } from "./api-error.js";
|
|
2
|
-
/** Seconds until a refusal clears, when the envelope carries them. */
|
|
3
|
-
export function retryAfterSecs(error) {
|
|
4
|
-
const details = error.details;
|
|
5
|
-
if (typeof details !== "object" || details === null)
|
|
6
|
-
return null;
|
|
7
|
-
const value = details.retryAfterSecs;
|
|
8
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
9
|
-
}
|
|
1
|
+
import { ApiError, retryAfterSecs } from "./api-error.js";
|
|
10
2
|
/**
|
|
11
3
|
* "in 4 minutes" / "in 2 hours" — a wait nobody has to convert from seconds.
|
|
12
4
|
*
|
|
@@ -30,11 +22,17 @@ export function createErrorDescriber({ t, copyPrefix, messageKeyPrefix, knownMes
|
|
|
30
22
|
// `in`, which would have accepted "toString".
|
|
31
23
|
const has = (name) => known.has(name);
|
|
32
24
|
function serverSentence(error) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
const lookup = (named) => {
|
|
26
|
+
if (named === undefined || !named.startsWith(messageKeyPrefix))
|
|
27
|
+
return null;
|
|
28
|
+
const name = named.slice(messageKeyPrefix.length);
|
|
29
|
+
return has(name) ? t(`${messageKeyPrefix}${name}`, error.params) : null;
|
|
30
|
+
};
|
|
31
|
+
// `messageKey` first: an API that sends both means the key, and the code is what the UI
|
|
32
|
+
// switches on. The code is only consulted when there is no key to prefer, and it is spelled
|
|
33
|
+
// under the same prefix so the gate is identical.
|
|
34
|
+
return (lookup(error.messageKey) ??
|
|
35
|
+
(error.code === undefined ? null : lookup(`${messageKeyPrefix}${error.code}`)));
|
|
38
36
|
}
|
|
39
37
|
return function describeError(error) {
|
|
40
38
|
if (!(error instanceof ApiError)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-error.js","sourceRoot":"","sources":["../src/describe-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"describe-error.js","sourceRoot":"","sources":["../src/describe-error.ts"],"names":[],"mappings":"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"}
|
package/dist/hydrate.d.ts
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* The constants themselves live in `prerender-contract.ts`, which imports nothing, because the
|
|
10
10
|
* BUILD needs them too and must not pull `react-dom/client` into a Node process to get a string.
|
|
11
|
+
*
|
|
12
|
+
* This module sits behind `@gusnips/react/hydrate` rather than in the barrel for the same reason
|
|
13
|
+
* one rung further out: it is the package's ONLY `react-dom` import, and a React Native app has
|
|
14
|
+
* no react-dom to give. Leaving it in the barrel made `react-dom` a required peer and shut the
|
|
15
|
+
* whole package out of every phone — for one function a phone would never call.
|
|
11
16
|
*/
|
|
12
17
|
import type { ReactNode } from "react";
|
|
13
18
|
export { PRERENDERED_ROUTE_ATTR, SHELL_ROUTE } from "./prerender-contract.ts";
|
package/dist/hydrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE9E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGpF"}
|
package/dist/hydrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.js","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hydrate.js","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,MAAe,EAAE,IAAe,EAAE,KAAa;IAC5E,IAAI,MAAM,CAAC,YAAY,CAAC,sBAAsB,CAAC,KAAK,KAAK;QAAE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;;QAChF,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { cn } from "./cn.ts";
|
|
2
|
-
export { ApiError, isAbortError } from "./api-error.ts";
|
|
2
|
+
export { ApiError, isAbortError, retryAfterSecs } from "./api-error.ts";
|
|
3
3
|
export { createApiClient, type ApiClient, type ApiClientOptions, type RefreshResult, type RequestOptions, type SessionAdapter, } from "./api-client.ts";
|
|
4
4
|
export { createSseParser, readSseStream, type SseFrame } from "./sse.ts";
|
|
5
|
-
export { queryDefaults, shouldRetry, type QueryDefaultsOptions } from "./query.ts";
|
|
5
|
+
export { queryDefaults, retryDelayMs, shouldRetry, type QueryDefaultsOptions } from "./query.ts";
|
|
6
6
|
export { ErrorBoundary, type ErrorBoundaryFallbackProps, type ErrorBoundaryProps, } from "./error-boundary.tsx";
|
|
7
|
-
export { createErrorDescriber, humanizeWait,
|
|
7
|
+
export { createErrorDescriber, humanizeWait, type DescribedError, type ErrorArm, type ErrorContext, type ErrorDescriberOptions, type Translate, } from "./describe-error.ts";
|
|
8
8
|
export { installPreloadErrorHandler, isChunkLoadError, isPreloadHintFailure, reloadOnceForChunkError, } from "./chunk-reload.ts";
|
|
9
|
-
export {
|
|
9
|
+
export { PRERENDERED_ROUTE_ATTR, SHELL_ROUTE } from "./prerender-contract.ts";
|
|
10
10
|
export { applyBrandVars, i18nInitOptions, type I18nInitOptions } from "./i18n.ts";
|
|
11
11
|
export type { EmptyStateProps, ErrorStateProps } from "./states.ts";
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEjG,OAAO,EACL,aAAa,EACb,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,GACf,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAElF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,21 +11,32 @@
|
|
|
11
11
|
// @gusnips/react/ui the Base UI wrappers → @base-ui/react
|
|
12
12
|
// @gusnips/react/store createAuthStore → zustand
|
|
13
13
|
// @gusnips/react/guards the route guards → react-router-dom
|
|
14
|
+
// @gusnips/react/hydrate hydrateOrMount → react-dom
|
|
14
15
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// `react-dom` is the one the second migration added, and it is the sharpest case yet: olhary
|
|
17
|
+
// ships a REACT NATIVE app beside its two web apps, and React Native has no react-dom to give.
|
|
18
|
+
// A required peer nobody can satisfy is not a strict contract, it is a closed door — the whole
|
|
19
|
+
// package was unusable there, for one function no phone would ever call. The rule did not need
|
|
20
|
+
// changing to catch it; `react-dom` simply had to stop being required.
|
|
21
|
+
//
|
|
22
|
+
// So what is left here imports `react` and `@gusnips/http` and nothing else, and an app on
|
|
23
|
+
// TanStack Router, on Redux, or on a phone takes the fetch client without installing a router, a
|
|
24
|
+
// store or a DOM renderer it will never call. `@tanstack/react-query`, `i18next` and
|
|
25
|
+
// `react-i18next` stay optional AND stay here, because `query.ts`, `i18n.ts` and `states.ts`
|
|
26
|
+
// import only their TYPES — which erase, so the built barrel does not reference them at runtime.
|
|
27
|
+
// Check that claim against `dist/`, never against this file: a type-only re-export looks
|
|
28
|
+
// identical in source.
|
|
21
29
|
export { cn } from "./cn.js";
|
|
22
|
-
export { ApiError, isAbortError } from "./api-error.js";
|
|
30
|
+
export { ApiError, isAbortError, retryAfterSecs } from "./api-error.js";
|
|
23
31
|
export { createApiClient, } from "./api-client.js";
|
|
24
32
|
export { createSseParser, readSseStream } from "./sse.js";
|
|
25
|
-
export { queryDefaults, shouldRetry } from "./query.js";
|
|
33
|
+
export { queryDefaults, retryDelayMs, shouldRetry } from "./query.js";
|
|
26
34
|
export { ErrorBoundary, } from "./error-boundary.js";
|
|
27
|
-
export { createErrorDescriber, humanizeWait,
|
|
35
|
+
export { createErrorDescriber, humanizeWait, } from "./describe-error.js";
|
|
28
36
|
export { installPreloadErrorHandler, isChunkLoadError, isPreloadHintFailure, reloadOnceForChunkError, } from "./chunk-reload.js";
|
|
29
|
-
|
|
37
|
+
// The two constants, but NOT `hydrateOrMount` — that one is at `@gusnips/react/hydrate`, because
|
|
38
|
+
// it is the package's only `react-dom` import. These come from the contract module, which imports
|
|
39
|
+
// nothing at all, so they are free to everyone: a build script, a browser entry, or a phone.
|
|
40
|
+
export { PRERENDERED_ROUTE_ATTR, SHELL_ROUTE } from "./prerender-contract.js";
|
|
30
41
|
export { applyBrandVars, i18nInitOptions } from "./i18n.js";
|
|
31
42
|
//# 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,kEAAkE;AAClE,EAAE;AACF,2CAA2C;AAC3C,EAAE;AACF,+FAA+F;AAC/F,gGAAgG;AAChG,+FAA+F;AAC/F,iGAAiG;AACjG,0BAA0B;AAC1B,EAAE;AACF,oEAAoE;AACpE,6DAA6D;AAC7D,sEAAsE;AACtE,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,2CAA2C;AAC3C,EAAE;AACF,+FAA+F;AAC/F,gGAAgG;AAChG,+FAA+F;AAC/F,iGAAiG;AACjG,0BAA0B;AAC1B,EAAE;AACF,oEAAoE;AACpE,6DAA6D;AAC7D,sEAAsE;AACtE,+DAA+D;AAC/D,EAAE;AACF,6FAA6F;AAC7F,+FAA+F;AAC/F,+FAA+F;AAC/F,+FAA+F;AAC/F,uEAAuE;AACvE,EAAE;AACF,2FAA2F;AAC3F,iGAAiG;AACjG,qFAAqF;AACrF,6FAA6F;AAC7F,iGAAiG;AACjG,yFAAyF;AACzF,uBAAuB;AAEvB,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EACL,eAAe,GAMhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAA6B,MAAM,YAAY,CAAC;AAEjG,OAAO,EACL,aAAa,GAGd,MAAM,qBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,YAAY,GAMb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,iGAAiG;AACjG,kGAAkG;AAClG,6FAA6F;AAC7F,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAwB,MAAM,WAAW,CAAC"}
|
package/dist/query.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface QueryDefaultsOptions {
|
|
|
8
8
|
durableLimitCodes?: readonly string[];
|
|
9
9
|
/** How many times to retry a transient failure. Two donors used 2, one used 1. */
|
|
10
10
|
maxRetries?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Longest stated wait still worth retrying. Default 10 seconds.
|
|
13
|
+
*
|
|
14
|
+
* Only applies when the refusal named its own expiry (`Retry-After`, or `retryAfterSecs` in
|
|
15
|
+
* the body). A burst limit that clears in two seconds is worth sitting out; a monthly ceiling
|
|
16
|
+
* that clears in an hour is an answer, and holding a spinner for it helps nobody — show the
|
|
17
|
+
* wait instead, which is what `humanizeWait` is for.
|
|
18
|
+
*/
|
|
19
|
+
maxRetryWaitSecs?: number;
|
|
11
20
|
staleTime?: number;
|
|
12
21
|
gcTime?: number;
|
|
13
22
|
/**
|
|
@@ -23,6 +32,12 @@ export interface QueryDefaultsOptions {
|
|
|
23
32
|
* Exported on its own because an app with its own QueryClient config still wants this rule, and
|
|
24
33
|
* because it is the part worth testing.
|
|
25
34
|
*/
|
|
26
|
-
export declare function shouldRetry(error: unknown, durableLimitCodes?: readonly string[]): boolean;
|
|
27
|
-
|
|
35
|
+
export declare function shouldRetry(error: unknown, durableLimitCodes?: readonly string[], maxRetryWaitSecs?: number): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* How long before the next attempt: the server's stated wait when there is one, react-query's
|
|
38
|
+
* exponential backoff otherwise, and never less than the backoff — a `Retry-After: 0` is a
|
|
39
|
+
* server saying "immediately", which for a client that just got refused is still too soon.
|
|
40
|
+
*/
|
|
41
|
+
export declare function retryDelayMs(attemptIndex: number, error: unknown): number;
|
|
42
|
+
export declare function queryDefaults({ durableLimitCodes, maxRetries, maxRetryWaitSecs, staleTime, gcTime, refetchOnWindowFocus, }?: QueryDefaultsOptions): DefaultOptions;
|
|
28
43
|
//# sourceMappingURL=query.d.ts.map
|
package/dist/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA+B5D,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,iBAAiB,GAAE,SAAS,MAAM,EAAO,EACzC,gBAAgB,SAA8B,GAC7C,OAAO,CAcT;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAKzE;AAED,wBAAgB,aAAa,CAAC,EAC5B,iBAAsB,EACtB,UAAc,EACd,gBAA8C,EAC9C,SAAyB,EACzB,MAAuB,EACvB,oBAA4B,GAC7B,GAAE,oBAAyB,GAAG,cAAc,CAe5C"}
|
package/dist/query.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiError } from "./api-error.js";
|
|
1
|
+
import { ApiError, retryAfterSecs } from "./api-error.js";
|
|
2
2
|
/**
|
|
3
3
|
* react-query defaults — the merge of three repos that each got part of this right.
|
|
4
4
|
*
|
|
@@ -13,33 +13,58 @@ import { ApiError } from "./api-error.js";
|
|
|
13
13
|
* - one set **`refetchOnReconnect`**, which none of the others had, and which is the single
|
|
14
14
|
* most useful refetch there is: coming back from a tunnel is exactly when the screen is stale.
|
|
15
15
|
*
|
|
16
|
+
* The second migration added the fourth rule, which no donor had: **when a 429 says how long,
|
|
17
|
+
* believe it.** Its limiter answers `Retry-After: 60` on a per-minute window, and the default
|
|
18
|
+
* backoff retries at 1s and 2s — two more requests that cannot succeed, both charged against the
|
|
19
|
+
* same limiter, and three seconds added before the screen says anything. So a stated wait is
|
|
20
|
+
* waited out when it is short, and is an ANSWER when it is not.
|
|
21
|
+
*
|
|
16
22
|
* This is invariant 8.
|
|
17
23
|
*/
|
|
18
24
|
/** 4xx statuses a second attempt can fix. Everything else in the 4xx family is an answer. */
|
|
19
25
|
const RETRYABLE_CLIENT_STATUS = new Set([408, 429]);
|
|
26
|
+
const DEFAULT_MAX_RETRY_WAIT_SECS = 10;
|
|
20
27
|
/**
|
|
21
28
|
* Should this failure be retried?
|
|
22
29
|
*
|
|
23
30
|
* Exported on its own because an app with its own QueryClient config still wants this rule, and
|
|
24
31
|
* because it is the part worth testing.
|
|
25
32
|
*/
|
|
26
|
-
export function shouldRetry(error, durableLimitCodes = []) {
|
|
33
|
+
export function shouldRetry(error, durableLimitCodes = [], maxRetryWaitSecs = DEFAULT_MAX_RETRY_WAIT_SECS) {
|
|
27
34
|
// No response at all — offline, DNS, a dropped connection. The request never landed, so
|
|
28
35
|
// nothing about it is an answer, and a second attempt is exactly right.
|
|
29
36
|
if (!(error instanceof ApiError))
|
|
30
37
|
return true;
|
|
31
38
|
if (error.code !== undefined && durableLimitCodes.includes(error.code))
|
|
32
39
|
return false;
|
|
40
|
+
// It told us when it clears. A wait we are not willing to hold is a refusal, not a hiccup —
|
|
41
|
+
// and it is the same judgement `durableLimitCodes` makes, except the server did the naming.
|
|
42
|
+
const wait = retryAfterSecs(error);
|
|
43
|
+
if (wait !== null && wait > maxRetryWaitSecs)
|
|
44
|
+
return false;
|
|
33
45
|
if (error.status >= 500)
|
|
34
46
|
return true;
|
|
35
47
|
return RETRYABLE_CLIENT_STATUS.has(error.status);
|
|
36
48
|
}
|
|
37
|
-
|
|
49
|
+
/**
|
|
50
|
+
* How long before the next attempt: the server's stated wait when there is one, react-query's
|
|
51
|
+
* exponential backoff otherwise, and never less than the backoff — a `Retry-After: 0` is a
|
|
52
|
+
* server saying "immediately", which for a client that just got refused is still too soon.
|
|
53
|
+
*/
|
|
54
|
+
export function retryDelayMs(attemptIndex, error) {
|
|
55
|
+
const backoff = Math.min(1000 * 2 ** attemptIndex, 30_000);
|
|
56
|
+
if (!(error instanceof ApiError))
|
|
57
|
+
return backoff;
|
|
58
|
+
const wait = retryAfterSecs(error);
|
|
59
|
+
return wait === null ? backoff : Math.max(backoff, wait * 1000);
|
|
60
|
+
}
|
|
61
|
+
export function queryDefaults({ durableLimitCodes = [], maxRetries = 2, maxRetryWaitSecs = DEFAULT_MAX_RETRY_WAIT_SECS, staleTime = 2 * 60 * 1000, gcTime = 10 * 60 * 1000, refetchOnWindowFocus = false, } = {}) {
|
|
38
62
|
return {
|
|
39
63
|
queries: {
|
|
40
64
|
staleTime,
|
|
41
65
|
gcTime,
|
|
42
|
-
retry: (failureCount, error) => failureCount < maxRetries && shouldRetry(error, durableLimitCodes),
|
|
66
|
+
retry: (failureCount, error) => failureCount < maxRetries && shouldRetry(error, durableLimitCodes, maxRetryWaitSecs),
|
|
67
|
+
retryDelay: retryDelayMs,
|
|
43
68
|
refetchOnWindowFocus,
|
|
44
69
|
refetchOnReconnect: true,
|
|
45
70
|
},
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,6FAA6F;AAC7F,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAEpD,MAAM,2BAA2B,GAAG,EAAE,CAAC;AA8BvC;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,oBAAuC,EAAE,EACzC,gBAAgB,GAAG,2BAA2B;IAE9C,wFAAwF;IACxF,wEAAwE;IACxE,IAAI,CAAC,CAAC,KAAK,YAAY,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAErF,4FAA4F;IAC5F,4FAA4F;IAC5F,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,GAAG,gBAAgB;QAAE,OAAO,KAAK,CAAC;IAE3D,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,uBAAuB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,YAAoB,EAAE,KAAc;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,YAAY,EAAE,MAAM,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC,KAAK,YAAY,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IACjD,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACnC,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAC5B,iBAAiB,GAAG,EAAE,EACtB,UAAU,GAAG,CAAC,EACd,gBAAgB,GAAG,2BAA2B,EAC9C,SAAS,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,EACzB,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EACvB,oBAAoB,GAAG,KAAK,MACJ,EAAE;IAC1B,OAAO;QACL,OAAO,EAAE;YACP,SAAS;YACT,MAAM;YACN,KAAK,EAAE,CAAC,YAAY,EAAE,KAAK,EAAE,EAAE,CAC7B,YAAY,GAAG,UAAU,IAAI,WAAW,CAAC,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,CAAC;YACtF,UAAU,EAAE,YAAY;YACxB,oBAAoB;YACpB,kBAAkB,EAAE,IAAI;SACzB;QACD,2FAA2F;QAC3F,yFAAyF;QACzF,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;KAC5B,CAAC;AACJ,CAAC"}
|