From 7d381bd4cec942868fc147084a343450023c7c3a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 20:12:09 +0000 Subject: [PATCH 01/13] docs: Show the error, expiry and validation playgrounds' code and preview side by side Row playgrounds now fold fixtures behind the preview header's toggle, so the concept pages whose fixtures carry the lesson can use row too. The playground's row guidance drops that exception. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- docs/core/concepts/error-policy.md | 2 +- docs/core/concepts/expiry-policy.md | 10 +++++----- docs/core/concepts/validation.md | 6 +++--- website/src/components/Playground/index.tsx | 9 ++++----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/core/concepts/error-policy.md b/docs/core/concepts/error-policy.md index 2be89c85f00a..ac04c4154461 100644 --- a/docs/core/concepts/error-policy.md +++ b/docs/core/concepts/error-policy.md @@ -40,7 +40,7 @@ A soft error keeps showing the last data. Hard errors (and soft ones with nothin -); [Controller.expireAll()](../api/Controller.md#expireAll) sets all responses' [expiry status](#expiry-status) matching `testKey` to [Stale](#stale). -); [validateRequired()](/rest/api/validateRequired) can be used to check if all defined fields are present. - { /** Store inspector open by default */ defaultOpen?: 'y' | 'n'; /** Tabs for files + editor beside preview (when wide enough). - * Use when the code is one tall open file and the preview is short. Stay - * stacked when the Store opens by default (it would cover the code), when - * the fixtures are the lesson (row folds them behind a toggle in the - * preview's header), or when the preview renders a long list (it gets tall - * in the narrow column). */ + * Use when the code is one tall open file and the preview is short; row + * folds fixtures behind a toggle in the preview's header. Stay stacked when + * the Store opens by default (it would cover the code), or when the preview + * renders a long list (it gets tall in the narrow column). */ row?: boolean; /** Inactive (e.g. unselected Demo tab): no Monaco until first shown, no preview */ hidden?: boolean; From 2b7a2c59df407b9f7a0f50a9c9831f695e69e2b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 20:16:24 +0000 Subject: [PATCH 02/13] docs: Open each first-two expiry playground on its getUpdated tab Row shows open files as tabs, so getUpdated and TimePage both started selected. defaultTab picks getUpdated, the file carrying the lesson (dataExpiryLength, invalidIfStale), as the StreamManager demo does. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- docs/core/concepts/expiry-policy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/concepts/expiry-policy.md b/docs/core/concepts/expiry-policy.md index 3e1fe73fff06..65dd38e78869 100644 --- a/docs/core/concepts/expiry-policy.md +++ b/docs/core/concepts/expiry-policy.md @@ -62,7 +62,7 @@ to make it becomes [stale](#stale) almost instantly; or a very large number to s Toggling between 'first' and 'second' changes the parameters. If the data is still considered fresh you will continue to see the old time without any refresh. - Date: Thu, 15 Oct 2026 21:01:23 +0000 Subject: [PATCH 03/13] fix(playground): Float row fixtures over the closed Store badge The badge sat at z-index 3 to stay over the Store as it slides in, so it also rose over the fixtures floating across the code. It now sits under them while the Store is closed, and rises above the Store only while the Store panel shows (data-visible, which holds through its slide out). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- website/src/components/Playground/styles.module.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index 925a7a156821..dbcd30a6912c 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -439,11 +439,12 @@ div.playgroundTextEdit > .playgroundHeader, } /* The Store toggle in the code's corner, over the Store as it slides in - under it; its label rises out as the other rises in */ + under it; its label rises out as the other rises in. Closed, it stays + under the fixtures floating over the code (they come later at its level). */ .storeBadge { --swap-in: 0 110%; --swap-out: 0 -110%; - z-index: 3; + z-index: 1; align-self: end; justify-self: end; margin: 0.75rem; @@ -460,6 +461,9 @@ div.playgroundTextEdit > .playgroundHeader, text-transform: uppercase; white-space: nowrap; } +.storeHost:has(> .storePanel[data-visible]) + .storeBadge { + z-index: 3; +} .storeBadge:hover { background: var(--pg-tab-hover-bg) linear-gradient(rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.06)); From b444397038c00a49af316a566f8b0c01557a0465 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:04:24 +0000 Subject: [PATCH 04/13] docs(playground): Say how the Store badge stacks against the fixtures and Store Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- website/src/components/Playground/styles.module.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/src/components/Playground/styles.module.css b/website/src/components/Playground/styles.module.css index dbcd30a6912c..f53de9df0cb8 100644 --- a/website/src/components/Playground/styles.module.css +++ b/website/src/components/Playground/styles.module.css @@ -440,7 +440,8 @@ div.playgroundTextEdit > .playgroundHeader, /* The Store toggle in the code's corner, over the Store as it slides in under it; its label rises out as the other rises in. Closed, it stays - under the fixtures floating over the code (they come later at its level). */ + under the fixtures floating over the code: same z-index as theirs, and the + preview (with them) comes after the editor in the DOM. */ .storeBadge { --swap-in: 0 110%; --swap-out: 0 -110%; @@ -461,6 +462,7 @@ div.playgroundTextEdit > .playgroundHeader, text-transform: uppercase; white-space: nowrap; } +/* over the Store (z-index 2) from opening until it has slid back out */ .storeHost:has(> .storePanel[data-visible]) + .storeBadge { z-index: 3; } From 646f0205066d6a24daca326fec7b2d7226c3f37c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:45:34 +0000 Subject: [PATCH 05/13] docs: Navigate the expiry demos with the playground router The dataExpiryLength and invalidIfStale demos each carried a Navigator that held the id in state and swapped TimePage under its own boundary. Routed Home and TimePage pages replace it: going back to a page still fresh shows the cached time at once, while invalidIfStale makes the navigation wait on a new fetch once the data goes stale. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- docs/core/concepts/expiry-policy.md | 164 +++++++++------------------- 1 file changed, 54 insertions(+), 110 deletions(-) diff --git a/docs/core/concepts/expiry-policy.md b/docs/core/concepts/expiry-policy.md index 65dd38e78869..4f53e56d5fa1 100644 --- a/docs/core/concepts/expiry-policy.md +++ b/docs/core/concepts/expiry-policy.md @@ -59,7 +59,7 @@ When using React Navigation, [focus events](https://reactnavigation.org/docs/use to transition from '[fresh](#fresh)' to '[stale](#stale)' status. Try setting it to a very low number like '50' to make it becomes [stale](#stale) almost instantly; or a very large number to stay around for a long time. -Toggling between 'first' and 'second' changes the parameters. If the data is still considered fresh +Going between the First and Second pages changes the parameters. If the data is still considered fresh you will continue to see the old time without any refresh. - API time for {id}:{' '} - - + ); } ``` -```tsx title="Navigator" collapsed -import React from 'react'; -import { AsyncBoundary } from '@data-client/react'; -import TimePage from './TimePage'; +```tsx title="TimePage" route="/time/:id" +import { useSuspense } from '@data-client/react'; +import { getUpdated } from './getUpdated'; -function Navigator() { - const [id, setId] = React.useState('1'); - const handleChange = e => setId(e.currentTarget.value); +export default function TimePage({ id }: { id: string }) { + const { updatedAt } = useSuspense(getUpdated, { id }); return (
- - + API time for {id}:{' '} +
- loading...
}> - -
Current Time:
); } -render(); ``` ::: :::vue -```html title="TimePage.vue" +```html title="Home.vue" route="/" collapsed + +``` + +```html title="TimePage.vue" route="/time/:id" - - -``` - :::
@@ -255,8 +228,8 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by the component suspending once its data goes stale. If the data is still -within the expiry time it just continues to display it. +This is demonstrated by a page suspending (navigation waits on the progress bar) once its data goes stale. +If the data is still within the expiry time it just continues to display it. - API time for {id}:{' '} - - + ); } ``` -```tsx title="Navigator" collapsed -import React from 'react'; -import { AsyncBoundary } from '@data-client/react'; -import TimePage from './TimePage'; +```tsx title="TimePage" route="/time/:id" +import { useSuspense } from '@data-client/react'; +import { getUpdated } from './getUpdated'; -function Navigator() { - const [id, setId] = React.useState('1'); - const handleChange = e => setId(e.currentTarget.value); +export default function TimePage({ id }: { id: string }) { + const { updatedAt } = useSuspense(getUpdated, { id }); return (
- - + API time for {id}:{' '} +
- loading...
}> - -
Current Time:
); } -render(); ``` ::: :::vue -```html title="TimePage.vue" +```html title="Home.vue" route="/" collapsed + +``` + +```html title="TimePage.vue" route="/time/:id" - - -``` - :::
From f44d0c8a70e66ef3ac78a4b1ba8e0e47bf382486 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:47:15 +0000 Subject: [PATCH 06/13] docs(skills): Regenerate expiry-policy references for the routed demos Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- .../references/expiry-policy.md | 100 +++++++----------- .../references/expiry-policy.vue.md | 82 ++++++-------- 2 files changed, 68 insertions(+), 114 deletions(-) diff --git a/.agents/skills/data-client-rest/references/expiry-policy.md b/.agents/skills/data-client-rest/references/expiry-policy.md index c5c20f474268..79b20b06ba8b 100644 --- a/.agents/skills/data-client-rest/references/expiry-policy.md +++ b/.agents/skills/data-client-rest/references/expiry-policy.md @@ -36,9 +36,11 @@ In these cases it will fetch if the data is considered stale. to transition from '[fresh](#fresh)' to '[stale](#stale)' status. Try setting it to a very low number like '50' to make it becomes [stale](#stale) almost instantly; or a very large number to stay around for a long time. -Toggling between 'first' and 'second' changes the parameters. If the data is still considered fresh +Going between the First and Second pages changes the parameters. If the data is still considered fresh you will continue to see the old time without any refresh. +Files with a `route` are pages mounted at that path, with its params as props; `Link` and `useNavigate()` stand for your router (with react-router, read params with `useParams()`). + ```ts title="api/lastUpdated" import { Entity, RestEndpoint } from '@data-client/rest'; import { Temporal } from 'temporal-polyfill'; @@ -61,54 +63,41 @@ export const lastUpdated = new RestEndpoint({ ```ts title="getUpdated" import { lastUpdated } from './api/lastUpdated'; -export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 }); +export const getUpdated = lastUpdated.extend({ + dataExpiryLength: 10000, +}); ``` -```tsx title="TimePage" -import { useSuspense } from '@data-client/react'; -import { getUpdated } from './getUpdated'; - -export default function TimePage({ id }) { - const { updatedAt } = useSuspense(getUpdated, { id }); +```tsx title="Home" route="/" +export default function Home() { return ( -
- API time for {id}:{' '} - -
+ ); } ``` -```tsx title="Navigator" -import React from 'react'; -import { AsyncBoundary } from '@data-client/react'; -import TimePage from './TimePage'; +```tsx title="TimePage" route="/time/:id" +import { useSuspense } from '@data-client/react'; +import { getUpdated } from './getUpdated'; -function Navigator() { - const [id, setId] = React.useState('1'); - const handleChange = e => setId(e.currentTarget.value); +export default function TimePage({ id }: { id: string }) { + const { updatedAt } = useSuspense(getUpdated, { id }); return (
- - + API time for {id}:{' '} +
- loading...
}> - -
Current Time:
); } -render(); ```
@@ -166,8 +155,10 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](https://dataclient.io/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by the component suspending once its data goes stale. If the data is still -within the expiry time it just continues to display it. +This is demonstrated by a page suspending (navigation waits on the progress bar) once its data goes stale. +If the data is still within the expiry time it just continues to display it. + +Files with a `route` are pages mounted at that path, with its params as props; `Link` and `useNavigate()` stand for your router (with react-router, read params with `useParams()`). ```ts title="api/lastUpdated" import { Entity, RestEndpoint } from '@data-client/rest'; @@ -197,51 +188,36 @@ export const getUpdated = lastUpdated.extend({ }); ``` -```tsx title="TimePage" -import { useSuspense } from '@data-client/react'; -import { getUpdated } from './getUpdated'; - -export default function TimePage({ id }) { - const { updatedAt } = useSuspense(getUpdated, { id }); +```tsx title="Home" route="/" +export default function Home() { return ( -
- API time for {id}:{' '} - -
+ ); } ``` -```tsx title="Navigator" -import React from 'react'; -import { AsyncBoundary } from '@data-client/react'; -import TimePage from './TimePage'; +```tsx title="TimePage" route="/time/:id" +import { useSuspense } from '@data-client/react'; +import { getUpdated } from './getUpdated'; -function Navigator() { - const [id, setId] = React.useState('1'); - const handleChange = e => setId(e.currentTarget.value); +export default function TimePage({ id }: { id: string }) { + const { updatedAt } = useSuspense(getUpdated, { id }); return (
- - + API time for {id}:{' '} +
- loading...
}> - -
Current Time:
); } -render(); ``` ## Force refresh diff --git a/.agents/skills/data-client-rest/references/expiry-policy.vue.md b/.agents/skills/data-client-rest/references/expiry-policy.vue.md index 993a1a6bd0b3..08fa0e7f2b76 100644 --- a/.agents/skills/data-client-rest/references/expiry-policy.vue.md +++ b/.agents/skills/data-client-rest/references/expiry-policy.vue.md @@ -33,9 +33,11 @@ In these cases it will fetch if the data is considered stale. to transition from '[fresh](#fresh)' to '[stale](#stale)' status. Try setting it to a very low number like '50' to make it becomes [stale](#stale) almost instantly; or a very large number to stay around for a long time. -Toggling between 'first' and 'second' changes the parameters. If the data is still considered fresh +Going between the First and Second pages changes the parameters. If the data is still considered fresh you will continue to see the old time without any refresh. +Files with a `route` are pages mounted at that path, with its params as props (vue-router `props: true`). + ```ts title="api/lastUpdated" import { Entity, RestEndpoint } from '@data-client/rest'; import { Temporal } from 'temporal-polyfill'; @@ -58,10 +60,21 @@ export const lastUpdated = new RestEndpoint({ ```ts title="getUpdated" import { lastUpdated } from './api/lastUpdated'; -export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 }); +export const getUpdated = lastUpdated.extend({ + dataExpiryLength: 10000, +}); ``` -```html title="TimePage.vue" +```html title="Home.vue" route="/" + +``` + +```html title="TimePage.vue" route="/time/:id" - - -``` -
@data-client/rest @@ -156,8 +146,10 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](https://dataclient.io/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by the component suspending once its data goes stale. If the data is still -within the expiry time it just continues to display it. +This is demonstrated by a page suspending (navigation waits on the progress bar) once its data goes stale. +If the data is still within the expiry time it just continues to display it. + +Files with a `route` are pages mounted at that path, with its params as props (vue-router `props: true`). ```ts title="api/lastUpdated" import { Entity, RestEndpoint } from '@data-client/rest'; @@ -187,7 +179,16 @@ export const getUpdated = lastUpdated.extend({ }); ``` -```html title="TimePage.vue" +```html title="Home.vue" route="/" + +``` + +```html title="TimePage.vue" route="/time/:id" - - -``` - ## Force refresh We sometimes want to fetch new data; while continuing to show the old (stale) data. From 37583694d257ff2e6be55aa7a45b58d03440e1b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 21:49:26 +0000 Subject: [PATCH 07/13] docs: Keep the playground's progress bar out of the Vue expiry prose Vue examples don't run, and the progress bar is the React playground router's, so only React readers get that aside. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- .agents/skills/data-client-rest/references/expiry-policy.md | 2 +- .agents/skills/data-client-rest/references/expiry-policy.vue.md | 2 +- docs/core/concepts/expiry-policy.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.agents/skills/data-client-rest/references/expiry-policy.md b/.agents/skills/data-client-rest/references/expiry-policy.md index 79b20b06ba8b..eeda2a0704b5 100644 --- a/.agents/skills/data-client-rest/references/expiry-policy.md +++ b/.agents/skills/data-client-rest/references/expiry-policy.md @@ -155,7 +155,7 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](https://dataclient.io/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by a page suspending (navigation waits on the progress bar) once its data goes stale. +This is demonstrated by a page suspending once its data goes stale (navigation waits on the progress bar). If the data is still within the expiry time it just continues to display it. Files with a `route` are pages mounted at that path, with its params as props; `Link` and `useNavigate()` stand for your router (with react-router, read params with `useParams()`). diff --git a/.agents/skills/data-client-rest/references/expiry-policy.vue.md b/.agents/skills/data-client-rest/references/expiry-policy.vue.md index 08fa0e7f2b76..b521231d0313 100644 --- a/.agents/skills/data-client-rest/references/expiry-policy.vue.md +++ b/.agents/skills/data-client-rest/references/expiry-policy.vue.md @@ -146,7 +146,7 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](https://dataclient.io/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by a page suspending (navigation waits on the progress bar) once its data goes stale. +This is demonstrated by a page suspending once its data goes stale. If the data is still within the expiry time it just continues to display it. Files with a `route` are pages mounted at that path, with its params as props (vue-router `props: true`). diff --git a/docs/core/concepts/expiry-policy.md b/docs/core/concepts/expiry-policy.md index 4f53e56d5fa1..ff783e5b1379 100644 --- a/docs/core/concepts/expiry-policy.md +++ b/docs/core/concepts/expiry-policy.md @@ -228,7 +228,7 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by a page suspending (navigation waits on the progress bar) once its data goes stale. +This is demonstrated by a page suspending once its data goes stale:react[ (navigation waits on the progress bar)]. If the data is still within the expiry time it just continues to display it. Date: Fri, 16 Oct 2026 00:37:05 +0000 Subject: [PATCH 08/13] docs: Keep the expiry demos' links and both times on every page The routed expiry demos showed only two links on the first page, and the time pages lost the links and the clock beside the API time. The playground router now takes a layout fence that wraps every page and stays mounted, and a start path, so the Navigator keeps First and Second (the current one marked) above the API time and Current Time throughout. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_016p6cSc9eX8eeud332Upv5b --- .../references/expiry-policy.md | 116 +++++----- .../references/expiry-policy.vue.md | 122 ++++++---- docs/core/concepts/expiry-policy.md | 214 ++++++++++-------- website/src/components/Playground/README.md | 7 +- .../components/Playground/editor/codeModel.ts | 7 + .../preview/__tests__/router.test.tsx | 49 ++++ .../components/Playground/preview/router.tsx | 68 ++++-- .../components/Playground/styles.module.css | 26 +++ 8 files changed, 384 insertions(+), 225 deletions(-) diff --git a/.agents/skills/data-client-rest/references/expiry-policy.md b/.agents/skills/data-client-rest/references/expiry-policy.md index eeda2a0704b5..ad688b771057 100644 --- a/.agents/skills/data-client-rest/references/expiry-policy.md +++ b/.agents/skills/data-client-rest/references/expiry-policy.md @@ -36,7 +36,7 @@ In these cases it will fetch if the data is considered stale. to transition from '[fresh](#fresh)' to '[stale](#stale)' status. Try setting it to a very low number like '50' to make it becomes [stale](#stale) almost instantly; or a very large number to stay around for a long time. -Going between the First and Second pages changes the parameters. If the data is still considered fresh +Toggling between 'first' and 'second' changes the parameters. If the data is still considered fresh you will continue to see the old time without any refresh. Files with a `route` are pages mounted at that path, with its params as props; `Link` and `useNavigate()` stand for your router (with react-router, read params with `useParams()`). @@ -63,22 +63,10 @@ export const lastUpdated = new RestEndpoint({ ```ts title="getUpdated" import { lastUpdated } from './api/lastUpdated'; -export const getUpdated = lastUpdated.extend({ - dataExpiryLength: 10000, -}); +export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 }); ``` -```tsx title="Home" route="/" -export default function Home() { - return ( - - ); -} -``` - -```tsx title="TimePage" route="/time/:id" +```tsx title="TimePage" route="/time/:id" start="/time/1" import { useSuspense } from '@data-client/react'; import { getUpdated } from './getUpdated'; @@ -86,12 +74,24 @@ export default function TimePage({ id }: { id: string }) { const { updatedAt } = useSuspense(getUpdated, { id }); return (
-
- API time for {id}:{' '} - -
+ API time for {id}:{' '} + +
+ ); +} +``` + +```tsx title="Navigator" layout +export default function Navigator({ children }) { + return ( +
+ + {children}
Current Time:
@@ -107,16 +107,10 @@ export default function TimePage({ id }: { id: string }) { Long cache lifetime ```typescript title="LongLivingResource.ts" -import { - RestEndpoint, - RestGenerics, - resource, -} from '@data-client/rest'; +import { RestEndpoint, RestGenerics, resource } from '@data-client/rest'; // We can now use LongLivingEndpoint to create endpoints that will be cached for one hour -class LongLivingEndpoint< - O extends RestGenerics, -> extends RestEndpoint { +class LongLivingEndpoint extends RestEndpoint { dataExpiryLength = 60 * 60 * 1000; // one hour } @@ -129,16 +123,10 @@ const LongLivingResource = resource({ Never retry on error ```typescript title="NoRetryResource.ts" -import { - RestEndpoint, - RestGenerics, - resource, -} from '@data-client/rest'; +import { RestEndpoint, RestGenerics, resource } from '@data-client/rest'; // We can now use NoRetryEndpoint to create endpoints that will be cached for one hour -class NoRetryEndpoint< - O extends RestGenerics, -> extends RestEndpoint { +class NoRetryEndpoint extends RestEndpoint { errorExpiryLength = Infinity; } @@ -155,7 +143,7 @@ const NoRetryResource = resource({ [Endpoint.invalidIfStale](https://dataclient.io/rest/api/Endpoint#invalidifstale) eliminates the '[stale](#stale)' status, making data that expires immediately be considered '[invalid](#invalid)'. -This is demonstrated by a page suspending once its data goes stale (navigation waits on the progress bar). +This is demonstrated by the page suspending once its data goes stale (navigation waits on the progress bar). If the data is still within the expiry time it just continues to display it. Files with a `route` are pages mounted at that path, with its params as props; `Link` and `useNavigate()` stand for your router (with react-router, read params with `useParams()`). @@ -188,17 +176,7 @@ export const getUpdated = lastUpdated.extend({ }); ``` -```tsx title="Home" route="/" -export default function Home() { - return ( - - ); -} -``` - -```tsx title="TimePage" route="/time/:id" +```tsx title="TimePage" route="/time/:id" start="/time/1" import { useSuspense } from '@data-client/react'; import { getUpdated } from './getUpdated'; @@ -206,12 +184,24 @@ export default function TimePage({ id }: { id: string }) { const { updatedAt } = useSuspense(getUpdated, { id }); return (
-
- API time for {id}:{' '} - -
+ API time for {id}:{' '} + +
+ ); +} +``` + +```tsx title="Navigator" layout +export default function Navigator({ children }) { + return ( +
+ + {children}
Current Time:
@@ -424,9 +414,7 @@ function Demo() { -
@@ -478,7 +466,11 @@ export default function TimePage({ id }) { ```tsx title="ShowTime" import { Invalidate, RestEndpoint } from '@data-client/rest'; -import { AsyncBoundary, useController, useLoading } from '@data-client/react'; +import { + AsyncBoundary, + useController, + useLoading, +} from '@data-client/react'; import { TimedEntity } from './api/lastUpdated'; import TimePage from './TimePage'; @@ -508,11 +500,7 @@ function ShowTime() {