<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][CHANGELOG.md]" value="# @tanstack/query-example-lit-pagination

## 0.0.2

### Patch Changes

- Updated dependencies [[`4082894`](https://github.com/TanStack/query/commit/4082894509f31376ebeb8514cc3e167bbbfc7c46)]:
  - @tanstack/lit-query@0.2.0
">
<input type="hidden" name="project[files][README.md]" value="# Example

To run this example from the repo root:

- `pnpm --dir examples/lit/pagination run dev`
">
<input type="hidden" name="project[files][eslint.config.js]" value="// @ts-check

import { tanstackConfig } from &#39;@tanstack/eslint-config&#39;
import pluginQuery from &#39;@tanstack/eslint-plugin-query&#39;
import { defineConfig } from &#39;eslint/config&#39;

export default defineConfig([
  ...tanstackConfig,
  ...pluginQuery.configs[&#39;flat/recommended-strict&#39;],
])
">
<input type="hidden" name="project[files][index.html]" value="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;link rel=&quot;shortcut icon&quot; type=&quot;image/svg+xml&quot; href=&quot;/emblem-light.svg&quot; /&gt;
    &lt;title&gt;TanStack Lit Query Pagination Demo&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;pagination-demo-root&gt;&lt;/pagination-demo-root&gt;
    &lt;script type=&quot;module&quot; src=&quot;/src/main.ts&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[files][package.json]" value="{
  &quot;name&quot;: &quot;@tanstack/query-example-lit-pagination&quot;,
  &quot;private&quot;: true,
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;node ./scripts/dev.mjs&quot;,
    &quot;build&quot;: &quot;vite build&quot;,
    &quot;preview&quot;: &quot;vite preview&quot;,
    &quot;test:eslint&quot;: &quot;eslint ./src&quot;,
    &quot;test:types&quot;: &quot;tsc --noEmit&quot;
  },
  &quot;dependencies&quot;: {
    &quot;@tanstack/lit-query&quot;: &quot;https://pkg.pr.new/TanStack/query/@tanstack/lit-query@b73f289&quot;,
    &quot;lit&quot;: &quot;^3.3.3&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@tanstack/eslint-plugin-query&quot;: &quot;https://pkg.pr.new/TanStack/query/@tanstack/eslint-plugin-query@b73f289&quot;,
    &quot;typescript&quot;: &quot;^6.0.3&quot;,
    &quot;vite&quot;: &quot;^8.3.5&quot;
  }
}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2022&quot;,
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;moduleResolution&quot;: &quot;Bundler&quot;,
    &quot;lib&quot;: [&quot;ES2022&quot;, &quot;DOM&quot;],
    &quot;strict&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;allowJs&quot;: true,
    &quot;useDefineForClassFields&quot;: false
  },
  &quot;include&quot;: [
    &quot;src/**/*.ts&quot;,
    &quot;config/ports.js&quot;,
    &quot;scripts/dev.mjs&quot;,
    &quot;server/index.mjs&quot;,
    &quot;*.config.ts&quot;,
    &quot;*.config.js&quot;
  ]
}
">
<input type="hidden" name="project[files][vite.config.ts]" value="import { defineConfig } from &#39;vite&#39;
import { DEMO_PORT } from &#39;./config/ports.js&#39;

export default defineConfig({
  server: {
    host: &#39;127.0.0.1&#39;,
    port: DEMO_PORT,
    strictPort: true,
  },
  preview: {
    host: &#39;127.0.0.1&#39;,
    port: DEMO_PORT,
    strictPort: true,
  },
})
">
<input type="hidden" name="project[files][config/ports.d.ts]" value="export const DEMO_PORT: number
export const API_PORT: number
">
<input type="hidden" name="project[files][config/ports.js]" value="const DEFAULT_DEMO_PORT = 4183
const DEFAULT_API_PORT = 4184

function readPortFromEnv(name, fallback) {
  const rawValue = process.env[name]
  if (!rawValue) {
    return fallback
  }

  const parsed = Number.parseInt(rawValue, 10)
  const valid = Number.isInteger(parsed) &amp;&amp; parsed &gt;= 1 &amp;&amp; parsed &lt;= 65535

  if (!valid) {
    throw new Error(
      `Invalid ${name}=&quot;${rawValue}&quot;. Expected integer in [1, 65535].`,
    )
  }

  return parsed
}

