diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37358720..f2599c52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,7 +11,7 @@ importers: devDependencies: '@rslint/core': specifier: ^0.5.3 - version: 0.5.3(jiti@2.6.1) + version: 0.5.3(jiti@2.7.0) cross-env: specifier: 10.1.0 version: 10.1.0 @@ -330,7 +330,7 @@ importers: version: 8.60.0(eslint@9.39.4)(typescript@5.9.3) eslint: specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-plugin-react-hooks: specifier: ^7.1.1 version: 7.1.1(eslint@9.39.4) @@ -707,6 +707,64 @@ importers: specifier: ^5.9.3 version: 5.9.3 + rsbuild/tanstack-start: + dependencies: + '@tanstack/react-router': + specifier: ^1.170.11 + version: 1.170.11(react-dom@19.2.6)(react@19.2.6) + '@tanstack/react-router-devtools': + specifier: ^1.167.0 + version: 1.167.0(@tanstack/react-router@1.170.11)(@tanstack/router-core@1.171.9)(csstype@3.2.3)(react-dom@19.2.6)(react@19.2.6) + '@tanstack/react-start': + specifier: ^1.168.19 + version: 1.168.19(@rsbuild/core@2.0.9)(@rspack/core@2.0.5)(@vitejs/plugin-rsc@0.5.27)(crossws@0.4.5)(react-dom@19.2.6)(react-server-dom-rspack@0.0.2)(react@19.2.6)(vite@8.0.16)(webpack@5.102.1) + dexie: + specifier: ^4.0.10 + version: 4.4.3 + react: + specifier: ^19.2.0 + version: 19.2.6 + react-dom: + specifier: ^19.2.0 + version: 19.2.6(react@19.2.6) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + zod: + specifier: ^4.4.3 + version: 4.4.3 + zustand: + specifier: ^5.0.10 + version: 5.0.14(@types/react@19.2.15)(react@19.2.6)(use-sync-external-store@1.6.0) + devDependencies: + '@rsbuild/core': + specifier: ^2.0.9 + version: 2.0.9(@module-federation/runtime-tools@2.5.0)(core-js@3.49.0) + '@rsbuild/plugin-react': + specifier: ^2.0.0 + version: 2.0.1(@rsbuild/core@2.0.9)(@rspack/core@2.0.5) + '@rsbuild/plugin-tailwindcss': + specifier: ^2.0.1 + version: 2.0.1(@rsbuild/core@2.0.9)(webpack@5.102.1) + '@types/node': + specifier: 22.10.2 + version: 22.10.2 + '@types/react': + specifier: ^19.2.2 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.2.2 + version: 19.2.3(@types/react@19.2.15) + nitro: + specifier: npm:nitro-nightly@latest + version: nitro-nightly@3.0.1-20260603-085201-f0cb358c(chokidar@5.0.0)(dotenv@16.4.5)(jiti@2.7.0)(lru-cache@11.3.5)(rollup@4.60.3)(vite@8.0.16) + tailwindcss: + specifier: ^4.1.18 + version: 4.3.0 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + rsbuild/umd: devDependencies: '@rsbuild/core': @@ -833,7 +891,7 @@ importers: version: 8.60.0(eslint@9.39.4)(typescript@5.9.3) eslint: specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-plugin-vue: specifier: ^9.33.0 version: 9.33.0(eslint@9.39.4) @@ -1887,7 +1945,7 @@ importers: version: 2.0.3(@rspack/core@2.0.5) eslint: specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-rspack-plugin: specifier: ^5.0.1 version: 5.0.1(@rspack/core@2.0.5)(eslint@9.39.4) @@ -3284,7 +3342,7 @@ importers: version: 10.1.0 postcss-load-config: specifier: 6.0.1 - version: 6.0.1(jiti@2.6.1)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) + version: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) svelte: specifier: ^5.56.0 version: 5.56.0(@typescript-eslint/types@8.60.0) @@ -4405,10 +4463,6 @@ packages: resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} @@ -6627,6 +6681,22 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@oozcitak/dom@2.0.2': + resolution: {integrity: sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==} + engines: {node: '>=20.0'} + + '@oozcitak/infra@2.0.2': + resolution: {integrity: sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==} + engines: {node: '>=20.0'} + + '@oozcitak/url@3.0.0': + resolution: {integrity: sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==} + engines: {node: '>=20.0'} + + '@oozcitak/util@10.0.0': + resolution: {integrity: sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==} + engines: {node: '>=20.0'} + '@oxc-parser/binding-android-arm-eabi@0.127.0': resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6757,6 +6827,9 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -7055,6 +7128,104 @@ packages: '@types/react': optional: true + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/plugin-babel@6.1.0': resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==} engines: {node: '>=14.0.0'} @@ -8306,6 +8477,161 @@ packages: peerDependencies: webpack: ^5 + '@tanstack/history@1.162.0': + resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==} + engines: {node: '>=20.19'} + + '@tanstack/react-router-devtools@1.167.0': + resolution: {integrity: sha512-nGw095EG7IHx0h5NtlEmzf6vcCTaFNPWdTSuDKazajhN0ct/v/TkekJ9J6KYUCeV1a8/2ZmToc58M+0rrOyn7w==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/react-router': ^1.170.0 + '@tanstack/router-core': ^1.170.0 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + '@tanstack/router-core': + optional: true + + '@tanstack/react-router@1.170.11': + resolution: {integrity: sha512-gP2vzdyaI8Ow/Uz/MRPfK2wN09YwRI0Y/oF74Wuy9R3KmjbfJv2tLrkM+Onu1xWklSn3ugZarMPJXRE0kzrJTA==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-start-client@1.168.8': + resolution: {integrity: sha512-CW2P0riDN+IQCuXx33R1H0ONEW3NespMfb2t6qSesOyuoPjnh4earDKaZsWYEVvewzx8465BOhOmh+nxEJRjJg==} + engines: {node: '>=22.12.0'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-start-rsc@0.1.18': + resolution: {integrity: sha512-pfekO3dvSLacSUW2kUJGnhfdNTo6rgQE6QjQzPaDsjUaNXT4zVWgbaqM0R6kXhwkGA69L1ZbBqtIXBwTQCrJzg==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rspack/core': '>=2.0.0-0' + '@vitejs/plugin-rsc': '>=0.5.20' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + react-server-dom-rspack: '>=0.0.2' + peerDependenciesMeta: + '@rspack/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + react-server-dom-rspack: + optional: true + + '@tanstack/react-start-server@1.167.14': + resolution: {integrity: sha512-Cma1M0ofxPxpmax1aQp6NM38N62MCgfEmto6RqfptZHd5UOlMp1dFf5zsnEukJq6vDVxg4lQyUgE2+qJuo2PmA==} + engines: {node: '>=22.12.0'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-start@1.168.19': + resolution: {integrity: sha512-UGguzD22ZdxZmz/Rcw2My/L40il/S51adm1zARclr7zkhoQfV7WlgBxjskPi5ngiOYAPlI7847Ptz8we5TSM3Q==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rsbuild/core': ^2.0.0 + '@vitejs/plugin-rsc': '*' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + vite: + optional: true + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.171.9': + resolution: {integrity: sha512-QM5ZwLT9c5ZcTJW0QQZRRIBC4qjImUyUCXCVyuYVOF9xr76XLsJSX4F2dOxr9VptAv+W+TkWNOYdX8VaO9kdgA==} + engines: {node: '>=20.19'} + + '@tanstack/router-devtools-core@1.168.0': + resolution: {integrity: sha512-wQoQhlBK7nlZgqzaqdYXKWNTpdHdsaREdaPhFZVH0/Ador+F+eM3/NF2i3f2LPeS0GgKraZUQXe1Q/1+KHyEYg==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/router-core': ^1.170.0 + csstype: ^3.0.10 + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.167.13': + resolution: {integrity: sha512-DldbCjA8S/CXQBuoyQqr76xqZe9k+H1ymV+ugj2IBHFi4yRzx4z4f2nSsPYlLdpXD2Cf/MEjLncaG7ceY5H5ig==} + engines: {node: '>=20.19'} + + '@tanstack/router-plugin@1.168.14': + resolution: {integrity: sha512-z+3vYJ7ouNnMzBIC1hsNWsxaQFu9Gf0WSdE3jBHWa326ipnONqDD5KeCqWGczq0HMdZY4UsDjyfvjucxXhrb0A==} + engines: {node: '>=20.19'} + peerDependencies: + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.170.11 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.162.1': + resolution: {integrity: sha512-62layyTGmclHDQS/eidwKRfN1hhCKwViG7iEBcVmL0MXgcAB3OOucWCEcDDGd9Cu11H6b4QQ5oOo47MWIqwz0A==} + engines: {node: '>=20.19'} + + '@tanstack/start-client-core@1.170.7': + resolution: {integrity: sha512-LKNHeK3n8DZ2ub1KpidWCISvJNq7wGuErrd2oSyoUDHSo90ldl7JJcG4OpbDS7GQjqIZ79M47eklajwgKXBxrQ==} + engines: {node: '>=22.12.0'} + + '@tanstack/start-fn-stubs@1.162.0': + resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} + engines: {node: '>=22.12.0'} + + '@tanstack/start-plugin-core@1.171.11': + resolution: {integrity: sha512-f6z9W8lYveloSLFocMGfUrS4UL2sc0qrJiB0cuhs885W/bRE1iG0Vm9cNhM/khWxrLmWNeN5eelcnfB77QjLJg==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rsbuild/core': ^2.0.0 + vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true + + '@tanstack/start-server-core@1.169.9': + resolution: {integrity: sha512-i2OXl+svinZI+7tE2FTQSc9vLIMp0/3nQAI47zg7cZ/0btmC2g2wVrEUa7pF4bmS2TrKEfmOancbURWfB2YrkA==} + engines: {node: '>=22.12.0'} + + '@tanstack/start-storage-context@1.167.11': + resolution: {integrity: sha512-19wywJH3jiamctg4BxXme0G9iH+P5qHSILxBbyksTK727shDEZPb6V/NzO2dz4cKFAoB6TdBcKBj/guADClOfQ==} + engines: {node: '>=22.12.0'} + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@tanstack/virtual-file-routes@1.162.0': + resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} + engines: {node: '>=20.19'} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -8493,6 +8819,9 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/node@22.10.2': + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} + '@types/node@24.12.4': resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} @@ -8697,6 +9026,17 @@ packages: peerDependencies: vue: ^3.0.0 + '@vitejs/plugin-rsc@0.5.27': + resolution: {integrity: sha512-s1fd5DUkPXk86DDHPM/kP93WrvI0MoA8klxdDZmD1fMSaA9xujfgunsm8ZoUH0FemR+63vNalFsIDR0AJH4ktg==} + peerDependencies: + react: '*' + react-dom: '*' + react-server-dom-webpack: '*' + vite: '*' + peerDependenciesMeta: + react-server-dom-webpack: + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -9026,6 +9366,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -9135,6 +9479,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-dead-code-elimination@1.0.12: + resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + babel-loader@10.1.1: resolution: {integrity: sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==} engines: {node: ^18.20.0 || ^20.10.0 || >=22.0.0} @@ -9608,6 +9955,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -9691,6 +10041,14 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.4.5: + resolution: {integrity: sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA==} + peerDependencies: + srvx: '>=0.11.5' + peerDependenciesMeta: + srvx: + optional: true + crypto-browserify@3.12.1: resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} engines: {node: '>= 0.10'} @@ -9787,6 +10145,29 @@ packages: dayjs@1.11.20: resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + db0@0.3.4: + resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==} + peerDependencies: + '@electric-sql/pglite': '*' + '@libsql/client': '*' + better-sqlite3: '*' + drizzle-orm: '*' + mysql2: '*' + sqlite3: '*' + peerDependenciesMeta: + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + better-sqlite3: + optional: true + drizzle-orm: + optional: true + mysql2: + optional: true + sqlite3: + optional: true + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -9944,6 +10325,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dexie@4.4.3: + resolution: {integrity: sha512-N+3IGQ3HPlyO2YAkntGAwitm42BpBGV86MttzUMiRzWLa4NGh0pltVRcUVF4ybL/OnXjCrr9k7SDPIKkFYP2Lg==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -9951,6 +10335,10 @@ packages: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + diffie-hellman@5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} @@ -10127,6 +10515,21 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + env-runner@0.1.9: + resolution: {integrity: sha512-W9AiZlPx0uXtghAJiTBkeZOgyQdecVvoln3cHoOEZswPq0cVMi+WBhUQjdUn+JcZFAFgOt+i5fcO7C2zniZoCg==} + hasBin: true + peerDependencies: + '@netlify/runtime': ^4.1.23 + '@vercel/queue': ^0.2.0 + miniflare: ^4.20260515.0 + peerDependenciesMeta: + '@netlify/runtime': + optional: true + '@vercel/queue': + optional: true + miniflare: + optional: true + envinfo@7.21.0: resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} engines: {node: '>=4'} @@ -10161,6 +10564,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -10410,6 +10816,9 @@ packages: exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -10462,6 +10871,9 @@ packages: picomatch: optional: true + fetchdts@0.1.7: + resolution: {integrity: sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -10768,6 +11180,11 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + goober@2.1.19: + resolution: {integrity: sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==} + peerDependencies: + csstype: ^3.0.10 + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -10775,6 +11192,26 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + h3@2.0.1-rc.20: + resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + + h3@2.0.1-rc.22: + resolution: {integrity: sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + happy-dom@20.9.0: resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} engines: {node: '>=20.0.0'} @@ -11007,6 +11444,9 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + httpxy@0.5.3: + resolution: {integrity: sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw==} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -11356,6 +11796,10 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbot@5.1.40: + resolution: {integrity: sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -11439,6 +11883,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + js-base64@2.6.4: resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} @@ -12459,6 +12907,40 @@ packages: sass: optional: true + nf3@0.3.17: + resolution: {integrity: sha512-N9zEWySuJFw+gR0lhS5863YsvNeudOdqRyFvNb+jMXbeTJOdrjDqkCpDginIZfUm0LzT1t1nCRiDeqQm/8kirQ==} + + nitro-nightly@3.0.1-20260603-085201-f0cb358c: + resolution: {integrity: sha512-fdZfrIpwlnX9PDgS1dlJQ/7K1ztXu0Clz7rLeoMYXX4oVRNKHSn9RoMRiQT5FseviNniDUv5dOmVIx0fLrxafw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@vercel/queue': ^0.2.0 + dotenv: '*' + giget: '*' + jiti: ^2.6.1 + rollup: ^4.60.3 + vite: ^7 || ^8 + xml2js: ^0.6.2 + zephyr-agent: ^0.2.0 + peerDependenciesMeta: + '@vercel/queue': + optional: true + dotenv: + optional: true + giget: + optional: true + jiti: + optional: true + rollup: + optional: true + vite: + optional: true + xml2js: + optional: true + zephyr-agent: + optional: true + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -12567,9 +13049,18 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ocache@0.1.4: + resolution: {integrity: sha512-e7geNdWjxSnvsSgvLuPvgKgu7ubM10ZmTPOgpr7mz2BXYtvjMKTiLhjFi/gWU8chkuP6hNkZBsa9LzOusyaqkQ==} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + ofetch@2.0.0-alpha.3: + resolution: {integrity: sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -13523,11 +14014,19 @@ packages: ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.60.3: resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.8.1: + resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -13868,10 +14367,20 @@ packages: peerDependencies: seroval: ^1.0 + seroval-plugins@1.5.4: + resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + seroval@1.5.2: resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} engines: {node: '>=10'} + seroval@1.5.4: + resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} + engines: {node: '>=10'} + serve-handler@6.1.7: resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} @@ -14671,6 +15180,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -14799,6 +15312,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turbo-stream@3.2.0: + resolution: {integrity: sha512-EK+bZ9UVrVh7JLslVFOV0GEMsociOqVOvEMTAd4ixMyffN5YNIEdLZWXUx5PJqDbTxSIBWw04HS9gCY4frYQDQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -14880,6 +15396,9 @@ packages: unconfig@7.5.0: resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -14887,6 +15406,9 @@ packages: resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + unhead@2.1.15: resolution: {integrity: sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==} @@ -15031,10 +15553,84 @@ packages: unraw@3.0.0: resolution: {integrity: sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==} - upath@1.2.0: - resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} - engines: {node: '>=4'} - + unstorage@2.0.0-alpha.7: + resolution: {integrity: sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog==} + peerDependencies: + '@azure/app-configuration': ^1.11.0 + '@azure/cosmos': ^4.9.1 + '@azure/data-tables': ^13.3.2 + '@azure/identity': ^4.13.0 + '@azure/keyvault-secrets': ^4.10.0 + '@azure/storage-blob': ^12.31.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.13.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.36.2 + '@vercel/blob': '>=0.27.3' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + chokidar: ^4 || ^5 + db0: '>=0.3.4' + idb-keyval: ^6.2.2 + ioredis: ^5.9.3 + lru-cache: ^11.2.6 + mongodb: ^6 || ^7 + ofetch: '*' + uploadthing: ^7.7.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + chokidar: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + lru-cache: + optional: true + mongodb: + optional: true + ofetch: + optional: true + uploadthing: + optional: true + + upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + upath@2.0.1: resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} engines: {node: '>=4'} @@ -15178,6 +15774,57 @@ packages: yaml: optional: true + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} @@ -15527,6 +16174,10 @@ packages: xml2js@0.4.17: resolution: {integrity: sha512-1O7wk/NTQN0UEOItIYTxK4qP4sMUVU60MbF4Nj0a8jd6eebMXOicVI/KFOEsYKKH4uBpx6XG9ZGZctXK5rtO5Q==} + xmlbuilder2@4.0.3: + resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==} + engines: {node: '>=20.0'} + xmlbuilder@4.2.1: resolution: {integrity: sha512-oEePiEefhQhAeUnwRnIBLBWmk/fsWWbQ53EEWsRuzECbQ3m5o/Esmq6H47CYYwSLW+Ynt0rS9hd0pd2ogMAWjg==} engines: {node: '>=0.8.0'} @@ -15600,8 +16251,26 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.1.12: - resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zustand@5.0.14: + resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -15702,7 +16371,7 @@ snapshots: '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -16050,8 +16719,6 @@ snapshots: '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.27.1': {} - '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-identifier@7.29.7': {} @@ -17831,12 +18498,12 @@ snapshots: '@eslint-community/eslint-utils@4.8.0(eslint@9.39.4)': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -18724,6 +19391,23 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@oozcitak/dom@2.0.2': + dependencies: + '@oozcitak/infra': 2.0.2 + '@oozcitak/url': 3.0.0 + '@oozcitak/util': 10.0.0 + + '@oozcitak/infra@2.0.2': + dependencies: + '@oozcitak/util': 10.0.0 + + '@oozcitak/url@3.0.0': + dependencies: + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 + + '@oozcitak/util@10.0.0': {} + '@oxc-parser/binding-android-arm-eabi@0.127.0': optional: true @@ -18790,6 +19474,8 @@ snapshots: '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.133.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -19037,6 +19723,57 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@rollup/plugin-babel@6.1.0(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@4.60.3)': dependencies: '@babel/core': 7.29.7 @@ -19203,7 +19940,7 @@ snapshots: '@rsbuild/plugin-eslint@2.0.1(@rsbuild/core@2.0.9)(@rspack/core@2.0.5)(eslint@9.39.4)': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-rspack-plugin: 5.0.0(@rspack/core@2.0.5)(eslint@9.39.4) optionalDependencies: '@rsbuild/core': 2.0.9(@module-federation/runtime-tools@2.5.0)(core-js@3.49.0) @@ -19448,7 +20185,7 @@ snapshots: - '@typescript/native-preview' - core-js - '@rslint/core@0.5.3(jiti@2.6.1)': + '@rslint/core@0.5.3(jiti@2.7.0)': dependencies: picomatch: 4.0.4 tinyglobby: 0.2.15 @@ -19459,7 +20196,7 @@ snapshots: '@rslint/linux-x64': 0.5.3 '@rslint/win32-arm64': 0.5.3 '@rslint/win32-x64': 0.5.3 - jiti: 2.6.1 + jiti: 2.7.0 '@rslint/darwin-arm64@0.5.3': optional: true @@ -19980,7 +20717,7 @@ snapshots: optionalDependencies: esbuild: 0.28.0 rollup: 4.60.3 - vite: 7.1.1(@types/node@24.12.4)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + vite: 7.1.1(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) webpack: 5.102.1(esbuild@0.28.0)(lightningcss@1.32.0) '@storybook/global@5.0.0': {} @@ -20333,6 +21070,234 @@ snapshots: tailwindcss: 4.3.0 webpack: 5.102.1 + '@tanstack/history@1.162.0': {} + + '@tanstack/react-router-devtools@1.167.0(@tanstack/react-router@1.170.11)(@tanstack/router-core@1.171.9)(csstype@3.2.3)(react-dom@19.2.6)(react@19.2.6)': + dependencies: + '@tanstack/react-router': 1.170.11(react-dom@19.2.6)(react@19.2.6) + '@tanstack/router-devtools-core': 1.168.0(@tanstack/router-core@1.171.9)(csstype@3.2.3) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@tanstack/router-core': 1.171.9 + transitivePeerDependencies: + - csstype + + '@tanstack/react-router@1.170.11(react-dom@19.2.6)(react@19.2.6)': + dependencies: + '@tanstack/history': 1.162.0 + '@tanstack/react-store': 0.9.3(react-dom@19.2.6)(react@19.2.6) + '@tanstack/router-core': 1.171.9 + isbot: 5.1.40 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@tanstack/react-start-client@1.168.8(react-dom@19.2.6)(react@19.2.6)': + dependencies: + '@tanstack/react-router': 1.170.11(react-dom@19.2.6)(react@19.2.6) + '@tanstack/router-core': 1.171.9 + '@tanstack/start-client-core': 1.170.7 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@tanstack/react-start-rsc@0.1.18(@rsbuild/core@2.0.9)(@rspack/core@2.0.5)(@vitejs/plugin-rsc@0.5.27)(crossws@0.4.5)(react-dom@19.2.6)(react-server-dom-rspack@0.0.2)(react@19.2.6)(vite@8.0.16)(webpack@5.102.1)': + dependencies: + '@tanstack/react-router': 1.170.11(react-dom@19.2.6)(react@19.2.6) + '@tanstack/react-start-server': 1.167.14(crossws@0.4.5)(react-dom@19.2.6)(react@19.2.6) + '@tanstack/router-core': 1.171.9 + '@tanstack/router-utils': 1.162.1 + '@tanstack/start-client-core': 1.170.7 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-plugin-core': 1.171.11(@rsbuild/core@2.0.9)(@tanstack/react-router@1.170.11)(crossws@0.4.5)(vite@8.0.16)(webpack@5.102.1) + '@tanstack/start-server-core': 1.169.9(crossws@0.4.5) + '@tanstack/start-storage-context': 1.167.11 + pathe: 2.0.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@rspack/core': 2.0.5(@module-federation/runtime-tools@2.5.0)(@swc/helpers@0.5.23) + '@vitejs/plugin-rsc': 0.5.27(react-dom@19.2.6)(react@19.2.6)(vite@8.0.16) + react-server-dom-rspack: 0.0.2(@rspack/core@2.0.5)(react-dom@19.2.6)(react@19.2.6) + transitivePeerDependencies: + - '@rsbuild/core' + - crossws + - supports-color + - vite + - vite-plugin-solid + - webpack + + '@tanstack/react-start-server@1.167.14(crossws@0.4.5)(react-dom@19.2.6)(react@19.2.6)': + dependencies: + '@tanstack/history': 1.162.0 + '@tanstack/react-router': 1.170.11(react-dom@19.2.6)(react@19.2.6) + '@tanstack/router-core': 1.171.9 + '@tanstack/start-client-core': 1.170.7 + '@tanstack/start-server-core': 1.169.9(crossws@0.4.5) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + transitivePeerDependencies: + - crossws + + '@tanstack/react-start@1.168.19(@rsbuild/core@2.0.9)(@rspack/core@2.0.5)(@vitejs/plugin-rsc@0.5.27)(crossws@0.4.5)(react-dom@19.2.6)(react-server-dom-rspack@0.0.2)(react@19.2.6)(vite@8.0.16)(webpack@5.102.1)': + dependencies: + '@tanstack/react-router': 1.170.11(react-dom@19.2.6)(react@19.2.6) + '@tanstack/react-start-client': 1.168.8(react-dom@19.2.6)(react@19.2.6) + '@tanstack/react-start-rsc': 0.1.18(@rsbuild/core@2.0.9)(@rspack/core@2.0.5)(@vitejs/plugin-rsc@0.5.27)(crossws@0.4.5)(react-dom@19.2.6)(react-server-dom-rspack@0.0.2)(react@19.2.6)(vite@8.0.16)(webpack@5.102.1) + '@tanstack/react-start-server': 1.167.14(crossws@0.4.5)(react-dom@19.2.6)(react@19.2.6) + '@tanstack/router-utils': 1.162.1 + '@tanstack/start-client-core': 1.170.7 + '@tanstack/start-plugin-core': 1.171.11(@rsbuild/core@2.0.9)(@tanstack/react-router@1.170.11)(crossws@0.4.5)(vite@8.0.16)(webpack@5.102.1) + '@tanstack/start-server-core': 1.169.9(crossws@0.4.5) + pathe: 2.0.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@rsbuild/core': 2.0.9(@module-federation/runtime-tools@2.5.0)(core-js@3.49.0) + '@vitejs/plugin-rsc': 0.5.27(react-dom@19.2.6)(react@19.2.6)(vite@8.0.16) + vite: 8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + transitivePeerDependencies: + - '@rspack/core' + - crossws + - react-server-dom-rspack + - supports-color + - vite-plugin-solid + - webpack + + '@tanstack/react-store@0.9.3(react-dom@19.2.6)(react@19.2.6)': + dependencies: + '@tanstack/store': 0.9.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + use-sync-external-store: 1.6.0(react@19.2.6) + + '@tanstack/router-core@1.171.9': + dependencies: + '@tanstack/history': 1.162.0 + cookie-es: 3.1.1 + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) + + '@tanstack/router-devtools-core@1.168.0(@tanstack/router-core@1.171.9)(csstype@3.2.3)': + dependencies: + '@tanstack/router-core': 1.171.9 + clsx: 2.1.1 + goober: 2.1.19(csstype@3.2.3) + optionalDependencies: + csstype: 3.2.3 + + '@tanstack/router-generator@1.167.13': + dependencies: + '@babel/types': 7.29.7 + '@tanstack/router-core': 1.171.9 + '@tanstack/router-utils': 1.162.1 + '@tanstack/virtual-file-routes': 1.162.0 + jiti: 2.7.0 + magic-string: 0.30.21 + prettier: 3.8.3 + zod: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.168.14(@rsbuild/core@2.0.9)(@tanstack/react-router@1.170.11)(vite@8.0.16)(webpack@5.102.1)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@tanstack/router-core': 1.171.9 + '@tanstack/router-generator': 1.167.13 + '@tanstack/router-utils': 1.162.1 + '@tanstack/virtual-file-routes': 1.162.0 + chokidar: 5.0.0 + unplugin: 3.0.0 + zod: 4.4.3 + optionalDependencies: + '@rsbuild/core': 2.0.9(@module-federation/runtime-tools@2.5.0)(core-js@3.49.0) + '@tanstack/react-router': 1.170.11(react-dom@19.2.6)(react@19.2.6) + vite: 8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + webpack: 5.102.1(esbuild@0.28.0) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.162.1': + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + ansis: 4.3.1 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.4 + pathe: 2.0.3 + tinyglobby: 0.2.17 + transitivePeerDependencies: + - supports-color + + '@tanstack/start-client-core@1.170.7': + dependencies: + '@tanstack/router-core': 1.171.9 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-storage-context': 1.167.11 + seroval: 1.5.4 + + '@tanstack/start-fn-stubs@1.162.0': {} + + '@tanstack/start-plugin-core@1.171.11(@rsbuild/core@2.0.9)(@tanstack/react-router@1.170.11)(crossws@0.4.5)(vite@8.0.16)(webpack@5.102.1)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.29.7 + '@babel/types': 7.29.7 + '@rolldown/pluginutils': 1.0.1 + '@tanstack/router-core': 1.171.9 + '@tanstack/router-generator': 1.167.13 + '@tanstack/router-plugin': 1.168.14(@rsbuild/core@2.0.9)(@tanstack/react-router@1.170.11)(vite@8.0.16)(webpack@5.102.1) + '@tanstack/router-utils': 1.162.1 + '@tanstack/start-client-core': 1.170.7 + '@tanstack/start-server-core': 1.169.9(crossws@0.4.5) + exsolve: 1.0.8 + lightningcss: 1.32.0 + pathe: 2.0.3 + picomatch: 4.0.4 + seroval: 1.5.4 + source-map: 0.7.6 + srvx: 0.11.16 + tinyglobby: 0.2.17 + ufo: 1.6.3 + vitefu: 1.1.3(vite@8.0.16) + xmlbuilder2: 4.0.3 + zod: 4.4.3 + optionalDependencies: + '@rsbuild/core': 2.0.9(@module-federation/runtime-tools@2.5.0)(core-js@3.49.0) + vite: 8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + transitivePeerDependencies: + - '@tanstack/react-router' + - crossws + - supports-color + - vite-plugin-solid + - webpack + + '@tanstack/start-server-core@1.169.9(crossws@0.4.5)': + dependencies: + '@tanstack/history': 1.162.0 + '@tanstack/router-core': 1.171.9 + '@tanstack/start-client-core': 1.170.7 + '@tanstack/start-storage-context': 1.167.11 + fetchdts: 0.1.7 + h3-v2: h3@2.0.1-rc.20(crossws@0.4.5) + seroval: 1.5.4 + transitivePeerDependencies: + - crossws + + '@tanstack/start-storage-context@1.167.11': + dependencies: + '@tanstack/router-core': 1.171.9 + + '@tanstack/store@0.9.3': {} + + '@tanstack/virtual-file-routes@1.162.0': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -20561,6 +21526,10 @@ snapshots: '@types/ms@0.7.34': {} + '@types/node@22.10.2': + dependencies: + undici-types: 6.20.0 + '@types/node@24.12.4': dependencies: undici-types: 7.16.0 @@ -20639,7 +21608,7 @@ snapshots: '@typescript-eslint/type-utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.60.0 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -20654,7 +21623,7 @@ snapshots: '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.60.0 debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -20683,7 +21652,7 @@ snapshots: '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -20712,7 +21681,7 @@ snapshots: '@typescript-eslint/scope-manager': 8.60.0 '@typescript-eslint/types': 8.60.0 '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -20845,6 +21814,21 @@ snapshots: dependencies: vue: 3.5.35(typescript@5.9.3) + '@vitejs/plugin-rsc@0.5.27(react-dom@19.2.6)(react@19.2.6)(vite@8.0.16)': + dependencies: + '@rolldown/pluginutils': 1.0.1 + es-module-lexer: 2.1.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + srvx: 0.11.16 + strip-literal: 3.1.0 + turbo-stream: 3.2.0 + vite: 8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.0.16) + optional: true + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -20859,7 +21843,7 @@ snapshots: estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.1.1(@types/node@24.12.4)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + vite: 7.1.1(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -21249,6 +22233,8 @@ snapshots: ansi-styles@6.2.1: {} + ansis@4.3.1: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -21356,6 +22342,15 @@ snapshots: axobject-query@4.1.0: {} + babel-dead-code-elimination@1.0.12: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + babel-loader@10.1.1(@babel/core@7.29.7)(@rspack/core@2.0.5)(webpack@5.102.1): dependencies: '@babel/core': 7.29.7 @@ -21873,6 +22868,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@3.1.1: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -21967,6 +22964,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.4.5(srvx@0.11.16): + optionalDependencies: + srvx: 0.11.16 + crypto-browserify@3.12.1: dependencies: browserify-cipher: 1.0.1 @@ -21999,7 +23000,7 @@ snapshots: semver: 7.7.4 optionalDependencies: '@rspack/core': 2.0.5(@module-federation/runtime-tools@2.5.0)(@swc/helpers@0.5.23) - webpack: 5.102.1 + webpack: 5.102.1(postcss@8.5.10) css-mediaquery@0.1.2: {} @@ -22094,6 +23095,8 @@ snapshots: dayjs@1.11.20: {} + db0@0.3.4: {} + de-indent@1.0.2: optional: true @@ -22220,10 +23223,14 @@ snapshots: dependencies: dequal: 2.0.3 + dexie@4.4.3: {} + didyoumean@1.2.2: {} diff@5.2.0: {} + diff@8.0.4: {} + diffie-hellman@5.0.3: dependencies: bn.js: 4.12.0 @@ -22477,6 +23484,13 @@ snapshots: env-paths@2.2.1: {} + env-runner@0.1.9: + dependencies: + crossws: 0.4.5(srvx@0.11.16) + exsolve: 1.0.8 + httpxy: 0.5.3 + srvx: 0.11.16 + envinfo@7.21.0: {} environment@1.1.0: {} @@ -22565,6 +23579,9 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.1.0: + optional: true + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -22728,17 +23745,17 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.28.5 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) hermes-parser: 0.25.1 - zod: 4.1.12 - zod-validation-error: 4.0.2(zod@4.1.12) + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color eslint-plugin-vue@9.33.0(eslint@9.39.4): dependencies: '@eslint-community/eslint-utils': 4.8.0(eslint@9.39.4) - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 @@ -22751,7 +23768,7 @@ snapshots: eslint-rspack-plugin@5.0.0(@rspack/core@2.0.5)(eslint@9.39.4): dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) micromatch: 4.0.8 normalize-path: 3.0.0 tinyglobby: 0.2.16 @@ -22760,7 +23777,7 @@ snapshots: eslint-rspack-plugin@5.0.1(@rspack/core@2.0.5)(eslint@9.39.4): dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) micromatch: 4.0.8 normalize-path: 3.0.0 tinyglobby: 0.2.16 @@ -22788,7 +23805,7 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.6.1): + eslint@9.39.4(jiti@2.7.0): dependencies: '@eslint-community/eslint-utils': 4.8.0(eslint@9.39.4) '@eslint-community/regexpp': 4.12.1 @@ -22825,7 +23842,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.3 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -23023,6 +24040,8 @@ snapshots: exsolve@1.0.7: {} + exsolve@1.0.8: {} + extend@3.0.2: {} fast-decode-uri-component@1.0.1: {} @@ -23088,6 +24107,8 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fetchdts@0.1.7: {} + fflate@0.8.2: {} figures@6.1.0: @@ -23428,10 +24449,28 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + goober@2.1.19(csstype@3.2.3): + dependencies: + csstype: 3.2.3 + gopd@1.2.0: {} graceful-fs@4.2.11: {} + h3@2.0.1-rc.20(crossws@0.4.5): + dependencies: + rou3: 0.8.1 + srvx: 0.11.16 + optionalDependencies: + crossws: 0.4.5(srvx@0.11.16) + + h3@2.0.1-rc.22(crossws@0.4.5): + dependencies: + rou3: 0.8.1 + srvx: 0.11.16 + optionalDependencies: + crossws: 0.4.5(srvx@0.11.16) + happy-dom@20.9.0: dependencies: '@types/node': 24.12.4 @@ -23839,6 +24878,8 @@ snapshots: transitivePeerDependencies: - supports-color + httpxy@0.5.3: {} + human-signals@2.1.0: {} human-signals@8.0.1: {} @@ -24129,6 +25170,8 @@ snapshots: isarray@2.0.5: {} + isbot@5.1.40: {} + isexe@2.0.0: {} isobject@3.0.1: {} @@ -24210,6 +25253,8 @@ snapshots: jiti@2.6.1: {} + jiti@2.7.0: {} + js-base64@2.6.4: {} js-beautify@1.15.4: @@ -25658,6 +26703,60 @@ snapshots: - '@babel/core' - babel-plugin-macros + nf3@0.3.17: {} + + nitro-nightly@3.0.1-20260603-085201-f0cb358c(chokidar@5.0.0)(dotenv@16.4.5)(jiti@2.7.0)(lru-cache@11.3.5)(rollup@4.60.3)(vite@8.0.16): + dependencies: + consola: 3.4.2 + crossws: 0.4.5(srvx@0.11.16) + db0: 0.3.4 + env-runner: 0.1.9 + h3: 2.0.1-rc.22(crossws@0.4.5) + hookable: 6.1.1 + nf3: 0.3.17 + ocache: 0.1.4 + ofetch: 2.0.0-alpha.3 + ohash: 2.0.11 + rolldown: 1.0.3 + srvx: 0.11.16 + unenv: 2.0.0-rc.24 + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(lru-cache@11.3.5)(ofetch@2.0.0-alpha.3) + optionalDependencies: + dotenv: 16.4.5 + jiti: 2.7.0 + rollup: 4.60.3 + vite: 8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@netlify/runtime' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - better-sqlite3 + - chokidar + - drizzle-orm + - idb-keyval + - ioredis + - lru-cache + - miniflare + - mongodb + - mysql2 + - sqlite3 + - uploadthing + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -25786,12 +26885,20 @@ snapshots: obug@2.1.1: {} + ocache@0.1.4: + dependencies: + ohash: 2.0.11 + ofetch@1.5.1: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 ufo: 1.6.3 + ofetch@2.0.0-alpha.3: {} + + ohash@2.0.11: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: @@ -26200,11 +27307,11 @@ snapshots: tsx: 4.19.2 yaml: 2.9.0 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0): + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 postcss: 8.5.15 tsx: 4.19.2 yaml: 2.9.0 @@ -26966,6 +28073,27 @@ snapshots: hash-base: 3.1.0 inherits: 2.0.4 + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + rollup@4.60.3: dependencies: '@types/estree': 1.0.8 @@ -26997,6 +28125,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 + rou3@0.8.1: {} + router@2.2.0: dependencies: debug: 4.4.3 @@ -27278,8 +28408,14 @@ snapshots: dependencies: seroval: 1.5.2 + seroval-plugins@1.5.4(seroval@1.5.4): + dependencies: + seroval: 1.5.4 + seroval@1.5.2: {} + seroval@1.5.4: {} + serve-handler@6.1.7: dependencies: bytes: 3.0.0 @@ -27974,7 +29110,7 @@ snapshots: '@babel/core': 7.29.7 less: 4.6.4 postcss: 8.5.15 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) pug: 3.0.2 sass: 1.100.0 stylus: 0.64.0 @@ -27987,7 +29123,7 @@ snapshots: '@babel/core': 7.29.7 less: 4.6.4 postcss: 8.5.15 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.19.2)(yaml@2.9.0) pug: 3.0.2 sass: 1.100.0 stylus: 0.64.0 @@ -28167,6 +29303,17 @@ snapshots: esbuild: 0.28.0 lightningcss: 1.32.0 + terser-webpack-plugin@5.6.1(esbuild@0.28.0)(webpack@5.102.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.37.0 + webpack: 5.102.1(esbuild@0.28.0) + optionalDependencies: + esbuild: 0.28.0 + optional: true + terser-webpack-plugin@5.6.1(lightningcss@1.32.0)(webpack@5.102.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -28177,6 +29324,17 @@ snapshots: optionalDependencies: lightningcss: 1.32.0 + terser-webpack-plugin@5.6.1(postcss@8.5.10)(webpack@5.102.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.37.0 + webpack: 5.102.1(postcss@8.5.10) + optionalDependencies: + postcss: 8.5.10 + optional: true + terser-webpack-plugin@5.6.1(postcss@8.5.15)(webpack@5.102.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -28246,6 +29404,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyrainbow@2.0.0: {} tinyspy@4.0.3: {} @@ -28358,6 +29521,9 @@ snapshots: safe-buffer: 5.2.1 optional: true + turbo-stream@3.2.0: + optional: true + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -28450,10 +29616,16 @@ snapshots: quansync: 1.0.0 unconfig-core: 7.5.0 + undici-types@6.20.0: {} + undici-types@7.16.0: {} undici@7.24.7: {} + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + unhead@2.1.15: dependencies: hookable: 6.1.1 @@ -28651,6 +29823,13 @@ snapshots: unraw@3.0.0: {} + unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4)(lru-cache@11.3.5)(ofetch@2.0.0-alpha.3): + optionalDependencies: + chokidar: 5.0.0 + db0: 0.3.4 + lru-cache: 11.3.5 + ofetch: 2.0.0-alpha.3 + upath@1.2.0: {} upath@2.0.1: {} @@ -28783,18 +29962,18 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite@7.1.1(@types/node@24.12.4)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0): + vite@7.1.1(@types/node@24.12.4)(jiti@2.7.0)(less@4.6.4)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 postcss: 8.5.15 rollup: 4.60.3 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.12.4 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 less: 4.6.4 lightningcss: 1.32.0 sass: 1.100.0 @@ -28805,6 +29984,31 @@ snapshots: yaml: 2.9.0 optional: true + vite@8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.10.2 + esbuild: 0.28.0 + fsevents: 2.3.3 + jiti: 2.7.0 + less: 4.6.4 + sass: 1.100.0 + sass-embedded: 1.100.0 + stylus: 0.64.0 + terser: 5.37.0 + tsx: 4.19.2 + yaml: 2.9.0 + optional: true + + vitefu@1.1.3(vite@8.0.16): + optionalDependencies: + vite: 8.0.16(@types/node@22.10.2)(esbuild@0.28.0)(jiti@2.7.0)(less@4.6.4)(sass-embedded@1.100.0)(sass@1.100.0)(stylus@0.64.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.9.0) + vm-browserify@1.1.2: {} void-elements@3.1.0: {} @@ -28845,7 +30049,7 @@ snapshots: vue-eslint-parser@9.4.3(eslint@9.39.4): dependencies: debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 @@ -28989,6 +30193,48 @@ snapshots: - postcss - uglify-js + webpack@5.102.1(esbuild@0.28.0): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.21.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.2 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.1(esbuild@0.28.0)(webpack@5.102.1) + watchpack: 2.5.1 + webpack-sources: 3.4.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + optional: true + webpack@5.102.1(esbuild@0.28.0)(lightningcss@1.32.0): dependencies: '@types/eslint-scope': 3.7.7 @@ -29071,6 +30317,48 @@ snapshots: - postcss - uglify-js + webpack@5.102.1(postcss@8.5.10): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.21.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.2 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.1(postcss@8.5.10)(webpack@5.102.1) + watchpack: 2.5.1 + webpack-sources: 3.4.1 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + optional: true + webpack@5.102.1(postcss@8.5.15): dependencies: '@types/eslint-scope': 3.7.7 @@ -29382,6 +30670,13 @@ snapshots: sax: 1.3.0 xmlbuilder: 4.2.1 + xmlbuilder2@4.0.3: + dependencies: + '@oozcitak/dom': 2.0.2 + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 + js-yaml: 4.1.1 + xmlbuilder@4.2.1: dependencies: lodash: 4.18.1 @@ -29429,10 +30724,16 @@ snapshots: zimmerframe@1.1.4: {} - zod-validation-error@4.0.2(zod@4.1.12): + zod-validation-error@4.0.2(zod@4.4.3): dependencies: - zod: 4.1.12 + zod: 4.4.3 + + zod@4.4.3: {} - zod@4.1.12: {} + zustand@5.0.14(@types/react@19.2.15)(react@19.2.6)(use-sync-external-store@1.6.0): + optionalDependencies: + '@types/react': 19.2.15 + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) zwitch@2.0.4: {} diff --git a/rsbuild/tanstack-start/.gitignore b/rsbuild/tanstack-start/.gitignore new file mode 100644 index 00000000..6ab0517d --- /dev/null +++ b/rsbuild/tanstack-start/.gitignore @@ -0,0 +1,20 @@ +node_modules +package-lock.json +yarn.lock + +.DS_Store +.cache +.env +.vercel +.output +.nitro +/build/ +/api/ +/server/build +/public/build# Sentry Config File +.env.sentry-build-plugin +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +.tanstack \ No newline at end of file diff --git a/rsbuild/tanstack-start/README.md b/rsbuild/tanstack-start/README.md new file mode 100644 index 00000000..e47983c5 --- /dev/null +++ b/rsbuild/tanstack-start/README.md @@ -0,0 +1,36 @@ +# TanStack Start - Basic Example + +This is the basic TanStack Start example, demonstrating the fundamentals of building applications with TanStack Router and TanStack Start. + +- [TanStack Router Docs](https://tanstack.com/router) + +It's deployed automagically with Netlify! + +- [Netlify](https://netlify.com/) + +## Start a new project based on this example + +To start a new project based on this example, run: + +```sh +npx gitpick TanStack/router/tree/main/examples/react/start-basic start-basic +``` + +## Getting Started + +From your terminal: + +```sh +pnpm install +pnpm dev +``` + +This starts your app in development mode, rebuilding assets on file changes. + +## Build + +To build the app for production: + +```sh +pnpm build +``` diff --git a/rsbuild/tanstack-start/package.json b/rsbuild/tanstack-start/package.json new file mode 100644 index 00000000..bc5b73ea --- /dev/null +++ b/rsbuild/tanstack-start/package.json @@ -0,0 +1,33 @@ +{ + "name": "tanstack-start-example-rscs", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "rsbuild dev", + "build": "rsbuild build && tsc --noEmit", + "start": "node server.js" + }, + "dependencies": { + "@tanstack/react-router": "^1.170.11", + "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-start": "^1.168.19", + "dexie": "^4.0.10", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3", + "zustand": "^5.0.10" + }, + "devDependencies": { + "@rsbuild/core": "^2.0.9", + "@rsbuild/plugin-react": "^2.0.0", + "@rsbuild/plugin-tailwindcss": "^2.0.1", + "@types/node": "22.10.2", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "nitro": "npm:nitro-nightly@latest", + "tailwindcss": "^4.1.18", + "typescript": "^5.7.2" + } +} diff --git a/rsbuild/tanstack-start/public/android-chrome-192x192.png b/rsbuild/tanstack-start/public/android-chrome-192x192.png new file mode 100644 index 00000000..09c8324f Binary files /dev/null and b/rsbuild/tanstack-start/public/android-chrome-192x192.png differ diff --git a/rsbuild/tanstack-start/public/android-chrome-512x512.png b/rsbuild/tanstack-start/public/android-chrome-512x512.png new file mode 100644 index 00000000..11d626ea Binary files /dev/null and b/rsbuild/tanstack-start/public/android-chrome-512x512.png differ diff --git a/rsbuild/tanstack-start/public/apple-touch-icon.png b/rsbuild/tanstack-start/public/apple-touch-icon.png new file mode 100644 index 00000000..5a9423cc Binary files /dev/null and b/rsbuild/tanstack-start/public/apple-touch-icon.png differ diff --git a/rsbuild/tanstack-start/public/example-guitar-flowers.jpg b/rsbuild/tanstack-start/public/example-guitar-flowers.jpg new file mode 100644 index 00000000..debe785e Binary files /dev/null and b/rsbuild/tanstack-start/public/example-guitar-flowers.jpg differ diff --git a/rsbuild/tanstack-start/public/example-guitar-motherboard.jpg b/rsbuild/tanstack-start/public/example-guitar-motherboard.jpg new file mode 100644 index 00000000..8f1a8d72 Binary files /dev/null and b/rsbuild/tanstack-start/public/example-guitar-motherboard.jpg differ diff --git a/rsbuild/tanstack-start/public/example-guitar-racing.jpg b/rsbuild/tanstack-start/public/example-guitar-racing.jpg new file mode 100644 index 00000000..44555574 Binary files /dev/null and b/rsbuild/tanstack-start/public/example-guitar-racing.jpg differ diff --git a/rsbuild/tanstack-start/public/example-guitar-steamer-trunk.jpg b/rsbuild/tanstack-start/public/example-guitar-steamer-trunk.jpg new file mode 100644 index 00000000..7b931893 Binary files /dev/null and b/rsbuild/tanstack-start/public/example-guitar-steamer-trunk.jpg differ diff --git a/rsbuild/tanstack-start/public/example-guitar-superhero.jpg b/rsbuild/tanstack-start/public/example-guitar-superhero.jpg new file mode 100644 index 00000000..3bbea274 Binary files /dev/null and b/rsbuild/tanstack-start/public/example-guitar-superhero.jpg differ diff --git a/rsbuild/tanstack-start/public/example-ukelele-tanstack.jpg b/rsbuild/tanstack-start/public/example-ukelele-tanstack.jpg new file mode 100644 index 00000000..1e8a5566 Binary files /dev/null and b/rsbuild/tanstack-start/public/example-ukelele-tanstack.jpg differ diff --git a/rsbuild/tanstack-start/public/favicon-16x16.png b/rsbuild/tanstack-start/public/favicon-16x16.png new file mode 100644 index 00000000..e3389b00 Binary files /dev/null and b/rsbuild/tanstack-start/public/favicon-16x16.png differ diff --git a/rsbuild/tanstack-start/public/favicon-32x32.png b/rsbuild/tanstack-start/public/favicon-32x32.png new file mode 100644 index 00000000..900c77d4 Binary files /dev/null and b/rsbuild/tanstack-start/public/favicon-32x32.png differ diff --git a/rsbuild/tanstack-start/public/favicon.ico b/rsbuild/tanstack-start/public/favicon.ico new file mode 100644 index 00000000..1a175167 Binary files /dev/null and b/rsbuild/tanstack-start/public/favicon.ico differ diff --git a/rsbuild/tanstack-start/public/favicon.png b/rsbuild/tanstack-start/public/favicon.png new file mode 100644 index 00000000..1e77bc06 Binary files /dev/null and b/rsbuild/tanstack-start/public/favicon.png differ diff --git a/rsbuild/tanstack-start/public/site.webmanifest b/rsbuild/tanstack-start/public/site.webmanifest new file mode 100644 index 00000000..fa99de77 --- /dev/null +++ b/rsbuild/tanstack-start/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/rsbuild/tanstack-start/rsbuild.config.ts b/rsbuild/tanstack-start/rsbuild.config.ts new file mode 100644 index 00000000..b7124fa5 --- /dev/null +++ b/rsbuild/tanstack-start/rsbuild.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginTailwindcss } from '@rsbuild/plugin-tailwindcss'; +import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild'; + +export default defineConfig({ + mode: 'development', + source: { + include: [/[\\/]node_modules[\\/]/], + }, + server: { + port: 3000, + }, + plugins: [ + pluginReact(), + pluginTailwindcss(), + tanstackStart({ + rsc: { + enabled: true, + }, + }), + ], +}); diff --git a/rsbuild/tanstack-start/server.js b/rsbuild/tanstack-start/server.js new file mode 100644 index 00000000..abbb4769 --- /dev/null +++ b/rsbuild/tanstack-start/server.js @@ -0,0 +1,54 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; +import { pathToFileURL } from 'node:url'; + +const distDir = process.env.E2E_DIST_DIR || 'dist'; + +function resolveDistClientDir() { + return path.resolve(distDir, 'client'); +} + +function resolveDistServerEntryPath() { + const serverJsPath = path.resolve(distDir, 'server', 'server.js'); + if (fs.existsSync(serverJsPath)) { + return serverJsPath; + } + + const indexJsPath = path.resolve(distDir, 'server', 'index.js'); + if (fs.existsSync(indexJsPath)) { + return indexJsPath; + } + + return serverJsPath; +} + +export function resolveStartCommand() { + const distClientDir = resolveDistClientDir(); + const distServerEntryPath = resolveDistServerEntryPath(); + return `srvx --prod -s ${JSON.stringify(distClientDir)} ${JSON.stringify(distServerEntryPath)}`; +} + +export function start() { + const child = spawn( + 'srvx', + ['--prod', '-s', resolveDistClientDir(), resolveDistServerEntryPath()], + { + stdio: 'inherit', + shell: process.platform === 'win32', + }, + ); + + child.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + return; + } + + process.exit(code ?? 0); + }); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + start(); +} diff --git a/rsbuild/tanstack-start/src/components/Layout.tsx b/rsbuild/tanstack-start/src/components/Layout.tsx new file mode 100644 index 00000000..39dde062 --- /dev/null +++ b/rsbuild/tanstack-start/src/components/Layout.tsx @@ -0,0 +1,129 @@ +import { useState } from 'react'; +import { Link, Outlet, useRouterState } from '@tanstack/react-router'; + +const navItems = [ + { path: '/', label: 'Home', description: 'Overview of all demos' }, + { + path: '/pokemon-rsc', + label: 'Pokemon RSC', + description: 'Async server components', + }, + { + path: '/e-commerce', + label: 'eCommerce Demo', + description: 'Composite RSC pattern', + }, + { + path: '/low-level-api', + label: 'Low-Level API', + description: 'RSC primitives', + }, +]; + +export function Layout() { + const [sidebarOpen, setSidebarOpen] = useState(false); + const routerState = useRouterState(); + const currentPath = routerState.location.pathname; + + return ( +
+ {/* Top Navigation Bar */} + + +
+ {/* Sidebar Overlay */} + {sidebarOpen && ( +
setSidebarOpen(false)} + /> + )} + + {/* Sidebar */} + + + {/* Main Content */} +
+ + + {/* Legend Footer */} +
+
+
+
+
+ Server Component +
+
+
+ Client Component +
+
+

