commit 02a4264bc366e92107cae5f7f138a324fc88115f Author: Shishant Biswas Date: Sat Sep 5 04:33:55 2026 +0530 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..539815e --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + + +sswr +swr +swrv +swrev +# reference \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2a52ae7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Shishant Biswas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..63387e9 --- /dev/null +++ b/README.md @@ -0,0 +1,281 @@ +# Supwr - Framework-Agnostic SWR Library (Under development) + +A modern, framework-agnostic Stale-While-Revalidate (SWR) data fetching library with adapters for React, Svelte, Solid, and Vue. + +## Features + +### Core Library (`@supwr/core`) +- **Framework-agnostic**: Core logic separated from framework-specific code +- **Advanced Caching**: LRU cache with TTL support +- **Request Deduplication**: Prevent duplicate requests automatically +- **Event System**: Subscribe to state changes +- **Mutation Support**: Optimistic updates with rollback +- **Revalidation Strategies**: Focus, reconnect, interval-based +- **TypeScript**: Full type safety throughout +- **Error Handling**: Comprehensive error management with retry logic + +### Framework Adapters +- **React** (`@supwr/react`): Hooks-based API with `useSyncExternalStore` +- **Svelte** (`@supwr/svelte`): Store-based API with Svelte reactivity +- **Solid** (`@supwr/solid`): Signal-based API with Solid reactivity +- **Vue** (`@supwr/vue`): Composable API with Vue 3 reactivity + +## Installation + +### Core Library +```bash +npm install @supwr/core +``` + +### Framework Adapters +```bash +# React +npm install @supwr/react + +# Svelte +npm install @supwr/svelte + +# Solid +npm install @supwr/solid + +# Vue +npm install @supwr/vue +``` + +## Quick Start + +### Core Usage +```typescript +import { createSWR } from '@supwr/core' + +const swr = createSWR({ + fetcher: (key) => fetch(key).then(res => res.json()), + refreshInterval: 5000, + revalidateOnFocus: true +}) + +// Subscribe to changes +const unsubscribe = swr.subscribe('/api/user', (data, error, isValidating, isLoading) => { + console.log('Data:', data) + console.log('Loading:', isLoading) +}) + +// Manual mutation +await swr.mutate('/api/user', { name: 'New Name' }) +``` + +### React Adapter +```tsx +import useSWR from '@supwr/react' + +function UserProfile() { + const { data, error, isLoading, mutate } = useSWR( + '/api/user', + (key) => fetch(key).then(res => res.json()) + ) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return ( +
+

{data?.name}