export const DEMO_PORT = readPortFromEnv(
  &#39;PAGINATION_DEMO_PORT&#39;,
  DEFAULT_DEMO_PORT,
)
export const API_PORT = readPortFromEnv(&#39;PAGINATION_API_PORT&#39;, DEFAULT_API_PORT)
">
<input type="hidden" name="project[files][public/emblem-light.svg]" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;svg width=&quot;190px&quot; height=&quot;190px&quot; viewBox=&quot;0 0 190 190&quot; version=&quot;1.1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot;&gt;
    &lt;!-- Generator: Sketch 60.1 (88133) - https://sketch.com --&gt;
    &lt;title&gt;emblem-light&lt;/title&gt;
    &lt;desc&gt;Created with Sketch.&lt;/desc&gt;
    &lt;g stroke=&quot;none&quot; stroke-width=&quot;1&quot; fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;
        &lt;g&gt;
            &lt;path d=&quot;M39.7239712,61.3436237 C36.631224,46.362877 35.9675112,34.8727722 37.9666331,26.5293551 C39.1555965,21.5671678 41.3293088,17.5190846 44.6346064,14.5984631 C48.1241394,11.5150478 52.5360327,10.0020122 57.493257,10.0020122 C65.6712013,10.0020122 74.2682602,13.7273214 83.4557246,20.8044264 C87.2031203,23.6910458 91.0924366,27.170411 95.1316515,31.2444746 C95.4531404,30.8310265 95.8165416,30.4410453 96.2214301,30.0806152 C107.64098,19.9149716 117.255245,13.5989272 125.478408,11.1636507 C130.367899,9.715636 134.958526,9.57768202 139.138936,10.983031 C143.551631,12.4664684 147.06766,15.5329489 149.548314,19.8281091 C153.642288,26.9166735 154.721918,36.2310983 153.195595,47.7320243 C152.573451,52.4199112 151.50985,57.5263831 150.007094,63.0593153 C150.574045,63.1277086 151.142416,63.2532808 151.705041,63.4395297 C166.193932,68.2358678 176.453582,73.3937462 182.665021,79.2882839 C186.360669,82.7953831 188.773972,86.6998434 189.646365,91.0218204 C190.567176,95.5836746 189.669313,100.159332 187.191548,104.451297 C183.105211,111.529614 175.591643,117.11221 164.887587,121.534031 C160.589552,123.309539 155.726579,124.917559 150.293259,126.363748 C150.541176,126.92292 150.733521,127.516759 150.862138,128.139758 C153.954886,143.120505 154.618598,154.61061 152.619477,162.954027 C151.430513,167.916214 149.256801,171.964297 145.951503,174.884919 C142.46197,177.968334 138.050077,179.48137 133.092853,179.48137 C124.914908,179.48137 116.31785,175.756061 107.130385,168.678956 C103.343104,165.761613 99.4108655,162.238839 95.3254337,158.108619 C94.9050753,158.765474 94.3889681,159.376011 93.7785699,159.919385 C82.3590198,170.085028 72.744755,176.401073 64.5215915,178.836349 C59.6321009,180.284364 55.0414736,180.422318 50.8610636,179.016969 C46.4483686,177.533532 42.9323404,174.467051 40.4516862,170.171891 C36.3577116,163.083327 35.2780823,153.768902 36.8044053,142.267976 C37.449038,137.410634 38.56762,132.103898 40.1575891,126.339009 C39.5361041,126.276104 38.9120754,126.144816 38.2949591,125.940529 C23.8060684,121.144191 13.5464184,115.986312 7.33497892,110.091775 C3.63933121,106.584675 1.22602752,102.680215 0.353635235,98.3582381 C-0.567176333,93.7963839 0.330686581,89.2207269 2.80845236,84.9287618 C6.89478863,77.8504443 14.4083565,72.2678481 25.1124133,67.8460273 C29.5385143,66.0176154 34.5637208,64.366822 40.1939394,62.8874674 C39.9933393,62.3969171 39.8349374,61.8811235 39.7239712,61.3436237 Z&quot; fill=&quot;#002C4B&quot; fill-rule=&quot;nonzero&quot; transform=&quot;translate(95.000000, 95.000000) scale(-1, 1) translate(-95.000000, -95.000000) &quot;&gt;&lt;/path&gt;
            &lt;path d=&quot;M80.3968824,64 L109.608177,64 C111.399254,64 113.053521,64.958025 113.944933,66.5115174 L128.577138,92.0115174 C129.461464,93.5526583 129.461464,95.4473417 128.577138,96.9884826 L113.944933,122.488483 C113.053521,124.041975 111.399254,125 109.608177,125 L80.3968824,125 C78.6058059,125 76.9515387,124.041975 76.0601262,122.488483 L61.4279211,96.9884826 C60.543596,95.4473417 60.543596,93.5526583 61.4279211,92.0115174 L76.0601262,66.5115174 C76.9515387,64.958025 78.6058059,64 80.3968824,64 Z M105.987827,70.2765273 C107.779849,70.2765273 109.434839,71.2355558 110.325899,72.7903404 L121.343038,92.0138131 C122.225607,93.5537825 122.225607,95.4462175 121.343038,96.9861869 L110.325899,116.20966 C109.434839,117.764444 107.779849,118.723473 105.987827,118.723473 L84.0172329,118.723473 C82.2252106,118.723473 80.5702207,117.764444 79.6791602,116.20966 L68.6620219,96.9861869 C67.7794521,95.4462175 67.7794521,93.5537825 68.6620219,92.0138131 L79.6791602,72.7903404 C80.5702207,71.2355558 82.2252106,70.2765273 84.0172329,70.2765273 L105.987827,70.2765273 Z M102.080648,77.1414791 L87.9244113,77.1414791 C86.1342282,77.1414791 84.4806439,78.0985567 83.5888998,79.6508285 L83.5888998,79.6508285 L76.4892166,92.0093494 C75.6032319,93.5515958 75.6032319,95.4484042 76.4892166,96.9906506 L76.4892166,96.9906506 L83.5888998,109.349172 C84.4806439,110.901443 86.1342282,111.858521 87.9244113,111.858521 L87.9244113,111.858521 L102.080648,111.858521 C103.870831,111.858521 105.524416,110.901443 106.41616,109.349172 L106.41616,109.349172 L113.515843,96.9906506 C114.401828,95.4484042 114.401828,93.5515958 113.515843,92.0093494 L113.515843,92.0093494 L106.41616,79.6508285 C105.524416,78.0985567 103.870831,77.1414791 102.080648,77.1414791 L102.080648,77.1414791 Z M98.3191856,83.7122186 C100.108028,83.7122186 101.760587,84.6678753 102.652827,86.2183156 L105.983552,92.0060969 C106.87203,93.5500005 106.87203,95.4499995 105.983552,96.9939031 L102.652827,102.781684 C101.760587,104.332125 100.108028,105.287781 98.3191856,105.287781 L91.685874,105.287781 C89.8970316,105.287781 88.2444725,104.332125 87.3522326,102.781684 L84.021508,96.9939031 C83.1330298,95.4499995 83.1330298,93.5500005 84.021508,92.0060969 L87.3522326,86.2183156 C88.2444725,84.6678753 89.8970316,83.7122186 91.685874,83.7122186 L98.3191856,83.7122186 Z M95.0037937,90.1848875 C93.459294,90.1848875 92.0343817,91.0072828 91.2630046,92.3424437 C90.4917325,93.6774232 90.4917325,95.3225768 91.2630046,96.6575563 C92.0343817,97.9927172 93.459294,98.8151125 95.0012659,98.8151125 L95.0012659,98.8151125 C96.5457656,98.8151125 97.9706779,97.9927172 98.7420549,96.6575563 C99.5133271,95.3225768 99.5133271,93.6774232 98.7420549,92.3424437 C97.9706779,91.0072828 96.5457656,90.1848875 95.0037937,90.1848875 L95.0037937,90.1848875 Z M60,94.5009646 L67.7677636,94.5009646&quot; fill=&quot;#FFD94C&quot;&gt;&lt;/path&gt;
            &lt;path d=&quot;M54.8601729,108.357758 C56.1715224,107.608286 57.8360246,108.074601 58.5779424,109.399303 L58.5779424,109.399303 L59.0525843,110.24352 C62.8563392,116.982993 66.8190116,123.380176 70.9406016,129.435068 C75.8078808,136.585427 81.28184,143.82411 87.3624792,151.151115 C88.3168778,152.30114 88.1849437,154.011176 87.065686,154.997937 L87.065686,154.997937 L86.4542085,155.534625 C66.3465389,173.103314 53.2778188,176.612552 47.2480482,166.062341 C41.3500652,155.742717 43.4844915,136.982888 53.6513274,109.782853 C53.876818,109.179582 54.3045861,108.675291 54.8601729,108.357758 Z M140.534177,129.041504 C141.986131,128.785177 143.375496,129.742138 143.65963,131.194242 L143.65963,131.194242 L143.812815,131.986376 C148.782365,157.995459 145.283348,171 133.315764,171 C121.609745,171 106.708724,159.909007 88.6127018,137.727022 C88.2113495,137.235047 87.9945723,136.617371 88,135.981509 C88.013158,134.480686 89.2357854,133.274651 90.730918,133.287756 L90.730918,133.287756 L91.6846544,133.294531 C99.3056979,133.335994 106.714387,133.071591 113.910723,132.501323 C122.409039,131.82788 131.283523,130.674607 140.534177,129.041504 Z M147.408726,73.8119663 C147.932139,72.4026903 149.508386,71.6634537 150.954581,72.149012 L150.954581,72.149012 L151.742552,72.4154854 C177.583763,81.217922 187.402356,90.8916805 181.198332,101.436761 C175.129904,111.751366 157.484347,119.260339 128.26166,123.963678 C127.613529,124.067994 126.948643,123.945969 126.382735,123.618843 C125.047025,122.846729 124.602046,121.158214 125.388848,119.847438 L125.388848,119.847438 L125.889328,119.0105 C129.877183,112.31633 133.481358,105.654262 136.701854,99.0242957 C140.50501,91.1948179 144.073967,82.7907081 147.408726,73.8119663 Z M61.7383398,66.0363218 C62.3864708,65.9320063 63.0513565,66.0540315 63.6172646,66.3811573 C64.9529754,67.153271 65.3979538,68.8417862 64.6111517,70.1525615 L64.6111517,70.1525615 L64.1106718,70.9895001 C60.1228168,77.6836699 56.5186416,84.3457379 53.2981462,90.9757043 C49.49499,98.8051821 45.9260328,107.209292 42.5912744,116.188034 C42.0678608,117.59731 40.4916142,118.336546 39.045419,117.850988 L39.045419,117.850988 L38.2574475,117.584515 C12.4162372,108.782078 2.59764398,99.1083195 8.80166786,88.5632391 C14.8700957,78.2486335 32.515653,70.7396611 61.7383398,66.0363218 Z M103.545792,34.4653746 C123.653461,16.8966864 136.722181,13.3874478 142.751952,23.9376587 C148.649935,34.2572826 146.515508,53.0171122 136.348673,80.2171474 C136.123182,80.8204179 135.695414,81.324709 135.139827,81.6422422 C133.828478,82.3917144 132.163975,81.9253986 131.422058,80.6006966 L131.422058,80.6006966 L130.947416,79.7564798 C127.143661,73.0170065 123.180988,66.6198239 119.059398,60.564932 C114.192119,53.4145727 108.71816,46.1758903 102.637521,38.8488847 C101.683122,37.6988602 101.815056,35.9888243 102.934314,35.0020629 L102.934314,35.0020629 Z M57.6842361,18 C69.3902551,18 84.2912758,29.0909926 102.387298,51.2729777 C102.788651,51.7649527 103.005428,52.3826288 103,53.0184911 C102.986842,54.5193144 101.764215,55.7253489 100.269082,55.7122445 L100.269082,55.7122445 L99.3153456,55.7054689 C91.6943021,55.6640063 84.2856126,55.9284091 77.0892772,56.4986773 C68.5909612,57.17212 59.7164767,58.325393 50.4658235,59.9584962 C49.0138691,60.2148231 47.6245044,59.2578618 47.3403697,57.805758 L47.3403697,57.805758 L47.1871846,57.0136235 C42.2176347,31.0045412 45.7166519,18 57.6842361,18 Z&quot; fill=&quot;#FF4154&quot;&gt;&lt;/path&gt;
        &lt;/g&gt;
    &lt;/g&gt;
&lt;/svg&gt;">
<input type="hidden" name="project[files][scripts/dev.mjs]" value="import { spawn } from &#39;node:child_process&#39;
import { once } from &#39;node:events&#39;
import { API_PORT } from &#39;../config/ports.js&#39;

const viteCommand = process.platform === &#39;win32&#39; ? &#39;vite.cmd&#39; : &#39;vite&#39;
const cwd = new URL(&#39;..&#39;, import.meta.url)

function forwardOutput(prefix, stream, output) {
  stream.on(&#39;data&#39;, (chunk) =&gt; {
    output.write(`${prefix}${chunk}`)
  })
}

function start(name, command, args, extraEnv = {}) {
  const child = spawn(command, args, {
    cwd,
    stdio: [&#39;ignore&#39;, &#39;pipe&#39;, &#39;pipe&#39;],
    env: {
      ...process.env,
      ...extraEnv,
    },
  })

  forwardOutput(`[${name}] `, child.stdout, process.stdout)
  forwardOutput(`[${name}] `, child.stderr, process.stderr)

  return child
}

async function stop(child) {
  if (!child || child.exitCode !== null) {
    return
  }

  child.kill(&#39;SIGTERM&#39;)
  await Promise.race([
    once(child, &#39;exit&#39;),
    new Promise((resolve) =&gt; setTimeout(resolve, 2000)),
  ])

  if (child.exitCode === null) {
    child.kill(&#39;SIGKILL&#39;)
    await Promise.race([
      once(child, &#39;exit&#39;),
      new Promise((resolve) =&gt; setTimeout(resolve, 2000)),
    ])
  }
}

async function run() {
  const api = start(&#39;api&#39;, process.execPath, [&#39;./server/index.mjs&#39;])
  const web = start(&#39;web&#39;, viteCommand, [], {
    VITE_PAGINATION_API_PORT: String(API_PORT),
  })

  const shutdown = async () =&gt; {
    await Promise.all([stop(web), stop(api)])
  }

  process.on(&#39;SIGINT&#39;, shutdown)
  process.on(&#39;SIGTERM&#39;, shutdown)

  const [winner] = await Promise.race([
    once(api, &#39;exit&#39;).then(([code]) =&gt; ({ name: &#39;api&#39;, code })),
    once(web, &#39;exit&#39;).then(([code]) =&gt; ({ name: &#39;web&#39;, code })),
  ])

  await shutdown()

  if (winner.code !== 0 &amp;&amp; winner.code !== null) {
    process.exitCode = winner.code
  } else {
    process.exitCode = 1
  }
}

run().catch((error) =&gt; {
  console.error(error)
  process.exitCode = 1
})
">
<input type="hidden" name="project[files][src/main.ts]" value="import { LitElement, html } from &#39;lit&#39;
import {
  QueryClient,
  QueryClientProvider,
  createMutationController,
  createQueryController,
  keepPreviousData,
  noop,
} from &#39;@tanstack/lit-query&#39;
import {
  armNextProjectMutationFailureOnServer,
  resetProjectsApiState,
} from &#39;./services/projects/api&#39;
import { projectsMutations, projectsQueries } from &#39;./services/projects/queries&#39;
import type {
  CreateQueryOptions,
  MutationResultAccessor,
  QueryKey,
  QueryResultAccessor,
} from &#39;@tanstack/lit-query&#39;
import type {
  CreateProjectInput,
  Project,
  ProjectsPageResponse,
  ProjectsQueryKey,
  ToggleProjectFavoriteInput,
} from &#39;./services/projects/types&#39;

type ProjectsCacheSnapshot = Array&lt;[QueryKey, ProjectsPageResponse | undefined]&gt;
type FavoriteMutationResult = {
  snapshots: ProjectsCacheSnapshot
}

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,
      staleTime: 5000,
    },
    mutations: {
      retry: false,
    },
  },
})

class PaginationQueryProvider extends QueryClientProvider {
  constructor() {
    super()
    this.client = queryClient
  }

  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }
}

customElements.define(&#39;pagination-query-provider&#39;, PaginationQueryProvider)

class PaginationDemo extends LitElement {
  static properties = {
    page: { state: true },
    delayMs: { state: true },
    forceErrorMode: { state: true },
    prefetchStatus: { state: true },
    resetError: { state: true },
    draftName: { state: true },
    draftOwner: { state: true },
    mutationControlStatus: { state: true },
    mutationControlError: { state: true },
  }

  private page = 1
  private delayMs = 250
  private forceErrorMode = false
  private prefetchStatus = &#39;idle&#39;
  private resetError: string | undefined
  private draftName = &#39;Platform Rollout&#39;
  private draftOwner = &#39;Team 6&#39;
  private mutationControlStatus = &#39;idle&#39;
  private mutationControlError: string | undefined
  private lastAutoPrefetchPage = 0
  private readonly projectsQueryOptions: CreateQueryOptions&lt;
    ProjectsPageResponse,
    Error,
    ProjectsPageResponse,
    ProjectsPageResponse,
    ProjectsQueryKey
  &gt;
  private readonly projectsQuery: QueryResultAccessor&lt;
    ProjectsPageResponse,
    Error
  &gt;
  private readonly createProjectMutation: MutationResultAccessor&lt;
    Project,
    Error,
    CreateProjectInput,
    unknown
  &gt;
  private readonly favoriteMutation: MutationResultAccessor&lt;
    Project,
    Error,
    ToggleProjectFavoriteInput,
    FavoriteMutationResult
  &gt;

  constructor() {
    super()

    this.projectsQueryOptions = {
      ...projectsQueries.list(this.page, this.delayMs, this.forceErrorMode),
      placeholderData: keepPreviousData,
    }

    this.projectsQuery = createQueryController(this, this.projectsQueryOptions)

    this.createProjectMutation = createMutationController(
      this,
      {
        ...projectsMutations.create(),
        onMutate: () =&gt; {
          this.mutationControlStatus = &#39;idle&#39;
          this.mutationControlError = undefined
        },
        onSuccess: async () =&gt; {
          this.page = 1
          this.lastAutoPrefetchPage = 0
          this.prefetchStatus = &#39;idle&#39;
          this.draftName = &#39;&#39;
          this.draftOwner = &#39;Team 6&#39;
          this.syncProjectsQueryOptions()
          await queryClient.invalidateQueries({
            queryKey: projectsQueries.all(),
            refetchType: &#39;none&#39;,
          })
          await this.projectsQuery.refetch()
        },
      },
      queryClient,
    )

    this.favoriteMutation = createMutationController(
      this,
      {
        ...projectsMutations.updateFavorite(),
        onMutate: async (variables) =&gt; {
          this.mutationControlStatus = &#39;idle&#39;
          this.mutationControlError = undefined
          await queryClient.cancelQueries({ queryKey: projectsQueries.all() })

          const snapshots = queryClient.getQueriesData&lt;ProjectsPageResponse&gt;({
            queryKey: projectsQueries.all(),
          })

          for (const [key, existing] of snapshots) {
            if (!existing) {
              continue
            }

            queryClient.setQueryData&lt;ProjectsPageResponse&gt;(key, {
              ...existing,
              projects: existing.projects.map((project) =&gt;
                project.id === variables.id
                  ? { ...project, isFavorite: variables.isFavorite }
                  : project,
              ),
            })
          }

          return { snapshots }
        },
        onError: (_error, _variables, onMutateResult) =&gt; {
          for (const [key, snapshot] of onMutateResult?.snapshots ?? []) {
            queryClient.setQueryData(key, snapshot)
          }
        },
        onSettled: async () =&gt; {
          await queryClient.invalidateQueries({
            queryKey: projectsQueries.all(),
          })
        },
      },
      queryClient,
    )
  }

  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }

  override updated(): void {
    this.maybePrefetchNextPage()
  }

  private syncProjectsQueryOptions(): void {
    const { queryKey, queryFn } = projectsQueries.list(
      this.page,
      this.delayMs,
      this.forceErrorMode,
    )
    this.projectsQueryOptions.queryKey = queryKey
    this.projectsQueryOptions.queryFn = queryFn
  }

  private refetchForCurrentState(): void {
    this.syncProjectsQueryOptions()
    void this.projectsQuery.refetch()
  }

  private async maybePrefetchNextPage(): Promise&lt;void&gt; {
    const query = this.projectsQuery()
    const currentData = query.data

    if (!currentData || query.isPlaceholderData || !currentData.hasMore) {
      return
    }

    if (this.lastAutoPrefetchPage === currentData.page) {
      return
    }

    this.lastAutoPrefetchPage = currentData.page
    await this.prefetchPage(currentData.page + 1)
  }

  private onDelayInput(event: Event): void {
    const target = event.target as HTMLInputElement
    const nextValue = Number.parseInt(target.value, 10)

    if (!Number.isInteger(nextValue) || nextValue &lt; 0) {
      return
    }

    this.delayMs = nextValue
    this.refetchForCurrentState()
  }

  private onErrorModeToggle(event: Event): void {
    const target = event.target as HTMLInputElement
    this.forceErrorMode = target.checked
    this.refetchForCurrentState()
  }

  private onDraftNameInput(event: Event): void {
    const target = event.target as HTMLInputElement
    this.draftName = target.value
  }

  private onDraftOwnerInput(event: Event): void {
    const target = event.target as HTMLInputElement
    this.draftOwner = target.value
  }

  private async prefetchPage(targetPage: number): Promise&lt;void&gt; {
    this.prefetchStatus = `pending:${targetPage}`

    try {
      await queryClient
        .query(
          projectsQueries.list(targetPage, this.delayMs, this.forceErrorMode),
        )
        .catch(noop)
      this.prefetchStatus = `ready:${targetPage}`
    } catch (error) {
      this.prefetchStatus = `error:${String(error)}`
    }
  }

  private async prefetchNext(): Promise&lt;void&gt; {
    const query = this.projectsQuery()
    const currentData = query.data

    if (!currentData?.hasMore) {
      this.prefetchStatus = &#39;skipped:no-next-page&#39;
      return
    }

    await this.prefetchPage(currentData.page + 1)
  }

  private goToPreviousPage(): void {
    if (this.page &gt; 1) {
      this.page -= 1
      this.refetchForCurrentState()
    }
  }

  private goToNextPage(): void {
    const currentData = this.projectsQuery().data
    if (!currentData?.hasMore) {
      return
    }

    this.page += 1
    this.refetchForCurrentState()
  }

  private async resetDemoState(): Promise&lt;void&gt; {
    this.resetError = undefined

    try {
      await resetProjectsApiState()
      this.page = 1
      this.delayMs = 250
      this.forceErrorMode = false
      this.prefetchStatus = &#39;idle&#39;
      this.resetError = undefined
      this.draftName = &#39;Platform Rollout&#39;
      this.draftOwner = &#39;Team 6&#39;
      this.mutationControlStatus = &#39;idle&#39;
      this.mutationControlError = undefined
      this.lastAutoPrefetchPage = 0
      this.syncProjectsQueryOptions()
      this.createProjectMutation.reset()
      this.favoriteMutation.reset()
      await queryClient.resetQueries({ queryKey: projectsQueries.all() })
      await this.projectsQuery.refetch()
    } catch (error) {
      this.resetError = String(error)
    }
  }

  private submitCreateProject(): void {
    const name = this.draftName.trim()
    const owner = this.draftOwner.trim()

    if (!name || !owner) {
      return
    }

    this.createProjectMutation.mutate({ name, owner })
  }

  private toggleFavorite(project: Project): void {
    this.favoriteMutation.mutate({
      id: project.id,
      isFavorite: !project.isFavorite,
    })
  }

  private async armNextMutationFailure(): Promise&lt;void&gt; {
    this.mutationControlError = undefined

    try {
      await armNextProjectMutationFailureOnServer()
      this.mutationControlStatus = &#39;armed&#39;
    } catch (error) {
      this.mutationControlStatus = &#39;error&#39;
      this.mutationControlError = String(error)
    }
  }

  render() {
    const query = this.projectsQuery()
    const projects = query.data?.projects ?? []
    const hasMore = query.data?.hasMore ?? false
    const createProject = this.createProjectMutation()
    const favoriteProject = this.favoriteMutation()

    return html`
      &lt;main&gt;
        &lt;h1&gt;TanStack Lit Query Pagination Demo&lt;/h1&gt;
        &lt;p&gt;
          Pagination + mutation demo with optimistic favorite toggles,
          invalidation, and deterministic server failures.
        &lt;/p&gt;

        &lt;section&gt;
          &lt;div data-testid=&quot;query-status&quot;&gt;query: ${query.status}&lt;/div&gt;
          &lt;div data-testid=&quot;is-fetching&quot;&gt;
            isFetching: ${query.isFetching ? &#39;yes&#39; : &#39;no&#39;}
          &lt;/div&gt;
          &lt;div data-testid=&quot;is-placeholder&quot;&gt;
            isPlaceholderData: ${query.isPlaceholderData ? &#39;yes&#39; : &#39;no&#39;}
          &lt;/div&gt;
          &lt;div data-testid=&quot;current-page&quot;&gt;page: ${this.page}&lt;/div&gt;
          &lt;div data-testid=&quot;response-page&quot;&gt;
            response-page: ${query.data?.page ?? &#39;-&#39;}
          &lt;/div&gt;
          &lt;div data-testid=&quot;has-more&quot;&gt;has-more: ${hasMore ? &#39;yes&#39; : &#39;no&#39;}&lt;/div&gt;
          &lt;div data-testid=&quot;total-projects&quot;&gt;
            total-projects: ${query.data?.totalProjects ?? 0}
          &lt;/div&gt;
          &lt;div data-testid=&quot;total-request-count&quot;&gt;
            total-requests: ${query.data?.requestMeta.totalRequestCount ?? 0}
          &lt;/div&gt;
          &lt;div data-testid=&quot;page-request-count&quot;&gt;
            page-requests: ${query.data?.requestMeta.pageRequestCount ?? 0}
          &lt;/div&gt;
          &lt;div data-testid=&quot;total-mutation-count&quot;&gt;
            total-mutations: ${query.data?.requestMeta.totalMutationCount ?? 0}
          &lt;/div&gt;
          &lt;div data-testid=&quot;prefetch-status&quot;&gt;
            prefetch: ${this.prefetchStatus}
          &lt;/div&gt;
        &lt;/section&gt;

        ${
          query.isError
            ? html`&lt;p data-testid=&quot;query-error&quot;&gt;${query.error.message}&lt;/p&gt;`
            : null
        }
        ${
          this.resetError
            ? html`&lt;p data-testid=&quot;reset-error&quot;&gt;${this.resetError}&lt;/p&gt;`
            : null
        }

        &lt;section&gt;
          &lt;label for=&quot;delayInput&quot;&gt;Delay (ms)&lt;/label&gt;
          &lt;input
            id=&quot;delayInput&quot;
            data-testid=&quot;delay-input&quot;
            type=&quot;number&quot;
            min=&quot;0&quot;
            .value=${String(this.delayMs)}
            @input=${this.onDelayInput}
          /&gt;

          &lt;label for=&quot;errorModeToggle&quot;&gt;Force query error&lt;/label&gt;
          &lt;input
            id=&quot;errorModeToggle&quot;
            data-testid=&quot;force-error-toggle&quot;
            type=&quot;checkbox&quot;
            .checked=${this.forceErrorMode}
            @change=${this.onErrorModeToggle}
          /&gt;

          &lt;button
            data-testid=&quot;reset-demo-state&quot;
            @click=${() =&gt; this.resetDemoState()}
          &gt;
            Reset Demo State
          &lt;/button&gt;
          &lt;button
            data-testid=&quot;refetch&quot;
            @click=${() =&gt; this.projectsQuery.refetch()}
          &gt;
            Refetch
          &lt;/button&gt;
          &lt;button
            data-testid=&quot;prefetch-next&quot;
            @click=${() =&gt; this.prefetchNext()}
          &gt;
            Prefetch Next
          &lt;/button&gt;
          &lt;button
            data-testid=&quot;arm-mutation-error&quot;
            @click=${() =&gt; this.armNextMutationFailure()}
          &gt;
            Fail Next Mutation
          &lt;/button&gt;
          &lt;div data-testid=&quot;mutation-control-status&quot;&gt;
            mutation-control: ${this.mutationControlStatus}
          &lt;/div&gt;
          ${
            this.mutationControlError
              ? html`&lt;div data-testid=&quot;mutation-control-error&quot;&gt;
                  ${this.mutationControlError}
                &lt;/div&gt;`
              : null
          }
        &lt;/section&gt;

        &lt;section&gt;
          &lt;div data-testid=&quot;create-mutation-status&quot;&gt;
            create-mutation: ${createProject.status}
          &lt;/div&gt;
          ${
            createProject.isError
              ? html`&lt;div data-testid=&quot;create-mutation-error&quot;&gt;
                  ${createProject.error.message}
                &lt;/div&gt;`
              : null
          }

          &lt;label for=&quot;projectNameInput&quot;&gt;Project name&lt;/label&gt;
          &lt;input
            id=&quot;projectNameInput&quot;
            data-testid=&quot;project-name-input&quot;
            .value=${this.draftName}
            @input=${this.onDraftNameInput}
          /&gt;

          &lt;label for=&quot;projectOwnerInput&quot;&gt;Owner&lt;/label&gt;
          &lt;input
            id=&quot;projectOwnerInput&quot;
            data-testid=&quot;project-owner-input&quot;
            .value=${this.draftOwner}
            @input=${this.onDraftOwnerInput}
          /&gt;

          &lt;button
            data-testid=&quot;create-project&quot;
            ?disabled=${createProject.isPending}
            @click=${() =&gt; this.submitCreateProject()}
          &gt;
            Create Project
          &lt;/button&gt;
        &lt;/section&gt;

        &lt;section&gt;
          &lt;div data-testid=&quot;favorite-mutation-status&quot;&gt;
            favorite-mutation: ${favoriteProject.status}
          &lt;/div&gt;
          ${
            favoriteProject.isError
              ? html`&lt;div data-testid=&quot;favorite-mutation-error&quot;&gt;
                  ${favoriteProject.error.message}
                &lt;/div&gt;`
              : null
          }
        &lt;/section&gt;

        &lt;section&gt;
          &lt;button
            data-testid=&quot;previous-page&quot;
            ?disabled=${this.page === 1 || query.isPlaceholderData}
            @click=${() =&gt; this.goToPreviousPage()}
          &gt;
            Previous
          &lt;/button&gt;
          &lt;button
            data-testid=&quot;next-page&quot;
            ?disabled=${!hasMore || query.isPlaceholderData}
            @click=${() =&gt; this.goToNextPage()}
          &gt;
            Next
          &lt;/button&gt;
        &lt;/section&gt;

        &lt;ul data-testid=&quot;project-list&quot;&gt;
          ${projects.map(
            (project) =&gt; html`
              &lt;li data-testid=&quot;project-item&quot;&gt;
                &lt;span data-testid=&quot;project-name&quot;
                  &gt;${project.id}: ${project.name} (${project.owner})&lt;/span
                &gt;
                &lt;span data-testid=&quot;project-favorite-state&quot;
                  &gt;${project.isFavorite ? &#39;favorite&#39; : &#39;standard&#39;}&lt;/span
                &gt;
                &lt;button
                  data-testid=&quot;toggle-favorite&quot;
                  ?disabled=${favoriteProject.isPending}
                  @click=${() =&gt; this.toggleFavorite(project)}
                &gt;
                  ${project.isFavorite ? &#39;Unfavorite&#39; : &#39;Favorite&#39;}
                &lt;/button&gt;
              &lt;/li&gt;
            `,
          )}
        &lt;/ul&gt;
      &lt;/main&gt;
    `
  }
}

customElements.define(&#39;pagination-demo&#39;, PaginationDemo)

class PaginationDemoRoot extends LitElement {
  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }

  render() {
    return html`
      &lt;pagination-query-provider&gt;
        &lt;pagination-demo&gt;&lt;/pagination-demo&gt;
      &lt;/pagination-query-provider&gt;
    `
  }
}

customElements.define(&#39;pagination-demo-root&#39;, PaginationDemoRoot)
">
<input type="hidden" name="project[files][src/vite-env.d.ts]" value="/// &lt;reference types=&quot;vite/client&quot; /&gt;

interface ImportMetaEnv {
  readonly VITE_PAGINATION_API_PORT?: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}
">
<input type="hidden" name="project[files][server/index.mjs]" value="import { createServer } from &#39;node:http&#39;
import { API_PORT } from &#39;../config/ports.js&#39;

const PAGE_SIZE = 10
const TOTAL_PROJECTS = 50
const JSON_CONTENT_TYPE = &#39;application/json&#39;

function createSeedProjects() {
  return Array.from({ length: TOTAL_PROJECTS }, (_, index) =&gt; ({
    id: index + 1,
    name: `Project ${index + 1}`,
    owner: `Team ${(index % 5) + 1}`,
    isFavorite: false,
  }))
}

let projects = createSeedProjects()
let nextProjectId = projects.length + 1
let totalRequestCount = 0
let totalMutationCount = 0
let failNextMutation = false
const perPageRequestCount = new Map()

function writeJson(res, status, body) {
  const payload = JSON.stringify(body)
  res.writeHead(status, {
    &#39;content-type&#39;: &#39;application/json; charset=utf-8&#39;,
    &#39;cache-control&#39;: &#39;no-store&#39;,
    &#39;access-control-allow-origin&#39;: &#39;*&#39;,
    &#39;access-control-allow-methods&#39;: &#39;GET,POST,PATCH,OPTIONS&#39;,
    &#39;access-control-allow-headers&#39;: &#39;content-type&#39;,
    &#39;content-length&#39;: Buffer.byteLength(payload),
  })
  res.end(payload)
}

function parsePositiveInt(rawValue, fallback) {
  if (rawValue == null || rawValue === &#39;&#39;) {
    return fallback
  }

  const parsed = Number.parseInt(rawValue, 10)
  if (!Number.isInteger(parsed) || parsed &lt; 1) {
    return undefined
  }

  return parsed
}

function parseNonNegativeInt(rawValue, fallback) {
  if (rawValue == null || rawValue === &#39;&#39;) {
    return fallback
  }

  const parsed = Number.parseInt(rawValue, 10)
  if (!Number.isInteger(parsed) || parsed &lt; 0) {
    return undefined
  }

  return parsed
}

function resetState() {
  projects = createSeedProjects()
  nextProjectId = projects.length + 1
  totalRequestCount = 0
  totalMutationCount = 0
  failNextMutation = false
  perPageRequestCount.clear()
}

async function sleep(ms) {
  if (!ms || ms &lt;= 0) {
    return
  }

  await new Promise((resolve) =&gt; {
    setTimeout(resolve, ms)
  })
}

async function readJsonBody(req) {
  const contentType = req.headers[&#39;content-type&#39;]
  if (!contentType || !contentType.startsWith(JSON_CONTENT_TYPE)) {
    return {
      ok: false,
      status: 415,
      error: &#39;Expected application/json request body&#39;,
    }
  }

  const chunks = []
  for await (const chunk of req) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
  }

  const raw = Buffer.concat(chunks).toString(&#39;utf8&#39;)
  if (!raw) {
    return {
      ok: false,
      status: 400,
      error: &#39;Request body is required&#39;,
    }
  }

  try {
    return {
      ok: true,
      value: JSON.parse(raw),
    }
  } catch {
    return {
      ok: false,
      status: 400,
      error: &#39;Request body must be valid JSON&#39;,
    }
  }
}

function buildProjectsPage(page) {
  const totalPages = Math.max(1, Math.ceil(projects.length / PAGE_SIZE))
  const boundedPage = Math.min(page, totalPages)
  const startIndex = (boundedPage - 1) * PAGE_SIZE
  const endIndex = startIndex + PAGE_SIZE
  const items = projects.slice(startIndex, endIndex)

  totalRequestCount += 1
  const pageRequests = (perPageRequestCount.get(boundedPage) ?? 0) + 1
  perPageRequestCount.set(boundedPage, pageRequests)

  return {
    page: boundedPage,
    pageSize: PAGE_SIZE,
    totalPages,
    totalProjects: projects.length,
    hasMore: boundedPage &lt; totalPages,
    projects: items,
    requestMeta: {
      totalRequestCount,
      pageRequestCount: pageRequests,
      totalMutationCount,
    },
  }
}

function maybeFailMutation(res) {
  if (!failNextMutation) {
    return false
  }

  failNextMutation = false
  writeJson(res, 500, { error: &#39;Forced mutation failure (test)&#39; })
  return true
}

function validateProjectName(name) {
  if (typeof name !== &#39;string&#39;) {
    return &#39;Project name must be a string&#39;
  }

  const trimmed = name.trim()
  if (trimmed.length &lt; 3) {
    return &#39;Project name must be at least 3 characters&#39;
  }

  if (trimmed.length &gt; 60) {
    return &#39;Project name must be 60 characters or fewer&#39;
  }

  return null
}

function validateOwner(owner) {
  if (typeof owner !== &#39;string&#39;) {
    return &#39;Owner must be a string&#39;
  }

  const trimmed = owner.trim()
  if (!trimmed) {
    return &#39;Owner is required&#39;
  }

  if (trimmed.length &gt; 40) {
    return &#39;Owner must be 40 characters or fewer&#39;
  }

  return null
}

const server = createServer(async (req, res) =&gt; {
  if (req.method === &#39;OPTIONS&#39;) {
    res.writeHead(204, {
      &#39;access-control-allow-origin&#39;: &#39;*&#39;,
      &#39;access-control-allow-methods&#39;: &#39;GET,POST,PATCH,OPTIONS&#39;,
      &#39;access-control-allow-headers&#39;: &#39;content-type&#39;,
      &#39;cache-control&#39;: &#39;no-store&#39;,
    })
    res.end()
    return
  }

  if (!req.url) {
    writeJson(res, 400, { error: &#39;Missing URL&#39; })
    return
  }

  const requestUrl = new URL(req.url, `http://127.0.0.1:${API_PORT}`)

  if (requestUrl.pathname === &#39;/api/projects&#39; &amp;&amp; req.method === &#39;GET&#39;) {
    const page = parsePositiveInt(requestUrl.searchParams.get(&#39;page&#39;), 1)
    if (!page) {
      writeJson(res, 400, { error: &#39;Invalid page parameter&#39; })
      return
    }

    const delayMs = parseNonNegativeInt(requestUrl.searchParams.get(&#39;delay&#39;), 0)
    if (delayMs === undefined) {
      writeJson(res, 400, { error: &#39;Invalid delay parameter&#39; })
      return
    }

    await sleep(delayMs)

    if (requestUrl.searchParams.get(&#39;error&#39;) === &#39;true&#39;) {
      writeJson(res, 500, {
        error: &#39;Forced server error (test)&#39;,
        page,
      })
      return
    }

    writeJson(res, 200, buildProjectsPage(page))
    return
  }

  if (requestUrl.pathname === &#39;/api/projects&#39; &amp;&amp; req.method === &#39;POST&#39;) {
    if (maybeFailMutation(res)) {
      return
    }

    const payload = await readJsonBody(req)
    if (!payload.ok) {
      writeJson(res, payload.status, { error: payload.error })
      return
    }

    const nameError = validateProjectName(payload.value?.name)
    if (nameError) {
      writeJson(res, 422, { error: nameError })
      return
    }

    const ownerError = validateOwner(payload.value?.owner)
    if (ownerError) {
      writeJson(res, 422, { error: ownerError })
      return
    }

    const project = {
      id: nextProjectId,
      name: payload.value.name.trim(),
      owner: payload.value.owner.trim(),
      isFavorite: false,
    }

    nextProjectId += 1
    totalMutationCount += 1
    projects = [project, ...projects]

    writeJson(res, 201, {
      project,
      mutationCount: totalMutationCount,
    })
    return
  }

  const patchMatch = requestUrl.pathname.match(/^\/api\/projects\/(\d+)$/)
  if (patchMatch &amp;&amp; req.method === &#39;PATCH&#39;) {
    if (maybeFailMutation(res)) {
      return
    }

    const projectId = Number.parseInt(patchMatch[1], 10)
    const projectIndex = projects.findIndex(
      (project) =&gt; project.id === projectId,
    )
    if (projectIndex === -1) {
      writeJson(res, 404, { error: `Project ${projectId} was not found` })
      return
    }

    const payload = await readJsonBody(req)
    if (!payload.ok) {
      writeJson(res, payload.status, { error: payload.error })
      return
    }

    if (typeof payload.value?.isFavorite !== &#39;boolean&#39;) {
      writeJson(res, 422, { error: &#39;isFavorite must be a boolean&#39; })
      return
    }

    const nextProject = {
      ...projects[projectIndex],
      isFavorite: payload.value.isFavorite,
    }

    totalMutationCount += 1
    projects = projects.map((project, index) =&gt;
      index === projectIndex ? nextProject : project,
    )

    writeJson(res, 200, {
      project: nextProject,
      mutationCount: totalMutationCount,
    })
    return
  }

  if (
    requestUrl.pathname === &#39;/api/testing/fail-next-mutation&#39; &amp;&amp;
    req.method === &#39;POST&#39;
  ) {
    failNextMutation = true
    writeJson(res, 200, { ok: true })
    return
  }

  if (requestUrl.pathname === &#39;/api/reset&#39; &amp;&amp; req.method === &#39;POST&#39;) {
    resetState()
    writeJson(res, 200, { ok: true })
    return
  }

  writeJson(res, 404, { error: &#39;Not found&#39; })
})

server.listen(API_PORT, &#39;127.0.0.1&#39;, () =&gt; {
  console.log(`[api] listening on http://127.0.0.1:${API_PORT}`)
})
">
<input type="hidden" name="project[files][src/services/projects/api.ts]" value="import type {
  CreateProjectInput,
  ProjectMutationResponse,
  ProjectsPageResponse,
  ToggleProjectFavoriteInput,
} from &#39;./types&#39;

const DEFAULT_API_PORT = 4184
const configuredApiPort = Number.parseInt(
  import.meta.env.VITE_PAGINATION_API_PORT ?? String(DEFAULT_API_PORT),
  10,
)
const API_PORT = Number.isInteger(configuredApiPort)
  ? configuredApiPort
  : DEFAULT_API_PORT
const API_BASE_URL = `http://127.0.0.1:${API_PORT}`

function buildProjectsUrl(
  page: number,
  delayMs: number,
  forceError: boolean,
): URL {
  const url = new URL(&#39;/api/projects&#39;, API_BASE_URL)
  url.searchParams.set(&#39;page&#39;, String(page))

  if (delayMs &gt; 0) {
    url.searchParams.set(&#39;delay&#39;, String(delayMs))
  }

  if (forceError) {
    url.searchParams.set(&#39;error&#39;, &#39;true&#39;)
  }

  return url
}

async function readJsonOrThrow&lt;T&gt;(
  response: Response,
  fallbackMessage: string,
): Promise&lt;T&gt; {
  if (response.ok) {
    return (await response.json()) as T
  }

  const payload = (await response.json().catch(() =&gt; null)) as {
    error?: string
  } | null

  throw new Error(
    payload &amp;&amp; typeof payload === &#39;object&#39; &amp;&amp; &#39;error&#39; in payload
      ? String(payload.error ?? fallbackMessage)
      : fallbackMessage,
  )
}

async function requestJson&lt;T&gt;(
  url: URL,
  init: RequestInit,
  fallbackMessage: string,
): Promise&lt;T&gt; {
  const response = await fetch(url, init)
  return readJsonOrThrow&lt;T&gt;(response, fallbackMessage)
}

export const projectsAPI = {
  getProjects: async (
    page: number,
    delayMs: number,
    forceError: boolean,
  ): Promise&lt;ProjectsPageResponse&gt; =&gt; {
    const response = await fetch(buildProjectsUrl(page, delayMs, forceError))
    return readJsonOrThrow&lt;ProjectsPageResponse&gt;(
      response,
      `Failed to fetch projects page ${page}`,
    )
  },
  createProject: async (
    input: CreateProjectInput,
  ): Promise&lt;ProjectMutationResponse&gt; =&gt; {
    return requestJson&lt;ProjectMutationResponse&gt;(
      new URL(&#39;/api/projects&#39;, API_BASE_URL),
      {
        method: &#39;POST&#39;,
        headers: {
          &#39;content-type&#39;: &#39;application/json&#39;,
        },
        body: JSON.stringify(input),
      },
      &#39;Failed to create project&#39;,
    )
  },
  updateProjectFavorite: async (
    input: ToggleProjectFavoriteInput,
  ): Promise&lt;ProjectMutationResponse&gt; =&gt; {
    return requestJson&lt;ProjectMutationResponse&gt;(
      new URL(`/api/projects/${input.id}`, API_BASE_URL),
      {
        method: &#39;PATCH&#39;,
        headers: {
          &#39;content-type&#39;: &#39;application/json&#39;,
        },
        body: JSON.stringify({ isFavorite: input.isFavorite }),
      },
      `Failed to update project ${input.id}`,
    )
  },
}

export async function armNextProjectMutationFailureOnServer(): Promise&lt;void&gt; {
  await requestJson&lt;{ ok: true }&gt;(
    new URL(&#39;/api/testing/fail-next-mutation&#39;, API_BASE_URL),
    {
      method: &#39;POST&#39;,
    },
    &#39;Failed to arm next mutation failure&#39;,
  )
}

export async function resetProjectsApiState(): Promise&lt;void&gt; {
  await requestJson&lt;{ ok: true }&gt;(
    new URL(&#39;/api/reset&#39;, API_BASE_URL),
    {
      method: &#39;POST&#39;,
    },
    &#39;Failed to reset API state&#39;,
  )
}
">
<input type="hidden" name="project[files][src/services/projects/queries.ts]" value="import { mutationOptions, queryOptions } from &#39;@tanstack/lit-query&#39;
import { projectsAPI } from &#39;./api&#39;
import type { CreateProjectInput, ToggleProjectFavoriteInput } from &#39;./types&#39;

export const projectsQueries = {
  all: () =&gt; [&#39;projects&#39;] as const,
  list: (page: number, delayMs: number, forceError: boolean) =&gt;
    queryOptions({
      queryKey: [
        ...projectsQueries.all(),
        &#39;list&#39;,
        page,
        delayMs,
        forceError,
      ] as const,
      queryFn: () =&gt; projectsAPI.getProjects(page, delayMs, forceError),
    }),
}

export const projectsMutations = {
  create: () =&gt;
    mutationOptions({
      mutationKey: [...projectsQueries.all(), &#39;create&#39;] as const,
      mutationFn: async (input: CreateProjectInput) =&gt; {
        const response = await projectsAPI.createProject(input)
        return response.project
      },
    }),
  updateFavorite: () =&gt;
    mutationOptions({
      mutationKey: [...projectsQueries.all(), &#39;update-favorite&#39;] as const,
      mutationFn: async (input: ToggleProjectFavoriteInput) =&gt; {
        const response = await projectsAPI.updateProjectFavorite(input)
        return response.project
      },
    }),
}
">
<input type="hidden" name="project[files][src/services/projects/types.ts]" value="export type Project = {
  id: number
  name: string
  owner: string
  isFavorite: boolean
}

export type ProjectsPageResponse = {
  page: number
  pageSize: number
  totalPages: number
  totalProjects: number
  hasMore: boolean
  projects: Array&lt;Project&gt;
  requestMeta: {
    totalRequestCount: number
    pageRequestCount: number
    totalMutationCount: number
  }
}

export type CreateProjectInput = {
  name: string
  owner: string
}

export type ToggleProjectFavoriteInput = {
  id: number
  isFavorite: boolean
}

export type ProjectMutationResponse = {
  project: Project
  mutationCount: number
}

export type ProjectsQueryKey = readonly [
  &#39;projects&#39;,
  &#39;list&#39;,
  number,
  number,
  boolean,
]
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="@tanstack/query-example-lit-pagination">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>