TanStack Start RSC Examples

+
+
+
+
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/e-Commerce/components/AddToCartButton.tsx b/rsbuild/tanstack-start/src/e-Commerce/components/AddToCartButton.tsx new file mode 100644 index 00000000..33d494a9 --- /dev/null +++ b/rsbuild/tanstack-start/src/e-Commerce/components/AddToCartButton.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react'; +import { useCartStore } from '~/e-Commerce/store/cartStore'; + +// Product info passed from parent +const product = { + id: 'tanstack-ukulele', + name: 'TanStack Ukulele', + price: 149.99, + image: '/example-ukelele-tanstack.jpg', +}; + +export function AddToCartButton() { + const [quantity, setQuantity] = useState(1); + const [added, setAdded] = useState(false); + const [favorite, setFavorite] = useState(false); + const addItem = useCartStore((state) => state.addItem); + + const handleAddToCart = () => { + // Add item(s) to cart + for (let i = 0; i < quantity; i++) { + addItem(product); + } + setAdded(true); + setQuantity(1); // Reset quantity after adding + setTimeout(() => setAdded(false), 2000); + }; + + return ( +
+ {/* Quantity Selector */} +
+ Quantity: +
+ + + {quantity} + + +
+
+ + {/* Action Buttons */} +
+ + + +
+ + {/* Info text */} +

