JSPM

  • Created
  • Published
  • Downloads 444
  • Score
    100M100P100Q109222F
  • License MIT

TypeScript SDK for PerspectAPI - Cloudflare Workers compatible

Package Exports

  • perspectapi-ts-sdk

Readme

PerspectAPI TypeScript SDK

A comprehensive TypeScript SDK for PerspectAPI, designed to work seamlessly with Cloudflare Workers and other JavaScript environments.

Features

  • 🚀 Cloudflare Workers Compatible - Uses native fetch API, no Node.js dependencies
  • 🔒 Type Safe - Full TypeScript support with comprehensive type definitions
  • 🔄 Automatic Retries - Built-in retry logic with exponential backoff
  • 🛡️ Error Handling - Structured error responses with detailed information
  • 📦 Modular Design - Use individual clients or the complete SDK
  • 🔑 Multiple Auth Methods - Support for JWT tokens and API keys
  • 📊 Comprehensive Coverage - All PerspectAPI endpoints supported
  • 🧩 High-Level Loaders - Drop-in helpers for products, content, and checkout flows with fallbacks
  • 📧 Newsletter Management - Complete newsletter subscription system with double opt-in, preferences, and lists

Installation

npm install perspectapi-ts-sdk

Quick Start

import { createPerspectApiClient } from 'perspectapi-ts-sdk';

// Initialize with API key
const client = createPerspectApiClient({
  baseUrl: 'https://your-perspectapi-instance.com',
  apiKey: 'your-api-key'
});

// Or initialize with JWT token
const client = createPerspectApiClient({
  baseUrl: 'https://your-perspectapi-instance.com',
  jwt: 'your-jwt-token'
});

// Use the client
async function example() {
  const siteName = 'your-site-name';

  // Get all content
  const content = await client.content.getContent(siteName);
  console.log(content.data);

  // Create new content
  const newContent = await client.content.createContent({
    page_title: 'Hello World',
    page_content: 'This is my first post!',
    page_status: 'publish',
    page_type: 'post'
  });
  
  console.log(newContent.data);
}

### High-Level Loaders (Quick Example)

```typescript
import {
  loadProducts,
  loadPosts,
  createCheckoutSession
} from 'perspectapi-ts-sdk';

const loaders = {
  client: createPerspectApiClient({ baseUrl, apiKey }),
  siteName: 'my-museum-site'
};

const products = await loadProducts(loaders);
const posts = await loadPosts(loaders);

const checkout = await createCheckoutSession({
  ...loaders,
  items: [{ productId: products[0].id, quantity: 1 }],
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel'
});

📚 See docs/loaders.md for full walkthroughs, including fallback data, custom logging, and Stripe price resolution.


## High-Level Data Loaders

The SDK now ships with convenience loaders that wrap the lower-level REST clients. They help you:

- normalise products (including nested media arrays and Stripe IDs)
- search products server-side with limit/offset support and filter by category names or IDs (no hard-coded IDs)
- fetch published posts/pages with sensible defaults
- fall back to sample data when PerspectAPI is not configured (useful for local previews)
- create Stripe checkout sessions by automatically resolving price IDs

All loaders accept a shared `LoaderOptions` object:

```typescript
import {
  loadProducts,
  loadProductBySlug,
  loadPosts,
  createCheckoutSession,
  type LoaderOptions
} from 'perspectapi-ts-sdk';

const options: LoaderOptions = {
  client: createPerspectApiClient({ baseUrl, apiKey }),
  siteName: 'museum-indian-art',
  logger: console,              // optional
  fallbackProducts: [],         // optional
  fallbackPosts: []             // optional
};

const products = await loadProducts(options);
const single = await loadProductBySlug({ ...options, slug: 'sunset-painting' });
const posts = await loadPosts(options);

await createCheckoutSession({
  ...options,
  items: [{ productId: products[0].id, quantity: 1 }],
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel'
});

