perspectapi-ts-sdk 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 PerspectAPI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,515 @@
1
+ # PerspectAPI TypeScript SDK
2
+
3
+ A comprehensive TypeScript SDK for PerspectAPI, designed to work seamlessly with Cloudflare Workers and other JavaScript environments.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Cloudflare Workers Compatible** - Uses native fetch API, no Node.js dependencies
8
+ - 🔒 **Type Safe** - Full TypeScript support with comprehensive type definitions
9
+ - 🔄 **Automatic Retries** - Built-in retry logic with exponential backoff
10
+ - 🛡️ **Error Handling** - Structured error responses with detailed information
11
+ - 📦 **Modular Design** - Use individual clients or the complete SDK
12
+ - 🔑 **Multiple Auth Methods** - Support for JWT tokens and API keys
13
+ - 📊 **Comprehensive Coverage** - All PerspectAPI endpoints supported
14
+ - 🧩 **High-Level Loaders** - Drop-in helpers for products, content, and checkout flows with fallbacks
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install perspectapi-ts-sdk
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { createPerspectApiClient } from 'perspectapi-ts-sdk';
26
+
27
+ // Initialize with API key
28
+ const client = createPerspectApiClient({
29
+ baseUrl: 'https://your-perspectapi-instance.com',
30
+ apiKey: 'your-api-key'
31
+ });
32
+
33
+ // Or initialize with JWT token
34
+ const client = createPerspectApiClient({
35
+ baseUrl: 'https://your-perspectapi-instance.com',
36
+ jwt: 'your-jwt-token'
37
+ });
38
+
39
+ // Use the client
40
+ async function example() {
41
+ const siteName = 'your-site-name';
42
+
43
+ // Get all content
44
+ const content = await client.content.getContent(siteName);
45
+ console.log(content.data);
46
+
47
+ // Create new content
48
+ const newContent = await client.content.createContent({
49
+ page_title: 'Hello World',
50
+ page_content: 'This is my first post!',
51
+ page_status: 'publish',
52
+ page_type: 'post'
53
+ });
54
+
55
+ console.log(newContent.data);
56
+ }
57
+
58
+ ### High-Level Loaders (Quick Example)
59
+
60
+ ```typescript
61
+ import {
62
+ loadProducts,
63
+ loadPosts,
64
+ createCheckoutSession
65
+ } from 'perspectapi-ts-sdk';
66
+
67
+ const loaders = {
68
+ client: createPerspectApiClient({ baseUrl, apiKey }),
69
+ siteName: 'my-museum-site'
70
+ };
71
+
72
+ const products = await loadProducts(loaders);
73
+ const posts = await loadPosts(loaders);
74
+
75
+ const checkout = await createCheckoutSession({
76
+ ...loaders,
77
+ items: [{ productId: products[0].id, quantity: 1 }],
78
+ successUrl: 'https://example.com/success',
79
+ cancelUrl: 'https://example.com/cancel'
80
+ });
81
+ ```
82
+
83
+ > 📚 See [docs/loaders.md](docs/loaders.md) for full walkthroughs, including fallback data, custom logging, and Stripe price resolution.
84
+ ```
85
+
86
+ ## High-Level Data Loaders
87
+
88
+ The SDK now ships with convenience loaders that wrap the lower-level REST clients. They help you:
89
+
90
+ - normalise products (including nested media arrays and Stripe IDs)
91
+ - search products server-side with limit/offset support and filter by category names or IDs (no hard-coded IDs)
92
+ - fetch published posts/pages with sensible defaults
93
+ - fall back to sample data when PerspectAPI is not configured (useful for local previews)
94
+ - create Stripe checkout sessions by automatically resolving price IDs
95
+
96
+ All loaders accept a shared `LoaderOptions` object:
97
+
98
+ ```typescript
99
+ import {
100
+ loadProducts,
101
+ loadProductBySlug,
102
+ loadPosts,
103
+ createCheckoutSession,
104
+ type LoaderOptions
105
+ } from 'perspectapi-ts-sdk';
106
+
107
+ const options: LoaderOptions = {
108
+ client: createPerspectApiClient({ baseUrl, apiKey }),
109
+ siteName: 'museum-indian-art',
110
+ logger: console, // optional
111
+ fallbackProducts: [], // optional
112
+ fallbackPosts: [] // optional
113
+ };
114
+
115
+ const products = await loadProducts(options);
116
+ const single = await loadProductBySlug({ ...options, slug: 'sunset-painting' });
117
+ const posts = await loadPosts(options);
118
+
119
+ await createCheckoutSession({
120
+ ...options,
121
+ items: [{ productId: products[0].id, quantity: 1 }],
122
+ successUrl: 'https://example.com/success',
123
+ cancelUrl: 'https://example.com/cancel'
124
+ });
125
+
126
+ // Filter products by multiple category names/IDs while combining search
127
+ const decorPillows = await loadProducts({
128
+ ...options,
129
+ category: ['Home Decor', 'Seasonal Sale'],
130
+ categoryIds: [12],
131
+ search: 'pillow',
132
+ offset: 24,
133
+ limit: 12
134
+ });
135
+ ```
136
+
137
+ > More real-world scenarios—including Cloudflare Workers, Remix/Next.js loaders, custom logger implementations, and checkout price resolution—are documented in [docs/loaders.md](docs/loaders.md).
138
+
139
+ ## Authentication
140
+
141
+ ### API Key Authentication
142
+
143
+ ```typescript
144
+ const client = createPerspectApiClient({
145
+ baseUrl: 'https://api.example.com',
146
+ apiKey: 'pk_your_api_key_here'
147
+ });
148
+ ```
149
+
150
+ ### JWT Token Authentication
151
+
152
+ ```typescript
153
+ const client = createPerspectApiClient({
154
+ baseUrl: 'https://api.example.com',
155
+ jwt: 'your.jwt.token'
156
+ });
157
+
158
+ // Or authenticate after initialization
159
+ await client.auth.signIn({
160
+ email: 'user@example.com',
161
+ password: 'password'
162
+ });
163
+ ```
164
+
165
+ ### Dynamic Authentication
166
+
167
+ ```typescript
168
+ const client = createPerspectApiClient({
169
+ baseUrl: 'https://api.example.com'
170
+ });
171
+
172
+ // Set authentication later
173
+ client.setApiKey('pk_your_api_key');
174
+ // or
175
+ client.setAuth('your.jwt.token');
176
+
177
+ // Clear authentication
178
+ client.clearAuth();
179
+ ```
180
+
181
+ ## API Reference
182
+
183
+ ### Content Management
184
+
185
+ ```typescript
186
+ // Get all content with pagination
187
+ const content = await client.content.getContent('your-site-name', {
188
+ page: 1,
189
+ limit: 20,
190
+ page_status: 'publish',
191
+ page_type: 'post'
192
+ });
193
+
194
+ // Get content by ID
195
+ const post = await client.content.getContentById(123);
196
+
197
+ // Get content by slug
198
+ const page = await client.content.getContentBySlug('your-site-name', 'about-us');
199
+
200
+ // Create new content
201
+ const newPost = await client.content.createContent({
202
+ page_title: 'My New Post',
203
+ page_content: '<p>Content goes here</p>',
204
+ content_markdown: '# My New Post\n\nContent goes here',
205
+ page_status: 'draft',
206
+ page_type: 'post',
207
+ slug: 'my-new-post'
208
+ });
209
+
210
+ // Update content
211
+ const updated = await client.content.updateContent(123, {
212
+ page_title: 'Updated Title',
213
+ page_status: 'publish'
214
+ });
215
+
216
+ // Delete content
217
+ await client.content.deleteContent(123);
218
+
219
+ // Publish/unpublish content
220
+ await client.content.publishContent(123);
221
+ await client.content.unpublishContent(123);
222
+ ```
223
+
224
+ ### Products & E-commerce
225
+
226
+ ```typescript
227
+ // Get all products
228
+ const products = await client.products.getProducts('my-site', {
229
+ page: 1,
230
+ limit: 20,
231
+ isActive: true
232
+ });
233
+
234
+ // Create new product
235
+ const product = await client.products.createProduct({
236
+ name: 'Amazing Product',
237
+ description: 'Product description',
238
+ price: 29.99,
239
+ currency: 'USD',
240
+ sku: 'PROD-001'
241
+ });
242
+
243
+ // Update product inventory
244
+ await client.products.updateProductInventory(123, {
245
+ quantity: 10,
246
+ operation: 'add',
247
+ reason: 'Restocked'
248
+ });
249
+
250
+ // Get product by slug and site name (NEW!)
251
+ const productBySlug = await client.products.getProductBySlug('my-site', 'awesome-laptop');
252
+ console.log('Product:', productBySlug.data?.name);
253
+ console.log('Variants:', productBySlug.data?.variants);
254
+
255
+ // Get products by category slug (NEW!)
256
+ const categoryProducts = await client.products.getProductsByCategorySlug(
257
+ 'my-site',
258
+ 'electronics',
259
+ {
260
+ page: 1,
261
+ limit: 20,
262
+ published: true,
263
+ search: 'phone'
264
+ }
265
+ );
266
+ console.log('Products:', categoryProducts.data?.data);
267
+ console.log('Category info:', categoryProducts.data?.category);
268
+ ```
269
+
270
+ ### Organizations & Sites
271
+
272
+ ```typescript
273
+ // Get organizations
274
+ const orgs = await client.organizations.getOrganizations();
275
+
276
+ // Create new organization
277
+ const org = await client.organizations.createOrganization({
278
+ name: 'My Company',
279
+ description: 'Company description'
280
+ });
281
+
282
+ // Get sites
283
+ const sites = await client.sites.getSites({
284
+ organizationId: 1
285
+ });
286
+
287
+ // Create new site
288
+ const site = await client.sites.createSite({
289
+ name: 'My Website',
290
+ domain: 'example.com',
291
+ organizationId: 1
292
+ });
293
+ ```
294
+
295
+ ### API Key Management
296
+
297
+ ```typescript
298
+ // Get all API keys
299
+ const apiKeys = await client.apiKeys.getApiKeys();
300
+
301
+ // Create new API key
302
+ const newKey = await client.apiKeys.createApiKey({
303
+ name: 'Frontend App Key',
304
+ description: 'API key for frontend application',
305
+ permissions: ['content:read', 'products:read'],
306
+ expiresAt: '2024-12-31T23:59:59Z'
307
+ });
308
+
309
+ console.log('New API key:', newKey.data.key);
310
+
311
+ // Test API key validity
312
+ const testResult = await client.apiKeys.testApiKey('pk_test_key');
313
+ console.log('Key valid:', testResult.data.valid);
314
+ ```
315
+
316
+ ### Webhooks
317
+
318
+ ```typescript
319
+ // Get all webhooks
320
+ const webhooks = await client.webhooks.getWebhooks();
321
+
322
+ // Create new webhook
323
+ const webhook = await client.webhooks.createWebhook({
324
+ name: 'Order Notifications',
325
+ url: 'https://myapp.com/webhooks/orders',
326
+ provider: 'stripe',
327
+ events: ['payment_intent.succeeded', 'payment_intent.payment_failed'],
328
+ isActive: true
329
+ });
330
+
331
+ // Test webhook
332
+ const testResult = await client.webhooks.testWebhook(webhook.data.id);
333
+ console.log('Webhook test:', testResult.data);
334
+ ```
335
+
336
+ ### Checkout & Payments
337
+
338
+ ```typescript
339
+ // Create Stripe checkout session
340
+ const session = await client.checkout.createCheckoutSession({
341
+ priceId: 'price_1234567890',
342
+ successUrl: 'https://myapp.com/success',
343
+ cancelUrl: 'https://myapp.com/cancel',
344
+ customerEmail: 'customer@example.com'
345
+ });
346
+
347
+ // Redirect user to checkout
348
+ window.location.href = session.data.url;
349
+
350
+ // Get checkout session status
351
+ const sessionStatus = await client.checkout.getCheckoutSession('cs_test_123');
352
+ ```
353
+
354
+ ### Contact Forms
355
+
356
+ ```typescript
357
+ const siteName = 'your-site-name';
358
+
359
+ // Submit contact form
360
+ const submission = await client.contact.submitContact(siteName, {
361
+ name: 'John Doe',
362
+ email: 'john@example.com',
363
+ subject: 'Question about pricing',
364
+ message: 'I have a question about your pricing plans.',
365
+ turnstileToken: 'turnstile-response-token'
366
+ });
367
+
368
+ // Get contact submissions (admin only)
369
+ const submissions = await client.contact.getContactSubmissions(siteName, {
370
+ status: 'unread',
371
+ page: 1,
372
+ limit: 50
373
+ });
374
+
375
+ // Update submission status
376
+ await client.contact.updateContactStatus(siteName, submission.data.id, 'read');
377
+ ```
378
+
379
+ ## Configuration Options
380
+
381
+ ```typescript
382
+ interface PerspectApiConfig {
383
+ baseUrl: string; // Required: API base URL
384
+ apiKey?: string; // Optional: API key for authentication
385
+ jwt?: string; // Optional: JWT token for authentication
386
+ timeout?: number; // Optional: Request timeout in ms (default: 30000)
387
+ retries?: number; // Optional: Number of retries (default: 3)
388
+ headers?: Record<string, string>; // Optional: Additional headers
389
+ }
390
+ ```
391
+
392
+ ## Error Handling
393
+
394
+ The SDK provides structured error handling:
395
+
396
+ ```typescript
397
+ import { createApiError } from 'perspectapi-ts-sdk';
398
+
399
+ try {
400
+ const content = await client.content.getContentById(999);
401
+ } catch (error) {
402
+ const apiError = createApiError(error);
403
+
404
+ console.log('Error message:', apiError.message);
405
+ console.log('Status code:', apiError.status);
406
+ console.log('Error code:', apiError.code);
407
+ console.log('Details:', apiError.details);
408
+ }
409
+ ```
410
+
411
+ ## Cloudflare Workers Usage
412
+
413
+ The SDK is fully compatible with Cloudflare Workers:
414
+
415
+ ```typescript
416
+ // worker.ts
417
+ import { createPerspectApiClient } from 'perspectapi-ts-sdk';
418
+
419
+ export default {
420
+ async fetch(request: Request, env: any): Promise<Response> {
421
+ const client = createPerspectApiClient({
422
+ baseUrl: env.PERSPECT_API_URL,
423
+ apiKey: env.PERSPECT_API_KEY
424
+ });
425
+
426
+ try {
427
+ const content = await client.content.getContent(env.PERSPECT_SITE_NAME, { limit: 10 });
428
+
429
+ return new Response(JSON.stringify(content.data), {
430
+ headers: { 'Content-Type': 'application/json' }
431
+ });
432
+ } catch (error) {
433
+ return new Response('Error fetching content', { status: 500 });
434
+ }
435
+ }
436
+ };
437
+ ```
438
+
439
+ ## Advanced Usage
440
+
441
+ ### Using Individual Clients
442
+
443
+ ```typescript
444
+ import { HttpClient, ContentClient } from 'perspectapi-ts-sdk';
445
+
446
+ // Create HTTP client
447
+ const http = new HttpClient({
448
+ baseUrl: 'https://api.example.com',
449
+ apiKey: 'your-api-key'
450
+ });
451
+
452
+ // Use individual client
453
+ const contentClient = new ContentClient(http);
454
+ const content = await contentClient.getContent('your-site-name');
455
+ ```
456
+
457
+ ### Custom Headers
458
+
459
+ ```typescript
460
+ const client = createPerspectApiClient({
461
+ baseUrl: 'https://api.example.com',
462
+ apiKey: 'your-api-key',
463
+ headers: {
464
+ 'X-Custom-Header': 'custom-value',
465
+ 'User-Agent': 'MyApp/1.0.0'
466
+ }
467
+ });
468
+ ```
469
+
470
+ ### Timeout and Retries
471
+
472
+ ```typescript
473
+ const client = createPerspectApiClient({
474
+ baseUrl: 'https://api.example.com',
475
+ apiKey: 'your-api-key',
476
+ timeout: 60000, // 60 seconds
477
+ retries: 5 // Retry up to 5 times
478
+ });
479
+ ```
480
+
481
+ ## TypeScript Support
482
+
483
+ The SDK is written in TypeScript and provides comprehensive type definitions:
484
+
485
+ ```typescript
486
+ import type {
487
+ Content,
488
+ Product,
489
+ ApiResponse,
490
+ PaginatedResponse
491
+ } from 'perspectapi-ts-sdk';
492
+
493
+ // Fully typed responses
494
+ const content: ApiResponse<Content> = await client.content.getContentById(123);
495
+ const products: PaginatedResponse<Product> = await client.products.getProducts('your-site-name');
496
+ ```
497
+
498
+ ## Contributing
499
+
500
+ 1. Fork the repository
501
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
502
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
503
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
504
+ 5. Open a Pull Request
505
+
506
+ ## License
507
+
508
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
509
+
510
+ ## Support
511
+
512
+ - 📖 [Documentation](https://docs.perspectapi.com)
513
+ - 🐛 [Issue Tracker](https://github.com/perspectapi/perspectapi-ts-sdk/issues)
514
+ - 💬 [Discussions](https://github.com/perspectapi/perspectapi-ts-sdk/discussions)
515
+ - 📧 [Email Support](mailto:support@perspectapi.com)