Free shipping on orders over $100

+
+ ); +} diff --git a/rsbuild/tanstack-start/src/e-Commerce/components/AlsoBoughtCarousel.tsx b/rsbuild/tanstack-start/src/e-Commerce/components/AlsoBoughtCarousel.tsx new file mode 100644 index 00000000..f437579c --- /dev/null +++ b/rsbuild/tanstack-start/src/e-Commerce/components/AlsoBoughtCarousel.tsx @@ -0,0 +1,83 @@ +import { useRef } from 'react'; +import type { AlsoBoughtProduct } from '~/e-Commerce/server-functions'; + +interface AlsoBoughtCarouselProps { + products: Array; +} + +export function AlsoBoughtCarousel({ products }: AlsoBoughtCarouselProps) { + const scrollRef = useRef(null); + + const scroll = (direction: 'left' | 'right') => { + if (scrollRef.current) { + const scrollAmount = 300; + scrollRef.current.scrollBy({ + left: direction === 'left' ? -scrollAmount : scrollAmount, + behavior: 'smooth', + }); + } + }; + + return ( +
+
+ {/* Left scroll button */} + + + {/* Scrollable container */} +
+ {products.map((product) => ( +
+ {product.name} +
+

{product.name}

+

${product.price}

+
+
+ ))} +
+ + {/* Right scroll button */} + +
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/e-Commerce/components/Header.tsx b/rsbuild/tanstack-start/src/e-Commerce/components/Header.tsx new file mode 100644 index 00000000..30d02dba --- /dev/null +++ b/rsbuild/tanstack-start/src/e-Commerce/components/Header.tsx @@ -0,0 +1,53 @@ +import { useCartStore } from '~/e-Commerce/store/cartStore'; + +export function Header() { + const totalItems = useCartStore((state) => state.totalItems); + const totalPrice = useCartStore((state) => state.totalPrice); + + return ( +
+
+
+ 🎸 +

TanStack Music Store

+
+ +
+

+ Premium instruments powered by Server Components +

+ + {/* Cart Button */} + +
+
+ + {/* Cart summary (when items in cart) */} + {totalItems > 0 && ( +
+ + {totalItems} item{totalItems !== 1 ? 's' : ''} in cart + + Total: ${totalPrice.toFixed(2)} +
+ )} +
+ ); +} diff --git a/rsbuild/tanstack-start/src/e-Commerce/components/StreamingComments.tsx b/rsbuild/tanstack-start/src/e-Commerce/components/StreamingComments.tsx new file mode 100644 index 00000000..4da71fee --- /dev/null +++ b/rsbuild/tanstack-start/src/e-Commerce/components/StreamingComments.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef, useState } from 'react'; +import { createFromReadableStream } from '@tanstack/react-start/rsc'; +import { streamComments } from '~/e-Commerce/server-functions'; + +interface CommentEntry { + id: number; + element: React.ReactNode; + receivedAt: number; +} + +// Helper to convert string payload back to ReadableStream +function payloadToStream(payload: string): ReadableStream { + const encoder = new TextEncoder(); + return new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(payload)); + controller.close(); + }, + }); +} + +// Format relative time +function formatRelativeTime(receivedAt: number): string { + const seconds = Math.floor((Date.now() - receivedAt) / 1000); + if (seconds < 1) return 'just now'; + if (seconds === 1) return '1 second ago'; + return `${seconds} seconds ago`; +} + +const MAX_COMMENTS = 10; + +export function StreamingComments() { + const [comments, setComments] = useState>([]); + const [isStreaming, setIsStreaming] = useState(false); + const [, setTick] = useState(0); // Force re-render for relative time updates + const hasStartedRef = useRef(false); + + // Update relative times every second + useEffect(() => { + const interval = setInterval(() => { + setTick((t) => t + 1); + }, 1000); + return () => clearInterval(interval); + }, []); + + // Auto-start streaming on mount + useEffect(() => { + if (hasStartedRef.current) return; + hasStartedRef.current = true; + + const startStream = async () => { + setIsStreaming(true); + + try { + for await (const { id, payload } of await streamComments()) { + // Decode the RSC payload + const stream = payloadToStream(payload); + const element = await createFromReadableStream(stream); + + const entry: CommentEntry = { + id, + element: element as React.ReactNode, + receivedAt: Date.now(), + }; + + // Add to comments, keeping newest at top, capped at MAX_COMMENTS + setComments((prev) => [entry, ...prev].slice(0, MAX_COMMENTS)); + } + } catch (error) { + console.error('Streaming error:', error); + } finally { + setIsStreaming(false); + } + }; + + startStream(); + }, []); + + return ( +
+ {isStreaming && ( +
+
+ Streaming live reviews... +
+ )} + +
+ {comments.length === 0 ? ( +
+
+
+ Loading reviews... +
+
+ ) : ( + comments.map((comment) => ( +
+ {/* The RSC element */} + {comment.element} + {/* Client-side relative time */} +
+ received {formatRelativeTime(comment.receivedAt)} +
+
+ )) + )} +
+ + {comments.length > 0 && ( +
+ Showing {comments.length} of {MAX_COMMENTS} reviews (newest first) +
+ )} +
+ ); +} diff --git a/rsbuild/tanstack-start/src/e-Commerce/server-functions.tsx b/rsbuild/tanstack-start/src/e-Commerce/server-functions.tsx new file mode 100644 index 00000000..f216b6fe --- /dev/null +++ b/rsbuild/tanstack-start/src/e-Commerce/server-functions.tsx @@ -0,0 +1,246 @@ +import { createServerFn } from '@tanstack/react-start'; +import { createCompositeComponent, renderToReadableStream } from '@tanstack/react-start/rsc'; + +// ============================================ +// Product Data & Types +// ============================================ + +export interface Product { + id: string; + name: string; + price: number; + description: string; + image: string; +} + +export interface AlsoBoughtProduct { + id: string; + name: string; + price: number; + image: string; +} + +// Main product +const mainProduct: Product = { + id: 'tanstack-ukulele', + name: 'TanStack Ukulele', + price: 149.99, + description: + 'Hand-crafted koa wood ukulele with mother-of-pearl TanStack inlay. Perfect for beach jams, campfire singalongs, or just strumming at your desk while waiting for builds to finish.', + image: '/example-ukelele-tanstack.jpg', +}; + +// Also bought products +const alsoBoughtProducts: Array = [ + { + id: 'guitar-flowers', + name: 'Floral Guitar', + price: 299.99, + image: '/example-guitar-flowers.jpg', + }, + { + id: 'guitar-motherboard', + name: 'Circuit Guitar', + price: 349.99, + image: '/example-guitar-motherboard.jpg', + }, + { + id: 'guitar-racing', + name: 'Racing Guitar', + price: 279.99, + image: '/example-guitar-racing.jpg', + }, + { + id: 'guitar-steamer-trunk', + name: 'Vintage Guitar', + price: 399.99, + image: '/example-guitar-steamer-trunk.jpg', + }, + { + id: 'guitar-superhero', + name: 'Hero Guitar', + price: 329.99, + image: '/example-guitar-superhero.jpg', + }, +]; + +// ============================================ +// Product Page Server Function +// ============================================ + +export const getProductPage = createServerFn().handler(async () => { + console.log('[Server] Rendering ProductPage composite'); + + const src = await createCompositeComponent( + (props: { + children?: React.ReactNode; + renderAlsoBought?: (data: { products: Array }) => React.ReactNode; + }) => ( +
+ {/* Product Hero Section */} +
+ {/* Product Image */} +
+ {mainProduct.name} +
+ + {/* Product Details */} +
+