// Filter products by multiple category names/IDs while combining search
const decorPillows = await loadProducts({
  ...options,
  category: ['Home Decor', 'Seasonal Sale'],
  categoryIds: [12],
  search: 'pillow',
  offset: 24,
  limit: 12
});

More real-world scenarios—including Cloudflare Workers, Remix/Next.js loaders, custom logger implementations, and checkout price resolution—are documented in docs/loaders.md.

Authentication

API Key Authentication

const client = createPerspectApiClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'pk_your_api_key_here'
});

JWT Token Authentication

const client = createPerspectApiClient({
  baseUrl: 'https://api.example.com',
  jwt: 'your.jwt.token'
});

// Or authenticate after initialization
await client.auth.signIn({
  email: 'user@example.com',
  password: 'password'
});

Dynamic Authentication

const client = createPerspectApiClient({
  baseUrl: 'https://api.example.com'
});

// Set authentication later
client.setApiKey('pk_your_api_key');
// or
client.setAuth('your.jwt.token');

// Clear authentication
client.clearAuth();

API Reference

Content Management

// Get all content with pagination
const content = await client.content.getContent('your-site-name', {
  page: 1,
  limit: 20,
  page_status: 'publish',
  page_type: 'post'
});

// Get content by ID
const post = await client.content.getContentById(123);

// Get content by slug
const page = await client.content.getContentBySlug('your-site-name', 'about-us');

// Create new content
const newPost = await client.content.createContent({
  page_title: 'My New Post',
  page_content: '<p>Content goes here</p>',
  content_markdown: '# My New Post\n\nContent goes here',
  page_status: 'draft',
  page_type: 'post',
  slug: 'my-new-post'
});

// Update content
const updated = await client.content.updateContent(123, {
  page_title: 'Updated Title',
  page_status: 'publish'
});

// Delete content
await client.content.deleteContent(123);

// Publish/unpublish content
await client.content.publishContent(123);
await client.content.unpublishContent(123);

Products & E-commerce

// Get all products
const products = await client.products.getProducts('my-site', {
  page: 1,
  limit: 20,
  isActive: true
});

// Create new product
const product = await client.products.createProduct({
  name: 'Amazing Product',
  description: 'Product description',
  price: 29.99,
  currency: 'USD',
  sku: 'PROD-001'
});

// Update product inventory
await client.products.updateProductInventory(123, {
  quantity: 10,
  operation: 'add',
  reason: 'Restocked'
});

// Get product by slug and site name (NEW!)
const productBySlug = await client.products.getProductBySlug('my-site', 'awesome-laptop');
console.log('Product:', productBySlug.data?.name);
console.log('Variants:', productBySlug.data?.variants);

// Get products by category slug (NEW!)
const categoryProducts = await client.products.getProductsByCategorySlug(
  'my-site',
  'electronics',
  {
    page: 1,
    limit: 20,
    published: true,
    search: 'phone'
  }
);
console.log('Products:', categoryProducts.data?.data);
console.log('Category info:', categoryProducts.data?.category);

Organizations & Sites

// Get organizations
const orgs = await client.organizations.getOrganizations();

// Create new organization
const org = await client.organizations.createOrganization({
  name: 'My Company',
  description: 'Company description'
});

// Get sites
const sites = await client.sites.getSites({
  organizationId: 1
});

// Create new site
const site = await client.sites.createSite({
  name: 'My Website',
  domain: 'example.com',
  organizationId: 1
});

API Key Management

// Get all API keys
const apiKeys = await client.apiKeys.getApiKeys();

// Create new API key
const newKey = await client.apiKeys.createApiKey({
  name: 'Frontend App Key',
  description: 'API key for frontend application',
  permissions: ['content:read', 'products:read'],
  expiresAt: '2024-12-31T23:59:59Z'
});

console.log('New API key:', newKey.data.key);

// Test API key validity
const testResult = await client.apiKeys.testApiKey('pk_test_key');
console.log('Key valid:', testResult.data.valid);

Webhooks

// Get all webhooks
const webhooks = await client.webhooks.getWebhooks();

