From f4c1efbe63663a933b5cc555c28dcf2df648346f Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Wed, 30 Oct 2024 03:43:48 +0100 Subject: [PATCH] Add deprecation warning for `cache` and add entry for `query` (#937) --- scripts/collections/build-file-tree.mjs | 3 +- scripts/create-i18n-entries.mjs | 5 +- scripts/create-i18n-tree.mjs | 5 +- src/routes/guides/routing-and-navigation.mdx | 4 +- .../reference/data-apis/cache.mdx | 7 +- .../reference/data-apis/data.json | 1 + .../reference/data-apis/query.mdx | 119 ++++++++++++++++++ .../reference/data-apis/response-helpers.mdx | 8 +- src/routes/solid-start/advanced/auth.mdx | 4 +- src/routes/solid-start/advanced/session.mdx | 4 +- .../data-loading.mdx | 14 +-- .../reference/server/http-status-code.mdx | 4 +- .../reference/server/use-server.mdx | 2 +- src/ui/layout/main-navigation.tsx | 2 + 14 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 src/routes/solid-router/reference/data-apis/query.mdx diff --git a/scripts/collections/build-file-tree.mjs b/scripts/collections/build-file-tree.mjs index 221308d5e..627fb0beb 100644 --- a/scripts/collections/build-file-tree.mjs +++ b/scripts/collections/build-file-tree.mjs @@ -44,7 +44,7 @@ export async function buildFileTree(entry = COLLECTIONS_ROOT) { const file = await fs.readFile(entryPath, "utf-8"); const parentSection = await getDirData(path.resolve(parentSegment)); - const { title, mainNavExclude } = matter(file).data; + const { title, mainNavExclude, isDeprecated } = matter(file).data; /** * @todo @@ -63,6 +63,7 @@ export async function buildFileTree(entry = COLLECTIONS_ROOT) { parent: parentSection.title, title, mainNavExclude, + isDeprecated, isTranslated: true, }; } else { diff --git a/scripts/create-i18n-entries.mjs b/scripts/create-i18n-entries.mjs index 8b607848f..640a80238 100644 --- a/scripts/create-i18n-entries.mjs +++ b/scripts/create-i18n-entries.mjs @@ -40,12 +40,15 @@ async function buildSectionList(entryList = [], locale, project = "") { ); if (existsSync(i18nEntryPath + ".mdx")) { - const { title } = await getFrontMatterData(i18nEntryPath + ".mdx"); + const { title, isDeprecated } = await getFrontMatterData( + i18nEntryPath + ".mdx" + ); sectionList.push({ ...entry, path: path.join(locale, entry.path), title, + isDeprecated, isTranslated: true, }); } else { diff --git a/scripts/create-i18n-tree.mjs b/scripts/create-i18n-tree.mjs index c298ed80b..d3d3c43c2 100644 --- a/scripts/create-i18n-tree.mjs +++ b/scripts/create-i18n-tree.mjs @@ -70,9 +70,12 @@ export async function createI18nTree(entryList, locale, project = "") { ); if (existsSync(i18nEntryPath + ".mdx")) { - const { title } = await getFrontMatterData(i18nEntryPath + ".mdx"); + const { title, isDeprecated } = await getFrontMatterData( + i18nEntryPath + ".mdx" + ); entries.push({ ...entry, + isDeprecated, isTranslated: true, title, path: `/${locale}${entry.path}`, diff --git a/src/routes/guides/routing-and-navigation.mdx b/src/routes/guides/routing-and-navigation.mdx index 8d45e167e..9c32fe9da 100644 --- a/src/routes/guides/routing-and-navigation.mdx +++ b/src/routes/guides/routing-and-navigation.mdx @@ -465,9 +465,9 @@ This pattern provides a way to import the data function without loading anything ```jsx // src/pages/users/[id].data.js -import { cache } from "@solidjs/router"; +import { query } from "@solidjs/router"; -export const getUser = cache(async (id) => { +export const getUser = query(async (id) => { return (await fetch(`https://swapi.tech/api/people/${id}/`)).json(); }, "getUser"); diff --git a/src/routes/solid-router/reference/data-apis/cache.mdx b/src/routes/solid-router/reference/data-apis/cache.mdx index de43c7f3b..845d15ff3 100644 --- a/src/routes/solid-router/reference/data-apis/cache.mdx +++ b/src/routes/solid-router/reference/data-apis/cache.mdx @@ -1,7 +1,12 @@ --- title: cache +isDeprecated: true --- + +This API is deprecated since `v0.15.0` of Solid-Router. Use [query](/solid-router/reference/data-apis/query) instead. It will be removed in an upcoming release. + + `cache` is a [higher-order function](https://en.wikipedia.org/wiki/Higher-order_function) designed to create a new function with the same signature as the function passed to it. When this newly created function is called for the first time with a specific set of arguments, the original function is run, and its return value is stored in a cache and returned to the caller of the created function. The next time the created function is called with the same arguments (as long as the cache is still valid), it will return the cached value instead of re-executing the original function. @@ -16,7 +21,7 @@ However, using `cache` directly in [`createResource`](/reference/basic-reactivit ## Usage ```js -const getUser = cache( +const getUser = query( (id, options = {}) => fetch(`/api/users/${id}?summary=${options.summary || false}`).then((r) => r.json() diff --git a/src/routes/solid-router/reference/data-apis/data.json b/src/routes/solid-router/reference/data-apis/data.json index 815133e33..60902fdb2 100644 --- a/src/routes/solid-router/reference/data-apis/data.json +++ b/src/routes/solid-router/reference/data-apis/data.json @@ -5,6 +5,7 @@ "cache.mdx", "create-async.mdx", "create-async-store.mdx", + "query.mdx", "response-helpers.mdx" ] } diff --git a/src/routes/solid-router/reference/data-apis/query.mdx b/src/routes/solid-router/reference/data-apis/query.mdx new file mode 100644 index 000000000..1ffba7338 --- /dev/null +++ b/src/routes/solid-router/reference/data-apis/query.mdx @@ -0,0 +1,119 @@ +--- +title: query +--- + +`query` is a [higher-order function](https://en.wikipedia.org/wiki/Higher-order_function) designed to create a new function with the same signature as the function passed to it. +When this newly created function is called for the first time with a specific set of arguments, the original function is run, and its return value is stored in a cache and returned to the caller of the created function. +The next time the created function is called with the same arguments (as long as the cache is still valid), it will return the cached value instead of re-executing the original function. + + +`query` can be defined anywhere and then used inside your components with [`createAsync`](/solid-router/reference/data-apis/create-async). + +However, using `query` directly in [`createResource`](/reference/basic-reactivity/create-resource) will not work since the fetcher is not reactive and will not invalidate properly. + + + +## Usage + +```js +const getUser = query( + (id, options = {}) => + fetch(`/api/users/${id}?summary=${options.summary || false}`).then((r) => + r.json() + ), + "usersById" +); + +getUser(123); // Causes a GET request to /api/users/123?summary=false +getUser(123); // Does not cause a GET request +getUser(123, { summary: true }); // Causes a GET request to /api/users/123?summary=true +setTimeout(() => getUser(123, { summary: true }), 999000); // Eventually causes another GET request to /api/users/123?summary=true +``` + +### With preload functions + +Using it with a [preload function](/solid-router/reference/preload-functions/preload): + +```js +import { lazy } from "solid-js"; +import { Route } from "@solidjs/router"; +import { getUser } from ... // the cache function + +const User = lazy(() => import("./pages/users/[id].js")); + +// preload function +function preloadUser({params, location}) { + void getUser(params.id) +} + +// Pass it in the route definition +; +``` + +### Inside a route's component + +Using it inside a route's component: + +```jsx +// pages/users/[id].js +import { getUser } from ... // the cache function + +export default function User(props) { + const user = createAsync(() => getUser(props.params.id)); + return

{user().name}

; +} +``` + +## Query Function Capabilities + +`query` accomplishes the following: + +1. Deduping on the server for the lifetime of the request. +2. It fills a preload cache in the browser - this lasts 5 seconds. + When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls. +3. A reactive refetch mechanism based on key. + This prevents routes that are not new from retriggering on action revalidation. +4. Serve as a back/forward cache for browser navigation for up to 5 minutes. + Any user based navigation or link click bypasses this cache. + Revalidation or new fetch updates the cache. + +## Cache Keys + +To ensure that the cache keys are consistent and unique, arguments are deterministically serialized using `JSON.stringify`. +Before serialization, key/value pairs in objects are sorted so that the order of properties does not affect the serialization. +For instance, both `{ name: 'Ryan', awesome: true }` and `{ awesome: true, name: 'Ryan' }` will serialize to the same string so that they produce the same cache key. + +## Return value + +The return value is a `CachedFunction`, a function that has the same signature as the function you passed to `cache`. +This cached function stores the return value using the cache key. +Under most circumstances, this temporarily prevents the passed function from running with the same arguments, even if the created function is called repeatedly. + +## Arguments + +| argument | type | description | +| -------- | ----------------------- | ------------------------------------------------------------------------- | +| `fn` | `(...args: any) => any` | A function whose return value you'd like to be cached. | +| `name`\* | string | Any arbitrary string that you'd like to use as the rest of the cache key. | + +\*Since the internal cache is shared by all the functions using `query`, the string should be unique for each function passed to `query`. +If the same key is used with multiple functions, one function might return the cached result of the other. + +## Methods + +### `.key` and `.keyFor` + +Query functions provide `.key` and `.keyFor`, are useful when retrieving the keys used in cases involving invalidation: + +```ts +let id = 5; +getUser.key; // returns "users" +getUser.keyFor(id); // returns "users[5]" +``` + +### `revalidate` + +The `query` can be revalidated using the `revalidate` method or the `revalidate` keys that are set on the response from the actions. +If the entire key is passed, it will invalidate all entries for the cache (ie. `users` in the example above). +If only a single entry needs to be invalidated, `keyFor` is provided. +To revalidate everything in the cache, pass `undefined` as the key. diff --git a/src/routes/solid-router/reference/data-apis/response-helpers.mdx b/src/routes/solid-router/reference/data-apis/response-helpers.mdx index 2823cd61f..cde1e2e12 100644 --- a/src/routes/solid-router/reference/data-apis/response-helpers.mdx +++ b/src/routes/solid-router/reference/data-apis/response-helpers.mdx @@ -12,7 +12,7 @@ Signature: `redirect(path, options)` Redirects to the next route. ```js -const getUser = cache(() => { +const getUser = query(() => { const user = await api.getCurrentUser(); if (!user) throw redirect("/login"); return user; @@ -26,7 +26,7 @@ Signature: `reload(options)` Reloads the data at the given cache key on the current route. ```js -const getTodo = cache(async (id: number) => { +const getTodo = query(async (id: number) => { const todo = await fetchTodo(id); return todo; }, "todo"); @@ -37,14 +37,14 @@ const updateTodo = action(async (todo: Todo) => { }); ``` -## Json +## json Signature: `json(data, options)` Returns JSON data from an action while also providing options for controlling revalidation of cache data on the route. ```js -const getTodo = cache(async (id: number) => { +const getTodo = query(async (id: number) => { const todo = await fetchTodo(id); return todo; }, "todo"); diff --git a/src/routes/solid-start/advanced/auth.mdx b/src/routes/solid-start/advanced/auth.mdx index c0081644a..fb7f8bd2c 100644 --- a/src/routes/solid-start/advanced/auth.mdx +++ b/src/routes/solid-start/advanced/auth.mdx @@ -22,10 +22,10 @@ The `getUser` function can be [implemented using sessions](/solid-start/advanced ## Protected Routes Routes can be protected by checking the user or session object during data fetching. -This example uses [Solid Router](/solid-router). +This example uses [Solid-Router](/solid-router). ```tsx -const getPrivatePosts = cache(async function() { +const getPrivatePosts = query(async function() { "use server" const user = await getUser() if(!user) { diff --git a/src/routes/solid-start/advanced/session.mdx b/src/routes/solid-start/advanced/session.mdx index 7d1f8c733..3c6beeabd 100644 --- a/src/routes/solid-start/advanced/session.mdx +++ b/src/routes/solid-start/advanced/session.mdx @@ -45,9 +45,9 @@ Additionally, you can use it with [`cache`](/solid-router/reference/data-apis/ca That way if the user is not authenticated, the request will be redirected to the login page. ```tsx title="/routes/api/store/admin.ts" -import { cache, createAsync, redirect } from "@solidjs/router"; +import { query, createAsync, redirect } from "@solidjs/router"; -const getUsers = cache(async (id: string) => { +const getUsers = query(async (id: string) => { "use server"; const user = await getUser(); if (!user) throw redirect("/login"); diff --git a/src/routes/solid-start/building-your-application/data-loading.mdx b/src/routes/solid-start/building-your-application/data-loading.mdx index 3281d498d..0a73dc3a6 100644 --- a/src/routes/solid-start/building-your-application/data-loading.mdx +++ b/src/routes/solid-start/building-your-application/data-loading.mdx @@ -59,7 +59,7 @@ import { createAsync, cache } from "@solidjs/router"; type User = { name: string; email: string }; -const getUsers = cache(async () => { +const getUsers = query(async () => { const response = await fetch("https://example.com/users"); return (await response.json()) as User[]; }, "users"); @@ -79,9 +79,9 @@ export default function Page() {
```tsx title="/routes/users.jsx" {4, 7, 10} import { For } from "solid-js"; -import { createAsync, cache } from "@solidjs/router"; +import { createAsync, query } from "@solidjs/router"; -const getUsers = cache(async () => { +const getUsers = query(async () => { const response = await fetch("https://example.com/users"); return (await response.json()); }, "users"); @@ -123,11 +123,11 @@ For example, it could be database access or internal APIs, or when you sit withi
```tsx title="/routes/users.tsx" {7} import { For } from "solid-js"; -import { createAsync, cache } from "@solidjs/router"; +import { createAsync, query } from "@solidjs/router"; type User = { name: string; email: string }; -const getUsers = cache(async () => { +const getUsers = query(async () => { "use server"; return store.users.list(); }, "users"); @@ -148,9 +148,9 @@ export default function Page() {
```tsx title="/routes/users.jsx" {5} import { For } from "solid-js"; -import { createAsync, cache } from "@solidjs/router"; +import { createAsync, query } from "@solidjs/router"; -const getUsers = cache(async () => { +const getUsers = query(async () => { "use server"; return store.users.list(); }, "users"); diff --git a/src/routes/solid-start/reference/server/http-status-code.mdx b/src/routes/solid-start/reference/server/http-status-code.mdx index 63f471446..71b9d9b3a 100644 --- a/src/routes/solid-start/reference/server/http-status-code.mdx +++ b/src/routes/solid-start/reference/server/http-status-code.mdx @@ -48,10 +48,10 @@ It is important to add `deferStream` to any resources calls that need to be load ```tsx {7,17-19, 15, 23} title="routes/[house].tsx" import { Show, ErrorBoundary } from "solid-js"; -import { cache, createAsync } from "@solidjs/router"; +import { query, createAsync } from "@solidjs/router"; import { HttpStatusCode } from "@solidjs/start"; -const getHouse = cache(async (house: string) => { +const getHouse = query(async (house: string) => { if (house != "gryffindor") { throw new Error("House not found"); } diff --git a/src/routes/solid-start/reference/server/use-server.mdx b/src/routes/solid-start/reference/server/use-server.mdx index 6a82c8b38..509e4b5ea 100644 --- a/src/routes/solid-start/reference/server/use-server.mdx +++ b/src/routes/solid-start/reference/server/use-server.mdx @@ -48,7 +48,7 @@ Server functions can be used for fetching data and performing actions on the ser The following examples show how to use server functions alongside solid-router's data APIs. ```tsx {3} -const getUser = cache((id) => { +const getUser = query((id) => { "use server"; return db.getUser(id); }, "users"); diff --git a/src/ui/layout/main-navigation.tsx b/src/ui/layout/main-navigation.tsx index f9130552e..c378205ac 100644 --- a/src/ui/layout/main-navigation.tsx +++ b/src/ui/layout/main-navigation.tsx @@ -14,6 +14,7 @@ interface Entry { children?: Entry[]; mainNavExclude: boolean; isTranslated?: boolean; + isDeprecated?: boolean; } type EntryList = { learn: Entry[]; reference: Entry[] }; @@ -53,6 +54,7 @@ function ListItemLink(props: { item: Entry }) { class={`hover:text-blue-700 dark:hover:text-blue-300 block w-full lg:text-sm pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full ${linkStyles()}`} > {props.item.title} + (deprecated)