perspectapi-ts-sdk 6.5.9 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -1011
- package/dist/chunk-K3T2AFYA.mjs +1393 -0
- package/dist/index-CWvUyMt3.d.mts +2224 -0
- package/dist/index-CWvUyMt3.d.ts +2224 -0
- package/dist/index.d.mts +130 -2221
- package/dist/index.d.ts +130 -2221
- package/dist/index.js +8 -2
- package/dist/index.mjs +13 -1364
- package/dist/v2/index.d.mts +1 -0
- package/dist/v2/index.d.ts +1 -0
- package/dist/v2/index.js +1419 -0
- package/dist/v2/index.mjs +40 -0
- package/docs/README.md +15 -0
- package/docs/v1-deprecated/README.md +9 -0
- package/docs/v1-deprecated/examples/README.md +324 -0
- package/docs/v1-deprecated/examples/basic-usage.ts +258 -0
- package/docs/v1-deprecated/examples/cloudflare-worker.ts +274 -0
- package/docs/v1-deprecated/examples/content-query-with-slug-prefix.ts +237 -0
- package/docs/v1-deprecated/examples/image-transforms.ts +200 -0
- package/docs/v1-deprecated/examples/site-user-checkout.ts +186 -0
- package/docs/v1-deprecated/examples/slug-prefix-examples.ts +491 -0
- package/docs/v1-deprecated/legacy-docs/caching.md +667 -0
- package/docs/v1-deprecated/legacy-docs/contact.md +1396 -0
- package/docs/v1-deprecated/legacy-docs/csrf-protection.md +664 -0
- package/docs/v1-deprecated/legacy-docs/image-transforms.md +523 -0
- package/docs/v1-deprecated/legacy-docs/loaders.md +304 -0
- package/docs/v1-deprecated/legacy-docs/newsletter.md +811 -0
- package/docs/v1-deprecated/legacy-docs/site-users.md +817 -0
- package/docs/v1-deprecated/legacy-notes/CHANGELOG-CHECKOUT.md +143 -0
- package/docs/v1-deprecated/legacy-notes/CSRF-CHECKOUT.md +271 -0
- package/docs/v1-deprecated/legacy-notes/IMAGE_TRANSFORMS_PORT.md +298 -0
- package/docs/v1-deprecated/sdk-readme.md +1076 -0
- package/examples/README.md +19 -0
- package/examples/basic-v2.ts +37 -0
- package/llms.txt +25 -0
- package/package.json +18 -7
- package/src/client/api-keys-client.ts +4 -0
- package/src/client/auth-client.ts +4 -0
- package/src/client/base-client.ts +7 -0
- package/src/client/bundles-client.ts +4 -0
- package/src/client/categories-client.ts +4 -0
- package/src/client/checkout-client.ts +4 -0
- package/src/client/contact-client.ts +4 -0
- package/src/client/content-client.ts +4 -0
- package/src/client/newsletter-client.ts +4 -0
- package/src/client/newsletter-management-client.ts +4 -0
- package/src/client/organizations-client.ts +4 -0
- package/src/client/products-client.ts +4 -0
- package/src/client/site-users-client.ts +10 -1
- package/src/client/sites-client.ts +4 -0
- package/src/client/webhooks-client.ts +4 -0
- package/src/deprecation.ts +2 -1
- package/src/index.ts +2 -1
- package/src/loaders.ts +59 -0
- package/src/perspect-api-client.ts +2 -2
- package/src/v2/client/orders-client.ts +6 -1
- package/src/v2/types.ts +3 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Checkout Client Updates - Breaking Changes
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
Updated `CheckoutClient.createCheckoutSession()` to use the correct PerspectAPI route pattern with site name parameter.
|
|
5
|
+
|
|
6
|
+
## Breaking Changes
|
|
7
|
+
|
|
8
|
+
### Method Signature Change
|
|
9
|
+
|
|
10
|
+
**Before:**
|
|
11
|
+
```typescript
|
|
12
|
+
async createCheckoutSession(data: CreateCheckoutSessionRequest): Promise<ApiResponse<CheckoutSession>>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**After:**
|
|
16
|
+
```typescript
|
|
17
|
+
async createCheckoutSession(siteName: string, data: CreateCheckoutSessionRequest): Promise<ApiResponse<CheckoutSession>>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Route Change
|
|
21
|
+
|
|
22
|
+
**Before:**
|
|
23
|
+
```
|
|
24
|
+
POST /api/v1/checkout/create-session
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**After:**
|
|
28
|
+
```
|
|
29
|
+
POST /api/v1/{siteName}/checkout/create-session
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This matches the pattern used by other site-specific endpoints like:
|
|
33
|
+
- `/api/v1/{siteName}/products`
|
|
34
|
+
- `/api/v1/{siteName}/content`
|
|
35
|
+
|
|
36
|
+
## Updated Type Definition
|
|
37
|
+
|
|
38
|
+
The `CreateCheckoutSessionRequest` interface now supports both single-item and multi-item checkouts:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
export interface CreateCheckoutSessionRequest {
|
|
42
|
+
// Single item checkout (legacy/simple)
|
|
43
|
+
priceId?: string;
|
|
44
|
+
quantity?: number;
|
|
45
|
+
|
|
46
|
+
// Multiple items checkout (recommended)
|
|
47
|
+
line_items?: Array<{
|
|
48
|
+
price: string;
|
|
49
|
+
quantity: number;
|
|
50
|
+
}>;
|
|
51
|
+
|
|
52
|
+
// Common fields
|
|
53
|
+
success_url?: string;
|
|
54
|
+
cancel_url?: string;
|
|
55
|
+
successUrl?: string; // Alternative naming
|
|
56
|
+
cancelUrl?: string; // Alternative naming
|
|
57
|
+
customer_email?: string;
|
|
58
|
+
customerEmail?: string; // Alternative naming
|
|
59
|
+
metadata?: Record<string, string>;
|
|
60
|
+
mode?: 'payment' | 'subscription' | 'setup';
|
|
61
|
+
automatic_tax?: {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
};
|
|
64
|
+
shipping_address_collection?: {
|
|
65
|
+
allowed_countries: string[];
|
|
66
|
+
};
|
|
67
|
+
billing_address_collection?: 'auto' | 'required';
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Migration Guide
|
|
72
|
+
|
|
73
|
+
### Old Usage (❌ Deprecated)
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const client = createPerspectApiClient({
|
|
77
|
+
baseUrl: 'https://api.perspect.co',
|
|
78
|
+
apiKey: 'your-api-key'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// This will no longer work
|
|
82
|
+
const session = await client.checkout.createCheckoutSession({
|
|
83
|
+
priceId: 'price_xxxxx',
|
|
84
|
+
successUrl: 'https://example.com/success',
|
|
85
|
+
cancelUrl: 'https://example.com/cancel'
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### New Usage (✅ Required)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const client = createPerspectApiClient({
|
|
93
|
+
baseUrl: 'https://api.perspect.co',
|
|
94
|
+
apiKey: 'your-api-key'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Single item checkout
|
|
98
|
+
const session = await client.checkout.createCheckoutSession('museum-indian-art', {
|
|
99
|
+
line_items: [{
|
|
100
|
+
price: 'price_xxxxx',
|
|
101
|
+
quantity: 1
|
|
102
|
+
}],
|
|
103
|
+
success_url: 'https://example.com/success',
|
|
104
|
+
cancel_url: 'https://example.com/cancel'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Multiple items checkout
|
|
108
|
+
const session = await client.checkout.createCheckoutSession('museum-indian-art', {
|
|
109
|
+
line_items: [
|
|
110
|
+
{ price: 'price_xxxxx', quantity: 2 },
|
|
111
|
+
{ price: 'price_yyyyy', quantity: 1 }
|
|
112
|
+
],
|
|
113
|
+
success_url: 'https://example.com/success',
|
|
114
|
+
cancel_url: 'https://example.com/cancel',
|
|
115
|
+
customer_email: 'customer@example.com',
|
|
116
|
+
mode: 'payment',
|
|
117
|
+
automatic_tax: { enabled: true },
|
|
118
|
+
shipping_address_collection: {
|
|
119
|
+
allowed_countries: ['US', 'CA']
|
|
120
|
+
},
|
|
121
|
+
billing_address_collection: 'required'
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Benefits
|
|
126
|
+
|
|
127
|
+
1. **Consistency** - Matches the pattern used by all other site-specific endpoints
|
|
128
|
+
2. **Multi-tenancy** - Properly isolates checkout sessions by site
|
|
129
|
+
3. **Flexibility** - Supports both single and multiple line items
|
|
130
|
+
4. **Full Stripe Support** - Exposes all Stripe checkout session configuration options
|
|
131
|
+
|
|
132
|
+
## Compatibility
|
|
133
|
+
|
|
134
|
+
- **Version**: 1.1.0+
|
|
135
|
+
- **PerspectAPI Backend**: Requires backend version that supports `/api/v1/{siteName}/checkout/create-session` route
|
|
136
|
+
- **Node.js**: 16.x or higher
|
|
137
|
+
- **TypeScript**: 4.5 or higher
|
|
138
|
+
|
|
139
|
+
## Related Changes
|
|
140
|
+
|
|
141
|
+
- Updated `CreateCheckoutSessionRequest` interface to support multiple line items
|
|
142
|
+
- Added support for snake_case and camelCase field naming for flexibility
|
|
143
|
+
- Enhanced JSDoc documentation with parameter descriptions
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# CSRF-Protected Checkout Implementation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The PerspectAPI SDK now includes automatic CSRF token handling for secure checkout operations. This follows the double-submit CSRF token pattern required by PerspectAPI.
|
|
6
|
+
|
|
7
|
+
## SDK Updates
|
|
8
|
+
|
|
9
|
+
### 1. CheckoutClient Enhancements
|
|
10
|
+
|
|
11
|
+
**New Method: `getCsrfToken(siteName)`**
|
|
12
|
+
```typescript
|
|
13
|
+
// Get CSRF token for a specific site
|
|
14
|
+
const csrfResponse = await client.checkout.getCsrfToken('museum-indian-art');
|
|
15
|
+
// Returns: { csrf_token: "...", site_id: "...", site_name: "..." }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Enhanced: `createCheckoutSession(siteName, data, csrfToken?)`**
|
|
19
|
+
```typescript
|
|
20
|
+
// Automatic CSRF handling (recommended)
|
|
21
|
+
const session = await client.checkout.createCheckoutSession(
|
|
22
|
+
'museum-indian-art',
|
|
23
|
+
{
|
|
24
|
+
line_items: [
|
|
25
|
+
{
|
|
26
|
+
price: 'price_xxx', // Existing Stripe price ID
|
|
27
|
+
quantity: 1
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
success_url: 'https://yoursite.com/success',
|
|
31
|
+
cancel_url: 'https://yoursite.com/cancel'
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Manual CSRF token (advanced)
|
|
36
|
+
const csrfToken = await client.checkout.getCsrfToken('museum-indian-art');
|
|
37
|
+
const session = await client.checkout.createCheckoutSession(
|
|
38
|
+
'museum-indian-art',
|
|
39
|
+
data,
|
|
40
|
+
csrfToken.data.csrf_token
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Main Client CSRF Methods
|
|
45
|
+
|
|
46
|
+
**Site-Specific Token (Recommended for Development)**
|
|
47
|
+
```typescript
|
|
48
|
+
// Works from localhost, no Host header needed
|
|
49
|
+
const token = await client.getCsrfToken('museum-indian-art');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Host-Based Token (Production)**
|
|
53
|
+
```typescript
|
|
54
|
+
// Requires correct Host header
|
|
55
|
+
const token = await client.getCsrfToken();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Enhanced Type Definitions
|
|
59
|
+
|
|
60
|
+
**Line Items Support:**
|
|
61
|
+
```typescript
|
|
62
|
+
interface CreateCheckoutSessionRequest {
|
|
63
|
+
line_items?: Array<{
|
|
64
|
+
// Option 1: Use existing Stripe price ID
|
|
65
|
+
price?: string;
|
|
66
|
+
quantity: number;
|
|
67
|
+
|
|
68
|
+
// Option 2: Define price inline
|
|
69
|
+
price_data?: {
|
|
70
|
+
currency: string;
|
|
71
|
+
product_data: {
|
|
72
|
+
name: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
images?: string[];
|
|
75
|
+
};
|
|
76
|
+
unit_amount: number; // Amount in cents
|
|
77
|
+
};
|
|
78
|
+
}>;
|
|
79
|
+
|
|
80
|
+
// Required fields
|
|
81
|
+
success_url: string;
|
|
82
|
+
cancel_url: string;
|
|
83
|
+
|
|
84
|
+
// Optional fields
|
|
85
|
+
customer_email?: string;
|
|
86
|
+
metadata?: Record<string, string>;
|
|
87
|
+
mode?: 'payment' | 'subscription' | 'setup';
|
|
88
|
+
automatic_tax?: { enabled: boolean };
|
|
89
|
+
shipping_address_collection?: { allowed_countries: string[] };
|
|
90
|
+
billing_address_collection?: 'auto' | 'required';
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API Endpoints
|
|
95
|
+
|
|
96
|
+
### 1. Get CSRF Token
|
|
97
|
+
|
|
98
|
+
**Site-Specific (Development-Friendly)**
|
|
99
|
+
```bash
|
|
100
|
+
GET /api/v1/csrf/token/{siteName}
|
|
101
|
+
|
|
102
|
+
# Example
|
|
103
|
+
curl -X GET "https://api.perspect.co/api/v1/csrf/token/museum-indian-art" \
|
|
104
|
+
-H "X-API-Key: your_api_key"
|
|
105
|
+
|
|
106
|
+
# Response
|
|
107
|
+
{
|
|
108
|
+
"csrf_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
109
|
+
"site_id": "site_xxx",
|
|
110
|
+
"site_name": "museum-indian-art"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Host-Based (Production)**
|
|
115
|
+
```bash
|
|
116
|
+
GET /api/v1/csrf/token
|
|
117
|
+
|
|
118
|
+
curl -X GET "https://api.perspect.co/api/v1/csrf/token" \
|
|
119
|
+
-H "X-API-Key: your_api_key" \
|
|
120
|
+
-H "Host: indianart.perspect.com"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 2. Create Checkout Session
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
POST /api/v1/{siteName}/checkout/create-session
|
|
127
|
+
|
|
128
|
+
curl -X POST "https://api.perspect.co/api/v1/museum-indian-art/checkout/create-session" \
|
|
129
|
+
-H "Content-Type: application/json" \
|
|
130
|
+
-H "X-API-Key: your_api_key" \
|
|
131
|
+
-H "X-CSRF-Token: csrf_token_from_step_1" \
|
|
132
|
+
-d '{
|
|
133
|
+
"line_items": [
|
|
134
|
+
{
|
|
135
|
+
"price": "price_xxx",
|
|
136
|
+
"quantity": 1
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"success_url": "https://yoursite.com/success",
|
|
140
|
+
"cancel_url": "https://yoursite.com/cancel"
|
|
141
|
+
}'
|
|
142
|
+
|
|
143
|
+
# Response
|
|
144
|
+
{
|
|
145
|
+
"id": "cs_test_...",
|
|
146
|
+
"url": "https://checkout.stripe.com/pay/cs_test_..."
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## How It Works
|
|
151
|
+
|
|
152
|
+
### Automatic CSRF Flow (SDK)
|
|
153
|
+
|
|
154
|
+
1. **Client calls** `createCheckoutSession(siteName, data)`
|
|
155
|
+
2. **SDK checks** if CSRF token provided
|
|
156
|
+
3. **If not**, SDK calls `GET /api/v1/csrf/token/{siteName}`
|
|
157
|
+
4. **SDK extracts** `csrf_token` from response
|
|
158
|
+
5. **SDK makes** checkout request with `X-CSRF-Token` header
|
|
159
|
+
6. **API validates** CSRF token and creates session
|
|
160
|
+
7. **SDK returns** checkout session with ID and URL
|
|
161
|
+
|
|
162
|
+
### Manual CSRF Flow (Direct API)
|
|
163
|
+
|
|
164
|
+
1. **Client calls** `GET /api/v1/csrf/token/{siteName}`
|
|
165
|
+
2. **API returns** CSRF token
|
|
166
|
+
3. **Client calls** `POST /api/v1/{siteName}/checkout/create-session` with `X-CSRF-Token` header
|
|
167
|
+
4. **API validates** token and creates session
|
|
168
|
+
5. **API returns** checkout session
|
|
169
|
+
|
|
170
|
+
## Benefits
|
|
171
|
+
|
|
172
|
+
### Security
|
|
173
|
+
- ✅ Double-submit CSRF protection
|
|
174
|
+
- ✅ Site-specific token signing
|
|
175
|
+
- ✅ JWT-based tokens with expiration
|
|
176
|
+
- ✅ Prevents cross-site request forgery attacks
|
|
177
|
+
|
|
178
|
+
### Developer Experience
|
|
179
|
+
- ✅ Automatic token handling in SDK
|
|
180
|
+
- ✅ Works from localhost without configuration
|
|
181
|
+
- ✅ No Host header manipulation needed
|
|
182
|
+
- ✅ Explicit site specification
|
|
183
|
+
|
|
184
|
+
### Portability
|
|
185
|
+
- ✅ Works across all environments (Cloudflare Workers, Node.js, etc.)
|
|
186
|
+
- ✅ No vendor lock-in
|
|
187
|
+
- ✅ Standard HTTP headers
|
|
188
|
+
- ✅ Compatible with any HTTP client
|
|
189
|
+
|
|
190
|
+
## Migration Guide
|
|
191
|
+
|
|
192
|
+
### Before (Direct Fetch)
|
|
193
|
+
```typescript
|
|
194
|
+
const response = await fetch(`${baseUrl}/api/v1/${siteName}/checkout/create-session`, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: {
|
|
197
|
+
'Content-Type': 'application/json',
|
|
198
|
+
'X-API-Key': apiKey
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify(data)
|
|
201
|
+
});
|
|
202
|
+
// ❌ Missing CSRF token - will fail with 401
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### After (SDK with Auto CSRF)
|
|
206
|
+
```typescript
|
|
207
|
+
const response = await client.checkout.createCheckoutSession(siteName, data);
|
|
208
|
+
// ✅ CSRF token automatically handled
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Example: Remix Integration
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// app/utils/perspectapi-loaders.server.ts
|
|
215
|
+
export async function createCheckoutSession(params: {
|
|
216
|
+
products: Array<{ stripe_product_id: string; quantity: number }>;
|
|
217
|
+
successUrl: string;
|
|
218
|
+
cancelUrl: string;
|
|
219
|
+
customerEmail?: string;
|
|
220
|
+
}) {
|
|
221
|
+
const siteName = getSiteName();
|
|
222
|
+
|
|
223
|
+
// SDK handles CSRF automatically
|
|
224
|
+
const response = await perspectClient.checkout.createCheckoutSession(
|
|
225
|
+
siteName,
|
|
226
|
+
{
|
|
227
|
+
line_items: params.products.map(p => ({
|
|
228
|
+
price: p.stripe_product_id,
|
|
229
|
+
quantity: p.quantity
|
|
230
|
+
})),
|
|
231
|
+
success_url: params.successUrl,
|
|
232
|
+
cancel_url: params.cancelUrl,
|
|
233
|
+
customer_email: params.customerEmail,
|
|
234
|
+
mode: 'payment',
|
|
235
|
+
automatic_tax: { enabled: true },
|
|
236
|
+
shipping_address_collection: {
|
|
237
|
+
allowed_countries: ['US', 'CA']
|
|
238
|
+
},
|
|
239
|
+
billing_address_collection: 'required'
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
id: response.data?.id,
|
|
245
|
+
url: response.data?.url
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Troubleshooting
|
|
251
|
+
|
|
252
|
+
### "CSRF token required" (401)
|
|
253
|
+
- Ensure you're using the SDK's `createCheckoutSession()` method
|
|
254
|
+
- Or manually fetch CSRF token and include in `X-CSRF-Token` header
|
|
255
|
+
- Verify API key has proper permissions
|
|
256
|
+
|
|
257
|
+
### "Failed to obtain CSRF token"
|
|
258
|
+
- Check that site name is correct
|
|
259
|
+
- Verify API key is valid
|
|
260
|
+
- Ensure CSRF endpoint is accessible
|
|
261
|
+
|
|
262
|
+
### CSRF token format issues
|
|
263
|
+
- SDK handles multiple response formats: `data.token`, `token`, `csrf_token`
|
|
264
|
+
- Response may vary based on API version
|
|
265
|
+
- SDK automatically extracts the correct field
|
|
266
|
+
|
|
267
|
+
## Related Documentation
|
|
268
|
+
|
|
269
|
+
- [Checkout Integration Guide](../museumof-remix/CHECKOUT-INTEGRATION.md)
|
|
270
|
+
- [PerspectAPI CSRF Implementation](https://github.com/perspectapiworkers)
|
|
271
|
+
- [Stripe Checkout Documentation](https://stripe.com/docs/payments/checkout)
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Image Transformation Utilities - Port Complete
|
|
2
|
+
|
|
3
|
+
Successfully ported Cloudflare Image Resizing utilities from `perspectapiworkers` to `perspectapi-ts-sdk`.
|
|
4
|
+
|
|
5
|
+
## What Was Ported
|
|
6
|
+
|
|
7
|
+
### 1. Core Utilities (`src/utils/image-transform.ts`)
|
|
8
|
+
|
|
9
|
+
**Functions:**
|
|
10
|
+
- ✅ `buildImageUrl()` - Build single transformed URL with custom options
|
|
11
|
+
- ✅ `generateResponsiveUrls()` - Generate multiple sizes at once
|
|
12
|
+
- ✅ `generateSrcSet()` - Generate srcset for responsive images
|
|
13
|
+
- ✅ `generateSizesAttribute()` - Generate sizes attribute
|
|
14
|
+
- ✅ `generateResponsiveImageHtml()` - Complete HTML generation
|
|
15
|
+
- ✅ `transformMediaItem()` - **NEW** - Convenience function for MediaItem objects
|
|
16
|
+
|
|
17
|
+
**Types:**
|
|
18
|
+
- ✅ `ImageTransformOptions` - All transformation options
|
|
19
|
+
- ✅ `ResponsiveImageSizes` - Size presets interface
|
|
20
|
+
- ✅ `DEFAULT_IMAGE_SIZES` - Default responsive sizes
|
|
21
|
+
|
|
22
|
+
**Transform Options Supported:**
|
|
23
|
+
- `width`, `height` - Dimensions
|
|
24
|
+
- `fit` - Resize mode (scale-down, contain, cover, crop, pad)
|
|
25
|
+
- `gravity` - Crop focus (auto, left, right, top, bottom, center)
|
|
26
|
+
- `quality` - 1-100
|
|
27
|
+
- `format` - auto, avif, webp, jpeg, png
|
|
28
|
+
- `dpr` - Device pixel ratio (1, 2, 3)
|
|
29
|
+
- `sharpen`, `blur`, `rotate` - Effects
|
|
30
|
+
- `metadata`, `background`, `trim` - Advanced options
|
|
31
|
+
|
|
32
|
+
### 2. Documentation (`docs/image-transforms.md`)
|
|
33
|
+
|
|
34
|
+
**Comprehensive guide covering:**
|
|
35
|
+
- ✅ Quick start examples
|
|
36
|
+
- ✅ All function documentation with examples
|
|
37
|
+
- ✅ Common use cases (e-commerce, blog, avatars)
|
|
38
|
+
- ✅ Framework examples (Next.js, Remix, Cloudflare Workers)
|
|
39
|
+
- ✅ React component examples
|
|
40
|
+
- ✅ Best practices
|
|
41
|
+
- ✅ Performance benefits
|
|
42
|
+
- ✅ Troubleshooting
|
|
43
|
+
- ✅ Requirements and setup
|
|
44
|
+
|
|
45
|
+
### 3. Examples (`examples/image-transforms.ts`)
|
|
46
|
+
|
|
47
|
+
**Demonstrates:**
|
|
48
|
+
- ✅ Product image transformation
|
|
49
|
+
- ✅ Custom transformations
|
|
50
|
+
- ✅ Responsive images
|
|
51
|
+
- ✅ High DPR (Retina) support
|
|
52
|
+
- ✅ React component patterns
|
|
53
|
+
- ✅ Common use cases
|
|
54
|
+
|
|
55
|
+
### 4. SDK Integration
|
|
56
|
+
|
|
57
|
+
**Exports added to `src/index.ts`:**
|
|
58
|
+
```typescript
|
|
59
|
+
export {
|
|
60
|
+
buildImageUrl,
|
|
61
|
+
generateResponsiveUrls,
|
|
62
|
+
generateSrcSet,
|
|
63
|
+
generateSizesAttribute,
|
|
64
|
+
generateResponsiveImageHtml,
|
|
65
|
+
transformMediaItem,
|
|
66
|
+
DEFAULT_IMAGE_SIZES
|
|
67
|
+
} from './utils/image-transform';
|
|
68
|
+
|
|
69
|
+
export type {
|
|
70
|
+
ImageTransformOptions,
|
|
71
|
+
ResponsiveImageSizes
|
|
72
|
+
} from './utils/image-transform';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**README updated:**
|
|
76
|
+
- ✅ Added Image Transformations section
|
|
77
|
+
- ✅ Quick start example
|
|
78
|
+
- ✅ Feature list
|
|
79
|
+
- ✅ Link to full documentation
|
|
80
|
+
|
|
81
|
+
## Key Improvements Over Original
|
|
82
|
+
|
|
83
|
+
### 1. `transformMediaItem()` Function
|
|
84
|
+
|
|
85
|
+
**New convenience function** specifically for working with SDK's `MediaItem` type:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const product = await client.products.getProduct('mysite', 123);
|
|
89
|
+
const media = product.data.media?.[0];
|
|
90
|
+
|
|
91
|
+
if (media) {
|
|
92
|
+
const urls = transformMediaItem('https://api.perspect.co', media);
|
|
93
|
+
// Automatically handles both 'link' and 'r2_key' formats
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This makes it trivial for SDK users to transform images from API responses.
|
|
98
|
+
|
|
99
|
+
### 2. Better Type Safety
|
|
100
|
+
|
|
101
|
+
All functions have proper TypeScript types and JSDoc documentation with examples.
|
|
102
|
+
|
|
103
|
+
### 3. SDK-Specific Examples
|
|
104
|
+
|
|
105
|
+
Documentation includes examples using the SDK's client methods, not just raw URLs.
|
|
106
|
+
|
|
107
|
+
## Usage Examples
|
|
108
|
+
|
|
109
|
+
### Basic Usage
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { createPerspectApiClient, transformMediaItem } from 'perspectapi-ts-sdk';
|
|
113
|
+
|
|
114
|
+
const client = createPerspectApiClient({
|
|
115
|
+
baseUrl: 'https://api.perspect.co',
|
|
116
|
+
apiKey: 'your-api-key'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Get products
|
|
120
|
+
const products = await client.products.getProducts('mysite', { limit: 10 });
|
|
121
|
+
|
|
122
|
+
// Transform images
|
|
123
|
+
products.data.forEach(product => {
|
|
124
|
+
const media = product.media?.[0];
|
|
125
|
+
if (media && !Array.isArray(media)) {
|
|
126
|
+
const urls = transformMediaItem('https://api.perspect.co', media);
|
|
127
|
+
console.log('Thumbnail:', urls.thumbnail);
|
|
128
|
+
console.log('Large:', urls.large);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### React Component
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { transformMediaItem, type MediaItem } from 'perspectapi-ts-sdk';
|
|
137
|
+
|
|
138
|
+
interface ProductImageProps {
|
|
139
|
+
media: MediaItem;
|
|
140
|
+
alt: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function ProductImage({ media, alt }: ProductImageProps) {
|
|
144
|
+
const urls = transformMediaItem('https://api.perspect.co', media);
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<img
|
|
148
|
+
src={urls.medium}
|
|
149
|
+
srcSet={`
|
|
150
|
+
${urls.small} 400w,
|
|
151
|
+
${urls.medium} 800w,
|
|
152
|
+
${urls.large} 1200w
|
|
153
|
+
`}
|
|
154
|
+
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw"
|
|
155
|
+
alt={alt}
|
|
156
|
+
loading="lazy"
|
|
157
|
+
className="rounded-lg"
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Custom Transformations
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { buildImageUrl } from 'perspectapi-ts-sdk';
|
|
167
|
+
|
|
168
|
+
// E-commerce thumbnail
|
|
169
|
+
const thumbnail = buildImageUrl(
|
|
170
|
+
'https://api.perspect.co',
|
|
171
|
+
'media/mysite/product.jpg',
|
|
172
|
+
{
|
|
173
|
+
width: 300,
|
|
174
|
+
height: 300,
|
|
175
|
+
fit: 'cover',
|
|
176
|
+
format: 'auto',
|
|
177
|
+
quality: 85
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Blog featured image
|
|
182
|
+
const featured = buildImageUrl(
|
|
183
|
+
'https://api.perspect.co',
|
|
184
|
+
'media/mysite/blog.jpg',
|
|
185
|
+
{
|
|
186
|
+
width: 800,
|
|
187
|
+
fit: 'scale-down',
|
|
188
|
+
format: 'auto',
|
|
189
|
+
quality: 85
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Retina display
|
|
194
|
+
const retina = buildImageUrl(
|
|
195
|
+
'https://api.perspect.co',
|
|
196
|
+
'media/mysite/icon.jpg',
|
|
197
|
+
{
|
|
198
|
+
width: 400,
|
|
199
|
+
dpr: 2,
|
|
200
|
+
format: 'auto'
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Benefits for SDK Users
|
|
206
|
+
|
|
207
|
+
### 1. Zero Configuration
|
|
208
|
+
No setup needed - just import and use. Works with any MediaItem from the API.
|
|
209
|
+
|
|
210
|
+
### 2. Type Safe
|
|
211
|
+
Full TypeScript support with autocomplete and type checking.
|
|
212
|
+
|
|
213
|
+
### 3. Framework Agnostic
|
|
214
|
+
Works in Next.js, Remix, Cloudflare Workers, vanilla JS, etc.
|
|
215
|
+
|
|
216
|
+
### 4. Performance
|
|
217
|
+
- On-the-fly resizing (no pre-generated thumbnails)
|
|
218
|
+
- Automatic format optimization (WebP/AVIF)
|
|
219
|
+
- CDN caching at the edge
|
|
220
|
+
- Bandwidth savings
|
|
221
|
+
|
|
222
|
+
### 5. Easy Migration
|
|
223
|
+
If you're already using the API, just add image transformations with one function call.
|
|
224
|
+
|
|
225
|
+
## Requirements
|
|
226
|
+
|
|
227
|
+
- Cloudflare Image Resizing must be enabled on your zone
|
|
228
|
+
- Images must be served through Cloudflare
|
|
229
|
+
- Free tier: 100,000 images/month
|
|
230
|
+
- Paid: $5 per 100,000 images after free tier
|
|
231
|
+
|
|
232
|
+
## Testing
|
|
233
|
+
|
|
234
|
+
**Comprehensive test suite** (`tests/utils/image-transform.test.ts`):
|
|
235
|
+
|
|
236
|
+
✅ **100+ test cases** covering:
|
|
237
|
+
- `buildImageUrl()` - All transform options, edge cases
|
|
238
|
+
- `generateResponsiveUrls()` - Default and custom sizes
|
|
239
|
+
- `generateSrcSet()` - Default and custom widths
|
|
240
|
+
- `generateSizesAttribute()` - Default and custom breakpoints
|
|
241
|
+
- `generateResponsiveImageHtml()` - All options and attributes
|
|
242
|
+
- `transformMediaItem()` - Both link and r2_key formats
|
|
243
|
+
- `DEFAULT_IMAGE_SIZES` - All presets
|
|
244
|
+
- Edge cases - Empty paths, query params, zero values
|
|
245
|
+
|
|
246
|
+
**Run tests:**
|
|
247
|
+
```bash
|
|
248
|
+
npm test tests/utils/image-transform.test.ts
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Test coverage includes:**
|
|
252
|
+
- URL format validation
|
|
253
|
+
- Cloudflare Image Resizing URL structure
|
|
254
|
+
- All transformation options (width, height, fit, gravity, quality, format, dpr, etc.)
|
|
255
|
+
- Responsive image generation
|
|
256
|
+
- MediaItem object handling
|
|
257
|
+
- Edge cases and error conditions
|
|
258
|
+
|
|
259
|
+
## Files Added/Modified
|
|
260
|
+
|
|
261
|
+
**New Files:**
|
|
262
|
+
- `src/utils/image-transform.ts` - Core utilities (256 lines)
|
|
263
|
+
- `docs/image-transforms.md` - Complete documentation (600+ lines)
|
|
264
|
+
- `examples/image-transforms.ts` - Working examples (200+ lines)
|
|
265
|
+
- `tests/utils/image-transform.test.ts` - Comprehensive tests (400+ lines)
|
|
266
|
+
- `IMAGE_TRANSFORMS_PORT.md` - This summary
|
|
267
|
+
|
|
268
|
+
**Modified Files:**
|
|
269
|
+
- `src/index.ts` - Added exports
|
|
270
|
+
- `README.md` - Added Image Transformations section
|
|
271
|
+
- `examples/README.md` - Added image transforms example
|
|
272
|
+
|
|
273
|
+
## Testing
|
|
274
|
+
|
|
275
|
+
Run the example:
|
|
276
|
+
```bash
|
|
277
|
+
export API_URL=https://api.perspect.co
|
|
278
|
+
export API_KEY=your-api-key
|
|
279
|
+
export SITE_NAME=your-site
|
|
280
|
+
|
|
281
|
+
npx tsx examples/image-transforms.ts
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Next Steps
|
|
285
|
+
|
|
286
|
+
SDK users can now:
|
|
287
|
+
1. Import image transformation utilities
|
|
288
|
+
2. Transform MediaItem objects from API responses
|
|
289
|
+
3. Build custom transformation URLs
|
|
290
|
+
4. Generate responsive images with srcset
|
|
291
|
+
5. Use in any framework (React, Vue, Svelte, etc.)
|
|
292
|
+
|
|
293
|
+
## Documentation
|
|
294
|
+
|
|
295
|
+
- **Quick Start**: See README.md
|
|
296
|
+
- **Full Guide**: See docs/image-transforms.md
|
|
297
|
+
- **Examples**: See examples/image-transforms.ts
|
|
298
|
+
- **API Reference**: TypeScript types in src/utils/image-transform.ts
|