// Create new webhook
const webhook = await client.webhooks.createWebhook({
  name: 'Order Notifications',
  url: 'https://myapp.com/webhooks/orders',
  provider: 'stripe',
  events: ['payment_intent.succeeded', 'payment_intent.payment_failed'],
  isActive: true
});

// Test webhook
const testResult = await client.webhooks.testWebhook(webhook.data.id);
console.log('Webhook test:', testResult.data);

Checkout & Payments

// Create Stripe checkout session
const session = await client.checkout.createCheckoutSession({
  priceId: 'price_1234567890',
  successUrl: 'https://myapp.com/success',
  cancelUrl: 'https://myapp.com/cancel',
  customerEmail: 'customer@example.com'
});

// Redirect user to checkout
window.location.href = session.data.url;

// Get checkout session status
const sessionStatus = await client.checkout.getCheckoutSession('cs_test_123');

Contact Forms

const siteName = 'your-site-name';

// Submit contact form
const submission = await client.contact.submitContact(siteName, {
  name: 'John Doe',
  email: 'john@example.com',
  subject: 'Question about pricing',
  message: 'I have a question about your pricing plans.',
  turnstileToken: 'turnstile-response-token'
});

// Get contact submissions (admin only)
const submissions = await client.contact.getContactSubmissions(siteName, {
  status: 'unread',
  page: 1,
  limit: 50
});

// Update submission status
await client.contact.updateContactStatus(siteName, submission.data.id, 'read');

Newsletter Subscriptions

const siteName = 'your-site-name';

// Subscribe to newsletter (with double opt-in)
const subscription = await client.newsletter.subscribe(siteName, {
  email: 'subscriber@example.com',
  name: 'Jane Doe',
  list_ids: ['list_default'],  // Optional: subscribe to specific lists
  frequency: 'weekly',          // instant, daily, weekly, monthly
  topics: ['news', 'updates'],  // Optional: topic preferences
  double_opt_in: true,          // Default: true for GDPR compliance
  turnstile_token: 'token'      // Optional: Turnstile verification
});

// Confirm subscription via email token
const confirmed = await client.newsletter.confirmSubscription(
  siteName,
  'confirmation-token-from-email'
);

// Unsubscribe
const unsubscribed = await client.newsletter.unsubscribe(siteName, {
  email: 'subscriber@example.com',
  reason: 'Too many emails'  // Optional feedback
});

// One-click unsubscribe (from email link)
await client.newsletter.unsubscribeByToken(siteName, 'unsubscribe-token');

// Update preferences
await client.newsletter.updatePreferences(siteName, 'subscriber@example.com', {
  frequency: 'monthly',
  topics: ['product-updates'],
  email_format: 'html',  // html, text, or both
  timezone: 'America/New_York',
  track_opens: false,
  track_clicks: false
});

// Check subscription status
const status = await client.newsletter.getStatus(siteName, 'subscriber@example.com');
console.log('Subscribed:', status.data.subscribed);
console.log('Status:', status.data.status); // pending, confirmed, unsubscribed

// Get available newsletter lists
const lists = await client.newsletter.getLists(siteName);
console.log('Available lists:', lists.data.lists);

Newsletter Admin Functions

// Get all subscriptions (admin only)
const subscriptions = await client.newsletter.getSubscriptions(siteName, {
  page: 1,
  limit: 100,
  status: 'confirmed',
  list_id: 'list_weekly',
  search: 'john',
  startDate: '2024-01-01',
  endDate: '2024-12-31'
});

// Update subscription status
await client.newsletter.updateSubscriptionStatus(
  siteName,
  'sub_123',
  'unsubscribed',
  'Admin action: User requested via support'
);

// Bulk operations
await client.newsletter.bulkUpdateSubscriptions(siteName, {
  ids: ['sub_123', 'sub_456'],
  action: 'add_to_list',
  list_id: 'list_special_offers'
});

// List management
const newList = await client.newsletter.createList(siteName, {
  list_name: 'VIP Customers',
  slug: 'vip-customers',
  description: 'Exclusive updates for VIP customers',
  is_public: false,
  is_default: false,
  double_opt_in: true,
  welcome_email_enabled: true
});

await client.newsletter.updateList(siteName, 'list_123', {
  description: 'Updated description',
  is_public: true
});

// Get statistics
const stats = await client.newsletter.getStatistics(siteName, {
  startDate: '2024-01-01',
  endDate: '2024-12-31',
  list_id: 'list_weekly'
});
console.log('Total subscribers:', stats.data.totalSubscribers);
console.log('Open rate:', stats.data.engagementMetrics.averageOpenRate);

// Export subscriptions
const exportData = await client.newsletter.exportSubscriptions(siteName, {
  format: 'csv',  // csv, json, or xlsx
  status: 'confirmed',
  list_id: 'list_weekly'
});
console.log('Download URL:', exportData.data.downloadUrl);

// Import subscriptions
const importResult = await client.newsletter.importSubscriptions(siteName, {
  subscriptions: [
    { email: 'user1@example.com', name: 'User One', lists: ['list_weekly'] },
    { email: 'user2@example.com', name: 'User Two', lists: ['list_daily'] }
  ],
  skip_confirmation: false,  // Skip double opt-in for imported users
  update_existing: true       // Update if email already exists
});
console.log('Imported:', importResult.data.imported);
console.log('Failed:', importResult.data.failed);

Configuration Options

interface PerspectApiConfig {
  baseUrl: string;           // Required: API base URL
  apiKey?: string;          // Optional: API key for authentication
  jwt?: string;             // Optional: JWT token for authentication
  timeout?: number;         // Optional: Request timeout in ms (default: 30000)
  retries?: number;         // Optional: Number of retries (default: 3)
  headers?: Record<string, string>; // Optional: Additional headers
}

Error Handling

The SDK provides structured error handling:

import { createApiError } from 'perspectapi-ts-sdk';

try {
  const content = await client.content.getContentById(999);
} catch (error) {
  const apiError = createApiError(error);
  
  console.log('Error message:', apiError.message);
  console.log('Status code:', apiError.status);
  console.log('Error code:', apiError.code);
  console.log('Details:', apiError.details);
}

Cloudflare Workers Usage

The SDK is fully compatible with Cloudflare Workers:

// worker.ts
import { createPerspectApiClient } from 'perspectapi-ts-sdk';

export default {
  async fetch(request: Request, env: any): Promise<Response> {
    const client = createPerspectApiClient({
      baseUrl: env.PERSPECT_API_URL,
      apiKey: env.PERSPECT_API_KEY
    });

    try {
      const content = await client.content.getContent(env.PERSPECT_SITE_NAME, { limit: 10 });
      
      return new Response(JSON.stringify(content.data), {
        headers: { 'Content-Type': 'application/json' }
      });
    } catch (error) {
      return new Response('Error fetching content', { status: 500 });
    }
  }
};

Advanced Usage

Using Individual Clients

import { HttpClient, ContentClient } from 'perspectapi-ts-sdk';

// Create HTTP client
const http = new HttpClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'your-api-key'
});

// Use individual client
const contentClient = new ContentClient(http);
const content = await contentClient.getContent('your-site-name');

Custom Headers

const client = createPerspectApiClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'your-api-key',
  headers: {
    'X-Custom-Header': 'custom-value',
    'User-Agent': 'MyApp/1.0.0'
  }
});

Timeout and Retries

const client = createPerspectApiClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'your-api-key',
  timeout: 60000,  // 60 seconds
  retries: 5       // Retry up to 5 times
});

TypeScript Support

The SDK is written in TypeScript and provides comprehensive type definitions:

import type { 
  Content, 
  Product, 
  ApiResponse, 
  PaginatedResponse 
} from 'perspectapi-ts-sdk';

// Fully typed responses
const content: ApiResponse<Content> = await client.content.getContentById(123);
const products: PaginatedResponse<Product> = await client.products.getProducts('your-site-name');

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support