@alxia/react-router 0.36.1 → 0.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.
- package/README.md +22 -5
- package/dist/chunks/{index-rgy503w9.js → index-2swe47m6.js} +41 -3
- package/dist/chunks/{index-rgy503w9.js.map → index-2swe47m6.js.map} +5 -4
- package/dist/errors/call-failure.d.ts +1 -1
- package/dist/errors/call-failure.d.ts.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +18 -2
- package/dist/errors/index.js.map +3 -3
- package/dist/errors/problem-error.d.ts +50 -0
- package/dist/errors/problem-error.d.ts.map +1 -0
- package/dist/errors/route-error.d.ts +3 -1
- package/dist/errors/route-error.d.ts.map +1 -1
- package/dist/fetcher/index.d.ts +1 -0
- package/dist/fetcher/index.d.ts.map +1 -1
- package/dist/fetcher/index.js +57 -1
- package/dist/fetcher/index.js.map +4 -3
- package/dist/fetcher/use-fetcher-promise.d.ts +37 -0
- package/dist/fetcher/use-fetcher-promise.d.ts.map +1 -0
- package/dist/session/cookies.d.ts +7 -0
- package/dist/session/cookies.d.ts.map +1 -1
- package/dist/session/index.d.ts +2 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +89 -7
- package/dist/session/index.js.map +6 -5
- package/dist/session/serialize.d.ts +9 -3
- package/dist/session/serialize.d.ts.map +1 -1
- package/dist/session/signed.d.ts +44 -0
- package/dist/session/signed.d.ts.map +1 -0
- package/docs/README.md +2 -2
- package/docs/guide/errors.md +32 -1
- package/docs/guide/fetcher.md +44 -0
- package/docs/guide/session.md +50 -1
- package/docs/roadmap.md +5 -0
- package/docs/troubleshooting.md +59 -0
- package/package.json +1 -1
package/docs/troubleshooting.md
CHANGED
|
@@ -82,6 +82,10 @@ a loader, a message React Router or the browser prints, or an error from
|
|
|
82
82
|
- [`TypeError: sessionCookies(): the … name "…" already carries a prefix. Pass the base name: the package adds __Host- (session) or __Secure- (challenge) when secure is true.`](#typeerror-sessioncookies-the--name--already-carries-a-prefix-pass-the-base-name-the-package-adds-__host--session-or-__secure--challenge-when-secure-is-true)
|
|
83
83
|
- [`TypeError: sessionCookies(): challengeSeconds must be a positive integer; got ….`](#typeerror-sessioncookies-challengeseconds-must-be-a-positive-integer-got-)
|
|
84
84
|
- [`TypeError: sessionCookies(): challengePath must start with "/" and hold no ";" or control character; got ….`](#typeerror-sessioncookies-challengepath-must-start-with--and-hold-no--or-control-character-got-)
|
|
85
|
+
- [`TypeError: sessionCookies(): sameSite must be "lax", "strict" or "none"; got ….`](#typeerror-sessioncookies-samesite-must-be-lax-strict-or-none-got-)
|
|
86
|
+
- [`TypeError: sessionCookies(): sameSite "none" needs secure: true, since a browser drops a SameSite=None cookie without Secure.`](#typeerror-sessioncookies-samesite-none-needs-secure-true-since-a-browser-drops-a-samesitenone-cookie-without-secure)
|
|
87
|
+
- [`TypeError: signedSession(): secret must be a string of at least 32 characters.`](#typeerror-signedsession-secret-must-be-a-string-of-at-least-32-characters)
|
|
88
|
+
- [`TypeError: signedSession(): the name "…" must be an RFC 6265 token without a __Host- or __Secure- prefix; …` or `maxAge must be a positive integer of seconds; got ….`](#typeerror-signedsession-the-name--must-be-an-rfc-6265-token-without-a-__host--or-__secure--prefix--or-maxage-must-be-a-positive-integer-of-seconds-got-)
|
|
85
89
|
- [`TypeError: signInFlow(): paths.… must be a path of this site with no query or fragment, starting with a single "/"; got ….`](#typeerror-signinflow-paths-must-be-a-path-of-this-site-with-no-query-or-fragment-starting-with-a-single--got-)
|
|
86
90
|
- [`TypeError: signInPath(): options.path must be a path of this site with no query or fragment, starting with a single "/"; got ….`](#typeerror-signinpath-optionspath-must-be-a-path-of-this-site-with-no-query-or-fragment-starting-with-a-single--got-)
|
|
87
91
|
- [`TypeError: signInFlow(): json must come from jsonActions(), which carries the site check every step runs; this object has none (a hand-made double?). Pass the app's jsonActions({ origin }) instance, or a spread copy of it.`](#typeerror-signinflow-json-must-come-from-jsonactions-which-carries-the-site-check-every-step-runs-this-object-has-none-a-hand-made-double-pass-the-apps-jsonactions-origin--instance-or-a-spread-copy-of-it)
|
|
@@ -627,6 +631,25 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|
|
627
631
|
|
|
628
632
|
The rows stay, `error` is `true`, and `loadMore` retries.
|
|
629
633
|
|
|
634
|
+
### `useFetcherPromise: a newer call replaced this one before the fetcher answered` (or `… the component unmounted before the fetcher answered`)
|
|
635
|
+
|
|
636
|
+
**When:** the Promise `run(submit)` returned rejects with a `FetcherPromiseAborted` instead of the action's answer.
|
|
637
|
+
|
|
638
|
+
**Why:** `'superseded'`: `run` was called again while the first call was still waiting (a double click), so only the newer call gets the answer. `'unmounted'`: the component left before the fetcher answered.
|
|
639
|
+
|
|
640
|
+
**Fix:** disable the control while the Promise is pending, and tell the two cases from a refusal by the class:
|
|
641
|
+
|
|
642
|
+
```ts
|
|
643
|
+
import { FetcherPromiseAborted } from '@alxia/react-router/fetcher';
|
|
644
|
+
|
|
645
|
+
try {
|
|
646
|
+
await run(() => post({ intent: 'delete', id }));
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (error instanceof FetcherPromiseAborted) return;
|
|
649
|
+
setError((error as Error).message); // the refusal's message
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
630
653
|
### A debounced load lands on the route's `ErrorBoundary`
|
|
631
654
|
|
|
632
655
|
**When:** the resource route behind `useDebouncedFetcher` throws (or throws a `Response`) and the whole page is replaced by its `ErrorBoundary` instead of showing the previous suggestions with a retry.
|
|
@@ -1822,6 +1845,42 @@ TypeError: sessionCookies(): challengePath must start with "/" and hold no ";" o
|
|
|
1822
1845
|
|
|
1823
1846
|
**Fix:** pass the path of the sign-in pages with its leading slash. The default is `'/sign-in'`, which covers `/sign-in` and the pages under it; not `/sign-in/code` alone, since a client navigation fetches `/sign-in/code.data`, which that path would not match.
|
|
1824
1847
|
|
|
1848
|
+
### `TypeError: sessionCookies(): sameSite must be "lax", "strict" or "none"; got ….`
|
|
1849
|
+
|
|
1850
|
+
```
|
|
1851
|
+
TypeError: sessionCookies(): sameSite must be "lax", "strict" or "none"; got "sometimes".
|
|
1852
|
+
```
|
|
1853
|
+
|
|
1854
|
+
**When:** the module that calls `sessionCookies`, `signedSession` or `serializeCookie` is imported (the message starts with that function's name).
|
|
1855
|
+
|
|
1856
|
+
**Why:** `sameSite` is one of the three values, in lower case.
|
|
1857
|
+
|
|
1858
|
+
**Fix:** pass `'lax'` (the default), `'strict'` or `'none'`.
|
|
1859
|
+
|
|
1860
|
+
### `TypeError: sessionCookies(): sameSite "none" needs secure: true, since a browser drops a SameSite=None cookie without Secure.`
|
|
1861
|
+
|
|
1862
|
+
**When:** `sameSite: 'none'` with `secure: false` (development over plain HTTP).
|
|
1863
|
+
|
|
1864
|
+
**Why:** a browser drops a `SameSite=None` cookie that is not `Secure`.
|
|
1865
|
+
|
|
1866
|
+
**Fix:** use `'lax'` or `'strict'` in development, for example `sameSite: import.meta.env.DEV ? 'lax' : 'none'`.
|
|
1867
|
+
|
|
1868
|
+
### `TypeError: signedSession(): secret must be a string of at least 32 characters.`
|
|
1869
|
+
|
|
1870
|
+
**When:** the module that calls `signedSession` is imported, often because the secret's environment variable is unset and `?? ''` gave an empty string.
|
|
1871
|
+
|
|
1872
|
+
**Why:** an HMAC key shorter than 32 characters is guessable.
|
|
1873
|
+
|
|
1874
|
+
**Fix:** set the variable to a random value of 32 characters or more (`openssl rand -base64 32`). Changing it later signs every operator out.
|
|
1875
|
+
|
|
1876
|
+
### `TypeError: signedSession(): the name "…" must be an RFC 6265 token without a __Host- or __Secure- prefix; …` or `maxAge must be a positive integer of seconds; got ….`
|
|
1877
|
+
|
|
1878
|
+
**When:** the module that calls `signedSession` is imported.
|
|
1879
|
+
|
|
1880
|
+
**Why:** the name is not a cookie name or already carries a prefix (the package adds `__Host-` when `secure`), or `maxAge` is not a positive whole number of seconds.
|
|
1881
|
+
|
|
1882
|
+
**Fix:** pass a base name such as `'my-app_operator'` and `maxAge: 8 * 60 * 60`.
|
|
1883
|
+
|
|
1825
1884
|
### `TypeError: signInFlow(): paths.… must be a path of this site with no query or fragment, starting with a single "/"; got ….`
|
|
1826
1885
|
|
|
1827
1886
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alxia/react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "alxia as the server of a React Router framework app: server rendering behind the app's hooks, loaders reading its typed context, the client's assets served; the code with no UI an installable app shares (`/pwa`), its hooks (`/pwa/react`), `noCache` for the worker's script (`/pwa/server`), the service worker on Workbox (`/pwa/worker`) and its build (`/pwa/build`), an API's refusals worded for the page (`/errors`), the BFF session: cookies, sign-in, step-up and mail links (`/session`), its page hooks (`/session/react`) and what it shares with the page (`/session/model`), a GraphQL subscription relayed to the browser over SSE (`/subscription`), the page's CSP nonce and inline boot scripts (`/document`) and a streaming `entry.server` (`/entry`)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|