+ +
+ ) +} +``` + +### Svelte Adapter +```svelte + + +{#if $isLoading} +
Loading...
+{:else if $error} +
Error: {$error.message}
+{:else} +
+

{$data?.name}

+ +
+{/if} +``` + +### Solid Adapter +```tsx +import { createSWRSignals } from '@supwr/solid' + +function UserProfile() { + const { data, error, isLoading, mutate } = createSWRSignals( + '/api/user', + (key) => fetch(key).then(res => res.json()) + ) + + return ( +
+ +
Loading...
+
+ +
Error: {error()?.message}
+
+ +
+

{data()?.name}

+ +
+
+
+ ) +} +``` + +### Vue Adapter +```vue + + + +``` + +## Architecture + +### Core Components +- **Cache**: LRU cache with TTL and automatic cleanup +- **Events**: Key-specific and global event management +- **Fetcher**: Request deduplication and abort support +- **Revalidation**: Multiple revalidation strategies +- **Mutation**: Optimistic updates with rollback +- **Configuration**: Extensible configuration system + +### Adapter Pattern +Each adapter: +1. Imports the core library +2. Provides framework-specific reactivity integration +3. Exports idiomatic API for the framework +4. Handles framework lifecycle management + +## API Reference + +### Core API +```typescript +interface SWRCore { + get(key: Key): CacheEntry | undefined + subscribe(key: Key, callback: Callback): Unsubscribe + mutate(key: Key, data?, options?): Promise + revalidate(key: Key, options?): Promise + delete(key: Key): void + clear(): void +} +``` + +### Configuration Options +```typescript +interface SWRConfig { + fetcher?: Fetcher + ttl?: number + refreshInterval?: number + revalidateOnFocus?: boolean + revalidateOnReconnect?: boolean + dedupingInterval?: number + errorRetryCount?: number + errorRetryInterval?: number + loadingTimeout?: number + onSuccess?: (data: Data, key: Key) => void + onError?: (error: Error, key: Key) => void + onErrorRetry?: (error, key, config, revalidate, options) => void + isPaused?: () => boolean + compare?: (a: Data, b: Data) => boolean + fallback?: Record +} +``` + +## Development + +### Setup +```bash +# Clone repository +git clone +cd supwr + +# Install dependencies +npm install + +# Build all packages +npm run build + +# Run tests +npm run test + +# Start development +npm run dev +``` + +### Project Structure +``` +supwr/ +├── src/ +│ ├── core/ # Core SWR library +│ └── adapters/ # Framework adapters +│ ├── react/ # React adapter +│ ├── svelte/ # Svelte adapter +│ ├── solid/ # Solid adapter +│ └── vue/ # Vue adapter +├── package.json # Workspace configuration +└── README.md +``` + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests for new functionality +5. Ensure all tests pass +6. Submit a pull request + +## License + +MIT © [Supwr](https://github.com/shishantbiswas/supwr) + +## Acknowledgments + +Inspired by: +- [SWR](https://swr.vercel.app/) - React Hooks library for data fetching +- [swrev](https://github.com/ConsoleTVs/swrev) - Framework-agnostic SWR implementation +- [solid-swr](https://github.com/tronikelis/solid-swr) - Solid implementation +- [sswr](https://github.com/ConsoleTVs/sswr) - Svelte implementation +- [swrv](https://github.com/Kong/swrv) - Vue implementation diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..7504b53 --- /dev/null +++ b/bun.lock @@ -0,0 +1,932 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "supwr-monorepo", + "dependencies": { + "@types/bun": "^1.4.0", + "bunup": "^0.16.32", + }, + }, + "examples/bun-react": { + "name": "bun-react-template", + "version": "0.1.0", + "dependencies": { + "@supwr/react": "workspace:*", + "bun-plugin-tailwind": "^0.1.2", + "react": "^18", + "react-dom": "^18", + "swr": "^2.5.1", + "tailwindcss": "^4.1.11", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^18", + "@types/react-dom": "^18", + }, + }, + "examples/vite-html": { + "name": "vite-html", + "version": "0.0.0", + "dependencies": { + "@supwr/core": "workspace:*", + }, + "devDependencies": { + "typescript": "~5.9.3", + "vite": "^7.3.1", + }, + }, + "examples/vite-solid": { + "name": "vite-solid", + "version": "0.0.0", + "dependencies": { + "solid-js": "^1.9.10", + }, + "devDependencies": { + "@types/node": "^24.10.1", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10", + }, + }, + "examples/vite-svelte": { + "name": "vite-svelte", + "version": "0.0.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tsconfig/svelte": "^5.0.6", + "@types/node": "^24.10.1", + "svelte": "^5.45.2", + "svelte-check": "^4.3.4", + "typescript": "~5.9.3", + "vite": "^7.3.1", + }, + }, + "examples/vite-vue": { + "name": "vite-vue", + "version": "0.0.0", + "dependencies": { + "vue": "^3.5.29", + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.11.0", + "@vitejs/plugin-vue": "^6.0.4", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-vue-devtools": "^8.0.6", + "vue-tsc": "^3.2.5", + }, + }, + "packages/core": { + "name": "@supwr/core", + "version": "0.0.1", + "devDependencies": { + "@types/bun": "^1.4.0", + "bunup": "^0.16.32", + }, + }, + "packages/react": { + "name": "@supwr/react", + "version": "0.0.1", + "dependencies": { + "@supwr/core": "workspace:*", + }, + "devDependencies": { + "@happy-dom/global-registrator": "^20.12.0", + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@types/bun": "^1.4.0", + "@types/react": "^19.2.18", + "bunup": "^0.16.32", + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + }, + }, + "packages/svelte": { + "name": "@supwr/svelte", + "version": "0.0.1", + "dependencies": { + "@supwr/core": "workspace:*", + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + }, + }, + "packages/vue": { + "name": "@supwr/vue", + "version": "0.0.1", + "dependencies": { + "@supwr/core": "workspace:*", + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + }, + }, + }, + "overrides": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + }, + "packages": { + "@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + + "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-decorators": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg=="], + + "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="], + + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@bunup/dts": ["@bunup/dts@0.14.53", "", { "dependencies": { "@babel/parser": "^7.28.6", "coffi": "^0.1.37", "oxc-minify": "^0.93.0", "oxc-resolver": "^11.16.2", "oxc-transform": "^0.93.0", "picocolors": "^1.1.1", "std-env": "^3.10.0", "ts-import-resolver": "^0.1.23" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-qyHgYEYxcghoChICEhuFKfA/EqqRA9WHOHLXcIkR9c70DgLAEXcOlkYhfBPmGxfMKJYL0X4VtQwst0/AMk9D3g=="], + + "@bunup/shared": ["@bunup/shared@0.16.31", "", { "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-h1dw669Uk68NCt1WvgqNfQoCcaK1RxLVAs3yQRCRaFxJiI9cqhU0EquVoGFM1g1BxzujGa7+bA9gt5JR0ZVLxA=="], + + "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], + + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.13.2", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.13.2" } }, "sha512-pGO7WhgHVLi7mi7zCLyJ+FkTad2aPTcjUXhRbgA4T5n1vLRJ/9uw3EsLieScNSX6HOfPF947bOP7N42l+KrWjg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="], + + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.4.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GCpf8QuFLsyioVawP5HrMxA1ZRBlu6Hq9RNnSc3UTUWAzIxBso9trjoZczw1HdgpqSssFkszfIV2zmOzFTjhkw=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.4.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cIrhwOr0SPEraewznhC+c/k6TG8bwFn5uZ4EJuXwjiKJLcAF36q7/bGjWkeXSe48JwMcPRUR054JXF7+cRwSSA=="], + + "@oven/bun-freebsd-aarch64": ["@oven/bun-freebsd-aarch64@1.4.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-09x7wnjMR6M5KGBDBhVl2CpfoCIQOkVDbPX2KfIhpXv4N6grbWE7dfLPw/Ydi9gaUMGhU7UKhoz444Nu6RCycA=="], + + "@oven/bun-freebsd-x64": ["@oven/bun-freebsd-x64@1.4.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dRwzti/qJqV1HWplU27iUWUqp+f2DtFSf2yqQKSb+HH2dDOC//Uqd9u/A5h1DMsLszfP5OGP9UwQIKxVwFODaA=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y5yAtCbHK6JjprXEtkdklDQFPADgs+CkfcliyY5g4JJ8baGHyQSrfpSkX3XVJ2C+aBLsdwNDdW+oczMsAwx6uA=="], + + "@oven/bun-linux-aarch64-android": ["@oven/bun-linux-aarch64-android@1.4.0", "", { "os": "android", "cpu": "arm64" }, "sha512-HpPIxJfDNPBPhiBNMyZoo/dOLijARfsx5j72vNuLtaTvl0Hh7HUculxjsOQ2WSyGoCgqXMEr1Qqjab1im9u1RA=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RUjAAkJ/CdNV++zVxyANWshPc73CECYsfhk0fWAkoJjtywxJ2BwXzI6nopBBDMfs0HS+fhRGn6zGwU8ccxLeJg=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Du44zebtPXJujvMLmtIxEQ6ykOhYt7L/Q+YIGVm+Yy+Pj/fpOnq60ggwIpKp/pGAFbYHNiTrA3JTjuZ9MTbZIg=="], + + "@oven/bun-linux-x64-android": ["@oven/bun-linux-x64-android@1.4.0", "", { "os": "android", "cpu": "x64" }, "sha512-u++KyLlfMn36yWz+AgJs+fZtS46UFDNpSSZhrcitkytONtNwq0X6Q9BDVEFXxYl/+Eec0xme1rb6MgW+U35WeA=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-C1Dv+ISL8YKEKM9jAHzNifOcRUoziy6UMxh+yVXjUCP6QnbRhENDHLaIWWkQZJyBLTn0I3xozflorAlHiGzGqA=="], + + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.4.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-FBAYaQpJBP0asgqzL6NFUfjdQqsV+kvTpJ/eWxPKj+RcDgIfPSuE8kvQuPYu5pa8u8JTujYMjmuyvHxVuQsInA=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jRKv1NPLznMSZY5BEWciMF7zv0Tiyo2pQSxAJ3w+YWJ6y3VWNJQQQdLlV5Jx8lbOFDrJdrc9dD3GV17k3BP41A=="], + + "@oxc-minify/binding-android-arm64": ["@oxc-minify/binding-android-arm64@0.93.0", "", { "os": "android", "cpu": "arm64" }, "sha512-N3j/JoK4hXwQbnyOJoEltM8MEkddWV3XtfYimO6jsMjr5R6QdauKaSVeQHO/lSezB7SFkrMPqr6X7tBfghHiXA=="], + + "@oxc-minify/binding-darwin-arm64": ["@oxc-minify/binding-darwin-arm64@0.93.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kLJJe7uBE+a9ql6eLGAtJ1g1LuEXi4aHbsiu342wGe+wRieSPi/Cx0aeDsQjdetwK5mqJWjWS2FO/n03jiw+IQ=="], + + "@oxc-minify/binding-darwin-x64": ["@oxc-minify/binding-darwin-x64@0.93.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0g6sVLaatgJpD28et/ykZCr5MHn7SWOblRCpXS47vcVmRyBFnUt21oiv3RqKacl1LKgk3czDHHribEfRyygiVw=="], + + "@oxc-minify/binding-freebsd-x64": ["@oxc-minify/binding-freebsd-x64@0.93.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-h8HUtllzBHIx31Uxx1maxd7ur4lMi0qoz0OzoqiohZ1/Ty5GZDe3F/yd6mN4M913e4xrNlcGLFaN8tI9W+Fstw=="], + + "@oxc-minify/binding-linux-arm-gnueabihf": ["@oxc-minify/binding-linux-arm-gnueabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CIElV4OLqkt5PJtirJ1ZRNvKkELhPkDWZmYpiDRI43mQl6Hkt0YsoBAeFKqrwx3AfwAYWvbFjHII++S+Vvnc4A=="], + + "@oxc-minify/binding-linux-arm-musleabihf": ["@oxc-minify/binding-linux-arm-musleabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-AOvZfiwnNULzSHxtW0BO0VXrFzDDFcknLZzQLZF8z9i070un+0S2Q3oZtA13gx7Z7LFJ87+/gKmgSKjHUI1Mzg=="], + + "@oxc-minify/binding-linux-arm64-gnu": ["@oxc-minify/binding-linux-arm64-gnu@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RNBTRvQ3XiVwoDU12y92HCwkznRb5N5ybGqC1Jt/eyWuRDI2838rzcoTrHQ/oke9/u4vHZ1lZwabR8z+VALE1g=="], + + "@oxc-minify/binding-linux-arm64-musl": ["@oxc-minify/binding-linux-arm64-musl@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qtE6qL7HC61rLKQBCvgsNJ9Bvvhe4U6V9VBPmvFw1SKRhoUp3PAzkWGnrvAgqQQnFaP34sUq/P4YmXmWL9m7bw=="], + + "@oxc-minify/binding-linux-riscv64-gnu": ["@oxc-minify/binding-linux-riscv64-gnu@0.93.0", "", { "os": "linux", "cpu": "none" }, "sha512-2/8Y2lY8Klns2xrOufR26SR6Ci79NbOR89vuQJCjQwVS7Veb8Gk3lJWn6XRna2QxdvjMQ+6+kmW2/CajJED1rg=="], + + "@oxc-minify/binding-linux-s390x-gnu": ["@oxc-minify/binding-linux-s390x-gnu@0.93.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-0TyiFgN1NPBQcKbdOh4VDt16kaFknKRXvmVshblEqp0JdwZeHFDCWVsTdpFH+NCbnUuzeY932IeP18dUnT/BVw=="], + + "@oxc-minify/binding-linux-x64-gnu": ["@oxc-minify/binding-linux-x64-gnu@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2vc22dp1RtbfKUM3DbkOvEwGa11JsjAdLHXBAAAkNAqD/ux+ERwLscoP2O71I5jXMTbeHe6eDem6gldyiWr76Q=="], + + "@oxc-minify/binding-linux-x64-musl": ["@oxc-minify/binding-linux-x64-musl@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-o7vVnDnF5k3xlgINjL7D2R9v47jczG5Jk+4xeZ+O9c9XxSx4jvGTqtBWf1Ka95fupJEm2QpSrO+wlA14exF3kA=="], + + "@oxc-minify/binding-wasm32-wasi": ["@oxc-minify/binding-wasm32-wasi@0.93.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-cW338ha926sD39uRDOrD/OMBgzosVtVPthXj1cl5rW9OQxuduXNqWbn/rJCA4X16T3IUcUnIbP2oCR8NI9D9xw=="], + + "@oxc-minify/binding-win32-arm64-msvc": ["@oxc-minify/binding-win32-arm64-msvc@0.93.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-f/STdah7v3L6hZDcGUlk2NVcXtwlrzFUiQHI+TcE21uMzHItCEv34gkDtrAq04sAsDF+Aykuw3LnsWwg0pv7RA=="], + + "@oxc-minify/binding-win32-x64-msvc": ["@oxc-minify/binding-win32-x64-msvc@0.93.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FLnVjLRW3ifwHR6/87q/OQCukw+KTCR1SpcawwcykG0F62BQZjiwXpEHwNWgrV41M9Mdd52ND8/+HrMqcFlZMw=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.24.2", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="], + + "@oxc-transform/binding-android-arm64": ["@oxc-transform/binding-android-arm64@0.93.0", "", { "os": "android", "cpu": "arm64" }, "sha512-sjmbt7SbsIgHC9luOLgwoFTI2zbTDesZlfiSFrSYNZv6S6o4zfR2Q/OLhRQqmar15JtxP8NVPuiPyqyx0mqHyg=="], + + "@oxc-transform/binding-darwin-arm64": ["@oxc-transform/binding-darwin-arm64@0.93.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XhYYdSU1Oz1UFeMm8fbfdPrODDQkLz2USDjKmfGuoOQRFKXlq0YTktfzF6z1bxn+T8pc9jIlBDr7f+cyy2CCjg=="], + + "@oxc-transform/binding-darwin-x64": ["@oxc-transform/binding-darwin-x64@0.93.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cRQE9cWmP1cLPqGKqbr453nio1uIgv2LAfIfdx1fQSClG6PvzfTWTqujM0bJpKquodkm4k07ug35+tp0aIkl0w=="], + + "@oxc-transform/binding-freebsd-x64": ["@oxc-transform/binding-freebsd-x64@0.93.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-m2vojbIELwBwX4ehbEs+7gXx4ooBn2mpR8MkxjZdhucMTj7P38W+jSdW+04pX+/bf2vYxP2madTEZXSX6mseLg=="], + + "@oxc-transform/binding-linux-arm-gnueabihf": ["@oxc-transform/binding-linux-arm-gnueabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NEoI0t9b8NHzvXuBIADYubKUbfsuDsY9g/uOTiVNoP+r16vpOdiY3avoqP2x2WPSiuprYVFM3Olq3WVngSg+IA=="], + + "@oxc-transform/binding-linux-arm-musleabihf": ["@oxc-transform/binding-linux-arm-musleabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-gzhgsb/o+V2PBElu2aMD7H1ZYOntr4lzuXDyVq/RbwwzF3G3jjFMB5hddbcjky8rdtmVzEaqqESI2h5kWkZUAw=="], + + "@oxc-transform/binding-linux-arm64-gnu": ["@oxc-transform/binding-linux-arm64-gnu@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4PdTqvMzLeMLbkwpHvj2ovQoIKaK8i1OnUGW7XzhZKPBGhkcdt/H3oa5FhZ2uoqSIM1KnKKP80MSC1OYqK+w0Q=="], + + "@oxc-transform/binding-linux-arm64-musl": ["@oxc-transform/binding-linux-arm64-musl@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c+CrpmFv32Z1WfR2V8sEKPI4XLewK9hQUq57RUDXj3P99IZ9eA0qIq/2Azle4YGbHdeEywAvqEDlbGa7o3ZFNQ=="], + + "@oxc-transform/binding-linux-riscv64-gnu": ["@oxc-transform/binding-linux-riscv64-gnu@0.93.0", "", { "os": "linux", "cpu": "none" }, "sha512-9rkciYe67g/uuVU4bFst96c7Xc2rk2fhzWTsBySUjTvxpgEeBXPsx78OLNwFVZoGf0lGNMXU/oSxr8OImEgvcw=="], + + "@oxc-transform/binding-linux-s390x-gnu": ["@oxc-transform/binding-linux-s390x-gnu@0.93.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-b/3he7qO4It9bTZbKWNvYMVNyoNldgWVDsTleWiRSskDZPTP6CggpcxVolltn8Eegiq4GycKSN1riInTngR6+w=="], + + "@oxc-transform/binding-linux-x64-gnu": ["@oxc-transform/binding-linux-x64-gnu@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-PeKWwubXPza6JGYjZGRt3sleTAaxTac4SG3Nd/VF9WGCY7ljAb6Q0t/gIuyjLm7tgB2E4luFezJogqkAW/b1ng=="], + + "@oxc-transform/binding-linux-x64-musl": ["@oxc-transform/binding-linux-x64-musl@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-UjeqejYo3ynOimHKKPdqtryD0iCWiYHRnNNl5sxzK4GPA/JcxNnRGejAbLH6gkPOFtAi2k4Y5ujc2nU8cX8LSw=="], + + "@oxc-transform/binding-wasm32-wasi": ["@oxc-transform/binding-wasm32-wasi@0.93.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-pMUgg0Mm5ASd8oEPf/yiZmHCqH5WMC0mjCK3CccEvfpUf+WC8WYiKiLkPz+0e7AyPW/Kb+MDI9FaYDKQ5QgyoQ=="], + + "@oxc-transform/binding-win32-arm64-msvc": ["@oxc-transform/binding-win32-arm64-msvc@0.93.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-RR30xNVMIEe5PWSD26uGEZp6yH7fkLKznvPSebVOVl2IWW8Sjnd59i6Ws08FmBKH9QP3jW30MypL6ESdlE5yWw=="], + + "@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.93.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6QN3DEaEw3eWioWEFRgNsTvYq8czYSnpkjB2za+/WdLN0g5FzOl2ZEfNiPrBWIPnSmjUmDWtWVWcSjwY7fX5/Q=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.1", "", { "os": "android", "cpu": "arm" }, "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.1", "", { "os": "android", "cpu": "arm64" }, "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.63.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.63.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.63.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.63.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.63.1", "", { "os": "linux", "cpu": "arm" }, "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.63.1", "", { "os": "linux", "cpu": "arm" }, "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.63.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.63.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.63.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.63.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.63.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.63.1", "", { "os": "linux", "cpu": "x64" }, "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.63.1", "", { "os": "linux", "cpu": "x64" }, "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.63.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.63.1", "", { "os": "none", "cpu": "arm64" }, "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.63.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.63.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.63.1", "", { "os": "win32", "cpu": "x64" }, "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.63.1", "", { "os": "win32", "cpu": "x64" }, "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w=="], + + "@supwr/core": ["@supwr/core@workspace:packages/core"], + + "@supwr/react": ["@supwr/react@workspace:packages/react"], + + "@supwr/svelte": ["@supwr/svelte@workspace:packages/svelte"], + + "@supwr/vue": ["@supwr/vue@workspace:packages/vue"], + + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.13", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ=="], + + "@sveltejs/load-config": ["@sveltejs/load-config@0.2.3", "", {}, "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ=="], + + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="], + + "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@7.0.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" }, "peerDependencies": { "@testing-library/dom": ">=10 <11", "vitest": ">= 0.32" }, "optionalPeers": ["vitest"] }, "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw=="], + + "@testing-library/react": ["@testing-library/react@16.3.3", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg=="], + + "@tsconfig/node24": ["@tsconfig/node24@24.0.5", "", {}, "sha512-DWC54fGPuhZgg3Ighg4Y/A/bA3ZdB5hnzAuv/MkQ/X85sVsBsMQxu8t7khutdYeiNksBbFdDyeqv1HSW2Zl8aA=="], + + "@tsconfig/svelte": ["@tsconfig/svelte@5.0.8", "", {}, "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/bun": ["@types/bun@1.4.1", "", { "dependencies": { "bun-types": "1.4.1" } }, "sha512-0AVGiTXGajf1rgKom3N+c5L7CBxuoyyv1i44M0nX4UDK0G/fnRAMiri93nHuVPIb429KKtAgj7HatVmmOjeQLA=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="], + + "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], + + "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], + + "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], + + "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], + + "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + + "@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.5.0", "", {}, "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA=="], + + "@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.5.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@vue/babel-helper-vue-transform-on": "1.5.0", "@vue/babel-plugin-resolve-type": "1.5.0", "@vue/shared": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw=="], + + "@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.42", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.42", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.42", "", { "dependencies": { "@vue/compiler-core": "3.5.42", "@vue/shared": "3.5.42" } }, "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.42", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.42", "@vue/compiler-dom": "3.5.42", "@vue/compiler-ssr": "3.5.42", "@vue/shared": "3.5.42", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.42", "", { "dependencies": { "@vue/compiler-dom": "3.5.42", "@vue/shared": "3.5.42" } }, "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA=="], + + "@vue/devtools-core": ["@vue/devtools-core@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1", "@vue/devtools-shared": "^8.2.1" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@8.2.1", "", { "dependencies": { "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.2.1", "", {}, "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g=="], + + "@vue/language-core": ["@vue/language-core@3.3.11", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.42", "", { "dependencies": { "@vue/shared": "3.5.42" } }, "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.42", "", { "dependencies": { "@vue/reactivity": "3.5.42", "@vue/shared": "3.5.42" } }, "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.42", "", { "dependencies": { "@vue/reactivity": "3.5.42", "@vue/runtime-core": "3.5.42", "@vue/shared": "3.5.42", "csstype": "^3.2.3" } }, "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.42", "", { "dependencies": { "@vue/compiler-ssr": "3.5.42", "@vue/runtime-dom": "3.5.42", "@vue/shared": "3.5.42" } }, "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ=="], + + "@vue/shared": ["@vue/shared@3.5.42", "", {}, "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g=="], + + "@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "alien-signals": ["alien-signals@3.2.1", "", {}, "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.10", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-lxve6Y02YiZTldB7efKpnbf1BH00XCFZNYYW235jSGsYaJNFtHrYlKV6/O+miHbjqpIr9FTe5+0no4hofAMbfA=="], + + "babel-preset-solid": ["babel-preset-solid@1.9.15", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.10" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.15" }, "optionalPeers": ["solid-js"] }, "sha512-GBmg1OiPb+OwcH51XbDAKPtvrPfQW7rCJTJxcp8+yhtWwN+kqnbEJk2SgVybd+uhTxTKAvjaFyiQSr/eUZBwzg=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw=="], + + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], + + "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], + + "bun": ["bun@1.4.0", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.4.0", "@oven/bun-darwin-x64": "1.4.0", "@oven/bun-freebsd-aarch64": "1.4.0", "@oven/bun-freebsd-x64": "1.4.0", "@oven/bun-linux-aarch64": "1.4.0", "@oven/bun-linux-aarch64-android": "1.4.0", "@oven/bun-linux-aarch64-musl": "1.4.0", "@oven/bun-linux-x64": "1.4.0", "@oven/bun-linux-x64-android": "1.4.0", "@oven/bun-linux-x64-musl": "1.4.0", "@oven/bun-windows-aarch64": "1.4.0", "@oven/bun-windows-x64": "1.4.0" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-iRiFkc2W7UVpCyZXO9tod45TP9QCyN19fWqbpeN/jaM/K7uzeHYx/OSPsahMJazGKBgPsnxRt+4Jc43d8BcHZw=="], + + "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], + + "bun-react-template": ["bun-react-template@workspace:examples/bun-react"], + + "bun-types": ["bun-types@1.4.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-loKuVrAFZKfEv+JvWkHRS9GW5IqLuLRjVXN9p+vZvBN86O5hf/pBZQ5hSoyipsrMmWObZBDvWnlmKvjKTM0PdA=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "bunup": ["bunup@0.16.32", "", { "dependencies": { "@bunup/dts": "^0.14.53", "@bunup/shared": "0.16.31", "chokidar": "^5.0.0", "coffi": "^0.1.37", "lightningcss": "^1.32.0", "picocolors": "^1.1.1", "tinyexec": "^1.2.4", "tree-kill": "^1.2.2", "zlye": "^0.4.4" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"], "bin": { "bunup": "dist/cli/index.js" } }, "sha512-w7RpjGl/AlJMRs0EovFJ0ddF5BOnxZlgDt5Q5xamz/WPd74BqJSetaeCQPDBzvSlOe0gZY90q6lFmjip/aKv2w=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "coffi": ["coffi@0.1.37", "", { "dependencies": { "strip-json-comments": "^5.0.3" } }, "sha512-ewO5Xis7sw7g54yI/3lJ/nNV90Er4ZnENeDORZjrs58T70MmwKFLZgevraNCz+RmB4KDKsYT1ui1wDB36iPWqQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.9.2", "", {}, "sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.420", "", {}, "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + + "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + + "esrap": ["esrap@2.3.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-yc0OC12UjPqLoc+fe+v5GNs4TOjAigUw3sTikfC+xeBPGUw7gDRz3DtYaqEhxyMVJojcSWJw7jT0QWR+CbuE/A=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "happy-dom": ["happy-dom@20.13.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-VC9HoEaT3Jf3K/1g/GjMYGAZCCg+ULgGVcXJlAeSzdTj8mIoPJRp14KVclP5gpUcF/kauuaVB2ri2/WxXbl4vg=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + + "is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], + + "merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + + "node-releases": ["node-releases@2.0.54", "", {}, "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="], + + "npm-run-all2": ["npm-run-all2@8.0.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", "which": "^5.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "ohash": ["ohash@2.0.12", "", {}, "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw=="], + + "open": ["open@11.0.2", "", { "dependencies": { "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.2.1", "wsl-utils": "^1.0.0" } }, "sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q=="], + + "oxc-minify": ["oxc-minify@0.93.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.93.0", "@oxc-minify/binding-darwin-arm64": "0.93.0", "@oxc-minify/binding-darwin-x64": "0.93.0", "@oxc-minify/binding-freebsd-x64": "0.93.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.93.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.93.0", "@oxc-minify/binding-linux-arm64-gnu": "0.93.0", "@oxc-minify/binding-linux-arm64-musl": "0.93.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.93.0", "@oxc-minify/binding-linux-s390x-gnu": "0.93.0", "@oxc-minify/binding-linux-x64-gnu": "0.93.0", "@oxc-minify/binding-linux-x64-musl": "0.93.0", "@oxc-minify/binding-wasm32-wasi": "0.93.0", "@oxc-minify/binding-win32-arm64-msvc": "0.93.0", "@oxc-minify/binding-win32-x64-msvc": "0.93.0" } }, "sha512-pwMjOGN/I+cfLVkSmECcVHROKwECNVAXCT5h/29S4f0aArIUh3CQnix1yYy7MTQ3yThNuGANjjE9jWJyT43Vbw=="], + + "oxc-resolver": ["oxc-resolver@11.24.2", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.24.2", "@oxc-resolver/binding-android-arm64": "11.24.2", "@oxc-resolver/binding-darwin-arm64": "11.24.2", "@oxc-resolver/binding-darwin-x64": "11.24.2", "@oxc-resolver/binding-freebsd-x64": "11.24.2", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-musl": "11.24.2", "@oxc-resolver/binding-openharmony-arm64": "11.24.2", "@oxc-resolver/binding-wasm32-wasi": "11.24.2", "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw=="], + + "oxc-transform": ["oxc-transform@0.93.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm64": "0.93.0", "@oxc-transform/binding-darwin-arm64": "0.93.0", "@oxc-transform/binding-darwin-x64": "0.93.0", "@oxc-transform/binding-freebsd-x64": "0.93.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.93.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.93.0", "@oxc-transform/binding-linux-arm64-gnu": "0.93.0", "@oxc-transform/binding-linux-arm64-musl": "0.93.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.93.0", "@oxc-transform/binding-linux-s390x-gnu": "0.93.0", "@oxc-transform/binding-linux-x64-gnu": "0.93.0", "@oxc-transform/binding-linux-x64-musl": "0.93.0", "@oxc-transform/binding-wasm32-wasi": "0.93.0", "@oxc-transform/binding-win32-arm64-msvc": "0.93.0", "@oxc-transform/binding-win32-x64-msvc": "0.93.0" } }, "sha512-QCwM2nMAWf4hEBehLVA2apllxdmmWLb5M0in9HwC2boaaFbP0QntbLy4hfRZGur2KKyEBErZbH9S5NYX8eHslg=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + + "pidtree": ["pidtree@0.6.1", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw=="], + + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], + + "powershell-utils": ["powershell-utils@0.2.1", "", {}, "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="], + + "react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="], + + "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "rollup": ["rollup@4.63.1", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.63.1", "@rollup/rollup-android-arm64": "4.63.1", "@rollup/rollup-darwin-arm64": "4.63.1", "@rollup/rollup-darwin-x64": "4.63.1", "@rollup/rollup-freebsd-arm64": "4.63.1", "@rollup/rollup-freebsd-x64": "4.63.1", "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", "@rollup/rollup-linux-arm-musleabihf": "4.63.1", "@rollup/rollup-linux-arm64-gnu": "4.63.1", "@rollup/rollup-linux-arm64-musl": "4.63.1", "@rollup/rollup-linux-loong64-gnu": "4.63.1", "@rollup/rollup-linux-loong64-musl": "4.63.1", "@rollup/rollup-linux-ppc64-gnu": "4.63.1", "@rollup/rollup-linux-ppc64-musl": "4.63.1", "@rollup/rollup-linux-riscv64-gnu": "4.63.1", "@rollup/rollup-linux-riscv64-musl": "4.63.1", "@rollup/rollup-linux-s390x-gnu": "4.63.1", "@rollup/rollup-linux-x64-gnu": "4.63.1", "@rollup/rollup-linux-x64-musl": "4.63.1", "@rollup/rollup-openbsd-x64": "4.63.1", "@rollup/rollup-openharmony-arm64": "4.63.1", "@rollup/rollup-win32-arm64-msvc": "4.63.1", "@rollup/rollup-win32-ia32-msvc": "4.63.1", "@rollup/rollup-win32-x64-gnu": "4.63.1", "@rollup/rollup-win32-x64-msvc": "4.63.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "seroval": ["seroval@1.5.6", "", {}, "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA=="], + + "seroval-plugins": ["seroval-plugins@1.5.6", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "solid-js": ["solid-js@1.9.15", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.4", "seroval-plugins": "~1.5.4" } }, "sha512-EeiY2xfpZJqPLjXspVEKjAII4yv8NyG//NxZ3IpOFHdUNnnTyL0uJOeS9LWGvA7cFCz5y94cjFwYlmw5Luncsg=="], + + "solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + + "svelte": ["svelte@5.57.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.12", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-NdbDn7fl4be1ViUG0oq/lvG6OZy3oENolV2ONjiqqsfVoeAfzaQAKUcEX3MrQod/Bebv1PgwET9rfXhgn9s4Kg=="], + + "svelte-check": ["svelte-check@4.7.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "^0.2.3", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.0.0 || ^6.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw=="], + + "swr": ["swr@2.5.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-BRw55e8r0B7SpDN20CAzoQAHl7y1yP7/Zt7oqUjMv0vSt2u2Xnkm88Ws+VypbV9BXHQVuSuyVq7zMjO16wSExw=="], + + "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], + + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-import-resolver": ["ts-import-resolver@0.1.23", "", { "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-282pgr6j6aOvP3P2I6XugDxdBobkpdMmdbWjRjGl5gjPI1p0+oTNGDh1t924t75kRlyIkF65DiwhSIUysmyHQA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "unplugin-utils": ["unplugin-utils@0.3.2", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.4" } }, "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA=="], + + "update-browserslist-db": ["update-browserslist-db@1.3.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], + + "vite-dev-rpc": ["vite-dev-rpc@2.0.0", "", { "dependencies": { "birpc": "^4.0.0", "vite-hot-client": "^2.2.0" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" } }, "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA=="], + + "vite-hot-client": ["vite-hot-client@2.2.0", "", { "peerDependencies": { "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" } }, "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w=="], + + "vite-html": ["vite-html@workspace:examples/vite-html"], + + "vite-plugin-inspect": ["vite-plugin-inspect@11.4.1", "", { "dependencies": { "ansis": "^4.3.0", "error-stack-parser-es": "^1.0.5", "obug": "^2.1.1", "ohash": "^2.0.11", "open": "^11.0.0", "perfect-debounce": "^2.1.0", "sirv": "^3.0.2", "unplugin-utils": "^0.3.1", "vite-dev-rpc": "^2.0.0" }, "peerDependencies": { "@nuxt/kit": "*", "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA=="], + + "vite-plugin-solid": ["vite-plugin-solid@2.11.14", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.0.0 || ^7.0.0", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-7ZVBt8rpoyqmlwin2kRIUveaHoF6/kulY7gsnD+qFh4nS29V4OPAnw+ojoAspXIjObiL9o1xh9a/nTuYHm02Rw=="], + + "vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.2.1", "", { "dependencies": { "@vue/devtools-core": "^8.2.1", "@vue/devtools-kit": "^8.2.1", "@vue/devtools-shared": "^8.2.1", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^6.0.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-5JLxXWWCo5lJMw16/xVeNvJ8k2zLwZPf1vITLzya/2IePrCBeGe/p/iAokgXHZpEi39fcYtPXmO8SaKeXmqCAA=="], + + "vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@6.0.0", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw=="], + + "vite-solid": ["vite-solid@workspace:examples/vite-solid"], + + "vite-svelte": ["vite-svelte@workspace:examples/vite-svelte"], + + "vite-vue": ["vite-vue@workspace:examples/vite-vue"], + + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "vscode-uri": ["vscode-uri@3.2.0", "", {}, "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg=="], + + "vue": ["vue@3.5.42", "", { "dependencies": { "@vue/compiler-dom": "3.5.42", "@vue/compiler-sfc": "3.5.42", "@vue/runtime-dom": "3.5.42", "@vue/server-renderer": "3.5.42", "@vue/shared": "3.5.42" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA=="], + + "vue-tsc": ["vue-tsc@3.3.11", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.11" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + + "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], + + "wsl-utils": ["wsl-utils@1.0.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "zimmerframe": ["zimmerframe@1.1.5", "", {}, "sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA=="], + + "zlye": ["zlye@0.4.4", "", { "dependencies": { "picocolors": "^1.1.1" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-fwpeC841X3ElOLYRMKXbwX29pitNrsm6nRNvEhDMrRXDl3BhR2i03Bkr0GNrpyYgZJuEzUsBylXAYzgGPXXOCQ=="], + + "@happy-dom/global-registrator/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "@types/react-dom/@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], + + "@types/ws/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], + + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], + + "buffer-image-size/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], + + "bun-react-template/@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], + + "bun-types/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], + + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "happy-dom/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "svelte/aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="], + + "svelte-check/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "vite-dev-rpc/birpc": ["birpc@4.2.0", "", {}, "sha512-KxgKcZPfrtzJDDALHPguGpGJUrzdgpymyiQQgzFjWreHMOpWrnFNVREr5J48x2DBh8ZVioscrV1SBkDipGiX+Q=="], + + "wsl-utils/powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + + "@happy-dom/global-registrator/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "@types/ws/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "buffer-image-size/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "bun-types/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "happy-dom/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "svelte-check/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + } +} diff --git a/bunup.config.ts b/bunup.config.ts new file mode 100644 index 0000000..4db32da --- /dev/null +++ b/bunup.config.ts @@ -0,0 +1,27 @@ +import { defineWorkspace } from "bunup"; + +export default defineWorkspace([ + { + name: "core", + root: "packages/core", + config: { + entry: ["src/index.ts"], + format: ["esm", "cjs"], + external: ["react", "react-dom", "@supwr/core"] + }, + }, + { + name: "react", + root: "packages/react", + config: { + entry: ["src/index.ts"], + format: ["esm", "cjs"], + external: ["react", "react-dom", "@supwr/core"] + }, + }, +], { + format: ["esm", "cjs"], + exports: true, + target: "browser", + watch:true +}); \ No newline at end of file diff --git a/examples/bun-react/.gitignore b/examples/bun-react/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/examples/bun-react/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/examples/bun-react/README.md b/examples/bun-react/README.md new file mode 100644 index 0000000..7213c42 --- /dev/null +++ b/examples/bun-react/README.md @@ -0,0 +1,21 @@ +# bun-react-tailwind-template + +To install dependencies: + +```bash +bun install +``` + +To start a development server: + +```bash +bun dev +``` + +To run for production: + +```bash +bun start +``` + +This project was created using `bun init` in bun v1.3.9. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/examples/bun-react/build.ts b/examples/bun-react/build.ts new file mode 100644 index 0000000..51780d0 --- /dev/null +++ b/examples/bun-react/build.ts @@ -0,0 +1,160 @@ +#!/usr/bin/env bun +import plugin from "bun-plugin-tailwind"; +import { existsSync } from "fs"; +import { rm } from "fs/promises"; +import path from "path"; + +if (process.argv.includes("--help") || process.argv.includes("-h")) { + console.log(` +🏗️ Bun Build Script + +Usage: bun run build.ts [options] + +Common Options: + --outdir Output directory (default: "dist") + --minify Enable minification (or --minify.whitespace, --minify.syntax, etc) + --sourcemap Sourcemap type: none|linked|inline|external + --target Build target: browser|bun|node + --format Output format: esm|cjs|iife + --splitting Enable code splitting + --packages Package handling: bundle|external + --public-path Public path for assets + --env Environment handling: inline|disable|prefix* + --conditions Package.json export conditions (comma separated) + --external External packages (comma separated) + --banner Add banner text to output + --footer Add footer text to output + --define Define global constants (e.g. --define.VERSION=1.0.0) + --help, -h Show this help message + +Example: + bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom +`); + process.exit(0); +} + +const toCamelCase = (str: string): string => str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase()); + +const parseValue = (value: string): any => { + if (value === "true") return true; + if (value === "false") return false; + + if (/^\d+$/.test(value)) return parseInt(value, 10); + if (/^\d*\.\d+$/.test(value)) return parseFloat(value); + + if (value.includes(",")) return value.split(",").map(v => v.trim()); + + return value; +}; + +function parseArgs(): Partial { + const config: Record = {}; + const args = process.argv.slice(2); + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === undefined) continue; + if (!arg.startsWith("--")) continue; + + if (arg.startsWith("--no-")) { + const key = toCamelCase(arg.slice(5)); + config[key] = false; + continue; + } + + if (!arg.includes("=") && (i === args.length - 1 || args[i + 1]?.startsWith("--"))) { + const key = toCamelCase(arg.slice(2)); + config[key] = true; + continue; + } + + let key: string; + let value: string; + + if (arg.includes("=")) { + [key, value] = arg.slice(2).split("=", 2) as [string, string]; + } else { + key = arg.slice(2); + value = args[++i] ?? ""; + } + + key = toCamelCase(key); + + if (key.includes(".")) { + const parts = key.split("."); + if (parts.length > 2) { + console.warn( + `Warning: Deeply nested option "${key}" is not supported. Only single-level nesting (e.g., --minify.whitespace) is allowed.`, + ); + continue; + } + const parentKey = parts[0]!; + const childKey = parts[1]!; + const existing = config[parentKey]; + if (typeof existing !== "object" || existing === null || Array.isArray(existing)) { + config[parentKey] = {}; + } + (config[parentKey] as Record)[childKey] = parseValue(value); + } else { + config[key] = parseValue(value); + } + } + + return config as Partial; +} + +const formatFileSize = (bytes: number): string => { + const units = ["B", "KB", "MB", "GB"]; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(2)} ${units[unitIndex]}`; +}; + +console.log("\n🚀 Starting build process...\n"); + +const cliConfig = parseArgs(); +const outdir = cliConfig.outdir || path.join(process.cwd(), "dist"); + +if (existsSync(outdir)) { + console.log(`🗑️ Cleaning previous build at ${outdir}`); + await rm(outdir, { recursive: true, force: true }); +} + +const start = performance.now(); + +const entrypoints = [...new Bun.Glob("**.html").scanSync("src")] + .map(a => path.resolve("src", a)) + .filter(dir => !dir.includes("node_modules")); +console.log(`📄 Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`); + +const result = await Bun.build({ + entrypoints, + outdir, + plugins: [plugin], + minify: true, + target: "browser", + sourcemap: "linked", + define: { + "process.env.NODE_ENV": JSON.stringify("production"), + }, + ...cliConfig, +}); + +const end = performance.now(); + +const outputTable = result.outputs.map(output => ({ + File: path.relative(process.cwd(), output.path), + Type: output.kind, + Size: formatFileSize(output.size), +})); + +console.table(outputTable); +const buildTime = (end - start).toFixed(2); + +console.log(`\n✅ Build completed in ${buildTime}ms\n`); diff --git a/examples/bun-react/bun-env.d.ts b/examples/bun-react/bun-env.d.ts new file mode 100644 index 0000000..72f1c26 --- /dev/null +++ b/examples/bun-react/bun-env.d.ts @@ -0,0 +1,17 @@ +// Generated by `bun init` + +declare module "*.svg" { + /** + * A path to the SVG file + */ + const path: `${string}.svg`; + export = path; +} + +declare module "*.module.css" { + /** + * A record of class names to their corresponding CSS module classes + */ + const classes: { readonly [key: string]: string }; + export = classes; +} diff --git a/examples/bun-react/bun.lock b/examples/bun-react/bun.lock new file mode 100644 index 0000000..bf35c83 --- /dev/null +++ b/examples/bun-react/bun.lock @@ -0,0 +1,318 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "bun-react-template", + "dependencies": { + "@supwr/react": "file:../../packages/react", + "bun-plugin-tailwind": "^0.1.2", + "react": "^18", + "react-dom": "^18", + "tailwindcss": "^4.1.11", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^18", + "@types/react-dom": "^18", + }, + }, + }, + "packages": { + "@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@bunup/dts": ["@bunup/dts@0.14.53", "", { "dependencies": { "@babel/parser": "^7.28.6", "coffi": "^0.1.37", "oxc-minify": "^0.93.0", "oxc-resolver": "^11.16.2", "oxc-transform": "^0.93.0", "picocolors": "^1.1.1", "std-env": "^3.10.0", "ts-import-resolver": "^0.1.23" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-qyHgYEYxcghoChICEhuFKfA/EqqRA9WHOHLXcIkR9c70DgLAEXcOlkYhfBPmGxfMKJYL0X4VtQwst0/AMk9D3g=="], + + "@bunup/shared": ["@bunup/shared@0.16.31", "", { "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-h1dw669Uk68NCt1WvgqNfQoCcaK1RxLVAs3yQRCRaFxJiI9cqhU0EquVoGFM1g1BxzujGa7+bA9gt5JR0ZVLxA=="], + + "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.12.0", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.12.0" } }, "sha512-BUE55Rew3oMwBzwCwmUnV+Oxk51V3xolM39Ts6kGiBXNELjujiESwk9qc99JRr6cVrj3OTd9MJ5zQ2fpn+jy6g=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="], + + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.4.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GCpf8QuFLsyioVawP5HrMxA1ZRBlu6Hq9RNnSc3UTUWAzIxBso9trjoZczw1HdgpqSssFkszfIV2zmOzFTjhkw=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.4.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cIrhwOr0SPEraewznhC+c/k6TG8bwFn5uZ4EJuXwjiKJLcAF36q7/bGjWkeXSe48JwMcPRUR054JXF7+cRwSSA=="], + + "@oven/bun-freebsd-aarch64": ["@oven/bun-freebsd-aarch64@1.4.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-09x7wnjMR6M5KGBDBhVl2CpfoCIQOkVDbPX2KfIhpXv4N6grbWE7dfLPw/Ydi9gaUMGhU7UKhoz444Nu6RCycA=="], + + "@oven/bun-freebsd-x64": ["@oven/bun-freebsd-x64@1.4.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dRwzti/qJqV1HWplU27iUWUqp+f2DtFSf2yqQKSb+HH2dDOC//Uqd9u/A5h1DMsLszfP5OGP9UwQIKxVwFODaA=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y5yAtCbHK6JjprXEtkdklDQFPADgs+CkfcliyY5g4JJ8baGHyQSrfpSkX3XVJ2C+aBLsdwNDdW+oczMsAwx6uA=="], + + "@oven/bun-linux-aarch64-android": ["@oven/bun-linux-aarch64-android@1.4.0", "", { "os": "android", "cpu": "arm64" }, "sha512-HpPIxJfDNPBPhiBNMyZoo/dOLijARfsx5j72vNuLtaTvl0Hh7HUculxjsOQ2WSyGoCgqXMEr1Qqjab1im9u1RA=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RUjAAkJ/CdNV++zVxyANWshPc73CECYsfhk0fWAkoJjtywxJ2BwXzI6nopBBDMfs0HS+fhRGn6zGwU8ccxLeJg=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Du44zebtPXJujvMLmtIxEQ6ykOhYt7L/Q+YIGVm+Yy+Pj/fpOnq60ggwIpKp/pGAFbYHNiTrA3JTjuZ9MTbZIg=="], + + "@oven/bun-linux-x64-android": ["@oven/bun-linux-x64-android@1.4.0", "", { "os": "android", "cpu": "x64" }, "sha512-u++KyLlfMn36yWz+AgJs+fZtS46UFDNpSSZhrcitkytONtNwq0X6Q9BDVEFXxYl/+Eec0xme1rb6MgW+U35WeA=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-C1Dv+ISL8YKEKM9jAHzNifOcRUoziy6UMxh+yVXjUCP6QnbRhENDHLaIWWkQZJyBLTn0I3xozflorAlHiGzGqA=="], + + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.4.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-FBAYaQpJBP0asgqzL6NFUfjdQqsV+kvTpJ/eWxPKj+RcDgIfPSuE8kvQuPYu5pa8u8JTujYMjmuyvHxVuQsInA=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jRKv1NPLznMSZY5BEWciMF7zv0Tiyo2pQSxAJ3w+YWJ6y3VWNJQQQdLlV5Jx8lbOFDrJdrc9dD3GV17k3BP41A=="], + + "@oxc-minify/binding-android-arm64": ["@oxc-minify/binding-android-arm64@0.93.0", "", { "os": "android", "cpu": "arm64" }, "sha512-N3j/JoK4hXwQbnyOJoEltM8MEkddWV3XtfYimO6jsMjr5R6QdauKaSVeQHO/lSezB7SFkrMPqr6X7tBfghHiXA=="], + + "@oxc-minify/binding-darwin-arm64": ["@oxc-minify/binding-darwin-arm64@0.93.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kLJJe7uBE+a9ql6eLGAtJ1g1LuEXi4aHbsiu342wGe+wRieSPi/Cx0aeDsQjdetwK5mqJWjWS2FO/n03jiw+IQ=="], + + "@oxc-minify/binding-darwin-x64": ["@oxc-minify/binding-darwin-x64@0.93.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0g6sVLaatgJpD28et/ykZCr5MHn7SWOblRCpXS47vcVmRyBFnUt21oiv3RqKacl1LKgk3czDHHribEfRyygiVw=="], + + "@oxc-minify/binding-freebsd-x64": ["@oxc-minify/binding-freebsd-x64@0.93.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-h8HUtllzBHIx31Uxx1maxd7ur4lMi0qoz0OzoqiohZ1/Ty5GZDe3F/yd6mN4M913e4xrNlcGLFaN8tI9W+Fstw=="], + + "@oxc-minify/binding-linux-arm-gnueabihf": ["@oxc-minify/binding-linux-arm-gnueabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CIElV4OLqkt5PJtirJ1ZRNvKkELhPkDWZmYpiDRI43mQl6Hkt0YsoBAeFKqrwx3AfwAYWvbFjHII++S+Vvnc4A=="], + + "@oxc-minify/binding-linux-arm-musleabihf": ["@oxc-minify/binding-linux-arm-musleabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-AOvZfiwnNULzSHxtW0BO0VXrFzDDFcknLZzQLZF8z9i070un+0S2Q3oZtA13gx7Z7LFJ87+/gKmgSKjHUI1Mzg=="], + + "@oxc-minify/binding-linux-arm64-gnu": ["@oxc-minify/binding-linux-arm64-gnu@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RNBTRvQ3XiVwoDU12y92HCwkznRb5N5ybGqC1Jt/eyWuRDI2838rzcoTrHQ/oke9/u4vHZ1lZwabR8z+VALE1g=="], + + "@oxc-minify/binding-linux-arm64-musl": ["@oxc-minify/binding-linux-arm64-musl@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qtE6qL7HC61rLKQBCvgsNJ9Bvvhe4U6V9VBPmvFw1SKRhoUp3PAzkWGnrvAgqQQnFaP34sUq/P4YmXmWL9m7bw=="], + + "@oxc-minify/binding-linux-riscv64-gnu": ["@oxc-minify/binding-linux-riscv64-gnu@0.93.0", "", { "os": "linux", "cpu": "none" }, "sha512-2/8Y2lY8Klns2xrOufR26SR6Ci79NbOR89vuQJCjQwVS7Veb8Gk3lJWn6XRna2QxdvjMQ+6+kmW2/CajJED1rg=="], + + "@oxc-minify/binding-linux-s390x-gnu": ["@oxc-minify/binding-linux-s390x-gnu@0.93.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-0TyiFgN1NPBQcKbdOh4VDt16kaFknKRXvmVshblEqp0JdwZeHFDCWVsTdpFH+NCbnUuzeY932IeP18dUnT/BVw=="], + + "@oxc-minify/binding-linux-x64-gnu": ["@oxc-minify/binding-linux-x64-gnu@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2vc22dp1RtbfKUM3DbkOvEwGa11JsjAdLHXBAAAkNAqD/ux+ERwLscoP2O71I5jXMTbeHe6eDem6gldyiWr76Q=="], + + "@oxc-minify/binding-linux-x64-musl": ["@oxc-minify/binding-linux-x64-musl@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-o7vVnDnF5k3xlgINjL7D2R9v47jczG5Jk+4xeZ+O9c9XxSx4jvGTqtBWf1Ka95fupJEm2QpSrO+wlA14exF3kA=="], + + "@oxc-minify/binding-wasm32-wasi": ["@oxc-minify/binding-wasm32-wasi@0.93.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-cW338ha926sD39uRDOrD/OMBgzosVtVPthXj1cl5rW9OQxuduXNqWbn/rJCA4X16T3IUcUnIbP2oCR8NI9D9xw=="], + + "@oxc-minify/binding-win32-arm64-msvc": ["@oxc-minify/binding-win32-arm64-msvc@0.93.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-f/STdah7v3L6hZDcGUlk2NVcXtwlrzFUiQHI+TcE21uMzHItCEv34gkDtrAq04sAsDF+Aykuw3LnsWwg0pv7RA=="], + + "@oxc-minify/binding-win32-x64-msvc": ["@oxc-minify/binding-win32-x64-msvc@0.93.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FLnVjLRW3ifwHR6/87q/OQCukw+KTCR1SpcawwcykG0F62BQZjiwXpEHwNWgrV41M9Mdd52ND8/+HrMqcFlZMw=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.24.2", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="], + + "@oxc-transform/binding-android-arm64": ["@oxc-transform/binding-android-arm64@0.93.0", "", { "os": "android", "cpu": "arm64" }, "sha512-sjmbt7SbsIgHC9luOLgwoFTI2zbTDesZlfiSFrSYNZv6S6o4zfR2Q/OLhRQqmar15JtxP8NVPuiPyqyx0mqHyg=="], + + "@oxc-transform/binding-darwin-arm64": ["@oxc-transform/binding-darwin-arm64@0.93.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XhYYdSU1Oz1UFeMm8fbfdPrODDQkLz2USDjKmfGuoOQRFKXlq0YTktfzF6z1bxn+T8pc9jIlBDr7f+cyy2CCjg=="], + + "@oxc-transform/binding-darwin-x64": ["@oxc-transform/binding-darwin-x64@0.93.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cRQE9cWmP1cLPqGKqbr453nio1uIgv2LAfIfdx1fQSClG6PvzfTWTqujM0bJpKquodkm4k07ug35+tp0aIkl0w=="], + + "@oxc-transform/binding-freebsd-x64": ["@oxc-transform/binding-freebsd-x64@0.93.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-m2vojbIELwBwX4ehbEs+7gXx4ooBn2mpR8MkxjZdhucMTj7P38W+jSdW+04pX+/bf2vYxP2madTEZXSX6mseLg=="], + + "@oxc-transform/binding-linux-arm-gnueabihf": ["@oxc-transform/binding-linux-arm-gnueabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NEoI0t9b8NHzvXuBIADYubKUbfsuDsY9g/uOTiVNoP+r16vpOdiY3avoqP2x2WPSiuprYVFM3Olq3WVngSg+IA=="], + + "@oxc-transform/binding-linux-arm-musleabihf": ["@oxc-transform/binding-linux-arm-musleabihf@0.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-gzhgsb/o+V2PBElu2aMD7H1ZYOntr4lzuXDyVq/RbwwzF3G3jjFMB5hddbcjky8rdtmVzEaqqESI2h5kWkZUAw=="], + + "@oxc-transform/binding-linux-arm64-gnu": ["@oxc-transform/binding-linux-arm64-gnu@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4PdTqvMzLeMLbkwpHvj2ovQoIKaK8i1OnUGW7XzhZKPBGhkcdt/H3oa5FhZ2uoqSIM1KnKKP80MSC1OYqK+w0Q=="], + + "@oxc-transform/binding-linux-arm64-musl": ["@oxc-transform/binding-linux-arm64-musl@0.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c+CrpmFv32Z1WfR2V8sEKPI4XLewK9hQUq57RUDXj3P99IZ9eA0qIq/2Azle4YGbHdeEywAvqEDlbGa7o3ZFNQ=="], + + "@oxc-transform/binding-linux-riscv64-gnu": ["@oxc-transform/binding-linux-riscv64-gnu@0.93.0", "", { "os": "linux", "cpu": "none" }, "sha512-9rkciYe67g/uuVU4bFst96c7Xc2rk2fhzWTsBySUjTvxpgEeBXPsx78OLNwFVZoGf0lGNMXU/oSxr8OImEgvcw=="], + + "@oxc-transform/binding-linux-s390x-gnu": ["@oxc-transform/binding-linux-s390x-gnu@0.93.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-b/3he7qO4It9bTZbKWNvYMVNyoNldgWVDsTleWiRSskDZPTP6CggpcxVolltn8Eegiq4GycKSN1riInTngR6+w=="], + + "@oxc-transform/binding-linux-x64-gnu": ["@oxc-transform/binding-linux-x64-gnu@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-PeKWwubXPza6JGYjZGRt3sleTAaxTac4SG3Nd/VF9WGCY7ljAb6Q0t/gIuyjLm7tgB2E4luFezJogqkAW/b1ng=="], + + "@oxc-transform/binding-linux-x64-musl": ["@oxc-transform/binding-linux-x64-musl@0.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-UjeqejYo3ynOimHKKPdqtryD0iCWiYHRnNNl5sxzK4GPA/JcxNnRGejAbLH6gkPOFtAi2k4Y5ujc2nU8cX8LSw=="], + + "@oxc-transform/binding-wasm32-wasi": ["@oxc-transform/binding-wasm32-wasi@0.93.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-pMUgg0Mm5ASd8oEPf/yiZmHCqH5WMC0mjCK3CccEvfpUf+WC8WYiKiLkPz+0e7AyPW/Kb+MDI9FaYDKQ5QgyoQ=="], + + "@oxc-transform/binding-win32-arm64-msvc": ["@oxc-transform/binding-win32-arm64-msvc@0.93.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-RR30xNVMIEe5PWSD26uGEZp6yH7fkLKznvPSebVOVl2IWW8Sjnd59i6Ws08FmBKH9QP3jW30MypL6ESdlE5yWw=="], + + "@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.93.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6QN3DEaEw3eWioWEFRgNsTvYq8czYSnpkjB2za+/WdLN0g5FzOl2ZEfNiPrBWIPnSmjUmDWtWVWcSjwY7fX5/Q=="], + + "@supwr/react": ["@supwr/react@file:../../packages/react", { "dependencies": { "@happy-dom/global-registrator": "^20.12.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3" }, "devDependencies": { "@types/bun": "^1.4.0", "@types/react": "^19.2.18", "bunup": "^0.16.32" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@7.0.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" }, "peerDependencies": { "@testing-library/dom": ">=10 <11", "vitest": ">= 0.32" }, "optionalPeers": ["vitest"] }, "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw=="], + + "@testing-library/react": ["@testing-library/react@16.3.3", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="], + + "@types/node": ["@types/node@26.4.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ=="], + + "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + + "@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], + + "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], + + "bun": ["bun@1.4.0", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.4.0", "@oven/bun-darwin-x64": "1.4.0", "@oven/bun-freebsd-aarch64": "1.4.0", "@oven/bun-freebsd-x64": "1.4.0", "@oven/bun-linux-aarch64": "1.4.0", "@oven/bun-linux-aarch64-android": "1.4.0", "@oven/bun-linux-aarch64-musl": "1.4.0", "@oven/bun-linux-x64": "1.4.0", "@oven/bun-linux-x64-android": "1.4.0", "@oven/bun-linux-x64-musl": "1.4.0", "@oven/bun-windows-aarch64": "1.4.0", "@oven/bun-windows-x64": "1.4.0" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-iRiFkc2W7UVpCyZXO9tod45TP9QCyN19fWqbpeN/jaM/K7uzeHYx/OSPsahMJazGKBgPsnxRt+4Jc43d8BcHZw=="], + + "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], + + "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="], + + "bunup": ["bunup@0.16.32", "", { "dependencies": { "@bunup/dts": "^0.14.53", "@bunup/shared": "0.16.31", "chokidar": "^5.0.0", "coffi": "^0.1.37", "lightningcss": "^1.32.0", "picocolors": "^1.1.1", "tinyexec": "^1.2.4", "tree-kill": "^1.2.2", "zlye": "^0.4.4" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"], "bin": { "bunup": "dist/cli/index.js" } }, "sha512-w7RpjGl/AlJMRs0EovFJ0ddF5BOnxZlgDt5Q5xamz/WPd74BqJSetaeCQPDBzvSlOe0gZY90q6lFmjip/aKv2w=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "coffi": ["coffi@0.1.37", "", { "dependencies": { "strip-json-comments": "^5.0.3" } }, "sha512-ewO5Xis7sw7g54yI/3lJ/nNV90Er4ZnENeDORZjrs58T70MmwKFLZgevraNCz+RmB4KDKsYT1ui1wDB36iPWqQ=="], + + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "happy-dom": ["happy-dom@20.12.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-7uMYJu2SEwwL8vVcKp0C0lnt6d2LSGGe+T+oY79PiCJNNSgFpbxW8n5KuzpDQvrU4mt+fYiK1+Jy7Z2v39YR6g=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "oxc-minify": ["oxc-minify@0.93.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.93.0", "@oxc-minify/binding-darwin-arm64": "0.93.0", "@oxc-minify/binding-darwin-x64": "0.93.0", "@oxc-minify/binding-freebsd-x64": "0.93.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.93.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.93.0", "@oxc-minify/binding-linux-arm64-gnu": "0.93.0", "@oxc-minify/binding-linux-arm64-musl": "0.93.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.93.0", "@oxc-minify/binding-linux-s390x-gnu": "0.93.0", "@oxc-minify/binding-linux-x64-gnu": "0.93.0", "@oxc-minify/binding-linux-x64-musl": "0.93.0", "@oxc-minify/binding-wasm32-wasi": "0.93.0", "@oxc-minify/binding-win32-arm64-msvc": "0.93.0", "@oxc-minify/binding-win32-x64-msvc": "0.93.0" } }, "sha512-pwMjOGN/I+cfLVkSmECcVHROKwECNVAXCT5h/29S4f0aArIUh3CQnix1yYy7MTQ3yThNuGANjjE9jWJyT43Vbw=="], + + "oxc-resolver": ["oxc-resolver@11.24.2", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.24.2", "@oxc-resolver/binding-android-arm64": "11.24.2", "@oxc-resolver/binding-darwin-arm64": "11.24.2", "@oxc-resolver/binding-darwin-x64": "11.24.2", "@oxc-resolver/binding-freebsd-x64": "11.24.2", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-musl": "11.24.2", "@oxc-resolver/binding-openharmony-arm64": "11.24.2", "@oxc-resolver/binding-wasm32-wasi": "11.24.2", "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw=="], + + "oxc-transform": ["oxc-transform@0.93.0", "", { "optionalDependencies": { "@oxc-transform/binding-android-arm64": "0.93.0", "@oxc-transform/binding-darwin-arm64": "0.93.0", "@oxc-transform/binding-darwin-x64": "0.93.0", "@oxc-transform/binding-freebsd-x64": "0.93.0", "@oxc-transform/binding-linux-arm-gnueabihf": "0.93.0", "@oxc-transform/binding-linux-arm-musleabihf": "0.93.0", "@oxc-transform/binding-linux-arm64-gnu": "0.93.0", "@oxc-transform/binding-linux-arm64-musl": "0.93.0", "@oxc-transform/binding-linux-riscv64-gnu": "0.93.0", "@oxc-transform/binding-linux-s390x-gnu": "0.93.0", "@oxc-transform/binding-linux-x64-gnu": "0.93.0", "@oxc-transform/binding-linux-x64-musl": "0.93.0", "@oxc-transform/binding-wasm32-wasi": "0.93.0", "@oxc-transform/binding-win32-arm64-msvc": "0.93.0", "@oxc-transform/binding-win32-x64-msvc": "0.93.0" } }, "sha512-QCwM2nMAWf4hEBehLVA2apllxdmmWLb5M0in9HwC2boaaFbP0QntbLy4hfRZGur2KKyEBErZbH9S5NYX8eHslg=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + + "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + + "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], + + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-import-resolver": ["ts-import-resolver@0.1.23", "", { "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-282pgr6j6aOvP3P2I6XugDxdBobkpdMmdbWjRjGl5gjPI1p0+oTNGDh1t924t75kRlyIkF65DiwhSIUysmyHQA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], + + "zlye": ["zlye@0.4.4", "", { "dependencies": { "picocolors": "^1.1.1" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-fwpeC841X3ElOLYRMKXbwX29pitNrsm6nRNvEhDMrRXDl3BhR2i03Bkr0GNrpyYgZJuEzUsBylXAYzgGPXXOCQ=="], + + "@supwr/react/@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + } +} diff --git a/examples/bun-react/bunfig.toml b/examples/bun-react/bunfig.toml new file mode 100644 index 0000000..da3535a --- /dev/null +++ b/examples/bun-react/bunfig.toml @@ -0,0 +1,4 @@ + +[serve.static] +plugins = ["bun-plugin-tailwind"] +env = "BUN_PUBLIC_*" diff --git a/examples/bun-react/package.json b/examples/bun-react/package.json new file mode 100644 index 0000000..f532f58 --- /dev/null +++ b/examples/bun-react/package.json @@ -0,0 +1,24 @@ +{ + "name": "bun-react-template", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --hot src/index.ts", + "start": "NODE_ENV=production bun src/index.ts", + "build": "bun run build.ts" + }, + "dependencies": { + "@supwr/react": "workspace:*", + "bun-plugin-tailwind": "^0.1.2", + "react": "^18", + "react-dom": "^18", + "swr": "^2.5.1", + "tailwindcss": "^4.1.11" + }, + "devDependencies": { + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/bun": "latest" + } +} \ No newline at end of file diff --git a/examples/bun-react/src/App.tsx b/examples/bun-react/src/App.tsx new file mode 100644 index 0000000..45c25c5 --- /dev/null +++ b/examples/bun-react/src/App.tsx @@ -0,0 +1,48 @@ +import { useSWR, useSWRMutate } from "@supwr/react" +// import useSWR from "swr" +import { Suspense } from "react"; + +export default function App() { + return
+ + {/* + + */} +
+} + +export interface Post { + userId: number; + id: number; + title: string; + body: string; +} + +const fetcher = (input: string | URL | Request, init?: RequestInit) => fetch(input, init).then(res => res.json() as Promise) + + +export function Demo() { + console.log('Demo render') + const { data, mutate } = useSWR(`https://jsonplaceholder.typicode.com/posts`, fetcher, { + // refreshInterval: 5000, // works + suspense: true, + }) + + // if (error) return
failed to load
+ // if (isLoading) return
loading...
+ // if (isValidating) return
validating...
+ + return ( +
+ Loading...

}> + {/* */} + {data?.map((entry) => ( +
+

{entry.title}

+

{entry.body}

+
+ ))} +
+
+ ) +} diff --git a/examples/bun-react/src/frontend.tsx b/examples/bun-react/src/frontend.tsx new file mode 100644 index 0000000..d809993 --- /dev/null +++ b/examples/bun-react/src/frontend.tsx @@ -0,0 +1,20 @@ +/** + * This file is the entry point for the React app, it sets up the root + * element and renders the App component to the DOM. + * + * It is included in `src/index.html`. + */ + +import { createRoot } from "react-dom/client"; +import App from "./App"; + +function start() { + const root = createRoot(document.getElementById("root")!); + root.render(); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); +} else { + start(); +} diff --git a/examples/bun-react/src/index.css b/examples/bun-react/src/index.css new file mode 100644 index 0000000..9addc76 --- /dev/null +++ b/examples/bun-react/src/index.css @@ -0,0 +1,50 @@ +@import "tailwindcss"; + +@layer base { + :root { + @apply text-[rgba(255,255,255,0.87)] bg-[#242424] font-sans; + } + + body { + @apply grid place-items-center min-w-[320px] min-h-screen relative m-0; + } +} + +body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + opacity: 0.05; + background: url("./logo.svg"); + background-size: 256px; + transform: rotate(-12deg) scale(1.35); + animation: slide 30s linear infinite; + pointer-events: none; +} + +@keyframes slide { + from { + background-position: 0 0; + } + to { + background-position: 256px 224px; + } +} + +@keyframes spin { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion) { + *, + ::before, + ::after { + animation: none !important; + } +} diff --git a/examples/bun-react/src/index.html b/examples/bun-react/src/index.html new file mode 100644 index 0000000..2a957d0 --- /dev/null +++ b/examples/bun-react/src/index.html @@ -0,0 +1,13 @@ + + + + + + + Bun + React + + +
+ + + diff --git a/examples/bun-react/src/index.ts b/examples/bun-react/src/index.ts new file mode 100644 index 0000000..863f069 --- /dev/null +++ b/examples/bun-react/src/index.ts @@ -0,0 +1,41 @@ +import { serve } from "bun"; +import index from "./index.html"; + +const server = serve({ + routes: { + // Serve index.html for all unmatched routes. + "/*": index, + + "/api/hello": { + async GET(req) { + return Response.json({ + message: "Hello, world!", + method: "GET", + }); + }, + async PUT(req) { + return Response.json({ + message: "Hello, world!", + method: "PUT", + }); + }, + }, + + "/api/hello/:name": async req => { + const name = req.params.name; + return Response.json({ + message: `Hello, ${name}!`, + }); + }, + }, + + development: process.env.NODE_ENV !== "production" && { + // Enable browser hot reloading in development + hmr: true, + + // Echo console logs from the browser to the server + console: true, + }, +}); + +console.log(`🚀 Server running at ${server.url}`); diff --git a/examples/bun-react/src/infinite.tsx b/examples/bun-react/src/infinite.tsx new file mode 100644 index 0000000..ce09677 --- /dev/null +++ b/examples/bun-react/src/infinite.tsx @@ -0,0 +1,97 @@ +import { useSWRInfinite } from "supwr/react" + +// Example fetcher for infinite loading +const fetcher = async (url: string) => { + const response = await fetch(url) + if (!response.ok) { + throw new Error('Failed to fetch') + } + return response.json() +} + +// Infinite loading example +function InfinitePosts() { + const { data, error, isLoading, size, setSize } = useSWRInfinite( + (index: number, previousPageData: any) => { + // Return URL for the page + if (index === 0) { + return '/api/posts?page=1' + } + // Get next page URL from previous page data + return previousPageData?.nextPageUrl || null + }, + fetcher, + { + initialSize: 1, + revalidateAll: false + } + ) + + const posts = data ? data.flat() : [] + const isLoadingMore = isLoading || (size > 0 && !data?.[size - 1]) + const isReachingEnd = data?.[data.length - 1]?.posts?.length === 0 + + const loadMore = () => setSize(size + 1) + + if (error) return
Error: {error.message}
+ + return ( +
+

Infinite Posts

+
+ {posts.map((post: any, index: number) => ( +
+

{post.title}

+

{post.content}

+
+ ))} +
+ + + +
Showing {posts.length} posts from {size} pages
+
+ ) +} + +// Example with template literal keys +function PaginatedData() { + const pageIndex = 1 + + const { data, error, isLoading } = useSWRInfinite( + (index: number) => [`/api/data?page=${index + 1}`], + fetcher + ) + + return ( +
+

Paginated Data

+ {data?.map((page: any[], pageIndex: number) => ( +
+

Page {pageIndex + 1}

+
    + {page.map((item: any, itemIndex: number) => ( +
  • {item.name}
  • + ))} +
+
+ ))} +
+ ) +} + +export default function InfiniteExample() { + return ( +
+ +
+ +
+ ) +} diff --git a/examples/bun-react/src/logo.svg b/examples/bun-react/src/logo.svg new file mode 100644 index 0000000..7ef1500 --- /dev/null +++ b/examples/bun-react/src/logo.svg @@ -0,0 +1 @@ +Bun Logo \ No newline at end of file diff --git a/examples/bun-react/src/react.svg b/examples/bun-react/src/react.svg new file mode 100644 index 0000000..1ab815a --- /dev/null +++ b/examples/bun-react/src/react.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/bun-react/tsconfig.json b/examples/bun-react/tsconfig.json new file mode 100644 index 0000000..f8dc197 --- /dev/null +++ b/examples/bun-react/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + "paths": { + "@/*": ["./src/*"] + }, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + + "exclude": ["dist", "node_modules"] +} diff --git a/examples/vite-html/.gitignore b/examples/vite-html/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/vite-html/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/vite-html/index.html b/examples/vite-html/index.html new file mode 100644 index 0000000..897915d --- /dev/null +++ b/examples/vite-html/index.html @@ -0,0 +1,13 @@ + + + + + + + vite-html + + +
+ + + diff --git a/examples/vite-html/package.json b/examples/vite-html/package.json new file mode 100644 index 0000000..a202334 --- /dev/null +++ b/examples/vite-html/package.json @@ -0,0 +1,18 @@ +{ + "name": "vite-html", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies":{ + "@supwr/core": "workspace:*" + }, + "devDependencies": { + "typescript": "~5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/vite-html/public/vite.svg b/examples/vite-html/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/vite-html/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-html/src/counter.ts b/examples/vite-html/src/counter.ts new file mode 100644 index 0000000..09e5afd --- /dev/null +++ b/examples/vite-html/src/counter.ts @@ -0,0 +1,9 @@ +export function setupCounter(element: HTMLButtonElement) { + let counter = 0 + const setCounter = (count: number) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/examples/vite-html/src/main.ts b/examples/vite-html/src/main.ts new file mode 100644 index 0000000..108c591 --- /dev/null +++ b/examples/vite-html/src/main.ts @@ -0,0 +1,71 @@ +import './style.css' +import { createSWR } from '@supwr/core' + +const swr = createSWR() +const app = document.querySelector('#app')! + +interface User { + name: { first: string; last: string } + email: string + picture: { large: string } + location: { city: string; country: string } +} + +const fetcher = (url: string) => fetch(url).then(res => res.json()).then(data => data.results[0]) + +function render(data?: User, error?: any, isValidating?: boolean) { + app.innerHTML = ` +
+
+

Supwr + HTML

+

Direct Core SWR for any project

+
+ +
+
+ +
+ +
+ ${error ? `
Failed to load user
` : ` +
+ ${data ? ` +
+ ${data.name.first} +
+
+

${data.name.first} ${data.name.last}

+ +
+ +

${data.location.city}, ${data.location.country}

+
+
+ ` : `
`} + ${isValidating ? `
Updating...
` : ''} +
+ `} +
+
+ +
+

Built with ❤️ and Supwr Core

+
+
+ ` + + document.querySelector('#refresh')?.addEventListener('click', () => { + swr.revalidate('random-user') + }) +} + +// Initial render +render() + +// Subscribe to SWR +swr.subscribe('random-user', (data: any, error: any, isValidating: boolean) => { + render(data, error, isValidating) +}, fetcher as any) + +// Trigger initial fetch +swr.revalidate('random-user', { fetcher: fetcher as any }) diff --git a/examples/vite-html/src/style.css b/examples/vite-html/src/style.css new file mode 100644 index 0000000..317ecb3 --- /dev/null +++ b/examples/vite-html/src/style.css @@ -0,0 +1,137 @@ +:root { + --primary: #f59e0b; + --bg: #111827; + --card-bg: rgba(31, 41, 55, 0.8); + --text: #f9fafb; + --text-muted: #9ca3af; + --border: rgba(255, 255, 255, 0.1); +} + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: 'Inter', system-ui, -apple-system, sans-serif; +} + +.container { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #111827, #1f2937); + padding: 2rem; +} + +header { + text-align: center; + margin-bottom: 3rem; +} + +header h1 { + font-size: 3rem; + font-weight: 800; + margin: 0; +} + +header h1 span { + color: var(--primary); +} + +header p { + color: var(--text-muted); + font-size: 1.1rem; +} + +.controls { + margin-bottom: 2rem; +} + +button { + background: var(--primary); + color: #000; + border: none; + padding: 0.75rem 2rem; + border-radius: 0.5rem; + font-weight: 700; + cursor: pointer; + transition: all 0.2s; +} + +button:hover { + filter: brightness(1.1); + transform: translateY(-1px); +} + +.card { + width: 320px; + background: var(--card-bg); + backdrop-filter: blur(16px); + border: 1px solid var(--border); + border-radius: 1rem; + overflow: hidden; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3); + position: relative; +} + +.image-wrapper { + height: 200px; + overflow: hidden; +} + +.image-wrapper img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.content { + padding: 1.5rem; +} + +.content h2 { + margin: 0; + font-size: 1.25rem; +} + +.email { + color: var(--text-muted); + font-size: 0.9rem; + margin: 0.25rem 0 1rem 0; +} + +.info label { + font-size: 0.7rem; + color: var(--text-muted); + text-transform: uppercase; +} + +.info p { + margin: 0.1rem 0 0 0; + font-weight: 500; +} + +.refresh-indicator { + position: absolute; + top: 1rem; + right: 1rem; + background: var(--primary); + color: #000; + padding: 0.2rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.6rem; + font-weight: 800; + animation: pulse 1s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } +} + +footer { + margin-top: 3rem; + color: var(--text-muted); + font-size: 0.9rem; +} diff --git a/examples/vite-html/src/typescript.svg b/examples/vite-html/src/typescript.svg new file mode 100644 index 0000000..d91c910 --- /dev/null +++ b/examples/vite-html/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-html/tsconfig.json b/examples/vite-html/tsconfig.json new file mode 100644 index 0000000..4ba8dd9 --- /dev/null +++ b/examples/vite-html/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/examples/vite-solid/.gitignore b/examples/vite-solid/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/vite-solid/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/vite-solid/README.md b/examples/vite-solid/README.md new file mode 100644 index 0000000..167c567 --- /dev/null +++ b/examples/vite-solid/README.md @@ -0,0 +1,28 @@ +## Usage + +```bash +$ npm install # or pnpm install or yarn install +``` + +### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) + +## Available Scripts + +In the project directory, you can run: + +### `npm run dev` + +Runs the app in the development mode.
+Open [http://localhost:5173](http://localhost:5173) to view it in the browser. + +### `npm run build` + +Builds the app for production to the `dist` folder.
+It correctly bundles Solid in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +## Deployment + +Learn more about deploying your application with the [documentations](https://vite.dev/guide/static-deploy.html) diff --git a/examples/vite-solid/index.html b/examples/vite-solid/index.html new file mode 100644 index 0000000..eef9861 --- /dev/null +++ b/examples/vite-solid/index.html @@ -0,0 +1,13 @@ + + + + + + + vite-solid + + +
+ + + diff --git a/examples/vite-solid/package.json b/examples/vite-solid/package.json new file mode 100644 index 0000000..fcc121a --- /dev/null +++ b/examples/vite-solid/package.json @@ -0,0 +1,20 @@ +{ + "name": "vite-solid", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "solid-js": "^1.9.10" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-solid": "^2.11.10" + } +} diff --git a/examples/vite-solid/public/vite.svg b/examples/vite-solid/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/vite-solid/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-solid/src/App.css b/examples/vite-solid/src/App.css new file mode 100644 index 0000000..e6e0143 --- /dev/null +++ b/examples/vite-solid/src/App.css @@ -0,0 +1,188 @@ +:root { + --primary: #58c4dc; + --bg: #0f172a; + --card-bg: rgba(30, 41, 59, 0.7); + --text: #f8fafc; + --text-muted: #94a3b8; + --border: rgba(255, 255, 255, 0.1); +} + +.container { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: radial-gradient(circle at top right, #1e293b, #0f172a); + color: var(--text); + font-family: 'Inter', system-ui, -apple-system, sans-serif; + padding: 2rem; +} + +header { + text-align: center; + margin-bottom: 3rem; +} + +header h1 { + font-size: 3.5rem; + font-weight: 800; + margin: 0; + letter-spacing: -0.02em; +} + +header h1 span { + color: var(--primary); +} + +header p { + color: var(--text-muted); + font-size: 1.25rem; + margin-top: 0.5rem; +} + +.controls { + display: flex; + align-items: center; + gap: 1.5rem; + margin-bottom: 2rem; + justify-content: center; +} + +button { + background: var(--primary); + color: var(--bg); + border: none; + padding: 0.75rem 1.5rem; + border-radius: 9999px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +button:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(88, 196, 220, 0.3); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.id-badge { + font-family: monospace; + font-size: 1.2rem; + background: rgba(255, 255, 255, 0.05); + padding: 0.5rem 1rem; + border-radius: 0.5rem; + border: 1px solid var(--border); +} + +.card-container { + perspective: 1000px; +} + +.card { + width: 320px; + background: var(--card-bg); + backdrop-filter: blur(12px); + border: 1px solid var(--border); + border-radius: 1.5rem; + overflow: hidden; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + transition: all 0.5s ease; + position: relative; +} + +.card.loading { + opacity: 0.7; + transform: scale(0.98); +} + +.image-wrapper { + position: relative; + height: 320px; +} + +.image-wrapper img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.status-badge { + position: absolute; + top: 1rem; + right: 1rem; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + backdrop-filter: blur(8px); + background: rgba(0, 0, 0, 0.5); +} + +.status-badge.alive { color: #4ade80; } +.status-badge.dead { color: #f87171; } +.status-badge.unknown { color: #94a3b8; } + +.content { + padding: 1.5rem; +} + +.content h2 { + margin: 0; + font-size: 1.5rem; +} + +.species { + color: var(--text-muted); + margin: 0.25rem 0 1rem 0; +} + +.info label { + display: block; + font-size: 0.75rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.25rem; +} + +.info p { + margin: 0; + font-weight: 500; +} + +.refresh-indicator { + position: absolute; + bottom: 0.5rem; + right: 1rem; + font-size: 0.7rem; + color: var(--primary); + animation: pulse 2s infinite; +} + +.skeleton { + height: 480px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.05), transparent); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +@keyframes pulse { + 0%, 100% { opacity: 0.5; } + 50% { opacity: 1; } +} + +footer { + margin-top: 4rem; + color: var(--text-muted); +} diff --git a/examples/vite-solid/src/App.tsx b/examples/vite-solid/src/App.tsx new file mode 100644 index 0000000..507b7a4 --- /dev/null +++ b/examples/vite-solid/src/App.tsx @@ -0,0 +1,72 @@ +import { createSignal, Show } from 'solid-js' +import { useSWR } from 'supwr/solid' +import './App.css' + +interface Character { + id: number + name: string + status: string + species: string + image: string + location: { name: string } +} + +const fetcher = (url: string) => fetch(url).then(res => res.json()) + +function App() { + const [id, setId] = createSignal(1) + const response = useSWR(() => `https://rickandmortyapi.com/api/character/${id()}`, fetcher) + + return ( +
+
+

Supwr + Solid

+

Ultra-lite SWR for the modern web

+
+ +
+
+ + ID: {id()} + +
+ +
+ Failed to load character
}> +
+
}> + {(character) => ( + <> +
+ {character().name} + + {character().status} + +
+
+

{character().name}

+

{character().species}

+
+ +

{character().location.name}

+
+
+ + )} + + +
Updating...
+
+
+ + + + +
+

Built with ❤️ and Supwr

+
+ + ) +} + +export default App diff --git a/examples/vite-solid/src/assets/solid.svg b/examples/vite-solid/src/assets/solid.svg new file mode 100644 index 0000000..025aa30 --- /dev/null +++ b/examples/vite-solid/src/assets/solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-solid/src/index.css b/examples/vite-solid/src/index.css new file mode 100644 index 0000000..08a3ac9 --- /dev/null +++ b/examples/vite-solid/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/vite-solid/src/index.tsx b/examples/vite-solid/src/index.tsx new file mode 100644 index 0000000..f67cd20 --- /dev/null +++ b/examples/vite-solid/src/index.tsx @@ -0,0 +1,8 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App.tsx' + +const root = document.getElementById('root') + +render(() => , root!) diff --git a/examples/vite-solid/tsconfig.app.json b/examples/vite-solid/tsconfig.app.json new file mode 100644 index 0000000..c0b480e --- /dev/null +++ b/examples/vite-solid/tsconfig.app.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/examples/vite-solid/tsconfig.json b/examples/vite-solid/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/examples/vite-solid/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/examples/vite-solid/tsconfig.node.json b/examples/vite-solid/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/examples/vite-solid/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/vite-solid/vite.config.ts b/examples/vite-solid/vite.config.ts new file mode 100644 index 0000000..4095d9b --- /dev/null +++ b/examples/vite-solid/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/vite-svelte/.gitignore b/examples/vite-svelte/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/vite-svelte/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/vite-svelte/.vscode/extensions.json b/examples/vite-svelte/.vscode/extensions.json new file mode 100644 index 0000000..bdef820 --- /dev/null +++ b/examples/vite-svelte/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/examples/vite-svelte/README.md b/examples/vite-svelte/README.md new file mode 100644 index 0000000..e6cd94f --- /dev/null +++ b/examples/vite-svelte/README.md @@ -0,0 +1,47 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some users. +- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. + +This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from 'svelte/store' +export default writable(0) +``` diff --git a/examples/vite-svelte/index.html b/examples/vite-svelte/index.html new file mode 100644 index 0000000..3d57a7b --- /dev/null +++ b/examples/vite-svelte/index.html @@ -0,0 +1,13 @@ + + + + + + + vite-svelte + + +
+ + + diff --git a/examples/vite-svelte/package.json b/examples/vite-svelte/package.json new file mode 100644 index 0000000..d0e1309 --- /dev/null +++ b/examples/vite-svelte/package.json @@ -0,0 +1,24 @@ +{ + "name": "vite-svelte", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "dependencies": { + + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tsconfig/svelte": "^5.0.6", + "@types/node": "^24.10.1", + "svelte": "^5.45.2", + "svelte-check": "^4.3.4", + "typescript": "~5.9.3", + "vite": "^7.3.1" + } +} \ No newline at end of file diff --git a/examples/vite-svelte/public/vite.svg b/examples/vite-svelte/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/vite-svelte/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-svelte/src/App.svelte b/examples/vite-svelte/src/App.svelte new file mode 100644 index 0000000..e901c82 --- /dev/null +++ b/examples/vite-svelte/src/App.svelte @@ -0,0 +1,52 @@ + + +
+

Svelte + Supwr

+ +
+ {#if $isLoading} +

Loading post data...

+ {:else if $error} +

Error loading data: {$error.message}

+ {:else if $data} +

{$data.title}

+

{$data.body}

+ + {#if $isValidating} +

Background updating...

+ {/if} + {/if} +
+
+ + diff --git a/examples/vite-svelte/src/app.css b/examples/vite-svelte/src/app.css new file mode 100644 index 0000000..61ba367 --- /dev/null +++ b/examples/vite-svelte/src/app.css @@ -0,0 +1,79 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/vite-svelte/src/assets/svelte.svg b/examples/vite-svelte/src/assets/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/examples/vite-svelte/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vite-svelte/src/lib/Counter.svelte b/examples/vite-svelte/src/lib/Counter.svelte new file mode 100644 index 0000000..37d75ce --- /dev/null +++ b/examples/vite-svelte/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/examples/vite-svelte/src/main.ts b/examples/vite-svelte/src/main.ts new file mode 100644 index 0000000..664a057 --- /dev/null +++ b/examples/vite-svelte/src/main.ts @@ -0,0 +1,9 @@ +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/vite-svelte/svelte.config.js b/examples/vite-svelte/svelte.config.js new file mode 100644 index 0000000..96b3455 --- /dev/null +++ b/examples/vite-svelte/svelte.config.js @@ -0,0 +1,8 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/examples/vite-svelte/tsconfig.app.json b/examples/vite-svelte/tsconfig.app.json new file mode 100644 index 0000000..31c18cf --- /dev/null +++ b/examples/vite-svelte/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "types": ["svelte", "vite/client"], + "noEmit": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/examples/vite-svelte/tsconfig.json b/examples/vite-svelte/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/examples/vite-svelte/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/examples/vite-svelte/tsconfig.node.json b/examples/vite-svelte/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/examples/vite-svelte/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/vite-svelte/vite.config.ts b/examples/vite-svelte/vite.config.ts new file mode 100644 index 0000000..d32eba1 --- /dev/null +++ b/examples/vite-svelte/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/vite-vue/.gitignore b/examples/vite-vue/.gitignore new file mode 100644 index 0000000..cd68f14 --- /dev/null +++ b/examples/vite-vue/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs diff --git a/examples/vite-vue/.vscode/extensions.json b/examples/vite-vue/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/examples/vite-vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/examples/vite-vue/README.md b/examples/vite-vue/README.md new file mode 100644 index 0000000..5442199 --- /dev/null +++ b/examples/vite-vue/README.md @@ -0,0 +1,42 @@ +# vite-vue + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +bun install +``` + +### Compile and Hot-Reload for Development + +```sh +bun dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +bun run build +``` diff --git a/examples/vite-vue/env.d.ts b/examples/vite-vue/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/examples/vite-vue/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vite-vue/index.html b/examples/vite-vue/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/examples/vite-vue/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/vite-vue/package.json b/examples/vite-vue/package.json new file mode 100644 index 0000000..cddb314 --- /dev/null +++ b/examples/vite-vue/package.json @@ -0,0 +1,30 @@ +{ + "name": "vite-vue", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "vue": "^3.5.29" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.11.0", + "@vitejs/plugin-vue": "^6.0.4", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-vue-devtools": "^8.0.6", + "vue-tsc": "^3.2.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } +} diff --git a/examples/vite-vue/public/favicon.ico b/examples/vite-vue/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/examples/vite-vue/public/favicon.ico differ diff --git a/examples/vite-vue/src/App.vue b/examples/vite-vue/src/App.vue new file mode 100644 index 0000000..a33bf96 --- /dev/null +++ b/examples/vite-vue/src/App.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/examples/vite-vue/src/main.ts b/examples/vite-vue/src/main.ts new file mode 100644 index 0000000..01433bc --- /dev/null +++ b/examples/vite-vue/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vite-vue/tsconfig.app.json b/examples/vite-vue/tsconfig.app.json new file mode 100644 index 0000000..c0f2d86 --- /dev/null +++ b/examples/vite-vue/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + // Extra safety for array and object lookups, but may have false positives. + "noUncheckedIndexedAccess": true, + + // Path mapping for cleaner imports. + "paths": { + "@/*": ["./src/*"] + }, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/examples/vite-vue/tsconfig.json b/examples/vite-vue/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/examples/vite-vue/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/examples/vite-vue/tsconfig.node.json b/examples/vite-vue/tsconfig.node.json new file mode 100644 index 0000000..5988839 --- /dev/null +++ b/examples/vite-vue/tsconfig.node.json @@ -0,0 +1,28 @@ +// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping. +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + // Most tools use transpilation instead of Node.js's native type-stripping. + // Bundler mode provides a smoother developer experience. + "module": "preserve", + "moduleResolution": "bundler", + + // Include Node.js types and avoid accidentally including other `@types/*` packages. + "types": ["node"], + + // Disable emitting output during `vue-tsc --build`, which is used for type-checking only. + "noEmit": true, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/examples/vite-vue/vite.config.ts b/examples/vite-vue/vite.config.ts new file mode 100644 index 0000000..4217010 --- /dev/null +++ b/examples/vite-vue/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +}) diff --git a/package.json b/package.json new file mode 100644 index 0000000..64bd3eb --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "supwr-monorepo", + "private": true, + "scripts": { + "build": "bunup", + "clean" : "rm -rf node_modules packages/*/node_modules examples/*/node_modules bun.lock" + }, + "workspaces": [ + "packages/*", + "examples/*" + ], + "dependencies": { + "@types/bun": "^1.4.0", + "bunup": "^0.16.32" + }, + "overrides": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + } +} \ No newline at end of file diff --git a/packages/core/__tests__/cache.test.ts b/packages/core/__tests__/cache.test.ts new file mode 100644 index 0000000..b802fea --- /dev/null +++ b/packages/core/__tests__/cache.test.ts @@ -0,0 +1,69 @@ +import { beforeEach, describe, expect, it, vi } from "bun:test"; +import { LRUCache, MemoryCache } from "../src/cache"; + +describe("LRUCache", () => { + let cache: LRUCache; + const maxSize = 2; + + beforeEach(() => { + cache = new LRUCache(maxSize); + }); + + it("inserts and retrieves items", () => { + const key = "foo"; + const lastModified = Date.now(); + const entry = { data: "bar", isValidating: false, isLoading: false, lastModified }; + cache.set(key, entry); + expect(cache.get(key)).toEqual(entry); + }); + + it("evicts oldest item when max size is reached", () => { + cache.set("1", { data: "1", isValidating: false, isLoading: false, lastModified: Date.now() }); + cache.set("2", { data: "2", isValidating: false, isLoading: false, lastModified: Date.now() }); + cache.set("3", { data: "3", isValidating: false, isLoading: false, lastModified: Date.now() }); + + expect(cache.has("1")).toBe(false); + expect(cache.has("2")).toBe(true); + expect(cache.has("3")).toBe(true); + }); + + it("updates LRU order on get", () => { + cache.set("1", { data: "1", isValidating: false, isLoading: false, lastModified: Date.now() }); + cache.set("2", { data: "2", isValidating: false, isLoading: false, lastModified: Date.now() }); + + // Access "1" to make it most recent + cache.get("1"); + + cache.set("3", { data: "3", isValidating: false, isLoading: false, lastModified: Date.now() }); + + expect(cache.has("2")).toBe(false); // "2" was oldest + expect(cache.has("1")).toBe(true); + expect(cache.has("3")).toBe(true); + }); + + it("handles TTL expiration", () => { + vi.useFakeTimers(); + const now = Date.now(); + cache.set("exp", { data: "val", isValidating: false, isLoading: false, lastModified: now, ttl: 1000 }); + + expect(cache.get("exp")).toBeDefined(); + + vi.advanceTimersByTime(1001); + + expect(cache.get("exp")).toBeUndefined(); + vi.useRealTimers(); + }); +}); + +describe("MemoryCache", () => { + it("cleans up on destroy", () => { + vi.useFakeTimers(); + const cache = new MemoryCache(100, 1000); + const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval'); + + cache.destroy(); + + expect(clearIntervalSpy).toHaveBeenCalled(); + vi.useRealTimers(); + }); +}); diff --git a/packages/core/__tests__/swr.test.ts b/packages/core/__tests__/swr.test.ts new file mode 100644 index 0000000..669e83a --- /dev/null +++ b/packages/core/__tests__/swr.test.ts @@ -0,0 +1,116 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; +import { createSWR } from "../src/swr"; +import type { Key, FetcherOptions } from "../src/types"; + +describe("SWRCore", () => { + let swr: any; + let fetcher: any; + const key = "test-key"; + + beforeEach(() => { + fetcher = vi.fn((k: Key, options: FetcherOptions) => Promise.resolve(`data-${k}`)); + swr = createSWR({ fetcher, dedupingInterval: 100 }); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("fetches data on subscription if not in cache", async () => { + const callback = vi.fn(); + swr.subscribe(key, callback); + + // Initial fetch is async + vi.runAllTimers(); + + expect(fetcher).toHaveBeenCalledWith(key, expect.objectContaining({ signal: expect.any(AbortSignal) })); + expect(callback).toHaveBeenCalledWith(`data-${key}`, undefined, false, false); + }); + + it("deduplicates concurrent fetches", async () => { + swr.revalidate(key); + swr.revalidate(key); + swr.revalidate(key); + + expect(fetcher).toHaveBeenCalledTimes(1); + }); + + it("updates all subscribers on mutation", async () => { + const cb1 = vi.fn(); + const cb2 = vi.fn(); + + swr.subscribe(key, cb1); + swr.subscribe(key, cb2); + + await swr.mutate(key, "manual-data"); + + expect(cb1).toHaveBeenCalledWith("manual-data", undefined, false, false); + expect(cb2).toHaveBeenCalledWith("manual-data", undefined, false, false); + }); + + it("handles fetcher errors", async () => { + const error = new Error("fetch failed"); + fetcher.mockRejectedValue(error); + + const callback = vi.fn(); + swr.subscribe(key, callback); + + vi.runAllTimers(); + + expect(callback).toHaveBeenCalledWith(undefined, error, false, false); + }); + + it("respects revalidateOnMount: false", async () => { + swr.updateConfig({ revalidateOnMount: false }); + // Put something in cache + await swr.mutate(key, "cached"); + fetcher.mockClear(); + + const callback = vi.fn(); + swr.subscribe(key, callback); + + vi.runAllTimers(); + + // Should not have fetched again despite subscription + expect(fetcher).not.toHaveBeenCalled(); + }); + + it("refreshInterval periodically revalidates with per-key fetcher", async () => { + const intervalFetcher = vi.fn((k: Key, options: FetcherOptions) => Promise.resolve(`data-${k}`)); + swr.updateConfig({ refreshInterval: 1000 }); + + const callback = vi.fn(); + swr.subscribe(key, callback, intervalFetcher); + + // Initial fetch (only run pending timers, not the interval) + vi.runOnlyPendingTimers(); + expect(intervalFetcher).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(`data-${key}`, undefined, false, false); + + // After interval passes, should revalidate again + vi.advanceTimersByTime(1000); + expect(intervalFetcher).toHaveBeenCalledTimes(2); + + // After another interval, should revalidate again + vi.advanceTimersByTime(1000); + expect(intervalFetcher).toHaveBeenCalledTimes(3); + }); + + it("revalidate uses stored fetcher when called without fetcher option", async () => { + // Create SWR without global fetcher + const noGlobalFetcherSwr = createSWR({ dedupingInterval: 100 }); + const perKeyFetcher = vi.fn((k: Key, options: FetcherOptions) => Promise.resolve(`data-${k}`)); + + const callback = vi.fn(); + noGlobalFetcherSwr.subscribe(key, callback, perKeyFetcher); + + // Initial fetch + vi.runOnlyPendingTimers(); + expect(perKeyFetcher).toHaveBeenCalledTimes(1); + + // Revalidate without passing fetcher - should use stored fetcher + await noGlobalFetcherSwr.revalidate(key); + expect(perKeyFetcher).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/core/bunup.config.ts b/packages/core/bunup.config.ts new file mode 100644 index 0000000..fb08f27 --- /dev/null +++ b/packages/core/bunup.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "bunup"; + +export default defineConfig({ + entry: "./src/index.ts", + dts: true, +}); \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..75d039a --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,31 @@ +{ + "name": "@supwr/core", + "version": "0.0.1", + "type": "module", + "files": [ + "dist" + ], + "module": "./dist/index.js", + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "scripts": { + "build": "bunup" + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "bunup": "^0.16.32" + } +} \ No newline at end of file diff --git a/packages/core/src/cache.ts b/packages/core/src/cache.ts new file mode 100644 index 0000000..84fb1bd --- /dev/null +++ b/packages/core/src/cache.ts @@ -0,0 +1,140 @@ +import type { Cache, CacheEntry, Key } from './types' + +/** + * LRU Cache implementation with TTL support + */ +export class LRUCache implements Cache { + private cache = new Map>() + private maxSize: number + + constructor(maxSize: number = 1000) { + this.maxSize = maxSize + } + + private serializeKey(key: Key): string { + if (key === null || key === undefined) { + return 'null' + } + + if (typeof key === 'string') { + return key + } + + if (Array.isArray(key)) { + return `["${key.join('","')}"]` + } + + return JSON.stringify(key) + } + + private isExpired(entry: CacheEntry): boolean { + if (!entry.ttl) return false + return Date.now() - entry.lastModified > entry.ttl + } + + get(key: Key): CacheEntry | undefined { + const serializedKey = this.serializeKey(key) + const entry = this.cache.get(serializedKey) + + if (!entry) return undefined + + if (this.isExpired(entry)) { + this.cache.delete(serializedKey) + return undefined + } + + // Move to end (LRU behavior) + this.cache.delete(serializedKey) + this.cache.set(serializedKey, entry) + + return entry + } + + set(key: Key, value: CacheEntry): void { + const serializedKey = this.serializeKey(key) + + // Remove oldest if at capacity + if (this.cache.size >= this.maxSize && !this.cache.has(serializedKey)) { + const firstKey = this.cache.keys().next().value + if (firstKey) { + this.cache.delete(firstKey) + } + } + + this.cache.set(serializedKey, { + ...value, + lastModified: Date.now() + }) + } + + delete(key: Key): void { + const serializedKey = this.serializeKey(key) + this.cache.delete(serializedKey) + } + + clear(): void { + this.cache.clear() + } + + keys(): Key[] { + return Array.from(this.cache.keys()).map(key => { + try { + return JSON.parse(key) + } catch { + return key + } + }) + } + + has(key: Key): boolean { + const serializedKey = this.serializeKey(key) + const entry = this.cache.get(serializedKey) + + if (!entry) return false + + if (this.isExpired(entry)) { + this.cache.delete(serializedKey) + return false + } + + return true + } + + // Cleanup expired entries + cleanup(): void { + for (const [key, entry] of this.cache.entries()) { + if (this.isExpired(entry)) { + this.cache.delete(key) + } + } + } + + // Get cache size + size(): number { + return this.cache.size + } +} + +/** + * Memory cache with automatic cleanup + */ +export class MemoryCache extends LRUCache { + private cleanupInterval: ReturnType | null = null + + constructor(maxSize: number = 1000, cleanupIntervalMs: number = 60000) { + super(maxSize) + + // Set up periodic cleanup + this.cleanupInterval = globalThis.setInterval(() => { + this.cleanup() + }, cleanupIntervalMs) + } + + destroy(): void { + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval) + this.cleanupInterval = null + } + this.clear() + } +} diff --git a/packages/core/src/events.ts b/packages/core/src/events.ts new file mode 100644 index 0000000..881d43e --- /dev/null +++ b/packages/core/src/events.ts @@ -0,0 +1,163 @@ +import type { Callback, EventManager, Key } from './types' +import { serializeKey } from './key' + +/** + * Event manager for handling SWR state changes + */ +export class SWREventManager implements EventManager { + private listeners = new Map>>() + + on(event: string, callback: Callback): () => void { + if (!this.listeners.has(event)) { + this.listeners.set(event, new Set()) + } + + const eventListeners = this.listeners.get(event)! + eventListeners.add(callback) + + // Return unsubscribe function + return () => { + eventListeners.delete(callback) + if (eventListeners.size === 0) { + this.listeners.delete(event) + } + } + } + + emit(event: string, ...args: unknown[]): void { + const eventListeners = this.listeners.get(event) + if (!eventListeners) return + + // Create a copy to avoid issues with listeners being added/removed during iteration + const listeners = Array.from(eventListeners) + + for (const callback of listeners) { + try { + callback(...args) + } catch (error) { + console.error('Error in event listener:', error) + } + } + } + + off(event: string, callback: Callback): void { + const eventListeners = this.listeners.get(event) + if (!eventListeners) return + + eventListeners.delete(callback) + if (eventListeners.size === 0) { + this.listeners.delete(event) + } + } + + // Clear all listeners for an event or all events + clear(event?: string): void { + if (event) { + this.listeners.delete(event) + } else { + this.listeners.clear() + } + } + + // Get the number of listeners for an event + listenerCount(event: string): number { + return this.listeners.get(event)?.size || 0 + } + + // Get all event names + eventNames(): string[] { + return Array.from(this.listeners.keys()) + } +} + +/** + * Key-specific event manager that handles events per cache key + */ +export class KeyEventManager { + private eventManager = new SWREventManager() + private keyListeners = new Map>>() + + subscribe(key: Key, callback: Callback): () => void { + const serializedKey = serializeKey(key) + + // Add to key-specific listeners + if (!this.keyListeners.has(serializedKey)) { + this.keyListeners.set(serializedKey, new Set()) + } + this.keyListeners.get(serializedKey)!.add(callback as Callback) + + // Also subscribe to global events for this key + const unsubscribeGlobal = this.eventManager.on(`key:${serializedKey}`, callback as Callback) + + // Return unsubscribe function + return () => { + const keyListeners = this.keyListeners.get(serializedKey) + if (keyListeners) { + keyListeners.delete(callback as Callback) + if (keyListeners.size === 0) { + this.keyListeners.delete(serializedKey) + } + } + unsubscribeGlobal() + } + } + + emit(key: Key, ...args: unknown[]): void { + const serializedKey = serializeKey(key) + + // Emit to key-specific listeners + const keyListeners = this.keyListeners.get(serializedKey) + if (keyListeners) { + const listeners = Array.from(keyListeners) + for (const callback of listeners) { + try { + callback(...args) + } catch (error) { + console.error('Error in key event listener:', error) + } + } + } + + // Also emit to global event system + this.eventManager.emit(`key:${serializedKey}`, ...args) + } + + // Global events + on(event: string, callback: Callback): () => void { + return this.eventManager.on(event, callback) + } + + emitGlobal(event: string, ...args: unknown[]): void { + this.eventManager.emit(event, ...args) + } + + clear(key?: Key): void { + if (key) { + const serializedKey = serializeKey(key) + this.keyListeners.delete(serializedKey) + this.eventManager.clear(`key:${serializedKey}`) + } else { + this.keyListeners.clear() + this.eventManager.clear() + } + } + + // Get statistics + getStats(): { keyListeners: number; globalListeners: number; totalKeys: number } { + let totalKeyListeners = 0 + for (const listeners of this.keyListeners.values()) { + totalKeyListeners += listeners.size + } + + let totalGlobalListeners = 0 + for (const event of this.eventManager.eventNames()) { + totalGlobalListeners += this.eventManager.listenerCount(event) + } + + return { + keyListeners: totalKeyListeners, + globalListeners: totalGlobalListeners, + totalKeys: this.keyListeners.size + } + } +} diff --git a/packages/core/src/fetcher.ts b/packages/core/src/fetcher.ts new file mode 100644 index 0000000..6537443 --- /dev/null +++ b/packages/core/src/fetcher.ts @@ -0,0 +1,197 @@ +import type { Fetcher, FetcherOptions, Key, CacheEntry } from './types' +import { serializeKey } from './key' + +export class DeduplicationManager { + private pendingRequests = new Map>() + + constructor(private dedupingInterval: number = 2000) {} + + async dedupe( + key: Key, + fetcher: Fetcher, + options: FetcherOptions + ): Promise { + const serializedKey = serializeKey(key) + const pendingRequest = this.pendingRequests.get(serializedKey) + + if (pendingRequest) { + return pendingRequest as Promise + } + + const requestPromise = this.createRequest(key, fetcher, options) + this.pendingRequests.set(serializedKey, requestPromise) + + try { + const result = await requestPromise + return result + } finally { + this.pendingRequests.delete(serializedKey) + } + } + + private async createRequest( + key: Key, + fetcher: Fetcher, + options: FetcherOptions + ): Promise { + try { + return await fetcher(key, options) + } catch (error) { + throw error + } + } + + clear(): void { + this.pendingRequests.clear() + } + + getPendingCount(): number { + return this.pendingRequests.size + } + + isPending(key: Key): boolean { + const serializedKey = serializeKey(key) + return this.pendingRequests.has(serializedKey) + } +} + +export class FetchManager { + private deduplicationManager: DeduplicationManager + private abortControllers = new Map() + + constructor(dedupingInterval: number = 2000) { + this.deduplicationManager = new DeduplicationManager(dedupingInterval) + } + + async fetch( + key: Key, + fetcher: Fetcher, + options: { + dedupe?: boolean + timeout?: number + signal?: AbortSignal + } = {} + ): Promise { + const serializedKey = serializeKey(key) + + const abortController = new AbortController() + this.abortControllers.set(serializedKey, abortController) + + if (options.signal) { + if (options.signal.aborted) { + abortController.abort() + throw new DOMException('Request aborted', 'AbortError') + } + + options.signal.addEventListener('abort', () => { + abortController.abort() + }, { once: true }) + } + + let timeoutId: ReturnType | undefined + if (options.timeout) { + timeoutId = globalThis.setTimeout(() => { + abortController.abort() + }, options.timeout) + } + + try { + const fetcherOptions: FetcherOptions = { + signal: abortController.signal + } + + let result: Data + + if (options.dedupe !== false) { + result = await this.deduplicationManager.dedupe(key, fetcher, fetcherOptions) + } else { + result = await fetcher(key, fetcherOptions) + } + + return result + } catch (error) { + if (abortController.signal.aborted) { + throw new DOMException('Request aborted', 'AbortError') + } + throw error + } finally { + this.abortControllers.delete(serializedKey) + if (timeoutId) { + clearTimeout(timeoutId) + } + } + } + + abort(key: Key): void { + const serializedKey = serializeKey(key) + const controller = this.abortControllers.get(serializedKey) + if (controller) { + controller.abort() + this.abortControllers.delete(serializedKey) + } + } + + abortAll(): void { + for (const controller of this.abortControllers.values()) { + controller.abort() + } + this.abortControllers.clear() + this.deduplicationManager.clear() + } + + isFetching(key: Key): boolean { + const serializedKey = serializeKey(key) + return this.abortControllers.has(serializedKey) || this.deduplicationManager.isPending(key) + } + + getActiveCount(): number { + return this.abortControllers.size + } + + getPendingCount(): number { + return this.deduplicationManager.getPendingCount() + } +} + +export function createFetcher( + input: string | URL, + init?: RequestInit +): Fetcher { + return async (key: Key, options: FetcherOptions) => { + const url = typeof key === 'string' ? key : String(key) + + const response = await fetch(url, { + ...init, + signal: options.signal + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return response.json() as Promise + } +} + +export function createFetcherWithErrorHandler( + fetcher: (url: string, options: RequestInit) => Promise, + errorHandler?: (error: unknown, key: Key) => Error +): Fetcher { + return async (key: Key, options: FetcherOptions) => { + try { + const url = typeof key === 'string' ? key : String(key) + const response = await fetcher(url, { signal: options.signal }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return response.json() as Promise + } catch (error) { + if (errorHandler) { + throw errorHandler(error, key) + } + throw error + } + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..8ca4623 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,65 @@ +// Core types +export type { + Key, + CacheEntry, + CacheOptions, + FetcherOptions, + Fetcher, + BareFetcher, + SWRFetcher, + MutatorOptions, + MutatorCallback, + MutatorValue, + RevalidateOptions, + Callback, + Unsubscribe, + SWRConfig, + Cache, + EventManager, + SWRCore +} from './types' + +// Core implementation +export { SWRCoreImpl, createSWR } from './swr' + +// Cache +export { LRUCache, MemoryCache } from './cache' + +// Events +export { SWREventManager, KeyEventManager } from './events' + +// Key utilities +export { + serializeKey, + resolveKey, + hashKey, + areKeysEqual, + isValidKey, + normalizeKey +} from './key' + +// Configuration +export { defaultConfig, mergeConfig, validateConfig, ConfigManager } from './options' + +// Fetcher +export { + DeduplicationManager, + FetchManager, + createFetcher, + createFetcherWithErrorHandler +} from './fetcher' + +// Revalidation +export { RevalidationManager, setupGlobalRevalidationHandlers } from './revalidate' + +// Mutation +export { + MutationManager, + createMutator, + createMutateFunction, + createOptimisticMutation, + createAsyncMutation +} from './mutate' + +// Default export +export { createSWR as default } from './swr' diff --git a/packages/core/src/key.ts b/packages/core/src/key.ts new file mode 100644 index 0000000..ab84947 --- /dev/null +++ b/packages/core/src/key.ts @@ -0,0 +1,87 @@ +import type { Key } from './types' + +/** + * Key resolution and serialization utilities + */ + +export function serializeKey(key: Key): string { + if (key === null || key === undefined) { + return 'null' + } + + if (typeof key === 'string') { + return key + } + + if (Array.isArray(key)) { + // Handle nested arrays and objects + return `[${key.map(item => serializeKey(item)).join(',')}]` + } + + if (typeof key === 'object') { + try { + return JSON.stringify(key, (k, v) => { + // Handle functions by converting to string representation + if (typeof v === 'function') { + return `[Function: ${v.name || 'anonymous'}]` + } + // Handle undefined + if (v === undefined) { + return '[Undefined]' + } + // Handle symbols + if (typeof v === 'symbol') { + return `[Symbol: ${v.toString()}]` + } + return v + }) + } catch (error) { + // Fallback for circular references + return '[Object]' + } + } + + return String(key) +} + +export function resolveKey(key: Key | (() => Key)): Key { + if (typeof key === 'function') { + try { + return key() + } catch (error) { + console.error('Error resolving key function:', error) + return null + } + } + return key +} + +export function hashKey(key: Key): string { + const serialized = serializeKey(key) + + // Simple hash function for better distribution + let hash = 0 + for (let i = 0; i < serialized.length; i++) { + const char = serialized.charCodeAt(i) + hash = ((hash << 5) - hash) + char + hash = hash & hash // Convert to 32-bit integer + } + + return Math.abs(hash).toString(36) +} + +export function areKeysEqual(key1: Key, key2: Key): boolean { + return serializeKey(key1) === serializeKey(key2) +} + +export function isValidKey(key: Key): boolean { + return key !== null && key !== undefined +} + +export function normalizeKey(key: Key): string { + if (!isValidKey(key)) { + return 'null' + } + + return serializeKey(key) +} diff --git a/packages/core/src/mutate.ts b/packages/core/src/mutate.ts new file mode 100644 index 0000000..d3d6a5b --- /dev/null +++ b/packages/core/src/mutate.ts @@ -0,0 +1,267 @@ +import type { + Key, + MutatorOptions, + MutatorValue, + MutatorCallback, + CacheEntry, + SWRConfig, + Fetcher +} from './types' +import { serializeKey } from './key' +import { FetchManager } from './fetcher' + +/** + * Mutation manager for handling data mutations with optimistic updates + */ +export class MutationManager { + private optimisticData = new Map() + private rollbackData = new Map() + + constructor( + private fetchManager: FetchManager, + private config: SWRConfig, + private onUpdate: (key: Key, entry: Partial>) => void, + private getCurrentData: (key: Key) => Data | undefined, + private getFetcher?: (key: Key) => Fetcher | undefined + ) {} + + updateConfig(config: SWRConfig): void { + this.config = config + } + + /** + * Mutate data for a key + */ + async mutate( + key: Key, + data?: MutatorValue, + options: MutatorOptions = {} + ): Promise { + const serializedKey = serializeKey(key) + const currentData = this.getCurrentData(key) + + // Store rollback data if needed + if (options.rollbackOnError !== false && currentData !== undefined) { + this.rollbackData.set(serializedKey, currentData) + } + + try { + // Handle optimistic data + if (options.optimisticData !== undefined) { + this.optimisticData.set(serializedKey, options.optimisticData) + this.onUpdate(key, { + data: options.optimisticData, + isLoading: false, + isValidating: false + }) + } else if (data !== undefined) { + // Handle immediate data update + const newData = await this.resolveData(data, currentData) + this.optimisticData.set(serializedKey, newData) + this.onUpdate(key, { + data: newData, + isLoading: false, + isValidating: false + }) + } + + // Revalidate if requested + if (options.revalidate !== false) { + await this.revalidate(key) + } + + // Return the final data + return this.getCurrentData(key) + } catch (error) { + // Rollback on error if requested + if (options.rollbackOnError !== false) { + const rollbackValue = this.rollbackData.get(serializedKey) + if (rollbackValue !== undefined) { + this.onUpdate(key, { + data: rollbackValue, + error: error as Error, + isLoading: false, + isValidating: false + }) + } + } + + throw error + } finally { + // Clean up optimistic data + this.optimisticData.delete(serializedKey) + this.rollbackData.delete(serializedKey) + } + } + + /** + * Resolve data from various input types + */ + private async resolveData( + data: MutatorValue, + currentData: Data | undefined + ): Promise { + if (typeof data === 'function') { + const result = (data as MutatorCallback)(currentData) + return Promise.resolve(result) + } + + if (data === undefined) { + throw new Error('Mutation data cannot be undefined') + } + + return data + } + + /** + * Revalidate a key after mutation + */ + private async revalidate(key: Key): Promise { + const fetcher = this.getFetcher?.(key) || this.config.fetcher + if (!fetcher) return + + try { + this.onUpdate(key, { isValidating: true }) + + const freshData = await this.fetchManager.fetch(key, fetcher, { + dedupe: false, // Always fetch fresh data for revalidation + timeout: this.config.loadingTimeout + }) + + // Update with fresh data + this.onUpdate(key, { + data: freshData, + error: undefined, + isValidating: false, + isLoading: false + }) + + this.config.onSuccess?.(freshData, key) + } catch (error) { + const errorObj = error as Error + + this.onUpdate(key, { + error: errorObj, + isValidating: false, + isLoading: false + }) + + this.config.onError?.(errorObj, key) + } + } + + /** + * Check if a key has optimistic data + */ + hasOptimisticData(key: Key): boolean { + const serializedKey = serializeKey(key) + return this.optimisticData.has(serializedKey) + } + + /** + * Get optimistic data for a key + */ + getOptimisticData(key: Key): Data | undefined { + const serializedKey = serializeKey(key) + return this.optimisticData.get(serializedKey) + } + + /** + * Clear all optimistic data + */ + clearOptimisticData(): void { + this.optimisticData.clear() + this.rollbackData.clear() + } + + /** + * Create a mutation function for a specific key + */ + createMutator(key: Key) { + return ( + data?: MutatorValue, + options?: MutatorOptions + ): Promise => { + return this.mutate(key, data, options) + } + } + + /** + * Batch mutate multiple keys + */ + async mutateBatch( + mutations: Array<{ + key: Key + data?: MutatorValue + options?: MutatorOptions + }> + ): Promise<(Data | undefined)[]> { + const results = await Promise.allSettled( + mutations.map(({ key, data, options }) => this.mutate(key, data, options)) + ) + + return results.map((result, index) => { + const mutation = mutations[index] + if (result.status === 'fulfilled') { + return result.value + } else { + console.error(`Mutation failed for key ${mutation?.key}:`, result.reason || 'Unknown error') + return undefined + } + }) + } +} + +/** + * Utility function to create a mutator + */ +export function createMutator( + fetchManager: FetchManager, + config: SWRConfig, + onUpdate: (key: Key, entry: Partial>) => void, + getCurrentData: (key: Key) => Data | undefined +): MutationManager { + return new MutationManager(fetchManager, config, onUpdate, getCurrentData) +} + +/** + * Higher-order function that creates a mutate function + */ +export function createMutateFunction( + mutationManager: MutationManager +) { + return ( + key: Key, + data?: MutatorValue, + options?: MutatorOptions + ): Promise => { + return mutationManager.mutate(key, data, options) + } +} + +/** + * Utility for creating optimistic mutations + */ +export function createOptimisticMutation( + currentData: Data, + updateFn: (current: Data) => Data +): { + optimisticData: Data + rollbackData: Data +} { + return { + optimisticData: updateFn(currentData), + rollbackData: currentData + } +} + +/** + * Utility for creating async mutations + */ +export function createAsyncMutation( + asyncFn: (currentData?: Data) => Promise +): MutatorCallback { + return async (currentData?: Data) => { + return await asyncFn(currentData) + } +} diff --git a/packages/core/src/options.ts b/packages/core/src/options.ts new file mode 100644 index 0000000..5282801 --- /dev/null +++ b/packages/core/src/options.ts @@ -0,0 +1,196 @@ +import type { SWRConfig, Fetcher } from './types' + +/** + * Default configuration for SWR + */ +export const defaultConfig: SWRConfig = { + // Cache options + ttl: 0, // No TTL by default + revalidateOnMount: true, + revalidateOnFocus: true, + revalidateOnReconnect: true, + refreshInterval: 0, + dedupingInterval: 2000, + errorRetryCount: 3, + errorRetryInterval: 5000, + loadingTimeout: 3000, + + // Callbacks + onSuccess: () => {}, + onError: () => {}, + onErrorRetry: (error, key, config, revalidate, { retryCount }) => { + if (retryCount >= Math.max(config.errorRetryCount || 0, 0)) return + + const timeout = ~~((Math.random() + 0.5) * (1 << (retryCount < 8 ? retryCount : 8))) * (config.errorRetryInterval || 0) + + setTimeout(revalidate, timeout) + }, + onLoadingSlow: () => {}, + + // Utilities + isPaused: () => false, + compare: (a, b) => a === b, + fallback: {} +} + +/** + * Merge user configuration with defaults + */ +export function mergeConfig( + userConfig?: Partial> +): SWRConfig { + if (!userConfig) return defaultConfig as SWRConfig + + return { + ...defaultConfig, + ...userConfig, + // Ensure arrays and objects are properly merged + fallback: { ...defaultConfig.fallback, ...userConfig.fallback } as Record + } as SWRConfig +} + +/** + * Validate configuration + */ +export function validateConfig( + config: SWRConfig +): { valid: boolean; errors: string[] } { + const errors: string[] = [] + + // Validate fetcher + if (config.fetcher && typeof config.fetcher !== 'function') { + errors.push('fetcher must be a function') + } + + // Validate numeric options + const numericFields = [ + 'ttl', + 'refreshInterval', + 'dedupingInterval', + 'errorRetryCount', + 'errorRetryInterval', + 'loadingTimeout' + ] as const + + for (const field of numericFields) { + const value = config[field] + if (value !== undefined && (typeof value !== 'number' || value < 0)) { + errors.push(`${field} must be a non-negative number`) + } + } + + // Validate boolean options + const booleanFields = [ + 'revalidateOnFocus', + 'revalidateOnReconnect' + ] as const + + for (const field of booleanFields) { + const value = config[field] + if (value !== undefined && typeof value !== 'boolean') { + errors.push(`${field} must be a boolean`) + } + } + + // Validate callbacks + const callbackFields = [ + 'onSuccess', + 'onError', + 'onErrorRetry', + 'onLoadingSlow', + 'isPaused', + 'compare' + ] as const + + for (const field of callbackFields) { + const value = config[field] + if (value !== undefined && typeof value !== 'function') { + errors.push(`${field} must be a function`) + } + } + + // Validate fallback + if (config.fallback && typeof config.fallback !== 'object') { + errors.push('fallback must be an object') + } + + return { + valid: errors.length === 0, + errors + } +} + +/** + * Create a configuration manager + */ +export class ConfigManager { + private config: SWRConfig + + constructor(userConfig?: Partial>) { + const merged = mergeConfig(userConfig) + const validation = validateConfig(merged) + + if (!validation.valid) { + console.warn('Invalid SWR configuration:', validation.errors) + } + + this.config = merged + } + + get(): SWRConfig { + return this.config + } + + update(updates: Partial>): void { + this.config = { ...this.config, ...updates } + + const validation = validateConfig(this.config) + if (!validation.valid) { + console.warn('Invalid SWR configuration update:', validation.errors) + } + } + + setFetcher(fetcher: Fetcher): void { + this.config.fetcher = fetcher + } + + getFetcher(): Fetcher | undefined { + return this.config.fetcher + } + + isPaused(): boolean { + return !!this.config.isPaused?.() + } + + shouldRevalidateOnFocus(): boolean { + return !!(this.config.revalidateOnFocus && !this.isPaused()) + } + + shouldRevalidateOnReconnect(): boolean { + return !!(this.config.revalidateOnReconnect && !this.isPaused()) + } + + getRefreshInterval(): number { + return this.config.refreshInterval || 0 + } + + getDedupingInterval(): number { + return this.config.dedupingInterval || 2000 + } + + getErrorRetryCount(): number { + return this.config.errorRetryCount || 3 + } + + getErrorRetryInterval(): number { + return this.config.errorRetryInterval || 5000 + } + + getLoadingTimeout(): number { + return this.config.loadingTimeout || 3000 + } + + getFallbackData(key: string): Data | undefined { + return this.config.fallback?.[key] + } +} diff --git a/packages/core/src/revalidate.ts b/packages/core/src/revalidate.ts new file mode 100644 index 0000000..50b414b --- /dev/null +++ b/packages/core/src/revalidate.ts @@ -0,0 +1,313 @@ +import type { Key, RevalidateOptions, SWRConfig, CacheEntry, Fetcher } from './types' +import { serializeKey } from './key' +import { FetchManager } from './fetcher' + +/** + * Revalidation manager handles different revalidation strategies + */ +export class RevalidationManager { + private intervals = new Map>() + private timeouts = new Map>() + private retryAttempts = new Map() + private fetchers = new Map>() + + constructor( + private fetchManager: FetchManager, + private config: SWRConfig, + private onUpdate: (key: Key, entry: Partial>) => void + ) {} + + /** + * Get the fetcher for a key + */ + setFetcher(key: Key, fetcher: Fetcher): void { + const serializedKey = serializeKey(key) + this.fetchers.set(serializedKey, fetcher) + } + + /** + * Get the stored fetcher for a key + */ + getFetcher(key: Key): Fetcher | undefined { + const serializedKey = serializeKey(key) + return this.fetchers.get(serializedKey) + } + + /** + * Remove the fetcher for a key + */ + removeFetcher(key: Key): void { + const serializedKey = serializeKey(key) + this.fetchers.delete(serializedKey) + } + + /** + * Start interval revalidation for a key + */ + startIntervalRevalidation(key: Key): void { + const serializedKey = serializeKey(key) + const interval = this.config.refreshInterval + + if (!interval || interval <= 0) return + + // Clear existing interval + this.stopIntervalRevalidation(key) + + const intervalId = setInterval(() => { + if (!this.config.isPaused?.()) { + const fetcher = this.fetchers.get(serializedKey) || this.config.fetcher + this.revalidate(key, { dedupe: true, fetcher }) + } + }, interval) + + this.intervals.set(serializedKey, intervalId) + } + + /** + * Stop interval revalidation for a key + */ + stopIntervalRevalidation(key: Key): void { + const serializedKey = serializeKey(key) + const intervalId = this.intervals.get(serializedKey) + + if (intervalId) { + clearInterval(intervalId) + this.intervals.delete(serializedKey) + } + } + + /** + * Revalidate on focus + */ + revalidateOnFocus(key: Key): void { + if (!this.config.revalidateOnFocus || this.config.isPaused?.()) return + + this.revalidate(key, { dedupe: true }) + } + + /** + * Revalidate on reconnect + */ + revalidateOnReconnect(key: Key): void { + if (!this.config.revalidateOnReconnect || this.config.isPaused?.()) return + + this.revalidate(key, { dedupe: true }) + } + + /** + * Manual revalidation + */ + async revalidate(key: Key, options: RevalidateOptions = {}): Promise { + const serializedKey = serializeKey(key) + const fetcher = options.fetcher || this.fetchers.get(serializedKey) || this.config.fetcher + + if (!fetcher) { + console.warn('No fetcher provided for revalidation') + return false + } + + console.log('RevalidationManager.revalidate called for', key, 'dedupe:', options.dedupe, 'fetcher:', !!fetcher) + + try { + // Set loading state + this.onUpdate(key, { isValidating: true }) + + const data = await this.fetchManager.fetch(key, fetcher, { + dedupe: options.dedupe !== false, + timeout: this.config.loadingTimeout + }) + + // Update cache with new data + this.onUpdate(key, { + data, + error: undefined, + isValidating: false, + isLoading: false, + lastModified: Date.now() + }) + + // Reset retry attempts on success + this.retryAttempts.delete(serializedKey) + + // Call success callback + this.config.onSuccess?.(data, key) + + return true + } catch (error) { + const errorObj = error as Error + + // Update cache with error + this.onUpdate(key, { + error: errorObj, + isValidating: false, + isLoading: false, + lastModified: Date.now() + }) + + // Call error callback + this.config.onError?.(errorObj, key) + + // Handle retry logic + await this.handleRetry(key, errorObj) + + return false + } + } + + /** + * Handle error retry logic + */ + private async handleRetry(key: Key, error: Error): Promise { + const serializedKey = serializeKey(key) + const retryCount = this.retryAttempts.get(serializedKey) || 0 + const maxRetries = this.config.errorRetryCount || 0 + + if (retryCount >= maxRetries) { + this.retryAttempts.delete(serializedKey) + return + } + + // Increment retry count + this.retryAttempts.set(serializedKey, retryCount + 1) + + // Calculate retry delay with exponential backoff + const baseInterval = this.config.errorRetryInterval || 5000 + const delay = Math.min(baseInterval * Math.pow(2, retryCount), 30000) + + // Schedule retry + const timeoutId = setTimeout(() => { + if (!this.config.isPaused?.()) { + this.revalidate(key, { dedupe: true }) + } + }, delay) + + this.timeouts.set(`${serializedKey}:retry`, timeoutId) + } + + /** + * Cancel retry for a key + */ + cancelRetry(key: Key): void { + const serializedKey = serializeKey(key) + const timeoutId = this.timeouts.get(`${serializedKey}:retry`) + + if (timeoutId) { + clearTimeout(timeoutId) + this.timeouts.delete(`${serializedKey}:retry`) + } + + this.retryAttempts.delete(serializedKey) + } + + /** + * Clear all revalidation for a key + */ + clear(key: Key): void { + this.stopIntervalRevalidation(key) + this.cancelRetry(key) + this.removeFetcher(key) + + const serializedKey = serializeKey(key) + + // Clear any other timeouts for this key + for (const [timeoutKey, timeoutId] of this.timeouts.entries()) { + if (timeoutKey.startsWith(serializedKey)) { + clearTimeout(timeoutId) + this.timeouts.delete(timeoutKey) + } + } + } + + /** + * Clear all revalidation + */ + clearAll(): void { + // Clear all intervals + for (const intervalId of this.intervals.values()) { + clearInterval(intervalId) + } + this.intervals.clear() + + // Clear all timeouts + for (const timeoutId of this.timeouts.values()) { + clearTimeout(timeoutId) + } + this.timeouts.clear() + + // Clear retry attempts + this.retryAttempts.clear() + } + + /** + * Update configuration + */ + updateConfig(config: Partial>): void { + this.config = { ...this.config, ...config } + + // Restart interval revalidation with new config if needed + // This would need to be called for all active keys + } + + /** + * Get revalidation statistics + */ + getStats(): { + intervalRevalidations: number + pendingRetries: number + retryAttempts: Map + } { + return { + intervalRevalidations: this.intervals.size, + pendingRetries: this.timeouts.size, + retryAttempts: new Map(this.retryAttempts) + } + } +} + +/** + * Setup global event listeners for focus and reconnect + */ +export function setupGlobalRevalidationHandlers( + revalidationManager: RevalidationManager, + keys: Key[] +): () => void { + const handleFocus = () => { + for (const key of keys) { + revalidationManager.revalidateOnFocus(key) + } + } + + const handleReconnect = () => { + for (const key of keys) { + revalidationManager.revalidateOnReconnect(key) + } + } + + const handleVisibilityChange = () => { + if (typeof globalThis !== 'undefined' && 'document' in globalThis && !(globalThis as any).document.hidden) { + handleFocus() + } + } + + // Add event listeners + if (typeof globalThis !== 'undefined' && 'window' in globalThis) { + const globalObj = globalThis as any + globalObj.addEventListener('focus', handleFocus) + globalObj.addEventListener('online', handleReconnect) + if ('document' in globalObj) { + globalObj.document.addEventListener('visibilitychange', handleVisibilityChange) + } + } + + // Return cleanup function + return () => { + if (typeof globalThis !== 'undefined' && 'window' in globalThis) { + const globalObj = globalThis as any + globalObj.removeEventListener('focus', handleFocus) + globalObj.removeEventListener('online', handleReconnect) + if ('document' in globalObj) { + globalObj.document.removeEventListener('visibilitychange', handleVisibilityChange) + } + } + } +} diff --git a/packages/core/src/swr.ts b/packages/core/src/swr.ts new file mode 100644 index 0000000..bed8041 --- /dev/null +++ b/packages/core/src/swr.ts @@ -0,0 +1,383 @@ +import type { + SWRCore, + Key, + CacheEntry, + Callback, + Unsubscribe, + SWRConfig, + MutatorValue, + MutatorOptions, + RevalidateOptions, + Fetcher +} from './types' +import { MemoryCache } from './cache' +import { KeyEventManager } from './events' +import { ConfigManager } from './options' +import { FetchManager } from './fetcher' +import { RevalidationManager } from './revalidate' +import { MutationManager } from './mutate' +import { resolveKey, serializeKey } from './key' + +/** + * Main SWR Core implementation + */ +export class SWRCoreImpl implements SWRCore { + private cache: MemoryCache + private eventManager: KeyEventManager + private configManager: ConfigManager + private fetchManager: FetchManager + private revalidationManager: RevalidationManager + private mutationManager: MutationManager + + // Track active keys for cleanup + private activeKeys = new Set() + + constructor(userConfig?: Partial>) { + // Initialize core components + this.configManager = new ConfigManager(userConfig) + this.cache = new MemoryCache() + this.eventManager = new KeyEventManager() + this.fetchManager = new FetchManager(this.configManager.getDedupingInterval()) + + // Initialize managers with dependencies + this.revalidationManager = new RevalidationManager( + this.fetchManager, + this.configManager.get(), + this.handleUpdate.bind(this) + ) + + this.mutationManager = new MutationManager( + this.fetchManager, + this.configManager.get(), + this.handleUpdate.bind(this), + (key) => this.get(key)?.data, + (key) => this.revalidationManager.getFetcher(key) + ) + + // Setup global revalidation handlers + this.setupGlobalHandlers() + } + + /** + * Get cache entry for a key + */ + get(key: Key): CacheEntry | undefined { + const resolvedKey = resolveKey(key) + if (!resolvedKey) return undefined + + return this.cache.get(resolvedKey) + } + + /** + * Subscribe to state changes for a key + */ + subscribe(key: Key, callback: Callback, fetcher?: Fetcher): Unsubscribe { + const resolvedKey = resolveKey(key) + if (!resolvedKey) { + return () => {} // No-op for invalid keys + } + + const serializedKey = serializeKey(resolvedKey) + + // Add to active keys + this.activeKeys.add(serializedKey) + + // Store fetcher for interval revalidation + if (fetcher) { + this.revalidationManager.setFetcher(resolvedKey, fetcher) + } + + // Check if we have an entry in cache + const entry = this.cache.get(resolvedKey) + + // Subscribe to events + const unsubscribe = this.eventManager.subscribe(resolvedKey, callback as Callback) + + const config = this.configManager.get() + + // Start initial fetch if no data exists + if (!entry) { + if (config.revalidateOnMount !== false) { + // Set initial loading state synchronously + this.handleUpdate(resolvedKey, { + isLoading: true, + isValidating: true + }) + } + + // Trigger initial revalidation asynchronously, but and check for deduplication + setTimeout(() => { + // Only revalidate if not already validating/loading (deduplication) + const currentEntry = this.get(resolvedKey) + if (currentEntry?.isLoading && !this.fetchManager.isFetching(resolvedKey)) { + this.revalidate(resolvedKey, { + fetcher, + dedupe: true + }) + } else if (!currentEntry) { + this.revalidate(resolvedKey, { + fetcher, + dedupe: true + }) + } + }, 0) + } else { + // If data exists, revalidate if revalidateOnMount is not false + if (config.revalidateOnMount !== false) { + // Use Promise.resolve().then() for consistency with SWR behavior, + // but skip if a fetch is already in flight to avoid duplicate requests + // (e.g. when a suspense-mode consumer has already kicked off a fetch). + Promise.resolve().then(() => { + if (!this.fetchManager.isFetching(resolvedKey)) { + this.revalidate(resolvedKey, { fetcher, dedupe: true }) + } + }) + } + } + + // Return cleanup function + return () => { + unsubscribe() + this.activeKeys.delete(serializedKey) + this.revalidationManager.clear(resolvedKey) + this.revalidationManager.removeFetcher(resolvedKey) + } + } + + /** + * Subscribe to state changes for a key WITHOUT triggering any fetch. + * Used by consumers (e.g. React hooks in suspense mode) that manage + * fetching themselves but still need notifications when data updates. + */ + addEventListener(key: Key, callback: Callback): Unsubscribe { + const resolvedKey = resolveKey(key) + if (!resolvedKey) { + return () => {} + } + return this.eventManager.subscribe(resolvedKey, callback as Callback) + } + + /** + * Set the loading+validating state for a key without fetching. + */ + setLoading(key: Key): void { + const resolvedKey = resolveKey(key) + if (!resolvedKey) return + this.handleUpdate(resolvedKey, { + isLoading: true, + isValidating: true + }) + } + + /** + * Mutate data for a key + */ + async mutate( + key: Key, + data?: MutatorValue, + options?: MutatorOptions + ): Promise { + const resolvedKey = resolveKey(key) + if (!resolvedKey) return undefined + + return this.mutationManager.mutate(resolvedKey, data, options) + } + + /** + * Revalidate data for a key + */ + async revalidate(key: Key, options?: RevalidateOptions): Promise { + const resolvedKey = resolveKey(key) + if (!resolvedKey) return false + + return this.revalidationManager.revalidate(resolvedKey, options as RevalidateOptions) + } + + /** + * Delete cache entry for a key + */ + delete(key: Key): void { + const resolvedKey = resolveKey(key) + if (!resolvedKey) return + + const serializedKey = serializeKey(resolvedKey) + + this.cache.delete(resolvedKey) + this.revalidationManager.clear(resolvedKey) + this.eventManager.clear(resolvedKey) + this.activeKeys.delete(serializedKey) + + // Emit delete event + this.eventManager.emit(resolvedKey, undefined, undefined, false, false) + } + + /** + * Clear all cache and state + */ + clear(): void { + this.cache.clear() + this.revalidationManager.clearAll() + this.eventManager.clear() + this.fetchManager.abortAll() + this.mutationManager.clearOptimisticData() + this.activeKeys.clear() + } + + /** + * Update configuration + */ + updateConfig(config: Partial>): void { + this.configManager.update(config) + + // Update managers with new config + this.revalidationManager.updateConfig(this.configManager.get()) + this.mutationManager.updateConfig(this.configManager.get()) + + // Restart interval revalidation for active keys + for (const serializedKey of this.activeKeys) { + try { + const key = JSON.parse(serializedKey) as Key + this.revalidationManager.startIntervalRevalidation(key) + } catch { + // Skip invalid keys + } + } + } + + /** + * Get current configuration + */ + getConfig(): SWRConfig { + return this.configManager.get() + } + + /** + * Get cache statistics + */ + getCacheStats(): { + size: number + keys: Key[] + activeKeys: number + } { + return { + size: this.cache.size(), + keys: this.cache.keys(), + activeKeys: this.activeKeys.size + } + } + + /** + * Get performance statistics + */ + getPerformanceStats(): { + activeRequests: number + pendingRequests: number + revalidations: unknown + } { + return { + activeRequests: this.fetchManager.getActiveCount(), + pendingRequests: this.fetchManager.getPendingCount(), + revalidations: this.revalidationManager.getStats() + } + } + + /** + * Handle cache updates and emit events + */ + private handleUpdate(key: Key, update: Partial>): void { + const currentEntry = this.cache.get(key) + const updatedEntry: CacheEntry = { + data: currentEntry?.data, + error: currentEntry?.error, + isValidating: currentEntry?.isValidating || false, + isLoading: currentEntry?.isLoading || false, + lastModified: currentEntry?.lastModified || Date.now(), + ...update + } + + // Update cache + this.cache.set(key, updatedEntry) + + // Start/stop interval revalidation based on data availability + if (updatedEntry.data !== undefined) { + this.revalidationManager.startIntervalRevalidation(key) + } else { + this.revalidationManager.stopIntervalRevalidation(key) + } + + // Emit state change + this.eventManager.emit( + key, + updatedEntry.data, + updatedEntry.error, + updatedEntry.isValidating, + updatedEntry.isLoading + ) + } + + /** + * Fetch initial data for a key + */ + private async fetchInitialData(key: Key, fetcher?: Fetcher): Promise { + const config = this.configManager.get() + const activeFetcher = fetcher || config.fetcher + + if (!activeFetcher) return + + try { + // Set loading state + this.handleUpdate(key, { isLoading: true, isValidating: true }) + + const data = await this.fetchManager.fetch(key, activeFetcher, { + dedupe: true, + timeout: config.loadingTimeout + }) + + // Update with data + this.handleUpdate(key, { + data, + error: undefined, + isLoading: false, + isValidating: false + }) + + config.onSuccess?.(data, key) + } catch (error) { + const errorObj = error as Error + + // Update with error + this.handleUpdate(key, { + error: errorObj, + isLoading: false, + isValidating: false + }) + + config.onError?.(errorObj, key) + } + } + + /** + * Setup global event handlers + */ + private setupGlobalHandlers(): void { + // This will be handled by the RevalidationManager + // which has its own global event setup + } + + /** + * Cleanup resources + */ + destroy(): void { + this.clear() + this.cache.destroy?.() + } +} + +/** + * Create a new SWR instance + */ +export function createSWR( + config?: Partial> +): SWRCore { + return new SWRCoreImpl(config) +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 0000000..17ba49c --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,112 @@ +/** + * Core types for the SWR implementation + */ + +export type Key = string | string[] | Record | null | undefined + +export interface CacheEntry { + data?: Data + error?: Error + isValidating: boolean + isLoading: boolean + lastModified: number + ttl?: number +} + +export interface CacheOptions { + ttl?: number + revalidateOnMount?: boolean + revalidateOnFocus?: boolean + revalidateOnReconnect?: boolean + refreshInterval?: number + dedupingInterval?: number + errorRetryCount?: number + errorRetryInterval?: number + loadingTimeout?: number +} + +export interface FetcherOptions { + signal: AbortSignal +} + +export type Fetcher = (key: Key, options: FetcherOptions) => Promise + +// Support for simpler fetcher signatures like SWR +type FetcherResponse = Data | Promise; +export type BareFetcher = (...args: any[]) => FetcherResponse; + +export type SWRFetcher = BareFetcher | Fetcher + +export interface MutatorOptions { + revalidate?: boolean + optimisticData?: Data + populateCache?: boolean + rollbackOnError?: boolean +} + +export type MutatorCallback = (currentData?: Data) => Data | Promise + +export type MutatorValue = Data | MutatorCallback | undefined + +export interface RevalidateOptions { + dedupe?: boolean + retryCount?: number + fetcher?: Fetcher +} + +export type Callback = ( + data?: Data, + error?: Error, + isValidating?: boolean, + isLoading?: boolean +) => void + +export type Unsubscribe = () => void + +export interface SWRConfig extends CacheOptions { + fetcher?: Fetcher + onSuccess?: (data: Data, key: Key) => void + onError?: (error: Error, key: Key) => void + onErrorRetry?: (error: Error, key: Key, config: SWRConfig, revalidate: () => void, options: { retryCount: number }) => void + onLoadingSlow?: (key: Key, config: SWRConfig) => void + isPaused?: () => boolean + compare?: (a: Data | undefined, b: Data | undefined) => boolean + fallback?: Record +} + +export interface Cache { + get(key: Key): CacheEntry | undefined + set(key: Key, value: CacheEntry): void + delete(key: Key): void + clear(): void + keys(): Key[] + has(key: Key): boolean +} + +export interface EventManager { + on(event: string, callback: Callback): Unsubscribe + emit(event: string, ...args: unknown[]): void + off(event: string, callback: Callback): void +} + +export interface SWRCore { + get(key: Key): CacheEntry | undefined + subscribe(key: Key, callback: Callback, fetcher?: Fetcher): Unsubscribe + /** + * Subscribe to state changes for a key WITHOUT triggering any fetch. + * Useful for consumers (like React hooks) that manage fetching themselves + * but still want to be notified when cached data updates. + */ + addEventListener(key: Key, callback: Callback): Unsubscribe + /** + * Set the loading+validating state for a key without fetching. + * Useful for consumers that want to surface loading state immediately + * before initiating their own fetch. + */ + setLoading(key: Key): void + mutate(key: Key, data?: MutatorValue, options?: MutatorOptions): Promise + revalidate(key: Key, options?: RevalidateOptions): Promise + delete(key: Key): void + clear(): void + updateConfig(config: Partial>): void +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..4f41276 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": [ + "DOM", + "ES2022" + ], + "target": "ES2018", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "preserve", + "allowJs": true, + "types": [ + "bun" + ], + // Bundler mode + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/react/__tests__/use-swr.test.ts b/packages/react/__tests__/use-swr.test.ts new file mode 100644 index 0000000..0261712 --- /dev/null +++ b/packages/react/__tests__/use-swr.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test"; +import { renderHook, act } from "@testing-library/react"; +import { useSWR } from "../src/index"; + +const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`)); + +describe("useSWR (React)", () => { + beforeEach(() => { + vi.useFakeTimers(); + fetcher.mockClear(); + }); + + + it("fetches data on mount", async () => { + const { result } = renderHook(() => useSWR("test-key", fetcher)); + + expect(result.current.isLoading).toBe(true); + + await act(async () => { + await vi.runAllTimers(); + }); + + expect(result.current.data).toBe("data-test-key"); + expect(result.current.isLoading).toBe(false); + }); + + it("deduplicates requests", async () => { + const { result: r1 } = renderHook(() => useSWR("dedupe-key", fetcher)); + const { result: r2 } = renderHook(() => useSWR("dedupe-key", fetcher)); + + await act(async () => { + await vi.runAllTimers(); + }); + + expect(fetcher).toHaveBeenCalledTimes(1); + expect(r1.current.data).toBe("data-dedupe-key"); + expect(r2.current.data).toBe("data-dedupe-key"); + }); + + it("mutates data manually", async () => { + const { result } = renderHook(() => useSWR("mutate-key", fetcher)); + + await act(async () => { + await vi.runAllTimers(); + }); + + expect(result.current.data).toBe("data-mutate-key"); + + await act(async () => { + await result.current.mutate("new-data"); + await vi.runAllTimers(); + }); + + expect(result.current.data).toBe("new-data"); + }); + + // it("handles errors", async () => { + // const error = new Error("failed"); + // fetcher.mockImplementationOnce(() => Promise.reject(error)); + + // const { result } = renderHook(() => useSWR("error-key", fetcher)); + + // await act(async () => { + // await vi.runAllTimers(); + // }); + + // expect(result.current.error).toBe(error); + // expect(result.current.isLoading).toBe(false); + // }); +}); \ No newline at end of file diff --git a/packages/react/bunfig.toml b/packages/react/bunfig.toml new file mode 100644 index 0000000..7d4db43 --- /dev/null +++ b/packages/react/bunfig.toml @@ -0,0 +1,2 @@ +[test] +preload = ["./happydom.ts", "./testing-library.ts"] \ No newline at end of file diff --git a/packages/react/happydom.ts b/packages/react/happydom.ts new file mode 100644 index 0000000..9cae201 --- /dev/null +++ b/packages/react/happydom.ts @@ -0,0 +1,3 @@ +import { GlobalRegistrator } from "@happy-dom/global-registrator"; + +GlobalRegistrator.register(); \ No newline at end of file diff --git a/packages/react/matchers.d.ts b/packages/react/matchers.d.ts new file mode 100644 index 0000000..c3a3ad9 --- /dev/null +++ b/packages/react/matchers.d.ts @@ -0,0 +1,7 @@ +import { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers"; +import { Matchers, AsymmetricMatchers } from "bun:test"; + +declare module "bun:test" { + interface Matchers extends TestingLibraryMatchers {} + interface AsymmetricMatchers extends TestingLibraryMatchers {} +} \ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..3b69932 --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,41 @@ +{ + "name": "@supwr/react", + "version": "0.0.1", + "type": "module", + "files": [ + "dist" + ], + "module": "./dist/index.js", + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "scripts": { + "build": "bunup" + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "@types/react": "^19.2.18", + "@happy-dom/global-registrator": "^20.12.0", + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "bunup": "^0.16.32" + }, + "dependencies": { + "@supwr/core": "workspace:*" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } +} \ No newline at end of file diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 0000000..0e8a72b --- /dev/null +++ b/packages/react/src/index.ts @@ -0,0 +1,6 @@ +// Main exports +// React SWR hooks +export { default as useSWR, SWRConfig, useSWRMutate, useSWRInstance, useSWRPrefetch } from './use-swr' +export { default as useSWRInfinite } from './use-swr-infinite' + +export type { SWRResponse, SWRHook } from './types' diff --git a/packages/react/src/middleware.ts b/packages/react/src/middleware.ts new file mode 100644 index 0000000..0b418f9 --- /dev/null +++ b/packages/react/src/middleware.ts @@ -0,0 +1,5 @@ +import { BareFetcher, Key, SWRConfig } from "@supwr/core"; +import { SWRHook, SWRResponse } from "./types"; + + +export type Middleware = (useSWRNext: SWRHook) => (key: Key, fetcher: BareFetcher | null, config: SWRConfig) => SWRResponse; \ No newline at end of file diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts new file mode 100644 index 0000000..c39ebf0 --- /dev/null +++ b/packages/react/src/types.ts @@ -0,0 +1,53 @@ +import type { SWRConfig as CoreSWRConfig, MutatorOptions} from '@supwr/core' +import { Middleware } from './middleware'; + +export interface SWRConfig extends CoreSWRConfig { + // React-specific config + suspense?: boolean + fallbackData?: Data + revalidateOnMount?: boolean + use?: Middleware[] + revalidateOnFocus?: boolean + revalidateOnReconnect?: boolean + refreshWhenHidden?: boolean + refreshWhenOffline?: boolean + compare?: (a: Data | undefined, b: Data | undefined) => boolean +} + +export interface SWRResponse { + data: Data | undefined + error: Error | undefined + isLoading: boolean + isValidating: boolean + mutate: (data?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: MutatorOptions) => Promise +} + +export interface SWRHook extends SWRResponse { + // Additional hook-specific properties can be added here +} + +// Infinite loading types +export interface SWRInfiniteKeyLoader { + (index: number, previousPageData: Data | null): Args | null | undefined +} + +export interface SWRInfiniteConfig extends Omit, 'compare'> { + initialSize?: number + revalidateAll?: boolean + persistSize?: boolean + revalidateFirstPage?: boolean + parallel?: boolean + compare?: (a: Data[] | undefined, b: Data[] | undefined) => boolean +} + +export interface SWRInfiniteResponse { + data: Data[] | undefined + error: Error | undefined + isLoading: boolean + isValidating: boolean + loading: boolean + validating: boolean + size: number + setSize: (size: number | ((size: number) => number)) => Promise + mutate: (data?: Data[] | Promise | ((currentData?: Data[]) => Data[] | Promise), options?: MutatorOptions) => Promise +} diff --git a/packages/react/src/use-swr-infinite.ts b/packages/react/src/use-swr-infinite.ts new file mode 100644 index 0000000..5fe2b05 --- /dev/null +++ b/packages/react/src/use-swr-infinite.ts @@ -0,0 +1,224 @@ +import { useCallback, useDebugValue, useMemo, useState, useSyncExternalStore } from 'react' +import { createSWR } from '@supwr/core' +import type { + SWRInfiniteKeyLoader, + SWRInfiniteConfig, + SWRInfiniteResponse, +} from './types' + +import { + Key, + Fetcher, + SWRFetcher, + BareFetcher +} from "@supwr/core" + + +// Global SWR instance for infinite loading +const globalSWR = createSWR() + +/** + * useSWRInfinite hook for infinite loading + */ +export function useSWRInfinite( + getKey: SWRInfiniteKeyLoader | null, + fetcher?: SWRFetcher | null, + config?: Partial> +): SWRInfiniteResponse { + const [size, setSize] = useState(config?.initialSize || 1) + + // Generate all keys for current size + const keys = useMemo(() => { + if (!getKey) return [] + + const keys: Key[] = [] + for (let i = 0; i < size; i++) { + const previousPageData = i === 0 ? null : (globalSWR.get(keys[i - 1])?.data as Data) || null + const keyArgs = getKey(i, previousPageData) + + if (keyArgs === null || keyArgs === undefined) { + break + } + + keys.push(keyArgs) + } + return keys + }, [getKey, size]) + + // Normalize fetcher + const normalizedFetcher = useMemo(() => { + if (!fetcher) return null + + if (fetcher.length === 2) return fetcher as Fetcher + + return (key: Key, options: any) => { + if (Array.isArray(key)) { + return (fetcher as BareFetcher)(...key) + } + return (fetcher as BareFetcher)(key) + } + }, [fetcher]) + + // Get data for all pages + const data = useMemo(() => { + if (keys.length === 0) return undefined + + const pages: Data[] = [] + for (const key of keys) { + const entry = globalSWR.get(key) + if (entry?.data !== undefined) { + pages.push(entry.data as Data) + } else { + // If any page doesn't have data, return undefined + return undefined + } + } + return pages + }, [keys]) + + // Check loading states + const isLoading = useMemo(() => { + if (keys.length === 0) return false + + // Check if first page is loading + const firstEntry = globalSWR.get(keys[0]) + return firstEntry?.isLoading || false + }, [keys]) + + const isValidating = useMemo(() => { + if (keys.length === 0) return false + + // Check if any page is validating + for (const key of keys) { + const entry = globalSWR.get(key) + if (entry?.isValidating) return true + } + return false + }, [keys]) + + // Get error from any page + const error = useMemo(() => { + if (keys.length === 0) return undefined + + for (const key of keys) { + const entry = globalSWR.get(key) + if (entry?.error) return entry.error + } + return undefined + }, [keys]) + + // Subscribe to all key changes + const subscribe = useCallback((callback: () => void) => { + if (keys.length === 0) return () => { } + + const unsubscribes = keys.map(key => + globalSWR.subscribe(key, callback) + ) + + return () => { + unsubscribes.forEach(unsubscribe => unsubscribe()) + } + }, [keys]) + + const getSnapshot = useCallback(() => ({ + data, + error, + isLoading, + isValidating + }), [data, error, isLoading, isValidating]) + + // Use sync external store + const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot) + + // Create mutate function for infinite data + const mutate = useCallback( + async (mutateData?: any, options?: any) => { + if (keys.length === 0) return undefined + + if (mutateData === undefined) { + // Revalidate all pages + const promises = keys.map(key => globalSWR.revalidate(key, options)) + return Promise.all(promises) as any + } + + // Mutate specific page or all pages + if (typeof mutateData === 'function') { + // Apply function to all pages + const currentData = data || [] + const newData = await Promise.all(currentData.map((page, index) => + (mutateData as any)(page, index) + )) + + // Update each page + const promises = keys.map((key, index) => + globalSWR.mutate(key, newData[index], options) + ) + const results = await Promise.all(promises) + return results[0] as Promise + } else if (Array.isArray(mutateData)) { + // Update specific pages + const promises = mutateData.map((pageData, index) => + keys[index] ? globalSWR.mutate(keys[index], pageData, options) : Promise.resolve(undefined) + ) + const results = await Promise.all(promises) + return results[0] as Promise + } else { + // Update first page + return globalSWR.mutate(keys[0], mutateData, options) + } + }, + [keys, data] + ) + + // Create setSize function + const setSizeFunction = useCallback( + (newSize: number | ((size: number) => number)) => { + const targetSize = typeof newSize === 'function' ? newSize(size) : newSize + + if (targetSize !== size) { + setSize(targetSize) + } + + // Return promise that resolves when new pages are loaded + return new Promise((resolve) => { + // This will resolve when the data is updated + setTimeout(() => { + if (!getKey) { + resolve([]) + return + } + + const pages: Data[] = [] + for (let i = 0; i < targetSize; i++) { + const previousPageData = i === 0 ? null : (globalSWR.get(keys[i - 1])?.data as Data) || null + const keyArgs = getKey(i, previousPageData) + + if (keyArgs === null || keyArgs === undefined) break + + const entry = globalSWR.get(keyArgs) + if (entry?.data) { + pages.push(entry.data as Data) + } + } + resolve([pages]) + }, 0) + }) + }, + [size, getKey, keys] + ) + + // Debug value + useDebugValue(data ? `${data.length} pages` : 'loading') + + return { + ...state, + loading: state.isLoading, + validating: state.isValidating, + error: state.error as Error, + size, + setSize: setSizeFunction, + mutate + } +} + +export default useSWRInfinite diff --git a/packages/react/src/use-swr.tsx b/packages/react/src/use-swr.tsx new file mode 100644 index 0000000..192d37f --- /dev/null +++ b/packages/react/src/use-swr.tsx @@ -0,0 +1,201 @@ +import React, { useCallback, useRef, useDebugValue, useMemo, useEffect, useContext, createContext, ReactNode, useSyncExternalStore } from 'react' +import { createSWR } from '@supwr/core' +import type { Key, Fetcher, SWRFetcher, BareFetcher, SWRCore } from '@supwr/core' +import { SWRResponse, SWRConfig as SWRConfiguration } from './types'; + +const globalSWR = createSWR() +const ConfigContext = createContext>>({}) + +export function useSWR( + key: Key | null, + fetcher?: SWRFetcher | null, + config?: Partial> +): SWRResponse { + + const contextConfig = useContext(ConfigContext) + const mergedConfig = useMemo(() => ({ + ...contextConfig, + ...config + }), [contextConfig, config]) + + const refreshInterval = mergedConfig?.refreshInterval + const swrRef = useRef(globalSWR) + + useEffect(() => { + if (mergedConfig) { + const { refreshInterval: _, ...configWithoutRefresh } = mergedConfig + swrRef.current.updateConfig(configWithoutRefresh as SWRConfig) + } + }, [mergedConfig]) + + useEffect(() => { + if (!key || !refreshInterval || refreshInterval <= 0) return + + const intervalId = setInterval(() => { + swrRef.current.revalidate(key, { + fetcher: fetcherRef.current as any, + dedupe: true + }) + }, refreshInterval) + + return () => clearInterval(intervalId) + }, [key, refreshInterval]) + + const normalizedFetcher = useMemo(() => { + if (!fetcher) return null + + if (fetcher.length === 2) return fetcher as Fetcher + + return (key: Key, options: any) => { + if (Array.isArray(key)) { + return (fetcher as BareFetcher)(...key) + } + return (fetcher as BareFetcher)(key) + } + }, [fetcher]) + + const fetcherRef = useRef(normalizedFetcher) + useEffect(() => { + fetcherRef.current = normalizedFetcher + }, [normalizedFetcher]) + + const subscribe = useCallback((callback: () => void) => { + if (!key) return () => { } + return swrRef.current.addEventListener(key, () => { + callback() + }) + }, [key]) + + const lastStateRef = useRef(null) + + const getSnapshot = useCallback(() => { + let nextState: any; + + if (!key || !fetcherRef.current) { + nextState = { + data: mergedConfig?.fallbackData, + error: undefined, + isValidating: false, + isLoading: false + } + } else { + const entry = swrRef.current.get(key) + const data = entry?.data ?? mergedConfig?.fallbackData + + nextState = { + data, + error: entry?.error, + isValidating: entry?.isValidating || false, + isLoading: entry?.isLoading || false + } + } + + if ( + lastStateRef.current && + lastStateRef.current.data === nextState.data && + lastStateRef.current.error === nextState.error && + lastStateRef.current.isValidating === nextState.isValidating && + lastStateRef.current.isLoading === nextState.isLoading + ) { + return lastStateRef.current + } + + lastStateRef.current = nextState + return nextState + }, [key, mergedConfig?.fallbackData]) + + const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot) + + const mutate = useCallback( + (data?: any, options?: any) => { + if (!key) return Promise.resolve(undefined) + return swrRef.current.mutate(key, data, options) + }, + [key] + ) + + const suspense = mergedConfig?.suspense + const hasData = state.data !== undefined + const hasError = state.error !== undefined + + useEffect(() => { + if (!key || !fetcherRef.current || suspense) return + + const entry = swrRef.current.get(key) + const revalidateOnMount = mergedConfig?.revalidateOnMount !== false + + if (!entry && revalidateOnMount) { + swrRef.current.setLoading(key) + swrRef.current.revalidate(key, { + fetcher: fetcherRef.current as any, + dedupe: true + }) + } else if (entry && revalidateOnMount && !entry.data) { + swrRef.current.revalidate(key, { + fetcher: fetcherRef.current as any, + dedupe: true + }) + } + }, [key, suspense, mergedConfig?.revalidateOnMount]) + + if (suspense) { + if (hasError) { + throw state.error + } + if (!hasData) { + const promise = swrRef.current.revalidate(key, { + fetcher: fetcherRef.current as any, + dedupe: true + }).then(() => true) + + ;(promise as any).catch(() => {}) + throw promise + } + } + + useDebugValue(state.data ? `data: ${JSON.stringify(state.data)}` : 'loading') + + return { + ...state, + loading: state.isLoading, + validating: state.isValidating, + mutate + } +} + +export function SWRConfig({ + children, + value +}: { + children: ReactNode + value: Partial> +}): React.ReactNode { + return ( + + {children} + + ) +} + +export type SWRConfig = SWRConfiguration + +export function useSWRMutate() { + return useCallback( + (key: Key, data?: any, options?: any) => { + return globalSWR.mutate(key, data, options) + }, + [] + ) +} + +export function useSWRInstance(): SWRCore { + return globalSWR +} + +export function useSWRPrefetch() { + return useCallback( + (key: Key, fetcher: SWRFetcher) => globalSWR.revalidate(key, { dedupe: true }), [] + ) +} + +export default useSWR diff --git a/packages/react/testing-library.ts b/packages/react/testing-library.ts new file mode 100644 index 0000000..f394b4f --- /dev/null +++ b/packages/react/testing-library.ts @@ -0,0 +1,10 @@ +import { afterEach, expect } from "bun:test"; +import { cleanup } from "@testing-library/react"; +import * as matchers from "@testing-library/jest-dom/matchers"; + +expect.extend(matchers); + +// Optional: cleans up `render` after each test +afterEach(() => { + cleanup(); +}); \ No newline at end of file diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 0000000..f832d0f --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": [ + "DOM", + "ES2022" + ], + "target": "ES2018", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "types": [ + "bun" + ], + // Bundler mode + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/solid/__tests__/use-swr.test.ts b/packages/solid/__tests__/use-swr.test.ts new file mode 100644 index 0000000..498da15 --- /dev/null +++ b/packages/solid/__tests__/use-swr.test.ts @@ -0,0 +1,68 @@ +// import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test"; +// import { createRoot } from "solid-js"; +// import { useSWR } from "../index"; + +// // Mock fetcher +// const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`)); + +// describe("useSWR (Solid)", () => { +// beforeEach(() => { +// vi.useFakeTimers(); +// fetcher.mockClear(); +// }); + +// afterEach(() => { +// vi.useRealTimers(); +// }); + +// it("fetches data on mount", async () => { +// createRoot((dispose) => { +// const result = useSWR(async () => "test-key", fetcher); + +// // Initial state +// expect(result.isLoading).toBe(true); + +// // Wait for fetch +// vi.runAllTimers(); + +// expect(result.data).toBe("data-test-key"); +// expect(result.isLoading).toBe(false); + +// dispose(); +// }); +// }); + +// it("deduplicates requests", async () => { +// createRoot((dispose) => { +// const r1 = useSWR(() => "dedupe-key", fetcher); +// const r2 = useSWR(() => "dedupe-key", fetcher); + +// vi.runAllTimers(); + +// expect(fetcher).toHaveBeenCalledTimes(1); +// expect(r1.data).toBe("data-dedupe-key"); +// expect(r2.data).toBe("data-dedupe-key"); + +// dispose(); +// }); +// }); + +// it("mutates data manually", async () => { +// await new Promise((resolve) => { +// createRoot(async (dispose) => { +// const result = useSWR(() => "mutate-key", fetcher); + +// vi.runAllTimers(); + +// expect(result.data).toBe("data-mutate-key"); + +// await result.mutate("new-data"); + +// expect(result.data).toBe("new-data"); + +// dispose(); +// resolve(); +// }); +// }); +// }); +// }); \ No newline at end of file diff --git a/packages/solid/index.ts b/packages/solid/index.ts new file mode 100644 index 0000000..daca0ec --- /dev/null +++ b/packages/solid/index.ts @@ -0,0 +1,5 @@ +// Solid SWR exports +export { default as useSWR, SWRConfig, useSWRMutate, useSWRInstance, useSWRPrefetch } from './use-swr' + +// Re-export core types +export type { Key, Fetcher } from '../../core' diff --git a/packages/solid/swr.ts b/packages/solid/swr.ts new file mode 100644 index 0000000..51d1631 --- /dev/null +++ b/packages/solid/swr.ts @@ -0,0 +1,181 @@ +import { + createSignal, + createEffect, + onCleanup, + batch +} from 'solid-js' +import { createSWR } from '../../core' +import type { SWRSignals, SWRResponse, Key, Fetcher, MutatorOptions } from './types' + +// Global SWR instance +const globalSWR = createSWR() + +/** + * Create SWR signals for Solid + */ +export function createSWRSignals( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +): SWRSignals { + // Update config if provided + if (config) { + globalSWR.updateConfig(config) + } + + // Initial state logic + let initialData: Data | undefined = undefined + let initialError: Error | undefined = undefined + let initialIsLoading = false + let initialIsValidating = false + + if (key) { + const entry = globalSWR.get(key) + if (entry) { + initialData = entry.data as Data + initialError = entry.error as Error + initialIsLoading = entry.isLoading + initialIsValidating = entry.isValidating + } else { + initialData = config?.fallbackData + } + } + + // Create signals + const [data, setData] = createSignal(initialData) + const [error, setError] = createSignal(initialError) + const [isLoading, setIsLoading] = createSignal(initialIsLoading) + const [isValidating, setIsValidating] = createSignal(initialIsValidating) + + // Subscribe to SWR changes + let unsubscribe: (() => void) | undefined + + if (key) { + unsubscribe = globalSWR.subscribe(key, (newData, newError, newIsValidating, newIsLoading) => { + batch(() => { + setData(newData) + setError(newError) + setIsValidating(newIsValidating ?? false) + setIsLoading(newIsLoading ?? false) + }) + }) + } + + // Cleanup on destroy + onCleanup(() => { + unsubscribe?.() + }) + + // Create mutate function + const mutate = (mutateData?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => { + if (!key) return Promise.resolve(undefined) + return globalSWR.mutate(key, mutateData, options) + } + + return { + data, + error, + isLoading, + isValidating, + loading: isLoading, + validating: isValidating, + mutate + } +} + +/** + * Create SWR response object for Solid + */ +export function createSWRResponse( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +): SWRResponse { + const signals = createSWRSignals(key, fetcher, config) + + return { + get data() { return signals.data() }, + get error() { return signals.error() }, + get isLoading() { return signals.isLoading() }, + get isValidating() { return signals.isValidating() }, + get loading() { return signals.isLoading() }, + get validating() { return signals.isValidating() }, + mutate: signals.mutate + } +} + +/** + * Hook-like function returning array [data, { mutate, error, isLoading, isValidating }] + */ +export function useSWR( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +): [Data | undefined, SWRResponse] { + const signals = createSWRSignals(key, fetcher, config) + + const response: SWRResponse = { + get data() { return signals.data() }, + get error() { return signals.error() }, + get isLoading() { return signals.isLoading() }, + get isValidating() { return signals.isValidating() }, + get loading() { return signals.isLoading() }, + get validating() { return signals.isValidating() }, + mutate: signals.mutate + } + + return [signals.data(), response] +} + +/** + * Individual signal creators + */ +export function swrDataSignal( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRSignals(key, fetcher, config as any).data +} + +export function swrErrorSignal( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRSignals(key, fetcher, config).error +} + +export function swrLoadingSignal( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRSignals(key, fetcher, config).isLoading +} + +export function swrValidatingSignal( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRSignals(key, fetcher, config).isValidating +} + +/** + * Hook for manual mutation + */ +export function useSWRMutate() { + return (key: Key, data?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => { + return globalSWR.mutate(key, data, options) + } +} + +/** + * Hook for accessing global SWR instance + */ +export function useSWRInstance() { + return globalSWR +} + +export default createSWRSignals diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json new file mode 100644 index 0000000..64a8bd3 --- /dev/null +++ b/packages/solid/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "types": ["solid-js", "bun"], + "skipLibCheck": true, + "noEmit": true + }, + "exclude": [ + "../react/**/*", + "../../node_modules/@types/react" + ], + "include": ["**/*", "../../core/**/*"] +} \ No newline at end of file diff --git a/packages/solid/types.ts b/packages/solid/types.ts new file mode 100644 index 0000000..230e480 --- /dev/null +++ b/packages/solid/types.ts @@ -0,0 +1,38 @@ +import type { SWRConfig as CoreSWRConfig, Key, Fetcher, MutatorOptions, BareFetcher } from '../../core' +import type { Accessor, Signal } from 'solid-js' + +export interface SWRConfig extends CoreSWRConfig { + // Solid-specific config + signal?: boolean + suspense?: boolean + fallbackData?: Data + revalidateOnMount?: boolean + revalidateOnFocus?: boolean + revalidateOnReconnect?: boolean + refreshWhenHidden?: boolean + refreshWhenOffline?: boolean + compare?: (a: Data | undefined, b: Data | undefined) => boolean +} + +export interface SWRSignals { + data: Accessor + error: Accessor + isLoading: Accessor + isValidating: Accessor + loading: Accessor + validating: Accessor + mutate: (data?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => Promise +} + +export interface SWRResponse { + data: Data | undefined + error: Error | undefined + isLoading: boolean + isValidating: boolean + loading: boolean + validating: boolean + mutate: (data?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => Promise +} + +// Re-export core types with SWR-compatible names +export type { Key, Fetcher, MutatorOptions, BareFetcher, SWRFetcher } from '../../core' diff --git a/packages/solid/use-swr.tsx b/packages/solid/use-swr.tsx new file mode 100644 index 0000000..2120c03 --- /dev/null +++ b/packages/solid/use-swr.tsx @@ -0,0 +1,190 @@ +/** @jsxRuntime automatic */ +/** @jsxImportSource solid-js */ + +import { createSignal, createEffect, onCleanup, Suspense, Show, createContext, useContext, createMemo } from 'solid-js' +import { createSWR } from '../../core' +import type { SWRConfig as SWRConfiguration, SWRResponse, Key, Fetcher, SWRFetcher } from './types' + +// Global SWR instance +const globalSWR = createSWR() + +// Solid Context for configuration +const ConfigContext = createContext>>({}) + +/** + * Enhanced useSWR hook for Solid + */ +export function useSWR( + key: Key | null, + fetcher?: SWRFetcher | null, + config?: Partial> +): SWRResponse { + // Get config from context and props + const contextConfig = useContext(ConfigContext) + const mergedConfig = createMemo(() => ({ + ...contextConfig, + ...config + })) + + // Create a stable reference to SWR instance with config + const swrRef = createMemo(() => globalSWR) + + // Update config if changed + createEffect(() => { + if (mergedConfig) { + swrRef().updateConfig(mergedConfig as any) + } + }) + + // Normalize fetcher to handle different signatures + const normalizedFetcher = createMemo(() => { + if (!fetcher) return null + + // If it's already a core fetcher, use it as-is + if (fetcher.length === 2) return fetcher + + // Convert bare fetcher to core fetcher + return (k: Key, options: any) => { + if (Array.isArray(k)) { + return (fetcher as any)(...k) + } + return (fetcher as any)(k) + } + }) + + // Create signals + const [data, setData] = createSignal( + key ? swrRef().get(key)?.data ?? mergedConfig()?.fallbackData : mergedConfig()?.fallbackData + ) + const [error, setError] = createSignal( + key ? swrRef().get(key)?.error as Error : undefined + ) + const [isLoading, setIsLoading] = createSignal( + key ? swrRef().get(key)?.isLoading ?? false : false + ) + const [isValidating, setIsValidating] = createSignal( + key ? swrRef().get(key)?.isValidating ?? false : false + ) + + // Subscribe to SWR changes + let unsubscribe: (() => void) | undefined + + createEffect(() => { + if (!key || !normalizedFetcher()) return + + unsubscribe = swrRef().subscribe(key, (newData: any, newError: any, newIsValidating?: boolean, newIsLoading?: boolean) => { + setData(newData) + setError(newError) + setIsValidating(newIsValidating ?? false) + setIsLoading(newIsLoading ?? false) + }, normalizedFetcher() as any) + + return unsubscribe + }) + + // Cleanup on destroy + onCleanup(() => { + unsubscribe?.() + }) + + // Create mutate function + const mutate = createMemo(() => + (mutateData?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: any): Promise => { + if (!key) return Promise.resolve(undefined) + return swrRef().mutate(key, mutateData, options) as Promise + } + ) + + // Handle suspense mode + if ((mergedConfig as any)?.suspense) { + if (error()) { + throw error() + } + if (data() === undefined) { + // In suspense mode, we need to trigger fetch and throw a promise + const promise = new Promise((resolve, reject) => { + const unsubscribe = swrRef().subscribe(key, (data: any, error: any) => { + if (error) { + reject(error) + } else if (data !== undefined) { + resolve(data) + } + }) + + // Start the fetch if not already loading + const entry = swrRef().get(key) + if (!entry?.isLoading && !entry?.isValidating) { + swrRef().revalidate(key) + } + + return unsubscribe + }) + + throw promise + } + } + + return { + get data() { return data() }, + get error() { return error() }, + get isLoading() { return isLoading() }, + get isValidating() { return isValidating() }, + get loading() { return isLoading() }, + get validating() { return isValidating() }, + mutate: mutate() + } +} + +/** + * Solid configuration provider component + */ +export function SWRConfig(props: { + children: any + value?: Partial> +}) { + return ( + + {props.children} + + ) +} + +/** + * Interface type for configuration + */ +export type SWRConfig = SWRConfiguration + +/** + * Hook for manual mutation + */ +export function useSWRMutate() { + return createMemo(() => + (key: Key, data?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: any) => { + return globalSWR.mutate(key, data, options) + } + ) +} + +/** + * Hook for accessing global SWR instance + */ +export function useSWRInstance() { + return globalSWR +} + +/** + * Hook for prefetching data + */ +export function useSWRPrefetch() { + return createMemo(() => + (key: Key, fetcher: Fetcher) => { + const normalizedFetcher = fetcher.length === 2 + ? fetcher + : (k: Key) => (fetcher as any)(Array.isArray(k) ? k : [k]) + + return globalSWR.revalidate(key, { dedupe: true }) + } + ) +} + +export default useSWR diff --git a/packages/svelte/__tests__/use-swr.test.ts b/packages/svelte/__tests__/use-swr.test.ts new file mode 100644 index 0000000..58d727b --- /dev/null +++ b/packages/svelte/__tests__/use-swr.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test"; +import { get } from "svelte/store"; +import { useSWR } from "../index"; + +// Mock fetcher +const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`)); + +describe("useSWR (Svelte)", () => { + beforeEach(() => { + vi.useFakeTimers(); + fetcher.mockClear(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("fetches data on mount", async () => { + const { data, isLoading } = useSWR("test-key", fetcher); + + // Initial state (synchronously set by my core change) + expect(get(isLoading)).toBe(true); + + // Wait for fetch + vi.runAllTimers(); + + expect(get(data)).toBe("data-test-key"); + expect(get(isLoading)).toBe(false); + }); + + it("deduplicates requests", async () => { + const { data: d1 } = useSWR("dedupe-key", fetcher); + const { data: d2 } = useSWR("dedupe-key", fetcher); + + vi.runAllTimers(); + + expect(fetcher).toHaveBeenCalledTimes(1); + expect(get(d1)).toBe("data-dedupe-key"); + expect(get(d2)).toBe("data-dedupe-key"); + }); + + it("mutates data manually", async () => { + const { data, mutate } = useSWR("mutate-key", fetcher); + + vi.runAllTimers(); + + expect(get(data)).toBe("data-mutate-key"); + + await mutate("new-data"); + + expect(get(data)).toBe("new-data"); + }); +}); diff --git a/packages/svelte/index.ts b/packages/svelte/index.ts new file mode 100644 index 0000000..9f8bfa8 --- /dev/null +++ b/packages/svelte/index.ts @@ -0,0 +1,21 @@ +// Svelte SWR exports +export { default as useSWR, useSWRPrefetch } from './use-swr' + +// Re-export core types +export type { Key, Fetcher } from '../../core' + +// Main exports +export { createSWRStores, createSWRStore } from './swr' +export { + swrDataStore, + swrErrorStore, + swrLoadingStore, + swrValidatingStore, + useSWRInstance +} from './swr' + +// Types +export type { SWRStores, SWRStore, SWRConfig } from './types' + +// Default export +export { createSWRStores as default } from './swr' diff --git a/packages/svelte/package.json b/packages/svelte/package.json new file mode 100644 index 0000000..5a3d9ff --- /dev/null +++ b/packages/svelte/package.json @@ -0,0 +1,15 @@ +{ + "name": "@supwr/svelte", + "version": "0.0.1", + "module": "src/index.ts", + "type": "module", + "scripts": { + "build": "bun build ./src/index.ts --outdir ./dist --external react --target browser" + }, + "dependencies": { + "@supwr/core": "workspace:*" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } +} \ No newline at end of file diff --git a/packages/svelte/swr.ts b/packages/svelte/swr.ts new file mode 100644 index 0000000..7cdcd57 --- /dev/null +++ b/packages/svelte/swr.ts @@ -0,0 +1,205 @@ +import { writable, derived } from 'svelte/store' +import { onDestroy } from 'svelte' +import { createSWR } from '../../core' +import type { SWRStores, SWRStore, Key, Fetcher, SWRFetcher } from './types' + +// Global SWR instance +const globalSWR = createSWR() + +/** + * Create SWR stores for Svelte + */ +export function createSWRStores( + key: Key | null, + fetcher?: SWRFetcher, + config?: Partial> +): SWRStores { + // Update config if provided + if (config) { + globalSWR.updateConfig(config as any) + } + + // Initial state logic + let initialData: Data | undefined = undefined + let initialError: Error | undefined = undefined + let initialIsLoading = false + let initialIsValidating = false + + if (key) { + const entry = globalSWR.get(key) + if (entry) { + initialData = entry.data as Data + initialError = entry.error as Error + initialIsLoading = entry.isLoading + initialIsValidating = entry.isValidating + } + } + + // Create base stores + const dataStore = writable(initialData) + const errorStore = writable(initialError) + const isLoadingStore = writable(initialIsLoading) + const isValidatingStore = writable(initialIsValidating) + + // Subscribe to SWR changes + let unsubscribe: (() => void) | undefined + + const normalizedFetcher = fetcher ? ( + fetcher.length === 2 ? fetcher : (k: Key, options: any) => { + if (Array.isArray(k)) { + return (fetcher as any)(...k) + } + return (fetcher as any)(k) + } + ) : null + + if (key) { + unsubscribe = globalSWR.subscribe(key, (newData, newError, newIsValidating, newIsLoading) => { + dataStore.set(newData as Data) + errorStore.set(newError as Error) + isValidatingStore.set(newIsValidating ?? false) + isLoadingStore.set(newIsLoading ?? false) + }, normalizedFetcher as any) + } + + // Cleanup on destroy + onDestroy(() => { + unsubscribe?.() + }) + + // Create mutate function + const mutate = (mutateData?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>):Promise => { + if (!key) return Promise.resolve(undefined) + return globalSWR.mutate(key, mutateData, options) as Promise + } + + return { + data: { subscribe: dataStore.subscribe }, + error: { subscribe: errorStore.subscribe }, + isLoading: { subscribe: isLoadingStore.subscribe }, + isValidating: { subscribe: isValidatingStore.subscribe }, + loading: { subscribe: isLoadingStore.subscribe }, + validating: { subscribe: isValidatingStore.subscribe }, + mutate + } +} + +/** + * Create a single SWR store containing all state + */ +export function createSWRStore( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +): SWRStore { + const stores = createSWRStores(key, fetcher, config) + + let initialData: Data | undefined; + let initialError: Error | undefined; + let initialIsLoading = false; + let initialIsValidating = false; + + stores.data.subscribe(v => initialData = v)(); + stores.error.subscribe(v => initialError = v)(); + stores.isLoading.subscribe(v => initialIsLoading = v)(); + stores.isValidating.subscribe(v => initialIsValidating = v)(); + + const combinedStore = writable<{ + data?: Data + error?: Error + isLoading: boolean + isValidating: boolean + loading: boolean + validating: boolean + }>({ + data: initialData, + error: initialError, + isLoading: initialIsLoading, + isValidating: initialIsValidating, + loading: initialIsLoading, + validating: initialIsValidating + }) + + // Update combined store when individual stores change + const unsubscribeData = stores.data.subscribe(data => { + combinedStore.update(state => ({ ...state, data })) + }) + + const unsubscribeError = stores.error.subscribe(error => { + combinedStore.update(state => ({ ...state, error })) + }) + + const unsubscribeLoading = stores.isLoading.subscribe(isLoading => { + combinedStore.update(state => ({ ...state, isLoading, loading: isLoading })) + }) + + const unsubscribeValidating = stores.isValidating.subscribe(isValidating => { + combinedStore.update(state => ({ ...state, isValidating, validating: isValidating })) + }) + + // Cleanup on destroy + onDestroy(() => { + unsubscribeData() + unsubscribeError() + unsubscribeLoading() + unsubscribeValidating() + }) + + return { + ...combinedStore, + mutate: stores.mutate + } +} + +/** + * Individual store creators + */ +export function swrDataStore( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRStores(key, fetcher, config as any).data +} + +export function swrErrorStore( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRStores(key, fetcher, config).error +} + +export function swrLoadingStore( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRStores(key, fetcher, config).isLoading +} + +export function swrValidatingStore( + key: Key | null, + fetcher?: Fetcher, + config?: Partial> +) { + return createSWRStores(key, fetcher, config).isValidating +} + +/** + * Hook for manual mutation + */ +export function useSWRMutate() { + return (key: Key, data?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => { + return globalSWR.mutate(key, data, options) + } +} + +/** + * Hook for accessing global SWR instance + */ +export function useSWRInstance() { + return globalSWR +} + +export default createSWRStores diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json new file mode 100644 index 0000000..7e1b887 --- /dev/null +++ b/packages/svelte/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["DOM", "ES2022"], + "target": "ES2018", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "preserve", + "allowJs": true, + "types": ["bun"], + + // Bundler mode + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "reference/swr", + "reference/swrev", + "reference/swrv", + "sswr", + "solid-swr" + ] +} diff --git a/packages/svelte/types.ts b/packages/svelte/types.ts new file mode 100644 index 0000000..a395ea6 --- /dev/null +++ b/packages/svelte/types.ts @@ -0,0 +1,39 @@ +import type { SWRConfig as CoreSWRConfig, Key, Fetcher, MutatorOptions, BareFetcher } from '../../core' +import type { Writable, Readable } from 'svelte/store' + +export interface SWRConfig extends CoreSWRConfig { + // Svelte-specific config + store?: boolean + suspense?: boolean + fallbackData?: Data + revalidateOnMount?: boolean + revalidateOnFocus?: boolean + revalidateOnReconnect?: boolean + refreshWhenHidden?: boolean + refreshWhenOffline?: boolean + compare?: (a: Data | undefined, b: Data | undefined) => boolean +} + +export interface SWRStores { + data: Readable + error: Readable + isLoading: Readable + isValidating: Readable + loading: Readable + validating: Readable + mutate: (data?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => Promise +} + +export interface SWRStore extends Writable<{ + data?: Data + error?: Error + isLoading: boolean + isValidating: boolean + loading: boolean + validating: boolean +}> { + mutate: (data?: Data | ((current: Data | undefined) => Data | Promise), options?: Partial>) => Promise +} + +// Re-export core types +export type { Key, Fetcher, MutatorOptions, BareFetcher, SWRFetcher } from '../../core' diff --git a/packages/svelte/use-swr.ts b/packages/svelte/use-swr.ts new file mode 100644 index 0000000..52bdf41 --- /dev/null +++ b/packages/svelte/use-swr.ts @@ -0,0 +1,195 @@ +import { onDestroy } from 'svelte' +import { writable, derived } from 'svelte/store' +import { createSWR } from '../../core' +import type { SWRConfig, SWRStores, SWRStore, Key, Fetcher, SWRFetcher } from './types' + +// Global SWR instance +const globalSWR = createSWR() + +/** + * Enhanced useSWR store for Svelte + */ +export function useSWR( + key: Key | null, + fetcher?: SWRFetcher | null, + config?: Partial> +): SWRStores { + // Get config from context (using a simple object for now) + const contextConfig = {} as Record + const mergedConfig = { + ...contextConfig, + ...config + } + + // Update config if provided + if (mergedConfig) { + globalSWR.updateConfig(mergedConfig as any) + } + + // Initial state logic + let initialData: Data | undefined = undefined + let initialError: Error | undefined = undefined + let initialIsLoading = false + let initialIsValidating = false + + if (key) { + const entry = globalSWR.get(key) + if (entry) { + initialData = entry.data as Data + initialError = entry.error as Error + initialIsLoading = entry.isLoading + initialIsValidating = entry.isValidating + } + } + + // Create base stores + const dataStore = writable(initialData) + const errorStore = writable(initialError) + const isLoadingStore = writable(initialIsLoading) + const isValidatingStore = writable(initialIsValidating) + + // Subscribe to SWR changes + let unsubscribe: (() => void) | undefined + + const normalizedFetcher = fetcher ? ( + fetcher.length === 2 ? fetcher as Fetcher : (k: Key, options: unknown) => { + if (Array.isArray(k)) { + return (fetcher as (...args: unknown[]) => Promise)(...k) + } + return (fetcher as (arg: unknown) => Promise)(k) + } + ) : null + + if (key) { + unsubscribe = globalSWR.subscribe(key, (newData, newError, newIsValidating, newIsLoading) => { + dataStore.set(newData as Data) + errorStore.set(newError as Error) + isValidatingStore.set(newIsValidating ?? false) + isLoadingStore.set(newIsLoading ?? false) + }, normalizedFetcher as Fetcher) + } + + // Cleanup on destroy if inside a component + try { + onDestroy(() => { + if (unsubscribe) unsubscribe() + }) + } catch (e) { + // Ignore error if called outside component initialization + // (e.g. in tests without a component context) + } + + // Create mutate function + const mutate = (mutateData?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: unknown) => { + if (!key) return Promise.resolve(undefined) + return globalSWR.mutate(key, mutateData, options as any) + } + + return { + data: { subscribe: dataStore.subscribe }, + error: { subscribe: errorStore.subscribe }, + isLoading: { subscribe: isLoadingStore.subscribe }, + isValidating: { subscribe: isValidatingStore.subscribe }, + loading: { subscribe: isLoadingStore.subscribe }, + validating: { subscribe: isValidatingStore.subscribe }, + mutate: mutate as any + } +} + +/** + * Enhanced useSWR store with all state combined + */ +export function useSWRStore( + key: Key | null, + fetcher?: SWRFetcher | null, + config?: Partial> +): SWRStore { + const stores = useSWR(key, fetcher, config) + + let initialData: Data | undefined; + let initialError: Error | undefined; + let initialIsLoading = false; + let initialIsValidating = false; + + stores.data.subscribe(v => initialData = v)(); + stores.error.subscribe(v => initialError = v)(); + stores.isLoading.subscribe(v => initialIsLoading = v)(); + stores.isValidating.subscribe(v => initialIsValidating = v)(); + + const combinedStore = writable<{ + data?: Data + error?: Error + isLoading: boolean + isValidating: boolean + loading: boolean + validating: boolean + }>({ + data: initialData, + error: initialError, + isLoading: initialIsLoading, + isValidating: initialIsValidating, + loading: initialIsLoading, + validating: initialIsValidating + }) + + // Update combined store when individual stores change + const unsubscribeData = stores.data.subscribe((data: any) => { + combinedStore.update((state: any) => ({ ...state, data })) + }) + + const unsubscribeError = stores.error.subscribe((error: any) => { + combinedStore.update((state: any) => ({ ...state, error })) + }) + + const unsubscribeLoading = stores.isLoading.subscribe((isLoading: any) => { + combinedStore.update((state: any) => ({ ...state, isLoading, loading: isLoading })) + }) + + const unsubscribeValidating = stores.isValidating.subscribe((isValidating) => { + combinedStore.update((state) => ({ ...state, isValidating, validating: isValidating })) + }) + + // Cleanup on destroy + try { + onDestroy(() => { + unsubscribeData() + unsubscribeError() + unsubscribeLoading() + unsubscribeValidating() + }) + } catch (e) { + // Ignore + } + + return { + ...combinedStore, + mutate: stores.mutate + } +} + +/** + * Hook for manual mutation + */ +export function useSWRMutate() { + return (key: Key, data?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: unknown) => { + return globalSWR.mutate(key, data, options as any) + } +} + +/** + * Hook for accessing global SWR instance + */ +export function useSWRInstance() { + return globalSWR +} + +/** + * Hook for prefetching data + */ +export function useSWRPrefetch() { + return (key: Key, fetcher: Fetcher) => { + return globalSWR.revalidate(key, { dedupe: true }) + } +} + +export default useSWR diff --git a/packages/vue/__tests__/use-swr.test.ts b/packages/vue/__tests__/use-swr.test.ts new file mode 100644 index 0000000..a148e0b --- /dev/null +++ b/packages/vue/__tests__/use-swr.test.ts @@ -0,0 +1,67 @@ +// import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test"; +// import { useSWR } from "../index"; +// import { effectScope } from "vue"; + +// function withSetup(composable: () => T): [T, () => void] { +// let result: T; +// const scope = effectScope(); +// scope.run(() => { +// result = composable(); +// }); +// return [result!, () => scope.stop()]; +// } + + +// // Mock fetcher +// const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`)); + +// describe("useSWR (Vue)", () => { +// beforeEach(() => { +// vi.useFakeTimers(); +// fetcher.mockClear(); +// }); + +// afterEach(() => { +// vi.useRealTimers(); +// }); + +// it("fetches data on mount", async () => { +// // const { data, isLoading } = useSWR("test-key", fetcher); +// const [{data, isLoading}, stop] = withSetup(() => useSWR("test-key", fetcher)); + +// // Initial state +// expect(isLoading.value).toBe(true); + +// // Wait for fetch +// vi.runAllTimers(); + +// expect(data.value).toBe("data-test-key"); +// expect(isLoading.value).toBe(false); +// stop(); +// }); + +// // it("deduplicates requests", async () => { +// // const [{ data: d1 }, stop1] = withSetup(() =>useSWR("dedupe-key", fetcher)); +// // const [{ data: d2 }, stop2] = withSetup(() =>useSWR("dedupe-key", fetcher)); + +// // vi.runAllTimers(); + +// // expect(fetcher).toHaveBeenCalledTimes(1); +// // expect(d1.value).toBe("data-dedupe-key"); +// // expect(d2.value).toBe("data-dedupe-key"); +// // stop1(); +// // stop2(); +// // }); + +// // it("mutates data manually", async () => { +// // const { data, mutate } = useSWR("mutate-key", fetcher); + +// // vi.runAllTimers(); + +// // expect(data.value).toBe("data-mutate-key"); + +// // await mutate("new-data"); + +// // expect(data.value).toBe("new-data"); +// // }); +// }); diff --git a/packages/vue/index.ts b/packages/vue/index.ts new file mode 100644 index 0000000..c5a7ca6 --- /dev/null +++ b/packages/vue/index.ts @@ -0,0 +1,5 @@ +// Vue SWR exports +export { default as useSWR, SWRConfig, useSWRMutate, useSWRInstance, useSWRPrefetch } from './use-swr' + +// Re-export core types +export type { Key, Fetcher } from '../../core' diff --git a/packages/vue/package.json b/packages/vue/package.json new file mode 100644 index 0000000..c49575e --- /dev/null +++ b/packages/vue/package.json @@ -0,0 +1,15 @@ +{ + "name": "@supwr/vue", + "version": "0.0.1", + "module": "src/index.ts", + "type": "module", + "scripts": { + "build": "bun build ./src/index.ts --outdir ./dist --external react --target browser" + }, + "dependencies": { + "@supwr/core": "workspace:*" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } +} \ No newline at end of file diff --git a/packages/vue/swr.ts b/packages/vue/swr.ts new file mode 100644 index 0000000..05b531c --- /dev/null +++ b/packages/vue/swr.ts @@ -0,0 +1,86 @@ +import { ref, computed, onUnmounted, watch } from 'vue' +import { createSWR } from '../../core' +import type { Key, Fetcher, SWRConfig as CoreSWRConfig } from '../../core' +import type { SWRRefs, SWRRef, SWRConfig } from './types' + +// Create a basic Vue SWR implementation +export function createSWRComposables( + key: Key, + fetcher: Fetcher, + config?: SWRConfig +): SWRRefs { + const swr = createSWR({ fetcher, ...config }) + + const data = ref() + const error = ref() + const loading = ref(false) + const validating = ref(false) + + // Subscribe to SWR changes + const unsubscribe = swr.subscribe(key, (newData, newError, newValidating, newLoading) => { + data.value = newData + error.value = newError + validating.value = newValidating + loading.value = newLoading + }) + + // Cleanup on unmount + onUnmounted(() => { + unsubscribe() + }) + + return { + data, + error, + loading, + validating, + mutate: (mutateData, options) => swr.mutate(key, mutateData as any, options), + revalidate: (options) => swr.revalidate(key, options) + } +} + +export function createSWRRef( + key: Key, + fetcher: Fetcher, + config?: SWRConfig +): SWRRef { + return createSWRComposables(key, fetcher, config) as SWRRef +} + +// Export individual refs for convenience +export const swrDataRef = (key: Key, fetcher: Fetcher, config?: SWRConfig) => + createSWRRef(key, fetcher, config).data + +export const swrErrorRef = (key: Key, fetcher: Fetcher, config?: SWRConfig) => + createSWRRef(key, fetcher, config).error + +export const swrLoadingRef = (key: Key, fetcher: Fetcher, config?: SWRConfig) => + createSWRRef(key, fetcher, config).loading + +export const swrValidatingRef = (key: Key, fetcher: Fetcher, config?: SWRConfig) => + createSWRRef(key, fetcher, config).validating + +// Hook-like function for Vue +export function useSWR( + key: Key, + fetcher: Fetcher, + config?: SWRConfig +): SWRRefs { + return createSWRComposables(key, fetcher, config) +} + +export function useSWRMutate( + key: Key, + fetcher: Fetcher, + config?: SWRConfig +): SWRRefs { + return createSWRComposables(key, fetcher, config) +} + +export function useSWRInstance( + key: Key, + fetcher: Fetcher, + config?: SWRConfig +): SWRRefs { + return createSWRComposables(key, fetcher, config) +} diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json new file mode 100644 index 0000000..c1fb1b6 --- /dev/null +++ b/packages/vue/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["DOM", "ES2022"], + "target": "ES2018", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "preserve", + "allowJs": true, + "types": ["bun"], + + // Bundler mode + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + ] +} diff --git a/packages/vue/types.ts b/packages/vue/types.ts new file mode 100644 index 0000000..66116b3 --- /dev/null +++ b/packages/vue/types.ts @@ -0,0 +1,30 @@ +import type { Ref, ComputedRef } from 'vue' +import type { Key, Fetcher, SWRConfig as CoreSWRConfig, MutatorOptions, BareFetcher } from '../../core' + +export interface SWRRefs { + data: Ref + error: Ref + isLoading: Ref + isValidating: Ref + mutate: (data?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: any) => Promise + revalidate: (options?: any) => Promise +} + +export interface SWRRef extends SWRRefs { + // Additional Vue-specific methods can be added here +} + +export interface SWRConfig extends CoreSWRConfig { + // Vue-specific configuration options + suspense?: boolean + fallbackData?: Data + revalidateOnMount?: boolean + revalidateOnFocus?: boolean + revalidateOnReconnect?: boolean + refreshWhenHidden?: boolean + refreshWhenOffline?: boolean + compare?: (a: Data | undefined, b: Data | undefined) => boolean +} + +// Re-export core types with SWR-compatible names +export type { Key, Fetcher, MutatorOptions, BareFetcher, SWRFetcher } from '../../core' diff --git a/packages/vue/use-swr.ts b/packages/vue/use-swr.ts new file mode 100644 index 0000000..e50c81e --- /dev/null +++ b/packages/vue/use-swr.ts @@ -0,0 +1,199 @@ +import { ref, computed, onUnmounted, Suspense, defineComponent, provide, inject, watch } from 'vue' +import { createSWR } from '../../core' +import type { SWRConfig as SWRConfiguration, SWRRefs, Key, Fetcher, SWRFetcher } from './types' + +// Global SWR instance +const globalSWR = createSWR() + +// Vue injection key for configuration +const SWR_CONFIG_KEY = Symbol('swr-config') + +/** + * Enhanced useSWR composable for Vue + */ +export function useSWR( + key: Key | null, + fetcher?: SWRFetcher | null, + config?: Partial> +): SWRRefs { + // Get config from injection and props + const injectedConfig = inject(SWR_CONFIG_KEY, {}) + const mergedConfig = computed(() => ({ + ...injectedConfig, + ...config + })) + + // Create a stable reference to SWR instance with config + const swrRef = ref(globalSWR) + + // Update config if changed + watch(mergedConfig, (newConfig) => { + if (newConfig) { + swrRef.value.updateConfig(newConfig as Partial> as any) + } + }, { deep: true }) + + // Normalize fetcher to handle different signatures + const normalizedFetcher = computed(() => { + const f = typeof fetcher === 'function' ? fetcher : null + if (!f) return null + + // If it's already a core fetcher, use it as-is + if (f.length === 2) return f as Fetcher + + // Convert bare fetcher to core fetcher + return (k: Key, options: unknown) => { + if (Array.isArray(k)) { + return (f as (...args: unknown[]) => Promise)(...k) + } + return (f as (arg: unknown) => Promise)(k) + } + }) + + // Create refs + const data = ref(undefined) + const error = ref(undefined) + const loading = ref(false) + const validating = ref(false) + + // Subscribe to SWR changes + let unsubscribe: (() => void) | undefined + + const updateRefs = (newData: Data | undefined, newError: Error | undefined, newValidating?: boolean, newLoading?: boolean) => { + data.value = newData + error.value = newError + validating.value = newValidating ?? false + loading.value = newLoading ?? false + } + + // Watch for key and fetcher changes + watch([() => key, normalizedFetcher], ([newKey, newFetcher]) => { + // Cleanup previous subscription + if (unsubscribe) { + unsubscribe() + } + + // Subscribe to new key if available + if (newKey && newFetcher) { + const entry = swrRef.value.get(newKey) + data.value = (entry?.data ?? mergedConfig.value?.fallbackData) as Data | undefined + error.value = entry?.error as Error | undefined + validating.value = entry?.isValidating ?? false + loading.value = entry?.isLoading ?? false + + unsubscribe = swrRef.value.subscribe(newKey, updateRefs as any, newFetcher as Fetcher) + } + }, { deep: true, immediate: true }) + + // Cleanup on unmount + onUnmounted(() => { + if (unsubscribe) { + unsubscribe() + } + }) + + // Create mutate function + const mutate = computed(() => + (mutateData?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: unknown) => { + if (!key) return Promise.resolve(undefined) + return swrRef.value.mutate(key, mutateData, options as any) + } + ) + + // Handle suspense mode + if (mergedConfig.value?.suspense) { + if (error.value) { + throw error.value + } + if (data.value === undefined) { + // In suspense mode, we need to trigger fetch and throw a promise + const promise = new Promise((resolve, reject) => { + const u = swrRef.value.subscribe(key, (d, e) => { + if (e) { + reject(e) + } else if (d !== undefined) { + resolve(d as Data) + } + }) + + // Start the fetch if not already loading + const entry = swrRef.value.get(key) + if (!entry?.isLoading && !entry?.isValidating) { + swrRef.value.revalidate(key) + } + + return u + }) + + throw promise + } + } + + return { + data: data as any, + error: error as any, + isLoading: loading, + isValidating: validating, + mutate: mutate.value as any, + revalidate: (options?: unknown) => swrRef.value.revalidate(key, options as any) + } +} + +/** + * Vue configuration provider component + */ +export const SWRConfig = defineComponent({ + name: 'SWRConfig', + props: { + value: { + type: Object, + default: () => ({}) + } + }, + setup(props, { slots }) { + // Provide configuration to child components + provide(SWR_CONFIG_KEY, props.value || {}) + + return () => slots.default?.() + } +}) + +/** + * Interface type for configuration + */ +export type SWRConfig = SWRConfiguration + +/** + * Hook for manual mutation + */ +export function useSWRMutate() { + return computed(() => + (key: Key, data?: Data | Promise | ((currentData?: Data) => Data | Promise), options?: unknown) => { + return globalSWR.mutate(key, data, options as any) + } + ).value +} + +/** + * Hook for accessing global SWR instance + */ +export function useSWRInstance() { + return ref(globalSWR) +} + +/** + * Hook for prefetching data + */ +export function useSWRPrefetch() { + return computed(() => + (key: Key, fetcher: Fetcher) => { + const normalizedFetcher = fetcher.length === 2 + ? fetcher + : (k: Key) => (fetcher as any)(Array.isArray(k) ? k : [k]) + + return globalSWR.revalidate(key, { dedupe: true }) + } + ).value +} + +export default useSWR