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.
Files changed (57) hide show
  1. package/README.md +46 -1011
  2. package/dist/chunk-K3T2AFYA.mjs +1393 -0
  3. package/dist/index-CWvUyMt3.d.mts +2224 -0
  4. package/dist/index-CWvUyMt3.d.ts +2224 -0
  5. package/dist/index.d.mts +130 -2221
  6. package/dist/index.d.ts +130 -2221
  7. package/dist/index.js +8 -2
  8. package/dist/index.mjs +13 -1364
  9. package/dist/v2/index.d.mts +1 -0
  10. package/dist/v2/index.d.ts +1 -0
  11. package/dist/v2/index.js +1419 -0
  12. package/dist/v2/index.mjs +40 -0
  13. package/docs/README.md +15 -0
  14. package/docs/v1-deprecated/README.md +9 -0
  15. package/docs/v1-deprecated/examples/README.md +324 -0
  16. package/docs/v1-deprecated/examples/basic-usage.ts +258 -0
  17. package/docs/v1-deprecated/examples/cloudflare-worker.ts +274 -0
  18. package/docs/v1-deprecated/examples/content-query-with-slug-prefix.ts +237 -0
  19. package/docs/v1-deprecated/examples/image-transforms.ts +200 -0
  20. package/docs/v1-deprecated/examples/site-user-checkout.ts +186 -0
  21. package/docs/v1-deprecated/examples/slug-prefix-examples.ts +491 -0
  22. package/docs/v1-deprecated/legacy-docs/caching.md +667 -0
  23. package/docs/v1-deprecated/legacy-docs/contact.md +1396 -0
  24. package/docs/v1-deprecated/legacy-docs/csrf-protection.md +664 -0
  25. package/docs/v1-deprecated/legacy-docs/image-transforms.md +523 -0
  26. package/docs/v1-deprecated/legacy-docs/loaders.md +304 -0
  27. package/docs/v1-deprecated/legacy-docs/newsletter.md +811 -0
  28. package/docs/v1-deprecated/legacy-docs/site-users.md +817 -0
  29. package/docs/v1-deprecated/legacy-notes/CHANGELOG-CHECKOUT.md +143 -0
  30. package/docs/v1-deprecated/legacy-notes/CSRF-CHECKOUT.md +271 -0
  31. package/docs/v1-deprecated/legacy-notes/IMAGE_TRANSFORMS_PORT.md +298 -0
  32. package/docs/v1-deprecated/sdk-readme.md +1076 -0
  33. package/examples/README.md +19 -0
  34. package/examples/basic-v2.ts +37 -0
  35. package/llms.txt +25 -0
  36. package/package.json +18 -7
  37. package/src/client/api-keys-client.ts +4 -0
  38. package/src/client/auth-client.ts +4 -0
  39. package/src/client/base-client.ts +7 -0
  40. package/src/client/bundles-client.ts +4 -0
  41. package/src/client/categories-client.ts +4 -0
  42. package/src/client/checkout-client.ts +4 -0
  43. package/src/client/contact-client.ts +4 -0
  44. package/src/client/content-client.ts +4 -0
  45. package/src/client/newsletter-client.ts +4 -0
  46. package/src/client/newsletter-management-client.ts +4 -0
  47. package/src/client/organizations-client.ts +4 -0
  48. package/src/client/products-client.ts +4 -0
  49. package/src/client/site-users-client.ts +10 -1
  50. package/src/client/sites-client.ts +4 -0
  51. package/src/client/webhooks-client.ts +4 -0
  52. package/src/deprecation.ts +2 -1
  53. package/src/index.ts +2 -1
  54. package/src/loaders.ts +59 -0
  55. package/src/perspect-api-client.ts +2 -2
  56. package/src/v2/client/orders-client.ts +6 -1
  57. package/src/v2/types.ts +3 -0
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Example Cloudflare Worker using PerspectAPI TypeScript SDK
3
+ *
4
+ * @deprecated Historical v1 example. Do not copy into new code. v1 sunsets on
5
+ * 2026-06-01; use createPerspectApiV2Client from perspectapi-ts-sdk/v2.
6
+ */
7
+
8
+ import { createPerspectApiClient } from '../../../src';
9
+
10
+ // Cloudflare Worker environment interface
11
+ interface Env {
12
+ PERSPECT_API_URL: string;
13
+ PERSPECT_API_KEY: string;
14
+ PERSPECT_SITE_NAME?: string;
15
+ }
16
+
17
+ // Main worker handler
18
+ export default {
19
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
20
+ // Initialize PerspectAPI client
21
+ const client = createPerspectApiClient({
22
+ baseUrl: env.PERSPECT_API_URL,
23
+ apiKey: env.PERSPECT_API_KEY,
24
+ timeout: 10000, // 10 seconds for Workers
25
+ retries: 2 // Fewer retries for Workers
26
+ });
27
+
28
+ const url = new URL(request.url);
29
+ const path = url.pathname;
30
+
31
+ try {
32
+ // Route handling
33
+ switch (path) {
34
+ case '/':
35
+ return handleHome(client);
36
+
37
+ case '/health':
38
+ return handleHealth(client);
39
+
40
+ case '/content':
41
+ return handleContent(client, url, env.PERSPECT_SITE_NAME);
42
+
43
+ case '/products':
44
+ return handleProducts(client, url, env.PERSPECT_SITE_NAME);
45
+
46
+ case '/contact':
47
+ if (request.method === 'POST') {
48
+ return handleContactSubmission(client, request, env.PERSPECT_SITE_NAME);
49
+ }
50
+ return new Response('Method not allowed', { status: 405 });
51
+
52
+ case '/checkout':
53
+ if (request.method === 'POST') {
54
+ return handleCheckout(client, request);
55
+ }
56
+ return new Response('Method not allowed', { status: 405 });
57
+
58
+ default:
59
+ return new Response('Not found', { status: 404 });
60
+ }
61
+ } catch (error) {
62
+ console.error('Worker error:', error);
63
+ return new Response('Internal server error', { status: 500 });
64
+ }
65
+ }
66
+ };
67
+
68
+ // Handler functions
69
+ async function handleHome(client: any): Promise<Response> {
70
+ const html = `
71
+ <!DOCTYPE html>
72
+ <html>
73
+ <head>
74
+ <title>PerspectAPI SDK Demo</title>
75
+ <style>
76
+ body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
77
+ .endpoint { margin: 10px 0; padding: 10px; background: #f5f5f5; border-radius: 5px; }
78
+ code { background: #e0e0e0; padding: 2px 4px; border-radius: 3px; }
79
+ </style>
80
+ </head>
81
+ <body>
82
+ <h1>PerspectAPI TypeScript SDK Demo</h1>
83
+ <p>This Cloudflare Worker demonstrates the PerspectAPI TypeScript SDK.</p>
84
+
85
+ <h2>Available Endpoints:</h2>
86
+ <div class="endpoint">
87
+ <strong>GET /health</strong> - API health check
88
+ </div>
89
+ <div class="endpoint">
90
+ <strong>GET /content</strong> - Get published content<br>
91
+ <small>Query params: <code>?limit=10&page=1&type=post</code></small>
92
+ </div>
93
+ <div class="endpoint">
94
+ <strong>GET /products</strong> - Get active products<br>
95
+ <small>Query params: <code>?limit=10&page=1</code></small>
96
+ </div>
97
+ <div class="endpoint">
98
+ <strong>POST /contact</strong> - Submit contact form<br>
99
+ <small>Body: <code>{"siteName": "...", "name": "...", "email": "...", "message": "..."}</code></small>
100
+ </div>
101
+ <div class="endpoint">
102
+ <strong>POST /checkout</strong> - Create checkout session<br>
103
+ <small>Body: <code>{"priceId": "...", "successUrl": "...", "cancelUrl": "..."}</code></small>
104
+ </div>
105
+ </body>
106
+ </html>
107
+ `;
108
+
109
+ return new Response(html, {
110
+ headers: { 'Content-Type': 'text/html' }
111
+ });
112
+ }
113
+
114
+ async function handleHealth(client: any): Promise<Response> {
115
+ try {
116
+ const health = await client.health();
117
+ return Response.json({
118
+ success: true,
119
+ perspectApi: health.data,
120
+ worker: {
121
+ timestamp: new Date().toISOString(),
122
+ region: 'cloudflare-worker'
123
+ }
124
+ });
125
+ } catch (error) {
126
+ return Response.json({
127
+ success: false,
128
+ error: 'Failed to connect to PerspectAPI'
129
+ }, { status: 500 });
130
+ }
131
+ }
132
+
133
+ async function handleContent(client: any, url: URL, siteNameFromEnv?: string): Promise<Response> {
134
+ try {
135
+ const limit = parseInt(url.searchParams.get('limit') || '10');
136
+ const page = parseInt(url.searchParams.get('page') || '1');
137
+ const type = url.searchParams.get('type') || undefined;
138
+ const siteName = siteNameFromEnv || url.searchParams.get('siteName');
139
+
140
+ if (!siteName) {
141
+ return Response.json({
142
+ success: false,
143
+ error: 'Site name is required to fetch content'
144
+ }, { status: 400 });
145
+ }
146
+
147
+ const content = await client.content.getContent(siteName, {
148
+ limit: Math.min(limit, 50), // Cap at 50
149
+ page: Math.max(page, 1),
150
+ page_status: 'publish',
151
+ page_type: type as any
152
+ });
153
+
154
+ return Response.json({
155
+ success: true,
156
+ data: content.data,
157
+ pagination: content.pagination
158
+ });
159
+ } catch (error) {
160
+ return Response.json({
161
+ success: false,
162
+ error: 'Failed to fetch content'
163
+ }, { status: 500 });
164
+ }
165
+ }
166
+
167
+ async function handleProducts(client: any, url: URL, siteNameFromEnv?: string): Promise<Response> {
168
+ try {
169
+ const limit = parseInt(url.searchParams.get('limit') || '10');
170
+ const page = parseInt(url.searchParams.get('page') || '1');
171
+ const siteName = siteNameFromEnv || url.searchParams.get('siteName');
172
+
173
+ if (!siteName) {
174
+ return Response.json({
175
+ success: false,
176
+ error: 'Site name is required to fetch products'
177
+ }, { status: 400 });
178
+ }
179
+
180
+ const products = await client.products.getProducts(siteName, {
181
+ limit: Math.min(limit, 50), // Cap at 50
182
+ page: Math.max(page, 1),
183
+ isActive: true
184
+ });
185
+
186
+ return Response.json({
187
+ success: true,
188
+ data: products.data,
189
+ pagination: products.pagination
190
+ });
191
+ } catch (error) {
192
+ return Response.json({
193
+ success: false,
194
+ error: 'Failed to fetch products'
195
+ }, { status: 500 });
196
+ }
197
+ }
198
+
199
+ async function handleContactSubmission(client: any, request: Request, siteNameFromEnv?: string): Promise<Response> {
200
+ try {
201
+ const body = await request.json() as any;
202
+
203
+ // Basic validation
204
+ if (!body.name || !body.email || !body.message) {
205
+ return Response.json({
206
+ success: false,
207
+ error: 'Name, email, and message are required'
208
+ }, { status: 400 });
209
+ }
210
+
211
+ const siteName = siteNameFromEnv || body.siteName;
212
+ if (!siteName) {
213
+ return Response.json({
214
+ success: false,
215
+ error: 'Site name is required for contact submissions'
216
+ }, { status: 400 });
217
+ }
218
+
219
+ const submission = await client.contact.submitContact(siteName, {
220
+ name: body.name,
221
+ email: body.email,
222
+ subject: body.subject || 'Contact from Cloudflare Worker',
223
+ message: body.message
224
+ });
225
+
226
+ return Response.json({
227
+ success: true,
228
+ data: {
229
+ id: submission.data.id,
230
+ message: 'Contact form submitted successfully'
231
+ }
232
+ });
233
+ } catch (error) {
234
+ return Response.json({
235
+ success: false,
236
+ error: 'Failed to submit contact form'
237
+ }, { status: 500 });
238
+ }
239
+ }
240
+
241
+ async function handleCheckout(client: any, request: Request): Promise<Response> {
242
+ try {
243
+ const body = await request.json() as any;
244
+
245
+ // Basic validation
246
+ if (!body.priceId || !body.successUrl || !body.cancelUrl) {
247
+ return Response.json({
248
+ success: false,
249
+ error: 'priceId, successUrl, and cancelUrl are required'
250
+ }, { status: 400 });
251
+ }
252
+
253
+ const session = await client.checkout.createCheckoutSession({
254
+ priceId: body.priceId,
255
+ successUrl: body.successUrl,
256
+ cancelUrl: body.cancelUrl,
257
+ customerEmail: body.customerEmail,
258
+ metadata: body.metadata
259
+ });
260
+
261
+ return Response.json({
262
+ success: true,
263
+ data: {
264
+ sessionId: session.data.id,
265
+ url: session.data.url
266
+ }
267
+ });
268
+ } catch (error) {
269
+ return Response.json({
270
+ success: false,
271
+ error: 'Failed to create checkout session'
272
+ }, { status: 500 });
273
+ }
274
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Example: Querying content with slug_prefix
3
+ *
4
+ * @deprecated Historical v1 example. Do not copy into new code. v1 sunsets on
5
+ * 2026-06-01; use createPerspectApiV2Client from perspectapi-ts-sdk/v2.
6
+ *
7
+ * This example shows how to use the PerspectAPI SDK to fetch content
8
+ * that will result in a call to:
9
+ * GET https://api.perspect.comm/api/v1/sites/clinicaldrugtrials?slug_prefix=blog&page_type=post&page_status=publish&limit=25
10
+ */
11
+
12
+ import { PerspectAPIClient } from '../../../src';
13
+ import type { ContentQueryParams } from '../../../src/types';
14
+
15
+ // Initialize the client
16
+ const client = new PerspectAPIClient({
17
+ apiKey: 'your-api-key-here', // Replace with your actual API key
18
+ // or use baseUrl if you have a different API endpoint
19
+ // baseUrl: 'https://api.perspect.comm'
20
+ });
21
+
22
+ /**
23
+ * Example 1: Basic content query with slug_prefix
24
+ * This will make the exact call you mentioned
25
+ */
26
+ async function fetchBlogPosts() {
27
+ console.log('=== Example 1: Fetching blog posts ===\n');
28
+
29
+ try {
30
+ // Define query parameters
31
+ const params: ContentQueryParams = {
32
+ slug_prefix: 'blog', // Filter by slug prefix
33
+ page_type: 'post', // Content type: 'post' or 'page'
34
+ page_status: 'publish', // Status: 'draft', 'publish', 'private', 'trash'
35
+ limit: 25 // Number of results to return
36
+ };
37
+
38
+ // Make the API call
39
+ // This translates to: GET https://api.perspect.comm/api/v1/sites/clinicaldrugtrials?slug_prefix=blog&page_type=post&page_status=publish&limit=25
40
+ const response = await client.content.getContent('clinicaldrugtrials', params);
41
+
42
+ // Check if the request was successful
43
+ if (response.success) {
44
+ console.log(`✅ Successfully fetched ${response.data?.length || 0} posts`);
45
+
46
+ // Log pagination info if available
47
+ if (response.pagination) {
48
+ console.log('\nPagination info:');
49
+ console.log(` Page: ${response.pagination.page}`);
50
+ console.log(` Limit: ${response.pagination.limit}`);
51
+ console.log(` Total items: ${response.pagination.total}`);
52
+ console.log(` Total pages: ${response.pagination.totalPages}`);
53
+ }
54
+
55
+ // Display the fetched content
56
+ if (response.data && response.data.length > 0) {
57
+ console.log('\nFetched posts:');
58
+ response.data.forEach((post, index) => {
59
+ console.log(`\n${index + 1}. ${post.pageTitle || 'Untitled'}`);
60
+ console.log(` Slug: ${post.slug_prefix ? `${post.slug_prefix}/${post.slug}` : post.slug}`);
61
+ console.log(` Status: ${post.pageStatus}`);
62
+ console.log(` Type: ${post.pageType}`);
63
+ console.log(` Created: ${post.createdAt}`);
64
+ });
65
+ } else {
66
+ console.warn('\n⚠️ No posts found matching the criteria');
67
+ console.log('This could mean:');
68
+ console.log(' 1. There are no published posts with slug_prefix "blog"');
69
+ console.log(' 2. The site name might be incorrect');
70
+ console.log(' 3. Authentication might be failing');
71
+ }
72
+ } else {
73
+ console.error('❌ Request failed:', response.error || response.message);
74
+ }
75
+ } catch (error) {
76
+ console.error('❌ Error fetching content:', error);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Example 2: Query with pagination
82
+ */
83
+ async function fetchBlogPostsWithPagination() {
84
+ console.log('\n=== Example 2: Fetching blog posts with pagination ===\n');
85
+
86
+ try {
87
+ // Fetch page 2 with 10 items per page
88
+ const params: ContentQueryParams = {
89
+ slug_prefix: 'blog',
90
+ page_type: 'post',
91
+ page_status: 'publish',
92
+ limit: 10,
93
+ page: 2 // Get the second page of results
94
+ };
95
+
96
+ const response = await client.content.getContent('clinicaldrugtrials', params);
97
+
98
+ if (response.success && response.data) {
99
+ console.log(`✅ Fetched page 2: ${response.data.length} posts`);
100
+
101
+ // Display titles
102
+ response.data.forEach(post => {
103
+ console.log(` - ${post.pageTitle}`);
104
+ });
105
+ }
106
+ } catch (error) {
107
+ console.error('❌ Error:', error);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Example 3: Query with different slug prefixes
113
+ */
114
+ async function fetchContentByDifferentPrefixes() {
115
+ console.log('\n=== Example 3: Fetching content with different slug prefixes ===\n');
116
+
117
+ const prefixes = ['blog', 'news', 'articles', 'resources'];
118
+
119
+ for (const prefix of prefixes) {
120
+ try {
121
+ const params: ContentQueryParams = {
122
+ slug_prefix: prefix,
123
+ page_type: 'post',
124
+ page_status: 'publish',
125
+ limit: 5
126
+ };
127
+
128
+ const response = await client.content.getContent('clinicaldrugtrials', params);
129
+
130
+ if (response.success) {
131
+ console.log(`Slug prefix "${prefix}": Found ${response.data?.length || 0} posts`);
132
+ }
133
+ } catch (error) {
134
+ console.error(`Error fetching "${prefix}":`, error);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Example 4: Debugging - See what's actually being sent
141
+ * The SDK now includes detailed logging to help debug issues
142
+ */
143
+ async function debugContentQuery() {
144
+ console.log('\n=== Example 4: Debug mode - See detailed logs ===\n');
145
+ console.log('With the logging added, you\'ll see:');
146
+ console.log(' - Initial parameters');
147
+ console.log(' - URL construction');
148
+ console.log(' - HTTP request details');
149
+ console.log(' - Response data\n');
150
+
151
+ try {
152
+ const params: ContentQueryParams = {
153
+ slug_prefix: 'blog',
154
+ page_type: 'post',
155
+ page_status: 'publish',
156
+ limit: 25
157
+ };
158
+
159
+ // This will trigger all the console.log statements we added
160
+ const response = await client.content.getContent('clinicaldrugtrials', params);
161
+
162
+ // The console will show:
163
+ // [PerspectAPI Content] Starting content fetch: { ... }
164
+ // [HTTP Client - URL Builder] Processing params: { ... }
165
+ // [HTTP Client] ⚠️ slug_prefix detected: "blog"
166
+ // [HTTP Client - URL Builder] Final URL: https://api.perspect.comm/api/v1/sites/clinicaldrugtrials?slug_prefix=blog&...
167
+ // [HTTP Client - Response] Status: 200 OK
168
+ // [PerspectAPI Content] HTTP response received: { ... }
169
+
170
+ if (response.data?.length === 0) {
171
+ console.log('\n⚠️ Check the logs above to see:');
172
+ console.log(' - What URL was actually called');
173
+ console.log(' - What parameters were sent');
174
+ console.log(' - What the server responded with');
175
+ }
176
+ } catch (error) {
177
+ console.error('Error:', error);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Example 5: Alternative approach using different parameters
183
+ */
184
+ async function fetchAllContent() {
185
+ console.log('\n=== Example 5: Fetch without slug_prefix to see all content ===\n');
186
+
187
+ try {
188
+ // Try without slug_prefix to see if there's any content at all
189
+ const params: ContentQueryParams = {
190
+ page_type: 'post',
191
+ page_status: 'publish',
192
+ limit: 10
193
+ };
194
+
195
+ const response = await client.content.getContent('clinicaldrugtrials', params);
196
+
197
+ if (response.success && response.data) {
198
+ console.log(`✅ Found ${response.data.length} posts without slug_prefix filter`);
199
+
200
+ // Show what slug_prefixes are actually in use
201
+ const prefixes = new Set(response.data.map(post => post.slug_prefix).filter(Boolean));
202
+ if (prefixes.size > 0) {
203
+ console.log('\nActual slug_prefixes in the content:');
204
+ prefixes.forEach(prefix => console.log(` - ${prefix}`));
205
+ }
206
+ }
207
+ } catch (error) {
208
+ console.error('Error:', error);
209
+ }
210
+ }
211
+
212
+ // Main execution
213
+ async function main() {
214
+ console.log('PerspectAPI Content Query Examples');
215
+ console.log('===================================\n');
216
+
217
+ // Run examples
218
+ await fetchBlogPosts();
219
+ await fetchBlogPostsWithPagination();
220
+ await fetchContentByDifferentPrefixes();
221
+ await debugContentQuery();
222
+ await fetchAllContent();
223
+ }
224
+
225
+ // Run the examples
226
+ if (require.main === module) {
227
+ main().catch(console.error);
228
+ }
229
+
230
+ // Export functions for use in other scripts
231
+ export {
232
+ fetchBlogPosts,
233
+ fetchBlogPostsWithPagination,
234
+ fetchContentByDifferentPrefixes,
235
+ debugContentQuery,
236
+ fetchAllContent
237
+ };
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Image Transformation Examples
3
+ *
4
+ * @deprecated Historical v1 example. Do not copy into new code. v1 sunsets on
5
+ * 2026-06-01; use createPerspectApiV2Client from perspectapi-ts-sdk/v2.
6
+ *
7
+ * Demonstrates how to use Cloudflare Image Resizing with the SDK
8
+ */
9
+
10
+ import {
11
+ createPerspectApiClient,
12
+ transformMediaItem,
13
+ buildImageUrl,
14
+ generateResponsiveUrls,
15
+ generateSrcSet,
16
+ generateResponsiveImageHtml,
17
+ type MediaItem
18
+ } from '../../../src';
19
+
20
+ const API_URL = process.env.API_URL || 'https://api.perspect.comm';
21
+ const API_KEY = process.env.API_KEY || 'your-api-key';
22
+ const SITE_NAME = process.env.SITE_NAME || 'your-site';
23
+
24
+ async function main() {
25
+ const client = createPerspectApiClient({
26
+ baseUrl: API_URL,
27
+ apiKey: API_KEY
28
+ });
29
+
30
+ console.log('=== Image Transformation Examples ===\n');
31
+
32
+ // Example 1: Transform product images
33
+ console.log('1. Product Images:');
34
+ const products = await client.products.getProducts(SITE_NAME, {
35
+ limit: 5
36
+ });
37
+
38
+ if (products.data && products.data.length > 0) {
39
+ const product = products.data[0];
40
+ const mediaArray = product.media;
41
+ // Handle both single MediaItem and MediaItem[] array
42
+ const media = Array.isArray(mediaArray)
43
+ ? (Array.isArray(mediaArray[0]) ? mediaArray[0][0] : mediaArray[0])
44
+ : undefined;
45
+
46
+ if (media && !Array.isArray(media)) {
47
+ console.log(`Product: ${product.name}`);
48
+
49
+ // Generate all responsive sizes
50
+ const urls = transformMediaItem(API_URL, media);
51
+ console.log('Responsive URLs:');
52
+ console.log(` Thumbnail (150x150): ${urls.thumbnail}`);
53
+ console.log(` Small (400px): ${urls.small}`);
54
+ console.log(` Medium (800px): ${urls.medium}`);
55
+ console.log(` Large (1200px): ${urls.large}`);
56
+ console.log(` Original: ${urls.original}`);
57
+ }
58
+ }
59
+
60
+ console.log('\n2. Custom Transformations:');
61
+
62
+ // Example 2: Custom single transformation
63
+ const customUrl = buildImageUrl(
64
+ API_URL,
65
+ 'media/mysite/photo.jpg',
66
+ {
67
+ width: 400,
68
+ height: 300,
69
+ fit: 'cover',
70
+ format: 'webp',
71
+ quality: 85
72
+ }
73
+ );
74
+ console.log(`Custom URL: ${customUrl}`);
75
+
76
+ // Example 3: Thumbnail with crop
77
+ const thumbnailUrl = buildImageUrl(
78
+ API_URL,
79
+ 'media/mysite/photo.jpg',
80
+ {
81
+ width: 150,
82
+ height: 150,
83
+ fit: 'cover',
84
+ gravity: 'auto',
85
+ format: 'auto',
86
+ quality: 75
87
+ }
88
+ );
89
+ console.log(`Thumbnail: ${thumbnailUrl}`);
90
+
91
+ // Example 4: High DPR (Retina)
92
+ const retinaUrl = buildImageUrl(
93
+ API_URL,
94
+ 'media/mysite/photo.jpg',
95
+ {
96
+ width: 400,
97
+ dpr: 2,
98
+ format: 'auto',
99
+ quality: 85
100
+ }
101
+ );
102
+ console.log(`Retina (2x): ${retinaUrl}`);
103
+
104
+ console.log('\n3. Responsive Images:');
105
+
106
+ // Example 5: Generate all sizes
107
+ const responsiveUrls = generateResponsiveUrls(
108
+ API_URL,
109
+ 'media/mysite/photo.jpg'
110
+ );
111
+ console.log('All sizes:', Object.keys(responsiveUrls));
112
+
113
+ // Example 6: Generate srcset
114
+ const srcset = generateSrcSet(
115
+ API_URL,
116
+ 'media/mysite/photo.jpg',
117
+ [400, 800, 1200, 1600]
118
+ );
119
+ console.log(`Srcset: ${srcset.substring(0, 100)}...`);
120
+
121
+ // Example 7: Complete HTML
122
+ const html = generateResponsiveImageHtml(
123
+ API_URL,
124
+ 'media/mysite/photo.jpg',
125
+ 'Example photo',
126
+ {
127
+ className: 'rounded-lg shadow-md',
128
+ loading: 'lazy',
129
+ widths: [400, 800, 1200]
130
+ }
131
+ );
132
+ console.log('\n4. Complete HTML:');
133
+ console.log(html);
134
+
135
+ console.log('\n5. React Component Example:');
136
+ console.log(`
137
+ import { transformMediaItem, type MediaItem } from 'perspectapi-ts-sdk';
138
+
139
+ interface ProductImageProps {
140
+ media: MediaItem;
141
+ alt: string;
142
+ }
143
+
144
+ export function ProductImage({ media, alt }: ProductImageProps) {
145
+ const urls = transformMediaItem('${API_URL}', media);
146
+
147
+ return (
148
+ <img
149
+ src={urls.medium}
150
+ srcSet={\`
151
+ \${urls.small} 400w,
152
+ \${urls.medium} 800w,
153
+ \${urls.large} 1200w
154
+ \`}
155
+ sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw"
156
+ alt={alt}
157
+ loading="lazy"
158
+ className="rounded-lg"
159
+ />
160
+ );
161
+ }
162
+ `);
163
+
164
+ console.log('\n6. Use Cases:');
165
+ console.log(`
166
+ // E-commerce product grid
167
+ const gridThumbnail = buildImageUrl(API_URL, mediaPath, {
168
+ width: 300,
169
+ height: 300,
170
+ fit: 'cover',
171
+ format: 'auto'
172
+ });
173
+
174
+ // Blog featured image
175
+ const featuredImage = buildImageUrl(API_URL, mediaPath, {
176
+ width: 800,
177
+ fit: 'scale-down',
178
+ format: 'auto',
179
+ quality: 85
180
+ });
181
+
182
+ // User avatar
183
+ const avatar = buildImageUrl(API_URL, mediaPath, {
184
+ width: 40,
185
+ height: 40,
186
+ fit: 'cover',
187
+ format: 'auto'
188
+ });
189
+
190
+ // Gallery lightbox
191
+ const lightbox = buildImageUrl(API_URL, mediaPath, {
192
+ width: 1600,
193
+ fit: 'scale-down',
194
+ format: 'auto',
195
+ quality: 90
196
+ });
197
+ `);
198
+ }
199
+
200
+ main().catch(console.error);