diff --git a/assets/search-index.json b/assets/search-index.json index aba652ce..225262cc 100644 --- a/assets/search-index.json +++ b/assets/search-index.json @@ -2656,7 +2656,7 @@ "title": "Queries", "searchTitle": "Queries", "url": "/docs/queries", - "content": "Queries are how you read and sync data with Zero. Here's a simple example: // src/queries.ts import {defineQueries, defineQuery} from '@rocicorp/zero' import {z} from 'zod' import {zql} from 'schema.ts' export const queries = defineQueries({ postsByAuthor: defineQuery( z.object({authorID: z.string()}), ({args: {authorID}}) => zql.post.where('authorID', authorID) ) }) Architecture A copy of each query exists on both the client and on your server: Often the implementations will be the same, and you can just share their code. This is easy with full-stack frameworks like TanStack Start or Next.js. But the implementations don't have to be the same, or even compute the same result. For example, the server can add extra filters to enforce permissions that the client query does not. Life of a Query When a query is invoked, it initially runs on the client, against the client-side datastore. Any matching data is returned immediately and the user sees instant results. In the background, the name and arguments for the query are sent to zero-cache. Zero-cache calls the queries endpoint on your server to get the ZQL for the query. Your server looks up its implementation of the query, invokes it, and returns the resulting ZQL expression to zero-cache. Zero-cache then runs this ZQL against the server-side data. The initial server result is sent back to the client and the client query updates in response. zero-cache receives updates from Postgres via logical replication. It updates affected queries and sends row changes back to the client, which updates the client query, and the user sees the changes. Defining Queries Basics Create a query using defineQuery. The only required argument is a QueryFn, which must return a ZQL expression: import {zql} from 'schema.ts' const allPostsQueryDef = defineQuery(() => zql.post) Arguments The QueryFn can take a single args parameter. To enable this, pass a validator to defineQuery: import {zql} from 'schema.ts' const postsByAuthor = defineQuery( z.object({authorID: z.string().optional()}), ({args: {authorID}}) => { let q = zql.post if (authorID !== undefined) { q = q.where('authorID', authorID) } return q } ) We use Zod in these examples, but you can use any validation library that implements Standard Schema. Zero queries run on both the client and on your server. In the server case, the parameters come from the client and are untrusted. The validator ensures the data passed to your query is of the expected type. Query Registries The result of defineQuery is a QueryDefinition. By itself this isn't super useful. You need to register it using defineQueries: export const queries = defineQueries({ posts: { all: allPostsQueryDef } }) Typically these are done together in one step: export const queries = defineQueries({ posts: { all: defineQuery(() => zql.post) } }) The result of defineQueries is called a QueryRegistry. Each field in the registry is a callable Query that you can use to read data: import {zero} from 'zero.ts' import {queries} from 'queries.ts' const allPosts = await zero.run(queries.posts.all()) Query Names Each Query has a queryName which is computed by defineQueries. This name is later sent to your server to identify the query to run: console.log(queries.posts.all.queryName) // \"posts.all\" Context Query parameters are supplied by the client application and passed to the server automatically by Zero. This makes them unsuitable for credentials, since the user could modify them. For this reason, Zero queries also support the concept of a context object. Access your context with the ctx parameter to your query: const myPostsQuery = defineQuery(({ctx: {userID}}) => { // User cannot control context.userID, so this safely // restricts the query to the user's own posts. return zql.post.where('authorID', userID) }) If you don't want to register your Context and Schema types globally, you can use defineQueryWithType and defineQueriesWithType: import { defineQueriesWithType, defineQueryWithType } from '@rocicorp/zero' import type {Schema} from 'schema.ts' import type {ZeroContext} from 'context.ts' const defineQuery = defineQueryWithType< Schema, ZeroContext >() const defineQueries = defineQueriesWithType() queries.ts By convention, all queries for an application are listed in a central queries.ts file. This allows them to be easily used on both the client and server: import {defineQueries, defineQuery} from '@rocicorp/zero' import {z} from 'zod' import {zql} from './schema.ts' export const queries = defineQueries({ posts: { get: defineQuery(z.string(), id => zql.post.where('id', id) ), byAuthor: defineQuery( z.object({ authorID: z.string(), includeDrafts: z.boolean().optional() }), ({args: {authorID, includeDrafts}}) => { let q = zql.post.where('authorID', authorID) if (!includeDrafts) { q = q.where('isDraft', false) } return q } ) } }) You can use as many levels of nesting as you want to organize your queries. As your application grows, you can move queries to different files to keep them organized: // posts.ts export const postQueries = { get: defineQuery(z.string(), id => zql.post.where('id', id) ) // ... } // users.ts export const userQueries = { byRole: defineQuery(z.string(), role => zql.user.where('role', role) ) // ... } // queries.ts import {postQueries} from './posts.ts' import {userQueries} from './users.ts' export const queries = defineQueries({ posts: postQueries, users: userQueries }) Because defineQueries establishes the full name for each query (i.e., posts.get, users.byRole), it should only be used once at the top level of your queries.ts file. Server Setup In order for queries to sync, you must provide an implementation of the query endpoint on your server. zero-cache calls this endpoint to resolve each query to ZQL that it can run. Registering the Endpoint Use ZERO_QUERY_URL to tell zero-cache where to find your query implementation: export ZERO_QUERY_URL=\"http://localhost:3000/api/zero/query\" # run zero-cache, e.g. `npx zero-cache-dev` Implementing the Endpoint You can use the handleQueryRequest and mustGetQuery functions to implement the endpoint. // src/routes/api/zero/query.ts import {createFileRoute} from '@tanstack/react-router' import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' export const Route = createFileRoute('/api/zero/query')({ server: { handlers: { POST: async ({request}) => { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request, userID: null }) return Response.json(result) } } } })// app/api/zero/query/route.ts import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' export async function POST(request: Request) { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request, userID: null }) return Response.json(result) }// src/routes/api/zero/query.ts import type {APIEvent} from '@solidjs/start/server' import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' export async function POST(event: APIEvent) { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request: event.request, userID: null }) return Response.json(result) }// api/app.ts import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' app.post('/api/zero/query', async c => { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request: c.req.raw, userID: null }) return c.json(result) }) handleQueryRequest accepts a standard Request and returns a JSON object which can be serialized and returned by your server framework of choice. mustGetQuery looks up the query in the registry and throws an error if not found. The query.fn function is your query implementation wrapped in the validator you provided. These examples have only public queries, so they do not pass a context. In authenticated apps, validate auth in the request, derive context from the session, and pass it to the query handler. See Authentication. Custom Query URL By default, Zero sends queries to the URL specified in the ZERO_QUERY_URL parameter in the zero-cache config. However you can customize this on a per-client basis. To do so, list multiple comma-separated URLs in ZERO_QUERY_URL: ZERO_QUERY_URL='https://api.example.com/query,https://api.staging.example.com/query' Then choose one of those URLs by passing it to queryURL on the Zero constructor: const zero = new Zero({ schema, queries, queryURL: 'https://api.staging.example.com/query' }) URL Patterns The strings listed in ZERO_QUERY_URL can also be URLPatterns: ZERO_QUERY_URL=\"https://mybranch-*.preview.myapp.com/query\" This queries URL will allow clients to choose URLs like: https://mybranch-aaa.preview.myapp.com/query ✅ https://mybranch-bbb.preview.myapp.com/query ✅ But rejects URLs like: https://preview.myapp.com/query ❌ (missing subdomain) https://malicious.com/query ❌ (different domain) https://mybranch-123.preview.myapp.com/query/extra ❌ (extra path) https://mybranch-123.preview.myapp.com/other ❌ (different path) Because URLPattern is a web standard, you can test them right in your browser: For more information, see the URLPattern docs. If you're configuring per-branch preview URLs (for example on Vercel), see Preview Deployments for the complete setup across both query and mutate endpoints. Running Queries Reactively The most common way to use queries is with the useQuery reactive hooks from the React or SolidJS bindings (or the equivalent low-level API): import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(queries.posts.get('user123')) return posts.map(post => (
{post.title}
)) }import {useQuery} from '@rocicorp/zero/solid' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(() => queries.posts.get('user123') ) return ( {post =>
{post.title}
}
) }import {queries} from 'zero/queries.ts' import {zero} from 'zero.ts' const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) for (let post of postsView.data) { console.log(post.title) } // updates as the underlying data changes postsView.addListener(posts => { console.log('posts', posts) }) These functions allow you to automatically re-render UI when a query changes. Conditionally Sometimes the inputs needed to construct a query are not available on the first render. For example, auth state or a route param might still be loading after a page refresh. Both React and Solid support conditional queries by passing undefined until the query can be constructed: import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function Username({userID}: {userID: string | undefined}) { const [user] = useQuery( userID ? queries.users.getUser({ userID }) : undefined ) return user ?
{user.username}
: null }import {useQuery} from '@rocicorp/zero/solid' import {Show} from 'solid-js' import {queries} from 'zero/queries.ts' function Username(props: {userID: string | undefined}) { const [user] = useQuery(() => props.userID ? queries.users.getUser({ userID: props.userID }) : undefined ) return ( {user =>
{user().username}
}
) } Once You usually want to subscribe to a query in a reactive UI, but every so often you'll need to run a query just once. To do this, use zero.run(): const results = await zero.run( queries.issues.byPriority('high') ) By default, run() only returns results that are currently available on the client. That is, it returns the data that would be given for result.type === 'unknown'. If you want to wait for the server to return results, pass {type: 'complete'} to run: const results = await zero.run( queries.issues.byPriority('high'), {type: 'complete'} ) For Preloading Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. Because preload queries are often much larger than a screenful of UI, Zero provides a special zero.preload() method to avoid the overhead of materializing the result into JS objects: // Preload a large number of the inbox query results. zero.preload( queries.issues.inbox({ sort: 'created', sortDirection: 'desc', limit: 1000 }) ) Missing Data Because Zero returns local results immediately and server results asynchronously, displaying \"not found\" / 404 UI can be slightly tricky. If you just use a simple existence check, you will often see the 404 UI flicker while the server result loads: const [issue] = useQuery(queries.issues.get('some-id')) // ❌ This causes flickering of the UI if (!issue) { return
404 Not Found
} else { return
{issue.title}
}const [issue] = useQuery(() => queries.issues.get('some-id') ) return ( {resolved => ( 404 Not Found} >
{resolved.title}
)}
)const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) postsView.addListener(posts => { // ❌ This is updated as data comes in console.log('posts', posts) }) To do this correctly, only display the \"not found\" UI when the result type is complete. This way the 404 page is slow but pages with data are still just as fast: const [issue, issueResult] = useQuery( queries.issues.get('some-id') ) if (!issue && issueResult.type === 'complete') { return
404 Not Found
} if (!issue) { return null } return
{issue.title}
const [issue, issueResult] = useQuery(() => queries.issues.get('some-id') ) return ( {resolved =>
{resolved.title}
}
404 Not Found
)const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) postsView.addListener((posts, resultType) => { if (resultType === 'complete') { console.log('posts', posts) } }) Partial Data Zero immediately returns the data for a query it has on the client, then falls back to the server for any missing data. Sometimes it's useful to know the difference between these two types of results. To do so, use the result from useQuery: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) if (issuesResult.type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) if (issuesResult().type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if (resultType === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') } }) The possible values of result.type are currently complete and unknown. The complete value is currently only returned when Zero has received the server result. In the future, Zero will be able to return this result type when it knows that all possible data for this query is already available locally. Additionally, we plan to add a prefix result for when the data is known to be a prefix of the complete result. See Consistency for more information. Handling Errors If the queries endpoint throws an application or parse error, zero-cache will report it to the client using the type and error fields on the query details object: const [posts, postsResult] = useQuery( queries.posts.byAuthorID('user123') ) if (postsResult.type === 'error') { return (
Error loading posts: {postsResult.error.message}
) }const [posts, postsResult] = useQuery(() => queries.posts.byAuthorID('user123') ) return (
Error loading posts: {postsResult().error.message}
)// Materialize a view of a query const postsView = queries.posts .byAuthorID('user123') .materialize() postsView.addListener((posts, resultType, error) => { if (resultType === 'error') { console.error('Error loading posts', error) } }) See Connection Status for how HTTP or network errors from the queries endpoint are handled. Granular Updates You can use the materialize() method to create a view that you can listen to for changes. However, this will only tell you when the view has changed and give you the complete new result. It won't tell you what changed. To know what changed, you can create your own custom View implementation: // Inside the View class // Instead of storing the change, we invoke some callback push(change: Change): void { switch (change.type) { case 'add': this.#onAdd?.(change) break case 'remove': this.#onRemove?.(change) break case 'edit': this.#onEdit?.(change) break case 'child': this.#onChild?.(change) break default: throw new Error(`Unknown change type: ${change['type']}`) } } For examples, see the View implementations in zero-vue or zero-solid. Query Caching Queries can be either active or cached. An active query is one that is currently being used by the application. Cached queries are not currently in use, but continue syncing in case they are needed again soon. Queries are deactivated according to how they were created: For useQuery(), the UI unmounts the component (which calls destroy() under the covers). For preload(), the UI calls cleanup() on the return value of preload(). For run(), queries are automatically deactivated immediately after the result is returned. For materialize() queries, the UI calls destroy() on the view. Additionally when a Zero instance closes, all active queries are automatically deactivated. This also happens when the containing page or script is unloaded. TTLs Each query has a ttl that controls how long it stays cached. If the user closes all tabs for your app, Zero stops running and the time that elapses doesn't count toward any TTLs. You do not need to account for such time when choosing a TTL – you only need to account for time your app is running without a query. TTL Defaults In most cases, the default TTL should work well: preload() queries default to ttl:'none', meaning they are not cached at all, and will stop syncing immediately when deactivated. But because preload() queries are typically registered at app startup and never shutdown, and because the ttl clock only ticks while Zero is running, this means that preload queries never get unregistered. Other queries have a default ttl of 5m (five minutes). Setting Different TTLs You can override the default TTL with the ttl parameter: const [user] = useQuery( queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // preload() zero.preload(queries.posts.byAuthorID('user123'), { ttl: '5m' })const [user] = useQuery( () => queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // preload() zero().preload(queries.posts.byAuthorID('user123'), { ttl: '5m' })// run() const user = await zero.run( queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // materialize() const view = zero.materialize( queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // preload() zero.preload(queries.posts.byAuthorID('user123'), { ttl: '5m' }) TTLs up to 10m (ten minutes) are currently supported. The following formats are allowed: Why Zero TTLs are Short Zero queries are not free. Just as in any database, queries consume resources on both the client and server. Memory is used to keep metadata about the query, and disk storage is used to keep the query's current state. We do drop this state after we haven't heard from a client for awhile, but this is only a partial improvement. If the client returns, we have to re-run the query to get the latest data. This means that we do not actually want to keep queries active unless there is a good chance they will be needed again soon. The default Zero TTL values might initially seem too short, but they are designed to work well with the way Zero's TTL clock works and strike a good balance between keeping queries alive long enough to be useful, while not keeping them alive so long that they consume resources unnecessarily. Local-Only Queries It can sometimes be useful to run queries only on the client. For example, to implement typeahead search, it really doesn't make sense to register a query with the server for every single keystroke. Zero doesn't yet have a way to run named queries local-only, but you can run ZQL expressions locally by passing them anywhere a query is supported. For example, to subscribe to a local-only query: // Queries the already synced data for issues, // without syncing more data. const [issues] = useQuery( zql.issue.orderBy('created', 'desc').limit(10) )// Queries the already synced data for issues, // without syncing more data. const [issues] = useQuery(() => zql.issue.orderBy('created', 'desc').limit(10) )// Queries the already synced data for issues, // without syncing more data. const view = z.materialize( zql.issue.orderBy('created', 'desc').limit(10) ) view.addListener(issues => { console.log('issues', issues) }) Custom Server Implementation It is possible to implement the ZERO_QUERY_URL endpoint without using Zero's TypeScript libraries, or even in a different language entirely. The endpoint receives a POST request with a JSON body of the form: type QueriesRequestBody = { id: string name: string args: readonly ReadonlyJSONValue[] }[] And responds with: type QueriesResponseBody = ( | { id: string name: string // See https://github.com/rocicorp/mono/blob/main/packages/zero-protocol/src/ast.ts ast: AST } | { error: 'app' id: string name: string details: ReadonlyJSONValue } | { error: 'zero' id: string name: string details: ReadonlyJSONValue } | { error: 'http' id: string name: string status: number details: ReadonlyJSONValue } )[] Consistency Zero always syncs a consistent partial replica of the backend database to the client. This avoids many common consistency issues that come up in classic web applications. But there are still some consistency issues to be aware of when using Zero. For example, imagine that you have a bug database w/ 10k issues. You preload the first 1k issues sorted by created. The user then does a query of issues assigned to themselves, sorted by created. Among the 1k issues that were preloaded imagine 100 are found that match the query. Since the data we preloaded is in the same order as this query, we are guaranteed that any local results found will be a prefix of the server results. The UX that result is nice: the user will see initial results to the query instantly. If more results are found server-side, those results are guaranteed to sort below the local results. There's no shuffling of results when the server response comes in. Now imagine that the user switches the sort to ‘sort by modified’. This new query will run locally, and will again find some local matches. But it is now unlikely that the local results found are a prefix of the server results. When the server result comes in, the user will probably see the results shuffle around. To avoid this annoying effect, what you should do in this example is also preload the first 1k issues sorted by modified desc. In general for any query shape you intend to do, you should preload the first n results for that query shape with no filters, in each sort you intend to use. Zero syncs the union of all active queries' results. You don't have to worry about syncing many sorts of the same query when it's likely the results will overlap heavily. In the future, we will be implementing a consistency model that fixes these issues automatically. We will prevent Zero from returning local data when that data is not known to be a prefix of the server result. Once the consistency model is implemented, preloading can be thought of as purely a performance thing, and not required to avoid unsightly flickering.", + "content": "Queries are how you read and sync data with Zero. Here's a simple example: // src/queries.ts import {defineQueries, defineQuery} from '@rocicorp/zero' import {z} from 'zod' import {zql} from 'schema.ts' export const queries = defineQueries({ postsByAuthor: defineQuery( z.object({authorID: z.string()}), ({args: {authorID}}) => zql.post.where('authorID', authorID) ) }) Architecture A copy of each query exists on both the client and on your server: Often the implementations will be the same, and you can just share their code. This is easy with full-stack frameworks like TanStack Start or Next.js. But the implementations don't have to be the same, or even compute the same result. For example, the server can add extra filters to enforce permissions that the client query does not. Life of a Query When a query is invoked, it initially runs on the client, against the client-side datastore. Any matching data is returned immediately and the user sees instant results. In the background, the name and arguments for the query are sent to zero-cache. Zero-cache calls the queries endpoint on your server to get the ZQL for the query. Your server looks up its implementation of the query, invokes it, and returns the resulting ZQL expression to zero-cache. Zero-cache then runs this ZQL against the server-side data. The initial server result is sent back to the client and the client query updates in response. zero-cache receives updates from Postgres via logical replication. It updates affected queries and sends row changes back to the client, which updates the client query, and the user sees the changes. Defining Queries Basics Create a query using defineQuery. The only required argument is a QueryFn, which must return a ZQL expression: import {zql} from 'schema.ts' const allPostsQueryDef = defineQuery(() => zql.post) Arguments The QueryFn can take a single args parameter. To enable this, pass a validator to defineQuery: import {zql} from 'schema.ts' const postsByAuthor = defineQuery( z.object({authorID: z.string().optional()}), ({args: {authorID}}) => { let q = zql.post if (authorID !== undefined) { q = q.where('authorID', authorID) } return q } ) We use Zod in these examples, but you can use any validation library that implements Standard Schema. Zero queries run on both the client and on your server. In the server case, the parameters come from the client and are untrusted. The validator ensures the data passed to your query is of the expected type. Query Registries The result of defineQuery is a QueryDefinition. By itself this isn't super useful. You need to register it using defineQueries: export const queries = defineQueries({ posts: { all: allPostsQueryDef } }) Typically these are done together in one step: export const queries = defineQueries({ posts: { all: defineQuery(() => zql.post) } }) The result of defineQueries is called a QueryRegistry. Each field in the registry is a callable Query that you can use to read data: import {zero} from 'zero.ts' import {queries} from 'queries.ts' const allPosts = await zero.run(queries.posts.all()) Query Names Each Query has a queryName which is computed by defineQueries. This name is later sent to your server to identify the query to run: console.log(queries.posts.all.queryName) // \"posts.all\" Context Query parameters are supplied by the client application and passed to the server automatically by Zero. This makes them unsuitable for credentials, since the user could modify them. For this reason, Zero queries also support the concept of a context object. Access your context with the ctx parameter to your query: const myPostsQuery = defineQuery(({ctx: {userID}}) => { // User cannot control context.userID, so this safely // restricts the query to the user's own posts. return zql.post.where('authorID', userID) }) If you don't want to register your Context and Schema types globally, you can use defineQueryWithType and defineQueriesWithType: import { defineQueriesWithType, defineQueryWithType } from '@rocicorp/zero' import type {Schema} from 'schema.ts' import type {ZeroContext} from 'context.ts' const defineQuery = defineQueryWithType< Schema, ZeroContext >() const defineQueries = defineQueriesWithType() queries.ts By convention, all queries for an application are listed in a central queries.ts file. This allows them to be easily used on both the client and server: import {defineQueries, defineQuery} from '@rocicorp/zero' import {z} from 'zod' import {zql} from './schema.ts' export const queries = defineQueries({ posts: { get: defineQuery(z.string(), id => zql.post.where('id', id) ), byAuthor: defineQuery( z.object({ authorID: z.string(), includeDrafts: z.boolean().optional() }), ({args: {authorID, includeDrafts}}) => { let q = zql.post.where('authorID', authorID) if (!includeDrafts) { q = q.where('isDraft', false) } return q } ) } }) You can use as many levels of nesting as you want to organize your queries. As your application grows, you can move queries to different files to keep them organized: // posts.ts export const postQueries = { get: defineQuery(z.string(), id => zql.post.where('id', id) ) // ... } // users.ts export const userQueries = { byRole: defineQuery(z.string(), role => zql.user.where('role', role) ) // ... } // queries.ts import {postQueries} from './posts.ts' import {userQueries} from './users.ts' export const queries = defineQueries({ posts: postQueries, users: userQueries }) Because defineQueries establishes the full name for each query (i.e., posts.get, users.byRole), it should only be used once at the top level of your queries.ts file. Server Setup In order for queries to sync, you must provide an implementation of the query endpoint on your server. zero-cache calls this endpoint to resolve each query to ZQL that it can run. Registering the Endpoint Use ZERO_QUERY_URL to tell zero-cache where to find your query implementation: export ZERO_QUERY_URL=\"http://localhost:3000/api/zero/query\" # run zero-cache, e.g. `npx zero-cache-dev` Implementing the Endpoint You can use the handleQueryRequest and mustGetQuery functions to implement the endpoint. // src/routes/api/zero/query.ts import {createFileRoute} from '@tanstack/react-router' import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' export const Route = createFileRoute('/api/zero/query')({ server: { handlers: { POST: async ({request}) => { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request, userID: null }) return Response.json(result) } } } })// app/api/zero/query/route.ts import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' export async function POST(request: Request) { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request, userID: null }) return Response.json(result) }// src/routes/api/zero/query.ts import type {APIEvent} from '@solidjs/start/server' import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' export async function POST(event: APIEvent) { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request: event.request, userID: null }) return Response.json(result) }// api/app.ts import {handleQueryRequest} from '@rocicorp/zero/server' import {mustGetQuery} from '@rocicorp/zero' import {queries} from 'queries.ts' import {schema} from 'schema.ts' app.post('/api/zero/query', async c => { const result = await handleQueryRequest({ handler: (name, args) => { const query = mustGetQuery(queries, name) return query.fn({args}) }, schema, request: c.req.raw, userID: null }) return c.json(result) }) handleQueryRequest accepts a standard Request and returns a JSON object which can be serialized and returned by your server framework of choice. mustGetQuery looks up the query in the registry and throws an error if not found. The query.fn function is your query implementation wrapped in the validator you provided. These examples have only public queries, so they do not pass a context. In authenticated apps, validate auth in the request, derive context from the session, and pass it to the query handler. See Authentication. Custom Query URL By default, Zero sends queries to the URL specified in the ZERO_QUERY_URL parameter in the zero-cache config. However you can customize this on a per-client basis. To do so, list multiple comma-separated URLs in ZERO_QUERY_URL: ZERO_QUERY_URL='https://api.example.com/query,https://api.staging.example.com/query' Then choose one of those URLs by passing it to queryURL on the Zero constructor: const zero = new Zero({ schema, queries, queryURL: 'https://api.staging.example.com/query' }) URL Patterns The strings listed in ZERO_QUERY_URL can also be URLPatterns: ZERO_QUERY_URL=\"https://mybranch-*.preview.myapp.com/query\" This queries URL will allow clients to choose URLs like: https://mybranch-aaa.preview.myapp.com/query ✅ https://mybranch-bbb.preview.myapp.com/query ✅ But rejects URLs like: https://preview.myapp.com/query ❌ (missing subdomain) https://malicious.com/query ❌ (different domain) https://mybranch-123.preview.myapp.com/query/extra ❌ (extra path) https://mybranch-123.preview.myapp.com/other ❌ (different path) Because URLPattern is a web standard, you can test them right in your browser: For more information, see the URLPattern docs. If you're configuring per-branch preview URLs (for example on Vercel), see Preview Deployments for the complete setup across both query and mutate endpoints. Running Queries Reactively The most common way to use queries is with the useQuery reactive hooks from the React or SolidJS bindings (or the equivalent low-level API): import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(queries.posts.get('user123')) return posts.map(post => (
{post.title}
)) }import {useQuery} from '@rocicorp/zero/solid' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(() => queries.posts.get('user123') ) return ( {post =>
{post.title}
}
) }import {queries} from 'zero/queries.ts' import {zero} from 'zero.ts' const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) for (let post of postsView.data) { console.log(post.title) } // updates as the underlying data changes postsView.addListener(posts => { console.log('posts', posts) }) These functions allow you to automatically re-render UI when a query changes. Conditionally Sometimes the inputs needed to construct a query are not available on the first render. For example, auth state or a route param might still be loading after a page refresh. Both React and Solid support conditional queries by passing undefined until the query can be constructed: import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function Username({userID}: {userID: string | undefined}) { const [user] = useQuery( userID ? queries.users.getUser({ userID }) : undefined ) return user ?
{user.username}
: null }import {useQuery} from '@rocicorp/zero/solid' import {Show} from 'solid-js' import {queries} from 'zero/queries.ts' function Username(props: {userID: string | undefined}) { const [user] = useQuery(() => props.userID ? queries.users.getUser({ userID: props.userID }) : undefined ) return ( {user =>
{user().username}
}
) } Once You usually want to subscribe to a query in a reactive UI, but every so often you'll need to run a query just once. To do this, use zero.run(): const results = await zero.run( queries.issues.byPriority('high') ) By default, run() only returns results that are currently available on the client. That is, it returns the data that would be given for result.type === 'unknown'. If you want to wait for the server to return results, pass {type: 'complete'} to run: const results = await zero.run( queries.issues.byPriority('high'), {type: 'complete'} ) To also accept a result the server confirmed during a previous session, pass {type: 'cached'}. It resolves as soon as the result is cached or complete, so it doesn't wait for the server when the client already stores a confirmed result: const results = await zero.run( queries.issues.byPriority('high'), {type: 'cached'} ) For Preloading Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. Because preload queries are often much larger than a screenful of UI, Zero provides a special zero.preload() method to avoid the overhead of materializing the result into JS objects: // Preload a large number of the inbox query results. zero.preload( queries.issues.inbox({ sort: 'created', sortDirection: 'desc', limit: 1000 }) ) preload() returns cleanup, which ends the preload (Zero keeps its rows for the query's TTL), and two promises: complete resolves once the server has returned the result, and cached resolves once the result is cached or complete. Missing Data Because Zero returns local results immediately and server results asynchronously, displaying \"not found\" / 404 UI can be slightly tricky. If you just use a simple existence check, you will often see the 404 UI flicker while the server result loads: const [issue] = useQuery(queries.issues.get('some-id')) // ❌ This causes flickering of the UI if (!issue) { return
404 Not Found
} else { return
{issue.title}
}const [issue] = useQuery(() => queries.issues.get('some-id') ) return ( {resolved => ( 404 Not Found} >
{resolved.title}
)}
)const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) postsView.addListener(posts => { // ❌ This is updated as data comes in console.log('posts', posts) }) To do this correctly, only display the \"not found\" UI when the result type is complete. This way the 404 page is slow but pages with data are still just as fast: const [issue, issueResult] = useQuery( queries.issues.get('some-id') ) if (!issue && issueResult.type === 'complete') { return
404 Not Found
} if (!issue) { return null } return
{issue.title}
const [issue, issueResult] = useQuery(() => queries.issues.get('some-id') ) return ( {resolved =>
{resolved.title}
}
404 Not Found
)const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) postsView.addListener((posts, resultType) => { if (resultType === 'complete') { console.log('posts', posts) } }) Partial Data Zero immediately returns the data for a query it has on the client, then falls back to the server for any missing data. Sometimes it's useful to know the difference between these two types of results. To do so, use the result from useQuery: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) if (issuesResult.type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) if (issuesResult().type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if (resultType === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') } }) The possible values of result.type are: unknown: the data Zero has on the client. Some of it may be missing. cached: the client stores a result that the server confirmed during a previous session. See Cached Results. complete: the server has returned the result during this session. error: the query failed. See Handling Errors. The complete value is currently only returned when Zero has received the server result. In the future, Zero will be able to return this result type when it knows that all possible data for this query is already available locally. Additionally, we plan to add a prefix result for when the data is known to be a prefix of the complete result. See Consistency for more information. Cached Results When Zero stores data on the client (in IndexedDB, or SQLite on React Native), it also remembers which query results the server confirmed. If the client still holds such a result when the app starts again, the query starts as cached instead of unknown, and becomes complete once the server returns the result in this session. If Zero evicts the stored result before then, the query goes back to unknown. A cached result was complete when the server sent it, but it may be out of date. Use it where a previous session's answer is good enough, for example to show stored data or an empty state without waiting for the server, which also works while offline. Wait for complete where the data must be current: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) const confirmed = issuesResult.type === 'cached' || issuesResult.type === 'complete' if (issues.length === 0 && confirmed) { return
Your inbox is empty
}const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) const confirmed = () => issuesResult().type === 'cached' || issuesResult().type === 'complete' return ( 0 || !confirmed()} fallback={
Your inbox is empty
} > {/* ... */}
)const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if ( issues.length === 0 && (resultType === 'cached' || resultType === 'complete') ) { console.log('Inbox is empty') } }) zero.run(query, {type: 'cached'}) and the cached promise from zero.preload() resolve on a cached or complete result. {type: 'complete'} and the complete promise still wait for the server. Handling Errors If the queries endpoint throws an application or parse error, zero-cache will report it to the client using the type and error fields on the query details object: const [posts, postsResult] = useQuery( queries.posts.byAuthorID('user123') ) if (postsResult.type === 'error') { return (
Error loading posts: {postsResult.error.message}
) }const [posts, postsResult] = useQuery(() => queries.posts.byAuthorID('user123') ) return (
Error loading posts: {postsResult().error.message}
)// Materialize a view of a query const postsView = queries.posts .byAuthorID('user123') .materialize() postsView.addListener((posts, resultType, error) => { if (resultType === 'error') { console.error('Error loading posts', error) } }) See Connection Status for how HTTP or network errors from the queries endpoint are handled. Granular Updates You can use the materialize() method to create a view that you can listen to for changes. However, this will only tell you when the view has changed and give you the complete new result. It won't tell you what changed. To know what changed, you can create your own custom View implementation: // Inside the View class // Instead of storing the change, we invoke some callback push(change: Change): void { switch (change.type) { case 'add': this.#onAdd?.(change) break case 'remove': this.#onRemove?.(change) break case 'edit': this.#onEdit?.(change) break case 'child': this.#onChild?.(change) break default: throw new Error(`Unknown change type: ${change['type']}`) } } For examples, see the View implementations in zero-vue or zero-solid. Query Caching Queries can be either active or cached. An active query is one that is currently being used by the application. Cached queries are not currently in use, but continue syncing in case they are needed again soon. Queries are deactivated according to how they were created: For useQuery(), the UI unmounts the component (which calls destroy() under the covers). For preload(), the UI calls cleanup() on the return value of preload(). For run(), queries are automatically deactivated immediately after the result is returned. For materialize() queries, the UI calls destroy() on the view. Additionally when a Zero instance closes, all active queries are automatically deactivated. This also happens when the containing page or script is unloaded. TTLs Each query has a ttl that controls how long it stays cached. If the user closes all tabs for your app, Zero stops running and the time that elapses doesn't count toward any TTLs. You do not need to account for such time when choosing a TTL – you only need to account for time your app is running without a query. TTL Defaults In most cases, the default TTL should work well: preload() queries default to ttl:'none', meaning they are not cached at all, and will stop syncing immediately when deactivated. But because preload() queries are typically registered at app startup and never shutdown, and because the ttl clock only ticks while Zero is running, this means that preload queries never get unregistered. Other queries have a default ttl of 5m (five minutes). Setting Different TTLs You can override the default TTL with the ttl parameter: const [user] = useQuery( queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // preload() zero.preload(queries.posts.byAuthorID('user123'), { ttl: '5m' })const [user] = useQuery( () => queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // preload() zero().preload(queries.posts.byAuthorID('user123'), { ttl: '5m' })// run() const user = await zero.run( queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // materialize() const view = zero.materialize( queries.posts.byAuthorID('user123'), {ttl: '5m'} ) // preload() zero.preload(queries.posts.byAuthorID('user123'), { ttl: '5m' }) TTLs up to 10m (ten minutes) are currently supported. The following formats are allowed: Why Zero TTLs are Short Zero queries are not free. Just as in any database, queries consume resources on both the client and server. Memory is used to keep metadata about the query, and disk storage is used to keep the query's current state. We do drop this state after we haven't heard from a client for awhile, but this is only a partial improvement. If the client returns, we have to re-run the query to get the latest data. This means that we do not actually want to keep queries active unless there is a good chance they will be needed again soon. The default Zero TTL values might initially seem too short, but they are designed to work well with the way Zero's TTL clock works and strike a good balance between keeping queries alive long enough to be useful, while not keeping them alive so long that they consume resources unnecessarily. Local-Only Queries It can sometimes be useful to run queries only on the client. For example, to implement typeahead search, it really doesn't make sense to register a query with the server for every single keystroke. Zero doesn't yet have a way to run named queries local-only, but you can run ZQL expressions locally by passing them anywhere a query is supported. For example, to subscribe to a local-only query: // Queries the already synced data for issues, // without syncing more data. const [issues] = useQuery( zql.issue.orderBy('created', 'desc').limit(10) )// Queries the already synced data for issues, // without syncing more data. const [issues] = useQuery(() => zql.issue.orderBy('created', 'desc').limit(10) )// Queries the already synced data for issues, // without syncing more data. const view = z.materialize( zql.issue.orderBy('created', 'desc').limit(10) ) view.addListener(issues => { console.log('issues', issues) }) Custom Server Implementation It is possible to implement the ZERO_QUERY_URL endpoint without using Zero's TypeScript libraries, or even in a different language entirely. The endpoint receives a POST request with a JSON body of the form: type QueriesRequestBody = { id: string name: string args: readonly ReadonlyJSONValue[] }[] And responds with: type QueriesResponseBody = ( | { id: string name: string // See https://github.com/rocicorp/mono/blob/main/packages/zero-protocol/src/ast.ts ast: AST } | { error: 'app' id: string name: string details: ReadonlyJSONValue } | { error: 'zero' id: string name: string details: ReadonlyJSONValue } | { error: 'http' id: string name: string status: number details: ReadonlyJSONValue } )[] Consistency Zero always syncs a consistent partial replica of the backend database to the client. This avoids many common consistency issues that come up in classic web applications. But there are still some consistency issues to be aware of when using Zero. For example, imagine that you have a bug database w/ 10k issues. You preload the first 1k issues sorted by created. The user then does a query of issues assigned to themselves, sorted by created. Among the 1k issues that were preloaded imagine 100 are found that match the query. Since the data we preloaded is in the same order as this query, we are guaranteed that any local results found will be a prefix of the server results. The UX that result is nice: the user will see initial results to the query instantly. If more results are found server-side, those results are guaranteed to sort below the local results. There's no shuffling of results when the server response comes in. Now imagine that the user switches the sort to ‘sort by modified’. This new query will run locally, and will again find some local matches. But it is now unlikely that the local results found are a prefix of the server results. When the server result comes in, the user will probably see the results shuffle around. To avoid this annoying effect, what you should do in this example is also preload the first 1k issues sorted by modified desc. In general for any query shape you intend to do, you should preload the first n results for that query shape with no filters, in each sort you intend to use. Zero syncs the union of all active queries' results. You don't have to worry about syncing many sorts of the same query when it's likely the results will overlap heavily. In the future, we will be implementing a consistency model that fixes these issues automatically. We will prevent Zero from returning local data when that data is not known to be a prefix of the server result. Once the consistency model is implemented, preloading can be thought of as purely a performance thing, and not required to avoid unsightly flickering.", "headings": [ { "text": "Architecture", @@ -2742,6 +2742,10 @@ "text": "Partial Data", "id": "partial-data" }, + { + "text": "Cached Results", + "id": "cached-results" + }, { "text": "Handling Errors", "id": "handling-errors" @@ -2932,7 +2936,7 @@ "sectionTitle": "Running Queries", "sectionId": "running-queries", "url": "/docs/queries", - "content": "Reactively The most common way to use queries is with the useQuery reactive hooks from the React or SolidJS bindings (or the equivalent low-level API): import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(queries.posts.get('user123')) return posts.map(post => (
{post.title}
)) }import {useQuery} from '@rocicorp/zero/solid' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(() => queries.posts.get('user123') ) return ( {post =>
{post.title}
}
) }import {queries} from 'zero/queries.ts' import {zero} from 'zero.ts' const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) for (let post of postsView.data) { console.log(post.title) } // updates as the underlying data changes postsView.addListener(posts => { console.log('posts', posts) }) These functions allow you to automatically re-render UI when a query changes. Conditionally Sometimes the inputs needed to construct a query are not available on the first render. For example, auth state or a route param might still be loading after a page refresh. Both React and Solid support conditional queries by passing undefined until the query can be constructed: import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function Username({userID}: {userID: string | undefined}) { const [user] = useQuery( userID ? queries.users.getUser({ userID }) : undefined ) return user ?
{user.username}
: null }import {useQuery} from '@rocicorp/zero/solid' import {Show} from 'solid-js' import {queries} from 'zero/queries.ts' function Username(props: {userID: string | undefined}) { const [user] = useQuery(() => props.userID ? queries.users.getUser({ userID: props.userID }) : undefined ) return ( {user =>
{user().username}
}
) } Once You usually want to subscribe to a query in a reactive UI, but every so often you'll need to run a query just once. To do this, use zero.run(): const results = await zero.run( queries.issues.byPriority('high') ) By default, run() only returns results that are currently available on the client. That is, it returns the data that would be given for result.type === 'unknown'. If you want to wait for the server to return results, pass {type: 'complete'} to run: const results = await zero.run( queries.issues.byPriority('high'), {type: 'complete'} ) For Preloading Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. Because preload queries are often much larger than a screenful of UI, Zero provides a special zero.preload() method to avoid the overhead of materializing the result into JS objects: // Preload a large number of the inbox query results. zero.preload( queries.issues.inbox({ sort: 'created', sortDirection: 'desc', limit: 1000 }) )", + "content": "Reactively The most common way to use queries is with the useQuery reactive hooks from the React or SolidJS bindings (or the equivalent low-level API): import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(queries.posts.get('user123')) return posts.map(post => (
{post.title}
)) }import {useQuery} from '@rocicorp/zero/solid' import {queries} from 'zero/queries.ts' function App() { const [posts] = useQuery(() => queries.posts.get('user123') ) return ( {post =>
{post.title}
}
) }import {queries} from 'zero/queries.ts' import {zero} from 'zero.ts' const postsView = zero.materialize( queries.posts.byAuthorID('user123') ) for (let post of postsView.data) { console.log(post.title) } // updates as the underlying data changes postsView.addListener(posts => { console.log('posts', posts) }) These functions allow you to automatically re-render UI when a query changes. Conditionally Sometimes the inputs needed to construct a query are not available on the first render. For example, auth state or a route param might still be loading after a page refresh. Both React and Solid support conditional queries by passing undefined until the query can be constructed: import {useQuery} from '@rocicorp/zero/react' import {queries} from 'zero/queries.ts' function Username({userID}: {userID: string | undefined}) { const [user] = useQuery( userID ? queries.users.getUser({ userID }) : undefined ) return user ?
{user.username}
: null }import {useQuery} from '@rocicorp/zero/solid' import {Show} from 'solid-js' import {queries} from 'zero/queries.ts' function Username(props: {userID: string | undefined}) { const [user] = useQuery(() => props.userID ? queries.users.getUser({ userID: props.userID }) : undefined ) return ( {user =>
{user().username}
}
) } Once You usually want to subscribe to a query in a reactive UI, but every so often you'll need to run a query just once. To do this, use zero.run(): const results = await zero.run( queries.issues.byPriority('high') ) By default, run() only returns results that are currently available on the client. That is, it returns the data that would be given for result.type === 'unknown'. If you want to wait for the server to return results, pass {type: 'complete'} to run: const results = await zero.run( queries.issues.byPriority('high'), {type: 'complete'} ) To also accept a result the server confirmed during a previous session, pass {type: 'cached'}. It resolves as soon as the result is cached or complete, so it doesn't wait for the server when the client already stores a confirmed result: const results = await zero.run( queries.issues.byPriority('high'), {type: 'cached'} ) For Preloading Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. Because preload queries are often much larger than a screenful of UI, Zero provides a special zero.preload() method to avoid the overhead of materializing the result into JS objects: // Preload a large number of the inbox query results. zero.preload( queries.issues.inbox({ sort: 'created', sortDirection: 'desc', limit: 1000 }) ) preload() returns cleanup, which ends the preload (Zero keeps its rows for the query's TTL), and two promises: complete resolves once the server has returned the result, and cached resolves once the result is cached or complete.", "kind": "section" }, { @@ -2962,7 +2966,7 @@ "sectionTitle": "Once", "sectionId": "once", "url": "/docs/queries", - "content": "You usually want to subscribe to a query in a reactive UI, but every so often you'll need to run a query just once. To do this, use zero.run(): const results = await zero.run( queries.issues.byPriority('high') ) By default, run() only returns results that are currently available on the client. That is, it returns the data that would be given for result.type === 'unknown'. If you want to wait for the server to return results, pass {type: 'complete'} to run: const results = await zero.run( queries.issues.byPriority('high'), {type: 'complete'} )", + "content": "You usually want to subscribe to a query in a reactive UI, but every so often you'll need to run a query just once. To do this, use zero.run(): const results = await zero.run( queries.issues.byPriority('high') ) By default, run() only returns results that are currently available on the client. That is, it returns the data that would be given for result.type === 'unknown'. If you want to wait for the server to return results, pass {type: 'complete'} to run: const results = await zero.run( queries.issues.byPriority('high'), {type: 'complete'} ) To also accept a result the server confirmed during a previous session, pass {type: 'cached'}. It resolves as soon as the result is cached or complete, so it doesn't wait for the server when the client already stores a confirmed result: const results = await zero.run( queries.issues.byPriority('high'), {type: 'cached'} )", "kind": "section" }, { @@ -2972,7 +2976,7 @@ "sectionTitle": "For Preloading", "sectionId": "for-preloading", "url": "/docs/queries", - "content": "Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. Because preload queries are often much larger than a screenful of UI, Zero provides a special zero.preload() method to avoid the overhead of materializing the result into JS objects: // Preload a large number of the inbox query results. zero.preload( queries.issues.inbox({ sort: 'created', sortDirection: 'desc', limit: 1000 }) )", + "content": "Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. Because preload queries are often much larger than a screenful of UI, Zero provides a special zero.preload() method to avoid the overhead of materializing the result into JS objects: // Preload a large number of the inbox query results. zero.preload( queries.issues.inbox({ sort: 'created', sortDirection: 'desc', limit: 1000 }) ) preload() returns cleanup, which ends the preload (Zero keeps its rows for the query's TTL), and two promises: complete resolves once the server has returned the result, and cached resolves once the result is cached or complete.", "kind": "section" }, { @@ -2992,11 +2996,21 @@ "sectionTitle": "Partial Data", "sectionId": "partial-data", "url": "/docs/queries", - "content": "Zero immediately returns the data for a query it has on the client, then falls back to the server for any missing data. Sometimes it's useful to know the difference between these two types of results. To do so, use the result from useQuery: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) if (issuesResult.type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) if (issuesResult().type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if (resultType === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') } }) The possible values of result.type are currently complete and unknown. The complete value is currently only returned when Zero has received the server result. In the future, Zero will be able to return this result type when it knows that all possible data for this query is already available locally. Additionally, we plan to add a prefix result for when the data is known to be a prefix of the complete result. See Consistency for more information.", + "content": "Zero immediately returns the data for a query it has on the client, then falls back to the server for any missing data. Sometimes it's useful to know the difference between these two types of results. To do so, use the result from useQuery: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) if (issuesResult.type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) if (issuesResult().type === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') }const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if (resultType === 'complete') { console.log('All data is present') } else { console.log('Some data is missing') } }) The possible values of result.type are: unknown: the data Zero has on the client. Some of it may be missing. cached: the client stores a result that the server confirmed during a previous session. See Cached Results. complete: the server has returned the result during this session. error: the query failed. See Handling Errors. The complete value is currently only returned when Zero has received the server result. In the future, Zero will be able to return this result type when it knows that all possible data for this query is already available locally. Additionally, we plan to add a prefix result for when the data is known to be a prefix of the complete result. See Consistency for more information. Cached Results When Zero stores data on the client (in IndexedDB, or SQLite on React Native), it also remembers which query results the server confirmed. If the client still holds such a result when the app starts again, the query starts as cached instead of unknown, and becomes complete once the server returns the result in this session. If Zero evicts the stored result before then, the query goes back to unknown. A cached result was complete when the server sent it, but it may be out of date. Use it where a previous session's answer is good enough, for example to show stored data or an empty state without waiting for the server, which also works while offline. Wait for complete where the data must be current: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) const confirmed = issuesResult.type === 'cached' || issuesResult.type === 'complete' if (issues.length === 0 && confirmed) { return
Your inbox is empty
}const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) const confirmed = () => issuesResult().type === 'cached' || issuesResult().type === 'complete' return ( 0 || !confirmed()} fallback={
Your inbox is empty
} > {/* ... */}
)const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if ( issues.length === 0 && (resultType === 'cached' || resultType === 'complete') ) { console.log('Inbox is empty') } }) zero.run(query, {type: 'cached'}) and the cached promise from zero.preload() resolve on a cached or complete result. {type: 'complete'} and the complete promise still wait for the server.", + "kind": "section" + }, + { + "id": "273-queries#cached-results", + "title": "Queries", + "searchTitle": "Cached Results", + "sectionTitle": "Cached Results", + "sectionId": "cached-results", + "url": "/docs/queries", + "content": "When Zero stores data on the client (in IndexedDB, or SQLite on React Native), it also remembers which query results the server confirmed. If the client still holds such a result when the app starts again, the query starts as cached instead of unknown, and becomes complete once the server returns the result in this session. If Zero evicts the stored result before then, the query goes back to unknown. A cached result was complete when the server sent it, but it may be out of date. Use it where a previous session's answer is good enough, for example to show stored data or an empty state without waiting for the server, which also works while offline. Wait for complete where the data must be current: const [issues, issuesResult] = useQuery( queries.issues.inbox() ) const confirmed = issuesResult.type === 'cached' || issuesResult.type === 'complete' if (issues.length === 0 && confirmed) { return
Your inbox is empty
}const [issues, issuesResult] = useQuery(() => queries.issues.inbox() ) const confirmed = () => issuesResult().type === 'cached' || issuesResult().type === 'complete' return ( 0 || !confirmed()} fallback={
Your inbox is empty
} > {/* ... */}
)const view = zero.materialize(queries.issues.inbox()) view.addListener((issues, resultType) => { if ( issues.length === 0 && (resultType === 'cached' || resultType === 'complete') ) { console.log('Inbox is empty') } }) zero.run(query, {type: 'cached'}) and the cached promise from zero.preload() resolve on a cached or complete result. {type: 'complete'} and the complete promise still wait for the server.", "kind": "section" }, { - "id": "273-queries#handling-errors", + "id": "274-queries#handling-errors", "title": "Queries", "searchTitle": "Handling Errors", "sectionTitle": "Handling Errors", @@ -3006,7 +3020,7 @@ "kind": "section" }, { - "id": "274-queries#granular-updates", + "id": "275-queries#granular-updates", "title": "Queries", "searchTitle": "Granular Updates", "sectionTitle": "Granular Updates", @@ -3016,7 +3030,7 @@ "kind": "section" }, { - "id": "275-queries#query-caching", + "id": "276-queries#query-caching", "title": "Queries", "searchTitle": "Query Caching", "sectionTitle": "Query Caching", @@ -3026,7 +3040,7 @@ "kind": "section" }, { - "id": "276-queries#ttls", + "id": "277-queries#ttls", "title": "Queries", "searchTitle": "TTLs", "sectionTitle": "TTLs", @@ -3036,7 +3050,7 @@ "kind": "section" }, { - "id": "277-queries#ttl-defaults", + "id": "278-queries#ttl-defaults", "title": "Queries", "searchTitle": "TTL Defaults", "sectionTitle": "TTL Defaults", @@ -3046,7 +3060,7 @@ "kind": "section" }, { - "id": "278-queries#setting-different-ttls", + "id": "279-queries#setting-different-ttls", "title": "Queries", "searchTitle": "Setting Different TTLs", "sectionTitle": "Setting Different TTLs", @@ -3056,7 +3070,7 @@ "kind": "section" }, { - "id": "279-queries#why-zero-ttls-are-short", + "id": "280-queries#why-zero-ttls-are-short", "title": "Queries", "searchTitle": "Why Zero TTLs are Short", "sectionTitle": "Why Zero TTLs are Short", @@ -3066,7 +3080,7 @@ "kind": "section" }, { - "id": "280-queries#local-only-queries", + "id": "281-queries#local-only-queries", "title": "Queries", "searchTitle": "Local-Only Queries", "sectionTitle": "Local-Only Queries", @@ -3076,7 +3090,7 @@ "kind": "section" }, { - "id": "281-queries#custom-server-implementation", + "id": "282-queries#custom-server-implementation", "title": "Queries", "searchTitle": "Custom Server Implementation", "sectionTitle": "Custom Server Implementation", @@ -3086,7 +3100,7 @@ "kind": "section" }, { - "id": "282-queries#consistency", + "id": "283-queries#consistency", "title": "Queries", "searchTitle": "Consistency", "sectionTitle": "Consistency", @@ -3118,7 +3132,7 @@ "kind": "page" }, { - "id": "283-quickstart#hello-zero-solid", + "id": "284-quickstart#hello-zero-solid", "title": "Quickstart", "searchTitle": "hello-zero-solid", "sectionTitle": "hello-zero-solid", @@ -3128,7 +3142,7 @@ "kind": "section" }, { - "id": "284-quickstart#hello-zero-cf", + "id": "285-quickstart#hello-zero-cf", "title": "Quickstart", "searchTitle": "hello-zero-cf", "sectionTitle": "hello-zero-cf", @@ -3138,7 +3152,7 @@ "kind": "section" }, { - "id": "285-quickstart#hello-zero", + "id": "286-quickstart#hello-zero", "title": "Quickstart", "searchTitle": "hello-zero", "sectionTitle": "hello-zero", @@ -3183,7 +3197,7 @@ "kind": "page" }, { - "id": "286-react#setup", + "id": "287-react#setup", "title": "React", "searchTitle": "Setup", "sectionTitle": "Setup", @@ -3193,7 +3207,7 @@ "kind": "section" }, { - "id": "287-react#usage", + "id": "288-react#usage", "title": "React", "searchTitle": "Usage", "sectionTitle": "Usage", @@ -3203,7 +3217,7 @@ "kind": "section" }, { - "id": "288-react#suspense", + "id": "289-react#suspense", "title": "React", "searchTitle": "Suspense", "sectionTitle": "Suspense", @@ -3213,7 +3227,7 @@ "kind": "section" }, { - "id": "289-react#examples", + "id": "290-react#examples", "title": "React", "searchTitle": "Examples", "sectionTitle": "Examples", @@ -3245,7 +3259,7 @@ "kind": "page" }, { - "id": "290-release-notes/0.1#breaking-changes", + "id": "291-release-notes/0.1#breaking-changes", "title": "Zero 0.1", "searchTitle": "Breaking changes", "sectionTitle": "Breaking changes", @@ -3255,7 +3269,7 @@ "kind": "section" }, { - "id": "291-release-notes/0.1#features", + "id": "292-release-notes/0.1#features", "title": "Zero 0.1", "searchTitle": "Features", "sectionTitle": "Features", @@ -3265,7 +3279,7 @@ "kind": "section" }, { - "id": "292-release-notes/0.1#source-tree-fixes", + "id": "293-release-notes/0.1#source-tree-fixes", "title": "Zero 0.1", "searchTitle": "Source tree fixes", "sectionTitle": "Source tree fixes", @@ -3301,7 +3315,7 @@ "kind": "page" }, { - "id": "293-release-notes/0.10#install", + "id": "294-release-notes/0.10#install", "title": "Zero 0.10", "searchTitle": "Install", "sectionTitle": "Install", @@ -3311,7 +3325,7 @@ "kind": "section" }, { - "id": "294-release-notes/0.10#features", + "id": "295-release-notes/0.10#features", "title": "Zero 0.10", "searchTitle": "Features", "sectionTitle": "Features", @@ -3321,7 +3335,7 @@ "kind": "section" }, { - "id": "295-release-notes/0.10#fixes", + "id": "296-release-notes/0.10#fixes", "title": "Zero 0.10", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3331,7 +3345,7 @@ "kind": "section" }, { - "id": "296-release-notes/0.10#breaking-changes", + "id": "297-release-notes/0.10#breaking-changes", "title": "Zero 0.10", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3367,7 +3381,7 @@ "kind": "page" }, { - "id": "297-release-notes/0.11#install", + "id": "298-release-notes/0.11#install", "title": "Zero 0.11", "searchTitle": "Install", "sectionTitle": "Install", @@ -3377,7 +3391,7 @@ "kind": "section" }, { - "id": "298-release-notes/0.11#features", + "id": "299-release-notes/0.11#features", "title": "Zero 0.11", "searchTitle": "Features", "sectionTitle": "Features", @@ -3387,7 +3401,7 @@ "kind": "section" }, { - "id": "299-release-notes/0.11#fixes", + "id": "300-release-notes/0.11#fixes", "title": "Zero 0.11", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3397,7 +3411,7 @@ "kind": "section" }, { - "id": "300-release-notes/0.11#breaking-changes", + "id": "301-release-notes/0.11#breaking-changes", "title": "Zero 0.11", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3433,7 +3447,7 @@ "kind": "page" }, { - "id": "301-release-notes/0.12#install", + "id": "302-release-notes/0.12#install", "title": "Zero 0.12", "searchTitle": "Install", "sectionTitle": "Install", @@ -3443,7 +3457,7 @@ "kind": "section" }, { - "id": "302-release-notes/0.12#features", + "id": "303-release-notes/0.12#features", "title": "Zero 0.12", "searchTitle": "Features", "sectionTitle": "Features", @@ -3453,7 +3467,7 @@ "kind": "section" }, { - "id": "303-release-notes/0.12#fixes", + "id": "304-release-notes/0.12#fixes", "title": "Zero 0.12", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3463,7 +3477,7 @@ "kind": "section" }, { - "id": "304-release-notes/0.12#breaking-changes", + "id": "305-release-notes/0.12#breaking-changes", "title": "Zero 0.12", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3499,7 +3513,7 @@ "kind": "page" }, { - "id": "305-release-notes/0.13#install", + "id": "306-release-notes/0.13#install", "title": "Zero 0.13", "searchTitle": "Install", "sectionTitle": "Install", @@ -3509,7 +3523,7 @@ "kind": "section" }, { - "id": "306-release-notes/0.13#features", + "id": "307-release-notes/0.13#features", "title": "Zero 0.13", "searchTitle": "Features", "sectionTitle": "Features", @@ -3519,7 +3533,7 @@ "kind": "section" }, { - "id": "307-release-notes/0.13#fixes", + "id": "308-release-notes/0.13#fixes", "title": "Zero 0.13", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3529,7 +3543,7 @@ "kind": "section" }, { - "id": "308-release-notes/0.13#breaking-changes", + "id": "309-release-notes/0.13#breaking-changes", "title": "Zero 0.13", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3565,7 +3579,7 @@ "kind": "page" }, { - "id": "309-release-notes/0.14#install", + "id": "310-release-notes/0.14#install", "title": "Zero 0.14", "searchTitle": "Install", "sectionTitle": "Install", @@ -3575,7 +3589,7 @@ "kind": "section" }, { - "id": "310-release-notes/0.14#features", + "id": "311-release-notes/0.14#features", "title": "Zero 0.14", "searchTitle": "Features", "sectionTitle": "Features", @@ -3585,7 +3599,7 @@ "kind": "section" }, { - "id": "311-release-notes/0.14#fixes", + "id": "312-release-notes/0.14#fixes", "title": "Zero 0.14", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3595,7 +3609,7 @@ "kind": "section" }, { - "id": "312-release-notes/0.14#breaking-changes", + "id": "313-release-notes/0.14#breaking-changes", "title": "Zero 0.14", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3635,7 +3649,7 @@ "kind": "page" }, { - "id": "313-release-notes/0.15#install", + "id": "314-release-notes/0.15#install", "title": "Zero 0.15", "searchTitle": "Install", "sectionTitle": "Install", @@ -3645,7 +3659,7 @@ "kind": "section" }, { - "id": "314-release-notes/0.15#upgrade-guide", + "id": "315-release-notes/0.15#upgrade-guide", "title": "Zero 0.15", "searchTitle": "Upgrade Guide", "sectionTitle": "Upgrade Guide", @@ -3655,7 +3669,7 @@ "kind": "section" }, { - "id": "315-release-notes/0.15#features", + "id": "316-release-notes/0.15#features", "title": "Zero 0.15", "searchTitle": "Features", "sectionTitle": "Features", @@ -3665,7 +3679,7 @@ "kind": "section" }, { - "id": "316-release-notes/0.15#fixes", + "id": "317-release-notes/0.15#fixes", "title": "Zero 0.15", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3675,7 +3689,7 @@ "kind": "section" }, { - "id": "317-release-notes/0.15#breaking-changes", + "id": "318-release-notes/0.15#breaking-changes", "title": "Zero 0.15", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3715,7 +3729,7 @@ "kind": "page" }, { - "id": "318-release-notes/0.16#install", + "id": "319-release-notes/0.16#install", "title": "Zero 0.16", "searchTitle": "Install", "sectionTitle": "Install", @@ -3725,7 +3739,7 @@ "kind": "section" }, { - "id": "319-release-notes/0.16#upgrading", + "id": "320-release-notes/0.16#upgrading", "title": "Zero 0.16", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -3735,7 +3749,7 @@ "kind": "section" }, { - "id": "320-release-notes/0.16#features", + "id": "321-release-notes/0.16#features", "title": "Zero 0.16", "searchTitle": "Features", "sectionTitle": "Features", @@ -3745,7 +3759,7 @@ "kind": "section" }, { - "id": "321-release-notes/0.16#fixes", + "id": "322-release-notes/0.16#fixes", "title": "Zero 0.16", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3755,7 +3769,7 @@ "kind": "section" }, { - "id": "322-release-notes/0.16#breaking-changes", + "id": "323-release-notes/0.16#breaking-changes", "title": "Zero 0.16", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3795,7 +3809,7 @@ "kind": "page" }, { - "id": "323-release-notes/0.17#install", + "id": "324-release-notes/0.17#install", "title": "Zero 0.17", "searchTitle": "Install", "sectionTitle": "Install", @@ -3805,7 +3819,7 @@ "kind": "section" }, { - "id": "324-release-notes/0.17#upgrading", + "id": "325-release-notes/0.17#upgrading", "title": "Zero 0.17", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -3815,7 +3829,7 @@ "kind": "section" }, { - "id": "325-release-notes/0.17#features", + "id": "326-release-notes/0.17#features", "title": "Zero 0.17", "searchTitle": "Features", "sectionTitle": "Features", @@ -3825,7 +3839,7 @@ "kind": "section" }, { - "id": "326-release-notes/0.17#fixes", + "id": "327-release-notes/0.17#fixes", "title": "Zero 0.17", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3835,7 +3849,7 @@ "kind": "section" }, { - "id": "327-release-notes/0.17#breaking-changes", + "id": "328-release-notes/0.17#breaking-changes", "title": "Zero 0.17", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3875,7 +3889,7 @@ "kind": "page" }, { - "id": "328-release-notes/0.18#install", + "id": "329-release-notes/0.18#install", "title": "Zero 0.18", "searchTitle": "Install", "sectionTitle": "Install", @@ -3885,7 +3899,7 @@ "kind": "section" }, { - "id": "329-release-notes/0.18#upgrading", + "id": "330-release-notes/0.18#upgrading", "title": "Zero 0.18", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -3895,7 +3909,7 @@ "kind": "section" }, { - "id": "330-release-notes/0.18#features", + "id": "331-release-notes/0.18#features", "title": "Zero 0.18", "searchTitle": "Features", "sectionTitle": "Features", @@ -3905,7 +3919,7 @@ "kind": "section" }, { - "id": "331-release-notes/0.18#fixes", + "id": "332-release-notes/0.18#fixes", "title": "Zero 0.18", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3915,7 +3929,7 @@ "kind": "section" }, { - "id": "332-release-notes/0.18#breaking-changes", + "id": "333-release-notes/0.18#breaking-changes", "title": "Zero 0.18", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -3955,7 +3969,7 @@ "kind": "page" }, { - "id": "333-release-notes/0.19#install", + "id": "334-release-notes/0.19#install", "title": "Zero 0.19", "searchTitle": "Install", "sectionTitle": "Install", @@ -3965,7 +3979,7 @@ "kind": "section" }, { - "id": "334-release-notes/0.19#upgrading", + "id": "335-release-notes/0.19#upgrading", "title": "Zero 0.19", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -3975,7 +3989,7 @@ "kind": "section" }, { - "id": "335-release-notes/0.19#features", + "id": "336-release-notes/0.19#features", "title": "Zero 0.19", "searchTitle": "Features", "sectionTitle": "Features", @@ -3985,7 +3999,7 @@ "kind": "section" }, { - "id": "336-release-notes/0.19#fixes", + "id": "337-release-notes/0.19#fixes", "title": "Zero 0.19", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -3995,7 +4009,7 @@ "kind": "section" }, { - "id": "337-release-notes/0.19#breaking-changes", + "id": "338-release-notes/0.19#breaking-changes", "title": "Zero 0.19", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4039,7 +4053,7 @@ "kind": "page" }, { - "id": "338-release-notes/0.2#breaking-changes", + "id": "339-release-notes/0.2#breaking-changes", "title": "Zero 0.2", "searchTitle": "Breaking changes", "sectionTitle": "Breaking changes", @@ -4049,7 +4063,7 @@ "kind": "section" }, { - "id": "339-release-notes/0.2#features", + "id": "340-release-notes/0.2#features", "title": "Zero 0.2", "searchTitle": "Features", "sectionTitle": "Features", @@ -4059,7 +4073,7 @@ "kind": "section" }, { - "id": "340-release-notes/0.2#fixes", + "id": "341-release-notes/0.2#fixes", "title": "Zero 0.2", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4069,7 +4083,7 @@ "kind": "section" }, { - "id": "341-release-notes/0.2#docs", + "id": "342-release-notes/0.2#docs", "title": "Zero 0.2", "searchTitle": "Docs", "sectionTitle": "Docs", @@ -4079,7 +4093,7 @@ "kind": "section" }, { - "id": "342-release-notes/0.2#source-tree-fixes", + "id": "343-release-notes/0.2#source-tree-fixes", "title": "Zero 0.2", "searchTitle": "Source tree fixes", "sectionTitle": "Source tree fixes", @@ -4089,7 +4103,7 @@ "kind": "section" }, { - "id": "343-release-notes/0.2#zbugs", + "id": "344-release-notes/0.2#zbugs", "title": "Zero 0.2", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -4129,7 +4143,7 @@ "kind": "page" }, { - "id": "344-release-notes/0.20#install", + "id": "345-release-notes/0.20#install", "title": "Zero 0.20", "searchTitle": "Install", "sectionTitle": "Install", @@ -4139,7 +4153,7 @@ "kind": "section" }, { - "id": "345-release-notes/0.20#upgrading", + "id": "346-release-notes/0.20#upgrading", "title": "Zero 0.20", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -4149,7 +4163,7 @@ "kind": "section" }, { - "id": "346-release-notes/0.20#features", + "id": "347-release-notes/0.20#features", "title": "Zero 0.20", "searchTitle": "Features", "sectionTitle": "Features", @@ -4159,7 +4173,7 @@ "kind": "section" }, { - "id": "347-release-notes/0.20#fixes", + "id": "348-release-notes/0.20#fixes", "title": "Zero 0.20", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4169,7 +4183,7 @@ "kind": "section" }, { - "id": "348-release-notes/0.20#breaking-changes", + "id": "349-release-notes/0.20#breaking-changes", "title": "Zero 0.20", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4209,7 +4223,7 @@ "kind": "page" }, { - "id": "349-release-notes/0.21#install", + "id": "350-release-notes/0.21#install", "title": "Zero 0.21", "searchTitle": "Install", "sectionTitle": "Install", @@ -4219,7 +4233,7 @@ "kind": "section" }, { - "id": "350-release-notes/0.21#upgrading", + "id": "351-release-notes/0.21#upgrading", "title": "Zero 0.21", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -4229,7 +4243,7 @@ "kind": "section" }, { - "id": "351-release-notes/0.21#features", + "id": "352-release-notes/0.21#features", "title": "Zero 0.21", "searchTitle": "Features", "sectionTitle": "Features", @@ -4239,7 +4253,7 @@ "kind": "section" }, { - "id": "352-release-notes/0.21#fixes", + "id": "353-release-notes/0.21#fixes", "title": "Zero 0.21", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4249,7 +4263,7 @@ "kind": "section" }, { - "id": "353-release-notes/0.21#breaking-changes", + "id": "354-release-notes/0.21#breaking-changes", "title": "Zero 0.21", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4301,7 +4315,7 @@ "kind": "page" }, { - "id": "354-release-notes/0.22#install", + "id": "355-release-notes/0.22#install", "title": "Zero 0.22", "searchTitle": "Install", "sectionTitle": "Install", @@ -4311,7 +4325,7 @@ "kind": "section" }, { - "id": "355-release-notes/0.22#upgrading", + "id": "356-release-notes/0.22#upgrading", "title": "Zero 0.22", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -4321,7 +4335,7 @@ "kind": "section" }, { - "id": "356-release-notes/0.22#how-ttls-used-to-work", + "id": "357-release-notes/0.22#how-ttls-used-to-work", "title": "Zero 0.22", "searchTitle": "How TTLs Used to Work", "sectionTitle": "How TTLs Used to Work", @@ -4331,7 +4345,7 @@ "kind": "section" }, { - "id": "357-release-notes/0.22#how-ttls-work-now", + "id": "358-release-notes/0.22#how-ttls-work-now", "title": "Zero 0.22", "searchTitle": "How TTLs Work Now", "sectionTitle": "How TTLs Work Now", @@ -4341,7 +4355,7 @@ "kind": "section" }, { - "id": "358-release-notes/0.22#using-new-ttls", + "id": "359-release-notes/0.22#using-new-ttls", "title": "Zero 0.22", "searchTitle": "Using New TTLs", "sectionTitle": "Using New TTLs", @@ -4351,7 +4365,7 @@ "kind": "section" }, { - "id": "359-release-notes/0.22#features", + "id": "360-release-notes/0.22#features", "title": "Zero 0.22", "searchTitle": "Features", "sectionTitle": "Features", @@ -4361,7 +4375,7 @@ "kind": "section" }, { - "id": "360-release-notes/0.22#fixes", + "id": "361-release-notes/0.22#fixes", "title": "Zero 0.22", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4371,7 +4385,7 @@ "kind": "section" }, { - "id": "361-release-notes/0.22#breaking-changes", + "id": "362-release-notes/0.22#breaking-changes", "title": "Zero 0.22", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4415,7 +4429,7 @@ "kind": "page" }, { - "id": "362-release-notes/0.23#install", + "id": "363-release-notes/0.23#install", "title": "Zero 0.23", "searchTitle": "Install", "sectionTitle": "Install", @@ -4425,7 +4439,7 @@ "kind": "section" }, { - "id": "363-release-notes/0.23#upgrading", + "id": "364-release-notes/0.23#upgrading", "title": "Zero 0.23", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -4435,7 +4449,7 @@ "kind": "section" }, { - "id": "364-release-notes/0.23#features", + "id": "365-release-notes/0.23#features", "title": "Zero 0.23", "searchTitle": "Features", "sectionTitle": "Features", @@ -4445,7 +4459,7 @@ "kind": "section" }, { - "id": "365-release-notes/0.23#fixes", + "id": "366-release-notes/0.23#fixes", "title": "Zero 0.23", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4455,7 +4469,7 @@ "kind": "section" }, { - "id": "366-release-notes/0.23#zbugs", + "id": "367-release-notes/0.23#zbugs", "title": "Zero 0.23", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -4465,7 +4479,7 @@ "kind": "section" }, { - "id": "367-release-notes/0.23#breaking-changes", + "id": "368-release-notes/0.23#breaking-changes", "title": "Zero 0.23", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4505,7 +4519,7 @@ "kind": "page" }, { - "id": "368-release-notes/0.24#installation", + "id": "369-release-notes/0.24#installation", "title": "Zero 0.24", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -4515,7 +4529,7 @@ "kind": "section" }, { - "id": "369-release-notes/0.24#features", + "id": "370-release-notes/0.24#features", "title": "Zero 0.24", "searchTitle": "Features", "sectionTitle": "Features", @@ -4525,7 +4539,7 @@ "kind": "section" }, { - "id": "370-release-notes/0.24#fixes", + "id": "371-release-notes/0.24#fixes", "title": "Zero 0.24", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4535,7 +4549,7 @@ "kind": "section" }, { - "id": "371-release-notes/0.24#breaking-changes", + "id": "372-release-notes/0.24#breaking-changes", "title": "Zero 0.24", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4545,7 +4559,7 @@ "kind": "section" }, { - "id": "372-release-notes/0.24#example-upgrades", + "id": "373-release-notes/0.24#example-upgrades", "title": "Zero 0.24", "searchTitle": "Example Upgrades", "sectionTitle": "Example Upgrades", @@ -4593,7 +4607,7 @@ "kind": "page" }, { - "id": "373-release-notes/0.25#installation", + "id": "374-release-notes/0.25#installation", "title": "Zero 0.25", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -4603,7 +4617,7 @@ "kind": "section" }, { - "id": "374-release-notes/0.25#overview", + "id": "375-release-notes/0.25#overview", "title": "Zero 0.25", "searchTitle": "Overview", "sectionTitle": "Overview", @@ -4613,7 +4627,7 @@ "kind": "section" }, { - "id": "375-release-notes/0.25#upgrading", + "id": "376-release-notes/0.25#upgrading", "title": "Zero 0.25", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -4623,7 +4637,7 @@ "kind": "section" }, { - "id": "376-release-notes/0.25#features", + "id": "377-release-notes/0.25#features", "title": "Zero 0.25", "searchTitle": "Features", "sectionTitle": "Features", @@ -4633,7 +4647,7 @@ "kind": "section" }, { - "id": "377-release-notes/0.25#performance", + "id": "378-release-notes/0.25#performance", "title": "Zero 0.25", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -4643,7 +4657,7 @@ "kind": "section" }, { - "id": "378-release-notes/0.25#fixes", + "id": "379-release-notes/0.25#fixes", "title": "Zero 0.25", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4653,7 +4667,7 @@ "kind": "section" }, { - "id": "379-release-notes/0.25#breaking-changes", + "id": "380-release-notes/0.25#breaking-changes", "title": "Zero 0.25", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4689,7 +4703,7 @@ "kind": "page" }, { - "id": "380-release-notes/0.26#installation", + "id": "381-release-notes/0.26#installation", "title": "Zero 0.26", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -4699,7 +4713,7 @@ "kind": "section" }, { - "id": "381-release-notes/0.26#features", + "id": "382-release-notes/0.26#features", "title": "Zero 0.26", "searchTitle": "Features", "sectionTitle": "Features", @@ -4709,7 +4723,7 @@ "kind": "section" }, { - "id": "382-release-notes/0.26#fixes", + "id": "383-release-notes/0.26#fixes", "title": "Zero 0.26", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4719,7 +4733,7 @@ "kind": "section" }, { - "id": "383-release-notes/0.26#breaking-changes", + "id": "384-release-notes/0.26#breaking-changes", "title": "Zero 0.26", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -4763,7 +4777,7 @@ "kind": "page" }, { - "id": "384-release-notes/0.3#install", + "id": "385-release-notes/0.3#install", "title": "Zero 0.3", "searchTitle": "Install", "sectionTitle": "Install", @@ -4773,7 +4787,7 @@ "kind": "section" }, { - "id": "385-release-notes/0.3#breaking-changes", + "id": "386-release-notes/0.3#breaking-changes", "title": "Zero 0.3", "searchTitle": "Breaking changes", "sectionTitle": "Breaking changes", @@ -4783,7 +4797,7 @@ "kind": "section" }, { - "id": "386-release-notes/0.3#features", + "id": "387-release-notes/0.3#features", "title": "Zero 0.3", "searchTitle": "Features", "sectionTitle": "Features", @@ -4793,7 +4807,7 @@ "kind": "section" }, { - "id": "387-release-notes/0.3#fixes", + "id": "388-release-notes/0.3#fixes", "title": "Zero 0.3", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4803,7 +4817,7 @@ "kind": "section" }, { - "id": "388-release-notes/0.3#docs", + "id": "389-release-notes/0.3#docs", "title": "Zero 0.3", "searchTitle": "Docs", "sectionTitle": "Docs", @@ -4813,7 +4827,7 @@ "kind": "section" }, { - "id": "389-release-notes/0.3#zbugs", + "id": "390-release-notes/0.3#zbugs", "title": "Zero 0.3", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -4857,7 +4871,7 @@ "kind": "page" }, { - "id": "390-release-notes/0.4#install", + "id": "391-release-notes/0.4#install", "title": "Zero 0.4", "searchTitle": "Install", "sectionTitle": "Install", @@ -4867,7 +4881,7 @@ "kind": "section" }, { - "id": "391-release-notes/0.4#breaking-changes", + "id": "392-release-notes/0.4#breaking-changes", "title": "Zero 0.4", "searchTitle": "Breaking changes", "sectionTitle": "Breaking changes", @@ -4877,7 +4891,7 @@ "kind": "section" }, { - "id": "392-release-notes/0.4#added-or--and--and-not-to-zql-documentation", + "id": "393-release-notes/0.4#added-or--and--and-not-to-zql-documentation", "title": "Zero 0.4", "searchTitle": "Added or , and , and not to ZQL (documentation).", "sectionTitle": "Added or , and , and not to ZQL (documentation).", @@ -4887,7 +4901,7 @@ "kind": "section" }, { - "id": "393-release-notes/0.4#fixes", + "id": "394-release-notes/0.4#fixes", "title": "Zero 0.4", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4897,7 +4911,7 @@ "kind": "section" }, { - "id": "394-release-notes/0.4#docs", + "id": "395-release-notes/0.4#docs", "title": "Zero 0.4", "searchTitle": "Docs", "sectionTitle": "Docs", @@ -4907,7 +4921,7 @@ "kind": "section" }, { - "id": "395-release-notes/0.4#zbugs", + "id": "396-release-notes/0.4#zbugs", "title": "Zero 0.4", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -4951,7 +4965,7 @@ "kind": "page" }, { - "id": "396-release-notes/0.5#install", + "id": "397-release-notes/0.5#install", "title": "Zero 0.5", "searchTitle": "Install", "sectionTitle": "Install", @@ -4961,7 +4975,7 @@ "kind": "section" }, { - "id": "397-release-notes/0.5#breaking-changes", + "id": "398-release-notes/0.5#breaking-changes", "title": "Zero 0.5", "searchTitle": "Breaking changes", "sectionTitle": "Breaking changes", @@ -4971,7 +4985,7 @@ "kind": "section" }, { - "id": "398-release-notes/0.5#features", + "id": "399-release-notes/0.5#features", "title": "Zero 0.5", "searchTitle": "Features", "sectionTitle": "Features", @@ -4981,7 +4995,7 @@ "kind": "section" }, { - "id": "399-release-notes/0.5#fixes", + "id": "400-release-notes/0.5#fixes", "title": "Zero 0.5", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -4991,7 +5005,7 @@ "kind": "section" }, { - "id": "400-release-notes/0.5#docs", + "id": "401-release-notes/0.5#docs", "title": "Zero 0.5", "searchTitle": "Docs", "sectionTitle": "Docs", @@ -5001,7 +5015,7 @@ "kind": "section" }, { - "id": "401-release-notes/0.5#zbugs", + "id": "402-release-notes/0.5#zbugs", "title": "Zero 0.5", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -5045,7 +5059,7 @@ "kind": "page" }, { - "id": "402-release-notes/0.6#install", + "id": "403-release-notes/0.6#install", "title": "Zero 0.6", "searchTitle": "Install", "sectionTitle": "Install", @@ -5055,7 +5069,7 @@ "kind": "section" }, { - "id": "403-release-notes/0.6#upgrade-guide", + "id": "404-release-notes/0.6#upgrade-guide", "title": "Zero 0.6", "searchTitle": "Upgrade Guide", "sectionTitle": "Upgrade Guide", @@ -5065,7 +5079,7 @@ "kind": "section" }, { - "id": "404-release-notes/0.6#breaking-changes", + "id": "405-release-notes/0.6#breaking-changes", "title": "Zero 0.6", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5075,7 +5089,7 @@ "kind": "section" }, { - "id": "405-release-notes/0.6#features", + "id": "406-release-notes/0.6#features", "title": "Zero 0.6", "searchTitle": "Features", "sectionTitle": "Features", @@ -5085,7 +5099,7 @@ "kind": "section" }, { - "id": "406-release-notes/0.6#zbugs", + "id": "407-release-notes/0.6#zbugs", "title": "Zero 0.6", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -5095,7 +5109,7 @@ "kind": "section" }, { - "id": "407-release-notes/0.6#docs", + "id": "408-release-notes/0.6#docs", "title": "Zero 0.6", "searchTitle": "Docs", "sectionTitle": "Docs", @@ -5135,7 +5149,7 @@ "kind": "page" }, { - "id": "408-release-notes/0.7#install", + "id": "409-release-notes/0.7#install", "title": "Zero 0.7", "searchTitle": "Install", "sectionTitle": "Install", @@ -5145,7 +5159,7 @@ "kind": "section" }, { - "id": "409-release-notes/0.7#features", + "id": "410-release-notes/0.7#features", "title": "Zero 0.7", "searchTitle": "Features", "sectionTitle": "Features", @@ -5155,7 +5169,7 @@ "kind": "section" }, { - "id": "410-release-notes/0.7#breaking-changes", + "id": "411-release-notes/0.7#breaking-changes", "title": "Zero 0.7", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5165,7 +5179,7 @@ "kind": "section" }, { - "id": "411-release-notes/0.7#zbugs", + "id": "412-release-notes/0.7#zbugs", "title": "Zero 0.7", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -5175,7 +5189,7 @@ "kind": "section" }, { - "id": "412-release-notes/0.7#docs", + "id": "413-release-notes/0.7#docs", "title": "Zero 0.7", "searchTitle": "Docs", "sectionTitle": "Docs", @@ -5211,7 +5225,7 @@ "kind": "page" }, { - "id": "413-release-notes/0.8#install", + "id": "414-release-notes/0.8#install", "title": "Zero 0.8", "searchTitle": "Install", "sectionTitle": "Install", @@ -5221,7 +5235,7 @@ "kind": "section" }, { - "id": "414-release-notes/0.8#features", + "id": "415-release-notes/0.8#features", "title": "Zero 0.8", "searchTitle": "Features", "sectionTitle": "Features", @@ -5231,7 +5245,7 @@ "kind": "section" }, { - "id": "415-release-notes/0.8#fixes", + "id": "416-release-notes/0.8#fixes", "title": "Zero 0.8", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5241,7 +5255,7 @@ "kind": "section" }, { - "id": "416-release-notes/0.8#breaking-changes", + "id": "417-release-notes/0.8#breaking-changes", "title": "Zero 0.8", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5277,7 +5291,7 @@ "kind": "page" }, { - "id": "417-release-notes/0.9#install", + "id": "418-release-notes/0.9#install", "title": "Zero 0.9", "searchTitle": "Install", "sectionTitle": "Install", @@ -5287,7 +5301,7 @@ "kind": "section" }, { - "id": "418-release-notes/0.9#features", + "id": "419-release-notes/0.9#features", "title": "Zero 0.9", "searchTitle": "Features", "sectionTitle": "Features", @@ -5297,7 +5311,7 @@ "kind": "section" }, { - "id": "419-release-notes/0.9#fixes", + "id": "420-release-notes/0.9#fixes", "title": "Zero 0.9", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5307,7 +5321,7 @@ "kind": "section" }, { - "id": "420-release-notes/0.9#breaking-changes", + "id": "421-release-notes/0.9#breaking-changes", "title": "Zero 0.9", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5347,7 +5361,7 @@ "kind": "page" }, { - "id": "421-release-notes/1.0#installation", + "id": "422-release-notes/1.0#installation", "title": "Zero 1.0", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5357,7 +5371,7 @@ "kind": "section" }, { - "id": "422-release-notes/1.0#overview", + "id": "423-release-notes/1.0#overview", "title": "Zero 1.0", "searchTitle": "Overview", "sectionTitle": "Overview", @@ -5367,7 +5381,7 @@ "kind": "section" }, { - "id": "423-release-notes/1.0#features", + "id": "424-release-notes/1.0#features", "title": "Zero 1.0", "searchTitle": "Features", "sectionTitle": "Features", @@ -5377,7 +5391,7 @@ "kind": "section" }, { - "id": "424-release-notes/1.0#fixes", + "id": "425-release-notes/1.0#fixes", "title": "Zero 1.0", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5387,7 +5401,7 @@ "kind": "section" }, { - "id": "425-release-notes/1.0#breaking-changes", + "id": "426-release-notes/1.0#breaking-changes", "title": "Zero 1.0", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5423,7 +5437,7 @@ "kind": "page" }, { - "id": "426-release-notes/1.1#installation", + "id": "427-release-notes/1.1#installation", "title": "Zero 1.1", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5433,7 +5447,7 @@ "kind": "section" }, { - "id": "427-release-notes/1.1#features", + "id": "428-release-notes/1.1#features", "title": "Zero 1.1", "searchTitle": "Features", "sectionTitle": "Features", @@ -5443,7 +5457,7 @@ "kind": "section" }, { - "id": "428-release-notes/1.1#fixes", + "id": "429-release-notes/1.1#fixes", "title": "Zero 1.1", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5453,7 +5467,7 @@ "kind": "section" }, { - "id": "429-release-notes/1.1#breaking-changes", + "id": "430-release-notes/1.1#breaking-changes", "title": "Zero 1.1", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5505,7 +5519,7 @@ "kind": "page" }, { - "id": "430-release-notes/1.10#installation", + "id": "431-release-notes/1.10#installation", "title": "Zero 1.10", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5515,7 +5529,7 @@ "kind": "section" }, { - "id": "431-release-notes/1.10#overview", + "id": "432-release-notes/1.10#overview", "title": "Zero 1.10", "searchTitle": "Overview", "sectionTitle": "Overview", @@ -5525,7 +5539,7 @@ "kind": "section" }, { - "id": "432-release-notes/1.10#features", + "id": "433-release-notes/1.10#features", "title": "Zero 1.10", "searchTitle": "Features", "sectionTitle": "Features", @@ -5535,7 +5549,7 @@ "kind": "section" }, { - "id": "433-release-notes/1.10#fixes", + "id": "434-release-notes/1.10#fixes", "title": "Zero 1.10", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5545,7 +5559,7 @@ "kind": "section" }, { - "id": "434-release-notes/1.10#queries-and-connections", + "id": "435-release-notes/1.10#queries-and-connections", "title": "Zero 1.10", "searchTitle": "Queries and Connections", "sectionTitle": "Queries and Connections", @@ -5555,7 +5569,7 @@ "kind": "section" }, { - "id": "435-release-notes/1.10#replication-and-backfills", + "id": "436-release-notes/1.10#replication-and-backfills", "title": "Zero 1.10", "searchTitle": "Replication and Backfills", "sectionTitle": "Replication and Backfills", @@ -5565,7 +5579,7 @@ "kind": "section" }, { - "id": "436-release-notes/1.10#backups-and-startup", + "id": "437-release-notes/1.10#backups-and-startup", "title": "Zero 1.10", "searchTitle": "Backups and Startup", "sectionTitle": "Backups and Startup", @@ -5575,7 +5589,7 @@ "kind": "section" }, { - "id": "437-release-notes/1.10#breaking-changes", + "id": "438-release-notes/1.10#breaking-changes", "title": "Zero 1.10", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5615,7 +5629,7 @@ "kind": "page" }, { - "id": "438-release-notes/1.2#installation", + "id": "439-release-notes/1.2#installation", "title": "Zero 1.2", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5625,7 +5639,7 @@ "kind": "section" }, { - "id": "439-release-notes/1.2#features", + "id": "440-release-notes/1.2#features", "title": "Zero 1.2", "searchTitle": "Features", "sectionTitle": "Features", @@ -5635,7 +5649,7 @@ "kind": "section" }, { - "id": "440-release-notes/1.2#performance", + "id": "441-release-notes/1.2#performance", "title": "Zero 1.2", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -5645,7 +5659,7 @@ "kind": "section" }, { - "id": "441-release-notes/1.2#fixes", + "id": "442-release-notes/1.2#fixes", "title": "Zero 1.2", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5655,7 +5669,7 @@ "kind": "section" }, { - "id": "442-release-notes/1.2#breaking-changes", + "id": "443-release-notes/1.2#breaking-changes", "title": "Zero 1.2", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5695,7 +5709,7 @@ "kind": "page" }, { - "id": "443-release-notes/1.3#installation", + "id": "444-release-notes/1.3#installation", "title": "Zero 1.3", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5705,7 +5719,7 @@ "kind": "section" }, { - "id": "444-release-notes/1.3#features", + "id": "445-release-notes/1.3#features", "title": "Zero 1.3", "searchTitle": "Features", "sectionTitle": "Features", @@ -5715,7 +5729,7 @@ "kind": "section" }, { - "id": "445-release-notes/1.3#performance", + "id": "446-release-notes/1.3#performance", "title": "Zero 1.3", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -5725,7 +5739,7 @@ "kind": "section" }, { - "id": "446-release-notes/1.3#fixes", + "id": "447-release-notes/1.3#fixes", "title": "Zero 1.3", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5735,7 +5749,7 @@ "kind": "section" }, { - "id": "447-release-notes/1.3#breaking-changes", + "id": "448-release-notes/1.3#breaking-changes", "title": "Zero 1.3", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5783,7 +5797,7 @@ "kind": "page" }, { - "id": "448-release-notes/1.4#installation", + "id": "449-release-notes/1.4#installation", "title": "Zero 1.4", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5793,7 +5807,7 @@ "kind": "section" }, { - "id": "449-release-notes/1.4#upgrading", + "id": "450-release-notes/1.4#upgrading", "title": "Zero 1.4", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -5803,7 +5817,7 @@ "kind": "section" }, { - "id": "450-release-notes/1.4#userid-anon", + "id": "451-release-notes/1.4#userid-anon", "title": "Zero 1.4", "searchTitle": "userID: \"anon\"", "sectionTitle": "userID: \"anon\"", @@ -5813,7 +5827,7 @@ "kind": "section" }, { - "id": "451-release-notes/1.4#features", + "id": "452-release-notes/1.4#features", "title": "Zero 1.4", "searchTitle": "Features", "sectionTitle": "Features", @@ -5823,7 +5837,7 @@ "kind": "section" }, { - "id": "452-release-notes/1.4#performance", + "id": "453-release-notes/1.4#performance", "title": "Zero 1.4", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -5833,7 +5847,7 @@ "kind": "section" }, { - "id": "453-release-notes/1.4#fixes", + "id": "454-release-notes/1.4#fixes", "title": "Zero 1.4", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5843,7 +5857,7 @@ "kind": "section" }, { - "id": "454-release-notes/1.4#breaking-changes", + "id": "455-release-notes/1.4#breaking-changes", "title": "Zero 1.4", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -5895,7 +5909,7 @@ "kind": "page" }, { - "id": "455-release-notes/1.5#installation", + "id": "456-release-notes/1.5#installation", "title": "Zero 1.5", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -5905,7 +5919,7 @@ "kind": "section" }, { - "id": "456-release-notes/1.5#upgrading", + "id": "457-release-notes/1.5#upgrading", "title": "Zero 1.5", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -5915,7 +5929,7 @@ "kind": "section" }, { - "id": "457-release-notes/1.5#authenticated-client-groups", + "id": "458-release-notes/1.5#authenticated-client-groups", "title": "Zero 1.5", "searchTitle": "Authenticated Client Groups", "sectionTitle": "Authenticated Client Groups", @@ -5925,7 +5939,7 @@ "kind": "section" }, { - "id": "458-release-notes/1.5#deploy-order", + "id": "459-release-notes/1.5#deploy-order", "title": "Zero 1.5", "searchTitle": "Deploy Order", "sectionTitle": "Deploy Order", @@ -5935,7 +5949,7 @@ "kind": "section" }, { - "id": "459-release-notes/1.5#features", + "id": "460-release-notes/1.5#features", "title": "Zero 1.5", "searchTitle": "Features", "sectionTitle": "Features", @@ -5945,7 +5959,7 @@ "kind": "section" }, { - "id": "460-release-notes/1.5#performance", + "id": "461-release-notes/1.5#performance", "title": "Zero 1.5", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -5955,7 +5969,7 @@ "kind": "section" }, { - "id": "461-release-notes/1.5#fixes", + "id": "462-release-notes/1.5#fixes", "title": "Zero 1.5", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -5965,7 +5979,7 @@ "kind": "section" }, { - "id": "462-release-notes/1.5#breaking-changes", + "id": "463-release-notes/1.5#breaking-changes", "title": "Zero 1.5", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -6013,7 +6027,7 @@ "kind": "page" }, { - "id": "463-release-notes/1.6#installation", + "id": "464-release-notes/1.6#installation", "title": "Zero 1.6", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -6023,7 +6037,7 @@ "kind": "section" }, { - "id": "464-release-notes/1.6#upgrading", + "id": "465-release-notes/1.6#upgrading", "title": "Zero 1.6", "searchTitle": "Upgrading", "sectionTitle": "Upgrading", @@ -6033,7 +6047,7 @@ "kind": "section" }, { - "id": "465-release-notes/1.6#planetscale-failover", + "id": "466-release-notes/1.6#planetscale-failover", "title": "Zero 1.6", "searchTitle": "PlanetScale Failover", "sectionTitle": "PlanetScale Failover", @@ -6043,7 +6057,7 @@ "kind": "section" }, { - "id": "466-release-notes/1.6#features", + "id": "467-release-notes/1.6#features", "title": "Zero 1.6", "searchTitle": "Features", "sectionTitle": "Features", @@ -6053,7 +6067,7 @@ "kind": "section" }, { - "id": "467-release-notes/1.6#performance", + "id": "468-release-notes/1.6#performance", "title": "Zero 1.6", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -6063,7 +6077,7 @@ "kind": "section" }, { - "id": "468-release-notes/1.6#fixes", + "id": "469-release-notes/1.6#fixes", "title": "Zero 1.6", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -6073,7 +6087,7 @@ "kind": "section" }, { - "id": "469-release-notes/1.6#breaking-changes", + "id": "470-release-notes/1.6#breaking-changes", "title": "Zero 1.6", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -6125,7 +6139,7 @@ "kind": "page" }, { - "id": "470-release-notes/1.7#installation", + "id": "471-release-notes/1.7#installation", "title": "Zero 1.7", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -6135,7 +6149,7 @@ "kind": "section" }, { - "id": "471-release-notes/1.7#overview", + "id": "472-release-notes/1.7#overview", "title": "Zero 1.7", "searchTitle": "Overview", "sectionTitle": "Overview", @@ -6145,7 +6159,7 @@ "kind": "section" }, { - "id": "472-release-notes/1.7#features", + "id": "473-release-notes/1.7#features", "title": "Zero 1.7", "searchTitle": "Features", "sectionTitle": "Features", @@ -6155,7 +6169,7 @@ "kind": "section" }, { - "id": "473-release-notes/1.7#performance", + "id": "474-release-notes/1.7#performance", "title": "Zero 1.7", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -6165,7 +6179,7 @@ "kind": "section" }, { - "id": "474-release-notes/1.7#replication", + "id": "475-release-notes/1.7#replication", "title": "Zero 1.7", "searchTitle": "Replication", "sectionTitle": "Replication", @@ -6175,7 +6189,7 @@ "kind": "section" }, { - "id": "475-release-notes/1.7#flipped-exists-queries", + "id": "476-release-notes/1.7#flipped-exists-queries", "title": "Zero 1.7", "searchTitle": "Flipped Exists Queries", "sectionTitle": "Flipped Exists Queries", @@ -6185,7 +6199,7 @@ "kind": "section" }, { - "id": "476-release-notes/1.7#fixes", + "id": "477-release-notes/1.7#fixes", "title": "Zero 1.7", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -6195,7 +6209,7 @@ "kind": "section" }, { - "id": "477-release-notes/1.7#breaking-changes", + "id": "478-release-notes/1.7#breaking-changes", "title": "Zero 1.7", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -6251,7 +6265,7 @@ "kind": "page" }, { - "id": "478-release-notes/1.8#installation", + "id": "479-release-notes/1.8#installation", "title": "Zero 1.8", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -6261,7 +6275,7 @@ "kind": "section" }, { - "id": "479-release-notes/1.8#overview", + "id": "480-release-notes/1.8#overview", "title": "Zero 1.8", "searchTitle": "Overview", "sectionTitle": "Overview", @@ -6271,7 +6285,7 @@ "kind": "section" }, { - "id": "480-release-notes/1.8#features", + "id": "481-release-notes/1.8#features", "title": "Zero 1.8", "searchTitle": "Features", "sectionTitle": "Features", @@ -6281,7 +6295,7 @@ "kind": "section" }, { - "id": "481-release-notes/1.8#performance", + "id": "482-release-notes/1.8#performance", "title": "Zero 1.8", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -6291,7 +6305,7 @@ "kind": "section" }, { - "id": "482-release-notes/1.8#replicating-large-transactions", + "id": "483-release-notes/1.8#replicating-large-transactions", "title": "Zero 1.8", "searchTitle": "Replicating Large Transactions", "sectionTitle": "Replicating Large Transactions", @@ -6301,7 +6315,7 @@ "kind": "section" }, { - "id": "483-release-notes/1.8#maintaining-limit-queries", + "id": "484-release-notes/1.8#maintaining-limit-queries", "title": "Zero 1.8", "searchTitle": "Maintaining limit() Queries", "sectionTitle": "Maintaining limit() Queries", @@ -6311,7 +6325,7 @@ "kind": "section" }, { - "id": "484-release-notes/1.8#client-side-hydration", + "id": "485-release-notes/1.8#client-side-hydration", "title": "Zero 1.8", "searchTitle": "Client-Side Hydration", "sectionTitle": "Client-Side Hydration", @@ -6321,7 +6335,7 @@ "kind": "section" }, { - "id": "485-release-notes/1.8#fixes", + "id": "486-release-notes/1.8#fixes", "title": "Zero 1.8", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -6331,7 +6345,7 @@ "kind": "section" }, { - "id": "486-release-notes/1.8#breaking-changes", + "id": "487-release-notes/1.8#breaking-changes", "title": "Zero 1.8", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -6375,7 +6389,7 @@ "kind": "page" }, { - "id": "487-release-notes/1.9#installation", + "id": "488-release-notes/1.9#installation", "title": "Zero 1.9", "searchTitle": "Installation", "sectionTitle": "Installation", @@ -6385,7 +6399,7 @@ "kind": "section" }, { - "id": "488-release-notes/1.9#overview", + "id": "489-release-notes/1.9#overview", "title": "Zero 1.9", "searchTitle": "Overview", "sectionTitle": "Overview", @@ -6395,7 +6409,7 @@ "kind": "section" }, { - "id": "489-release-notes/1.9#features", + "id": "490-release-notes/1.9#features", "title": "Zero 1.9", "searchTitle": "Features", "sectionTitle": "Features", @@ -6405,7 +6419,7 @@ "kind": "section" }, { - "id": "490-release-notes/1.9#performance", + "id": "491-release-notes/1.9#performance", "title": "Zero 1.9", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -6415,7 +6429,7 @@ "kind": "section" }, { - "id": "491-release-notes/1.9#cold-mutation-latency", + "id": "492-release-notes/1.9#cold-mutation-latency", "title": "Zero 1.9", "searchTitle": "Cold Mutation Latency", "sectionTitle": "Cold Mutation Latency", @@ -6425,7 +6439,7 @@ "kind": "section" }, { - "id": "492-release-notes/1.9#fixes", + "id": "493-release-notes/1.9#fixes", "title": "Zero 1.9", "searchTitle": "Fixes", "sectionTitle": "Fixes", @@ -6462,7 +6476,7 @@ "kind": "page" }, { - "id": "493-reporting-bugs#zbugs", + "id": "494-reporting-bugs#zbugs", "title": "Reporting Bugs", "searchTitle": "zbugs", "sectionTitle": "zbugs", @@ -6472,7 +6486,7 @@ "kind": "section" }, { - "id": "494-reporting-bugs#discord", + "id": "495-reporting-bugs#discord", "title": "Reporting Bugs", "searchTitle": "Discord", "sectionTitle": "Discord", @@ -6508,7 +6522,7 @@ "kind": "page" }, { - "id": "495-rest#pattern", + "id": "496-rest#pattern", "title": "REST", "searchTitle": "Pattern", "sectionTitle": "Pattern", @@ -6518,7 +6532,7 @@ "kind": "section" }, { - "id": "496-rest#tanstack-start-example", + "id": "497-rest#tanstack-start-example", "title": "REST", "searchTitle": "TanStack Start Example", "sectionTitle": "TanStack Start Example", @@ -6528,7 +6542,7 @@ "kind": "section" }, { - "id": "497-rest#openapi-generation", + "id": "498-rest#openapi-generation", "title": "REST", "searchTitle": "OpenAPI Generation", "sectionTitle": "OpenAPI Generation", @@ -6538,7 +6552,7 @@ "kind": "section" }, { - "id": "498-rest#full-working-example", + "id": "499-rest#full-working-example", "title": "REST", "searchTitle": "Full Working Example", "sectionTitle": "Full Working Example", @@ -6566,7 +6580,7 @@ "kind": "page" }, { - "id": "499-roadmap#q4-2025", + "id": "500-roadmap#q4-2025", "title": "Roadmap", "searchTitle": "Q4 2025", "sectionTitle": "Q4 2025", @@ -6576,7 +6590,7 @@ "kind": "section" }, { - "id": "500-roadmap#beyond", + "id": "501-roadmap#beyond", "title": "Roadmap", "searchTitle": "Beyond", "sectionTitle": "Beyond", @@ -6612,7 +6626,7 @@ "kind": "page" }, { - "id": "501-samples#gigabugs", + "id": "502-samples#gigabugs", "title": "Samples", "searchTitle": "Gigabugs", "sectionTitle": "Gigabugs", @@ -6622,7 +6636,7 @@ "kind": "section" }, { - "id": "502-samples#ztunes", + "id": "503-samples#ztunes", "title": "Samples", "searchTitle": "ztunes", "sectionTitle": "ztunes", @@ -6632,7 +6646,7 @@ "kind": "section" }, { - "id": "503-samples#zslack", + "id": "504-samples#zslack", "title": "Samples", "searchTitle": "zslack", "sectionTitle": "zslack", @@ -6642,7 +6656,7 @@ "kind": "section" }, { - "id": "504-samples#zero-music", + "id": "505-samples#zero-music", "title": "Samples", "searchTitle": "zero-music", "sectionTitle": "zero-music", @@ -6782,7 +6796,7 @@ "kind": "page" }, { - "id": "505-schema#generating-from-database", + "id": "506-schema#generating-from-database", "title": "Zero Schema", "searchTitle": "Generating from Database", "sectionTitle": "Generating from Database", @@ -6792,7 +6806,7 @@ "kind": "section" }, { - "id": "506-schema#writing-by-hand", + "id": "507-schema#writing-by-hand", "title": "Zero Schema", "searchTitle": "Writing by Hand", "sectionTitle": "Writing by Hand", @@ -6802,7 +6816,7 @@ "kind": "section" }, { - "id": "507-schema#table-schemas", + "id": "508-schema#table-schemas", "title": "Zero Schema", "searchTitle": "Table Schemas", "sectionTitle": "Table Schemas", @@ -6812,7 +6826,7 @@ "kind": "section" }, { - "id": "508-schema#name-mapping", + "id": "509-schema#name-mapping", "title": "Zero Schema", "searchTitle": "Name Mapping", "sectionTitle": "Name Mapping", @@ -6822,7 +6836,7 @@ "kind": "section" }, { - "id": "509-schema#multiple-schemas", + "id": "510-schema#multiple-schemas", "title": "Zero Schema", "searchTitle": "Multiple Schemas", "sectionTitle": "Multiple Schemas", @@ -6832,7 +6846,7 @@ "kind": "section" }, { - "id": "510-schema#optional-columns", + "id": "511-schema#optional-columns", "title": "Zero Schema", "searchTitle": "Optional Columns", "sectionTitle": "Optional Columns", @@ -6842,7 +6856,7 @@ "kind": "section" }, { - "id": "511-schema#enumerations", + "id": "512-schema#enumerations", "title": "Zero Schema", "searchTitle": "Enumerations", "sectionTitle": "Enumerations", @@ -6852,7 +6866,7 @@ "kind": "section" }, { - "id": "512-schema#custom-json-types", + "id": "513-schema#custom-json-types", "title": "Zero Schema", "searchTitle": "Custom JSON Types", "sectionTitle": "Custom JSON Types", @@ -6862,7 +6876,7 @@ "kind": "section" }, { - "id": "513-schema#compound-primary-keys", + "id": "514-schema#compound-primary-keys", "title": "Zero Schema", "searchTitle": "Compound Primary Keys", "sectionTitle": "Compound Primary Keys", @@ -6872,7 +6886,7 @@ "kind": "section" }, { - "id": "514-schema#unique-keys", + "id": "515-schema#unique-keys", "title": "Zero Schema", "searchTitle": "Unique Keys", "sectionTitle": "Unique Keys", @@ -6882,7 +6896,7 @@ "kind": "section" }, { - "id": "515-schema#relationships", + "id": "516-schema#relationships", "title": "Zero Schema", "searchTitle": "Relationships", "sectionTitle": "Relationships", @@ -6892,7 +6906,7 @@ "kind": "section" }, { - "id": "516-schema#many-to-many-relationships", + "id": "517-schema#many-to-many-relationships", "title": "Zero Schema", "searchTitle": "Many-to-Many Relationships", "sectionTitle": "Many-to-Many Relationships", @@ -6902,7 +6916,7 @@ "kind": "section" }, { - "id": "517-schema#compound-keys-relationships", + "id": "518-schema#compound-keys-relationships", "title": "Zero Schema", "searchTitle": "Compound Keys Relationships", "sectionTitle": "Compound Keys Relationships", @@ -6912,7 +6926,7 @@ "kind": "section" }, { - "id": "518-schema#circular-relationships", + "id": "519-schema#circular-relationships", "title": "Zero Schema", "searchTitle": "Circular Relationships", "sectionTitle": "Circular Relationships", @@ -6922,7 +6936,7 @@ "kind": "section" }, { - "id": "519-schema#database-schemas", + "id": "520-schema#database-schemas", "title": "Zero Schema", "searchTitle": "Database Schemas", "sectionTitle": "Database Schemas", @@ -6932,7 +6946,7 @@ "kind": "section" }, { - "id": "520-schema#register-schema-type", + "id": "521-schema#register-schema-type", "title": "Zero Schema", "searchTitle": "Register Schema Type", "sectionTitle": "Register Schema Type", @@ -6942,7 +6956,7 @@ "kind": "section" }, { - "id": "521-schema#schema-changes", + "id": "522-schema#schema-changes", "title": "Zero Schema", "searchTitle": "Schema Changes", "sectionTitle": "Schema Changes", @@ -6952,7 +6966,7 @@ "kind": "section" }, { - "id": "522-schema#development", + "id": "523-schema#development", "title": "Zero Schema", "searchTitle": "Development", "sectionTitle": "Development", @@ -6962,7 +6976,7 @@ "kind": "section" }, { - "id": "523-schema#production", + "id": "524-schema#production", "title": "Zero Schema", "searchTitle": "Production", "sectionTitle": "Production", @@ -6972,7 +6986,7 @@ "kind": "section" }, { - "id": "524-schema#expand-changes", + "id": "525-schema#expand-changes", "title": "Zero Schema", "searchTitle": "Expand Changes", "sectionTitle": "Expand Changes", @@ -6982,7 +6996,7 @@ "kind": "section" }, { - "id": "525-schema#contract-changes", + "id": "526-schema#contract-changes", "title": "Zero Schema", "searchTitle": "Contract Changes", "sectionTitle": "Contract Changes", @@ -6992,7 +7006,7 @@ "kind": "section" }, { - "id": "526-schema#compound-changes", + "id": "527-schema#compound-changes", "title": "Zero Schema", "searchTitle": "Compound Changes", "sectionTitle": "Compound Changes", @@ -7002,7 +7016,7 @@ "kind": "section" }, { - "id": "527-schema#examples", + "id": "528-schema#examples", "title": "Zero Schema", "searchTitle": "Examples", "sectionTitle": "Examples", @@ -7012,7 +7026,7 @@ "kind": "section" }, { - "id": "528-schema#adding-a-column", + "id": "529-schema#adding-a-column", "title": "Zero Schema", "searchTitle": "Adding a Column", "sectionTitle": "Adding a Column", @@ -7022,7 +7036,7 @@ "kind": "section" }, { - "id": "529-schema#removing-a-column", + "id": "530-schema#removing-a-column", "title": "Zero Schema", "searchTitle": "Removing a Column", "sectionTitle": "Removing a Column", @@ -7032,7 +7046,7 @@ "kind": "section" }, { - "id": "530-schema#renaming-a-column", + "id": "531-schema#renaming-a-column", "title": "Zero Schema", "searchTitle": "Renaming a Column", "sectionTitle": "Renaming a Column", @@ -7042,7 +7056,7 @@ "kind": "section" }, { - "id": "531-schema#making-a-column-optional", + "id": "532-schema#making-a-column-optional", "title": "Zero Schema", "searchTitle": "Making a Column Optional", "sectionTitle": "Making a Column Optional", @@ -7052,7 +7066,7 @@ "kind": "section" }, { - "id": "532-schema#quick-reference", + "id": "533-schema#quick-reference", "title": "Zero Schema", "searchTitle": "Quick Reference", "sectionTitle": "Quick Reference", @@ -7062,7 +7076,7 @@ "kind": "section" }, { - "id": "533-schema#backfill", + "id": "534-schema#backfill", "title": "Zero Schema", "searchTitle": "Backfill", "sectionTitle": "Backfill", @@ -7072,7 +7086,7 @@ "kind": "section" }, { - "id": "534-schema#monitoring-backfill-progress", + "id": "535-schema#monitoring-backfill-progress", "title": "Zero Schema", "searchTitle": "Monitoring Backfill Progress", "sectionTitle": "Monitoring Backfill Progress", @@ -7144,7 +7158,7 @@ "kind": "page" }, { - "id": "535-self-host#docker-images", + "id": "536-self-host#docker-images", "title": "Self-Hosting Zero", "searchTitle": "Docker Images", "sectionTitle": "Docker Images", @@ -7154,7 +7168,7 @@ "kind": "section" }, { - "id": "536-self-host#minimum-viable-strategy", + "id": "537-self-host#minimum-viable-strategy", "title": "Self-Hosting Zero", "searchTitle": "Minimum Viable Strategy", "sectionTitle": "Minimum Viable Strategy", @@ -7164,7 +7178,7 @@ "kind": "section" }, { - "id": "537-self-host#maximal-strategy", + "id": "538-self-host#maximal-strategy", "title": "Self-Hosting Zero", "searchTitle": "Maximal Strategy", "sectionTitle": "Maximal Strategy", @@ -7174,7 +7188,7 @@ "kind": "section" }, { - "id": "538-self-host#replica-lifecycle", + "id": "539-self-host#replica-lifecycle", "title": "Self-Hosting Zero", "searchTitle": "Replica Lifecycle", "sectionTitle": "Replica Lifecycle", @@ -7184,7 +7198,7 @@ "kind": "section" }, { - "id": "539-self-host#performance", + "id": "540-self-host#performance", "title": "Self-Hosting Zero", "searchTitle": "Performance", "sectionTitle": "Performance", @@ -7194,7 +7208,7 @@ "kind": "section" }, { - "id": "540-self-host#hydration", + "id": "541-self-host#hydration", "title": "Self-Hosting Zero", "searchTitle": "Hydration", "sectionTitle": "Hydration", @@ -7204,7 +7218,7 @@ "kind": "section" }, { - "id": "541-self-host#ivm-advancement", + "id": "542-self-host#ivm-advancement", "title": "Self-Hosting Zero", "searchTitle": "IVM advancement", "sectionTitle": "IVM advancement", @@ -7214,7 +7228,7 @@ "kind": "section" }, { - "id": "542-self-host#system-level", + "id": "543-self-host#system-level", "title": "Self-Hosting Zero", "searchTitle": "System-level", "sectionTitle": "System-level", @@ -7224,7 +7238,7 @@ "kind": "section" }, { - "id": "543-self-host#networking", + "id": "544-self-host#networking", "title": "Self-Hosting Zero", "searchTitle": "Networking", "sectionTitle": "Networking", @@ -7234,7 +7248,7 @@ "kind": "section" }, { - "id": "544-self-host#sticky-sessions", + "id": "545-self-host#sticky-sessions", "title": "Self-Hosting Zero", "searchTitle": "Sticky Sessions", "sectionTitle": "Sticky Sessions", @@ -7244,7 +7258,7 @@ "kind": "section" }, { - "id": "545-self-host#rolling-updates", + "id": "546-self-host#rolling-updates", "title": "Self-Hosting Zero", "searchTitle": "Rolling Updates", "sectionTitle": "Rolling Updates", @@ -7254,7 +7268,7 @@ "kind": "section" }, { - "id": "546-self-host#clientserver-version-compatibility", + "id": "547-self-host#clientserver-version-compatibility", "title": "Self-Hosting Zero", "searchTitle": "Client/Server Version Compatibility", "sectionTitle": "Client/Server Version Compatibility", @@ -7264,7 +7278,7 @@ "kind": "section" }, { - "id": "547-self-host#configuration", + "id": "548-self-host#configuration", "title": "Self-Hosting Zero", "searchTitle": "Configuration", "sectionTitle": "Configuration", @@ -7300,7 +7314,7 @@ "kind": "page" }, { - "id": "548-server-zql#creating-a-database", + "id": "549-server-zql#creating-a-database", "title": "ZQL on the Server", "searchTitle": "Creating a Database", "sectionTitle": "Creating a Database", @@ -7310,7 +7324,7 @@ "kind": "section" }, { - "id": "549-server-zql#custom-database", + "id": "550-server-zql#custom-database", "title": "ZQL on the Server", "searchTitle": "Custom Database", "sectionTitle": "Custom Database", @@ -7320,7 +7334,7 @@ "kind": "section" }, { - "id": "550-server-zql#running-zql", + "id": "551-server-zql#running-zql", "title": "ZQL on the Server", "searchTitle": "Running ZQL", "sectionTitle": "Running ZQL", @@ -7330,7 +7344,7 @@ "kind": "section" }, { - "id": "551-server-zql#ssr", + "id": "552-server-zql#ssr", "title": "ZQL on the Server", "searchTitle": "SSR", "sectionTitle": "SSR", @@ -7362,7 +7376,7 @@ "kind": "page" }, { - "id": "552-solidjs#setup", + "id": "553-solidjs#setup", "title": "SolidJS", "searchTitle": "Setup", "sectionTitle": "Setup", @@ -7372,7 +7386,7 @@ "kind": "section" }, { - "id": "553-solidjs#usage", + "id": "554-solidjs#usage", "title": "SolidJS", "searchTitle": "Usage", "sectionTitle": "Usage", @@ -7382,7 +7396,7 @@ "kind": "section" }, { - "id": "554-solidjs#examples", + "id": "555-solidjs#examples", "title": "SolidJS", "searchTitle": "Examples", "sectionTitle": "Examples", @@ -7418,7 +7432,7 @@ "kind": "page" }, { - "id": "555-status#breaking-changes", + "id": "556-status#breaking-changes", "title": "Project Status", "searchTitle": "Breaking Changes", "sectionTitle": "Breaking Changes", @@ -7428,7 +7442,7 @@ "kind": "section" }, { - "id": "556-status#roadmap", + "id": "557-status#roadmap", "title": "Project Status", "searchTitle": "Roadmap", "sectionTitle": "Roadmap", @@ -7438,7 +7452,7 @@ "kind": "section" }, { - "id": "557-status#2026", + "id": "558-status#2026", "title": "Project Status", "searchTitle": "2026", "sectionTitle": "2026", @@ -7448,7 +7462,7 @@ "kind": "section" }, { - "id": "558-status#soon", + "id": "559-status#soon", "title": "Project Status", "searchTitle": "Soon", "sectionTitle": "Soon", @@ -7480,7 +7494,7 @@ "kind": "page" }, { - "id": "559-sync#problem", + "id": "560-sync#problem", "title": "What is Sync?", "searchTitle": "Problem", "sectionTitle": "Problem", @@ -7490,7 +7504,7 @@ "kind": "section" }, { - "id": "560-sync#solution", + "id": "561-sync#solution", "title": "What is Sync?", "searchTitle": "Solution", "sectionTitle": "Solution", @@ -7500,7 +7514,7 @@ "kind": "section" }, { - "id": "561-sync#history-of-sync", + "id": "562-sync#history-of-sync", "title": "What is Sync?", "searchTitle": "History of Sync", "sectionTitle": "History of Sync", @@ -7584,7 +7598,7 @@ "kind": "page" }, { - "id": "562-tutorial#setup", + "id": "563-tutorial#setup", "title": "Tutorial", "searchTitle": "Setup", "sectionTitle": "Setup", @@ -7594,7 +7608,7 @@ "kind": "section" }, { - "id": "563-tutorial#create-a-project", + "id": "564-tutorial#create-a-project", "title": "Tutorial", "searchTitle": "Create a Project", "sectionTitle": "Create a Project", @@ -7604,7 +7618,7 @@ "kind": "section" }, { - "id": "564-tutorial#set-up-your-database", + "id": "565-tutorial#set-up-your-database", "title": "Tutorial", "searchTitle": "Set Up Your Database", "sectionTitle": "Set Up Your Database", @@ -7614,7 +7628,7 @@ "kind": "section" }, { - "id": "565-tutorial#install-and-run-zero-cache", + "id": "566-tutorial#install-and-run-zero-cache", "title": "Tutorial", "searchTitle": "Install and Run Zero-Cache", "sectionTitle": "Install and Run Zero-Cache", @@ -7624,7 +7638,7 @@ "kind": "section" }, { - "id": "566-tutorial#integrate-zero", + "id": "567-tutorial#integrate-zero", "title": "Tutorial", "searchTitle": "Integrate Zero", "sectionTitle": "Integrate Zero", @@ -7634,7 +7648,7 @@ "kind": "section" }, { - "id": "567-tutorial#set-up-your-zero-schema", + "id": "568-tutorial#set-up-your-zero-schema", "title": "Tutorial", "searchTitle": "Set Up Your Zero Schema", "sectionTitle": "Set Up Your Zero Schema", @@ -7644,7 +7658,7 @@ "kind": "section" }, { - "id": "568-tutorial#set-up-the-zero-client", + "id": "569-tutorial#set-up-the-zero-client", "title": "Tutorial", "searchTitle": "Set Up the Zero Client", "sectionTitle": "Set Up the Zero Client", @@ -7654,7 +7668,7 @@ "kind": "section" }, { - "id": "569-tutorial#sync-data", + "id": "570-tutorial#sync-data", "title": "Tutorial", "searchTitle": "Sync Data", "sectionTitle": "Sync Data", @@ -7664,7 +7678,7 @@ "kind": "section" }, { - "id": "570-tutorial#define-query", + "id": "571-tutorial#define-query", "title": "Tutorial", "searchTitle": "Define Query", "sectionTitle": "Define Query", @@ -7674,7 +7688,7 @@ "kind": "section" }, { - "id": "571-tutorial#add-query-endpoint", + "id": "572-tutorial#add-query-endpoint", "title": "Tutorial", "searchTitle": "Add Query Endpoint", "sectionTitle": "Add Query Endpoint", @@ -7684,7 +7698,7 @@ "kind": "section" }, { - "id": "572-tutorial#invoke-query", + "id": "573-tutorial#invoke-query", "title": "Tutorial", "searchTitle": "Invoke Query", "sectionTitle": "Invoke Query", @@ -7694,7 +7708,7 @@ "kind": "section" }, { - "id": "573-tutorial#mutate-data", + "id": "574-tutorial#mutate-data", "title": "Tutorial", "searchTitle": "Mutate Data", "sectionTitle": "Mutate Data", @@ -7704,7 +7718,7 @@ "kind": "section" }, { - "id": "574-tutorial#define-mutators", + "id": "575-tutorial#define-mutators", "title": "Tutorial", "searchTitle": "Define Mutators", "sectionTitle": "Define Mutators", @@ -7714,7 +7728,7 @@ "kind": "section" }, { - "id": "575-tutorial#add-mutate-endpoint", + "id": "576-tutorial#add-mutate-endpoint", "title": "Tutorial", "searchTitle": "Add Mutate Endpoint", "sectionTitle": "Add Mutate Endpoint", @@ -7724,7 +7738,7 @@ "kind": "section" }, { - "id": "576-tutorial#invoke-mutators", + "id": "577-tutorial#invoke-mutators", "title": "Tutorial", "searchTitle": "Invoke Mutators", "sectionTitle": "Invoke Mutators", @@ -7734,7 +7748,7 @@ "kind": "section" }, { - "id": "577-tutorial#next-steps", + "id": "578-tutorial#next-steps", "title": "Tutorial", "searchTitle": "Next Steps", "sectionTitle": "Next Steps", @@ -7810,7 +7824,7 @@ "kind": "page" }, { - "id": "578-when-to-use#zero-might-be-a-good-fit", + "id": "579-when-to-use#zero-might-be-a-good-fit", "title": "When To Use Zero", "searchTitle": "Zero Might be a Good Fit", "sectionTitle": "Zero Might be a Good Fit", @@ -7820,7 +7834,7 @@ "kind": "section" }, { - "id": "579-when-to-use#you-want-to-sync-only-a-small-subset-of-data-to-client", + "id": "580-when-to-use#you-want-to-sync-only-a-small-subset-of-data-to-client", "title": "When To Use Zero", "searchTitle": "You want to sync only a small subset of data to client", "sectionTitle": "You want to sync only a small subset of data to client", @@ -7830,7 +7844,7 @@ "kind": "section" }, { - "id": "580-when-to-use#you-need-fine-grained-read-or-write-permissions", + "id": "581-when-to-use#you-need-fine-grained-read-or-write-permissions", "title": "When To Use Zero", "searchTitle": "You need fine-grained read or write permissions", "sectionTitle": "You need fine-grained read or write permissions", @@ -7840,7 +7854,7 @@ "kind": "section" }, { - "id": "581-when-to-use#you-are-building-a-traditional-client-server-web-app", + "id": "582-when-to-use#you-are-building-a-traditional-client-server-web-app", "title": "When To Use Zero", "searchTitle": "You are building a traditional client-server web app", "sectionTitle": "You are building a traditional client-server web app", @@ -7850,7 +7864,7 @@ "kind": "section" }, { - "id": "582-when-to-use#you-use-postgresql", + "id": "583-when-to-use#you-use-postgresql", "title": "When To Use Zero", "searchTitle": "You use PostgreSQL", "sectionTitle": "You use PostgreSQL", @@ -7860,7 +7874,7 @@ "kind": "section" }, { - "id": "583-when-to-use#your-app-is-broadly-like-linear", + "id": "584-when-to-use#your-app-is-broadly-like-linear", "title": "When To Use Zero", "searchTitle": "Your app is broadly \"like Linear\"", "sectionTitle": "Your app is broadly \"like Linear\"", @@ -7870,7 +7884,7 @@ "kind": "section" }, { - "id": "584-when-to-use#interaction-performance-is-very-important-to-you", + "id": "585-when-to-use#interaction-performance-is-very-important-to-you", "title": "When To Use Zero", "searchTitle": "Interaction performance is very important to you", "sectionTitle": "Interaction performance is very important to you", @@ -7880,7 +7894,7 @@ "kind": "section" }, { - "id": "585-when-to-use#zero-might-not-be-a-good-fit", + "id": "586-when-to-use#zero-might-not-be-a-good-fit", "title": "When To Use Zero", "searchTitle": "Zero Might Not be a Good Fit", "sectionTitle": "Zero Might Not be a Good Fit", @@ -7890,7 +7904,7 @@ "kind": "section" }, { - "id": "586-when-to-use#you-need-the-privacy-or-data-ownership-benefits-of-local-first", + "id": "587-when-to-use#you-need-the-privacy-or-data-ownership-benefits-of-local-first", "title": "When To Use Zero", "searchTitle": "You need the privacy or data ownership benefits of local-first", "sectionTitle": "You need the privacy or data ownership benefits of local-first", @@ -7900,7 +7914,7 @@ "kind": "section" }, { - "id": "587-when-to-use#you-need-to-support-offline-writes-or-long-periods-offline", + "id": "588-when-to-use#you-need-to-support-offline-writes-or-long-periods-offline", "title": "When To Use Zero", "searchTitle": "You need to support offline writes or long periods offline", "sectionTitle": "You need to support offline writes or long periods offline", @@ -7910,7 +7924,7 @@ "kind": "section" }, { - "id": "588-when-to-use#you-are-building-a-native-mobile-app", + "id": "589-when-to-use#you-are-building-a-native-mobile-app", "title": "When To Use Zero", "searchTitle": "You are building a native mobile app", "sectionTitle": "You are building a native mobile app", @@ -7920,7 +7934,7 @@ "kind": "section" }, { - "id": "589-when-to-use#the-total-backend-dataset-is--100gb", + "id": "590-when-to-use#the-total-backend-dataset-is--100gb", "title": "When To Use Zero", "searchTitle": "The total backend dataset is > ~100GB", "sectionTitle": "The total backend dataset is > ~100GB", @@ -7930,7 +7944,7 @@ "kind": "section" }, { - "id": "590-when-to-use#zero-might-not-be-a-good-fit-yet", + "id": "591-when-to-use#zero-might-not-be-a-good-fit-yet", "title": "When To Use Zero", "searchTitle": "Zero Might Not be a Good Fit Yet", "sectionTitle": "Zero Might Not be a Good Fit Yet", @@ -7940,7 +7954,7 @@ "kind": "section" }, { - "id": "591-when-to-use#alternatives", + "id": "592-when-to-use#alternatives", "title": "When To Use Zero", "searchTitle": "Alternatives", "sectionTitle": "Alternatives", @@ -8336,7 +8350,7 @@ "kind": "page" }, { - "id": "592-zero-cache-config#required-flags", + "id": "593-zero-cache-config#required-flags", "title": "zero-cache Config", "searchTitle": "Required Flags", "sectionTitle": "Required Flags", @@ -8346,7 +8360,7 @@ "kind": "section" }, { - "id": "593-zero-cache-config#upstream-db", + "id": "594-zero-cache-config#upstream-db", "title": "zero-cache Config", "searchTitle": "Upstream DB", "sectionTitle": "Upstream DB", @@ -8356,7 +8370,7 @@ "kind": "section" }, { - "id": "594-zero-cache-config#admin-password", + "id": "595-zero-cache-config#admin-password", "title": "zero-cache Config", "searchTitle": "Admin Password", "sectionTitle": "Admin Password", @@ -8366,7 +8380,7 @@ "kind": "section" }, { - "id": "595-zero-cache-config#optional-flags", + "id": "596-zero-cache-config#optional-flags", "title": "zero-cache Config", "searchTitle": "Optional Flags", "sectionTitle": "Optional Flags", @@ -8376,7 +8390,7 @@ "kind": "section" }, { - "id": "596-zero-cache-config#app-id", + "id": "597-zero-cache-config#app-id", "title": "zero-cache Config", "searchTitle": "App ID", "sectionTitle": "App ID", @@ -8386,7 +8400,7 @@ "kind": "section" }, { - "id": "597-zero-cache-config#app-publications", + "id": "598-zero-cache-config#app-publications", "title": "zero-cache Config", "searchTitle": "App Publications", "sectionTitle": "App Publications", @@ -8396,7 +8410,7 @@ "kind": "section" }, { - "id": "598-zero-cache-config#auth-revalidate-interval-seconds", + "id": "599-zero-cache-config#auth-revalidate-interval-seconds", "title": "zero-cache Config", "searchTitle": "Auth Revalidate Interval Seconds", "sectionTitle": "Auth Revalidate Interval Seconds", @@ -8406,7 +8420,7 @@ "kind": "section" }, { - "id": "599-zero-cache-config#auth-retransform-interval-seconds", + "id": "600-zero-cache-config#auth-retransform-interval-seconds", "title": "zero-cache Config", "searchTitle": "Auth Retransform Interval Seconds", "sectionTitle": "Auth Retransform Interval Seconds", @@ -8416,7 +8430,7 @@ "kind": "section" }, { - "id": "600-zero-cache-config#auto-reset", + "id": "601-zero-cache-config#auto-reset", "title": "zero-cache Config", "searchTitle": "Auto Reset", "sectionTitle": "Auto Reset", @@ -8426,7 +8440,7 @@ "kind": "section" }, { - "id": "601-zero-cache-config#change-db", + "id": "602-zero-cache-config#change-db", "title": "zero-cache Config", "searchTitle": "Change DB", "sectionTitle": "Change DB", @@ -8436,7 +8450,7 @@ "kind": "section" }, { - "id": "602-zero-cache-config#change-streamer-back-pressure-limit-heap-proportion", + "id": "603-zero-cache-config#change-streamer-back-pressure-limit-heap-proportion", "title": "zero-cache Config", "searchTitle": "Change Streamer Back Pressure Limit Heap Proportion", "sectionTitle": "Change Streamer Back Pressure Limit Heap Proportion", @@ -8446,7 +8460,7 @@ "kind": "section" }, { - "id": "603-zero-cache-config#change-streamer-flow-control-consensus-timeout-proportion", + "id": "604-zero-cache-config#change-streamer-flow-control-consensus-timeout-proportion", "title": "zero-cache Config", "searchTitle": "Change Streamer Flow Control Consensus Timeout Proportion", "sectionTitle": "Change Streamer Flow Control Consensus Timeout Proportion", @@ -8456,7 +8470,7 @@ "kind": "section" }, { - "id": "604-zero-cache-config#change-streamer-flow-control-slow-subscriber-grace-period-seconds", + "id": "605-zero-cache-config#change-streamer-flow-control-slow-subscriber-grace-period-seconds", "title": "zero-cache Config", "searchTitle": "Change Streamer Flow Control Slow Subscriber Grace Period Seconds", "sectionTitle": "Change Streamer Flow Control Slow Subscriber Grace Period Seconds", @@ -8466,7 +8480,7 @@ "kind": "section" }, { - "id": "605-zero-cache-config#change-streamer-mode", + "id": "606-zero-cache-config#change-streamer-mode", "title": "zero-cache Config", "searchTitle": "Change Streamer Mode", "sectionTitle": "Change Streamer Mode", @@ -8476,7 +8490,7 @@ "kind": "section" }, { - "id": "606-zero-cache-config#change-streamer-port", + "id": "607-zero-cache-config#change-streamer-port", "title": "zero-cache Config", "searchTitle": "Change Streamer Port", "sectionTitle": "Change Streamer Port", @@ -8486,7 +8500,7 @@ "kind": "section" }, { - "id": "607-zero-cache-config#change-streamer-startup-delay-ms", + "id": "608-zero-cache-config#change-streamer-startup-delay-ms", "title": "zero-cache Config", "searchTitle": "Change Streamer Startup Delay (ms)", "sectionTitle": "Change Streamer Startup Delay (ms)", @@ -8496,7 +8510,7 @@ "kind": "section" }, { - "id": "608-zero-cache-config#change-streamer-uri", + "id": "609-zero-cache-config#change-streamer-uri", "title": "zero-cache Config", "searchTitle": "Change Streamer URI", "sectionTitle": "Change Streamer URI", @@ -8506,7 +8520,7 @@ "kind": "section" }, { - "id": "609-zero-cache-config#cvr-db", + "id": "610-zero-cache-config#cvr-db", "title": "zero-cache Config", "searchTitle": "CVR DB", "sectionTitle": "CVR DB", @@ -8516,7 +8530,7 @@ "kind": "section" }, { - "id": "610-zero-cache-config#cvr-garbage-collection-inactivity-threshold-hours", + "id": "611-zero-cache-config#cvr-garbage-collection-inactivity-threshold-hours", "title": "zero-cache Config", "searchTitle": "CVR Garbage Collection Inactivity Threshold Hours", "sectionTitle": "CVR Garbage Collection Inactivity Threshold Hours", @@ -8526,7 +8540,7 @@ "kind": "section" }, { - "id": "611-zero-cache-config#cvr-garbage-collection-initial-batch-size", + "id": "612-zero-cache-config#cvr-garbage-collection-initial-batch-size", "title": "zero-cache Config", "searchTitle": "CVR Garbage Collection Initial Batch Size", "sectionTitle": "CVR Garbage Collection Initial Batch Size", @@ -8536,7 +8550,7 @@ "kind": "section" }, { - "id": "612-zero-cache-config#cvr-garbage-collection-initial-interval-seconds", + "id": "613-zero-cache-config#cvr-garbage-collection-initial-interval-seconds", "title": "zero-cache Config", "searchTitle": "CVR Garbage Collection Initial Interval Seconds", "sectionTitle": "CVR Garbage Collection Initial Interval Seconds", @@ -8546,7 +8560,7 @@ "kind": "section" }, { - "id": "613-zero-cache-config#cvr-max-connections", + "id": "614-zero-cache-config#cvr-max-connections", "title": "zero-cache Config", "searchTitle": "CVR Max Connections", "sectionTitle": "CVR Max Connections", @@ -8556,7 +8570,7 @@ "kind": "section" }, { - "id": "614-zero-cache-config#enable-query-planner", + "id": "615-zero-cache-config#enable-query-planner", "title": "zero-cache Config", "searchTitle": "Enable Query Planner", "sectionTitle": "Enable Query Planner", @@ -8566,7 +8580,7 @@ "kind": "section" }, { - "id": "615-zero-cache-config#enable-crud-mutations", + "id": "616-zero-cache-config#enable-crud-mutations", "title": "zero-cache Config", "searchTitle": "Enable CRUD Mutations", "sectionTitle": "Enable CRUD Mutations", @@ -8576,7 +8590,7 @@ "kind": "section" }, { - "id": "616-zero-cache-config#enable-telemetry", + "id": "617-zero-cache-config#enable-telemetry", "title": "zero-cache Config", "searchTitle": "Enable Telemetry", "sectionTitle": "Enable Telemetry", @@ -8586,7 +8600,7 @@ "kind": "section" }, { - "id": "617-zero-cache-config#initial-sync-table-copy-workers", + "id": "618-zero-cache-config#initial-sync-table-copy-workers", "title": "zero-cache Config", "searchTitle": "Initial Sync Table Copy Workers", "sectionTitle": "Initial Sync Table Copy Workers", @@ -8596,7 +8610,7 @@ "kind": "section" }, { - "id": "618-zero-cache-config#lazy-startup", + "id": "619-zero-cache-config#lazy-startup", "title": "zero-cache Config", "searchTitle": "Lazy Startup", "sectionTitle": "Lazy Startup", @@ -8606,7 +8620,7 @@ "kind": "section" }, { - "id": "619-zero-cache-config#litestream-backup-url", + "id": "620-zero-cache-config#litestream-backup-url", "title": "zero-cache Config", "searchTitle": "Litestream Backup URL", "sectionTitle": "Litestream Backup URL", @@ -8616,7 +8630,7 @@ "kind": "section" }, { - "id": "620-zero-cache-config#litestream-endpoint", + "id": "621-zero-cache-config#litestream-endpoint", "title": "zero-cache Config", "searchTitle": "Litestream Endpoint", "sectionTitle": "Litestream Endpoint", @@ -8626,7 +8640,7 @@ "kind": "section" }, { - "id": "621-zero-cache-config#litestream-checkpoint-threshold-mb", + "id": "622-zero-cache-config#litestream-checkpoint-threshold-mb", "title": "zero-cache Config", "searchTitle": "Litestream Checkpoint Threshold MB", "sectionTitle": "Litestream Checkpoint Threshold MB", @@ -8636,7 +8650,7 @@ "kind": "section" }, { - "id": "622-zero-cache-config#litestream-config-path", + "id": "623-zero-cache-config#litestream-config-path", "title": "zero-cache Config", "searchTitle": "Litestream Config Path", "sectionTitle": "Litestream Config Path", @@ -8646,7 +8660,7 @@ "kind": "section" }, { - "id": "623-zero-cache-config#litestream-config-path-v5", + "id": "624-zero-cache-config#litestream-config-path-v5", "title": "zero-cache Config", "searchTitle": "Litestream Config Path V5", "sectionTitle": "Litestream Config Path V5", @@ -8656,7 +8670,7 @@ "kind": "section" }, { - "id": "624-zero-cache-config#litestream-executable", + "id": "625-zero-cache-config#litestream-executable", "title": "zero-cache Config", "searchTitle": "Litestream Executable", "sectionTitle": "Litestream Executable", @@ -8666,7 +8680,7 @@ "kind": "section" }, { - "id": "625-zero-cache-config#litestream-v5-executable", + "id": "626-zero-cache-config#litestream-v5-executable", "title": "zero-cache Config", "searchTitle": "Litestream V5 Executable", "sectionTitle": "Litestream V5 Executable", @@ -8676,7 +8690,7 @@ "kind": "section" }, { - "id": "626-zero-cache-config#litestream-restore-using-v5", + "id": "627-zero-cache-config#litestream-restore-using-v5", "title": "zero-cache Config", "searchTitle": "Litestream Restore Using V5", "sectionTitle": "Litestream Restore Using V5", @@ -8686,7 +8700,7 @@ "kind": "section" }, { - "id": "627-zero-cache-config#litestream-backup-using-v5", + "id": "628-zero-cache-config#litestream-backup-using-v5", "title": "zero-cache Config", "searchTitle": "Litestream Backup Using V5", "sectionTitle": "Litestream Backup Using V5", @@ -8696,7 +8710,7 @@ "kind": "section" }, { - "id": "628-zero-cache-config#litestream-vfs-query-executable", + "id": "629-zero-cache-config#litestream-vfs-query-executable", "title": "zero-cache Config", "searchTitle": "Litestream VFS Query Executable", "sectionTitle": "Litestream VFS Query Executable", @@ -8706,7 +8720,7 @@ "kind": "section" }, { - "id": "629-zero-cache-config#litestream-vfs-poll-interval-ms", + "id": "630-zero-cache-config#litestream-vfs-poll-interval-ms", "title": "zero-cache Config", "searchTitle": "Litestream VFS Poll Interval (ms)", "sectionTitle": "Litestream VFS Poll Interval (ms)", @@ -8716,7 +8730,7 @@ "kind": "section" }, { - "id": "630-zero-cache-config#litestream-vfs-poll-timeout-ms", + "id": "631-zero-cache-config#litestream-vfs-poll-timeout-ms", "title": "zero-cache Config", "searchTitle": "Litestream VFS Poll Timeout (ms)", "sectionTitle": "Litestream VFS Poll Timeout (ms)", @@ -8726,7 +8740,7 @@ "kind": "section" }, { - "id": "631-zero-cache-config#litestream-vfs-log-file", + "id": "632-zero-cache-config#litestream-vfs-log-file", "title": "zero-cache Config", "searchTitle": "Litestream VFS Log File", "sectionTitle": "Litestream VFS Log File", @@ -8736,7 +8750,7 @@ "kind": "section" }, { - "id": "632-zero-cache-config#litestream-incremental-backup-interval-minutes", + "id": "633-zero-cache-config#litestream-incremental-backup-interval-minutes", "title": "zero-cache Config", "searchTitle": "Litestream Incremental Backup Interval Minutes", "sectionTitle": "Litestream Incremental Backup Interval Minutes", @@ -8746,7 +8760,7 @@ "kind": "section" }, { - "id": "633-zero-cache-config#litestream-incremental-backup-interval-seconds", + "id": "634-zero-cache-config#litestream-incremental-backup-interval-seconds", "title": "zero-cache Config", "searchTitle": "Litestream Incremental Backup Interval Seconds", "sectionTitle": "Litestream Incremental Backup Interval Seconds", @@ -8756,7 +8770,7 @@ "kind": "section" }, { - "id": "634-zero-cache-config#litestream-maximum-checkpoint-page-count", + "id": "635-zero-cache-config#litestream-maximum-checkpoint-page-count", "title": "zero-cache Config", "searchTitle": "Litestream Maximum Checkpoint Page Count", "sectionTitle": "Litestream Maximum Checkpoint Page Count", @@ -8766,7 +8780,7 @@ "kind": "section" }, { - "id": "635-zero-cache-config#litestream-minimum-checkpoint-page-count", + "id": "636-zero-cache-config#litestream-minimum-checkpoint-page-count", "title": "zero-cache Config", "searchTitle": "Litestream Minimum Checkpoint Page Count", "sectionTitle": "Litestream Minimum Checkpoint Page Count", @@ -8776,7 +8790,7 @@ "kind": "section" }, { - "id": "636-zero-cache-config#litestream-multipart-concurrency", + "id": "637-zero-cache-config#litestream-multipart-concurrency", "title": "zero-cache Config", "searchTitle": "Litestream Multipart Concurrency", "sectionTitle": "Litestream Multipart Concurrency", @@ -8786,7 +8800,7 @@ "kind": "section" }, { - "id": "637-zero-cache-config#litestream-multipart-size", + "id": "638-zero-cache-config#litestream-multipart-size", "title": "zero-cache Config", "searchTitle": "Litestream Multipart Size", "sectionTitle": "Litestream Multipart Size", @@ -8796,7 +8810,7 @@ "kind": "section" }, { - "id": "638-zero-cache-config#litestream-log-level", + "id": "639-zero-cache-config#litestream-log-level", "title": "zero-cache Config", "searchTitle": "Litestream Log Level", "sectionTitle": "Litestream Log Level", @@ -8806,7 +8820,7 @@ "kind": "section" }, { - "id": "639-zero-cache-config#litestream-port", + "id": "640-zero-cache-config#litestream-port", "title": "zero-cache Config", "searchTitle": "Litestream Port", "sectionTitle": "Litestream Port", @@ -8816,7 +8830,7 @@ "kind": "section" }, { - "id": "640-zero-cache-config#litestream-region", + "id": "641-zero-cache-config#litestream-region", "title": "zero-cache Config", "searchTitle": "Litestream Region", "sectionTitle": "Litestream Region", @@ -8826,7 +8840,7 @@ "kind": "section" }, { - "id": "641-zero-cache-config#litestream-restore-parallelism", + "id": "642-zero-cache-config#litestream-restore-parallelism", "title": "zero-cache Config", "searchTitle": "Litestream Restore Parallelism", "sectionTitle": "Litestream Restore Parallelism", @@ -8836,7 +8850,7 @@ "kind": "section" }, { - "id": "642-zero-cache-config#litestream-snapshot-backup-interval-hours", + "id": "643-zero-cache-config#litestream-snapshot-backup-interval-hours", "title": "zero-cache Config", "searchTitle": "Litestream Snapshot Backup Interval Hours", "sectionTitle": "Litestream Snapshot Backup Interval Hours", @@ -8846,7 +8860,7 @@ "kind": "section" }, { - "id": "643-zero-cache-config#log-format", + "id": "644-zero-cache-config#log-format", "title": "zero-cache Config", "searchTitle": "Log Format", "sectionTitle": "Log Format", @@ -8856,7 +8870,7 @@ "kind": "section" }, { - "id": "644-zero-cache-config#log-ivm-sampling", + "id": "645-zero-cache-config#log-ivm-sampling", "title": "zero-cache Config", "searchTitle": "Log IVM Sampling", "sectionTitle": "Log IVM Sampling", @@ -8866,7 +8880,7 @@ "kind": "section" }, { - "id": "645-zero-cache-config#log-level", + "id": "646-zero-cache-config#log-level", "title": "zero-cache Config", "searchTitle": "Log Level", "sectionTitle": "Log Level", @@ -8876,7 +8890,7 @@ "kind": "section" }, { - "id": "646-zero-cache-config#log-slow-hydrate-threshold", + "id": "647-zero-cache-config#log-slow-hydrate-threshold", "title": "zero-cache Config", "searchTitle": "Log Slow Hydrate Threshold", "sectionTitle": "Log Slow Hydrate Threshold", @@ -8886,7 +8900,7 @@ "kind": "section" }, { - "id": "647-zero-cache-config#log-slow-row-threshold", + "id": "648-zero-cache-config#log-slow-row-threshold", "title": "zero-cache Config", "searchTitle": "Log Slow Row Threshold", "sectionTitle": "Log Slow Row Threshold", @@ -8896,7 +8910,7 @@ "kind": "section" }, { - "id": "648-zero-cache-config#mutate-api-key", + "id": "649-zero-cache-config#mutate-api-key", "title": "zero-cache Config", "searchTitle": "Mutate API Key", "sectionTitle": "Mutate API Key", @@ -8906,7 +8920,7 @@ "kind": "section" }, { - "id": "649-zero-cache-config#mutate-allowed-client-headers", + "id": "650-zero-cache-config#mutate-allowed-client-headers", "title": "zero-cache Config", "searchTitle": "Mutate Allowed Client Headers", "sectionTitle": "Mutate Allowed Client Headers", @@ -8916,7 +8930,7 @@ "kind": "section" }, { - "id": "650-zero-cache-config#mutate-allowed-request-headers", + "id": "651-zero-cache-config#mutate-allowed-request-headers", "title": "zero-cache Config", "searchTitle": "Mutate Allowed Request Headers", "sectionTitle": "Mutate Allowed Request Headers", @@ -8926,7 +8940,7 @@ "kind": "section" }, { - "id": "651-zero-cache-config#mutate-forward-cookies", + "id": "652-zero-cache-config#mutate-forward-cookies", "title": "zero-cache Config", "searchTitle": "Mutate Forward Cookies", "sectionTitle": "Mutate Forward Cookies", @@ -8936,7 +8950,7 @@ "kind": "section" }, { - "id": "652-zero-cache-config#mutate-url", + "id": "653-zero-cache-config#mutate-url", "title": "zero-cache Config", "searchTitle": "Mutate URL", "sectionTitle": "Mutate URL", @@ -8946,7 +8960,7 @@ "kind": "section" }, { - "id": "653-zero-cache-config#number-of-sync-workers", + "id": "654-zero-cache-config#number-of-sync-workers", "title": "zero-cache Config", "searchTitle": "Number of Sync Workers", "sectionTitle": "Number of Sync Workers", @@ -8956,7 +8970,7 @@ "kind": "section" }, { - "id": "654-zero-cache-config#per-user-mutation-limit-max", + "id": "655-zero-cache-config#per-user-mutation-limit-max", "title": "zero-cache Config", "searchTitle": "Per User Mutation Limit Max", "sectionTitle": "Per User Mutation Limit Max", @@ -8966,7 +8980,7 @@ "kind": "section" }, { - "id": "655-zero-cache-config#per-user-mutation-limit-window-ms", + "id": "656-zero-cache-config#per-user-mutation-limit-window-ms", "title": "zero-cache Config", "searchTitle": "Per User Mutation Limit Window (ms)", "sectionTitle": "Per User Mutation Limit Window (ms)", @@ -8976,7 +8990,7 @@ "kind": "section" }, { - "id": "656-zero-cache-config#pg-replication-slot-failover", + "id": "657-zero-cache-config#pg-replication-slot-failover", "title": "zero-cache Config", "searchTitle": "PG Replication Slot Failover", "sectionTitle": "PG Replication Slot Failover", @@ -8986,7 +9000,7 @@ "kind": "section" }, { - "id": "657-zero-cache-config#port", + "id": "658-zero-cache-config#port", "title": "zero-cache Config", "searchTitle": "Port", "sectionTitle": "Port", @@ -8996,7 +9010,7 @@ "kind": "section" }, { - "id": "658-zero-cache-config#query-api-key", + "id": "659-zero-cache-config#query-api-key", "title": "zero-cache Config", "searchTitle": "Query API Key", "sectionTitle": "Query API Key", @@ -9006,7 +9020,7 @@ "kind": "section" }, { - "id": "659-zero-cache-config#query-allowed-client-headers", + "id": "660-zero-cache-config#query-allowed-client-headers", "title": "zero-cache Config", "searchTitle": "Query Allowed Client Headers", "sectionTitle": "Query Allowed Client Headers", @@ -9016,7 +9030,7 @@ "kind": "section" }, { - "id": "660-zero-cache-config#query-allowed-request-headers", + "id": "661-zero-cache-config#query-allowed-request-headers", "title": "zero-cache Config", "searchTitle": "Query Allowed Request Headers", "sectionTitle": "Query Allowed Request Headers", @@ -9026,7 +9040,7 @@ "kind": "section" }, { - "id": "661-zero-cache-config#query-forward-cookies", + "id": "662-zero-cache-config#query-forward-cookies", "title": "zero-cache Config", "searchTitle": "Query Forward Cookies", "sectionTitle": "Query Forward Cookies", @@ -9036,7 +9050,7 @@ "kind": "section" }, { - "id": "662-zero-cache-config#query-hydration-stats", + "id": "663-zero-cache-config#query-hydration-stats", "title": "zero-cache Config", "searchTitle": "Query Hydration Stats", "sectionTitle": "Query Hydration Stats", @@ -9046,7 +9060,7 @@ "kind": "section" }, { - "id": "663-zero-cache-config#query-url", + "id": "664-zero-cache-config#query-url", "title": "zero-cache Config", "searchTitle": "Query URL", "sectionTitle": "Query URL", @@ -9056,7 +9070,7 @@ "kind": "section" }, { - "id": "664-zero-cache-config#replica-file", + "id": "665-zero-cache-config#replica-file", "title": "zero-cache Config", "searchTitle": "Replica File", "sectionTitle": "Replica File", @@ -9066,7 +9080,7 @@ "kind": "section" }, { - "id": "665-zero-cache-config#replica-vacuum-interval-hours", + "id": "666-zero-cache-config#replica-vacuum-interval-hours", "title": "zero-cache Config", "searchTitle": "Replica Vacuum Interval Hours", "sectionTitle": "Replica Vacuum Interval Hours", @@ -9076,7 +9090,7 @@ "kind": "section" }, { - "id": "666-zero-cache-config#replication-lag-report-interval-ms", + "id": "667-zero-cache-config#replication-lag-report-interval-ms", "title": "zero-cache Config", "searchTitle": "Replication Lag Report Interval (ms)", "sectionTitle": "Replication Lag Report Interval (ms)", @@ -9086,7 +9100,7 @@ "kind": "section" }, { - "id": "667-zero-cache-config#server-version", + "id": "668-zero-cache-config#server-version", "title": "zero-cache Config", "searchTitle": "Server Version", "sectionTitle": "Server Version", @@ -9096,7 +9110,7 @@ "kind": "section" }, { - "id": "668-zero-cache-config#shadow-sync-enabled", + "id": "669-zero-cache-config#shadow-sync-enabled", "title": "zero-cache Config", "searchTitle": "Shadow Sync Enabled", "sectionTitle": "Shadow Sync Enabled", @@ -9106,7 +9120,7 @@ "kind": "section" }, { - "id": "669-zero-cache-config#shadow-sync-interval-hours", + "id": "670-zero-cache-config#shadow-sync-interval-hours", "title": "zero-cache Config", "searchTitle": "Shadow Sync Interval Hours", "sectionTitle": "Shadow Sync Interval Hours", @@ -9116,7 +9130,7 @@ "kind": "section" }, { - "id": "670-zero-cache-config#shadow-sync-sample-rate", + "id": "671-zero-cache-config#shadow-sync-sample-rate", "title": "zero-cache Config", "searchTitle": "Shadow Sync Sample Rate", "sectionTitle": "Shadow Sync Sample Rate", @@ -9126,7 +9140,7 @@ "kind": "section" }, { - "id": "671-zero-cache-config#shadow-sync-max-rows-per-table", + "id": "672-zero-cache-config#shadow-sync-max-rows-per-table", "title": "zero-cache Config", "searchTitle": "Shadow Sync Max Rows Per Table", "sectionTitle": "Shadow Sync Max Rows Per Table", @@ -9136,7 +9150,7 @@ "kind": "section" }, { - "id": "672-zero-cache-config#storage-db-temp-dir", + "id": "673-zero-cache-config#storage-db-temp-dir", "title": "zero-cache Config", "searchTitle": "Storage DB Temp Dir", "sectionTitle": "Storage DB Temp Dir", @@ -9146,7 +9160,7 @@ "kind": "section" }, { - "id": "673-zero-cache-config#task-id", + "id": "674-zero-cache-config#task-id", "title": "zero-cache Config", "searchTitle": "Task ID", "sectionTitle": "Task ID", @@ -9156,7 +9170,7 @@ "kind": "section" }, { - "id": "674-zero-cache-config#upstream-max-connections", + "id": "675-zero-cache-config#upstream-max-connections", "title": "zero-cache Config", "searchTitle": "Upstream Max Connections", "sectionTitle": "Upstream Max Connections", @@ -9166,7 +9180,7 @@ "kind": "section" }, { - "id": "675-zero-cache-config#upstream-pg-replication-slot-failover", + "id": "676-zero-cache-config#upstream-pg-replication-slot-failover", "title": "zero-cache Config", "searchTitle": "Upstream PG Replication Slot Failover", "sectionTitle": "Upstream PG Replication Slot Failover", @@ -9176,7 +9190,7 @@ "kind": "section" }, { - "id": "676-zero-cache-config#upstream-pg-stream-inbound-timeout", + "id": "677-zero-cache-config#upstream-pg-stream-inbound-timeout", "title": "zero-cache Config", "searchTitle": "Upstream PG Stream Inbound Timeout", "sectionTitle": "Upstream PG Stream Inbound Timeout", @@ -9186,7 +9200,7 @@ "kind": "section" }, { - "id": "677-zero-cache-config#websocket-compression", + "id": "678-zero-cache-config#websocket-compression", "title": "zero-cache Config", "searchTitle": "Websocket Compression", "sectionTitle": "Websocket Compression", @@ -9196,7 +9210,7 @@ "kind": "section" }, { - "id": "678-zero-cache-config#websocket-compression-options", + "id": "679-zero-cache-config#websocket-compression-options", "title": "zero-cache Config", "searchTitle": "Websocket Compression Options", "sectionTitle": "Websocket Compression Options", @@ -9206,7 +9220,7 @@ "kind": "section" }, { - "id": "679-zero-cache-config#websocket-max-payload-bytes", + "id": "680-zero-cache-config#websocket-max-payload-bytes", "title": "zero-cache Config", "searchTitle": "Websocket Max Payload Bytes", "sectionTitle": "Websocket Max Payload Bytes", @@ -9216,7 +9230,7 @@ "kind": "section" }, { - "id": "680-zero-cache-config#yield-threshold-ms", + "id": "681-zero-cache-config#yield-threshold-ms", "title": "zero-cache Config", "searchTitle": "Yield Threshold (ms)", "sectionTitle": "Yield Threshold (ms)", @@ -9226,7 +9240,7 @@ "kind": "section" }, { - "id": "681-zero-cache-config#deprecated-flags", + "id": "682-zero-cache-config#deprecated-flags", "title": "zero-cache Config", "searchTitle": "Deprecated Flags", "sectionTitle": "Deprecated Flags", @@ -9236,7 +9250,7 @@ "kind": "section" }, { - "id": "682-zero-cache-config#change-max-connections", + "id": "683-zero-cache-config#change-max-connections", "title": "zero-cache Config", "searchTitle": "Change Max Connections", "sectionTitle": "Change Max Connections", @@ -9246,7 +9260,7 @@ "kind": "section" }, { - "id": "683-zero-cache-config#auth-jwk", + "id": "684-zero-cache-config#auth-jwk", "title": "zero-cache Config", "searchTitle": "Auth JWK", "sectionTitle": "Auth JWK", @@ -9256,7 +9270,7 @@ "kind": "section" }, { - "id": "684-zero-cache-config#auth-jwks-url", + "id": "685-zero-cache-config#auth-jwks-url", "title": "zero-cache Config", "searchTitle": "Auth JWKS URL", "sectionTitle": "Auth JWKS URL", @@ -9266,7 +9280,7 @@ "kind": "section" }, { - "id": "685-zero-cache-config#auth-secret", + "id": "686-zero-cache-config#auth-secret", "title": "zero-cache Config", "searchTitle": "Auth Secret", "sectionTitle": "Auth Secret", @@ -9386,7 +9400,7 @@ "kind": "page" }, { - "id": "686-zql#create-a-builder", + "id": "687-zql#create-a-builder", "title": "ZQL", "searchTitle": "Create a Builder", "sectionTitle": "Create a Builder", @@ -9396,7 +9410,7 @@ "kind": "section" }, { - "id": "687-zql#select", + "id": "688-zql#select", "title": "ZQL", "searchTitle": "Select", "sectionTitle": "Select", @@ -9406,7 +9420,7 @@ "kind": "section" }, { - "id": "688-zql#ordering", + "id": "689-zql#ordering", "title": "ZQL", "searchTitle": "Ordering", "sectionTitle": "Ordering", @@ -9416,7 +9430,7 @@ "kind": "section" }, { - "id": "689-zql#limit", + "id": "690-zql#limit", "title": "ZQL", "searchTitle": "Limit", "sectionTitle": "Limit", @@ -9426,7 +9440,7 @@ "kind": "section" }, { - "id": "690-zql#paging", + "id": "691-zql#paging", "title": "ZQL", "searchTitle": "Paging", "sectionTitle": "Paging", @@ -9436,7 +9450,7 @@ "kind": "section" }, { - "id": "691-zql#getting-a-single-result", + "id": "692-zql#getting-a-single-result", "title": "ZQL", "searchTitle": "Getting a Single Result", "sectionTitle": "Getting a Single Result", @@ -9446,7 +9460,7 @@ "kind": "section" }, { - "id": "692-zql#relationships", + "id": "693-zql#relationships", "title": "ZQL", "searchTitle": "Relationships", "sectionTitle": "Relationships", @@ -9456,7 +9470,7 @@ "kind": "section" }, { - "id": "693-zql#refining-relationships", + "id": "694-zql#refining-relationships", "title": "ZQL", "searchTitle": "Refining Relationships", "sectionTitle": "Refining Relationships", @@ -9466,7 +9480,7 @@ "kind": "section" }, { - "id": "694-zql#nested-relationships", + "id": "695-zql#nested-relationships", "title": "ZQL", "searchTitle": "Nested Relationships", "sectionTitle": "Nested Relationships", @@ -9476,7 +9490,7 @@ "kind": "section" }, { - "id": "695-zql#where", + "id": "696-zql#where", "title": "ZQL", "searchTitle": "Where", "sectionTitle": "Where", @@ -9486,7 +9500,7 @@ "kind": "section" }, { - "id": "696-zql#comparison-operators", + "id": "697-zql#comparison-operators", "title": "ZQL", "searchTitle": "Comparison Operators", "sectionTitle": "Comparison Operators", @@ -9496,7 +9510,7 @@ "kind": "section" }, { - "id": "697-zql#equals-is-the-default-comparison-operator", + "id": "698-zql#equals-is-the-default-comparison-operator", "title": "ZQL", "searchTitle": "Equals is the Default Comparison Operator", "sectionTitle": "Equals is the Default Comparison Operator", @@ -9506,7 +9520,7 @@ "kind": "section" }, { - "id": "698-zql#comparing-to-null", + "id": "699-zql#comparing-to-null", "title": "ZQL", "searchTitle": "Comparing to null", "sectionTitle": "Comparing to null", @@ -9516,7 +9530,7 @@ "kind": "section" }, { - "id": "699-zql#comparing-to-undefined", + "id": "700-zql#comparing-to-undefined", "title": "ZQL", "searchTitle": "Comparing to undefined", "sectionTitle": "Comparing to undefined", @@ -9526,7 +9540,7 @@ "kind": "section" }, { - "id": "700-zql#compound-filters", + "id": "701-zql#compound-filters", "title": "ZQL", "searchTitle": "Compound Filters", "sectionTitle": "Compound Filters", @@ -9536,7 +9550,7 @@ "kind": "section" }, { - "id": "701-zql#comparing-literal-values", + "id": "702-zql#comparing-literal-values", "title": "ZQL", "searchTitle": "Comparing Literal Values", "sectionTitle": "Comparing Literal Values", @@ -9546,7 +9560,7 @@ "kind": "section" }, { - "id": "702-zql#relationship-filters", + "id": "703-zql#relationship-filters", "title": "ZQL", "searchTitle": "Relationship Filters", "sectionTitle": "Relationship Filters", @@ -9556,7 +9570,7 @@ "kind": "section" }, { - "id": "703-zql#type-helpers", + "id": "704-zql#type-helpers", "title": "ZQL", "searchTitle": "Type Helpers", "sectionTitle": "Type Helpers", @@ -9566,7 +9580,7 @@ "kind": "section" }, { - "id": "704-zql#planning", + "id": "705-zql#planning", "title": "ZQL", "searchTitle": "Planning", "sectionTitle": "Planning", @@ -9576,7 +9590,7 @@ "kind": "section" }, { - "id": "705-zql#inspecting-query-plans", + "id": "706-zql#inspecting-query-plans", "title": "ZQL", "searchTitle": "Inspecting Query Plans", "sectionTitle": "Inspecting Query Plans", @@ -9586,7 +9600,7 @@ "kind": "section" }, { - "id": "706-zql#manually-flipping-joins", + "id": "707-zql#manually-flipping-joins", "title": "ZQL", "searchTitle": "Manually Flipping Joins", "sectionTitle": "Manually Flipping Joins", @@ -9596,7 +9610,7 @@ "kind": "section" }, { - "id": "707-zql#scalar-subqueries", + "id": "708-zql#scalar-subqueries", "title": "ZQL", "searchTitle": "Scalar Subqueries", "sectionTitle": "Scalar Subqueries", @@ -9606,7 +9620,7 @@ "kind": "section" }, { - "id": "708-zql#why-it-matters", + "id": "709-zql#why-it-matters", "title": "ZQL", "searchTitle": "Why It Matters", "sectionTitle": "Why It Matters", @@ -9616,7 +9630,7 @@ "kind": "section" }, { - "id": "709-zql#trade-offs", + "id": "710-zql#trade-offs", "title": "ZQL", "searchTitle": "Trade-offs", "sectionTitle": "Trade-offs", @@ -9626,7 +9640,7 @@ "kind": "section" }, { - "id": "710-zql#future-work", + "id": "711-zql#future-work", "title": "ZQL", "searchTitle": "Future Work", "sectionTitle": "Future Work", diff --git a/contents/docs/queries.mdx b/contents/docs/queries.mdx index c097fccf..120b1dfe 100644 --- a/contents/docs/queries.mdx +++ b/contents/docs/queries.mdx @@ -584,6 +584,15 @@ const results = await zero.run( ) ``` +To also accept a result the server confirmed during a previous session, pass `{type: 'cached'}`. It resolves as soon as the result is [`cached`](#cached-results) or `complete`, so it doesn't wait for the server when the client already stores a confirmed result: + +```tsx +const results = await zero.run( + queries.issues.byPriority('high'), + {type: 'cached'} +) +``` + ### For Preloading Almost all Zero apps will want to preload some data in order to maximize the feel of instantaneous UI transitions. @@ -601,6 +610,8 @@ zero.preload( ) ``` +`preload()` returns `cleanup`, which ends the preload (Zero keeps its rows for the query's [TTL](#ttls)), and two promises: `complete` resolves once the server has returned the result, and `cached` resolves once the result is [`cached`](#cached-results) or `complete`. + ## Missing Data Because Zero returns local results immediately and server results asynchronously, displaying "not found" / 404 UI can be slightly tricky. @@ -765,10 +776,79 @@ view.addListener((issues, resultType) => { -The possible values of `result.type` are currently `complete` and `unknown`. +The possible values of `result.type` are: + +- `unknown`: the data Zero has on the client. Some of it may be missing. +- `cached`: the client stores a result that the server confirmed during a previous session. See [Cached Results](#cached-results). +- `complete`: the server has returned the result during this session. +- `error`: the query failed. See [Handling Errors](#handling-errors). The `complete` value is currently only returned when Zero has received the server result. In the future, Zero will be able to return this result type when it _knows_ that all possible data for this query is already available locally. Additionally, we plan to add a `prefix` result for when the data is known to be a prefix of the complete result. See [Consistency](#consistency) for more information. +### Cached Results + +When Zero stores data on the client (in IndexedDB, or SQLite on React Native), it also remembers which query results the server confirmed. If the client still holds such a result when the app starts again, the query starts as `cached` instead of `unknown`, and becomes `complete` once the server returns the result in this session. If Zero evicts the stored result before then, the query goes back to `unknown`. + +A `cached` result was complete when the server sent it, but it may be out of date. Use it where a previous session's answer is good enough, for example to show stored data or an empty state without waiting for the server, which also works while offline. Wait for `complete` where the data must be current: + + + +```tsx +const [issues, issuesResult] = useQuery( + queries.issues.inbox() +) + +const confirmed = + issuesResult.type === 'cached' || + issuesResult.type === 'complete' + +if (issues.length === 0 && confirmed) { + return
Your inbox is empty
+} +``` + +```tsx +const [issues, issuesResult] = useQuery(() => + queries.issues.inbox() +) + +const confirmed = () => + issuesResult().type === 'cached' || + issuesResult().type === 'complete' + +return ( + 0 || !confirmed()} + fallback={
Your inbox is empty
} + > + {/* ... */} +
+) +``` + +```ts +const view = zero.materialize(queries.issues.inbox()) + +view.addListener((issues, resultType) => { + if ( + issues.length === 0 && + (resultType === 'cached' || resultType === 'complete') + ) { + console.log('Inbox is empty') + } +}) +``` + +
+ +`zero.run(query, {type: 'cached'})` and the `cached` promise from `zero.preload()` resolve on a `cached` or `complete` result. `{type: 'complete'}` and the `complete` promise still wait for the server. + ## Handling Errors If the queries endpoint throws an application or parse error, `zero-cache` will report it to the client using the `type` and `error` fields on the query details object: