Query Reference
Performance
What the package ships and how its engine and bindings behave under load, read from the measurement files with their method and environment. Targets that are missed are listed as missed.
Markdown version of this page, for assistants (Markdown for AI)
On this page
Sizes
Each row is a consumer entry built for production. Marginal rows subtract the row they are
measured over. Read from packages/query/artifacts/size.json.
| Entry | Over | gzip | brotli | Budget (gzip) | Target (gzip) |
|---|---|---|---|---|---|
| part: poll (lazy) | – | 1039 B | 963 B | 1100 B | – |
| part: connect (lazy) | – | 931 B | 828 B | 1100 B | – |
| part: access.svelte (lazy) | – | 947 B | 876 B | 1100 B | – |
| part: gc (lazy) | – | 741 B | 671 B | 800 B | – |
| part: limiter (lazy) | – | 796 B | 742 B | 900 B | – |
| part: share (lazy) | – | 536 B | 503 B | 600 B | – |
| part: index-part (lazy) | – | 470 B | 430 B | 600 B | – |
| query | – | 11787 B | 10839 B | 11800 B | 5000 B missed |
| query+mutation | – | 15409 B | 14167 B | 15500 B | 6500 B missed |
| root (every export) | – | 16134 B | 14844 B | 16200 B | – |
| query + every part (static) | – | 15667 B | 14383 B | 15700 B | – |
| every part (static, marginal) | query | 3880 B | 3544 B | 4300 B | – |
| http (marginal) | query | 2239 B | 2079 B | 2400 B | 1500 B missed |
| laravel (marginal) | http (marginal) | 1681 B | 1548 B | 1900 B | 600 B missed |
| forms (marginal) | query+mutation | 1406 B | 1226 B | 1600 B | 700 B missed |
| persist (marginal) | query | 3428 B | 3201 B | 3700 B | 1500 B missed |
| live (marginal) | query | 2777 B | 2565 B | 3100 B | 900 B missed |
| devtools (production stub) | – | 92 B | 68 B | 100 B | 60 B missed |
| paged (marginal) | query | 1234 B | 1109 B | 1400 B | 800 B missed |
| infinite (marginal) | query | 1648 B | 1502 B | 1800 B | 900 B missed |
| ssr (marginal) | query | 568 B | 514 B | 700 B | 400 B missed |
| ssr every export (marginal) | query | 1287 B | 1178 B | 1500 B | – |
| tabs (marginal) | query | 3104 B | 2874 B | 3500 B | 1000 B missed |
| testing (not shipped) | – | 18878 B | 17235 B | 19300 B | – |
Consumer entries bundled with Vite in production mode (Rollup tree-shaking, esbuild minify, ESM, es2022, production condition, svelte external, .svelte.js compiled with compileModule). "query" = defineQuery + createQuery + client (first-load code; optional parts are their own "part:" rows, loaded on first use); "query+mutation" adds defineMutation, createMutation, patch; "query + every part (static)" adds `allParts` from `./parts` (every optional part in the first load, no lazy chunk). Vite's preload helper is the application's and not counted. Marginal rows subtract the row named in "over". gzip level 9 / brotli quality 11 (node:zlib). Budgets are gzip; "target" is the 04 §2 target.
Engine benchmarks
The engine in Node under the production condition, with a virtual clock and an in-memory transport; median of fresh runs, every sample in the file. Node v22.23.3, AMD EPYC 9555P 64-Core Processor, 2026-10-06.
| ID | Benchmark | Target | Result | Measured |
|---|---|---|---|---|
| B1 | exact invalidation, cache of 10k / 50k entries | ≤ 0.05 ms at 50k | missed | 0.009 ms / 0.0277 ms (800 endpoints); 0.0805 ms with 50k entries of one endpoint (first call 15.74 ms, then 0.0018 ms) |
| B2 | 100 tag invalidations (realtime burst), 50k entries | ≤ 5 ms | met | 1.963 ms at 50k (0.4172 ms at 10k), burst + flush |
| B3 | one write at 1k / 5k / 20k / 100k entries | flat ± 20 % | missed | update 2.969 / 2.633 / 3.493 / 4.179 µs (client.set 2.162 / 2.177 / 2.971 / 3.429 µs, warm 1.659 / 1.437 / 1.435 / 1.765 µs); new entry 2.353 / 2.765 / 2.378 / 3.479 µs; fetch 9.594 / 9.85 / 10.057 / 9.884 µs (largest deviation from 1k: 59 %) |
| B4 | invalidate one tag provided by 5 000 entries (sync part) | ≤ 5 ms | met | 1.532 ms sync; 24.805 ms to queue the refetches; 70.781 ms settled |
| B5 | mount 5 000 observers with fresh and poll | ≤ 1 platform timer; ≤ 40 ms | missed | 1 platform timer; 71.044 ms mount (20.677 ms unmount) |
| B6 | refetch 100k rows, 0 / 1 / all rows changed (row-wise identity reuse, internal) | ≤ 10 ms with 1 changed | missed | 18.564 ms / 18.708 ms / 12.071 ms at 100k (0 / 1 / all changed) |
| B7 | refetch 50-row page, identical text | ≤ 0.1 ms, no signal write | met | 11.003 µs per refetch cycle with text retained (polled), 16.084 µs without; data effect runs: 0 / 0 |
| B8 | key serialization (canonical string), realistic table params | ≤ 2 µs, once per params change | met | 1.294 µs per key (canonical 1.288 µs) |
| B9 | register 5 000 endpoints across 500 modules | linear, constant stack depth | met | 0.6951 µs per endpoint at 5 000 vs 0.901 µs at 500 (×0.77); 17 frames for the first and the last module |
| B10 | 1 000 live events/s into tagged entries with 10k cached | no task > 50 ms; ≤ 1 request per observed entry in flight | met | tag rule: longest task 0.5503 ms, 1 in flight per entry, 8.702 ms busy; endpoint patch rule: longest task 0.3163 ms, 18.414 ms busy; entity patch rule: longest task 0.3578 ms, 19.424 ms busy |
| B11 | fetching/mutating counters read 10k times during a storm | O(1) per read | met | 0.0141 / 0.0163 / 0.0226 / 0.0228 µs per read with 100 / 1k / 5k / 50k in flight (×1.62 for ×500 requests) |
| B12 | persistence flush of 1 000 dirty entries among 10k persisted | no task > 16 ms | met | longest task 4.104 ms over 3 tasks (4.157 ms in all); 5.997 ms when the partition is over its byte budget |
| B13 | a $derived page state over 100 bindings during a burst of 1 000 fetch starts/settles | O(members) per change, no data reads, ≤ 2 ms per burst step | met | 0.0607 ms median step, 1.119 ms slowest; 0 data reads |
| B14 | an optimistic entity patch across 20 cached 50-row pages: push + display + remove | ≤ 0.5 ms (F04 M2) | met | 0.0498 ms per cycle |
| B15 | `allowed(input)` read for 500 table rows, then one rights change | ≤ 1 ms per pass; one re-evaluation per row on the change | met | 0.2164 ms per read pass, 0.6987 ms for the change pass; 1 re-evaluation per row |
| B16 | `send(request)` overhead excluding transport | ≤ the ep.fetch pipeline cost; 0 entries, 0 index writes | met | 0.6238 µs per send vs 4.241 µs per ep.fetch; 0 entries, 0 index writes |
| F17 | entity patch across cached pages | ≤ 0.5 ms (40 pages + 1 detail); ≤ 1 ms (a row of a 10 000-row page) | met | 0.02 ms on 40 pages + 1 detail; 96.816 µs on a 10 000-row page |
Engine benchmarks of docs/query/04 §3, produced by bench/run.mjs. Node, the engine from src under the production condition (#env DEV = false, Svelte browser + production build, compiled with dev: false), a virtual clock and an in-memory transport that answers with an already resolved promise unless a latency is stated. Each entry: WARMUP discarded runs, then RUNS fresh runs (state rebuilt, heap collected), median reported with every sample. A partial run (file filters) replaces only its own ids; the environment is that of the latest run.
Browser benchmarks
Pages of the playground in chromium 153.0.8010.12, production build, CPU throttled 4 times, against a fake server with a fixed latency and six connections; AMD EPYC 9555P 64-Core Processor, 2026-10-04.
P1: Paging quickly through a server-paged DataTable
| Quantity | Median | p95 |
|---|---|---|
| 19 "Next page" clicks 50 ms apart, first click to the frame where page 20 is shown and nothing fetches; CPU 4×, fake server 150 ms; median of 5 | 1118.1 ms | 1133.2 ms |
| last of the 19 clicks to the frame where page 20 is shown and nothing fetches; CPU 4×; median of 5 | 183.8 ms | 189.4 ms |
| requests that reached the fake server during the 19 clicks; median of 5 | 19 | 19 |
| requests aborted after reaching the fake server (pages left before their answer); median of 5 | 18 | 18 |
| longtask entries from the first click to the settled frame; median of 5 | 0 | 1 |
| summed duration of those long tasks; median of 5 | 0 ms | 68 ms |
P2: A polled table that receives identical answers
| Quantity | Median | p95 |
|---|---|---|
| poll requests answered in 60 s at 2 s; median of 5 | 30 | 30 |
| MutationObserver records inside the table body (rows and cells) during 60 s of identical poll answers; median of 5 | 0 | 0 |
| MutationObserver records in the whole table, including the data-refreshing indicator on its root; median of 5 | 180 | 180 |
| runs of a page $effect that reads list.data during the 60 s; median of 5 | 0 | 0 |
| main-thread task time (CDP Performance.getMetrics TaskDuration) over the 60 s run, CPU 4×; median of 5 | 1341.5 ms | 1499.58 ms |
| longtask entries during the 60 s; median of 5 | 0 | 0 |
P3: A user click during an invalidation storm on a dashboard
| Quantity | Median | p95 |
|---|---|---|
| click "Open details" 500 ms into the storm to the frame showing the details (one 200 ms request); CPU 4×; median of 5 | 213.1 ms | 218.9 ms |
| time the details request waited for a fake-server connection (6, FIFO); median of 5 | 0 ms | 0 ms |
| storm start to the frame where all 40 widgets show the last server version; median of 5 | 3523.3 ms | 3524.5 ms |
| widget requests that reached the fake server during the run; median of 5 | 90 | 90 |
| widget requests aborted after reaching the fake server; median of 5 | 0 | 0 |
| most requests the fake server held at once; median of 5 | 6 | 6 |
| longtask entries during the run; median of 5 | 0 | 0 |
P4: A large page refetched with one changed row
| Quantity | Median | p95 |
|---|---|---|
| fake server answer handed over (JSON text) to the $effect.pre that sees the new value (0 = no commit); CPU 4×; median of 5 | 38.9 ms | 135.8 ms |
| fake server answer handed over to the $effect after the DOM update of the same flush (0 = no commit); CPU 4×; median of 5 | 55.6 ms | 149.3 ms |
| fake server answer handed over to the animation frame after the changed cell shows its new text; CPU 4×; median of 5 | 1145.7 ms | 1393.4 ms |
| control without the library: one direct textContent write into a cell of the same 10 000-row document to the next animation frame; CPU 4×; median of 5 | 264.2 ms | 296.8 ms |
| row objects whose identity changed; median of 5 | 1 | 1 |
| runs of an $effect.pre reading the data; median of 5 | 1 | 1 |
| fake server answer handed over (JSON text) to the $effect.pre that sees the new value (0 = no commit); CPU 4×; median of 5 | 0 ms | 0 ms |
| fake server answer handed over to the $effect after the DOM update of the same flush (0 = no commit); CPU 4×; median of 5 | 0 ms | 0 ms |
| fake server answer handed over to the animation frame after fetching ended; CPU 4×; median of 5 | 805.4 ms | 1069.5 ms |
| control without the library: one direct textContent write into a cell of the same 10 000-row document to the next animation frame; CPU 4×; median of 5 | 319.9 ms | 379.8 ms |
| row objects whose identity changed; median of 5 | 0 | 0 |
| runs of an $effect.pre reading the data; median of 5 | 0 | 0 |
P5: An optimistic toggle clicked rapidly while the list polls
| Quantity | Median | p95 |
|---|---|---|
| aria-pressed changes to a value other than the one the user last chose, during 20 clicks 40 ms apart until two polls after the last write; median of 5 | 0 | 0 |
| 1 when the shown state, the server state and the last choice agree at the end; median of 5 | 1 | 1 |
| aria-pressed changes observed (20 = one per click); median of 5 | 20 | 20 |
| first click to two poll answers after the last write; CPU 4×; median of 5 | 8276.3 ms | 8276.6 ms |
| list requests (polls and invalidation refetches) during the run; median of 5 | 3 | 3 |
P6: The same polled list open in several tabs, with ./tabs
| Quantity | Median | p95 |
|---|---|---|
| list requests per minute at the network (30 s window × 2), list polled every 2 s, 1 page(s) of one context with ./tabs; CPU 4×; median of 5 | 30 | 30 |
| live connections (EventSource requests) opened, 1 page(s) of one context with ./tabs; median of 5 | 1 | 1 |
| list requests per minute at the network (30 s window × 2), list polled every 2 s, 2 page(s) of one context with ./tabs; CPU 4×; median of 5 | 30 | 30 |
| live connections (EventSource requests) opened, 2 page(s) of one context with ./tabs; median of 5 | 1 | 1 |
| list requests per minute at the network (30 s window × 2), list polled every 2 s, 5 page(s) of one context with ./tabs; CPU 4×; median of 5 | 30 | 30 |
| live connections (EventSource requests) opened, 5 page(s) of one context with ./tabs; median of 5 | 1 | 1 |
| list requests per minute at the network (30 s window × 2), list polled every 2 s, 5 pages of one context without ./tabs (control); CPU 4×; median of 5 | 150 | 150 |
| live connections (EventSource requests) opened, 5 pages of one context without ./tabs (control); median of 5 | 5 | 5 |
P7: A reload with persisted reference data
| Quantity | Median | p95 |
|---|---|---|
| reload with a persisted entry: navigation start to the animation frame after the restored 250 rows were first rendered; CPU 4×; median of 5 | 115.2 ms | 178.7 ms |
| time covered by IndexedDB store reads (list, get; overlaps once) before that frame; median of 5 | 40 ms | 43.1 ms |
| store reads before that frame; median of 5 | 2 | 2 |
| baseline without the persist module: navigation start to the frame after the network answer (800 ms) was rendered; CPU 4×; median of 5 | 870 ms | 877.4 ms |
Browser benchmarks P1–P7 of docs/query/04-performance.md §5 from apps/playground/tests/bench/query-*.bench.spec.ts (Playwright project bench, production build, CPU throttled 4× over the DevTools protocol, the fake server of @svelte-lean/test-utils): each entry is the median of its samples with min, max, p95 and the method in one sentence. Published by `yarn bench:publish` (apps/playground/scripts/publish-results.mjs), which copies test-results/query-bench.json here unchanged with publishedAt set to environment.date. Method: docs/proof/methodology.md. The query.P6.* entries (tests/bench/query-tabs.bench.spec.ts) were recorded on 2026-10-04T14:23:22.864Z in the same environment (machine, browser, mode, CPU throttling) and appended to this file.
Long session
One session of client-side navigations with polled lists, a live source, invalidation storms, hidden periods and freezes; chromium 153.0.8010.12, 2026-10-04.
| Quantity | Median | Max |
|---|---|---|
| JSHeapUsedSize after two forced GCs on the empty route /query/soak, after 50 warm-up navigations and keep (20 s) + 3 s; CPU 4× | 5816040 B | 5816040 B |
| JSHeapUsedSize after two forced GCs every 100 navigations during the session (one sample per batch, on a session page with its entries) | 25009304 B | 30814436 B |
| JSHeapUsedSize after two forced GCs on the empty route after 2000 navigations and keep + 3 s | 4528280 B | 4528280 B |
| heap-end minus heap-baseline; target within 10 % of the baseline (581604 B) | -1287760 B | -1287760 B |
| Performance.getMetrics Nodes at the baseline and at the end | 297 | 297 |
| client.stats() entries minus observed on the empty route after keep + 3 s (target 0) | 0 | 0 |
| client.stats().observed on the empty route at the end: the three polled shell lists | 3 | 3 |
| client.stats().entries sampled every 1 s; the largest sample of each 100-navigation batch | 178 | 233 |
| client.stats().inactiveBytes sampled every 1 s; the largest sample of each batch (budget 8388608 B) | 8385278.5 B | 8388297 B |
| pending platform timers armed by the package (a counting ClientInit.clock) sampled every 1 s; the largest sample of each batch (target ≤ 1 per client) | 1 | 1 |
| the most platform timers the package had pending at any moment of the session (counted at every setTimer/clearTimer) | 1 | 1 |
| client.stats().timers (items in the scheduler heap behind the one platform timer), largest 1 s sample per batch | 12.5 | 15 |
| listeners on window and document (DOMDebugger.getEventListeners): baseline, every 100 navigations, end (target constant) | 28 | 28 |
| Performance.getMetrics JSEventListeners after forced GC: baseline, every batch, end | 34 | 38 |
| long animation frames over 50 ms during the session (PerformanceObserver long-animation-frame; the observer is checked first with an 80 ms timer task) | 4 | 4 |
| of those, frames with a script entry attributed to the package chunk | 0 | 0 |
| script entries attributed to the package chunk whose own duration was over 50 ms (target 0) | 0 | 0 |
| self time in the package chunk per navigation, CPU profile (0.5 ms sampling) over navigations 501–600, 1001–1100, 1501–1600, 1901–2000; one sample per profiled batch; CPU 4×; ./live is inlined in the layout chunk and not counted | 0.63 ms | 0.9 ms |
| the package chunk self time as a percentage of all sampled time in the same profiled batches | 0.35 | 0.5 |
| goto() to the page reporting its data (or 2 s), median of each 100-navigation batch; CPU 4× | 170.75 ms | 197 ms |
| requests answered and aborted by the fake server over the whole run (warm-up and end included) | 4665 | 9036 |
| storms, tag invalidations and live events sent during the session | 12000 | 19013 |
Soak benchmark of docs/query/features/F14-memory-long-sessions.md §7 from apps/playground/tests/stress/query-soak.spec.ts (Playwright project stress, SLEAN_STRESS=1, production build, CPU throttled 4×): one long session; each entry lists its samples over the session (per 100-navigation batch, or baseline and end) with median, min, max, p95 and the method in one sentence. Published by `yarn bench:publish` (apps/playground/scripts/publish-results.mjs), which copies test-results/query-soak.json here unchanged with publishedAt set to environment.date. Method: docs/proof/methodology.md.
Type cost
What endpoint definitions cost the type checker as a program grows. M2: instantiations a consumer adds when it imports two endpoints, relative to the smallest program. M3: the whole-program slope, instantiations per endpoint. TypeScript 5.9.3.
| Program | M2 ratio | M2 gate | M3 | M3 gate |
|---|---|---|---|---|
| contract | 1 | 1.1 | 61.9 | 65 |
| aggregate (control) | 33.082 | – | 66.9 | – |
| generated | 1 | 1.1 | 61.4 | 65 |
tsc --extendedDiagnostics over generated programs (types-bench/generate.mjs): feature modules of 10 endpoints in the 03a shape, each augmenting Tags and Permissions, plus a consumer importing two endpoints. M2 = instantiations with the consumer minus without it. M3 = whole-program instantiation slope from N=100 to N=3000. "aggregate" is the control shape (one object of every endpoint with generated hook names). "generated" is the same feature mix written by @svelte-lean/query-codegen from an OpenAPI document (types-bench/openapi.mjs: openapi-typescript output, generated modules, one satisfies-typed policy module per feature); it is held to the same gates. Programs read the built declarations (dist/*.d.ts), as a consumer does.
Comparison
A harness runs the browser scenarios and the engine benchmarks against the same pages written with TanStack Query and RTK Query (benchmarks/compare/query). Its results are committed as evidence and are not quoted here: the repository's methodology admits a comparison with another library on the site only when every condition of a fair comparison holds, which these runs do not yet meet (methodology).
Reproduce
yarn build:packages
yarn workspace @svelte-lean/query size # packages/query/artifacts/size.json and types.json
yarn workspace @svelte-lean/query bench # packages/query/artifacts/bench.json
yarn bench:publish # apps/playground/results/*.json (browser, soak)