perspectapi-ts-sdk 3.5.1 → 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 +29 -1
- package/dist/index.d.mts +59 -2
- package/dist/index.d.ts +59 -2
- package/dist/index.js +216 -3
- package/dist/index.mjs +216 -3
- package/package.json +1 -1
- package/src/client/newsletter-client.ts +343 -3
- package/src/index.ts +3 -0
- package/src/types/index.ts +31 -0
package/README.md
CHANGED
|
@@ -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>`, `
|
|
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
|
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
|
/**
|
|
@@ -3030,4 +3087,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
3030
3087
|
error: string;
|
|
3031
3088
|
}>;
|
|
3032
3089
|
|
|
3033
|
-
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
|
/**
|
|
@@ -3030,4 +3087,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
3030
3087
|
error: string;
|
|
3031
3088
|
}>;
|
|
3032
3089
|
|
|
3033
|
-
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
|
-
|
|
2071
|
-
|
|
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
|
package/dist/index.mjs
CHANGED
|
@@ -2003,11 +2003,123 @@ var NewsletterClient = class extends BaseClient {
|
|
|
2003
2003
|
/**
|
|
2004
2004
|
* Get available newsletter lists
|
|
2005
2005
|
*/
|
|
2006
|
-
async getLists(siteName) {
|
|
2007
|
-
|
|
2008
|
-
|
|
2006
|
+
async getLists(siteName, cachePolicy) {
|
|
2007
|
+
const endpoint = this.newsletterEndpoint(siteName, "/newsletter/lists");
|
|
2008
|
+
const path = this.buildPath(endpoint);
|
|
2009
|
+
return this.fetchWithCache(
|
|
2010
|
+
endpoint,
|
|
2011
|
+
void 0,
|
|
2012
|
+
this.buildNewsletterTags(siteName, [
|
|
2013
|
+
"newsletter:lists",
|
|
2014
|
+
`newsletter:lists:site:${siteName}`
|
|
2015
|
+
]),
|
|
2016
|
+
cachePolicy,
|
|
2017
|
+
() => this.http.get(path)
|
|
2009
2018
|
);
|
|
2010
2019
|
}
|
|
2020
|
+
/**
|
|
2021
|
+
* List publicly available (sent) newsletter campaigns
|
|
2022
|
+
*/
|
|
2023
|
+
async getPublishedCampaigns(siteName, params, cachePolicy) {
|
|
2024
|
+
const endpoint = this.newsletterEndpoint(siteName, "/newsletter/campaigns");
|
|
2025
|
+
const path = this.buildPath(endpoint);
|
|
2026
|
+
const normalizedParams = params ? { ...params } : void 0;
|
|
2027
|
+
if (normalizedParams) {
|
|
2028
|
+
const validatedLimit = validateOptionalLimit(
|
|
2029
|
+
normalizedParams.limit,
|
|
2030
|
+
"newsletter campaigns query"
|
|
2031
|
+
);
|
|
2032
|
+
if (validatedLimit !== void 0) {
|
|
2033
|
+
normalizedParams.limit = validatedLimit;
|
|
2034
|
+
} else {
|
|
2035
|
+
delete normalizedParams.limit;
|
|
2036
|
+
}
|
|
2037
|
+
if (typeof normalizedParams.search === "string" && normalizedParams.search.trim().length === 0) {
|
|
2038
|
+
delete normalizedParams.search;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return this.fetchWithCache(
|
|
2042
|
+
endpoint,
|
|
2043
|
+
normalizedParams,
|
|
2044
|
+
this.buildNewsletterTags(siteName, [
|
|
2045
|
+
"newsletter:campaigns",
|
|
2046
|
+
`newsletter:campaigns:list:${siteName}`
|
|
2047
|
+
]),
|
|
2048
|
+
cachePolicy,
|
|
2049
|
+
() => this.http.get(path, normalizedParams)
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Fetch a publicly available (sent) newsletter campaign by slug
|
|
2054
|
+
*/
|
|
2055
|
+
async getPublishedCampaignBySlug(siteName, slug, optionsOrPolicy, cachePolicy) {
|
|
2056
|
+
let normalizedSlug = slug.trim();
|
|
2057
|
+
if (!normalizedSlug) {
|
|
2058
|
+
throw new Error("slug is required");
|
|
2059
|
+
}
|
|
2060
|
+
const isCachePolicyArg = this.isCachePolicy(optionsOrPolicy);
|
|
2061
|
+
const providedSlugPrefix = !isCachePolicyArg && optionsOrPolicy ? this.pickString(optionsOrPolicy, ["slugPrefix", "slug_prefix"]) : void 0;
|
|
2062
|
+
const resolvedCachePolicy = isCachePolicyArg ? optionsOrPolicy : cachePolicy;
|
|
2063
|
+
let slugPrefix = providedSlugPrefix;
|
|
2064
|
+
if (!slugPrefix) {
|
|
2065
|
+
const parts = normalizedSlug.split("/").filter(Boolean);
|
|
2066
|
+
if (parts.length > 1) {
|
|
2067
|
+
slugPrefix = parts.slice(0, -1).join("/");
|
|
2068
|
+
normalizedSlug = parts[parts.length - 1];
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
normalizedSlug = normalizedSlug.trim();
|
|
2072
|
+
if (!normalizedSlug) {
|
|
2073
|
+
throw new Error("slug is required");
|
|
2074
|
+
}
|
|
2075
|
+
const endpoint = this.newsletterEndpoint(
|
|
2076
|
+
siteName,
|
|
2077
|
+
`/newsletter/campaigns/${encodeURIComponent(normalizedSlug)}`
|
|
2078
|
+
);
|
|
2079
|
+
const path = this.buildPath(endpoint);
|
|
2080
|
+
const queryParams = slugPrefix ? { slug_prefix: slugPrefix } : void 0;
|
|
2081
|
+
return this.fetchWithCache(
|
|
2082
|
+
endpoint,
|
|
2083
|
+
queryParams,
|
|
2084
|
+
this.buildNewsletterTags(
|
|
2085
|
+
siteName,
|
|
2086
|
+
[
|
|
2087
|
+
"newsletter:campaigns",
|
|
2088
|
+
`newsletter:campaigns:slug:${siteName}:${normalizedSlug}`,
|
|
2089
|
+
`newsletter:campaigns:detail:${siteName}`
|
|
2090
|
+
],
|
|
2091
|
+
slugPrefix
|
|
2092
|
+
),
|
|
2093
|
+
resolvedCachePolicy,
|
|
2094
|
+
() => this.http.get(path, queryParams)
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Invalidate cached published newsletter campaign data from a webhook payload.
|
|
2099
|
+
* Only publish events (and legacy sent aliases) trigger invalidation.
|
|
2100
|
+
*/
|
|
2101
|
+
async invalidatePublishedCampaignCacheFromWebhook(payload, fallbackSiteName) {
|
|
2102
|
+
const normalized = this.normalizeNewsletterWebhookPayload(payload, fallbackSiteName);
|
|
2103
|
+
if (!normalized.shouldInvalidate) {
|
|
2104
|
+
return {
|
|
2105
|
+
invalidated: false,
|
|
2106
|
+
tags: [],
|
|
2107
|
+
reason: normalized.reason || "event_not_publish_related"
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
const tags = this.buildNewsletterTags(
|
|
2111
|
+
normalized.siteName,
|
|
2112
|
+
[
|
|
2113
|
+
"newsletter:campaigns",
|
|
2114
|
+
`newsletter:campaigns:list:${normalized.siteName}`,
|
|
2115
|
+
normalized.slug ? `newsletter:campaigns:slug:${normalized.siteName}:${normalized.slug}` : "",
|
|
2116
|
+
normalized.campaignId ? `newsletter:campaigns:id:${normalized.siteName}:${normalized.campaignId}` : ""
|
|
2117
|
+
],
|
|
2118
|
+
normalized.slugPrefix
|
|
2119
|
+
);
|
|
2120
|
+
await this.invalidateCache({ tags });
|
|
2121
|
+
return { invalidated: true, tags };
|
|
2122
|
+
}
|
|
2011
2123
|
/**
|
|
2012
2124
|
* Check subscription status by email
|
|
2013
2125
|
*/
|
|
@@ -2203,6 +2315,107 @@ var NewsletterClient = class extends BaseClient {
|
|
|
2203
2315
|
59
|
|
2204
2316
|
]);
|
|
2205
2317
|
}
|
|
2318
|
+
buildNewsletterTags(siteName, extraTags = [], slugPrefix) {
|
|
2319
|
+
const tags = /* @__PURE__ */ new Set(["newsletter"]);
|
|
2320
|
+
if (siteName) {
|
|
2321
|
+
tags.add(`newsletter:site:${siteName}`);
|
|
2322
|
+
}
|
|
2323
|
+
if (slugPrefix) {
|
|
2324
|
+
tags.add(`newsletter:prefix:${slugPrefix}`);
|
|
2325
|
+
}
|
|
2326
|
+
extraTags.filter(Boolean).forEach((tag) => tags.add(tag));
|
|
2327
|
+
return Array.from(tags.values());
|
|
2328
|
+
}
|
|
2329
|
+
extractSlugPrefix(slug) {
|
|
2330
|
+
const normalized = slug.trim();
|
|
2331
|
+
if (!normalized.includes("/")) {
|
|
2332
|
+
return void 0;
|
|
2333
|
+
}
|
|
2334
|
+
const [prefix] = normalized.split("/").filter(Boolean);
|
|
2335
|
+
return prefix || void 0;
|
|
2336
|
+
}
|
|
2337
|
+
normalizeNewsletterWebhookPayload(payload, fallbackSiteName) {
|
|
2338
|
+
const eventType = (this.pickString(payload, ["event_type", "type"]) || "").toLowerCase();
|
|
2339
|
+
const dataRaw = payload.data;
|
|
2340
|
+
const data = dataRaw && typeof dataRaw === "object" && !Array.isArray(dataRaw) ? dataRaw : payload;
|
|
2341
|
+
const originType = (this.pickString(data, ["origin_type", "originType"]) || "").toLowerCase();
|
|
2342
|
+
const status = (this.pickString(data, ["status", "campaign_status", "campaignStatus"]) || "").toLowerCase();
|
|
2343
|
+
if (!this.isPublishEvent(eventType, status, originType)) {
|
|
2344
|
+
return {
|
|
2345
|
+
shouldInvalidate: false,
|
|
2346
|
+
reason: "not_publish_or_sent_event",
|
|
2347
|
+
siteName: fallbackSiteName || ""
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
const siteName = this.pickString(payload, ["site", "site_name", "siteName"]) || this.pickString(data, ["site", "site_name", "siteName"]) || fallbackSiteName || "";
|
|
2351
|
+
if (!siteName) {
|
|
2352
|
+
return {
|
|
2353
|
+
shouldInvalidate: false,
|
|
2354
|
+
reason: "missing_site_name",
|
|
2355
|
+
siteName: ""
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
let slug = this.pickString(data, ["slug", "canonical_slug", "canonicalSlug"]) || this.pickString(payload, ["slug", "canonical_slug", "canonicalSlug"]) || void 0;
|
|
2359
|
+
let slugPrefix = this.pickString(data, ["slug_prefix", "slugPrefix"]) || this.pickString(payload, ["slug_prefix", "slugPrefix"]) || (slug ? this.extractSlugPrefix(slug) : void 0);
|
|
2360
|
+
if (slug) {
|
|
2361
|
+
const slugParts = slug.split("/").filter(Boolean);
|
|
2362
|
+
if (slugParts.length > 1) {
|
|
2363
|
+
if (!slugPrefix) {
|
|
2364
|
+
slugPrefix = slugParts.slice(0, -1).join("/");
|
|
2365
|
+
}
|
|
2366
|
+
slug = slugParts[slugParts.length - 1];
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
const campaignId = this.pickString(data, ["campaign_id", "campaignId"]) || this.pickString(payload, ["campaign_id", "campaignId"]) || void 0;
|
|
2370
|
+
return {
|
|
2371
|
+
shouldInvalidate: true,
|
|
2372
|
+
siteName,
|
|
2373
|
+
slug,
|
|
2374
|
+
slugPrefix,
|
|
2375
|
+
campaignId
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
isPublishEvent(eventType, status, originType) {
|
|
2379
|
+
const knownPublishEvents = /* @__PURE__ */ new Set([
|
|
2380
|
+
"newsletter.published",
|
|
2381
|
+
"newsletter.sent",
|
|
2382
|
+
"newsletter_campaign.published",
|
|
2383
|
+
"newsletter_campaign.sent",
|
|
2384
|
+
"newsletter.campaign.published",
|
|
2385
|
+
"newsletter.campaign.sent",
|
|
2386
|
+
"campaign.published",
|
|
2387
|
+
"campaign.sent"
|
|
2388
|
+
]);
|
|
2389
|
+
if (knownPublishEvents.has(eventType)) {
|
|
2390
|
+
return true;
|
|
2391
|
+
}
|
|
2392
|
+
if (eventType === "content.published" && originType === "newsletter_campaign") {
|
|
2393
|
+
return true;
|
|
2394
|
+
}
|
|
2395
|
+
if ((eventType.includes("newsletter") || eventType.includes("campaign")) && eventType.endsWith(".updated") && (status === "published" || status === "sent")) {
|
|
2396
|
+
return true;
|
|
2397
|
+
}
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
pickString(source, keys) {
|
|
2401
|
+
if (!source) {
|
|
2402
|
+
return void 0;
|
|
2403
|
+
}
|
|
2404
|
+
for (const key of keys) {
|
|
2405
|
+
const value = source[key];
|
|
2406
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
2407
|
+
return value.trim();
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
return void 0;
|
|
2411
|
+
}
|
|
2412
|
+
isCachePolicy(value) {
|
|
2413
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2414
|
+
return false;
|
|
2415
|
+
}
|
|
2416
|
+
const record = value;
|
|
2417
|
+
return "ttlSeconds" in record || "tags" in record || "metadata" in record || "skipCache" in record;
|
|
2418
|
+
}
|
|
2206
2419
|
};
|
|
2207
2420
|
|
|
2208
2421
|
// src/client/site-users-client.ts
|
package/package.json
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { BaseClient } from './base-client';
|
|
6
6
|
import type { CacheManager } from '../cache/cache-manager';
|
|
7
|
+
import type { CachePolicy } from '../cache/types';
|
|
7
8
|
import type {
|
|
8
9
|
NewsletterSubscription,
|
|
9
10
|
CreateNewsletterSubscriptionRequest,
|
|
10
11
|
NewsletterList,
|
|
12
|
+
NewsletterCampaignDetail,
|
|
13
|
+
NewsletterCampaignListResponse,
|
|
11
14
|
NewsletterPreferences,
|
|
12
15
|
NewsletterStatusResponse,
|
|
13
16
|
NewsletterSubscribeResponse,
|
|
@@ -17,6 +20,7 @@ import type {
|
|
|
17
20
|
PaginatedResponse,
|
|
18
21
|
ApiResponse,
|
|
19
22
|
} from '../types';
|
|
23
|
+
import { validateOptionalLimit } from '../utils/validators';
|
|
20
24
|
|
|
21
25
|
export class NewsletterClient extends BaseClient {
|
|
22
26
|
constructor(http: any, cache?: CacheManager) {
|
|
@@ -119,15 +123,174 @@ export class NewsletterClient extends BaseClient {
|
|
|
119
123
|
/**
|
|
120
124
|
* Get available newsletter lists
|
|
121
125
|
*/
|
|
122
|
-
async getLists(
|
|
126
|
+
async getLists(
|
|
127
|
+
siteName: string,
|
|
128
|
+
cachePolicy?: CachePolicy
|
|
129
|
+
): Promise<ApiResponse<{
|
|
123
130
|
lists: NewsletterList[];
|
|
124
131
|
total: number;
|
|
125
132
|
}>> {
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
const endpoint = this.newsletterEndpoint(siteName, '/newsletter/lists');
|
|
134
|
+
const path = this.buildPath(endpoint);
|
|
135
|
+
|
|
136
|
+
return this.fetchWithCache<ApiResponse<{
|
|
137
|
+
lists: NewsletterList[];
|
|
138
|
+
total: number;
|
|
139
|
+
}>>(
|
|
140
|
+
endpoint,
|
|
141
|
+
undefined,
|
|
142
|
+
this.buildNewsletterTags(siteName, [
|
|
143
|
+
'newsletter:lists',
|
|
144
|
+
`newsletter:lists:site:${siteName}`,
|
|
145
|
+
]),
|
|
146
|
+
cachePolicy,
|
|
147
|
+
() => this.http.get(path)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* List publicly available (sent) newsletter campaigns
|
|
153
|
+
*/
|
|
154
|
+
async getPublishedCampaigns(
|
|
155
|
+
siteName: string,
|
|
156
|
+
params?: {
|
|
157
|
+
page?: number;
|
|
158
|
+
limit?: number;
|
|
159
|
+
search?: string;
|
|
160
|
+
},
|
|
161
|
+
cachePolicy?: CachePolicy
|
|
162
|
+
): Promise<ApiResponse<NewsletterCampaignListResponse>> {
|
|
163
|
+
const endpoint = this.newsletterEndpoint(siteName, '/newsletter/campaigns');
|
|
164
|
+
const path = this.buildPath(endpoint);
|
|
165
|
+
const normalizedParams = params ? { ...params } : undefined;
|
|
166
|
+
|
|
167
|
+
if (normalizedParams) {
|
|
168
|
+
const validatedLimit = validateOptionalLimit(
|
|
169
|
+
normalizedParams.limit,
|
|
170
|
+
'newsletter campaigns query',
|
|
171
|
+
);
|
|
172
|
+
if (validatedLimit !== undefined) {
|
|
173
|
+
normalizedParams.limit = validatedLimit;
|
|
174
|
+
} else {
|
|
175
|
+
delete normalizedParams.limit;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
typeof normalizedParams.search === 'string' &&
|
|
180
|
+
normalizedParams.search.trim().length === 0
|
|
181
|
+
) {
|
|
182
|
+
delete normalizedParams.search;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this.fetchWithCache<ApiResponse<NewsletterCampaignListResponse>>(
|
|
187
|
+
endpoint,
|
|
188
|
+
normalizedParams,
|
|
189
|
+
this.buildNewsletterTags(siteName, [
|
|
190
|
+
'newsletter:campaigns',
|
|
191
|
+
`newsletter:campaigns:list:${siteName}`,
|
|
192
|
+
]),
|
|
193
|
+
cachePolicy,
|
|
194
|
+
() => this.http.get<NewsletterCampaignListResponse>(path, normalizedParams)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Fetch a publicly available (sent) newsletter campaign by slug
|
|
200
|
+
*/
|
|
201
|
+
async getPublishedCampaignBySlug(
|
|
202
|
+
siteName: string,
|
|
203
|
+
slug: string,
|
|
204
|
+
optionsOrPolicy?: { slugPrefix?: string } | CachePolicy,
|
|
205
|
+
cachePolicy?: CachePolicy
|
|
206
|
+
): Promise<ApiResponse<NewsletterCampaignDetail>> {
|
|
207
|
+
let normalizedSlug = slug.trim();
|
|
208
|
+
if (!normalizedSlug) {
|
|
209
|
+
throw new Error('slug is required');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const isCachePolicyArg = this.isCachePolicy(optionsOrPolicy);
|
|
213
|
+
const providedSlugPrefix =
|
|
214
|
+
!isCachePolicyArg && optionsOrPolicy
|
|
215
|
+
? this.pickString(optionsOrPolicy as Record<string, unknown>, ['slugPrefix', 'slug_prefix'])
|
|
216
|
+
: undefined;
|
|
217
|
+
const resolvedCachePolicy = isCachePolicyArg
|
|
218
|
+
? (optionsOrPolicy as CachePolicy)
|
|
219
|
+
: cachePolicy;
|
|
220
|
+
|
|
221
|
+
let slugPrefix = providedSlugPrefix;
|
|
222
|
+
if (!slugPrefix) {
|
|
223
|
+
const parts = normalizedSlug.split('/').filter(Boolean);
|
|
224
|
+
if (parts.length > 1) {
|
|
225
|
+
slugPrefix = parts.slice(0, -1).join('/');
|
|
226
|
+
normalizedSlug = parts[parts.length - 1];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
normalizedSlug = normalizedSlug.trim();
|
|
230
|
+
if (!normalizedSlug) {
|
|
231
|
+
throw new Error('slug is required');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const endpoint = this.newsletterEndpoint(
|
|
235
|
+
siteName,
|
|
236
|
+
`/newsletter/campaigns/${encodeURIComponent(normalizedSlug)}`
|
|
237
|
+
);
|
|
238
|
+
const path = this.buildPath(endpoint);
|
|
239
|
+
const queryParams = slugPrefix ? { slug_prefix: slugPrefix } : undefined;
|
|
240
|
+
|
|
241
|
+
return this.fetchWithCache<ApiResponse<NewsletterCampaignDetail>>(
|
|
242
|
+
endpoint,
|
|
243
|
+
queryParams,
|
|
244
|
+
this.buildNewsletterTags(
|
|
245
|
+
siteName,
|
|
246
|
+
[
|
|
247
|
+
'newsletter:campaigns',
|
|
248
|
+
`newsletter:campaigns:slug:${siteName}:${normalizedSlug}`,
|
|
249
|
+
`newsletter:campaigns:detail:${siteName}`,
|
|
250
|
+
],
|
|
251
|
+
slugPrefix
|
|
252
|
+
),
|
|
253
|
+
resolvedCachePolicy,
|
|
254
|
+
() => this.http.get<NewsletterCampaignDetail>(path, queryParams)
|
|
128
255
|
);
|
|
129
256
|
}
|
|
130
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Invalidate cached published newsletter campaign data from a webhook payload.
|
|
260
|
+
* Only publish events (and legacy sent aliases) trigger invalidation.
|
|
261
|
+
*/
|
|
262
|
+
async invalidatePublishedCampaignCacheFromWebhook(
|
|
263
|
+
payload: Record<string, unknown>,
|
|
264
|
+
fallbackSiteName?: string,
|
|
265
|
+
): Promise<{ invalidated: boolean; tags: string[]; reason?: string }> {
|
|
266
|
+
const normalized = this.normalizeNewsletterWebhookPayload(payload, fallbackSiteName);
|
|
267
|
+
if (!normalized.shouldInvalidate) {
|
|
268
|
+
return {
|
|
269
|
+
invalidated: false,
|
|
270
|
+
tags: [],
|
|
271
|
+
reason: normalized.reason || 'event_not_publish_related',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const tags = this.buildNewsletterTags(
|
|
276
|
+
normalized.siteName,
|
|
277
|
+
[
|
|
278
|
+
'newsletter:campaigns',
|
|
279
|
+
`newsletter:campaigns:list:${normalized.siteName}`,
|
|
280
|
+
normalized.slug
|
|
281
|
+
? `newsletter:campaigns:slug:${normalized.siteName}:${normalized.slug}`
|
|
282
|
+
: '',
|
|
283
|
+
normalized.campaignId
|
|
284
|
+
? `newsletter:campaigns:id:${normalized.siteName}:${normalized.campaignId}`
|
|
285
|
+
: '',
|
|
286
|
+
],
|
|
287
|
+
normalized.slugPrefix,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
await this.invalidateCache({ tags });
|
|
291
|
+
return { invalidated: true, tags };
|
|
292
|
+
}
|
|
293
|
+
|
|
131
294
|
/**
|
|
132
295
|
* Check subscription status by email
|
|
133
296
|
*/
|
|
@@ -432,4 +595,181 @@ export class NewsletterClient extends BaseClient {
|
|
|
432
595
|
0x44, 0x01, 0x00, 0x3b,
|
|
433
596
|
]);
|
|
434
597
|
}
|
|
598
|
+
|
|
599
|
+
private buildNewsletterTags(
|
|
600
|
+
siteName?: string,
|
|
601
|
+
extraTags: string[] = [],
|
|
602
|
+
slugPrefix?: string
|
|
603
|
+
): string[] {
|
|
604
|
+
const tags = new Set<string>(['newsletter']);
|
|
605
|
+
if (siteName) {
|
|
606
|
+
tags.add(`newsletter:site:${siteName}`);
|
|
607
|
+
}
|
|
608
|
+
if (slugPrefix) {
|
|
609
|
+
tags.add(`newsletter:prefix:${slugPrefix}`);
|
|
610
|
+
}
|
|
611
|
+
extraTags.filter(Boolean).forEach(tag => tags.add(tag));
|
|
612
|
+
return Array.from(tags.values());
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private extractSlugPrefix(slug: string): string | undefined {
|
|
616
|
+
const normalized = slug.trim();
|
|
617
|
+
if (!normalized.includes('/')) {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
const [prefix] = normalized.split('/').filter(Boolean);
|
|
621
|
+
return prefix || undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private normalizeNewsletterWebhookPayload(
|
|
625
|
+
payload: Record<string, unknown>,
|
|
626
|
+
fallbackSiteName?: string
|
|
627
|
+
): {
|
|
628
|
+
shouldInvalidate: boolean;
|
|
629
|
+
reason?: string;
|
|
630
|
+
siteName: string;
|
|
631
|
+
slug?: string;
|
|
632
|
+
slugPrefix?: string;
|
|
633
|
+
campaignId?: string;
|
|
634
|
+
} {
|
|
635
|
+
const eventType = (
|
|
636
|
+
this.pickString(payload, ['event_type', 'type']) || ''
|
|
637
|
+
).toLowerCase();
|
|
638
|
+
|
|
639
|
+
const dataRaw = payload.data;
|
|
640
|
+
const data = (dataRaw && typeof dataRaw === 'object' && !Array.isArray(dataRaw))
|
|
641
|
+
? (dataRaw as Record<string, unknown>)
|
|
642
|
+
: payload;
|
|
643
|
+
|
|
644
|
+
const originType = (
|
|
645
|
+
this.pickString(data, ['origin_type', 'originType']) || ''
|
|
646
|
+
).toLowerCase();
|
|
647
|
+
const status = (
|
|
648
|
+
this.pickString(data, ['status', 'campaign_status', 'campaignStatus']) || ''
|
|
649
|
+
).toLowerCase();
|
|
650
|
+
|
|
651
|
+
if (!this.isPublishEvent(eventType, status, originType)) {
|
|
652
|
+
return {
|
|
653
|
+
shouldInvalidate: false,
|
|
654
|
+
reason: 'not_publish_or_sent_event',
|
|
655
|
+
siteName: fallbackSiteName || '',
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const siteName =
|
|
660
|
+
this.pickString(payload, ['site', 'site_name', 'siteName']) ||
|
|
661
|
+
this.pickString(data, ['site', 'site_name', 'siteName']) ||
|
|
662
|
+
fallbackSiteName ||
|
|
663
|
+
'';
|
|
664
|
+
|
|
665
|
+
if (!siteName) {
|
|
666
|
+
return {
|
|
667
|
+
shouldInvalidate: false,
|
|
668
|
+
reason: 'missing_site_name',
|
|
669
|
+
siteName: '',
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let slug =
|
|
674
|
+
this.pickString(data, ['slug', 'canonical_slug', 'canonicalSlug']) ||
|
|
675
|
+
this.pickString(payload, ['slug', 'canonical_slug', 'canonicalSlug']) ||
|
|
676
|
+
undefined;
|
|
677
|
+
|
|
678
|
+
let slugPrefix =
|
|
679
|
+
this.pickString(data, ['slug_prefix', 'slugPrefix']) ||
|
|
680
|
+
this.pickString(payload, ['slug_prefix', 'slugPrefix']) ||
|
|
681
|
+
(slug ? this.extractSlugPrefix(slug) : undefined);
|
|
682
|
+
|
|
683
|
+
if (slug) {
|
|
684
|
+
const slugParts = slug.split('/').filter(Boolean);
|
|
685
|
+
if (slugParts.length > 1) {
|
|
686
|
+
if (!slugPrefix) {
|
|
687
|
+
slugPrefix = slugParts.slice(0, -1).join('/');
|
|
688
|
+
}
|
|
689
|
+
slug = slugParts[slugParts.length - 1];
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const campaignId =
|
|
694
|
+
this.pickString(data, ['campaign_id', 'campaignId']) ||
|
|
695
|
+
this.pickString(payload, ['campaign_id', 'campaignId']) ||
|
|
696
|
+
undefined;
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
shouldInvalidate: true,
|
|
700
|
+
siteName,
|
|
701
|
+
slug,
|
|
702
|
+
slugPrefix,
|
|
703
|
+
campaignId,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private isPublishEvent(
|
|
708
|
+
eventType: string,
|
|
709
|
+
status: string,
|
|
710
|
+
originType: string
|
|
711
|
+
): boolean {
|
|
712
|
+
const knownPublishEvents = new Set([
|
|
713
|
+
'newsletter.published',
|
|
714
|
+
'newsletter.sent',
|
|
715
|
+
'newsletter_campaign.published',
|
|
716
|
+
'newsletter_campaign.sent',
|
|
717
|
+
'newsletter.campaign.published',
|
|
718
|
+
'newsletter.campaign.sent',
|
|
719
|
+
'campaign.published',
|
|
720
|
+
'campaign.sent',
|
|
721
|
+
]);
|
|
722
|
+
|
|
723
|
+
if (knownPublishEvents.has(eventType)) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Content webhook event for unified newsletter campaign content.
|
|
728
|
+
if (eventType === 'content.published' && originType === 'newsletter_campaign') {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Some systems emit update events with final status.
|
|
733
|
+
if (
|
|
734
|
+
(eventType.includes('newsletter') || eventType.includes('campaign')) &&
|
|
735
|
+
eventType.endsWith('.updated') &&
|
|
736
|
+
(status === 'published' || status === 'sent')
|
|
737
|
+
) {
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private pickString(
|
|
745
|
+
source: Record<string, unknown> | undefined,
|
|
746
|
+
keys: string[]
|
|
747
|
+
): string | undefined {
|
|
748
|
+
if (!source) {
|
|
749
|
+
return undefined;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
for (const key of keys) {
|
|
753
|
+
const value = source[key];
|
|
754
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
755
|
+
return value.trim();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private isCachePolicy(value: unknown): value is CachePolicy {
|
|
763
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const record = value as Record<string, unknown>;
|
|
768
|
+
return (
|
|
769
|
+
'ttlSeconds' in record ||
|
|
770
|
+
'tags' in record ||
|
|
771
|
+
'metadata' in record ||
|
|
772
|
+
'skipCache' in record
|
|
773
|
+
);
|
|
774
|
+
}
|
|
435
775
|
}
|
package/src/index.ts
CHANGED
|
@@ -100,6 +100,9 @@ export type {
|
|
|
100
100
|
NewsletterSubscription,
|
|
101
101
|
CreateNewsletterSubscriptionRequest,
|
|
102
102
|
NewsletterList,
|
|
103
|
+
NewsletterCampaignSummary,
|
|
104
|
+
NewsletterCampaignDetail,
|
|
105
|
+
NewsletterCampaignListResponse,
|
|
103
106
|
NewsletterPreferences,
|
|
104
107
|
NewsletterStatusResponse,
|
|
105
108
|
NewsletterSubscribeResponse,
|
package/src/types/index.ts
CHANGED
|
@@ -141,6 +141,37 @@ export interface NewsletterList {
|
|
|
141
141
|
subscriber_count?: number;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
export interface NewsletterCampaignSummary {
|
|
145
|
+
id: string;
|
|
146
|
+
campaign_name: string;
|
|
147
|
+
slug: string;
|
|
148
|
+
slug_prefix?: string | null;
|
|
149
|
+
subject: string;
|
|
150
|
+
preview_text?: string | null;
|
|
151
|
+
status: string;
|
|
152
|
+
sent_at?: string | null;
|
|
153
|
+
completed_at?: string | null;
|
|
154
|
+
created_at: string;
|
|
155
|
+
updated_at: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface NewsletterCampaignDetail extends NewsletterCampaignSummary {
|
|
159
|
+
markdown_content?: string | null;
|
|
160
|
+
html_content?: string | null;
|
|
161
|
+
text_content?: string | null;
|
|
162
|
+
excerpt?: string | null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface NewsletterCampaignListResponse {
|
|
166
|
+
items: NewsletterCampaignSummary[];
|
|
167
|
+
pagination: {
|
|
168
|
+
page: number;
|
|
169
|
+
limit: number;
|
|
170
|
+
total: number;
|
|
171
|
+
pages: number;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
144
175
|
export interface NewsletterPreferences {
|
|
145
176
|
frequency?: 'instant' | 'daily' | 'weekly' | 'monthly';
|
|
146
177
|
topics?: string[];
|