fixed package again for new npm org and updated readme links

This commit is contained in:
Casey Collier 2025-07-20 12:55:21 -04:00
parent 3c7eee100a
commit 88e06d820a
5 changed files with 331 additions and 179 deletions

331
README.md
View file

@ -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>) => 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<API>({
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. 🚀
**TypedFetch**: Because life's too short for complex HTTP clients. 🚀

View file

@ -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"
}
}
}

30
website/package.json Normal file
View file

@ -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"
}
}

41
website/src/App.tsx Normal file
View file

@ -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 (
<MantineProvider theme={theme} defaultColorScheme="dark">
<BrowserRouter>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/docs" element={<DocsLayout />}>
<Route index element={<GettingStarted />} />
<Route path="installation" element={<Installation />} />
<Route path="basic-usage" element={<BasicUsage />} />
<Route path="configuration" element={<Configuration />} />
<Route path="error-handling" element={<ErrorHandling />} />
<Route path="type-inference" element={<TypeInference />} />
<Route path="caching" element={<Caching />} />
<Route path="interceptors" element={<Interceptors />} />
<Route path="api-reference" element={<APIReference />} />
</Route>
</Routes>
</BrowserRouter>
</MantineProvider>
);
}

100
website/src/theme.ts Normal file
View file

@ -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'
}
}
}
});