@alxia/react-router 0.23.0 → 0.24.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 +15 -2
- package/dist/errors/code-resolver.d.ts +1 -1
- package/dist/errors/code-resolver.d.ts.map +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +2 -2
- package/dist/lists/index.d.ts +2 -1
- package/dist/lists/index.d.ts.map +1 -1
- package/dist/lists/index.js +30 -2
- package/dist/lists/index.js.map +5 -4
- package/dist/lists/or-failed.d.ts +19 -0
- package/dist/lists/or-failed.d.ts.map +1 -0
- package/dist/lists/use-search-change.d.ts +10 -5
- package/dist/lists/use-search-change.d.ts.map +1 -1
- package/docs/guide/errors.md +7 -4
- package/docs/guide/lists.md +31 -2
- package/docs/troubleshooting.md +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1077,7 +1077,19 @@ export default function Users({ loaderData }: Route.ComponentProps) {
|
|
|
1077
1077
|
}
|
|
1078
1078
|
```
|
|
1079
1079
|
|
|
1080
|
-
The loader answers `?after=<cursor>` with the next page. A loader that throws on a cursor request lands on the route's `ErrorBoundary
|
|
1080
|
+
The loader answers `?after=<cursor>` with the next page. A loader that throws on a cursor request lands on the route's `ErrorBoundary`; wrap the read in `orFailed(after, run)` to keep the rows and offer a retry instead:
|
|
1081
|
+
|
|
1082
|
+
```ts
|
|
1083
|
+
import { orFailed } from '@alxia/react-router/lists';
|
|
1084
|
+
|
|
1085
|
+
export function loader({ request }: Route.LoaderArgs) {
|
|
1086
|
+
const url = new URL(request.url);
|
|
1087
|
+
// no ?after: a failure throws (ErrorBoundary); with ?after: it answers { failed: true }
|
|
1088
|
+
return orFailed(url.searchParams.get('after'), () => listUsers(url.searchParams));
|
|
1089
|
+
}
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
`usePagedRows` reads `{ failed: true }` as a page without rows, so it sets `error` and keeps the rows. See [troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md). See the
|
|
1081
1093
|
[lists guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/lists.md).
|
|
1082
1094
|
|
|
1083
1095
|
## Traps
|
|
@@ -1289,9 +1301,10 @@ From `@alxia/react-router/lists` (`react` and `react-router` are its only import
|
|
|
1289
1301
|
|
|
1290
1302
|
| export | |
|
|
1291
1303
|
| --- | --- |
|
|
1292
|
-
| `useSearchChange({ resetKeys? })`, `SearchChangeOptions` | `(changes) => void`: sets a parameter given a string, deletes one given `null` or `undefined`, and deletes `resetKeys` (default `['after']`); the history entry is replaced and the scroll position kept |
|
|
1304
|
+
| `useSearchChange({ resetKeys? })`, `SearchChange`, `SearchChangeOptions` | `(changes: SearchChange) => void`: a record sets a parameter given a string, deletes one given `null` or `undefined`, and deletes `resetKeys` (default `['after']`); a `URLSearchParams` replaces the whole search, and an updater `(prev) => URLSearchParams` computes it (`resetKeys` does not apply to these two); the history entry is replaced and the scroll position kept |
|
|
1293
1305
|
| `useSameRouteLoading()` | `true` while a navigation is loading and its pathname is the current one (another search of the list), `false` for any other page |
|
|
1294
1306
|
| `usePagedRows(firstPage, rowsOf, { cursorParam?, nextOf? }?)`, `PagedRows`, `PagedRowsOptions` | `{ rows, next, loading, error, loadMore }`: appends the pages the same route answers to `?<cursorParam>=<next>` (default `after`, `nextOf` default `page.next`) through `useFetcher`; starts over when `firstPage` is another object; a failure keeps the rows and sets `error` (an answer that is not a page), and data that is not a page is ignored |
|
|
1307
|
+
| `orFailed(after, run)`, `FAILED`, `isFailedPage(value)`, `FailedPage` | the loader half of `usePagedRows`: `run()`'s answer; an error is rethrown when `after` is empty, and with an `after` it answers `FAILED` (`{ failed: true }`, typed `Promise<T \| FailedPage>`), except a 3xx `Response`, which is rethrown. `isFailedPage` narrows an answer |
|
|
1295
1308
|
|
|
1296
1309
|
From `@alxia/react-router/subscription` (no import, no peer):
|
|
1297
1310
|
|
|
@@ -11,7 +11,7 @@ export type CodeIssueLike = {
|
|
|
11
11
|
readonly expected?: unknown;
|
|
12
12
|
readonly message?: string | undefined;
|
|
13
13
|
};
|
|
14
|
-
/** An API error code (`<module>.errors.<slug
|
|
14
|
+
/** An API error code (`<module>.errors.<slug>`, the module in kebab-case or camelCase), as opposed to a GraphQL one (`BAD_USER_INPUT`). */
|
|
15
15
|
export declare const API_CODE: RegExp;
|
|
16
16
|
export interface ApiCodeResolverOptions<L extends string> {
|
|
17
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-resolver.d.ts","sourceRoot":"","sources":["../../src/errors/code-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF,
|
|
1
|
+
{"version":3,"file":"code-resolver.d.ts","sourceRoot":"","sources":["../../src/errors/code-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF,2IAA2I;AAC3I,eAAO,MAAM,QAAQ,QAA6C,CAAC;AAEnE,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,MAAM;IACvD;;;OAGG;IACH,SAAS,CACR,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC3C,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GACtB,MAAM,CAAC;IACV,mDAAmD;IACnD,QAAQ,IAAI,CAAC,CAAC;IACd,yEAAyE;IACzE,MAAM,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;IAClD,yEAAyE;IACzE,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,MAAM;IAChD,SAAS,EAAE,CACV,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC3C,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KACpB,MAAM,CAAC;IACZ,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,CAAC;CACrD;AAOD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC/C,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EACxB,OAAO,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAChC,eAAe,CAAC,CAAC,CAAC,CAiBpB"}
|
package/dist/errors/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
rememberSiteCheck
|
|
16
16
|
} from "../chunks/index-rgy503w9.js";
|
|
17
17
|
// src/errors/code-resolver.ts
|
|
18
|
-
var API_CODE = /^[a-z-]
|
|
18
|
+
var API_CODE = /^[a-z][a-zA-Z0-9-]*\.errors\.[a-z0-9-]+$/;
|
|
19
19
|
function paramsCode(issue) {
|
|
20
20
|
const code = issue.params?.code;
|
|
21
21
|
return typeof code === "string" ? code : undefined;
|
|
@@ -538,5 +538,5 @@ export {
|
|
|
538
538
|
waitMinutes2 as waitMinutes
|
|
539
539
|
};
|
|
540
540
|
|
|
541
|
-
//# debugId=
|
|
541
|
+
//# debugId=C040A51D1737CFBA64756E2164756E21
|
|
542
542
|
//# sourceMappingURL=index.js.map
|
package/dist/errors/index.js.map
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/errors/code-resolver.ts", "../src/errors/describe-refusal.ts", "../src/errors/fetcher-result.ts", "../src/errors/submit-form.ts", "../src/errors/describe.ts", "../src/errors/api-errors.ts", "../src/errors/log.ts", "../src/errors/call-failure.ts", "../src/errors/catalogue-check.ts", "../src/errors/catalogue-read.ts", "../src/errors/catalogue.ts", "../src/errors/json-actions.ts", "../src/errors/message-values.ts", "../src/errors/messages.ts", "../src/errors/provider.ts", "../src/errors/use-json-fetcher.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { ApiMessages } from './messages';\n\n/** The part of a zod issue a code reads, by shape (`@nxgt/material-core`'s `CodeIssue`). */\nexport type CodeIssueLike = {\n\treadonly code: string;\n\t/** A custom issue's params: `.refine(…, { params: { code } })`. */\n\treadonly params?: Record<string, unknown> | undefined;\n\treadonly input?: unknown;\n\treadonly origin?: string | undefined;\n\treadonly minimum?: unknown;\n\treadonly maximum?: unknown;\n\treadonly expected?: unknown;\n\treadonly message?: string | undefined;\n};\n\n/** An API error code (`<module>.errors.<slug
|
|
5
|
+
"import type { ApiMessages } from './messages';\n\n/** The part of a zod issue a code reads, by shape (`@nxgt/material-core`'s `CodeIssue`). */\nexport type CodeIssueLike = {\n\treadonly code: string;\n\t/** A custom issue's params: `.refine(…, { params: { code } })`. */\n\treadonly params?: Record<string, unknown> | undefined;\n\treadonly input?: unknown;\n\treadonly origin?: string | undefined;\n\treadonly minimum?: unknown;\n\treadonly maximum?: unknown;\n\treadonly expected?: unknown;\n\treadonly message?: string | undefined;\n};\n\n/** An API error code (`<module>.errors.<slug>`, the module in kebab-case or camelCase), as opposed to a GraphQL one (`BAD_USER_INPUT`). */\nexport const API_CODE = /^[a-z][a-zA-Z0-9-]*\\.errors\\.[a-z0-9-]+$/;\n\nexport interface ApiCodeResolverOptions<L extends string> {\n\t/**\n\t * The app's translator, for every key that is not an API code. Method\n\t * syntax: the app's own `Translate<LocaleKey>` is accepted.\n\t */\n\ttranslate(\n\t\tkey: string,\n\t\tcontext?: Readonly<Record<string, unknown>>,\n\t\tlanguage?: L | (() => L),\n\t): string;\n\t/** The page's language, when a call names none. */\n\tlanguage(): L;\n\t/** Which code an issue carries; the issue's `params.code` by default. */\n\tcodeOf?(issue: CodeIssueLike): string | undefined;\n\t/** Whether a key is an API code; `<module>.errors.<slug>` by default. */\n\tisApiCode?(key: string): boolean;\n}\n\n/** What `@nxgt/material`'s `useCodeResolver(schema, resolver)` takes. */\nexport interface ApiCodeResolver<L extends string> {\n\ttranslate: (\n\t\tkey: string,\n\t\tcontext?: Readonly<Record<string, unknown>>,\n\t\tlanguage?: L | (() => L),\n\t) => string;\n\tcodeOf: (issue: CodeIssueLike) => string | undefined;\n}\n\nfunction paramsCode(issue: CodeIssueLike) {\n\tconst code = (issue.params as { code?: unknown } | undefined)?.code;\n\treturn typeof code === 'string' ? code : undefined;\n}\n\n/**\n * The words of what a form's checks ask for: a refinement carrying an API\n * code in `params.code` reads exactly as the API words it (from the\n * catalogue `messages` holds), any other key takes the app's translator, and\n * a code the catalogue lacks comes back as it is, so the form library words\n * the issue with its own locale instead. Both functions are stable: a\n * resolver is made once per schema and language, so build this once, at\n * module scope.\n */\nexport function apiCodeResolver<L extends string>(\n\tmessages: ApiMessages<L>,\n\toptions: ApiCodeResolverOptions<L>,\n): ApiCodeResolver<L> {\n\tconst isApiCode = (key: string) =>\n\t\toptions.isApiCode ? options.isApiCode(key) : API_CODE.test(key);\n\treturn {\n\t\ttranslate(key, context, provideLanguage) {\n\t\t\tif (!isApiCode(key))\n\t\t\t\treturn options.translate(key, context, provideLanguage);\n\t\t\tconst language =\n\t\t\t\ttypeof provideLanguage === 'function'\n\t\t\t\t\t? provideLanguage()\n\t\t\t\t\t: (provideLanguage ?? options.language());\n\t\t\treturn messages.apiMessage(key, context, language) ?? key;\n\t\t},\n\t\tcodeOf(issue) {\n\t\t\treturn options.codeOf ? options.codeOf(issue) : paramsCode(issue);\n\t\t},\n\t};\n}\n",
|
|
6
6
|
"import { isApiErrorLike } from './api-error';\nimport type { ApiErrors } from './api-errors';\n\n/** The API's refusal of `error`, or `undefined` when it is not one. */\nexport function refusalOf<L extends string>(\n\terrors: Pick<ApiErrors<L>, 'apiErrorMessage' | 'unavailableMessage'>,\n\terror: unknown,\n\tlanguage: L,\n): ReturnType<ApiErrors<L>['apiRefusal']> {\n\tif (isApiErrorLike(error)) {\n\t\treturn {\n\t\t\tmessage: errors.apiErrorMessage(error, language),\n\t\t\tstatus: error.status,\n\t\t};\n\t}\n\tif (error instanceof Response && error.status === 503) {\n\t\treturn { message: errors.unavailableMessage(language), status: 503 };\n\t}\n\treturn undefined;\n}\n\n/** The worded message of each field an API error names, by field path. */\nexport function fieldErrorsOf<L extends string>(\n\terrors: Pick<ApiErrors<L>, 'apiErrorMessage' | 'translateCode'>,\n\terror: Parameters<ApiErrors<L>['fieldErrors']>[0],\n\tlanguage: L,\n): ReturnType<ApiErrors<L>['fieldErrors']> {\n\tconst extensions = error.errors[0]?.extensions;\n\tconst fields = Array.isArray(extensions?.fields) ? extensions.fields : [];\n\tif (!fields.length && extensions?.field) {\n\t\treturn { [extensions.field]: errors.apiErrorMessage(error, language) };\n\t}\n\treturn Object.fromEntries(\n\t\tfields.map(({ path, code, message, params }) => [\n\t\t\tpath,\n\t\t\terrors.translateCode(code, message, language, params),\n\t\t]),\n\t);\n}\n",
|
|
7
7
|
"import { data } from 'react-router';\nimport { isApiErrorLike } from './api-error';\nimport type { ApiErrors, ApiRefusal } from './api-errors';\nimport { type RouteErrorOptions, routeError } from './route-error';\n\n/**\n * A fetcher's refusal: the API's (`apiRefusal`), an unreachable API's too,\n * so the page stays where it was. A 401 (the session caller redirects\n * first) or any other failure is thrown, for the error page.\n */\nexport function fetcherRefusal<L extends string>(\n\terrors: Pick<ApiErrors<L>, 'apiRefusal'>,\n\terror: unknown,\n\tlanguage: L,\n\troute?: RouteErrorOptions,\n): ApiRefusal {\n\tconst refusal =\n\t\tisApiErrorLike(error) && error.status === 401\n\t\t\t? undefined\n\t\t\t: errors.apiRefusal(error, language);\n\tif (!refusal) throw routeError(error, route);\n\treturn refusal;\n}\n\n/**\n * Runs the mutation of an action a fetcher posted to: `{ ok: true }` with\n * `about` and the mutation's value, or the refusal (`fetcherRefusal`) as a\n * worded `error`, with its HTTP status, for a toast or a form message.\n */\nexport async function fetcherResult<\n\tL extends string,\n\tAbout extends object,\n\tValue extends object = Record<never, never>,\n>(\n\terrors: Pick<ApiErrors<L>, 'apiRefusal'>,\n\tlanguage: L,\n\tabout: About,\n\trun: () => Promise<Value>,\n\troute?: RouteErrorOptions,\n) {\n\ttry {\n\t\treturn { ok: true as const, ...about, ...(await run()) };\n\t} catch (error) {\n\t\tconst { message, status } = fetcherRefusal(errors, error, language, route);\n\t\treturn data({ ok: false as const, ...about, error: message }, { status });\n\t}\n}\n",
|
|
8
8
|
"import { data } from 'react-router';\nimport { isApiErrorLike } from './api-error';\nimport type { ApiErrors } from './api-errors';\nimport type { FormErrors } from './form-errors';\nimport { type RouteErrorOptions, routeError } from './route-error';\n\n/**\n * Runs a form's mutation. An `ApiError` becomes the form's errors: each\n * refused field (`extensions.fields`, or the one `extensions.field` names)\n * under the form field `fieldOf` names (else on the form), and any other\n * code as the form's message. A 404 about none of the form's fields (the\n * record itself is gone), a 401 and other failures go to the error page.\n */\nexport async function submitForm<L extends string, T>(\n\terrors: Pick<ApiErrors<L>, 'apiErrorMessage' | 'fieldErrors'>,\n\tlanguage: L,\n\trun: () => Promise<T>,\n\tfieldOf: (path: string) => string | undefined,\n\troute?: RouteErrorOptions,\n): Promise<{ ok: true; value: T } | ReturnType<typeof data<FormErrors>>> {\n\ttry {\n\t\treturn { ok: true, value: await run() };\n\t} catch (error) {\n\t\tif (!isApiErrorLike(error) || error.status === 401) {\n\t\t\tthrow routeError(error, route);\n\t\t}\n\t\tconst field = error.errors[0]?.extensions?.field;\n\t\tif (error.status === 404 && !(field && fieldOf(field))) {\n\t\t\tthrow routeError(error, route);\n\t\t}\n\t\tconst byField: Record<string, string> = {};\n\t\tconst unplaced: string[] = [];\n\t\tfor (const [path, message] of Object.entries(\n\t\t\terrors.fieldErrors(error, language),\n\t\t)) {\n\t\t\tconst field = fieldOf(path);\n\t\t\tif (field) byField[field] = message;\n\t\t\telse unplaced.push(message);\n\t\t}\n\t\tconst formError =\n\t\t\tObject.keys(byField).length && !unplaced.length\n\t\t\t\t? undefined\n\t\t\t\t: [errors.apiErrorMessage(error, language), ...unplaced].join(' ');\n\t\treturn data({ fieldErrors: byField, formError }, { status: error.status });\n\t}\n}\n",
|
|
@@ -20,6 +20,6 @@
|
|
|
20
20
|
"import { type FetcherWithComponents, useFetcher } from 'react-router';\n\n/** What a fetcher can post as JSON. */\nexport type JsonBody = Parameters<FetcherWithComponents<unknown>['submit']>[0];\n\n/**\n * A fetcher posting JSON to its page's action (which reads it with\n * `readJson`, and answers `fetcherResult` or the like): `post` sends a\n * body, `busy` while it is in flight. `key` shares the fetcher between the\n * form that posts and the component that reads the answer; `action` posts to\n * another route's action instead (a resource route's, such as a\n * `notifications/poll`). `post` takes the target of one call as its second\n * argument (a list's rows each post to their own record's action).\n */\nexport function useJsonFetcher<Action, Body extends JsonBody = JsonBody>(\n\tkey?: string,\n\taction?: string,\n): {\n\tfetcher: ReturnType<typeof useFetcher<Action>>;\n\tpost(body: Body, to?: string): Promise<void>;\n\tbusy: boolean;\n} {\n\tconst fetcher = useFetcher<Action>(key ? { key } : undefined);\n\tconst post = (body: Body, to = action) =>\n\t\tfetcher.submit(body, {\n\t\t\tmethod: 'post',\n\t\t\tencType: 'application/json',\n\t\t\t...(to ? { action: to } : {}),\n\t\t});\n\treturn { fetcher, post, busy: fetcher.state !== 'idle' };\n}\n"
|
|
21
21
|
],
|
|
22
22
|
"mappings": "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",
|
|
23
|
-
"debugId": "
|
|
23
|
+
"debugId": "C040A51D1737CFBA64756E2164756E21",
|
|
24
24
|
"names": []
|
|
25
25
|
}
|
package/dist/lists/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* URL (a search, filters, a cursor). `react` and `react-router` are its only
|
|
4
4
|
* imports, and every hook renders on the server.
|
|
5
5
|
*/
|
|
6
|
+
export { FAILED, type FailedPage, isFailedPage, orFailed, } from './or-failed';
|
|
6
7
|
export { DEFAULT_CURSOR_PARAM, type PagedRows, type PagedRowsOptions, usePagedRows, } from './use-paged-rows';
|
|
7
8
|
export { useSameRouteLoading } from './use-same-route-loading';
|
|
8
|
-
export { type SearchChangeOptions, useSearchChange } from './use-search-change';
|
|
9
|
+
export { type SearchChange, type SearchChangeOptions, useSearchChange, } from './use-search-change';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lists/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACN,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,YAAY,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lists/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACN,MAAM,EACN,KAAK,UAAU,EACf,YAAY,EACZ,QAAQ,GACR,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,YAAY,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,eAAe,GACf,MAAM,qBAAqB,CAAC"}
|
package/dist/lists/index.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
// src/lists/or-failed.ts
|
|
2
|
+
var FAILED = Object.freeze({ failed: true });
|
|
3
|
+
function isFailedPage(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && value.failed === true;
|
|
5
|
+
}
|
|
6
|
+
async function orFailed(after, run) {
|
|
7
|
+
if (!after)
|
|
8
|
+
return await run();
|
|
9
|
+
try {
|
|
10
|
+
return await run();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error instanceof Response && error.status >= 300 && error.status < 400)
|
|
13
|
+
throw error;
|
|
14
|
+
return FAILED;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
1
17
|
// src/lists/use-paged-rows.ts
|
|
2
18
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
19
|
import { useFetcher, useLocation } from "react-router";
|
|
@@ -101,6 +117,15 @@ function useSearchChange(options = {}) {
|
|
|
101
117
|
const resetKeys = options.resetKeys ?? RESET_KEYS;
|
|
102
118
|
const joined = resetKeys.join("\x00");
|
|
103
119
|
return useCallback2((changes) => {
|
|
120
|
+
const navigation = { replace: true, preventScrollReset: true };
|
|
121
|
+
if (typeof changes === "function") {
|
|
122
|
+
setSearchParams((current) => changes(new URLSearchParams(current)), navigation);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (changes instanceof URLSearchParams) {
|
|
126
|
+
setSearchParams(new URLSearchParams(changes), navigation);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
104
129
|
setSearchParams((current) => {
|
|
105
130
|
const next = new URLSearchParams(current);
|
|
106
131
|
for (const [key, value] of Object.entries(changes)) {
|
|
@@ -112,15 +137,18 @@ function useSearchChange(options = {}) {
|
|
|
112
137
|
for (const key of joined === "" ? [] : joined.split("\x00"))
|
|
113
138
|
next.delete(key);
|
|
114
139
|
return next;
|
|
115
|
-
},
|
|
140
|
+
}, navigation);
|
|
116
141
|
}, [setSearchParams, joined]);
|
|
117
142
|
}
|
|
118
143
|
export {
|
|
119
144
|
DEFAULT_CURSOR_PARAM,
|
|
145
|
+
FAILED,
|
|
146
|
+
isFailedPage,
|
|
147
|
+
orFailed,
|
|
120
148
|
usePagedRows,
|
|
121
149
|
useSameRouteLoading,
|
|
122
150
|
useSearchChange
|
|
123
151
|
};
|
|
124
152
|
|
|
125
|
-
//# debugId=
|
|
153
|
+
//# debugId=CCC4EB19C4BC264164756E2164756E21
|
|
126
154
|
//# sourceMappingURL=index.js.map
|
package/dist/lists/index.js.map
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/lists/use-paged-rows.ts", "../src/lists/use-same-route-loading.ts", "../src/lists/use-search-change.ts"],
|
|
3
|
+
"sources": ["../src/lists/or-failed.ts", "../src/lists/use-paged-rows.ts", "../src/lists/use-same-route-loading.ts", "../src/lists/use-search-change.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
+
"/** What a loader answers for a failed cursor page: no rows, so `usePagedRows` sets `error`. */\nexport const FAILED = Object.freeze({ failed: true as const });\n\n/** The type of {@link FAILED}. */\nexport type FailedPage = typeof FAILED;\n\n/** Whether a loader answer is {@link FAILED}. */\nexport function isFailedPage(value: unknown): value is FailedPage {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as { failed?: unknown }).failed === true\n\t);\n}\n\n/**\n * The loader half of `usePagedRows`. Runs `run` and returns its answer.\n *\n * - With no `after` (the first page), an error is rethrown, so the route's\n * `ErrorBoundary` shows.\n * - With an `after` (a \"load more\"), any error becomes {@link FAILED}, so the\n * rows stay on screen with a retry, except a 3xx `Response` (a redirect),\n * which is rethrown.\n */\nexport async function orFailed<T>(\n\tafter: string | null | undefined,\n\trun: () => Promise<T> | T,\n): Promise<T | FailedPage> {\n\tif (!after) return await run();\n\ttry {\n\t\treturn await run();\n\t} catch (error) {\n\t\tif (error instanceof Response && error.status >= 300 && error.status < 400)\n\t\t\tthrow error;\n\t\treturn FAILED;\n\t}\n}\n",
|
|
5
6
|
"import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useFetcher, useLocation } from 'react-router';\n\nexport interface PagedRowsOptions<Page> {\n\t/** The search parameter that carries the cursor (default `after`). */\n\treadonly cursorParam?: string;\n\t/** The cursor of the page after this one, `null` at the end (default `page.next`). */\n\treadonly nextOf?: (page: Page) => string | null;\n}\n\nexport interface PagedRows<Row> {\n\t/** The first page's rows, then the rows of each page loaded after it. */\n\treadonly rows: readonly Row[];\n\t/** The cursor of the next page, `null` when there is none. */\n\treadonly next: string | null;\n\t/** A page is being fetched. */\n\treadonly loading: boolean;\n\t/**\n\t * The last `loadMore` ended with nothing usable (the route answered something\n\t * that is not a page). The rows stay; call `loadMore` again to retry. The\n\t * next `loadMore` clears it.\n\t */\n\treadonly error: boolean;\n\t/** Fetches the page after the last one; nothing when there is none or one is on its way. */\n\treadonly loadMore: () => void;\n}\n\n/** The default cursor parameter, shared with `useSearchChange`'s default `resetKeys`. */\nexport const DEFAULT_CURSOR_PARAM = 'after';\n\ninterface Loaded<Page, Row> {\n\treadonly first: Page;\n\treadonly extra: readonly Row[];\n\treadonly next: string | null;\n\treadonly error: boolean;\n}\n\ntype NextOf<Page> = (page: Page) => string | null;\n\n/** The state for a first page: nothing loaded after it yet. */\nfunction restart<Page, Row>(\n\tfirst: Page,\n\tnextOf: NextOf<Page>,\n): Loaded<Page, Row> {\n\treturn { first, extra: [], next: nextOf(first) ?? null, error: false };\n}\n\n/**\n * The rows and cursor of `value` when it is a page (an object whose rows are\n * an array and whose cursor is a string or `null`), else `undefined`.\n */\nfunction readPage<Page, Row>(\n\tvalue: unknown,\n\trowsOf: (page: Page) => readonly Row[],\n\tnextOf: NextOf<Page>,\n): { rows: readonly Row[]; next: string | null } | undefined {\n\tif (typeof value !== 'object' || value === null) return undefined;\n\ttry {\n\t\tconst rows = rowsOf(value as Page);\n\t\tconst next = nextOf(value as Page);\n\t\tif (!Array.isArray(rows)) return undefined;\n\t\tif (next !== null && typeof next !== 'string') return undefined;\n\t\treturn { rows, next };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** The state after an answer: its rows appended, or `error` when it was not a page. */\nfunction settle<Page, Row>(\n\tprevious: Loaded<Page, Row>,\n\tfirst: Page,\n\tmore: { rows: readonly Row[]; next: string | null } | undefined,\n\tnextOf: NextOf<Page>,\n): Loaded<Page, Row> {\n\tconst base =\n\t\tprevious.first === first ? previous : restart<Page, Row>(first, nextOf);\n\tif (more === undefined) return { ...base, error: true };\n\treturn {\n\t\t...base,\n\t\textra: [...base.extra, ...more.rows],\n\t\tnext: more.next,\n\t\terror: false,\n\t};\n}\n\n/**\n * Appends the pages a \"load more\" asks of the route the page is on. The\n * route's loader answers `?<cursorParam>=<next>` with the next page, through\n * `useFetcher`, so the address bar and the page's own data stay as they are.\n *\n * - It starts over whenever `firstPage` is another object (a new search, a\n * revalidation): the rows are the new first page's alone, in that very\n * render. A page still on its way for the old one is dropped.\n * - A failed load keeps the rows and sets `error`. A thrown loader error goes\n * to the route's `ErrorBoundary` as React Router always does; `error` covers\n * an answer that is not a page, such as `data({ message }, { status: 503 })`\n * or `null`.\n * - Data of the fetcher that is not a page (no rows, or no cursor) is ignored.\n */\nexport function usePagedRows<Page, Row>(\n\tfirstPage: Page,\n\trowsOf: (page: Page) => readonly Row[],\n\toptions: PagedRowsOptions<Page> = {},\n): PagedRows<Row> {\n\tconst cursorParam = options.cursorParam ?? DEFAULT_CURSOR_PARAM;\n\tconst nextOf: NextOf<Page> =\n\t\toptions.nextOf ??\n\t\t((page) => (page as { next?: string | null }).next as string | null);\n\tconst fetcher = useFetcher();\n\tconst { pathname, search } = useLocation();\n\tconst [loaded, setLoaded] = useState(() =>\n\t\trestart<Page, Row>(firstPage, nextOf),\n\t);\n\t// What was asked, and for which first page, so an answer that comes after a restart is dropped.\n\tconst asked = useRef<{ first: Page; seen: unknown; started: boolean } | null>(\n\t\tnull,\n\t);\n\tconst current =\n\t\tloaded.first === firstPage ? loaded : restart<Page, Row>(firstPage, nextOf);\n\n\t// Deliberate deps: rowsOf and nextOf are usually inline functions, new on\n\t// every render, and are read at the time of the answer; the effect must run\n\t// on the fetcher's settling, not on each render.\n\tuseEffect(() => {\n\t\tconst request = asked.current;\n\t\tif (request === null) return;\n\t\tif (fetcher.state !== 'idle') {\n\t\t\trequest.started = true;\n\t\t\treturn;\n\t\t}\n\t\t// Idle: the answer is in when the fetcher was seen busy, or when its data\n\t\t// is no longer the one it had at the call (an answer within one batch).\n\t\tif (!request.started && fetcher.data === request.seen) return;\n\t\tasked.current = null;\n\t\tif (request.first !== firstPage) return;\n\t\tconst more =\n\t\t\tfetcher.data === request.seen\n\t\t\t\t? undefined\n\t\t\t\t: readPage(fetcher.data, rowsOf, nextOf);\n\t\tsetLoaded((previous) => settle(previous, firstPage, more, nextOf));\n\t}, [fetcher.state, fetcher.data, firstPage]);\n\n\tconst next = current.next;\n\tconst busy = fetcher.state !== 'idle';\n\tconst { load } = fetcher;\n\tconst loadMore = useCallback(() => {\n\t\tif (next === null || busy) return;\n\t\tconst params = new URLSearchParams(search);\n\t\tparams.set(cursorParam, next);\n\t\tasked.current = { first: firstPage, seen: fetcher.data, started: false };\n\t\tsetLoaded({ ...current, error: false });\n\t\tvoid load(`${pathname}?${params}`);\n\t}, [\n\t\tnext,\n\t\tbusy,\n\t\tsearch,\n\t\tcursorParam,\n\t\tfirstPage,\n\t\tfetcher.data,\n\t\tcurrent,\n\t\tpathname,\n\t\tload,\n\t]);\n\n\treturn {\n\t\trows: [...rowsOf(firstPage), ...current.extra],\n\t\tnext,\n\t\tloading: busy,\n\t\terror: current.error,\n\t\tloadMore,\n\t};\n}\n",
|
|
6
7
|
"import { useLocation, useNavigation } from 'react-router';\n\n/**\n * `true` while a navigation is loading and goes to the pathname the page is\n * on: another search of this list, not another page. It stays `false` for a\n * navigation elsewhere (and when idle), so a list can show its skeleton\n * without flashing on a click that leaves it.\n */\nexport function useSameRouteLoading(): boolean {\n\tconst navigation = useNavigation();\n\tconst { pathname } = useLocation();\n\treturn (\n\t\tnavigation.state === 'loading' && navigation.location.pathname === pathname\n\t);\n}\n",
|
|
7
|
-
"import { useCallback } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { DEFAULT_CURSOR_PARAM } from './use-paged-rows';\n\nexport interface SearchChangeOptions {\n\t/**\n\t * The parameters dropped by every change, because they only mean something\n\t * for the search they were read from (default `['after']`, a cursor).\n\t */\n\treadonly resetKeys?: readonly string[];\n}\n\nconst RESET_KEYS: readonly string[] = [DEFAULT_CURSOR_PARAM];\n\n/**\n * Returns `(changes) => void
|
|
8
|
+
"import { useCallback } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { DEFAULT_CURSOR_PARAM } from './use-paged-rows';\n\nexport interface SearchChangeOptions {\n\t/**\n\t * The parameters dropped by every change, because they only mean something\n\t * for the search they were read from (default `['after']`, a cursor).\n\t */\n\treadonly resetKeys?: readonly string[];\n}\n\nconst RESET_KEYS: readonly string[] = [DEFAULT_CURSOR_PARAM];\n\n/** What `change` takes: a record of changes, a whole search, or an updater. */\nexport type SearchChange =\n\t| Record<string, string | null | undefined>\n\t| URLSearchParams\n\t| ((prev: URLSearchParams) => URLSearchParams);\n\n/**\n * Returns `(changes) => void`. With a record it sets the parameters it is\n * given a value for, deletes the ones given `null` or `undefined` (an empty\n * string is a value, so it is set), and deletes `resetKeys` in the same step.\n * With a `URLSearchParams` it replaces the whole search by it, and with an\n * updater `(prev) => URLSearchParams` by what the updater returns; both are\n * the caller's whole search, so `resetKeys` does not apply. Every form\n * replaces the history entry and keeps the scroll position.\n */\nexport function useSearchChange(\n\toptions: SearchChangeOptions = {},\n): (changes: SearchChange) => void {\n\tconst [, setSearchParams] = useSearchParams();\n\tconst resetKeys = options.resetKeys ?? RESET_KEYS;\n\tconst joined = resetKeys.join('\\u0000');\n\treturn useCallback(\n\t\t(changes) => {\n\t\t\tconst navigation = { replace: true, preventScrollReset: true };\n\t\t\tif (typeof changes === 'function') {\n\t\t\t\tsetSearchParams(\n\t\t\t\t\t(current) => changes(new URLSearchParams(current)),\n\t\t\t\t\tnavigation,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (changes instanceof URLSearchParams) {\n\t\t\t\tsetSearchParams(new URLSearchParams(changes), navigation);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsetSearchParams((current) => {\n\t\t\t\tconst next = new URLSearchParams(current);\n\t\t\t\tfor (const [key, value] of Object.entries(changes)) {\n\t\t\t\t\tif (value === null || value === undefined) next.delete(key);\n\t\t\t\t\telse next.set(key, value);\n\t\t\t\t}\n\t\t\t\tfor (const key of joined === '' ? [] : joined.split('\\u0000'))\n\t\t\t\t\tnext.delete(key);\n\t\t\t\treturn next;\n\t\t\t}, navigation);\n\t\t},\n\t\t[setSearchParams, joined],\n\t);\n}\n"
|
|
8
9
|
],
|
|
9
|
-
"mappings": "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
|
|
10
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "CCC4EB19C4BC264164756E2164756E21",
|
|
11
12
|
"names": []
|
|
12
13
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** What a loader answers for a failed cursor page: no rows, so `usePagedRows` sets `error`. */
|
|
2
|
+
export declare const FAILED: Readonly<{
|
|
3
|
+
failed: true;
|
|
4
|
+
}>;
|
|
5
|
+
/** The type of {@link FAILED}. */
|
|
6
|
+
export type FailedPage = typeof FAILED;
|
|
7
|
+
/** Whether a loader answer is {@link FAILED}. */
|
|
8
|
+
export declare function isFailedPage(value: unknown): value is FailedPage;
|
|
9
|
+
/**
|
|
10
|
+
* The loader half of `usePagedRows`. Runs `run` and returns its answer.
|
|
11
|
+
*
|
|
12
|
+
* - With no `after` (the first page), an error is rethrown, so the route's
|
|
13
|
+
* `ErrorBoundary` shows.
|
|
14
|
+
* - With an `after` (a "load more"), any error becomes {@link FAILED}, so the
|
|
15
|
+
* rows stay on screen with a retry, except a 3xx `Response` (a redirect),
|
|
16
|
+
* which is rethrown.
|
|
17
|
+
*/
|
|
18
|
+
export declare function orFailed<T>(after: string | null | undefined, run: () => Promise<T> | T): Promise<T | FailedPage>;
|
|
19
|
+
//# sourceMappingURL=or-failed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"or-failed.d.ts","sourceRoot":"","sources":["../../src/lists/or-failed.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,eAAO,MAAM,MAAM;;EAA2C,CAAC;AAE/D,kCAAkC;AAClC,MAAM,MAAM,UAAU,GAAG,OAAO,MAAM,CAAC;AAEvC,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAMhE;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GACvB,OAAO,CAAC,CAAC,GAAG,UAAU,CAAC,CASzB"}
|
|
@@ -5,11 +5,16 @@ export interface SearchChangeOptions {
|
|
|
5
5
|
*/
|
|
6
6
|
readonly resetKeys?: readonly string[];
|
|
7
7
|
}
|
|
8
|
+
/** What `change` takes: a record of changes, a whole search, or an updater. */
|
|
9
|
+
export type SearchChange = Record<string, string | null | undefined> | URLSearchParams | ((prev: URLSearchParams) => URLSearchParams);
|
|
8
10
|
/**
|
|
9
|
-
* Returns `(changes) => void
|
|
10
|
-
* deletes the ones given `null` or `undefined` (an empty
|
|
11
|
-
* so it is set), and deletes `resetKeys` in
|
|
12
|
-
*
|
|
11
|
+
* Returns `(changes) => void`. With a record it sets the parameters it is
|
|
12
|
+
* given a value for, deletes the ones given `null` or `undefined` (an empty
|
|
13
|
+
* string is a value, so it is set), and deletes `resetKeys` in the same step.
|
|
14
|
+
* With a `URLSearchParams` it replaces the whole search by it, and with an
|
|
15
|
+
* updater `(prev) => URLSearchParams` by what the updater returns; both are
|
|
16
|
+
* the caller's whole search, so `resetKeys` does not apply. Every form
|
|
17
|
+
* replaces the history entry and keeps the scroll position.
|
|
13
18
|
*/
|
|
14
|
-
export declare function useSearchChange(options?: SearchChangeOptions): (changes:
|
|
19
|
+
export declare function useSearchChange(options?: SearchChangeOptions): (changes: SearchChange) => void;
|
|
15
20
|
//# sourceMappingURL=use-search-change.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-search-change.d.ts","sourceRoot":"","sources":["../../src/lists/use-search-change.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAID
|
|
1
|
+
{"version":3,"file":"use-search-change.d.ts","sourceRoot":"","sources":["../../src/lists/use-search-change.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAID,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GACzC,eAAe,GACf,CAAC,CAAC,IAAI,EAAE,eAAe,KAAK,eAAe,CAAC,CAAC;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC9B,OAAO,GAAE,mBAAwB,GAC/B,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CA+BjC"}
|
package/docs/guide/errors.md
CHANGED
|
@@ -62,7 +62,8 @@ export function codeOf(error: unknown) {
|
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
An `ApiError` is assignable to `ApiErrorLike` as it is. The shape's code is
|
|
65
|
-
the API's own `<module>.errors.<slug>`, such as `todos.errors.title-taken
|
|
65
|
+
the API's own `<module>.errors.<slug>`, such as `todos.errors.title-taken`
|
|
66
|
+
or `userProfiles.errors.slug-taken` (the module is kebab-case or camelCase).
|
|
66
67
|
|
|
67
68
|
`waitMinutes` reads a throttle's wait the way the API words it: the
|
|
68
69
|
`minutes` of `extensions.params` or beside it, at least one, else the
|
|
@@ -261,7 +262,8 @@ it once, at module scope: a new function re-creates the resolver.
|
|
|
261
262
|
Two options adapt it. `codeOf(issue)` says which code an issue carries (the
|
|
262
263
|
refinement's `params.code` by default); an app that also words an empty field
|
|
263
264
|
as the API's `validation.errors.required` passes its own. `isApiCode(key)`
|
|
264
|
-
says which keys are API codes (`<module>.errors.<slug>` by default
|
|
265
|
+
says which keys are API codes (`<module>.errors.<slug>` by default, the
|
|
266
|
+
module in kebab-case or camelCase).
|
|
265
267
|
|
|
266
268
|
```ts
|
|
267
269
|
import { apiCodeResolver } from '@alxia/react-router/errors';
|
|
@@ -397,8 +399,9 @@ code), `translateCode`'s, nor the entries of `extensions.fields`, which go
|
|
|
397
399
|
straight to the catalogue.
|
|
398
400
|
|
|
399
401
|
`isApiCode` changes what counts as an API code (`<module>.errors.<slug>` by
|
|
400
|
-
default). Here only the codes of the
|
|
401
|
-
other code, even one shaped like a
|
|
402
|
+
default, the module in kebab-case or camelCase). Here only the codes of the
|
|
403
|
+
`todos` module are the catalogue's; any other code, even one shaped like a
|
|
404
|
+
catalogue code, says `refused`:
|
|
402
405
|
|
|
403
406
|
```ts
|
|
404
407
|
export const apiErrors = createApiErrors<'en' | 'fr'>({
|
package/docs/guide/lists.md
CHANGED
|
@@ -47,6 +47,18 @@ meaningless. It navigates with `replace: true` and `preventScrollReset: true`,
|
|
|
47
47
|
so typing does not fill the history nor jump the page. Pass
|
|
48
48
|
`{ resetKeys: ['after', 'page'] }` to drop more.
|
|
49
49
|
|
|
50
|
+
`change` also takes a whole `URLSearchParams`, which replaces the search, or an
|
|
51
|
+
updater `(prev) => URLSearchParams`, for a filter bar or a server table that
|
|
52
|
+
hands back its next parameters. Both navigate exactly as the record form does
|
|
53
|
+
(`replace: true`, `preventScrollReset: true`), and since they are the whole
|
|
54
|
+
search, `resetKeys` does not apply to them: drop the cursor yourself if the
|
|
55
|
+
new search needs it gone.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<Table onParamsChange={(next: URLSearchParams) => change(next)} />;
|
|
59
|
+
change((prev) => new URLSearchParams([...prev].filter(([key]) => key !== 'status')));
|
|
60
|
+
```
|
|
61
|
+
|
|
50
62
|
## `useSameRouteLoading`
|
|
51
63
|
|
|
52
64
|
```tsx
|
|
@@ -107,5 +119,22 @@ with another one a search that keeps the old cursor shows the wrong page.
|
|
|
107
119
|
|
|
108
120
|
A loader that **throws** on a cursor request is not an `error` here: React
|
|
109
121
|
Router sends a fetcher's thrown error to the route's `ErrorBoundary`. To offer
|
|
110
|
-
a retry instead,
|
|
111
|
-
|
|
122
|
+
a retry instead, wrap the read in `orFailed(after, run)`: it answers
|
|
123
|
+
`FAILED` (`{ failed: true }`, a value that is not a page) for a request that
|
|
124
|
+
has the cursor, rethrows when there is none and rethrows a 3xx `Response`.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { orFailed } from '@alxia/react-router/lists';
|
|
128
|
+
|
|
129
|
+
export function loader({ request }: Route.LoaderArgs) {
|
|
130
|
+
const url = new URL(request.url);
|
|
131
|
+
return orFailed(url.searchParams.get('after'), async () => ({
|
|
132
|
+
page: await listUsers({
|
|
133
|
+
q: url.searchParams.get('q') ?? '',
|
|
134
|
+
after: url.searchParams.get('after'),
|
|
135
|
+
}),
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`isFailedPage(loaderData)` narrows the answer when a component reads it.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -539,7 +539,16 @@ name.
|
|
|
539
539
|
|
|
540
540
|
**Why:** React Router sends the error of a fetcher's load to the route's boundary. `usePagedRows` only sees what the fetcher answers, so its `error` is `true` for an answer that is not a page, not for a throw.
|
|
541
541
|
|
|
542
|
-
**Fix:**
|
|
542
|
+
**Fix:** wrap the read in `orFailed(after, run)` from `@alxia/react-router/lists`, which answers `{ failed: true }` for a request that has the cursor and rethrows otherwise (and rethrows a redirect):
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
export function loader({ request }: Route.LoaderArgs) {
|
|
546
|
+
const url = new URL(request.url);
|
|
547
|
+
return orFailed(url.searchParams.get('after'), () => listUsers(url.searchParams));
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
By hand, that is:
|
|
543
552
|
|
|
544
553
|
```ts
|
|
545
554
|
export async function loader({ request }: Route.LoaderArgs) {
|
|
@@ -2419,6 +2428,9 @@ failed before the API's validation layer), or a code that `isApiCode`
|
|
|
2419
2428
|
rejects. Without `refused`, `createApiErrors` says the API's own message for
|
|
2420
2429
|
such an error.
|
|
2421
2430
|
|
|
2431
|
+
Versions before the camelCase patch also rejected a camelCase module
|
|
2432
|
+
(`userProfiles.errors.slug-taken`): upgrade, or pass `isApiCode`.
|
|
2433
|
+
|
|
2422
2434
|
**Fix:** pass `refused`, the app's generic refusal; it replaces the raw
|
|
2423
2435
|
message for every error that is not an API code
|
|
2424
2436
|
([wording a refusal](guide/errors.md#wording-a-refusal)):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alxia/react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
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",
|