{mainProduct.name}

+

{mainProduct.description}

+

${mainProduct.price}

+ + {/* CTA Slot (client component) */} +
{props.children}
+
+
+ + {/* Also Bought Section */} +
+

+ Customers who bought this also bought +

+
+ {props.renderAlsoBought?.({ products: alsoBoughtProducts })} +
+
+
+ ), + ); + + return { src }; +}); + +// ============================================ +// Streaming Comments +// ============================================ + +// Sample comments for streaming +const comments = [ + { + author: 'MusicLover42', + text: "Best ukulele I've ever owned! The tone is incredible and the TanStack inlay is gorgeous.", + rating: 5, + }, + { + author: 'BeachVibes', + text: 'Perfect for beach jams. Everyone asks where I got it!', + rating: 5, + }, + { + author: 'UkeNewbie', + text: 'As a beginner, this was the perfect first instrument. Great quality for the price.', + rating: 5, + }, + { + author: 'DevMusician', + text: 'I code with TanStack and play TanStack. Living the dream!', + rating: 5, + }, + { + author: 'IslandDreamer', + text: "The sound is so warm and rich. It's like Hawaii in a box!", + rating: 5, + }, + { + author: 'GuitarConvert', + text: 'Switched from guitar to uke because of this beauty. No regrets!', + rating: 5, + }, + { + author: 'CampfireKing', + text: 'This uke is the star of every campfire. Worth every penny.', + rating: 4, + }, + { + author: 'TechStrummer', + text: 'Finally, an instrument that understands my stack preferences!', + rating: 5, + }, +]; + +// Server component for each comment +function CommentCard({ + author, + text, + rating, + timestamp, +}: { + author: string; + text: string; + rating: number; + timestamp: string; +}) { + const gradients = [ + 'from-purple-500 to-indigo-600', + 'from-blue-500 to-cyan-500', + 'from-green-500 to-emerald-500', + 'from-amber-500 to-orange-500', + 'from-pink-500 to-rose-500', + ]; + const gradient = gradients[Math.floor(Math.random() * gradients.length)]; + + return ( +
+
+
{author}
+
+ {Array.from({ length: rating }).map((_, i) => ( + + ))} +
+
+

{text}

+
+ Posted: {new Date(timestamp).toLocaleTimeString()} +
+
+ ); +} + +// Helper to convert stream to string +async function streamToString(stream: ReadableStream): Promise { + const reader = stream.getReader(); + const chunks: Array = []; + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(decoder.decode(value, { stream: true })); + } + + return chunks.join(''); +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// Async generator that streams comment RSCs +export const streamComments = createServerFn().handler(async function* () { + for (let i = 0; i < comments.length; i++) { + await sleep(2000); // Wait 2 seconds between comments + + const comment = comments[i]; + console.log(`[Server] Rendering comment from ${comment.author}`); + + const stream = await renderToReadableStream( + , + ); + + const payload = await streamToString(stream); + yield { id: i, payload }; + } +}); diff --git a/rsbuild/tanstack-start/src/e-Commerce/store/cartStore.ts b/rsbuild/tanstack-start/src/e-Commerce/store/cartStore.ts new file mode 100644 index 00000000..97087264 --- /dev/null +++ b/rsbuild/tanstack-start/src/e-Commerce/store/cartStore.ts @@ -0,0 +1,77 @@ +import { create } from 'zustand'; + +interface CartItem { + id: string; + name: string; + price: number; + quantity: number; + image: string; +} + +interface CartStore { + items: Array; + totalItems: number; + totalPrice: number; + addItem: (item: Omit) => void; + removeItem: (id: string) => void; + updateQuantity: (id: string, quantity: number) => void; + clearCart: () => void; +} + +export const useCartStore = create((set, get) => ({ + items: [], + totalItems: 0, + totalPrice: 0, + + addItem: (item) => { + const { items } = get(); + const existingItem = items.find((i) => i.id === item.id); + + if (existingItem) { + set({ + items: items.map((i) => (i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i)), + totalItems: get().totalItems + 1, + totalPrice: get().totalPrice + item.price, + }); + } else { + set({ + items: [...items, { ...item, quantity: 1 }], + totalItems: get().totalItems + 1, + totalPrice: get().totalPrice + item.price, + }); + } + }, + + removeItem: (id) => { + const { items } = get(); + const item = items.find((i) => i.id === id); + if (item) { + set({ + items: items.filter((i) => i.id !== id), + totalItems: get().totalItems - item.quantity, + totalPrice: get().totalPrice - item.price * item.quantity, + }); + } + }, + + updateQuantity: (id, quantity) => { + const { items } = get(); + const item = items.find((i) => i.id === id); + if (item) { + const diff = quantity - item.quantity; + set({ + items: items.map((i) => (i.id === id ? { ...i, quantity } : i)), + totalItems: get().totalItems + diff, + totalPrice: get().totalPrice + item.price * diff, + }); + } + }, + + clearCart: () => { + set({ + items: [], + totalItems: 0, + totalPrice: 0, + }); + }, +})); diff --git a/rsbuild/tanstack-start/src/home/server-functions.tsx b/rsbuild/tanstack-start/src/home/server-functions.tsx new file mode 100644 index 00000000..271f02a8 --- /dev/null +++ b/rsbuild/tanstack-start/src/home/server-functions.tsx @@ -0,0 +1,93 @@ +import { createServerFn } from '@tanstack/react-start'; +import { createCompositeComponent } from '@tanstack/react-start/rsc'; + +export interface DemoInfo { + title: string; + description: string; + path: string; + features: Array; + color: string; +} + +const demos: Array = [ + { + title: 'eCommerce Composite Demo', + description: + 'A product page built with composite server components. Shows how to combine server-rendered layouts with client-interactive elements like add-to-cart buttons and carousels.', + path: '/e-commerce', + features: [ + 'Composite RSC pattern', + 'Zustand cart state', + 'Streaming comments', + 'Also bought carousel', + ], + color: 'from-purple-500 to-indigo-600', + }, + { + title: 'Pokemon RSC Demo', + description: + 'Demonstrates async server components that fetch their own data. The Pokemon list is fetched and rendered entirely on the server.', + path: '/pokemon-rsc', + features: ['Async server components', 'Server-side data fetching', 'renderServerComponent API'], + color: 'from-amber-500 to-orange-500', + }, + { + title: 'Low-Level RSC API Demo', + description: + "Explore TanStack Start's low-level Flight stream APIs including renderToReadableStream, createFromReadableStream, and createFromFetch.", + path: '/low-level-api', + features: [ + 'Direct Flight streams', + 'Parallel streaming', + 'Nested Suspense', + 'IndexedDB caching', + 'HTTP streaming ticker', + ], + color: 'from-cyan-500 to-blue-600', + }, +]; + +// Server function to get the home page composite +export const getHomePage = createServerFn().handler(async () => { + console.log('[Server] Rendering HomePage composite'); + + const src = await createCompositeComponent( + (props: { renderDemoCard?: (data: { demo: DemoInfo }) => React.ReactNode }) => ( +
+
+ {/* Hero Section */} +
+

+ React Server Components Examples +

+

+ Explore different patterns and APIs for building applications with React Server + Components in TanStack Start. +

+
+ + {/* Demo Cards */} +
+ {demos.map((demo) => ( +
{props.renderDemoCard?.({ demo })}
+ ))} +
+ + {/* Quick Info */} +
+

About These Examples

+

+ These examples demonstrate various React Server Component patterns available in + TanStack Start. Server components are rendered on the server and sent to the client as + a Flight stream. Client components handle interactivity and state management. The + visual indicators in the footer show which parts are server-rendered (blue) vs + client-interactive (green). +

+
+
+
+ ), + ); + + return { src }; +}); diff --git a/rsbuild/tanstack-start/src/low-level/components/DemoSection.tsx b/rsbuild/tanstack-start/src/low-level/components/DemoSection.tsx new file mode 100644 index 00000000..87823f47 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/DemoSection.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react'; + +interface DemoSectionProps { + title: string; + description: string; + children: ReactNode; +} + +export function DemoSection({ title, description, children }: DemoSectionProps) { + return ( +
+
+

{title}

+

{description}

+
+
{children}
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/dexie-cache/DexieCacheDemo.tsx b/rsbuild/tanstack-start/src/low-level/components/dexie-cache/DexieCacheDemo.tsx new file mode 100644 index 00000000..c8d9eaa9 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/dexie-cache/DexieCacheDemo.tsx @@ -0,0 +1,185 @@ +import { useCallback, useEffect, useState } from 'react'; +import { createFromReadableStream } from '@tanstack/react-start/rsc'; + +import { db } from '../../db'; +import { getCounterRscPayload } from './server-functions'; + +// Convert string payload back to ReadableStream +function payloadToStream(payload: string): ReadableStream { + const encoder = new TextEncoder(); + return new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(payload)); + controller.close(); + }, + }); +} + +export function DexieCacheDemo() { + const [counter, setCounter] = useState(0); + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + const [source, setSource] = useState<'cache' | 'server' | null>(null); + const [cacheStats, setCacheStats] = useState({ count: 0, size: 0 }); + const [loadTime, setLoadTime] = useState(null); + + // Load cache stats on mount and after operations + const updateCacheStats = useCallback(async () => { + const entries = await db.cache.toArray(); + const totalSize = entries.reduce((sum, e) => sum + e.payload.length, 0); + setCacheStats({ count: entries.length, size: totalSize }); + }, []); + + const loadRsc = useCallback( + async (count: number) => { + setLoading(true); + setSource(null); + setLoadTime(null); + const startTime = performance.now(); + + try { + const cacheKey = `counter-${count}`; + + // Check if cached in Dexie + const cached = await db.cache.get(cacheKey); + + if (cached) { + // Load from cache + console.log(`[Client] Cache HIT for counter: ${count}`); + const stream = payloadToStream(cached.payload); + const element = await createFromReadableStream(stream); + setResult(element as React.ReactNode); + setSource('cache'); + } else { + // Fetch from server + console.log(`[Client] Cache MISS for counter: ${count}`); + const { payload } = await getCounterRscPayload({ data: { count } }); + + // Store in Dexie + await db.cache.put({ + id: cacheKey, + payload, + createdAt: Date.now(), + }); + + // Decode and render + const stream = payloadToStream(payload); + const element = await createFromReadableStream(stream); + setResult(element as React.ReactNode); + setSource('server'); + await updateCacheStats(); + } + + setLoadTime(performance.now() - startTime); + } catch (error) { + console.error('Error loading RSC:', error); + } finally { + setLoading(false); + } + }, + [updateCacheStats], + ); + + // Auto-load RSC when counter changes + useEffect(() => { + loadRsc(counter); + }, [counter, loadRsc]); + + // Load cache stats on mount + useEffect(() => { + updateCacheStats(); + }, [updateCacheStats]); + + const clearCache = useCallback(async () => { + await db.cache.clear(); + await updateCacheStats(); + setResult(null); + setSource(null); + setLoadTime(null); + }, [updateCacheStats]); + + return ( +
+ {/* Counter controls */} +
+ +
{counter}
+ + +
+ + {/* Cache stats */} +
+
+ Cached entries:{' '} + {cacheStats.count} +
+
+ Cache size:{' '} + {(cacheStats.size / 1024).toFixed(2)} KB +
+
+ + {/* Source indicator */} + {loading ? ( +
+
+ Loading... +
+ ) : ( + source && ( +
+
+ + {source === 'cache' ? 'Loaded from Dexie cache' : 'Fetched from server'} + + {loadTime !== null && ( + ({loadTime.toFixed(1)}ms) + )} +
+ ) + )} + + {/* Result */} + {result && ( +
+
RSC for Counter #{counter}:
+ {result} +
+ )} + + {/* How it works */} +
+ How it works: When you change the counter, the app automatically checks + Dexie (IndexedDB) for a cached Flight payload. If found, it decodes the cached string + directly. If not found, it fetches from the server, stores the payload in Dexie, then + decodes it. Each counter value gets its own cache entry with unique server-generated data. +
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/dexie-cache/server-functions.tsx b/rsbuild/tanstack-start/src/low-level/components/dexie-cache/server-functions.tsx new file mode 100644 index 00000000..1fef2ef1 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/dexie-cache/server-functions.tsx @@ -0,0 +1,70 @@ +import { createServerFn } from '@tanstack/react-start'; +import { renderToReadableStream } from '@tanstack/react-start/rsc'; + +// Colors for the counter-based RSC +const counterColors = [ + { bg: 'from-red-500 to-orange-500', text: 'text-red-100', name: 'Warm' }, + { bg: 'from-blue-500 to-cyan-500', text: 'text-blue-100', name: 'Cool' }, + { + bg: 'from-green-500 to-emerald-500', + text: 'text-green-100', + name: 'Nature', + }, + { bg: 'from-purple-500 to-pink-500', text: 'text-purple-100', name: 'Royal' }, + { + bg: 'from-amber-500 to-yellow-500', + text: 'text-amber-100', + name: 'Golden', + }, +]; + +// Server component for counter-based RSC +function CounterCard({ count }: { count: number }) { + const colorIndex = count % counterColors.length; + const color = counterColors[colorIndex]; + const timestamp = new Date().toISOString(); + const randomValue = Math.floor(Math.random() * 10000); + + console.log(`[Server] Rendering CounterCard for count: ${count}`); + + return ( +
+
+
+

Counter #{count}

+

Theme: {color.name}

+
+
+
{randomValue}
+
Random Value
+
+
+
Generated: {timestamp}
+
+ ); +} + +// Server function that returns Flight payload as a string for caching +export const getCounterRscPayload = createServerFn() + .inputValidator((data: { count: number }) => data) + .handler(async ({ data }) => { + console.log(`[Server] Creating Flight payload for count: ${data.count}`); + + const stream = await renderToReadableStream(); + + // Convert stream to string for caching + const reader = stream.getReader(); + const chunks: Array = []; + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(decoder.decode(value, { stream: true })); + } + + const payload = chunks.join(''); + console.log(`[Server] Flight payload size: ${payload.length} bytes`); + + return { payload, count: data.count }; + }); diff --git a/rsbuild/tanstack-start/src/low-level/components/direct-flight/DirectFlightStreamDemo.tsx b/rsbuild/tanstack-start/src/low-level/components/direct-flight/DirectFlightStreamDemo.tsx new file mode 100644 index 00000000..4ab75123 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/direct-flight/DirectFlightStreamDemo.tsx @@ -0,0 +1,52 @@ +import { useCallback, useState } from 'react'; +import { createFromReadableStream } from '@tanstack/react-start/rsc'; + +import { getFlightStreamDirect } from './server-functions'; + +export function DirectFlightStreamDemo() { + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + const [streamInfo, setStreamInfo] = useState(''); + + const loadStream = useCallback(async () => { + setLoading(true); + setStreamInfo('Fetching Flight stream from server function...'); + + try { + const stream = await getFlightStreamDirect(); + setStreamInfo('Decoding Flight stream with createFromReadableStream...'); + + // Use createFromReadableStream to decode the Flight stream + const element = await createFromReadableStream(stream); + setResult(element as React.ReactNode); + setStreamInfo('Stream decoded successfully!'); + } catch (error) { + setStreamInfo(`Error: ${error}`); + } finally { + setLoading(false); + } + }, []); + + return ( +
+ + + {streamInfo && ( +
{streamInfo}
+ )} + + {result && ( +
+
Server-Rendered Content:
+ {result} +
+ )} +
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/direct-flight/server-functions.tsx b/rsbuild/tanstack-start/src/low-level/components/direct-flight/server-functions.tsx new file mode 100644 index 00000000..743e6663 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/direct-flight/server-functions.tsx @@ -0,0 +1,21 @@ +import { createServerFn } from '@tanstack/react-start'; +import { renderToReadableStream } from '@tanstack/react-start/rsc'; + +// Server Component that will be rendered to a Flight stream +function ServerGreeting({ name, timestamp }: { name: string; timestamp: string }) { + console.log('[Server] Rendering ServerGreeting:', name); + return ( +
+

Hello, {name}!

+

Rendered on the server at: {timestamp}

+
+ ); +} + +// Server function using low-level renderToReadableStream +export const getFlightStreamDirect = createServerFn({ method: 'GET' }).handler(async () => { + const stream = await renderToReadableStream( + , + ); + return stream; +}); diff --git a/rsbuild/tanstack-start/src/low-level/components/fetch-flight/FetchFlightStreamDemo.tsx b/rsbuild/tanstack-start/src/low-level/components/fetch-flight/FetchFlightStreamDemo.tsx new file mode 100644 index 00000000..c51bbff0 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/fetch-flight/FetchFlightStreamDemo.tsx @@ -0,0 +1,39 @@ +import { useCallback, useState } from 'react'; +import { createFromFetch } from '@tanstack/react-start/rsc'; + +export function FetchFlightStreamDemo() { + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + + const loadFromApi = useCallback(async () => { + setLoading(true); + try { + // Use createFromFetch to decode a Flight stream from a fetch response + const element = await createFromFetch(fetch('/api/rsc')); + setResult(element as React.ReactNode); + } catch (error) { + console.error('Error fetching RSC:', error); + } finally { + setLoading(false); + } + }, []); + + return ( +
+ + + {result && ( +
+
API Route Response:
+ {result} +
+ )} +
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/nested-suspense/NestedSuspenseDemo.tsx b/rsbuild/tanstack-start/src/low-level/components/nested-suspense/NestedSuspenseDemo.tsx new file mode 100644 index 00000000..d6c63bea --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/nested-suspense/NestedSuspenseDemo.tsx @@ -0,0 +1,43 @@ +import { Suspense, useCallback, useState } from 'react'; +import { createFromReadableStream } from '@tanstack/react-start/rsc'; + +import { getNestedServerComponent } from './server-functions'; + +export function NestedSuspenseDemo() { + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + + const loadNested = useCallback(async () => { + setLoading(true); + try { + const stream = await getNestedServerComponent(); + const element = await createFromReadableStream(stream); + setResult(element as React.ReactNode); + } catch (error) { + console.error('Error:', error); + } finally { + setLoading(false); + } + }, []); + + return ( +
+ + + {result && ( +
+
+ Nested Server Components with Suspense: +
+ Loading...
}>{result} +
+ )} +
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/nested-suspense/server-functions.tsx b/rsbuild/tanstack-start/src/low-level/components/nested-suspense/server-functions.tsx new file mode 100644 index 00000000..c65ea905 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/nested-suspense/server-functions.tsx @@ -0,0 +1,55 @@ +import { Suspense } from 'react'; +import { createServerFn } from '@tanstack/react-start'; +import { renderToReadableStream } from '@tanstack/react-start/rsc'; + +// Server Component with async data +async function ServerDataCard({ delay }: { delay: number }) { + console.log('[Server] Starting ServerDataCard with delay:', delay); + await new Promise((resolve) => setTimeout(resolve, delay)); + const data = { + value: Math.floor(Math.random() * 1000), + computedAt: new Date().toISOString(), + }; + console.log('[Server] Finished ServerDataCard:', data); + + return ( +
+
{data.value}
+
Computed at: {data.computedAt}
+
Delay: {delay}ms
+
+ ); +} + +function LoadingSkeleton({ label }: { label: string }) { + return ( +
+
+
+
{label}
+
+ ); +} + +// Server function returning a nested server component structure +export const getNestedServerComponent = createServerFn({ + method: 'GET', +}).handler(async () => { + const stream = await renderToReadableStream( +
+
+

Nested Server Component

+

This entire tree is rendered on the server

