perspectapi-ts-sdk 3.5.0 → 3.6.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 CHANGED
@@ -93,7 +93,7 @@ import { PerspectApiClient } from 'perspectapi-ts-sdk';
93
93
  import { myCacheAdapter } from './cache-adapter';
94
94
 
95
95
  const perspect = new PerspectApiClient({
96
- baseUrl: 'https://api.perspect.co',
96
+ baseUrl: 'https://api.perspect.comm',
97
97
  apiKey: env.PERSPECT_API_KEY,
98
98
  cache: {
99
99
  adapter: myCacheAdapter,
@@ -148,7 +148,7 @@ export default {
148
148
  };
149
149
  ```
150
150
 
151
- When PerspectAPI sends a webhook—or when your Worker mutates data directly—call `perspect.cache.invalidate({ tags: [...] })` using the tags emitted by the SDK (`products:site:<site>`, `content:slug:<site>:<slug>`, `content:category:<site>:<category_slug>`, etc.) so stale entries are purged immediately.
151
+ When PerspectAPI sends a webhook—or when your Worker mutates data directly—call `perspect.cache.invalidate({ tags: [...] })` using the tags emitted by the SDK (`products:site:<site>`, `content:slug:<site>:<slug>`, `newsletter:campaigns:slug:<site>:<slug>`, etc.) so stale entries are purged immediately.
152
152
 
153
153
  ### Webhook-driven cache invalidation
154
154
 
@@ -233,6 +233,34 @@ export default {
233
233
  ```
234
234
 
235
235
  > 🔁 Adjust the `WebhookEvent` union and `tagMap` to match the actual payloads you receive. PerspectAPI webhooks also carry version IDs and environment metadata that you can use for more granular targeting if needed.
236
+
237
+ ### Newsletter Publish Invalidation
238
+
239
+ Newsletter list and campaign reads are cache-aware:
240
+
241
+ ```ts
242
+ const campaigns = await perspect.newsletter.getPublishedCampaigns('museum-indian-art', {
243
+ page: 1,
244
+ limit: 20,
245
+ });
246
+ const campaign = await perspect.newsletter.getPublishedCampaignBySlug(
247
+ 'museum-indian-art',
248
+ 'spring-launch',
249
+ { slugPrefix: 'updates' }
250
+ );
251
+ ```
252
+
253
+ For webhook handling, use the newsletter helper so invalidation happens only when campaigns are published (not when drafts are created):
254
+
255
+ ```ts
256
+ const result = await perspect.newsletter.invalidatePublishedCampaignCacheFromWebhook(
257
+ webhookPayload,
258
+ 'museum-indian-art'
259
+ );
260
+
261
+ if (result.invalidated) {
262
+ console.log(result.tags);
263
+ }
236
264
  ```
237
265
 
238
266
  ## Image Transformations
@@ -248,7 +276,7 @@ const media = product.data.media?.[0];
248
276
 
249
277
  if (media) {
250
278
  // Generate all responsive sizes automatically
251
- const urls = transformMediaItem('https://api.perspect.co', media);
279
+ const urls = transformMediaItem('https://api.perspect.comm', media);
252
280
 
253
281
  console.log(urls.thumbnail); // 150x150 cover crop
254
282
  console.log(urls.small); // 400px wide
@@ -259,7 +287,7 @@ if (media) {
259
287
 
260
288
  // Or build custom transformations
261
289
  const customUrl = buildImageUrl(
262
- 'https://api.perspect.co',
290
+ 'https://api.perspect.comm',
263
291
  'media/mysite/photo.jpg',
264
292
  {
265
293
  width: 400,
package/dist/index.d.mts CHANGED
@@ -202,6 +202,34 @@ interface NewsletterList {
202
202
  is_default: boolean;
203
203
  subscriber_count?: number;
204
204
  }
205
+ interface NewsletterCampaignSummary {
206
+ id: string;
207
+ campaign_name: string;
208
+ slug: string;
209
+ slug_prefix?: string | null;
210
+ subject: string;
211
+ preview_text?: string | null;
212
+ status: string;
213
+ sent_at?: string | null;
214
+ completed_at?: string | null;
215
+ created_at: string;
216
+ updated_at: string;
217
+ }
218
+ interface NewsletterCampaignDetail extends NewsletterCampaignSummary {
219
+ markdown_content?: string | null;
220
+ html_content?: string | null;
221
+ text_content?: string | null;
222
+ excerpt?: string | null;
223
+ }
224
+ interface NewsletterCampaignListResponse {
225
+ items: NewsletterCampaignSummary[];
226
+ pagination: {
227
+ page: number;
228
+ limit: number;
229
+ total: number;
230
+ pages: number;
231
+ };
232
+ }
205
233
  interface NewsletterPreferences {
206
234
  frequency?: 'instant' | 'daily' | 'weekly' | 'monthly';
207
235
  topics?: string[];
@@ -2043,10 +2071,33 @@ declare class NewsletterClient extends BaseClient {
2043
2071
  /**
2044
2072
  * Get available newsletter lists
2045
2073
  */
2046
- getLists(siteName: string): Promise<ApiResponse<{
2074
+ getLists(siteName: string, cachePolicy?: CachePolicy): Promise<ApiResponse<{
2047
2075
  lists: NewsletterList[];
2048
2076
  total: number;
2049
2077
  }>>;
2078
+ /**
2079
+ * List publicly available (sent) newsletter campaigns
2080
+ */
2081
+ getPublishedCampaigns(siteName: string, params?: {
2082
+ page?: number;
2083
+ limit?: number;
2084
+ search?: string;
2085
+ }, cachePolicy?: CachePolicy): Promise<ApiResponse<NewsletterCampaignListResponse>>;
2086
+ /**
2087
+ * Fetch a publicly available (sent) newsletter campaign by slug
2088
+ */
2089
+ getPublishedCampaignBySlug(siteName: string, slug: string, optionsOrPolicy?: {
2090
+ slugPrefix?: string;
2091
+ } | CachePolicy, cachePolicy?: CachePolicy): Promise<ApiResponse<NewsletterCampaignDetail>>;
2092
+ /**
2093
+ * Invalidate cached published newsletter campaign data from a webhook payload.
2094
+ * Only publish events (and legacy sent aliases) trigger invalidation.
2095
+ */
2096
+ invalidatePublishedCampaignCacheFromWebhook(payload: Record<string, unknown>, fallbackSiteName?: string): Promise<{
2097
+ invalidated: boolean;
2098
+ tags: string[];
2099
+ reason?: string;
2100
+ }>;
2050
2101
  /**
2051
2102
  * Check subscription status by email
2052
2103
  */
@@ -2221,6 +2272,12 @@ declare class NewsletterClient extends BaseClient {
2221
2272
  * Client sites can use this to serve the tracking pixel response.
2222
2273
  */
2223
2274
  static getTrackingPixel(): Uint8Array;
2275
+ private buildNewsletterTags;
2276
+ private extractSlugPrefix;
2277
+ private normalizeNewsletterWebhookPayload;
2278
+ private isPublishEvent;
2279
+ private pickString;
2280
+ private isCachePolicy;
2224
2281
  }
2225
2282
 
2226
2283
  /**
@@ -2416,6 +2473,15 @@ declare class SiteUsersClient extends BaseClient {
2416
2473
  changeSubscriptionPlan(siteName: string, subscriptionId: string, productId: number, csrfToken?: string): Promise<ApiResponse<{
2417
2474
  success: boolean;
2418
2475
  }>>;
2476
+ /**
2477
+ * Create a Stripe Billing Portal session for updating payment methods
2478
+ * @param siteName - The site name
2479
+ * @param returnUrl - URL to redirect back to after portal session
2480
+ * @param csrfToken - CSRF token (required)
2481
+ */
2482
+ createBillingPortalSession(siteName: string, returnUrl: string, csrfToken?: string): Promise<ApiResponse<{
2483
+ url: string;
2484
+ }>>;
2419
2485
  /**
2420
2486
  * Get linked newsletter subscriptions
2421
2487
  * @param siteName - The site name
@@ -2769,7 +2835,7 @@ declare const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes;
2769
2835
  /**
2770
2836
  * Build Cloudflare Image Resizing URL
2771
2837
  *
2772
- * @param baseUrl - The base URL of your API (e.g., "https://api.perspect.co")
2838
+ * @param baseUrl - The base URL of your API (e.g., "https://api.perspect.comm")
2773
2839
  * @param mediaPath - The path to the media file (e.g., "media/site/image.jpg")
2774
2840
  * @param options - Transform options
2775
2841
  * @returns Cloudflare Image Resizing URL
@@ -2777,11 +2843,11 @@ declare const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes;
2777
2843
  * @example
2778
2844
  * ```typescript
2779
2845
  * const url = buildImageUrl(
2780
- * 'https://api.perspect.co',
2846
+ * 'https://api.perspect.comm',
2781
2847
  * 'media/mysite/photo.jpg',
2782
2848
  * { width: 400, format: 'webp', quality: 85 }
2783
2849
  * );
2784
- * // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.co/media/mysite/photo.jpg'
2850
+ * // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.comm/media/mysite/photo.jpg'
2785
2851
  * ```
2786
2852
  */
2787
2853
  declare function buildImageUrl(baseUrl: string, mediaPath: string, options?: ImageTransformOptions): string;
@@ -2791,7 +2857,7 @@ declare function buildImageUrl(baseUrl: string, mediaPath: string, options?: Ima
2791
2857
  * @example
2792
2858
  * ```typescript
2793
2859
  * const urls = generateResponsiveUrls(
2794
- * 'https://api.perspect.co',
2860
+ * 'https://api.perspect.comm',
2795
2861
  * 'media/mysite/photo.jpg'
2796
2862
  * );
2797
2863
  * // Returns: { thumbnail: '...', small: '...', medium: '...', large: '...', original: '...' }
@@ -2804,7 +2870,7 @@ declare function generateResponsiveUrls(baseUrl: string, mediaPath: string, size
2804
2870
  * @example
2805
2871
  * ```typescript
2806
2872
  * const srcset = generateSrcSet(
2807
- * 'https://api.perspect.co',
2873
+ * 'https://api.perspect.comm',
2808
2874
  * 'media/mysite/photo.jpg',
2809
2875
  * [400, 800, 1200]
2810
2876
  * );
@@ -2831,7 +2897,7 @@ declare function generateSizesAttribute(breakpoints?: Array<{
2831
2897
  * @example
2832
2898
  * ```typescript
2833
2899
  * const html = generateResponsiveImageHtml(
2834
- * 'https://api.perspect.co',
2900
+ * 'https://api.perspect.comm',
2835
2901
  * 'media/mysite/photo.jpg',
2836
2902
  * 'My photo',
2837
2903
  * { className: 'rounded-lg', loading: 'lazy' }
@@ -2857,7 +2923,7 @@ declare function generateResponsiveImageHtml(baseUrl: string, mediaPath: string,
2857
2923
  * const media = product.data.media?.[0];
2858
2924
  *
2859
2925
  * if (media) {
2860
- * const urls = transformMediaItem('https://api.perspect.co', media);
2926
+ * const urls = transformMediaItem('https://api.perspect.comm', media);
2861
2927
  * console.log(urls.thumbnail); // Cloudflare-transformed thumbnail URL
2862
2928
  * }
2863
2929
  * ```
@@ -3021,4 +3087,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
3021
3087
  error: string;
3022
3088
  }>;
3023
3089
 
3024
- export { type AddCollectionItemRequest, type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, BaseClient, type BlogPost, type BundleCollection, type BundleCollectionItem, type BundleCollectionItemWithProduct, BundlesClient, type CacheConfig, CacheManager, CategoriesClient, type Category, type CategorySummary, type CheckoutAddress, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, type CheckoutSessionTax, type CheckoutTaxBreakdownItem, type CheckoutTaxCustomerExemptionRequest, type CheckoutTaxExemptionStatus, type CheckoutTaxRequest, type CheckoutTaxStrategy, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, type ContentCategoryResponse, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateBundleCollectionRequest, type CreateBundleGroupRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, type CreditBalance, type CreditBalanceWithTransactions, type CreditTransaction, DEFAULT_IMAGE_SIZES, type GrantCreditRequest, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductBundleGroup, type ProductQueryParams, ProductsClient, type RequestOptions, type RequestOtpRequest, type ResponsiveImageSizes, type SetProfileValueRequest, type Site, type SiteUser, type SiteUserOrder, type SiteUserProfile, type SiteUserSubscription, SiteUsersClient, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type UpdateSiteUserRequest, type User, type VerifyOtpRequest, type VerifyOtpResponse, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
3090
+ export { type AddCollectionItemRequest, type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, BaseClient, type BlogPost, type BundleCollection, type BundleCollectionItem, type BundleCollectionItemWithProduct, BundlesClient, type CacheConfig, CacheManager, CategoriesClient, type Category, type CategorySummary, type CheckoutAddress, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, type CheckoutSessionTax, type CheckoutTaxBreakdownItem, type CheckoutTaxCustomerExemptionRequest, type CheckoutTaxExemptionStatus, type CheckoutTaxRequest, type CheckoutTaxStrategy, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, type ContentCategoryResponse, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateBundleCollectionRequest, type CreateBundleGroupRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, type CreditBalance, type CreditBalanceWithTransactions, type CreditTransaction, DEFAULT_IMAGE_SIZES, type GrantCreditRequest, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, type NewsletterCampaignDetail, type NewsletterCampaignListResponse, type NewsletterCampaignSummary, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductBundleGroup, type ProductQueryParams, ProductsClient, type RequestOptions, type RequestOtpRequest, type ResponsiveImageSizes, type SetProfileValueRequest, type Site, type SiteUser, type SiteUserOrder, type SiteUserProfile, type SiteUserSubscription, SiteUsersClient, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type UpdateSiteUserRequest, type User, type VerifyOtpRequest, type VerifyOtpResponse, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
package/dist/index.d.ts CHANGED
@@ -202,6 +202,34 @@ interface NewsletterList {
202
202
  is_default: boolean;
203
203
  subscriber_count?: number;
204
204
  }
205
+ interface NewsletterCampaignSummary {
206
+ id: string;
207
+ campaign_name: string;
208
+ slug: string;
209
+ slug_prefix?: string | null;
210
+ subject: string;
211
+ preview_text?: string | null;
212
+ status: string;
213
+ sent_at?: string | null;
214
+ completed_at?: string | null;
215
+ created_at: string;
216
+ updated_at: string;
217
+ }
218
+ interface NewsletterCampaignDetail extends NewsletterCampaignSummary {
219
+ markdown_content?: string | null;
220
+ html_content?: string | null;
221
+ text_content?: string | null;
222
+ excerpt?: string | null;
223
+ }
224
+ interface NewsletterCampaignListResponse {
225
+ items: NewsletterCampaignSummary[];
226
+ pagination: {
227
+ page: number;
228
+ limit: number;
229
+ total: number;
230
+ pages: number;
231
+ };
232
+ }
205
233
  interface NewsletterPreferences {
206
234
  frequency?: 'instant' | 'daily' | 'weekly' | 'monthly';
207
235
  topics?: string[];
@@ -2043,10 +2071,33 @@ declare class NewsletterClient extends BaseClient {
2043
2071
  /**
2044
2072
  * Get available newsletter lists
2045
2073
  */
2046
- getLists(siteName: string): Promise<ApiResponse<{
2074
+ getLists(siteName: string, cachePolicy?: CachePolicy): Promise<ApiResponse<{
2047
2075
  lists: NewsletterList[];
2048
2076
  total: number;
2049
2077
  }>>;
2078
+ /**
2079
+ * List publicly available (sent) newsletter campaigns
2080
+ */
2081
+ getPublishedCampaigns(siteName: string, params?: {
2082
+ page?: number;
2083
+ limit?: number;
2084
+ search?: string;
2085
+ }, cachePolicy?: CachePolicy): Promise<ApiResponse<NewsletterCampaignListResponse>>;
2086
+ /**
2087
+ * Fetch a publicly available (sent) newsletter campaign by slug
2088
+ */
2089
+ getPublishedCampaignBySlug(siteName: string, slug: string, optionsOrPolicy?: {
2090
+ slugPrefix?: string;
2091
+ } | CachePolicy, cachePolicy?: CachePolicy): Promise<ApiResponse<NewsletterCampaignDetail>>;
2092
+ /**
2093
+ * Invalidate cached published newsletter campaign data from a webhook payload.
2094
+ * Only publish events (and legacy sent aliases) trigger invalidation.
2095
+ */
2096
+ invalidatePublishedCampaignCacheFromWebhook(payload: Record<string, unknown>, fallbackSiteName?: string): Promise<{
2097
+ invalidated: boolean;
2098
+ tags: string[];
2099
+ reason?: string;
2100
+ }>;
2050
2101
  /**
2051
2102
  * Check subscription status by email
2052
2103
  */
@@ -2221,6 +2272,12 @@ declare class NewsletterClient extends BaseClient {
2221
2272
  * Client sites can use this to serve the tracking pixel response.
2222
2273
  */
2223
2274
  static getTrackingPixel(): Uint8Array;
2275
+ private buildNewsletterTags;
2276
+ private extractSlugPrefix;
2277
+ private normalizeNewsletterWebhookPayload;
2278
+ private isPublishEvent;
2279
+ private pickString;
2280
+ private isCachePolicy;
2224
2281
  }
2225
2282
 
2226
2283
  /**
@@ -2416,6 +2473,15 @@ declare class SiteUsersClient extends BaseClient {
2416
2473
  changeSubscriptionPlan(siteName: string, subscriptionId: string, productId: number, csrfToken?: string): Promise<ApiResponse<{
2417
2474
  success: boolean;
2418
2475
  }>>;
2476
+ /**
2477
+ * Create a Stripe Billing Portal session for updating payment methods
2478
+ * @param siteName - The site name
2479
+ * @param returnUrl - URL to redirect back to after portal session
2480
+ * @param csrfToken - CSRF token (required)
2481
+ */
2482
+ createBillingPortalSession(siteName: string, returnUrl: string, csrfToken?: string): Promise<ApiResponse<{
2483
+ url: string;
2484
+ }>>;
2419
2485
  /**
2420
2486
  * Get linked newsletter subscriptions
2421
2487
  * @param siteName - The site name
@@ -2769,7 +2835,7 @@ declare const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes;
2769
2835
  /**
2770
2836
  * Build Cloudflare Image Resizing URL
2771
2837
  *
2772
- * @param baseUrl - The base URL of your API (e.g., "https://api.perspect.co")
2838
+ * @param baseUrl - The base URL of your API (e.g., "https://api.perspect.comm")
2773
2839
  * @param mediaPath - The path to the media file (e.g., "media/site/image.jpg")
2774
2840
  * @param options - Transform options
2775
2841
  * @returns Cloudflare Image Resizing URL
@@ -2777,11 +2843,11 @@ declare const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes;
2777
2843
  * @example
2778
2844
  * ```typescript
2779
2845
  * const url = buildImageUrl(
2780
- * 'https://api.perspect.co',
2846
+ * 'https://api.perspect.comm',
2781
2847
  * 'media/mysite/photo.jpg',
2782
2848
  * { width: 400, format: 'webp', quality: 85 }
2783
2849
  * );
2784
- * // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.co/media/mysite/photo.jpg'
2850
+ * // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.comm/media/mysite/photo.jpg'
2785
2851
  * ```
2786
2852
  */
2787
2853
  declare function buildImageUrl(baseUrl: string, mediaPath: string, options?: ImageTransformOptions): string;
@@ -2791,7 +2857,7 @@ declare function buildImageUrl(baseUrl: string, mediaPath: string, options?: Ima
2791
2857
  * @example
2792
2858
  * ```typescript
2793
2859
  * const urls = generateResponsiveUrls(
2794
- * 'https://api.perspect.co',
2860
+ * 'https://api.perspect.comm',
2795
2861
  * 'media/mysite/photo.jpg'
2796
2862
  * );
2797
2863
  * // Returns: { thumbnail: '...', small: '...', medium: '...', large: '...', original: '...' }
@@ -2804,7 +2870,7 @@ declare function generateResponsiveUrls(baseUrl: string, mediaPath: string, size
2804
2870
  * @example
2805
2871
  * ```typescript
2806
2872
  * const srcset = generateSrcSet(
2807
- * 'https://api.perspect.co',
2873
+ * 'https://api.perspect.comm',
2808
2874
  * 'media/mysite/photo.jpg',
2809
2875
  * [400, 800, 1200]
2810
2876
  * );
@@ -2831,7 +2897,7 @@ declare function generateSizesAttribute(breakpoints?: Array<{
2831
2897
  * @example
2832
2898
  * ```typescript
2833
2899
  * const html = generateResponsiveImageHtml(
2834
- * 'https://api.perspect.co',
2900
+ * 'https://api.perspect.comm',
2835
2901
  * 'media/mysite/photo.jpg',
2836
2902
  * 'My photo',
2837
2903
  * { className: 'rounded-lg', loading: 'lazy' }
@@ -2857,7 +2923,7 @@ declare function generateResponsiveImageHtml(baseUrl: string, mediaPath: string,
2857
2923
  * const media = product.data.media?.[0];
2858
2924
  *
2859
2925
  * if (media) {
2860
- * const urls = transformMediaItem('https://api.perspect.co', media);
2926
+ * const urls = transformMediaItem('https://api.perspect.comm', media);
2861
2927
  * console.log(urls.thumbnail); // Cloudflare-transformed thumbnail URL
2862
2928
  * }
2863
2929
  * ```
@@ -3021,4 +3087,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
3021
3087
  error: string;
3022
3088
  }>;
3023
3089
 
3024
- export { type AddCollectionItemRequest, type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, BaseClient, type BlogPost, type BundleCollection, type BundleCollectionItem, type BundleCollectionItemWithProduct, BundlesClient, type CacheConfig, CacheManager, CategoriesClient, type Category, type CategorySummary, type CheckoutAddress, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, type CheckoutSessionTax, type CheckoutTaxBreakdownItem, type CheckoutTaxCustomerExemptionRequest, type CheckoutTaxExemptionStatus, type CheckoutTaxRequest, type CheckoutTaxStrategy, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, type ContentCategoryResponse, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateBundleCollectionRequest, type CreateBundleGroupRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, type CreditBalance, type CreditBalanceWithTransactions, type CreditTransaction, DEFAULT_IMAGE_SIZES, type GrantCreditRequest, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductBundleGroup, type ProductQueryParams, ProductsClient, type RequestOptions, type RequestOtpRequest, type ResponsiveImageSizes, type SetProfileValueRequest, type Site, type SiteUser, type SiteUserOrder, type SiteUserProfile, type SiteUserSubscription, SiteUsersClient, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type UpdateSiteUserRequest, type User, type VerifyOtpRequest, type VerifyOtpResponse, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
3090
+ export { type AddCollectionItemRequest, type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, BaseClient, type BlogPost, type BundleCollection, type BundleCollectionItem, type BundleCollectionItemWithProduct, BundlesClient, type CacheConfig, CacheManager, CategoriesClient, type Category, type CategorySummary, type CheckoutAddress, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, type CheckoutSessionTax, type CheckoutTaxBreakdownItem, type CheckoutTaxCustomerExemptionRequest, type CheckoutTaxExemptionStatus, type CheckoutTaxRequest, type CheckoutTaxStrategy, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, type ContentCategoryResponse, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateBundleCollectionRequest, type CreateBundleGroupRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, type CreditBalance, type CreditBalanceWithTransactions, type CreditTransaction, DEFAULT_IMAGE_SIZES, type GrantCreditRequest, HttpClient, type HttpMethod, type ImageTransformOptions, InMemoryCacheAdapter, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, type NewsletterCampaignDetail, type NewsletterCampaignListResponse, type NewsletterCampaignSummary, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, NoopCacheAdapter, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductBundleGroup, type ProductQueryParams, ProductsClient, type RequestOptions, type RequestOtpRequest, type ResponsiveImageSizes, type SetProfileValueRequest, type Site, type SiteUser, type SiteUserOrder, type SiteUserProfile, type SiteUserSubscription, SiteUsersClient, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type UpdateSiteUserRequest, type User, type VerifyOtpRequest, type VerifyOtpResponse, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
package/dist/index.js CHANGED
@@ -2066,11 +2066,123 @@ var NewsletterClient = class extends BaseClient {
2066
2066
  /**
2067
2067
  * Get available newsletter lists
2068
2068
  */
2069
- async getLists(siteName) {
2070
- return this.getSingle(
2071
- this.newsletterEndpoint(siteName, "/newsletter/lists")
2069
+ async getLists(siteName, cachePolicy) {
2070
+ const endpoint = this.newsletterEndpoint(siteName, "/newsletter/lists");
2071
+ const path = this.buildPath(endpoint);
2072
+ return this.fetchWithCache(
2073
+ endpoint,
2074
+ void 0,
2075
+ this.buildNewsletterTags(siteName, [
2076
+ "newsletter:lists",
2077
+ `newsletter:lists:site:${siteName}`
2078
+ ]),
2079
+ cachePolicy,
2080
+ () => this.http.get(path)
2072
2081
  );
2073
2082
  }
2083
+ /**
2084
+ * List publicly available (sent) newsletter campaigns
2085
+ */
2086
+ async getPublishedCampaigns(siteName, params, cachePolicy) {
2087
+ const endpoint = this.newsletterEndpoint(siteName, "/newsletter/campaigns");
2088
+ const path = this.buildPath(endpoint);
2089
+ const normalizedParams = params ? { ...params } : void 0;
2090
+ if (normalizedParams) {
2091
+ const validatedLimit = validateOptionalLimit(
2092
+ normalizedParams.limit,
2093
+ "newsletter campaigns query"
2094
+ );
2095
+ if (validatedLimit !== void 0) {
2096
+ normalizedParams.limit = validatedLimit;
2097
+ } else {
2098
+ delete normalizedParams.limit;
2099
+ }
2100
+ if (typeof normalizedParams.search === "string" && normalizedParams.search.trim().length === 0) {
2101
+ delete normalizedParams.search;
2102
+ }
2103
+ }
2104
+ return this.fetchWithCache(
2105
+ endpoint,
2106
+ normalizedParams,
2107
+ this.buildNewsletterTags(siteName, [
2108
+ "newsletter:campaigns",
2109
+ `newsletter:campaigns:list:${siteName}`
2110
+ ]),
2111
+ cachePolicy,
2112
+ () => this.http.get(path, normalizedParams)
2113
+ );
2114
+ }
2115
+ /**
2116
+ * Fetch a publicly available (sent) newsletter campaign by slug
2117
+ */
2118
+ async getPublishedCampaignBySlug(siteName, slug, optionsOrPolicy, cachePolicy) {
2119
+ let normalizedSlug = slug.trim();
2120
+ if (!normalizedSlug) {
2121
+ throw new Error("slug is required");
2122
+ }
2123
+ const isCachePolicyArg = this.isCachePolicy(optionsOrPolicy);
2124
+ const providedSlugPrefix = !isCachePolicyArg && optionsOrPolicy ? this.pickString(optionsOrPolicy, ["slugPrefix", "slug_prefix"]) : void 0;
2125
+ const resolvedCachePolicy = isCachePolicyArg ? optionsOrPolicy : cachePolicy;
2126
+ let slugPrefix = providedSlugPrefix;
2127
+ if (!slugPrefix) {
2128
+ const parts = normalizedSlug.split("/").filter(Boolean);
2129
+ if (parts.length > 1) {
2130
+ slugPrefix = parts.slice(0, -1).join("/");
2131
+ normalizedSlug = parts[parts.length - 1];
2132
+ }
2133
+ }
2134
+ normalizedSlug = normalizedSlug.trim();
2135
+ if (!normalizedSlug) {
2136
+ throw new Error("slug is required");
2137
+ }
2138
+ const endpoint = this.newsletterEndpoint(
2139
+ siteName,
2140
+ `/newsletter/campaigns/${encodeURIComponent(normalizedSlug)}`
2141
+ );
2142
+ const path = this.buildPath(endpoint);
2143
+ const queryParams = slugPrefix ? { slug_prefix: slugPrefix } : void 0;
2144
+ return this.fetchWithCache(
2145
+ endpoint,
2146
+ queryParams,
2147
+ this.buildNewsletterTags(
2148
+ siteName,
2149
+ [
2150
+ "newsletter:campaigns",
2151
+ `newsletter:campaigns:slug:${siteName}:${normalizedSlug}`,
2152
+ `newsletter:campaigns:detail:${siteName}`
2153
+ ],
2154
+ slugPrefix
2155
+ ),
2156
+ resolvedCachePolicy,
2157
+ () => this.http.get(path, queryParams)
2158
+ );
2159
+ }
2160
+ /**
2161
+ * Invalidate cached published newsletter campaign data from a webhook payload.
2162
+ * Only publish events (and legacy sent aliases) trigger invalidation.
2163
+ */
2164
+ async invalidatePublishedCampaignCacheFromWebhook(payload, fallbackSiteName) {
2165
+ const normalized = this.normalizeNewsletterWebhookPayload(payload, fallbackSiteName);
2166
+ if (!normalized.shouldInvalidate) {
2167
+ return {
2168
+ invalidated: false,
2169
+ tags: [],
2170
+ reason: normalized.reason || "event_not_publish_related"
2171
+ };
2172
+ }
2173
+ const tags = this.buildNewsletterTags(
2174
+ normalized.siteName,
2175
+ [
2176
+ "newsletter:campaigns",
2177
+ `newsletter:campaigns:list:${normalized.siteName}`,
2178
+ normalized.slug ? `newsletter:campaigns:slug:${normalized.siteName}:${normalized.slug}` : "",
2179
+ normalized.campaignId ? `newsletter:campaigns:id:${normalized.siteName}:${normalized.campaignId}` : ""
2180
+ ],
2181
+ normalized.slugPrefix
2182
+ );
2183
+ await this.invalidateCache({ tags });
2184
+ return { invalidated: true, tags };
2185
+ }
2074
2186
  /**
2075
2187
  * Check subscription status by email
2076
2188
  */
@@ -2266,6 +2378,107 @@ var NewsletterClient = class extends BaseClient {
2266
2378
  59
2267
2379
  ]);
2268
2380
  }
2381
+ buildNewsletterTags(siteName, extraTags = [], slugPrefix) {
2382
+ const tags = /* @__PURE__ */ new Set(["newsletter"]);
2383
+ if (siteName) {
2384
+ tags.add(`newsletter:site:${siteName}`);
2385
+ }
2386
+ if (slugPrefix) {
2387
+ tags.add(`newsletter:prefix:${slugPrefix}`);
2388
+ }
2389
+ extraTags.filter(Boolean).forEach((tag) => tags.add(tag));
2390
+ return Array.from(tags.values());
2391
+ }
2392
+ extractSlugPrefix(slug) {
2393
+ const normalized = slug.trim();
2394
+ if (!normalized.includes("/")) {
2395
+ return void 0;
2396
+ }
2397
+ const [prefix] = normalized.split("/").filter(Boolean);
2398
+ return prefix || void 0;
2399
+ }
2400
+ normalizeNewsletterWebhookPayload(payload, fallbackSiteName) {
2401
+ const eventType = (this.pickString(payload, ["event_type", "type"]) || "").toLowerCase();
2402
+ const dataRaw = payload.data;
2403
+ const data = dataRaw && typeof dataRaw === "object" && !Array.isArray(dataRaw) ? dataRaw : payload;
2404
+ const originType = (this.pickString(data, ["origin_type", "originType"]) || "").toLowerCase();
2405
+ const status = (this.pickString(data, ["status", "campaign_status", "campaignStatus"]) || "").toLowerCase();
2406
+ if (!this.isPublishEvent(eventType, status, originType)) {
2407
+ return {
2408
+ shouldInvalidate: false,
2409
+ reason: "not_publish_or_sent_event",
2410
+ siteName: fallbackSiteName || ""
2411
+ };
2412
+ }
2413
+ const siteName = this.pickString(payload, ["site", "site_name", "siteName"]) || this.pickString(data, ["site", "site_name", "siteName"]) || fallbackSiteName || "";
2414
+ if (!siteName) {
2415
+ return {
2416
+ shouldInvalidate: false,
2417
+ reason: "missing_site_name",
2418
+ siteName: ""
2419
+ };
2420
+ }
2421
+ let slug = this.pickString(data, ["slug", "canonical_slug", "canonicalSlug"]) || this.pickString(payload, ["slug", "canonical_slug", "canonicalSlug"]) || void 0;
2422
+ let slugPrefix = this.pickString(data, ["slug_prefix", "slugPrefix"]) || this.pickString(payload, ["slug_prefix", "slugPrefix"]) || (slug ? this.extractSlugPrefix(slug) : void 0);
2423
+ if (slug) {
2424
+ const slugParts = slug.split("/").filter(Boolean);
2425
+ if (slugParts.length > 1) {
2426
+ if (!slugPrefix) {
2427
+ slugPrefix = slugParts.slice(0, -1).join("/");
2428
+ }
2429
+ slug = slugParts[slugParts.length - 1];
2430
+ }
2431
+ }
2432
+ const campaignId = this.pickString(data, ["campaign_id", "campaignId"]) || this.pickString(payload, ["campaign_id", "campaignId"]) || void 0;
2433
+ return {
2434
+ shouldInvalidate: true,
2435
+ siteName,
2436
+ slug,
2437
+ slugPrefix,
2438
+ campaignId
2439
+ };
2440
+ }
2441
+ isPublishEvent(eventType, status, originType) {
2442
+ const knownPublishEvents = /* @__PURE__ */ new Set([
2443
+ "newsletter.published",
2444
+ "newsletter.sent",
2445
+ "newsletter_campaign.published",
2446
+ "newsletter_campaign.sent",
2447
+ "newsletter.campaign.published",
2448
+ "newsletter.campaign.sent",
2449
+ "campaign.published",
2450
+ "campaign.sent"
2451
+ ]);
2452
+ if (knownPublishEvents.has(eventType)) {
2453
+ return true;
2454
+ }
2455
+ if (eventType === "content.published" && originType === "newsletter_campaign") {
2456
+ return true;
2457
+ }
2458
+ if ((eventType.includes("newsletter") || eventType.includes("campaign")) && eventType.endsWith(".updated") && (status === "published" || status === "sent")) {
2459
+ return true;
2460
+ }
2461
+ return false;
2462
+ }
2463
+ pickString(source, keys) {
2464
+ if (!source) {
2465
+ return void 0;
2466
+ }
2467
+ for (const key of keys) {
2468
+ const value = source[key];
2469
+ if (typeof value === "string" && value.trim().length > 0) {
2470
+ return value.trim();
2471
+ }
2472
+ }
2473
+ return void 0;
2474
+ }
2475
+ isCachePolicy(value) {
2476
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2477
+ return false;
2478
+ }
2479
+ const record = value;
2480
+ return "ttlSeconds" in record || "tags" in record || "metadata" in record || "skipCache" in record;
2481
+ }
2269
2482
  };
2270
2483
 
2271
2484
  // src/client/site-users-client.ts
@@ -2529,6 +2742,19 @@ var SiteUsersClient = class extends BaseClient {
2529
2742
  csrfToken
2530
2743
  );
2531
2744
  }
2745
+ /**
2746
+ * Create a Stripe Billing Portal session for updating payment methods
2747
+ * @param siteName - The site name
2748
+ * @param returnUrl - URL to redirect back to after portal session
2749
+ * @param csrfToken - CSRF token (required)
2750
+ */
2751
+ async createBillingPortalSession(siteName, returnUrl, csrfToken) {
2752
+ return this.create(
2753
+ this.siteUserEndpoint(siteName, "/users/me/billing-portal"),
2754
+ { return_url: returnUrl },
2755
+ csrfToken
2756
+ );
2757
+ }
2532
2758
  /**
2533
2759
  * Get linked newsletter subscriptions
2534
2760
  * @param siteName - The site name