Skip to content

feat: allow to control query lifetime#15775

Draft
dummdidumm wants to merge 1 commit into
remote-functions-cachefrom
query-lifetime
Draft

feat: allow to control query lifetime#15775
dummdidumm wants to merge 1 commit into
remote-functions-cachefrom
query-lifetime

Conversation

@dummdidumm
Copy link
Copy Markdown
Member

SvelteKit remote query functions lack the capability to control how long they are fresh, how long they should be kept around after they are no longer used (bfcache behavior), and whether or not they should refresh after a certain time or after navigation. Therefore we introduce a new config-like function on the query function itself: query.lifetime. It is a function with the following signature:

interface QueryLifetime {
  staleAfter?: `${number}m` | `${number}s` | number, // if the client still has a reference to the query somewhere and calls the same query after the stale time it will do a new request. If not set never goes stale
  refreshAfter?: `${number}m` | `${number}s` | number, // if the client has a reference to the query somewhere for more than the specified time it will refresh the query. If not set never refreshes
  refreshOnNavigation?: boolean, // whether or not to do a refresh on client-side navigations. If not set false
  bfcache?: {
    limit: number, // after how many navigations the query should be deleted from the client cache
    maxAge: `${number}m` | `${number}s` | number  // after what time the query should be deleted from the client cache
    } | false // what to do when no reference to the client is held anymore. Default false i.e. deleted from cache directly, else behavior depends on limit/maxAge
  }
query.lifetime(options: QueryLifetime);

... where all the ${number}m | ${number}s | number fields mean "either do a string that says how many seconds or minutes, or pass a number which means how many seconds". Example:

import {query} from '$app/server';
const myQuery = query(() => {
  query.lifetime({ staleAfter: '5s', refreshAfter: '10s', refreshOnNavigation: true, bfcache: { limit: 5, maxAge: '10s' } });
  return someValue();
});

As you can see query.lifetime(...) needs to be called in the context of a query function.

The defaults are:
{ staleAfter: '60m', refreshOnNavigation: false, bfcache: { limit: 5, maxAge: '10m' } }

Implementation-wise this spans the client and the server.

  • On the server it adds a new property lifetime onto RequestState['remote'] which is a Map<string, QueryLifetime> (where string is the id+stringified-payload, similar to e.g. RequestState['remote']['refreshes']). This property is filled by a call to query.lifetime(...).
  • On the client it adds logic to read the config in query.svelte.js and act on it accordingly (i.e. notice when navigations occur, then refresh if needed / evict from bfcache if needed / etc)

closes #15039

builds on top of #15678 (i.e. this PR is the merge target) but only to avoid some merge conflicts I anticipate; this PR should definitely go in after #15678.

SvelteKit remote query functions lack the capability to control how long they are fresh, how long they should be kept around after they are no longer used (bfcache behavior), and whether or not they should refresh after a certain time or after navigation. Therefore we introduce a new config-like function on the query function itself: `query.lifetime`. It is a function with the following signature:

```ts
interface QueryLifetime {
  staleAfter?: `${number}m` | `${number}s` | number, // if the client still has a reference to the query somewhere and calls the same query after the stale time it will do a new request. If not set never goes stale
  refreshAfter?: `${number}m` | `${number}s` | number, // if the client has a reference to the query somewhere for more than the specified time it will refresh the query. If not set never refreshes
  refreshOnNavigation?: boolean, // whether or not to do a refresh on client-side navigations. If not set false
  bfcache?: {
    limit: number, // after how many navigations the query should be deleted from the client cache
    maxAge: `${number}m` | `${number}s` | number  // after what time the query should be deleted from the client cache
    } | false // what to do when no reference to the client is held anymore. Default false i.e. deleted from cache directly, else behavior depends on limit/maxAge
  }
query.lifetime(options: QueryLifetime);
```

... where all the `${number}m` | `${number}s` | number fields mean "either do a string that says how many seconds or minutes, or pass a number which means how many seconds". Example:

```ts
import {query} from '$app/server';
const myQuery = query(() => {
  query.lifetime({ staleAfter: '5s', refreshAfter: '10s', refreshOnNavigation: true, bfcache: { limit: 5, maxAge: '10s' } });
  return someValue();
});
```

As you can see `query.lifetime(...)` needs to be called in the context of a query function.

The defaults are:
`{ staleAfter: '60m', refreshOnNavigation: false, bfcache: { limit: 5, maxAge: '10m' } }`

Implementation-wise this spans the client and the server.
- On the server it adds a new property `lifetime` onto `RequestState['remote']` which is a `Map<string, QueryLifetime>` (where string is the id+stringified-payload, similar to e.g. `RequestState['remote']['refreshes']`). This property is filled by a call to `query.lifetime(...)`.
- On the client it adds logic to read the config in `query.svelte.js` and act on it accordingly (i.e. notice when navigations occur, then refresh if needed / evict from bfcache if needed / etc)
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 29, 2026

⚠️ No Changeset found

Latest commit: 98500a4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@svelte-docs-bot
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant