diff --git a/examples/solid/start-bun/.gitignore b/examples/solid/start-bun/.gitignore new file mode 100644 index 00000000000..029f7fba9a9 --- /dev/null +++ b/examples/solid/start-bun/.gitignore @@ -0,0 +1,12 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +count.txt +.env +.nitro +.tanstack +.output +.vinxi +todos.json diff --git a/examples/solid/start-bun/.prettierignore b/examples/solid/start-bun/.prettierignore new file mode 100644 index 00000000000..5322d7feeae --- /dev/null +++ b/examples/solid/start-bun/.prettierignore @@ -0,0 +1,3 @@ +package-lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/examples/solid/start-bun/.vscode/settings.json b/examples/solid/start-bun/.vscode/settings.json new file mode 100644 index 00000000000..00b5278e580 --- /dev/null +++ b/examples/solid/start-bun/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/examples/solid/start-bun/README.md b/examples/solid/start-bun/README.md new file mode 100644 index 00000000000..e659b0dd980 --- /dev/null +++ b/examples/solid/start-bun/README.md @@ -0,0 +1,173 @@ +# TanStack Start + Bun Production Server + +An optimized production server for TanStack Start applications using Bun, implementing intelligent static asset loading with configurable memory management. + +## šŸš€ Features + +- **Hybrid Loading Strategy**: Small files are preloaded into memory, large files are served on-demand +- **Configurable File Filtering**: Include/Exclude patterns for precise control +- **Memory-efficient Response Generation**: Optimized for high performance +- **Production-ready Caching Headers**: Automatic Cache-Control headers for optimal performance +- **Detailed Logging**: Vite-like output for better overview + +## šŸ“¦ Installation + +This project was created with TanStack Start: + +```bash +bunx create-start-app@latest +``` + +Install dependencies: + +```bash +bun install +``` + +## šŸƒā€ā™‚ļø Development + +For development: + +```bash +bun run dev +``` + +## šŸ”Ø Production Build + +Build the application for production: + +```bash +bun run build +``` + +## šŸš€ Production Server with server.ts + +### Quick Start - Use in Your Project + +You can easily use this production server in your own TanStack Start project: + +1. **Copy the `server.ts` file** into your project root +2. **Build your project** with `bun run build` +3. **Start the server** directly with: + ```bash + bun run server.ts + ``` + +Or add it to your `package.json` scripts: + +```json +{ + "scripts": { + "start": "bun run server.ts" + } +} +``` + +Then run with: + +```bash +bun run start +``` + +### Server Features + +The `server.ts` implements a high-performance production server with the following features: + +#### 1. Intelligent Asset Loading + +The server automatically decides which files to preload into memory and which to serve on-demand: + +- **In-Memory Loading**: Small files (default < 5MB) are loaded into memory at startup +- **On-Demand Loading**: Large files are loaded from disk only when requested +- **Optimized Performance**: Frequently used assets are served from memory + +#### 2. Configuration via Environment Variables + +```bash +# Server Port (default: 3000) +PORT=3000 + +# Maximum file size for in-memory loading (in bytes, default: 5MB) +STATIC_PRELOAD_MAX_BYTES=5242880 + +# Include patterns (comma-separated, only these files will be preloaded) +STATIC_PRELOAD_INCLUDE="*.js,*.css,*.woff2" + +# Exclude patterns (comma-separated, these files will be excluded) +STATIC_PRELOAD_EXCLUDE="*.map,*.txt" + +# Enable detailed logging +STATIC_PRELOAD_VERBOSE=true +``` + +### Example Configurations + +#### Minimal Memory Footprint + +```bash +# Preload only critical assets +STATIC_PRELOAD_MAX_BYTES=1048576 \ +STATIC_PRELOAD_INCLUDE="*.js,*.css" \ +STATIC_PRELOAD_EXCLUDE="*.map,vendor-*" \ +bun run start +``` + +#### Maximum Performance + +```bash +# Preload all small assets +STATIC_PRELOAD_MAX_BYTES=10485760 \ +bun run start +``` + +#### Debug Mode + +```bash +# With detailed logging +STATIC_PRELOAD_VERBOSE=true \ +bun run start +``` + +### Server Output + +The server displays a clear overview of all loaded assets at startup: + +```txt +šŸ“¦ Loading static assets from ./dist/client... + Max preload size: 5.00 MB + Include patterns: *.js,*.css,*.woff2 + +šŸ“ Preloaded into memory: + /assets/index-a1b2c3d4.js 45.23 kB │ gzip: 15.83 kB + /assets/index-e5f6g7h8.css 12.45 kB │ gzip: 4.36 kB + +šŸ’¾ Served on-demand: + /assets/vendor-i9j0k1l2.js 245.67 kB │ gzip: 86.98 kB + +āœ… Preloaded 2 files (57.68 KB) into memory +ā„¹ļø 1 files will be served on-demand (1 too large, 0 filtered) + +šŸš€ Server running at http://localhost:3000 +``` + +## Testing + +This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: + +```bash +bun run test +``` + +## Styling + +This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. + +## Linting & Formatting + +This project uses [eslint](https://eslint.org/) and [prettier](https://prettier.io/) for linting and formatting. Eslint is configured using [tanstack/eslint-config](https://tanstack.com/config/latest/docs/eslint). The following scripts are available: + +```bash +bun run lint +bun run format +bun run check +``` diff --git a/examples/solid/start-bun/eslint.config.js b/examples/solid/start-bun/eslint.config.js new file mode 100644 index 00000000000..676b32a87ff --- /dev/null +++ b/examples/solid/start-bun/eslint.config.js @@ -0,0 +1,5 @@ +// @ts-check + +import { tanstackConfig } from '@tanstack/eslint-config' + +export default [...tanstackConfig] diff --git a/examples/solid/start-bun/package.json b/examples/solid/start-bun/package.json new file mode 100644 index 00000000000..0693dbf89a7 --- /dev/null +++ b/examples/solid/start-bun/package.json @@ -0,0 +1,41 @@ +{ + "name": "tanstack-solid-start-bun-hosting", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev --port 3000", + "start": "bun run server.ts", + "build": "vite build", + "serve": "vite preview", + "test": "vitest run", + "lint": "eslint", + "format": "prettier", + "check": "prettier --write . && eslint --fix" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.13", + "@tanstack/solid-devtools": "^0.7.0", + "@tanstack/solid-router": "^1.135.2", + "@tanstack/solid-router-devtools": "^1.135.2", + "@tanstack/solid-router-ssr-query": "^1.135.2", + "@tanstack/solid-start": "^1.135.2", + "@tanstack/router-plugin": "^1.135.2", + "solid-js": "^1.9.10", + "tailwindcss": "^4.1.13", + "vite-tsconfig-paths": "^5.1.4" + }, + "devDependencies": { + "@tanstack/eslint-config": "^0.3.2", + "@testing-library/dom": "^10.4.1", + "@solidjs/testing-library": "^0.8.10", + "@types/bun": "^1.2.22", + "@types/node": "22.10.2", + "vite-plugin-solid": "^2.11.10", + "jsdom": "^27.0.0", + "prettier": "^3.6.2", + "typescript": "^5.9.2", + "vite": "^7.1.7", + "vitest": "^3.2.4", + "web-vitals": "^5.1.0" + } +} diff --git a/examples/solid/start-bun/prettier.config.js b/examples/solid/start-bun/prettier.config.js new file mode 100644 index 00000000000..f7279f7ba51 --- /dev/null +++ b/examples/solid/start-bun/prettier.config.js @@ -0,0 +1,10 @@ +// @ts-check + +/** @type {import('prettier').Config} */ +const config = { + semi: false, + singleQuote: true, + trailingComma: 'all', +} + +export default config diff --git a/examples/solid/start-bun/public/favicon.ico b/examples/solid/start-bun/public/favicon.ico new file mode 100644 index 00000000000..a11777cc471 Binary files /dev/null and b/examples/solid/start-bun/public/favicon.ico differ diff --git a/examples/solid/start-bun/public/manifest.json b/examples/solid/start-bun/public/manifest.json new file mode 100644 index 00000000000..078ef501162 --- /dev/null +++ b/examples/solid/start-bun/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "TanStack App", + "name": "Create TanStack App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/solid/start-bun/public/robots.txt b/examples/solid/start-bun/public/robots.txt new file mode 100644 index 00000000000..e9e57dc4d41 --- /dev/null +++ b/examples/solid/start-bun/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/solid/start-bun/server.ts b/examples/solid/start-bun/server.ts new file mode 100644 index 00000000000..be8d3b1ed5a --- /dev/null +++ b/examples/solid/start-bun/server.ts @@ -0,0 +1,556 @@ +/** + * TanStack Start Production Server with Bun + * + * A high-performance production server for TanStack Start applications that + * implements intelligent static asset loading with configurable memory management. + * + * Features: + * - Hybrid loading strategy (preload small files, serve large files on-demand) + * - Configurable file filtering with include/exclude patterns + * - Memory-efficient response generation + * - Production-ready caching headers + * + * Environment Variables: + * + * PORT (number) + * - Server port number + * - Default: 3000 + * + * ASSET_PRELOAD_MAX_SIZE (number) + * - Maximum file size in bytes to preload into memory + * - Files larger than this will be served on-demand from disk + * - Default: 5242880 (5MB) + * - Example: ASSET_PRELOAD_MAX_SIZE=5242880 (5MB) + * + * ASSET_PRELOAD_INCLUDE_PATTERNS (string) + * - Comma-separated list of glob patterns for files to include + * - If specified, only matching files are eligible for preloading + * - Patterns are matched against filenames only, not full paths + * - Example: ASSET_PRELOAD_INCLUDE_PATTERNS="*.js,*.css,*.woff2" + * + * ASSET_PRELOAD_EXCLUDE_PATTERNS (string) + * - Comma-separated list of glob patterns for files to exclude + * - Applied after include patterns + * - Patterns are matched against filenames only, not full paths + * - Example: ASSET_PRELOAD_EXCLUDE_PATTERNS="*.map,*.txt" + * + * ASSET_PRELOAD_VERBOSE_LOGGING (boolean) + * - Enable detailed logging of loaded and skipped files + * - Default: false + * - Set to "true" to enable verbose output + * + * ASSET_PRELOAD_ENABLE_ETAG (boolean) + * - Enable ETag generation for preloaded assets + * - Default: true + * - Set to "false" to disable ETag support + * + * ASSET_PRELOAD_ENABLE_GZIP (boolean) + * - Enable Gzip compression for eligible assets + * - Default: true + * - Set to "false" to disable Gzip compression + * + * ASSET_PRELOAD_GZIP_MIN_SIZE (number) + * - Minimum file size in bytes required for Gzip compression + * - Files smaller than this will not be compressed + * - Default: 1024 (1KB) + * + * ASSET_PRELOAD_GZIP_MIME_TYPES (string) + * - Comma-separated list of MIME types eligible for Gzip compression + * - Supports partial matching for types ending with "/" + * - Default: text/,application/javascript,application/json,application/xml,image/svg+xml + * + * Usage: + * bun run server.ts + */ + +import path from 'node:path' + +// Configuration +const SERVER_PORT = Number(process.env.PORT ?? 3000) +const CLIENT_DIRECTORY = './dist/client' +const SERVER_ENTRY_POINT = './dist/server/server.js' + +// Logging utilities for professional output +const log = { + info: (message: string) => { + console.log(`[INFO] ${message}`) + }, + success: (message: string) => { + console.log(`[SUCCESS] ${message}`) + }, + warning: (message: string) => { + console.log(`[WARNING] ${message}`) + }, + error: (message: string) => { + console.log(`[ERROR] ${message}`) + }, + header: (message: string) => { + console.log(`\n${message}\n`) + }, +} + +// Preloading configuration from environment variables +const MAX_PRELOAD_BYTES = Number( + process.env.ASSET_PRELOAD_MAX_SIZE ?? 5 * 1024 * 1024, // 5MB default +) + +// Parse comma-separated include patterns (no defaults) +const INCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + .map((pattern: string) => convertGlobToRegExp(pattern)) + +// Parse comma-separated exclude patterns (no defaults) +const EXCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + .map((pattern: string) => convertGlobToRegExp(pattern)) + +// Verbose logging flag +const VERBOSE = process.env.ASSET_PRELOAD_VERBOSE_LOGGING === 'true' + +// Optional ETag feature +const ENABLE_ETAG = (process.env.ASSET_PRELOAD_ENABLE_ETAG ?? 'true') === 'true' + +// Optional Gzip feature +const ENABLE_GZIP = (process.env.ASSET_PRELOAD_ENABLE_GZIP ?? 'true') === 'true' +const GZIP_MIN_BYTES = Number(process.env.ASSET_PRELOAD_GZIP_MIN_SIZE ?? 1024) // 1KB +const GZIP_TYPES = ( + process.env.ASSET_PRELOAD_GZIP_MIME_TYPES ?? + 'text/,application/javascript,application/json,application/xml,image/svg+xml' +) + .split(',') + .map((v) => v.trim()) + .filter(Boolean) + +/** + * Convert a simple glob pattern to a regular expression + * Supports * wildcard for matching any characters + */ +function convertGlobToRegExp(globPattern: string): RegExp { + // Escape regex special chars except *, then replace * with .* + const escapedPattern = globPattern + .replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&') + .replace(/\*/g, '.*') + return new RegExp(`^${escapedPattern}$`, 'i') +} + +/** + * Compute ETag for a given data buffer + */ +function computeEtag(data: Uint8Array): string { + const hash = Bun.hash(data) + return `W/"${hash.toString(16)}-${data.byteLength.toString()}"` +} + +/** + * Metadata for preloaded static assets + */ +interface AssetMetadata { + route: string + size: number + type: string +} + +/** + * In-memory asset with ETag and Gzip support + */ +interface InMemoryAsset { + raw: Uint8Array + gz?: Uint8Array + etag?: string + type: string + immutable: boolean + size: number +} + +/** + * Result of static asset preloading process + */ +interface PreloadResult { + routes: Record Response | Promise> + loaded: AssetMetadata[] + skipped: AssetMetadata[] +} + +/** + * Check if a file is eligible for preloading based on configured patterns + */ +function isFileEligibleForPreloading(relativePath: string): boolean { + const fileName = relativePath.split(/[/\\]/).pop() ?? relativePath + + // If include patterns are specified, file must match at least one + if (INCLUDE_PATTERNS.length > 0) { + if (!INCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) { + return false + } + } + + // If exclude patterns are specified, file must not match any + if (EXCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) { + return false + } + + return true +} + +/** + * Check if a MIME type is compressible + */ +function isMimeTypeCompressible(mimeType: string): boolean { + return GZIP_TYPES.some((type) => + type.endsWith('/') ? mimeType.startsWith(type) : mimeType === type, + ) +} + +/** + * Conditionally compress data based on size and MIME type + */ +function compressDataIfAppropriate( + data: Uint8Array, + mimeType: string, +): Uint8Array | undefined { + if (!ENABLE_GZIP) return undefined + if (data.byteLength < GZIP_MIN_BYTES) return undefined + if (!isMimeTypeCompressible(mimeType)) return undefined + try { + return Bun.gzipSync(data.buffer as ArrayBuffer) + } catch { + return undefined + } +} + +/** + * Create response handler function with ETag and Gzip support + */ +function createResponseHandler( + asset: InMemoryAsset, +): (req: Request) => Response { + return (req: Request) => { + const headers: Record = { + 'Content-Type': asset.type, + 'Cache-Control': asset.immutable + ? 'public, max-age=31536000, immutable' + : 'public, max-age=3600', + } + + if (ENABLE_ETAG && asset.etag) { + const ifNone = req.headers.get('if-none-match') + if (ifNone && ifNone === asset.etag) { + return new Response(null, { + status: 304, + headers: { ETag: asset.etag }, + }) + } + headers.ETag = asset.etag + } + + if ( + ENABLE_GZIP && + asset.gz && + req.headers.get('accept-encoding')?.includes('gzip') + ) { + headers['Content-Encoding'] = 'gzip' + headers['Content-Length'] = String(asset.gz.byteLength) + const gzCopy = new Uint8Array(asset.gz) + return new Response(gzCopy, { status: 200, headers }) + } + + headers['Content-Length'] = String(asset.raw.byteLength) + const rawCopy = new Uint8Array(asset.raw) + return new Response(rawCopy, { status: 200, headers }) + } +} + +/** + * Create composite glob pattern from include patterns + */ +function createCompositeGlobPattern(): Bun.Glob { + const raw = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + if (raw.length === 0) return new Bun.Glob('**/*') + if (raw.length === 1) return new Bun.Glob(raw[0]) + return new Bun.Glob(`{${raw.join(',')}}`) +} + +/** + * Initialize static routes with intelligent preloading strategy + * Small files are loaded into memory, large files are served on-demand + */ +async function initializeStaticRoutes( + clientDirectory: string, +): Promise { + const routes: Record Response | Promise> = + {} + const loaded: AssetMetadata[] = [] + const skipped: AssetMetadata[] = [] + + log.info(`Loading static assets from ${clientDirectory}...`) + if (VERBOSE) { + console.log( + `Max preload size: ${(MAX_PRELOAD_BYTES / 1024 / 1024).toFixed(2)} MB`, + ) + if (INCLUDE_PATTERNS.length > 0) { + console.log( + `Include patterns: ${process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? ''}`, + ) + } + if (EXCLUDE_PATTERNS.length > 0) { + console.log( + `Exclude patterns: ${process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? ''}`, + ) + } + } + + let totalPreloadedBytes = 0 + + try { + const glob = createCompositeGlobPattern() + for await (const relativePath of glob.scan({ cwd: clientDirectory })) { + const filepath = path.join(clientDirectory, relativePath) + const route = `/${relativePath.split(path.sep).join(path.posix.sep)}` + + try { + // Get file metadata + const file = Bun.file(filepath) + + // Skip if file doesn't exist or is empty + if (!(await file.exists()) || file.size === 0) { + continue + } + + const metadata: AssetMetadata = { + route, + size: file.size, + type: file.type || 'application/octet-stream', + } + + // Determine if file should be preloaded + const matchesPattern = isFileEligibleForPreloading(relativePath) + const withinSizeLimit = file.size <= MAX_PRELOAD_BYTES + + if (matchesPattern && withinSizeLimit) { + // Preload small files into memory with ETag and Gzip support + const bytes = new Uint8Array(await file.arrayBuffer()) + const gz = compressDataIfAppropriate(bytes, metadata.type) + const etag = ENABLE_ETAG ? computeEtag(bytes) : undefined + const asset: InMemoryAsset = { + raw: bytes, + gz, + etag, + type: metadata.type, + immutable: true, + size: bytes.byteLength, + } + routes[route] = createResponseHandler(asset) + + loaded.push({ ...metadata, size: bytes.byteLength }) + totalPreloadedBytes += bytes.byteLength + } else { + // Serve large or filtered files on-demand + routes[route] = () => { + const fileOnDemand = Bun.file(filepath) + return new Response(fileOnDemand, { + headers: { + 'Content-Type': metadata.type, + 'Cache-Control': 'public, max-age=3600', + }, + }) + } + + skipped.push(metadata) + } + } catch (error: unknown) { + if (error instanceof Error && error.name !== 'EISDIR') { + log.error(`Failed to load ${filepath}: ${error.message}`) + } + } + } + + // Show detailed file overview only when verbose mode is enabled + if (VERBOSE && (loaded.length > 0 || skipped.length > 0)) { + const allFiles = [...loaded, ...skipped].sort((a, b) => + a.route.localeCompare(b.route), + ) + + // Calculate max path length for alignment + const maxPathLength = Math.min( + Math.max(...allFiles.map((f) => f.route.length)), + 60, + ) + + // Format file size with KB and actual gzip size + const formatFileSize = (bytes: number, gzBytes?: number) => { + const kb = bytes / 1024 + const sizeStr = kb < 100 ? kb.toFixed(2) : kb.toFixed(1) + + if (gzBytes !== undefined) { + const gzKb = gzBytes / 1024 + const gzStr = gzKb < 100 ? gzKb.toFixed(2) : gzKb.toFixed(1) + return { + size: sizeStr, + gzip: gzStr, + } + } + + // Rough gzip estimation (typically 30-70% compression) if no actual gzip data + const gzipKb = kb * 0.35 + return { + size: sizeStr, + gzip: gzipKb < 100 ? gzipKb.toFixed(2) : gzipKb.toFixed(1), + } + } + + if (loaded.length > 0) { + console.log('\nšŸ“ Preloaded into memory:') + console.log( + 'Path │ Size │ Gzip Size', + ) + loaded + .sort((a, b) => a.route.localeCompare(b.route)) + .forEach((file) => { + const { size, gzip } = formatFileSize(file.size) + const paddedPath = file.route.padEnd(maxPathLength) + const sizeStr = `${size.padStart(7)} kB` + const gzipStr = `${gzip.padStart(7)} kB` + console.log(`${paddedPath} │ ${sizeStr} │ ${gzipStr}`) + }) + } + + if (skipped.length > 0) { + console.log('\nšŸ’¾ Served on-demand:') + console.log( + 'Path │ Size │ Gzip Size', + ) + skipped + .sort((a, b) => a.route.localeCompare(b.route)) + .forEach((file) => { + const { size, gzip } = formatFileSize(file.size) + const paddedPath = file.route.padEnd(maxPathLength) + const sizeStr = `${size.padStart(7)} kB` + const gzipStr = `${gzip.padStart(7)} kB` + console.log(`${paddedPath} │ ${sizeStr} │ ${gzipStr}`) + }) + } + } + + // Show detailed verbose info if enabled + if (VERBOSE) { + if (loaded.length > 0 || skipped.length > 0) { + const allFiles = [...loaded, ...skipped].sort((a, b) => + a.route.localeCompare(b.route), + ) + console.log('\nšŸ“Š Detailed file information:') + console.log( + 'Status │ Path │ MIME Type │ Reason', + ) + allFiles.forEach((file) => { + const isPreloaded = loaded.includes(file) + const status = isPreloaded ? 'MEMORY' : 'ON-DEMAND' + const reason = + !isPreloaded && file.size > MAX_PRELOAD_BYTES + ? 'too large' + : !isPreloaded + ? 'filtered' + : 'preloaded' + const route = + file.route.length > 30 + ? file.route.substring(0, 27) + '...' + : file.route + console.log( + `${status.padEnd(12)} │ ${route.padEnd(30)} │ ${file.type.padEnd(28)} │ ${reason.padEnd(10)}`, + ) + }) + } else { + console.log('\nšŸ“Š No files found to display') + } + } + + // Log summary after the file list + console.log() // Empty line for separation + if (loaded.length > 0) { + log.success( + `Preloaded ${String(loaded.length)} files (${(totalPreloadedBytes / 1024 / 1024).toFixed(2)} MB) into memory`, + ) + } else { + log.info('No files preloaded into memory') + } + + if (skipped.length > 0) { + const tooLarge = skipped.filter((f) => f.size > MAX_PRELOAD_BYTES).length + const filtered = skipped.length - tooLarge + log.info( + `${String(skipped.length)} files will be served on-demand (${String(tooLarge)} too large, ${String(filtered)} filtered)`, + ) + } + } catch (error) { + log.error( + `Failed to load static files from ${clientDirectory}: ${String(error)}`, + ) + } + + return { routes, loaded, skipped } +} + +/** + * Initialize the server + */ +async function initializeServer() { + log.header('Starting Production Server') + + // Load TanStack Start server handler + let handler: { fetch: (request: Request) => Response | Promise } + try { + const serverModule = (await import(SERVER_ENTRY_POINT)) as { + default: { fetch: (request: Request) => Response | Promise } + } + handler = serverModule.default + log.success('TanStack Start application handler initialized') + } catch (error) { + log.error(`Failed to load server handler: ${String(error)}`) + process.exit(1) + } + + // Build static routes with intelligent preloading + const { routes } = await initializeStaticRoutes(CLIENT_DIRECTORY) + + // Create Bun server + const server = Bun.serve({ + port: SERVER_PORT, + + routes: { + // Serve static assets (preloaded or on-demand) + ...routes, + + // Fallback to TanStack Start handler for all other routes + '/*': (req: Request) => { + try { + return handler.fetch(req) + } catch (error) { + log.error(`Server handler error: ${String(error)}`) + return new Response('Internal Server Error', { status: 500 }) + } + }, + }, + + // Global error handler + error(error) { + log.error( + `Uncaught server error: ${error instanceof Error ? error.message : String(error)}`, + ) + return new Response('Internal Server Error', { status: 500 }) + }, + }) + + log.success(`Server listening on http://localhost:${String(server.port)}`) +} + +// Initialize the server +initializeServer().catch((error: unknown) => { + log.error(`Failed to start server: ${String(error)}`) + process.exit(1) +}) diff --git a/examples/solid/start-bun/src/components/Header.tsx b/examples/solid/start-bun/src/components/Header.tsx new file mode 100644 index 00000000000..6591627eaeb --- /dev/null +++ b/examples/solid/start-bun/src/components/Header.tsx @@ -0,0 +1,21 @@ +import { Link } from '@tanstack/solid-router' + +export default function Header() { + return ( +
+ +
+ ) +} diff --git a/examples/solid/start-bun/src/logo.svg b/examples/solid/start-bun/src/logo.svg new file mode 100644 index 00000000000..76c8c2ab6ce --- /dev/null +++ b/examples/solid/start-bun/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/solid/start-bun/src/routeTree.gen.ts b/examples/solid/start-bun/src/routeTree.gen.ts new file mode 100644 index 00000000000..19009b2f5bb --- /dev/null +++ b/examples/solid/start-bun/src/routeTree.gen.ts @@ -0,0 +1,135 @@ +/* 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 IndexRouteImport } from './routes/index' +import { Route as ApiDemoNamesRouteImport } from './routes/api.demo-names' +import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.server-funcs' +import { Route as DemoStartApiRequestRouteImport } from './routes/demo.start.api-request' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiDemoNamesRoute = ApiDemoNamesRouteImport.update({ + id: '/api/demo-names', + path: '/api/demo-names', + getParentRoute: () => rootRouteImport, +} as any) +const DemoStartServerFuncsRoute = DemoStartServerFuncsRouteImport.update({ + id: '/demo/start/server-funcs', + path: '/demo/start/server-funcs', + getParentRoute: () => rootRouteImport, +} as any) +const DemoStartApiRequestRoute = DemoStartApiRequestRouteImport.update({ + id: '/demo/start/api-request', + path: '/demo/start/api-request', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/api/demo-names': typeof ApiDemoNamesRoute + '/demo/start/api-request': typeof DemoStartApiRequestRoute + '/demo/start/server-funcs': typeof DemoStartServerFuncsRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/api/demo-names': typeof ApiDemoNamesRoute + '/demo/start/api-request': typeof DemoStartApiRequestRoute + '/demo/start/server-funcs': typeof DemoStartServerFuncsRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/api/demo-names': typeof ApiDemoNamesRoute + '/demo/start/api-request': typeof DemoStartApiRequestRoute + '/demo/start/server-funcs': typeof DemoStartServerFuncsRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/api/demo-names' + | '/demo/start/api-request' + | '/demo/start/server-funcs' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/api/demo-names' + | '/demo/start/api-request' + | '/demo/start/server-funcs' + id: + | '__root__' + | '/' + | '/api/demo-names' + | '/demo/start/api-request' + | '/demo/start/server-funcs' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + ApiDemoNamesRoute: typeof ApiDemoNamesRoute + DemoStartApiRequestRoute: typeof DemoStartApiRequestRoute + DemoStartServerFuncsRoute: typeof DemoStartServerFuncsRoute +} + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/api/demo-names': { + id: '/api/demo-names' + path: '/api/demo-names' + fullPath: '/api/demo-names' + preLoaderRoute: typeof ApiDemoNamesRouteImport + parentRoute: typeof rootRouteImport + } + '/demo/start/server-funcs': { + id: '/demo/start/server-funcs' + path: '/demo/start/server-funcs' + fullPath: '/demo/start/server-funcs' + preLoaderRoute: typeof DemoStartServerFuncsRouteImport + parentRoute: typeof rootRouteImport + } + '/demo/start/api-request': { + id: '/demo/start/api-request' + path: '/demo/start/api-request' + fullPath: '/demo/start/api-request' + preLoaderRoute: typeof DemoStartApiRequestRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + ApiDemoNamesRoute: ApiDemoNamesRoute, + DemoStartApiRequestRoute: DemoStartApiRequestRoute, + DemoStartServerFuncsRoute: DemoStartServerFuncsRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/examples/solid/start-bun/src/router.tsx b/examples/solid/start-bun/src/router.tsx new file mode 100644 index 00000000000..d9d23a5d844 --- /dev/null +++ b/examples/solid/start-bun/src/router.tsx @@ -0,0 +1,13 @@ +import { createRouter } from '@tanstack/solid-router' + +// Import the generated route tree +import { routeTree } from './routeTree.gen' + +// Create a new router instance +export const getRouter = () => { + return createRouter({ + routeTree, + scrollRestoration: true, + defaultPreloadStaleTime: 0, + }) +} diff --git a/examples/solid/start-bun/src/routes/__root.tsx b/examples/solid/start-bun/src/routes/__root.tsx new file mode 100644 index 00000000000..99c515ded4f --- /dev/null +++ b/examples/solid/start-bun/src/routes/__root.tsx @@ -0,0 +1,61 @@ +import { TanStackDevtools } from '@tanstack/solid-devtools' +import { HeadContent, Scripts, createRootRoute } from '@tanstack/solid-router' +import { TanStackRouterDevtoolsPanel } from '@tanstack/solid-router-devtools' + +import { HydrationScript } from 'solid-js/web' +import Header from '../components/Header' + +import appCss from '../styles.css?url' +import type { JSX } from 'solid-js' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'TanStack Start Starter', + }, + ], + links: [ + { + rel: 'stylesheet', + href: appCss, + }, + ], + }), + + shellComponent: RootDocument, +}) + +function RootDocument({ children }: { children: JSX.Element }) { + return ( + + + + + + +
+ {children} + , + }, + ]} + /> + + + + ) +} diff --git a/examples/solid/start-bun/src/routes/api.demo-names.ts b/examples/solid/start-bun/src/routes/api.demo-names.ts new file mode 100644 index 00000000000..8dd0fea2970 --- /dev/null +++ b/examples/solid/start-bun/src/routes/api.demo-names.ts @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/api/demo-names')({ + server: { + handlers: { + GET: () => { + return new Response(JSON.stringify(['Alice', 'Bob', 'Charlie']), { + headers: { + 'Content-Type': 'application/json', + }, + }) + }, + }, + }, +}) diff --git a/examples/solid/start-bun/src/routes/demo.start.api-request.tsx b/examples/solid/start-bun/src/routes/demo.start.api-request.tsx new file mode 100644 index 00000000000..e152aa31568 --- /dev/null +++ b/examples/solid/start-bun/src/routes/demo.start.api-request.tsx @@ -0,0 +1,42 @@ +import { createEffect, createSignal } from 'solid-js' +import { createFileRoute } from '@tanstack/solid-router' + +function getNames() { + return fetch('/api/demo-names').then((res) => res.json()) +} + +export const Route = createFileRoute('/demo/start/api-request')({ + component: Home, +}) + +function Home() { + const [names, setNames] = createSignal>([]) + + createEffect(() => { + getNames().then(setNames) + }, []) + + return ( +
+
+

Start API Request Demo - Names List

+
    + {names().map((name) => ( +
  • + {name} +
  • + ))} +
+
+
+ ) +} diff --git a/examples/solid/start-bun/src/routes/demo.start.server-funcs.tsx b/examples/solid/start-bun/src/routes/demo.start.server-funcs.tsx new file mode 100644 index 00000000000..4e96ec85f3e --- /dev/null +++ b/examples/solid/start-bun/src/routes/demo.start.server-funcs.tsx @@ -0,0 +1,96 @@ +import fs from 'node:fs' +import { createSignal } from 'solid-js' +import { createFileRoute, useRouter } from '@tanstack/solid-router' +import { createServerFn } from '@tanstack/solid-start' + +const filePath = 'todos.json' + +async function readTodos() { + return JSON.parse( + await fs.promises.readFile(filePath, 'utf-8').catch(() => + JSON.stringify( + [ + { id: 1, name: 'Get groceries' }, + { id: 2, name: 'Buy a new phone' }, + ], + null, + 2, + ), + ), + ) +} + +const getTodos = createServerFn({ + method: 'GET', +}).handler(async () => await readTodos()) + +const addTodo = createServerFn({ method: 'POST' }) + .inputValidator((d: string) => d) + .handler(async ({ data }) => { + const todos = await readTodos() + todos.push({ id: todos.length + 1, name: data }) + await fs.promises.writeFile(filePath, JSON.stringify(todos, null, 2)) + return todos + }) + +export const Route = createFileRoute('/demo/start/server-funcs')({ + component: Home, + loader: async () => await getTodos(), +}) + +function Home() { + const router = useRouter() + const todos = Route.useLoaderData() + + const [todo, setTodo] = createSignal('') + + const submitTodo = async () => { + await addTodo({ data: todo() }) + setTodo('') + await router.invalidate() + } + + return ( +
+
+

Start Server Functions - Todo Example

+
    + {todos().map((t: any) => ( +
  • + {t.name} +
  • + ))} +
+
+ setTodo(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + submitTodo() + } + }} + placeholder="Enter a new todo..." + class="w-full px-4 py-3 rounded-lg border border-white/20 bg-white/10 backdrop-blur-sm text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent" + /> + +
+
+
+ ) +} diff --git a/examples/solid/start-bun/src/routes/index.tsx b/examples/solid/start-bun/src/routes/index.tsx new file mode 100644 index 00000000000..a883efcc92c --- /dev/null +++ b/examples/solid/start-bun/src/routes/index.tsx @@ -0,0 +1,39 @@ +import { createFileRoute } from '@tanstack/solid-router' +import logo from '../logo.svg' + +export const Route = createFileRoute('/')({ + component: App, +}) + +function App() { + return ( +
+
+ logo +

+ Edit src/routes/index.tsx and save to reload. +

+ + Learn Solid + + + Learn TanStack + +
+
+ ) +} diff --git a/examples/solid/start-bun/src/styles.css b/examples/solid/start-bun/src/styles.css new file mode 100644 index 00000000000..06f1bca4b05 --- /dev/null +++ b/examples/solid/start-bun/src/styles.css @@ -0,0 +1,15 @@ +@import 'tailwindcss'; + +body { + @apply m-0; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: + source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +} diff --git a/examples/solid/start-bun/tsconfig.json b/examples/solid/start-bun/tsconfig.json new file mode 100644 index 00000000000..70c9888deae --- /dev/null +++ b/examples/solid/start-bun/tsconfig.json @@ -0,0 +1,36 @@ +{ + "include": [ + "**/*.ts", + "**/*.tsx", + "eslint.config.js", + "prettier.config.js", + "vite.config.js" + ], + + "compilerOptions": { + "target": "ES2022", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client", "bun"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": false, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/examples/solid/start-bun/vite.config.ts b/examples/solid/start-bun/vite.config.ts new file mode 100644 index 00000000000..0a49792ba5b --- /dev/null +++ b/examples/solid/start-bun/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import viteSolid from 'vite-plugin-solid' +import viteTsConfigPaths from 'vite-tsconfig-paths' +import tailwindcss from '@tailwindcss/vite' + +const config = defineConfig({ + plugins: [ + // this is the plugin that enables path aliases + viteTsConfigPaths({ + projects: ['./tsconfig.json'], + }), + tailwindcss(), + tanstackStart(), + viteSolid({ ssr: true }), + ], +}) + +export default config diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 868c3a3cd0f..4e4433c899a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7627,6 +7627,76 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + examples/solid/start-bun: + dependencies: + '@tailwindcss/vite': + specifier: ^4.1.13 + version: 4.1.17(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + '@tanstack/solid-devtools': + specifier: ^0.7.0 + version: 0.7.14(csstype@3.1.3)(solid-js@1.9.10) + '@tanstack/solid-router': + specifier: ^1.135.2 + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + '@tanstack/solid-router-ssr-query': + specifier: workspace:* + version: link:../../../packages/solid-router-ssr-query + '@tanstack/solid-start': + specifier: workspace:* + version: link:../../../packages/solid-start + solid-js: + specifier: 1.9.10 + version: 1.9.10 + tailwindcss: + specifier: ^4.1.13 + version: 4.1.17 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + devDependencies: + '@solidjs/testing-library': + specifier: ^0.8.10 + version: 0.8.10(solid-js@1.9.10) + '@tanstack/eslint-config': + specifier: ^0.3.2 + version: 0.3.2(@typescript-eslint/utils@8.44.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0(jiti@2.6.1))(typescript@5.9.2) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 + '@types/bun': + specifier: ^1.2.22 + version: 1.2.22(@types/react@19.2.2) + '@types/node': + specifier: 22.10.2 + version: 22.10.2 + jsdom: + specifier: ^27.0.0 + version: 27.0.0(postcss@8.5.6) + prettier: + specifier: ^3.6.2 + version: 3.6.2 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + vite: + specifier: ^7.1.7 + version: 7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + vite-plugin-solid: + specifier: ^2.11.10 + version: 2.11.10(@testing-library/jest-dom@6.6.3)(solid-js@1.9.10)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) + web-vitals: + specifier: ^5.1.0 + version: 5.1.0 + examples/solid/start-convex-better-auth: dependencies: '@convex-dev/better-auth': @@ -12426,11 +12496,21 @@ packages: peerDependencies: solid-js: 1.9.10 + '@solid-primitives/event-listener@2.4.3': + resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==} + peerDependencies: + solid-js: 1.9.10 + '@solid-primitives/keyboard@1.3.0': resolution: {integrity: sha512-0QX9O3eUaQorNNmXZn8a4efSByayIScVq+iGSwheD7m3SL/ACLM5oZlCNpTPLcemnVVfUPAHFiViEj86XpN5qw==} peerDependencies: solid-js: 1.9.10 + '@solid-primitives/keyboard@1.3.3': + resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==} + peerDependencies: + solid-js: 1.9.10 + '@solid-primitives/media@2.3.0': resolution: {integrity: sha512-7+C3wfbWnGE/WPoNsqcp/EeOP2aNNB92RCpsWhBth8E5lZo/J+rK6jMb7umVsK0zguT8HBpeXp1pFyFbcsHStA==} peerDependencies: @@ -12451,11 +12531,21 @@ packages: peerDependencies: solid-js: 1.9.10 + '@solid-primitives/resize-observer@2.1.3': + resolution: {integrity: sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==} + peerDependencies: + solid-js: 1.9.10 + '@solid-primitives/rootless@1.5.0': resolution: {integrity: sha512-YJ+EveQeDv9DLqfDKfsPAAGy2x3vBruoD23yn+nD2dT84QjoBxWT1T0qA0TMFjek6/xuN3flqnHtQ4r++4zdjg==} peerDependencies: solid-js: 1.9.10 + '@solid-primitives/rootless@1.5.2': + resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==} + peerDependencies: + solid-js: 1.9.10 + '@solid-primitives/scheduled@1.5.0': resolution: {integrity: sha512-RVw24IRNh1FQ4DCMb3OahB70tXIwc5vH8nhR4nNPsXwUPQeuOkLsDI5BlxaPk0vyZgqw9lDpufgI3HnPwplgDw==} peerDependencies: @@ -12471,6 +12561,11 @@ packages: peerDependencies: solid-js: 1.9.10 + '@solid-primitives/static-store@0.1.2': + resolution: {integrity: sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==} + peerDependencies: + solid-js: 1.9.10 + '@solid-primitives/styles@0.0.114': resolution: {integrity: sha512-SFXr16mgr6LvZAIj6L7i59HHg+prAmIF8VP/U3C6jSHz68Eh1G71vaWr9vlJVpy/j6bh1N8QUzu5CgtvIC92OQ==} peerDependencies: @@ -12481,6 +12576,11 @@ packages: peerDependencies: solid-js: 1.9.10 + '@solid-primitives/utils@6.3.2': + resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} + peerDependencies: + solid-js: 1.9.10 + '@solidjs/meta@0.29.4': resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==} peerDependencies: @@ -12982,22 +13082,46 @@ packages: resolution: {integrity: sha512-7Wwfw6wBv2Kc+OBNIJQzBSJ6q7GABtwVT+VOQ/7/Gl7z8z1rtEYUZrxUrNvbbrHY+J5/WNZNZjJjTWDf8nTUBw==} engines: {node: '>=18'} + '@tanstack/devtools-client@0.0.4': + resolution: {integrity: sha512-LefnH9KE9uRDEWifc3QDcooskA8ikfs41bybDTgpYQpyTUspZnaEdUdya9Hry0KYxZ8nos0S3nNbsP79KHqr6Q==} + engines: {node: '>=18'} + '@tanstack/devtools-event-bus@0.3.2': resolution: {integrity: sha512-yJT2As/drc+Epu0nsqCsJaKaLcaNGufiNxSlp/+/oeTD0jsBxF9/PJBfh66XVpYXkKr97b8689mSu7QMef0Rrw==} engines: {node: '>=18'} + '@tanstack/devtools-event-bus@0.3.3': + resolution: {integrity: sha512-lWl88uLAz7ZhwNdLH6A3tBOSEuBCrvnY9Fzr5JPdzJRFdM5ZFdyNWz1Bf5l/F3GU57VodrN0KCFi9OA26H5Kpg==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-client@0.3.4': + resolution: {integrity: sha512-eq+PpuutUyubXu+ycC1GIiVwBs86NF/8yYJJAKSpPcJLWl6R/761F1H4F/9ziX6zKezltFUH1ah3Cz8Ah+KJrw==} + engines: {node: '>=18'} + '@tanstack/devtools-ui@0.3.5': resolution: {integrity: sha512-DU8OfLntngnph+Tb7ivQvh4F4w+rDu6r01fXlhjq/Nmgdr0gtsOox4kdmyq5rCs+C6aPgP3M7+BE+fv4dN+VvA==} engines: {node: '>=18'} peerDependencies: solid-js: 1.9.10 + '@tanstack/devtools-ui@0.4.4': + resolution: {integrity: sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg==} + engines: {node: '>=18'} + peerDependencies: + solid-js: 1.9.10 + '@tanstack/devtools@0.6.14': resolution: {integrity: sha512-dOtHoeLjjcHeNscu+ZEf89EFboQsy0ggb6pf8Sha59qBUeQbjUsaAvwP8Ogwg89oJxFQbTP7DKYNBNw5CxlNEA==} engines: {node: '>=18'} peerDependencies: solid-js: 1.9.10 + '@tanstack/devtools@0.8.1': + resolution: {integrity: sha512-ZyKX+IJvMaTY0TywdA6r6kbD6V+0dxWQVDGC3jd2q4RP9OMHzpXd0cKreDvqOGrAPtEUXpGBxW0AtsaauDWurQ==} + engines: {node: '>=18'} + peerDependencies: + solid-js: 1.9.10 + '@tanstack/eslint-config@0.3.2': resolution: {integrity: sha512-2g+PuGR3GuvvCiR3xZs+IMqAvnYU9bvH+jRml0BFBSxHBj22xFCTNvJWhvgj7uICFF9IchDkFUto91xDPMu5cg==} engines: {node: '>=18'} @@ -13056,6 +13180,12 @@ packages: react: ^19.2.0 react-dom: ^19.2.0 + '@tanstack/solid-devtools@0.7.14': + resolution: {integrity: sha512-hyaB29d1fU2n9v2XeFEB6t2k5gKionK8mxxb9MNtF7qJheZ6z9259jU8OsBdA/f6v2oYck1K1Ia9+ooJsdy4jg==} + engines: {node: '>=18'} + peerDependencies: + solid-js: 1.9.10 + '@tanstack/solid-query-devtools@5.72.2': resolution: {integrity: sha512-duYFN6EMsV0iwz1vV60GvxsQy3PXycgHhrucaik2yvJlRKrEYuHTYGqpRTy+P3JrFZkEzVLjevMnbzR4IIt5vw==} peerDependencies: @@ -23541,6 +23671,11 @@ snapshots: '@solid-primitives/utils': 6.3.0(solid-js@1.9.10) solid-js: 1.9.10 + '@solid-primitives/event-listener@2.4.3(solid-js@1.9.10)': + dependencies: + '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) + solid-js: 1.9.10 + '@solid-primitives/keyboard@1.3.0(solid-js@1.9.10)': dependencies: '@solid-primitives/event-listener': 2.4.0(solid-js@1.9.10) @@ -23548,6 +23683,13 @@ snapshots: '@solid-primitives/utils': 6.3.0(solid-js@1.9.10) solid-js: 1.9.10 + '@solid-primitives/keyboard@1.3.3(solid-js@1.9.10)': + dependencies: + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) + '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) + solid-js: 1.9.10 + '@solid-primitives/media@2.3.0(solid-js@1.9.10)': dependencies: '@solid-primitives/event-listener': 2.4.0(solid-js@1.9.10) @@ -23573,11 +23715,24 @@ snapshots: '@solid-primitives/utils': 6.3.0(solid-js@1.9.10) solid-js: 1.9.10 + '@solid-primitives/resize-observer@2.1.3(solid-js@1.9.10)': + dependencies: + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.10) + '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) + solid-js: 1.9.10 + '@solid-primitives/rootless@1.5.0(solid-js@1.9.10)': dependencies: '@solid-primitives/utils': 6.3.0(solid-js@1.9.10) solid-js: 1.9.10 + '@solid-primitives/rootless@1.5.2(solid-js@1.9.10)': + dependencies: + '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) + solid-js: 1.9.10 + '@solid-primitives/scheduled@1.5.0(solid-js@1.9.10)': dependencies: solid-js: 1.9.10 @@ -23592,6 +23747,11 @@ snapshots: '@solid-primitives/utils': 6.3.0(solid-js@1.9.10) solid-js: 1.9.10 + '@solid-primitives/static-store@0.1.2(solid-js@1.9.10)': + dependencies: + '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) + solid-js: 1.9.10 + '@solid-primitives/styles@0.0.114(solid-js@1.9.10)': dependencies: '@solid-primitives/rootless': 1.5.0(solid-js@1.9.10) @@ -23602,6 +23762,10 @@ snapshots: dependencies: solid-js: 1.9.10 + '@solid-primitives/utils@6.3.2(solid-js@1.9.10)': + dependencies: + solid-js: 1.9.10 + '@solidjs/meta@0.29.4(solid-js@1.9.10)': dependencies: solid-js: 1.9.10 @@ -24029,6 +24193,10 @@ snapshots: - typescript - vite + '@tanstack/devtools-client@0.0.4': + dependencies: + '@tanstack/devtools-event-client': 0.3.4 + '@tanstack/devtools-event-bus@0.3.2': dependencies: ws: 8.18.3 @@ -24036,6 +24204,15 @@ snapshots: - bufferutil - utf-8-validate + '@tanstack/devtools-event-bus@0.3.3': + dependencies: + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tanstack/devtools-event-client@0.3.4': {} + '@tanstack/devtools-ui@0.3.5(csstype@3.1.3)(solid-js@1.9.10)': dependencies: clsx: 2.1.1 @@ -24044,6 +24221,14 @@ snapshots: transitivePeerDependencies: - csstype + '@tanstack/devtools-ui@0.4.4(csstype@3.1.3)(solid-js@1.9.10)': + dependencies: + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + solid-js: 1.9.10 + transitivePeerDependencies: + - csstype + '@tanstack/devtools@0.6.14(csstype@3.1.3)(solid-js@1.9.10)': dependencies: '@solid-primitives/keyboard': 1.3.0(solid-js@1.9.10) @@ -24057,6 +24242,22 @@ snapshots: - csstype - utf-8-validate + '@tanstack/devtools@0.8.1(csstype@3.1.3)(solid-js@1.9.10)': + dependencies: + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) + '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.10) + '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.10) + '@tanstack/devtools-client': 0.0.4 + '@tanstack/devtools-event-bus': 0.3.3 + '@tanstack/devtools-ui': 0.4.4(csstype@3.1.3)(solid-js@1.9.10) + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + solid-js: 1.9.10 + transitivePeerDependencies: + - bufferutil + - csstype + - utf-8-validate + '@tanstack/eslint-config@0.3.2(@typescript-eslint/utils@8.44.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@eslint/js': 9.36.0 @@ -24133,6 +24334,15 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) + '@tanstack/solid-devtools@0.7.14(csstype@3.1.3)(solid-js@1.9.10)': + dependencies: + '@tanstack/devtools': 0.8.1(csstype@3.1.3)(solid-js@1.9.10) + solid-js: 1.9.10 + transitivePeerDependencies: + - bufferutil + - csstype + - utf-8-validate + '@tanstack/solid-query-devtools@5.72.2(@tanstack/solid-query@5.90.9(solid-js@1.9.10))(solid-js@1.9.10)': dependencies: '@tanstack/query-devtools': 5.72.2