+
+
+ }> + + + }> + + +
+
, + ); + return stream; +}); diff --git a/rsbuild/tanstack-start/src/low-level/components/parallel-streams/ParallelStreamsDemo.tsx b/rsbuild/tanstack-start/src/low-level/components/parallel-streams/ParallelStreamsDemo.tsx new file mode 100644 index 00000000..3c500809 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/parallel-streams/ParallelStreamsDemo.tsx @@ -0,0 +1,96 @@ +import { useCallback, useState } from 'react'; +import { createFromReadableStream } from '@tanstack/react-start/rsc'; + +import { getParallelStreams } from './server-functions'; + +export function ParallelStreamsDemo() { + const [streams, setStreams] = useState<{ + card1: React.ReactNode | null; + card2: React.ReactNode | null; + card3: React.ReactNode | null; + }>({ card1: null, card2: null, card3: null }); + const [loading, setLoading] = useState(false); + const [timing, setTiming] = useState>([]); + + const loadParallel = useCallback(async () => { + setLoading(true); + setStreams({ card1: null, card2: null, card3: null }); + setTiming([]); + + const startTime = Date.now(); + const addTiming = (label: string) => { + const elapsed = Date.now() - startTime; + setTiming((prev) => [...prev, `${label}: ${elapsed}ms`]); + }; + + try { + const { stream1, stream2, stream3 } = await getParallelStreams(); + addTiming('Streams received'); + + // Decode streams in parallel + const [el1, el2, el3] = await Promise.all([ + createFromReadableStream(stream1).then((el) => { + addTiming('Card 1 decoded'); + return el; + }), + createFromReadableStream(stream2).then((el) => { + addTiming('Card 2 decoded'); + return el; + }), + createFromReadableStream(stream3).then((el) => { + addTiming('Card 3 decoded'); + return el; + }), + ]); + + setStreams({ + card1: el1 as React.ReactNode, + card2: el2 as React.ReactNode, + card3: el3 as React.ReactNode, + }); + } catch (error) { + console.error('Error:', error); + } finally { + setLoading(false); + } + }, []); + + return ( +
+ + + {timing.length > 0 && ( +
+ {timing.map((t, i) => ( +
+ {t} +
+ ))} +
+ )} + +
+ {[streams.card1, streams.card2, streams.card3].map((card, i) => ( +
+
Card {i + 1}
+ {card || ( +
+
+
+
+ )} +
+ ))} +
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/parallel-streams/server-functions.tsx b/rsbuild/tanstack-start/src/low-level/components/parallel-streams/server-functions.tsx new file mode 100644 index 00000000..86dd562c --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/parallel-streams/server-functions.tsx @@ -0,0 +1,33 @@ +import { createServerFn } from '@tanstack/react-start'; +import { renderToReadableStream } from '@tanstack/react-start/rsc'; + +// Server Component with async data (used for parallel and nested demos) +async function ServerDataCard({ delay }: { delay: number }) { + console.log('[Server] Starting ServerDataCard with delay:', delay); + await new Promise((resolve) => setTimeout(resolve, delay)); + const data = { + value: Math.floor(Math.random() * 1000), + computedAt: new Date().toISOString(), + }; + console.log('[Server] Finished ServerDataCard:', data); + + return ( +
+
{data.value}
+
Computed at: {data.computedAt}
+
Delay: {delay}ms
+
+ ); +} + +// Server function that returns multiple streams for parallel loading +export const getParallelStreams = createServerFn({ method: 'GET' }).handler(async () => { + // Create three independent streams with different delays + const [stream1, stream2, stream3] = await Promise.all([ + renderToReadableStream(), + renderToReadableStream(), + renderToReadableStream(), + ]); + + return { stream1, stream2, stream3 }; +}); diff --git a/rsbuild/tanstack-start/src/low-level/components/streaming-ticker/StreamingTicker.tsx b/rsbuild/tanstack-start/src/low-level/components/streaming-ticker/StreamingTicker.tsx new file mode 100644 index 00000000..4f62e25f --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/streaming-ticker/StreamingTicker.tsx @@ -0,0 +1,151 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { createFromReadableStream } from '@tanstack/react-start/rsc'; + +import { streamTickerRscs } from './server-functions'; + +interface TickerEntry { + id: number; + element: React.ReactNode; + receivedAt: number; +} + +// Helper to convert string payload back to ReadableStream +function payloadToStream(payload: string): ReadableStream { + const encoder = new TextEncoder(); + return new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(payload)); + controller.close(); + }, + }); +} + +// Format relative time +function formatRelativeTime(receivedAt: number): string { + const seconds = Math.floor((Date.now() - receivedAt) / 1000); + if (seconds < 1) return 'just now'; + if (seconds === 1) return '1 second ago'; + return `${seconds} seconds ago`; +} + +export function StreamingTicker() { + const [items, setItems] = useState>([]); + const [isStreaming, setIsStreaming] = useState(false); + const [, setTick] = useState(0); // Force re-render for relative time updates + const abortRef = useRef(false); + + // Update relative times every second + useEffect(() => { + const interval = setInterval(() => { + setTick((t) => t + 1); + }, 1000); + return () => clearInterval(interval); + }, []); + + const startStream = useCallback(async () => { + setIsStreaming(true); + abortRef.current = false; + setItems([]); + + try { + for await (const { id, payload } of await streamTickerRscs()) { + if (abortRef.current) break; + + // Decode the RSC payload + const stream = payloadToStream(payload); + const element = await createFromReadableStream(stream); + + const entry: TickerEntry = { + id, + element: element as React.ReactNode, + receivedAt: Date.now(), + }; + + // Add to items, keeping only last 10 + setItems((prev) => [entry, ...prev].slice(0, 10)); + } + } catch (error) { + console.error('Streaming error:', error); + } finally { + setIsStreaming(false); + } + }, []); + + const stopStream = useCallback(() => { + abortRef.current = true; + setIsStreaming(false); + }, []); + + const clearItems = useCallback(() => { + abortRef.current = true; + setIsStreaming(false); + setItems([]); + }, []); + + return ( +
+
+ {!isStreaming ? ( + + ) : ( + + )} + +
+ + {isStreaming && ( +
+
+ Streaming RSCs... +
+ )} + +
+ {items.length === 0 ? ( +
+ Click "Start Stream" to begin receiving RSC payloads +
+ ) : ( + items.map((item) => ( +
+ {/* The RSC element */} + {item.element} + {/* Client-side relative time */} +
+ received {formatRelativeTime(item.receivedAt)} +
+
+ )) + )} +
+ + {items.length > 0 && ( +
+ Showing {items.length} of last 10 items (newest first) +
+ )} + +
+ How it works: Each ticker item is a React Server Component rendered on the + server with renderToReadableStream, streamed as a Flight payload, and decoded + on the client with createFromReadableStream. The "received X seconds ago" text + updates live on the client. +
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/low-level/components/streaming-ticker/server-functions.tsx b/rsbuild/tanstack-start/src/low-level/components/streaming-ticker/server-functions.tsx new file mode 100644 index 00000000..da794a26 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/components/streaming-ticker/server-functions.tsx @@ -0,0 +1,81 @@ +import { createServerFn } from '@tanstack/react-start'; +import { renderToReadableStream } from '@tanstack/react-start/rsc'; + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// Helper to convert stream to string +async function streamToString(stream: ReadableStream): Promise { + const reader = stream.getReader(); + const chunks: Array = []; + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(decoder.decode(value, { stream: true })); + } + + return chunks.join(''); +} + +// Server component for each ticker item +function TickerItemCard({ + id, + timestamp, + variant, +}: { + id: number; + timestamp: string; + variant: 'info' | 'success' | 'warning'; +}) { + const tickerColors = { + info: 'from-blue-500 to-cyan-500', + success: 'from-green-500 to-emerald-500', + warning: 'from-amber-500 to-orange-500', + }; + + const formattedTime = new Date(timestamp).toLocaleTimeString(); + + return ( +
+
+
Server Update #{id + 1}
+
+ {variant} +
+
+
Generated: {formattedTime}
+
+ ); +} + +// Server function that streams RSC Flight payloads +export const streamTickerRscs = createServerFn().handler(async function* () { + const variants = ['info', 'success', 'warning'] as const; + + for (let i = 0; i < 15; i++) { + await sleep(800); + + console.log(`[Server] Rendering ticker item #${i + 1}`); + + // Render the server component to a Flight stream + const stream = await renderToReadableStream( + , + ); + + // Convert stream to string for yielding + const payload = await streamToString(stream); + + yield { id: i, payload }; + } +}); diff --git a/rsbuild/tanstack-start/src/low-level/db.ts b/rsbuild/tanstack-start/src/low-level/db.ts new file mode 100644 index 00000000..c903ffe8 --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/db.ts @@ -0,0 +1,18 @@ +import Dexie from 'dexie'; + +import type { EntityTable } from 'dexie'; + +// Dexie database for RSC payload caching +export interface RscCacheEntry { + id: string; + payload: string; + createdAt: number; +} + +export const db = new Dexie('RscCache') as Dexie & { + cache: EntityTable; +}; + +db.version(1).stores({ + cache: 'id, createdAt', +}); diff --git a/rsbuild/tanstack-start/src/low-level/index.ts b/rsbuild/tanstack-start/src/low-level/index.ts new file mode 100644 index 00000000..4065d96e --- /dev/null +++ b/rsbuild/tanstack-start/src/low-level/index.ts @@ -0,0 +1,29 @@ +// Shared components +export { DemoSection } from './components/DemoSection'; + +// Demo 1: Direct Flight Stream +export { DirectFlightStreamDemo } from './components/direct-flight/DirectFlightStreamDemo'; +export { getFlightStreamDirect } from './components/direct-flight/server-functions'; + +// Demo 2: Fetch Flight Stream (uses /api/rsc route) +export { FetchFlightStreamDemo } from './components/fetch-flight/FetchFlightStreamDemo'; + +// Demo 3: Parallel Streams +export { ParallelStreamsDemo } from './components/parallel-streams/ParallelStreamsDemo'; +export { getParallelStreams } from './components/parallel-streams/server-functions'; + +// Demo 4: Nested Suspense +export { NestedSuspenseDemo } from './components/nested-suspense/NestedSuspenseDemo'; +export { getNestedServerComponent } from './components/nested-suspense/server-functions'; + +// Demo 5: Dexie Cache +export { DexieCacheDemo } from './components/dexie-cache/DexieCacheDemo'; +export { getCounterRscPayload } from './components/dexie-cache/server-functions'; + +// Demo 6: Streaming Ticker +export { StreamingTicker } from './components/streaming-ticker/StreamingTicker'; +export { streamTickerRscs } from './components/streaming-ticker/server-functions'; + +// Dexie database +export { db } from './db'; +export type { RscCacheEntry } from './db'; diff --git a/rsbuild/tanstack-start/src/pokemon/Button.tsx b/rsbuild/tanstack-start/src/pokemon/Button.tsx new file mode 100644 index 00000000..1c1523bd --- /dev/null +++ b/rsbuild/tanstack-start/src/pokemon/Button.tsx @@ -0,0 +1,13 @@ +'use client'; + +export function Button({ className, title }: { className?: string; title?: string }) { + return ( + + ); +} diff --git a/rsbuild/tanstack-start/src/pokemon/server-functions.tsx b/rsbuild/tanstack-start/src/pokemon/server-functions.tsx new file mode 100644 index 00000000..4e07991e --- /dev/null +++ b/rsbuild/tanstack-start/src/pokemon/server-functions.tsx @@ -0,0 +1,130 @@ +import { createServerFn } from '@tanstack/react-start'; +import { renderServerComponent } from '@tanstack/react-start/rsc'; +import { Button } from './Button'; + +// ============================================ +// Pokemon RSC - Async Server Component Example +// ============================================ + +// Types for PokeAPI +export interface PokemonListItem { + name: string; + url: string; +} + +export interface PokemonDetails { + id: number; + name: string; + sprites: { front_default: string }; + types: Array<{ type: { name: string } }>; +} + +// Type color mapping for Pokemon type badges +export const typeColors: Record = { + normal: 'bg-gray-400', + fire: 'bg-red-500', + water: 'bg-blue-500', + electric: 'bg-yellow-400', + grass: 'bg-green-500', + ice: 'bg-cyan-300', + fighting: 'bg-orange-700', + poison: 'bg-purple-500', + ground: 'bg-amber-600', + flying: 'bg-indigo-300', + psychic: 'bg-pink-500', + bug: 'bg-lime-500', + rock: 'bg-stone-500', + ghost: 'bg-purple-700', + dragon: 'bg-indigo-600', + dark: 'bg-gray-700', + steel: 'bg-gray-400', + fairy: 'bg-pink-300', +}; + +// Server component for individual Pokemon card +function PokemonCard({ pokemon }: { pokemon: PokemonDetails }) { + return ( +
+ {pokemon.name} +

{pokemon.name}

+

#{pokemon.id}

+
+ {pokemon.types.map((t) => ( +
+
+ ); +} + +// Async Server Component - fetches its own data (like Next.js RSC) +async function PokemonList() { + console.log('[Server] Fetching Pokemon data...'); + + // Data fetching happens INSIDE the component + const response = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20'); + const data = await response.json(); + + // Fetch details for each Pokemon to get sprites and types + const pokemonDetails = await Promise.all( + data.results.map(async (pokemon: PokemonListItem) => { + const res = await fetch(pokemon.url); + return res.json() as Promise; + }), + ); + + console.log(`[Server] Fetched ${pokemonDetails.length} Pokemon`); + + return ( +
+

Pokemon (Server Component)

+

+ This list was fetched and rendered entirely on the server using an async React component. +

+
+ {pokemonDetails.map((pokemon) => ( + + ))} +
+
+ ); +} + +// Server function that renders the async component +export const getPokemonList = createServerFn().handler(async () => { + const Renderable = await renderServerComponent(); + return { Renderable }; +}); + +// Server function that fetches Pokemon data (traditional SSR - no RSC) +export const fetchPokemonData = createServerFn().handler(async () => { + console.log('[Server] Fetching Pokemon data (traditional loader)...'); + + const response = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20'); + const data = await response.json(); + + const pokemonDetails = await Promise.all( + data.results.map(async (pokemon: PokemonListItem) => { + const res = await fetch(pokemon.url); + const { name, id, sprites, types } = await res.json(); + return { + name, + id, + sprites: { + front_default: sprites.front_default, + }, + types: types.map((t: { type: { name: string } }): { type: { name: string } } => ({ + type: { name: t.type.name }, + })), + } as unknown as Promise; + }), + ); + + console.log(`[Server] Fetched ${pokemonDetails.length} Pokemon (traditional)`); + + return { pokemon: pokemonDetails }; +}); diff --git a/rsbuild/tanstack-start/src/routeTree.gen.ts b/rsbuild/tanstack-start/src/routeTree.gen.ts new file mode 100644 index 00000000..a3961d27 --- /dev/null +++ b/rsbuild/tanstack-start/src/routeTree.gen.ts @@ -0,0 +1,165 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root'; +import { Route as PokemonRscRouteImport } from './routes/pokemon-rsc'; +import { Route as PokemonRouteImport } from './routes/pokemon'; +import { Route as LowLevelApiRouteImport } from './routes/low-level-api'; +import { Route as ECommerceRouteImport } from './routes/e-commerce'; +import { Route as IndexRouteImport } from './routes/index'; +import { Route as ApiRscRouteImport } from './routes/api/rsc'; + +const PokemonRscRoute = PokemonRscRouteImport.update({ + id: '/pokemon-rsc', + path: '/pokemon-rsc', + getParentRoute: () => rootRouteImport, +} as any); +const PokemonRoute = PokemonRouteImport.update({ + id: '/pokemon', + path: '/pokemon', + getParentRoute: () => rootRouteImport, +} as any); +const LowLevelApiRoute = LowLevelApiRouteImport.update({ + id: '/low-level-api', + path: '/low-level-api', + getParentRoute: () => rootRouteImport, +} as any); +const ECommerceRoute = ECommerceRouteImport.update({ + id: '/e-commerce', + path: '/e-commerce', + getParentRoute: () => rootRouteImport, +} as any); +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any); +const ApiRscRoute = ApiRscRouteImport.update({ + id: '/api/rsc', + path: '/api/rsc', + getParentRoute: () => rootRouteImport, +} as any); + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute; + '/e-commerce': typeof ECommerceRoute; + '/low-level-api': typeof LowLevelApiRoute; + '/pokemon': typeof PokemonRoute; + '/pokemon-rsc': typeof PokemonRscRoute; + '/api/rsc': typeof ApiRscRoute; +} +export interface FileRoutesByTo { + '/': typeof IndexRoute; + '/e-commerce': typeof ECommerceRoute; + '/low-level-api': typeof LowLevelApiRoute; + '/pokemon': typeof PokemonRoute; + '/pokemon-rsc': typeof PokemonRscRoute; + '/api/rsc': typeof ApiRscRoute; +} +export interface FileRoutesById { + __root__: typeof rootRouteImport; + '/': typeof IndexRoute; + '/e-commerce': typeof ECommerceRoute; + '/low-level-api': typeof LowLevelApiRoute; + '/pokemon': typeof PokemonRoute; + '/pokemon-rsc': typeof PokemonRscRoute; + '/api/rsc': typeof ApiRscRoute; +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath; + fullPaths: '/' | '/e-commerce' | '/low-level-api' | '/pokemon' | '/pokemon-rsc' | '/api/rsc'; + fileRoutesByTo: FileRoutesByTo; + to: '/' | '/e-commerce' | '/low-level-api' | '/pokemon' | '/pokemon-rsc' | '/api/rsc'; + id: + | '__root__' + | '/' + | '/e-commerce' + | '/low-level-api' + | '/pokemon' + | '/pokemon-rsc' + | '/api/rsc'; + fileRoutesById: FileRoutesById; +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute; + ECommerceRoute: typeof ECommerceRoute; + LowLevelApiRoute: typeof LowLevelApiRoute; + PokemonRoute: typeof PokemonRoute; + PokemonRscRoute: typeof PokemonRscRoute; + ApiRscRoute: typeof ApiRscRoute; +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/pokemon-rsc': { + id: '/pokemon-rsc'; + path: '/pokemon-rsc'; + fullPath: '/pokemon-rsc'; + preLoaderRoute: typeof PokemonRscRouteImport; + parentRoute: typeof rootRouteImport; + }; + '/pokemon': { + id: '/pokemon'; + path: '/pokemon'; + fullPath: '/pokemon'; + preLoaderRoute: typeof PokemonRouteImport; + parentRoute: typeof rootRouteImport; + }; + '/low-level-api': { + id: '/low-level-api'; + path: '/low-level-api'; + fullPath: '/low-level-api'; + preLoaderRoute: typeof LowLevelApiRouteImport; + parentRoute: typeof rootRouteImport; + }; + '/e-commerce': { + id: '/e-commerce'; + path: '/e-commerce'; + fullPath: '/e-commerce'; + preLoaderRoute: typeof ECommerceRouteImport; + parentRoute: typeof rootRouteImport; + }; + '/': { + id: '/'; + path: '/'; + fullPath: '/'; + preLoaderRoute: typeof IndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + '/api/rsc': { + id: '/api/rsc'; + path: '/api/rsc'; + fullPath: '/api/rsc'; + preLoaderRoute: typeof ApiRscRouteImport; + parentRoute: typeof rootRouteImport; + }; + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + ECommerceRoute: ECommerceRoute, + LowLevelApiRoute: LowLevelApiRoute, + PokemonRoute: PokemonRoute, + PokemonRscRoute: PokemonRscRoute, + ApiRscRoute: ApiRscRoute, +}; +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes(); + +import type { getRouter } from './router.tsx'; +import type { createStart } from '@tanstack/react-start'; +declare module '@tanstack/react-start' { + interface Register { + ssr: true; + router: Awaited>; + } +} diff --git a/rsbuild/tanstack-start/src/router.tsx b/rsbuild/tanstack-start/src/router.tsx new file mode 100644 index 00000000..19d81b0d --- /dev/null +++ b/rsbuild/tanstack-start/src/router.tsx @@ -0,0 +1,11 @@ +import { createRouter } from '@tanstack/react-router'; +import { routeTree } from './routeTree.gen'; + +export function getRouter() { + const router = createRouter({ + routeTree, + defaultPreload: 'intent', + scrollRestoration: true, + }); + return router; +} diff --git a/rsbuild/tanstack-start/src/routes/__root.tsx b/rsbuild/tanstack-start/src/routes/__root.tsx new file mode 100644 index 00000000..4212bb6b --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/__root.tsx @@ -0,0 +1,44 @@ +import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'; +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; +import * as React from 'react'; +// @ts-ignore +import appCss from '~/styles/app.css?url'; +import { Layout } from '~/components/Layout'; + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'TanStack Start RSC Examples', + }, + ], + links: [ + { rel: 'stylesheet', href: appCss }, + { rel: 'icon', href: '/favicon.ico' }, + ], + }), + shellComponent: RootDocument, + component: Layout, +}); + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + + {children} + + + + + ); +} diff --git a/rsbuild/tanstack-start/src/routes/api/rsc.tsx b/rsbuild/tanstack-start/src/routes/api/rsc.tsx new file mode 100644 index 00000000..96ce2363 --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/api/rsc.tsx @@ -0,0 +1,39 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { createServerFn } from '@tanstack/react-start'; +import { renderToReadableStream } from '@tanstack/react-start/rsc'; + +// Server component to render +function ApiServerComponent() { + const timestamp = new Date().toISOString(); + + return ( +
+

API Route RSC

+

This component was streamed from /api/rsc

+

Timestamp: {timestamp}

+
+ ); +} + +// Server function to render the RSC +const getFlightStream = createServerFn({ method: 'GET' }).handler(async () => { + console.log('[API] Rendering Flight stream for /api/rsc'); + return renderToReadableStream(); +}); + +export const Route = createFileRoute('/api/rsc')({ + server: { + handlers: { + GET: async () => { + const stream = await getFlightStream(); + + return new Response(stream, { + headers: { + 'Content-Type': 'text/x-component', + 'Cache-Control': 'no-store', + }, + }); + }, + }, + }, +}); diff --git a/rsbuild/tanstack-start/src/routes/e-commerce.tsx b/rsbuild/tanstack-start/src/routes/e-commerce.tsx new file mode 100644 index 00000000..37b17cc7 --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/e-commerce.tsx @@ -0,0 +1,44 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { CompositeComponent } from '@tanstack/react-start/rsc'; +import { getProductPage } from '~/e-Commerce/server-functions'; +import { AddToCartButton } from '~/e-Commerce/components/AddToCartButton'; +import { AlsoBoughtCarousel } from '~/e-Commerce/components/AlsoBoughtCarousel'; +import { Header } from '~/e-Commerce/components/Header'; +import { StreamingComments } from '~/e-Commerce/components/StreamingComments'; + +export const Route = createFileRoute('/e-commerce')({ + loader: async () => { + const ProductPage = await getProductPage(); + return { ProductPage }; + }, + component: ECommercePage, +}); + +function ECommercePage() { + const { ProductPage } = Route.useLoaderData(); + + return ( +
+
+ {/* Page Header with Cart */} +
+ + {/* Main Product Section */} + } + > + + + + {/* Customer Reviews Section */} +
+

Customer Reviews

+
+ +
+
+
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/routes/index.tsx b/rsbuild/tanstack-start/src/routes/index.tsx new file mode 100644 index 00000000..0078d6e1 --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/index.tsx @@ -0,0 +1,66 @@ +import { Link, createFileRoute } from '@tanstack/react-router'; +import { CompositeComponent } from '@tanstack/react-start/rsc'; +import type { DemoInfo } from '~/home/server-functions'; +import { getHomePage } from '~/home/server-functions'; + +export const Route = createFileRoute('/')({ + loader: async () => { + const HomePage = await getHomePage(); + return { HomePage }; + }, + component: Home, +}); + +function DemoCard({ demo }: { demo: DemoInfo }) { + return ( + +
+
+
+

+ {demo.title} +

+

{demo.description}

+
+ {demo.features.map((feature) => ( + + {feature} + + ))} +
+
+ View Demo + + + +
+
+
+ + ); +} + +function Home() { + const { HomePage } = Route.useLoaderData(); + + return ( +
+
+
+ } + /> +
+
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/routes/low-level-api.tsx b/rsbuild/tanstack-start/src/routes/low-level-api.tsx new file mode 100644 index 00000000..68f3ec3a --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/low-level-api.tsx @@ -0,0 +1,80 @@ +import { createFileRoute } from '@tanstack/react-router'; + +import { + DemoSection, + DexieCacheDemo, + DirectFlightStreamDemo, + FetchFlightStreamDemo, + NestedSuspenseDemo, + ParallelStreamsDemo, + StreamingTicker, +} from '~/low-level'; + +export const Route = createFileRoute('/low-level-api')({ + component: LowLevelApiPage, +}); + +function LowLevelApiPage() { + return ( +
+
+
+

Low-Level RSC Primitives

+

+ Demonstrating TanStack Start's low-level Flight stream APIs +

+
+ +
+ {/* Demo 1: Direct Flight Stream */} + + + + + {/* Demo 2: Fetch-based Flight Stream */} + + + + + {/* Demo 3: Parallel Streams */} + + + + + {/* Demo 4: Nested with Suspense */} + + + + + {/* Demo 5: Dexie Cache */} + + + + + {/* Demo 6: HTTP Streaming Ticker */} + + + +
+
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/routes/pokemon-rsc.tsx b/rsbuild/tanstack-start/src/routes/pokemon-rsc.tsx new file mode 100644 index 00000000..4f5404dc --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/pokemon-rsc.tsx @@ -0,0 +1,42 @@ +import { Link, createFileRoute } from '@tanstack/react-router'; +import { getPokemonList } from '~/pokemon/server-functions'; + +export const Route = createFileRoute('/pokemon-rsc')({ + loader: async () => { + const { Renderable } = await getPokemonList(); + return Renderable; + }, + component: PokemonPage, +}); + +function PokemonPage() { + const PokemonList = Route.useLoaderData(); + + return ( +
+
+ {/* Comparison Link */} +
+
+

+ Compare: See how this RSC version differs from the traditional SSR + approach in structure and payload size. +

+
+ + View Traditional Version + + + + +
+ + {/* Pokemon List - Server Component */} +
{PokemonList}
+
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/routes/pokemon.tsx b/rsbuild/tanstack-start/src/routes/pokemon.tsx new file mode 100644 index 00000000..b42fe2d2 --- /dev/null +++ b/rsbuild/tanstack-start/src/routes/pokemon.tsx @@ -0,0 +1,93 @@ +import { Link, createFileRoute } from '@tanstack/react-router'; +import type { PokemonDetails } from '~/pokemon/server-functions'; +import { fetchPokemonData, typeColors } from '~/pokemon/server-functions'; + +export const Route = createFileRoute('/pokemon')({ + loader: async () => { + // Traditional loader pattern - fetches data, returns JSON + // This data is serialized to JSON and sent to the client + // The component then renders it on the client + const { pokemon } = await fetchPokemonData(); + return { pokemon }; + }, + component: PokemonPage, +}); + +function Button({ className, title }: { className?: string; title?: string }) { + return ( + + ); +} + +function PokemonCard({ pokemon }: { pokemon: PokemonDetails }) { + return ( +
+ {pokemon.name} +

{pokemon.name}

+

#{pokemon.id}

+
+ {pokemon.types.map((t) => ( +
+
+ ); +} + +function PokemonPage() { + const { pokemon } = Route.useLoaderData(); + + return ( +
+
+
+

Pokemon (Traditional SSR)

+

+ This list was fetched in the loader and rendered as a normal React component. The data + is serialized to JSON and sent to the client, where the component re-renders with that + data. +

+ +
+

+ Note: Unlike the RSC version, this page sends the full Pokemon data + as JSON in the initial HTML payload. The component code also ships to the client and + re-hydrates. Check the Network tab to compare payload sizes! +

+
+ + + + + + View RSC Version + + +
+ {pokemon.map((p) => ( + + ))} +
+
+
+
+ ); +} diff --git a/rsbuild/tanstack-start/src/styles/app.css b/rsbuild/tanstack-start/src/styles/app.css new file mode 100644 index 00000000..c36c737c --- /dev/null +++ b/rsbuild/tanstack-start/src/styles/app.css @@ -0,0 +1,30 @@ +@import 'tailwindcss'; + +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } +} + +@layer base { + html { + color-scheme: light dark; + } + + * { + @apply border-gray-200 dark:border-gray-800; + } + + html, + body { + @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; + } + + .using-mouse * { + outline: none !important; + } +} diff --git a/rsbuild/tanstack-start/tsconfig.json b/rsbuild/tanstack-start/tsconfig.json new file mode 100644 index 00000000..85549257 --- /dev/null +++ b/rsbuild/tanstack-start/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2024"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2024", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true + } +}