From 88e06d820adb9a3efe341c1927c4061b0b2eda37 Mon Sep 17 00:00:00 2001 From: Casey Collier Date: Sun, 20 Jul 2025 12:55:21 -0400 Subject: [PATCH] fixed package again for new npm org and updated readme links --- README.md | 331 ++++++++++++++++++++----------------------- package.json | 8 +- website/package.json | 30 ++++ website/src/App.tsx | 41 ++++++ website/src/theme.ts | 100 +++++++++++++ 5 files changed, 331 insertions(+), 179 deletions(-) create mode 100644 website/package.json create mode 100644 website/src/App.tsx create mode 100644 website/src/theme.ts diff --git a/README.md b/README.md index 5ee19f7..a48d375 100644 --- a/README.md +++ b/README.md @@ -1,247 +1,235 @@ # TypedFetch -> Fetch for humans who have shit to build +> Type-safe HTTP client that doesn't suck - Fetch for humans who have stuff to build -TypedFetch is a next-generation HTTP client that brings type safety, intelligent error handling, and developer-friendly features to API communication. It eliminates the boilerplate and pain points of traditional fetch/axios approaches while providing a tRPC-like experience that works with any REST API. +Zero dependencies. Full type safety. Just works. + +📦 **npm**: `@catalystlabs/typedfetch` +🌐 **Website**: [typedfetch.dev](https://typedfetch.dev) +📚 **Docs**: [typedfetch.dev/docs](https://typedfetch.dev/docs) +💻 **Source**: [git.catalystlab.cc/caseycollier/TypeFetched](https://git.catalystlab.cc/caseycollier/TypeFetched) ## 🚀 Quick Start ```bash -npm install typedfetch +# Using bun +bun add @catalystlabs/typedfetch + +# Using npm +npm install @catalystlabs/typedfetch ``` ```typescript -import { createTypedFetch } from 'typedfetch' +import { tf } from '@catalystlabs/typedfetch' -// Define your API structure -const client = createTypedFetch<{ - users: { - get: (id: string) => User - list: () => User[] - create: (data: CreateUser) => User +// Zero config - just works! +const { data, response } = await tf.get('https://api.github.com/users/github') +console.log(data.name) // Full response data + +// With configuration +import { createTypedFetch } from '@catalystlabs/typedfetch' + +const client = createTypedFetch({ + baseURL: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer token' } -}>({ - baseURL: 'https://api.example.com' }) -// Use with full type safety -const { data, error } = await client.users.get('123') -if (error) { - // TypeScript knows the error structure! - switch (error.type) { - case 'not_found': // Handle 404 - case 'network': // Handle network error - } -} -// TypeScript knows data is User | undefined +const { data, response } = await client.get('/users/123') ``` ## ✨ Features -### 🔒 Type Safety Without Code Generation -- Full TypeScript inference throughout request/response cycle -- No more `any` types or manual casting -- Compile-time URL validation -- Response type discrimination +### 🔒 Type Safety +- TypeScript inference for response data +- No manual type casting needed +- Type-safe error handling -### 🛡️ Unified Error Handling -- Categorized, actionable errors (network, HTTP, parsing, timeout, abort) -- Discriminated unions for type-safe error handling -- Automatic retry logic with exponential backoff -- Circuit breaker pattern for failing endpoints - -### 🚀 Zero Boilerplate -- Define once, use everywhere -- Intelligent defaults -- Proxy-based API with dot notation -- Auto-injected auth tokens - -### ⚡ Built-in Resilience -- Request retry with smart conditions -- Request deduplication -- Multi-tier caching (memory + IndexedDB) +### 🛡️ Built-in Resilience +- Automatic retries with exponential backoff +- Circuit breaker for failing endpoints +- Request caching (memory + IndexedDB) - HTTP cache header respect -- Offline support -### 🔧 Developer Experience -- Setup in <5 minutes -- IntelliSense for everything -- Clear, actionable error messages -- Zero compile step required +### 🚀 Simple API +- Clean, chainable API +- Standard HTTP methods: get(), post(), put(), delete() +- Consistent response format +- Zero boilerplate + +### ⚡ Performance +- <15KB gzipped bundle +- Zero runtime dependencies +- Efficient caching +- Request deduplication ## 📚 Documentation ### Basic Usage ```typescript -interface User { - id: string - name: string - email: string -} +import { tf } from '@catalystlabs/typedfetch' -interface CreateUserData { - name: string - email: string -} +// GET request +const { data, response } = await tf.get('https://api.example.com/users') -const client = createTypedFetch<{ - users: { - get: (id: string) => User - list: () => User[] - create: (data: CreateUserData) => User - update: (params: { id: string } & Partial) => User - delete: (id: string) => void +// POST request +const { data, response } = await tf.post('https://api.example.com/users', { + body: { + name: 'John Doe', + email: 'john@example.com' } -}>({ +}) + +// PUT request +const { data, response } = await tf.put('https://api.example.com/users/123', { + body: { + name: 'Jane Doe' + } +}) + +// DELETE request +const { data, response } = await tf.delete('https://api.example.com/users/123') +``` + +### Configuration + +```typescript +import { createTypedFetch } from '@catalystlabs/typedfetch' + +const client = createTypedFetch({ baseURL: 'https://api.example.com', - auth: () => getToken(), // Auto-injected + headers: { + 'Authorization': 'Bearer token', + 'Content-Type': 'application/json' + }, timeout: 30000, // Retry configuration retry: { attempts: 3, - delay: (attempt) => Math.min(1000 * Math.pow(2, attempt - 1), 10000), - condition: (error) => error.retryable + delay: 1000, + maxDelay: 10000, + backoff: 'exponential' }, // Cache configuration cache: { - storage: 'both', // memory + IndexedDB - ttl: { - 'users.list': 300000, // 5 minutes - 'users.get': 3600000, // 1 hour - } + enabled: true, + ttl: 300000, // 5 minutes + storage: 'memory' // or 'indexeddb' + } +}) + +// Or configure the global instance +import { tf } from '@catalystlabs/typedfetch' + +tf.configure({ + baseURL: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer token' } }) ``` -### Advanced Configuration +### Response Format + +All methods return a consistent response format: ```typescript -const client = createTypedFetch({ - baseURL: process.env.API_URL, - - // Global interceptors - interceptors: { - request: [(config) => { - config.headers.authorization = `Bearer ${getToken()}` - return config - }], - response: [(response) => { - logMetrics(response) - return response - }] - }, - - // Request deduplication - dedupe: { - window: 1000, // 1 second - key: (config) => `${config.method}:${config.url}` - } -}) +const { data, response } = await tf.get('/endpoint') + +// data: The parsed response body +// response: The full Response object from fetch API ``` ### Error Handling ```typescript -const { data, error, loading } = await client.users.get('123') - -if (error) { - switch (error.type) { - case 'http': - if (error.status === 404) { - console.log('User not found') - } else if (error.status >= 500) { - console.log('Server error - will retry automatically') - } - break - case 'network': - console.log('Network error:', error.message) - break - case 'timeout': - console.log('Request timed out') - break - case 'parse': - console.log('Invalid response format') - break - case 'validation': - console.log('Response validation failed') - break - case 'abort': - console.log('Request was cancelled') - break +try { + const { data, response } = await tf.get('/users/123') + // Handle success +} catch (error) { + if (error.response) { + // Server responded with error status + console.log(error.response.status) + console.log(error.data) + } else if (error.request) { + // Request was made but no response received + console.log('Network error:', error.message) + } else { + // Something else happened + console.log('Error:', error.message) } } ``` -### Transforms & Interceptors +### Advanced Features + +#### Circuit Breaker +Automatically stops making requests to failing endpoints: ```typescript -import { - createDateTransform, - createCamelCaseTransform, - createLoggingInterceptor -} from 'typedfetch' - -// Add request/response transforms -client.addRequestTransform(createSnakeCaseTransform()) -client.addResponseTransform('users', createDateTransform()) -client.addResponseTransform('users', createCamelCaseTransform()) - -// Add interceptors -const logging = createLoggingInterceptor({ - logRequests: true, - logResponses: true +const client = createTypedFetch({ + circuitBreaker: { + enabled: true, + failureThreshold: 5, + resetTimeout: 60000 // 1 minute + } }) -client.addRequestInterceptor(logging.request) -client.addResponseInterceptor(logging.response) ``` -## 🏗️ Architecture +#### Request Caching +Intelligent caching with multiple storage options: -TypedFetch is built with a layered architecture: +```typescript +const client = createTypedFetch({ + cache: { + enabled: true, + ttl: 300000, // 5 minutes + storage: 'indexeddb', // Persistent storage + respectCacheHeaders: true // Honor HTTP cache headers + } +}) +``` -- **Layer 0**: Core Types & Interfaces -- **Layer 1**: Protocol Abstraction (fetch/XHR/Node.js) -- **Layer 2**: Request Pipeline (interceptors, transforms) -- **Layer 3**: Resilience Core (retry, circuit breaker, deduplication) -- **Layer 4**: Cache Management (HTTP headers, LRU, IndexedDB) -- **Layer 5**: Developer API (Proxy-based interface) +#### Custom Headers per Request +```typescript +const { data } = await tf.get('/endpoint', { + headers: { + 'X-Custom-Header': 'value' + } +}) +``` ## 🎯 Why TypedFetch? ### vs Axios -- ✅ Type safety without manual interfaces -- ✅ Built-in retry, caching, deduplication -- ✅ Modern async/await patterns -- ✅ Smaller bundle size (<15KB) +- ✅ Built on modern fetch API +- ✅ Smaller bundle size +- ✅ Better TypeScript support +- ✅ Built-in resilience features -### vs tRPC -- ✅ Works with any backend (no server changes needed) -- ✅ REST API compatibility -- ✅ Gradual adoption -- ✅ Framework agnostic - -### vs React Query/SWR -- ✅ Framework agnostic -- ✅ Built-in HTTP client -- ✅ More control over requests -- ✅ Type-safe error handling +### vs Native Fetch +- ✅ Automatic JSON parsing +- ✅ Better error handling +- ✅ Built-in retries and caching +- ✅ Simpler API ## 📦 Bundle Size - Core: <15KB gzipped - Zero runtime dependencies -- Tree-shakeable modules +- Tree-shakeable - Works without build step ## 🌐 Browser Support - Modern browsers (ES2020+) -- Node.js 16+ -- Service Worker support -- IndexedDB for persistence +- Node.js 18+ +- Deno +- Bun ## 🤝 Contributing @@ -251,13 +239,6 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f MIT License - see [LICENSE](LICENSE) for details. -## 🔗 Links - -- [Documentation](https://typedfetch.dev) -- [Examples](./examples) -- [GitHub](https://github.com/typedfetch/typedfetch) -- [NPM](https://www.npmjs.com/package/typedfetch) - --- -**TypedFetch**: Because life's too short for `any` types and network errors. 🚀 \ No newline at end of file +**TypedFetch**: Because life's too short for complex HTTP clients. 🚀 \ No newline at end of file diff --git a/package.json b/package.json index 0fc77fa..c5c6c87 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@catalystlabs/typedfetch", "version": "0.1.0", - "description": "Type-safe HTTP client that doesn't suck - Fetch for humans who have shit to build", + "description": "Type-safe HTTP client that doesn't suck - Fetch for humans who have stuff to build", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -48,10 +48,10 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/typedfetch/typedfetch.git" + "url": "git+https://git.catalystlab.cc/caseycollier/TypeFetched.git" }, "bugs": { - "url": "https://github.com/typedfetch/typedfetch/issues" + "url": "https://git.catalystlab.cc/caseycollier/TypeFetched/issues" }, "homepage": "https://typedfetch.dev", "devDependencies": { @@ -80,4 +80,4 @@ "type": "github", "url": "https://github.com/sponsors/typedfetch" } -} \ No newline at end of file +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..a3ed679 --- /dev/null +++ b/website/package.json @@ -0,0 +1,30 @@ +{ + "name": "typedfetch-website", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@mantine/core": "^8.0.0", + "@mantine/hooks": "^8.0.0", + "@mantine/prism": "^7.13.4", + "@mantine/spotlight": "^8.0.0", + "@tabler/icons-react": "^3.26.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.1", + "framer-motion": "^11.15.0", + "@catalystlabs/typedfetch": "^0.1.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7", + "typescript": "^5.8.3" + } +} \ No newline at end of file diff --git a/website/src/App.tsx b/website/src/App.tsx new file mode 100644 index 0000000..150033f --- /dev/null +++ b/website/src/App.tsx @@ -0,0 +1,41 @@ +import { MantineProvider } from '@mantine/core'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { theme } from './theme'; +import { LandingPage } from './pages/LandingPage'; +import { DocsLayout } from './layouts/DocsLayout'; +import { GettingStarted } from './pages/docs/GettingStarted'; +import { Installation } from './pages/docs/Installation'; +import { BasicUsage } from './pages/docs/BasicUsage'; +import { Configuration } from './pages/docs/Configuration'; +import { ErrorHandling } from './pages/docs/ErrorHandling'; +import { TypeInference } from './pages/docs/TypeInference'; +import { Caching } from './pages/docs/Caching'; +import { Interceptors } from './pages/docs/Interceptors'; +import { APIReference } from './pages/docs/APIReference'; +import '@mantine/core/styles.css'; +import '@mantine/prism/styles.css'; +import '@mantine/spotlight/styles.css'; +import './styles/global.css'; + +export function App() { + return ( + + + + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + ); +} \ No newline at end of file diff --git a/website/src/theme.ts b/website/src/theme.ts new file mode 100644 index 0000000..0884518 --- /dev/null +++ b/website/src/theme.ts @@ -0,0 +1,100 @@ +import { createTheme, MantineColorsTuple } from '@mantine/core'; + +// Color Psychology: +// Primary (Blue-Purple gradient): Trust, innovation, creativity +// Accent (Coral): Energy, friendliness, approachability +// Success (Teal): Growth, clarity, balance +// Dark mode optimized for reduced eye strain + +const primary: MantineColorsTuple = [ + '#f3f0ff', + '#e5deff', + '#c8b9ff', + '#ab91ff', + '#9670ff', + '#8759ff', + '#7d4cff', + '#6b3de6', + '#5f35cc', + '#512ab3' +]; + +const accent: MantineColorsTuple = [ + '#fff0ec', + '#ffe0d6', + '#ffbfa8', + '#ff9b76', + '#ff7d4d', + '#ff6933', + '#ff5e26', + '#e44d1a', + '#ca4217', + '#b13812' +]; + +const teal: MantineColorsTuple = [ + '#e6fcf5', + '#c3fae8', + '#96f2d7', + '#63e6be', + '#38d9a9', + '#20c997', + '#12b886', + '#0ca678', + '#099268', + '#087f5b' +]; + +export const theme = createTheme({ + primaryColor: 'brand', + colors: { + brand: primary, + accent: accent, + teal: teal + }, + + // Modern, clean typography + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + fontFamilyMonospace: '"Fira Code", "JetBrains Mono", monospace', + + // Smooth shadows for depth + shadows: { + xs: '0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1)', + sm: '0 4px 6px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.06)', + md: '0 10px 15px rgba(0, 0, 0, 0.05), 0 4px 6px rgba(0, 0, 0, 0.05)', + lg: '0 20px 25px rgba(0, 0, 0, 0.05), 0 10px 10px rgba(0, 0, 0, 0.04)', + xl: '0 25px 50px rgba(0, 0, 0, 0.06)' + }, + + // Smooth radius for modern feel + radius: { + xs: '4px', + sm: '8px', + md: '12px', + lg: '16px', + xl: '24px' + }, + + // Enhanced focus styles for accessibility + focusRing: 'auto', + + // Component specific styles + components: { + Button: { + defaultProps: { + radius: 'md' + } + }, + Card: { + defaultProps: { + radius: 'lg', + shadow: 'sm' + } + }, + Code: { + defaultProps: { + radius: 'sm' + } + } + } +}); \ No newline at end of file