fixed package again for new npm org and updated readme links
This commit is contained in:
parent
3c7eee100a
commit
88e06d820a
5 changed files with 331 additions and 179 deletions
331
README.md
331
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>) => 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. 🚀
|
|
@ -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
30
website/package.json
Normal 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
41
website/src/App.tsx
Normal 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
100
website/src/theme.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
Loading…
Reference in a new issue