perspectapi-ts-sdk 2.1.0 → 2.3.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 +24 -1
- package/dist/index.d.mts +82 -1
- package/dist/index.d.ts +82 -1
- package/dist/index.js +130 -10
- package/dist/index.mjs +130 -10
- package/package.json +1 -1
- package/src/client/content-client.ts +28 -2
- package/src/client/products-client.ts +11 -0
- package/src/loaders.ts +105 -10
- package/src/types/index.ts +75 -0
- package/src/utils/validators.ts +53 -0
package/README.md
CHANGED
|
@@ -537,7 +537,29 @@ const session = await client.checkout.createCheckoutSession({
|
|
|
537
537
|
priceId: 'price_1234567890',
|
|
538
538
|
successUrl: 'https://myapp.com/success',
|
|
539
539
|
cancelUrl: 'https://myapp.com/cancel',
|
|
540
|
-
customerEmail: 'customer@example.com'
|
|
540
|
+
customerEmail: 'customer@example.com',
|
|
541
|
+
currency: 'usd',
|
|
542
|
+
shipping_amount: 500,
|
|
543
|
+
shipping_address: {
|
|
544
|
+
country: 'US',
|
|
545
|
+
state: 'CA',
|
|
546
|
+
postal_code: '94110'
|
|
547
|
+
},
|
|
548
|
+
billing_address: {
|
|
549
|
+
country: 'US',
|
|
550
|
+
state: 'CA',
|
|
551
|
+
postal_code: '94110'
|
|
552
|
+
},
|
|
553
|
+
tax: {
|
|
554
|
+
strategy: 'manual_rates',
|
|
555
|
+
customer_identifier: 'acct-123',
|
|
556
|
+
customer_display_name: 'Acme Corp',
|
|
557
|
+
customer_exemption: {
|
|
558
|
+
status: 'exempt',
|
|
559
|
+
tax_id: '99-1234567',
|
|
560
|
+
tax_id_type: 'ein'
|
|
561
|
+
}
|
|
562
|
+
}
|
|
541
563
|
});
|
|
542
564
|
|
|
543
565
|
// Redirect user to checkout
|
|
@@ -545,6 +567,7 @@ window.location.href = session.data.url;
|
|
|
545
567
|
|
|
546
568
|
// Get checkout session status
|
|
547
569
|
const sessionStatus = await client.checkout.getCheckoutSession('cs_test_123');
|
|
570
|
+
console.log(sessionStatus.data.tax?.amount); // Display assessed tax (if calculated)
|
|
548
571
|
```
|
|
549
572
|
|
|
550
573
|
### Contact Forms
|
package/dist/index.d.mts
CHANGED
|
@@ -424,6 +424,42 @@ type CheckoutMetadataValue = string | number | boolean | null | CheckoutMetadata
|
|
|
424
424
|
[key: string]: CheckoutMetadataValue;
|
|
425
425
|
};
|
|
426
426
|
type CheckoutMetadata = Record<string, CheckoutMetadataValue>;
|
|
427
|
+
type CheckoutTaxStrategy = 'disabled' | 'gateway_auto' | 'manual_rates' | 'external_service';
|
|
428
|
+
type CheckoutTaxExemptionStatus = 'none' | 'exempt' | 'reverse_charge';
|
|
429
|
+
interface CheckoutAddress {
|
|
430
|
+
line1?: string;
|
|
431
|
+
line2?: string;
|
|
432
|
+
city?: string;
|
|
433
|
+
state?: string;
|
|
434
|
+
postal_code?: string;
|
|
435
|
+
country?: string;
|
|
436
|
+
}
|
|
437
|
+
interface CheckoutTaxCustomerExemptionRequest {
|
|
438
|
+
status?: CheckoutTaxExemptionStatus;
|
|
439
|
+
reason?: string;
|
|
440
|
+
tax_id?: string;
|
|
441
|
+
tax_id_type?: string;
|
|
442
|
+
certificate_url?: string;
|
|
443
|
+
metadata?: Record<string, any>;
|
|
444
|
+
expires_at?: string;
|
|
445
|
+
}
|
|
446
|
+
interface CheckoutTaxRequest {
|
|
447
|
+
strategy?: CheckoutTaxStrategy;
|
|
448
|
+
customer_identifier?: string;
|
|
449
|
+
customer_profile_id?: string;
|
|
450
|
+
customer_display_name?: string;
|
|
451
|
+
allow_exemption?: boolean;
|
|
452
|
+
require_tax_id?: boolean;
|
|
453
|
+
save_profile?: boolean;
|
|
454
|
+
customer_exemption?: CheckoutTaxCustomerExemptionRequest;
|
|
455
|
+
manual_rate_percent?: number;
|
|
456
|
+
manual_rate_map?: Record<string, number>;
|
|
457
|
+
external_service?: {
|
|
458
|
+
provider: string;
|
|
459
|
+
config?: Record<string, any>;
|
|
460
|
+
};
|
|
461
|
+
metadata?: Record<string, any>;
|
|
462
|
+
}
|
|
427
463
|
interface CreateCheckoutSessionRequest {
|
|
428
464
|
priceId?: string;
|
|
429
465
|
quantity?: number;
|
|
@@ -446,6 +482,7 @@ interface CreateCheckoutSessionRequest {
|
|
|
446
482
|
cancelUrl?: string;
|
|
447
483
|
customer_email?: string;
|
|
448
484
|
customerEmail?: string;
|
|
485
|
+
currency?: string;
|
|
449
486
|
metadata?: CheckoutMetadata;
|
|
450
487
|
mode?: 'payment' | 'subscription' | 'setup';
|
|
451
488
|
automatic_tax?: {
|
|
@@ -455,11 +492,35 @@ interface CreateCheckoutSessionRequest {
|
|
|
455
492
|
allowed_countries: string[];
|
|
456
493
|
};
|
|
457
494
|
billing_address_collection?: 'auto' | 'required';
|
|
495
|
+
shipping_amount?: number;
|
|
496
|
+
shippingAmount?: number;
|
|
497
|
+
shipping_address?: CheckoutAddress;
|
|
498
|
+
shippingAddress?: CheckoutAddress;
|
|
499
|
+
billing_address?: CheckoutAddress;
|
|
500
|
+
billingAddress?: CheckoutAddress;
|
|
501
|
+
tax?: CheckoutTaxRequest;
|
|
502
|
+
}
|
|
503
|
+
interface CheckoutTaxBreakdownItem {
|
|
504
|
+
jurisdiction: string;
|
|
505
|
+
rate_percent: number;
|
|
506
|
+
tax_amount: number;
|
|
507
|
+
taxable_amount: number;
|
|
508
|
+
source: 'manual_map' | 'manual_percent' | 'gateway' | 'external';
|
|
509
|
+
}
|
|
510
|
+
interface CheckoutSessionTax {
|
|
511
|
+
amount: number;
|
|
512
|
+
currency: string;
|
|
513
|
+
strategy: CheckoutTaxStrategy;
|
|
514
|
+
exemption_applied: boolean;
|
|
515
|
+
exemption_status: CheckoutTaxExemptionStatus;
|
|
516
|
+
breakdown?: CheckoutTaxBreakdownItem[] | null;
|
|
458
517
|
}
|
|
459
518
|
interface CheckoutSession {
|
|
460
519
|
id: string;
|
|
461
520
|
url: string;
|
|
462
521
|
status: string;
|
|
522
|
+
payment_status?: string;
|
|
523
|
+
tax?: CheckoutSessionTax | null;
|
|
463
524
|
}
|
|
464
525
|
interface ContactSubmission {
|
|
465
526
|
id: string;
|
|
@@ -2318,10 +2379,30 @@ interface CheckoutSessionOptions {
|
|
|
2318
2379
|
logger?: LoaderLogger;
|
|
2319
2380
|
fallbackProducts?: Product[];
|
|
2320
2381
|
metadata?: CheckoutMetadata;
|
|
2382
|
+
/**
|
|
2383
|
+
* Optional currency override; defaults to the currency defined on each Stripe price.
|
|
2384
|
+
*/
|
|
2385
|
+
currency?: string;
|
|
2386
|
+
/**
|
|
2387
|
+
* Optional shipping amount (in the smallest currency unit).
|
|
2388
|
+
*/
|
|
2389
|
+
shippingAmount?: number;
|
|
2390
|
+
/**
|
|
2391
|
+
* Shipping address forwarded to the checkout API for tax estimation.
|
|
2392
|
+
*/
|
|
2393
|
+
shippingAddress?: CheckoutAddress;
|
|
2394
|
+
/**
|
|
2395
|
+
* Billing address forwarded to the checkout API for tax estimation.
|
|
2396
|
+
*/
|
|
2397
|
+
billingAddress?: CheckoutAddress;
|
|
2321
2398
|
/**
|
|
2322
2399
|
* Optional resolver to convert a product record into a Stripe price ID.
|
|
2323
2400
|
*/
|
|
2324
2401
|
priceIdResolver?: (product: Product, mode: 'live' | 'test') => string | undefined;
|
|
2402
|
+
/**
|
|
2403
|
+
* Optional tax configuration that will be forwarded to the checkout API.
|
|
2404
|
+
*/
|
|
2405
|
+
tax?: CheckoutTaxRequest;
|
|
2325
2406
|
}
|
|
2326
2407
|
/**
|
|
2327
2408
|
* Convenience helper that creates a checkout session by looking up Stripe price IDs
|
|
@@ -2331,4 +2412,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
2331
2412
|
error: string;
|
|
2332
2413
|
}>;
|
|
2333
2414
|
|
|
2334
|
-
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, 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 ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
|
2415
|
+
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, 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, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, 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 ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, 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
|
@@ -424,6 +424,42 @@ type CheckoutMetadataValue = string | number | boolean | null | CheckoutMetadata
|
|
|
424
424
|
[key: string]: CheckoutMetadataValue;
|
|
425
425
|
};
|
|
426
426
|
type CheckoutMetadata = Record<string, CheckoutMetadataValue>;
|
|
427
|
+
type CheckoutTaxStrategy = 'disabled' | 'gateway_auto' | 'manual_rates' | 'external_service';
|
|
428
|
+
type CheckoutTaxExemptionStatus = 'none' | 'exempt' | 'reverse_charge';
|
|
429
|
+
interface CheckoutAddress {
|
|
430
|
+
line1?: string;
|
|
431
|
+
line2?: string;
|
|
432
|
+
city?: string;
|
|
433
|
+
state?: string;
|
|
434
|
+
postal_code?: string;
|
|
435
|
+
country?: string;
|
|
436
|
+
}
|
|
437
|
+
interface CheckoutTaxCustomerExemptionRequest {
|
|
438
|
+
status?: CheckoutTaxExemptionStatus;
|
|
439
|
+
reason?: string;
|
|
440
|
+
tax_id?: string;
|
|
441
|
+
tax_id_type?: string;
|
|
442
|
+
certificate_url?: string;
|
|
443
|
+
metadata?: Record<string, any>;
|
|
444
|
+
expires_at?: string;
|
|
445
|
+
}
|
|
446
|
+
interface CheckoutTaxRequest {
|
|
447
|
+
strategy?: CheckoutTaxStrategy;
|
|
448
|
+
customer_identifier?: string;
|
|
449
|
+
customer_profile_id?: string;
|
|
450
|
+
customer_display_name?: string;
|
|
451
|
+
allow_exemption?: boolean;
|
|
452
|
+
require_tax_id?: boolean;
|
|
453
|
+
save_profile?: boolean;
|
|
454
|
+
customer_exemption?: CheckoutTaxCustomerExemptionRequest;
|
|
455
|
+
manual_rate_percent?: number;
|
|
456
|
+
manual_rate_map?: Record<string, number>;
|
|
457
|
+
external_service?: {
|
|
458
|
+
provider: string;
|
|
459
|
+
config?: Record<string, any>;
|
|
460
|
+
};
|
|
461
|
+
metadata?: Record<string, any>;
|
|
462
|
+
}
|
|
427
463
|
interface CreateCheckoutSessionRequest {
|
|
428
464
|
priceId?: string;
|
|
429
465
|
quantity?: number;
|
|
@@ -446,6 +482,7 @@ interface CreateCheckoutSessionRequest {
|
|
|
446
482
|
cancelUrl?: string;
|
|
447
483
|
customer_email?: string;
|
|
448
484
|
customerEmail?: string;
|
|
485
|
+
currency?: string;
|
|
449
486
|
metadata?: CheckoutMetadata;
|
|
450
487
|
mode?: 'payment' | 'subscription' | 'setup';
|
|
451
488
|
automatic_tax?: {
|
|
@@ -455,11 +492,35 @@ interface CreateCheckoutSessionRequest {
|
|
|
455
492
|
allowed_countries: string[];
|
|
456
493
|
};
|
|
457
494
|
billing_address_collection?: 'auto' | 'required';
|
|
495
|
+
shipping_amount?: number;
|
|
496
|
+
shippingAmount?: number;
|
|
497
|
+
shipping_address?: CheckoutAddress;
|
|
498
|
+
shippingAddress?: CheckoutAddress;
|
|
499
|
+
billing_address?: CheckoutAddress;
|
|
500
|
+
billingAddress?: CheckoutAddress;
|
|
501
|
+
tax?: CheckoutTaxRequest;
|
|
502
|
+
}
|
|
503
|
+
interface CheckoutTaxBreakdownItem {
|
|
504
|
+
jurisdiction: string;
|
|
505
|
+
rate_percent: number;
|
|
506
|
+
tax_amount: number;
|
|
507
|
+
taxable_amount: number;
|
|
508
|
+
source: 'manual_map' | 'manual_percent' | 'gateway' | 'external';
|
|
509
|
+
}
|
|
510
|
+
interface CheckoutSessionTax {
|
|
511
|
+
amount: number;
|
|
512
|
+
currency: string;
|
|
513
|
+
strategy: CheckoutTaxStrategy;
|
|
514
|
+
exemption_applied: boolean;
|
|
515
|
+
exemption_status: CheckoutTaxExemptionStatus;
|
|
516
|
+
breakdown?: CheckoutTaxBreakdownItem[] | null;
|
|
458
517
|
}
|
|
459
518
|
interface CheckoutSession {
|
|
460
519
|
id: string;
|
|
461
520
|
url: string;
|
|
462
521
|
status: string;
|
|
522
|
+
payment_status?: string;
|
|
523
|
+
tax?: CheckoutSessionTax | null;
|
|
463
524
|
}
|
|
464
525
|
interface ContactSubmission {
|
|
465
526
|
id: string;
|
|
@@ -2318,10 +2379,30 @@ interface CheckoutSessionOptions {
|
|
|
2318
2379
|
logger?: LoaderLogger;
|
|
2319
2380
|
fallbackProducts?: Product[];
|
|
2320
2381
|
metadata?: CheckoutMetadata;
|
|
2382
|
+
/**
|
|
2383
|
+
* Optional currency override; defaults to the currency defined on each Stripe price.
|
|
2384
|
+
*/
|
|
2385
|
+
currency?: string;
|
|
2386
|
+
/**
|
|
2387
|
+
* Optional shipping amount (in the smallest currency unit).
|
|
2388
|
+
*/
|
|
2389
|
+
shippingAmount?: number;
|
|
2390
|
+
/**
|
|
2391
|
+
* Shipping address forwarded to the checkout API for tax estimation.
|
|
2392
|
+
*/
|
|
2393
|
+
shippingAddress?: CheckoutAddress;
|
|
2394
|
+
/**
|
|
2395
|
+
* Billing address forwarded to the checkout API for tax estimation.
|
|
2396
|
+
*/
|
|
2397
|
+
billingAddress?: CheckoutAddress;
|
|
2321
2398
|
/**
|
|
2322
2399
|
* Optional resolver to convert a product record into a Stripe price ID.
|
|
2323
2400
|
*/
|
|
2324
2401
|
priceIdResolver?: (product: Product, mode: 'live' | 'test') => string | undefined;
|
|
2402
|
+
/**
|
|
2403
|
+
* Optional tax configuration that will be forwarded to the checkout API.
|
|
2404
|
+
*/
|
|
2405
|
+
tax?: CheckoutTaxRequest;
|
|
2325
2406
|
}
|
|
2326
2407
|
/**
|
|
2327
2408
|
* Convenience helper that creates a checkout session by looking up Stripe price IDs
|
|
@@ -2331,4 +2412,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
2331
2412
|
error: string;
|
|
2332
2413
|
}>;
|
|
2333
2414
|
|
|
2334
|
-
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, 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 ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
|
2415
|
+
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, type CacheConfig, CacheManager, CategoriesClient, type Category, 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, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, 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 ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, 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
|
@@ -729,6 +729,43 @@ var AuthClient = class extends BaseClient {
|
|
|
729
729
|
}
|
|
730
730
|
};
|
|
731
731
|
|
|
732
|
+
// src/utils/validators.ts
|
|
733
|
+
var MAX_API_QUERY_LIMIT = 100;
|
|
734
|
+
var ALLOWED_CONTENT_TYPES = ["post", "page"];
|
|
735
|
+
function validateLimit(limit, context) {
|
|
736
|
+
if (typeof limit !== "number" || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
737
|
+
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
738
|
+
}
|
|
739
|
+
if (limit < 1) {
|
|
740
|
+
throw new Error(`[PerspectAPI] ${context} limit must be at least 1.`);
|
|
741
|
+
}
|
|
742
|
+
if (limit > MAX_API_QUERY_LIMIT) {
|
|
743
|
+
throw new Error(
|
|
744
|
+
`[PerspectAPI] ${context} limit ${limit} exceeds the maximum allowed value of ${MAX_API_QUERY_LIMIT}.`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
return limit;
|
|
748
|
+
}
|
|
749
|
+
function validateOptionalLimit(limit, context) {
|
|
750
|
+
if (limit === void 0 || limit === null) {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
return validateLimit(limit, context);
|
|
754
|
+
}
|
|
755
|
+
function validateOptionalContentType(pageType, context) {
|
|
756
|
+
if (pageType === void 0 || pageType === null) {
|
|
757
|
+
return void 0;
|
|
758
|
+
}
|
|
759
|
+
if (ALLOWED_CONTENT_TYPES.includes(pageType)) {
|
|
760
|
+
return pageType;
|
|
761
|
+
}
|
|
762
|
+
throw new Error(
|
|
763
|
+
`[PerspectAPI] ${context} received unsupported page_type "${pageType}". Allowed values are ${ALLOWED_CONTENT_TYPES.join(
|
|
764
|
+
", "
|
|
765
|
+
)}.`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
732
769
|
// src/client/content-client.ts
|
|
733
770
|
var ContentClient = class extends BaseClient {
|
|
734
771
|
constructor(http, cache) {
|
|
@@ -740,12 +777,33 @@ var ContentClient = class extends BaseClient {
|
|
|
740
777
|
async getContent(siteName, params, cachePolicy) {
|
|
741
778
|
const endpoint = this.siteScopedEndpoint(siteName);
|
|
742
779
|
const path = this.buildPath(endpoint);
|
|
780
|
+
const normalizedParams = params ? { ...params } : void 0;
|
|
781
|
+
if (normalizedParams) {
|
|
782
|
+
const validatedLimit = validateOptionalLimit(
|
|
783
|
+
normalizedParams.limit,
|
|
784
|
+
"content query"
|
|
785
|
+
);
|
|
786
|
+
if (validatedLimit !== void 0) {
|
|
787
|
+
normalizedParams.limit = validatedLimit;
|
|
788
|
+
} else {
|
|
789
|
+
delete normalizedParams.limit;
|
|
790
|
+
}
|
|
791
|
+
const validatedPageType = validateOptionalContentType(
|
|
792
|
+
normalizedParams.page_type,
|
|
793
|
+
"content query"
|
|
794
|
+
);
|
|
795
|
+
if (validatedPageType !== void 0) {
|
|
796
|
+
normalizedParams.page_type = validatedPageType;
|
|
797
|
+
} else {
|
|
798
|
+
delete normalizedParams.page_type;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
743
801
|
return this.fetchWithCache(
|
|
744
802
|
endpoint,
|
|
745
|
-
|
|
803
|
+
normalizedParams,
|
|
746
804
|
this.buildContentTags(siteName),
|
|
747
805
|
cachePolicy,
|
|
748
|
-
() => this.http.get(path,
|
|
806
|
+
() => this.http.get(path, normalizedParams)
|
|
749
807
|
);
|
|
750
808
|
}
|
|
751
809
|
/**
|
|
@@ -1107,6 +1165,15 @@ var ProductsClient = class extends BaseClient {
|
|
|
1107
1165
|
};
|
|
1108
1166
|
const normalizedParams = params ? { ...params } : void 0;
|
|
1109
1167
|
if (normalizedParams) {
|
|
1168
|
+
const validatedLimit = validateOptionalLimit(
|
|
1169
|
+
normalizedParams.limit,
|
|
1170
|
+
"products query"
|
|
1171
|
+
);
|
|
1172
|
+
if (validatedLimit !== void 0) {
|
|
1173
|
+
normalizedParams.limit = validatedLimit;
|
|
1174
|
+
} else {
|
|
1175
|
+
delete normalizedParams.limit;
|
|
1176
|
+
}
|
|
1110
1177
|
const normalizedCategories = normalizeList(normalizedParams.category);
|
|
1111
1178
|
if (normalizedCategories !== void 0) {
|
|
1112
1179
|
normalizedParams.category = normalizedCategories;
|
|
@@ -2322,12 +2389,16 @@ async function loadProducts(options) {
|
|
|
2322
2389
|
siteName,
|
|
2323
2390
|
logger = noopLogger,
|
|
2324
2391
|
fallbackProducts,
|
|
2325
|
-
limit
|
|
2392
|
+
limit: requestedLimit,
|
|
2326
2393
|
offset,
|
|
2327
2394
|
search,
|
|
2328
2395
|
category,
|
|
2329
2396
|
categoryIds
|
|
2330
2397
|
} = options;
|
|
2398
|
+
const resolvedLimit = validateLimit(
|
|
2399
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2400
|
+
"products query"
|
|
2401
|
+
);
|
|
2331
2402
|
if (!client) {
|
|
2332
2403
|
log(logger, "warn", "[PerspectAPI] No client configured, using fallback products");
|
|
2333
2404
|
return resolveFallbackProducts({ siteName, fallbackProducts });
|
|
@@ -2336,7 +2407,7 @@ async function loadProducts(options) {
|
|
|
2336
2407
|
log(logger, "info", `[PerspectAPI] Loading products for site "${siteName}"`);
|
|
2337
2408
|
const queryParams = {
|
|
2338
2409
|
isActive: true,
|
|
2339
|
-
limit,
|
|
2410
|
+
limit: resolvedLimit,
|
|
2340
2411
|
offset,
|
|
2341
2412
|
search
|
|
2342
2413
|
};
|
|
@@ -2396,13 +2467,20 @@ async function loadPages(options) {
|
|
|
2396
2467
|
}
|
|
2397
2468
|
try {
|
|
2398
2469
|
log(logger, "info", `[PerspectAPI] Loading pages for site "${siteName}"`);
|
|
2470
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2471
|
+
const resolvedLimit = validateLimit(
|
|
2472
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2473
|
+
"pages query"
|
|
2474
|
+
);
|
|
2475
|
+
const requestedPageType = params?.page_type;
|
|
2476
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "pages query") ?? "page";
|
|
2399
2477
|
const response = await client.content.getContent(
|
|
2400
2478
|
siteName,
|
|
2401
2479
|
{
|
|
2402
2480
|
...params,
|
|
2403
2481
|
page_status: params?.page_status ?? "publish",
|
|
2404
|
-
page_type:
|
|
2405
|
-
limit:
|
|
2482
|
+
page_type: validatedPageType,
|
|
2483
|
+
limit: resolvedLimit
|
|
2406
2484
|
}
|
|
2407
2485
|
);
|
|
2408
2486
|
if (!response.data) {
|
|
@@ -2422,13 +2500,20 @@ async function loadPosts(options) {
|
|
|
2422
2500
|
}
|
|
2423
2501
|
try {
|
|
2424
2502
|
log(logger, "info", `[PerspectAPI] Loading posts for site "${siteName}"`);
|
|
2503
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2504
|
+
const resolvedLimit = validateLimit(
|
|
2505
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2506
|
+
"posts query"
|
|
2507
|
+
);
|
|
2508
|
+
const requestedPageType = params?.page_type;
|
|
2509
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "posts query") ?? "post";
|
|
2425
2510
|
const response = await client.content.getContent(
|
|
2426
2511
|
siteName,
|
|
2427
2512
|
{
|
|
2428
2513
|
...params,
|
|
2429
2514
|
page_status: params?.page_status ?? "publish",
|
|
2430
|
-
page_type:
|
|
2431
|
-
limit:
|
|
2515
|
+
page_type: validatedPageType,
|
|
2516
|
+
limit: resolvedLimit
|
|
2432
2517
|
}
|
|
2433
2518
|
);
|
|
2434
2519
|
if (!response.data) {
|
|
@@ -2488,7 +2573,12 @@ async function createCheckoutSession(options) {
|
|
|
2488
2573
|
logger = noopLogger,
|
|
2489
2574
|
fallbackProducts,
|
|
2490
2575
|
metadata,
|
|
2491
|
-
|
|
2576
|
+
currency,
|
|
2577
|
+
shippingAmount,
|
|
2578
|
+
shippingAddress,
|
|
2579
|
+
billingAddress,
|
|
2580
|
+
priceIdResolver,
|
|
2581
|
+
tax
|
|
2492
2582
|
} = options;
|
|
2493
2583
|
if (!client) {
|
|
2494
2584
|
log(logger, "error", "[PerspectAPI] Cannot create checkout session without SDK client");
|
|
@@ -2499,7 +2589,7 @@ async function createCheckoutSession(options) {
|
|
|
2499
2589
|
client,
|
|
2500
2590
|
siteName,
|
|
2501
2591
|
logger,
|
|
2502
|
-
limit:
|
|
2592
|
+
limit: Math.min(Math.max(items.length, 1), MAX_API_QUERY_LIMIT),
|
|
2503
2593
|
fallbackProducts
|
|
2504
2594
|
});
|
|
2505
2595
|
const productMap = new Map(
|
|
@@ -2519,6 +2609,18 @@ async function createCheckoutSession(options) {
|
|
|
2519
2609
|
quantity: item.quantity
|
|
2520
2610
|
};
|
|
2521
2611
|
});
|
|
2612
|
+
const resolvedTax = (() => {
|
|
2613
|
+
if (!tax && !customerEmail) {
|
|
2614
|
+
return void 0;
|
|
2615
|
+
}
|
|
2616
|
+
if (!tax) {
|
|
2617
|
+
return { customer_identifier: customerEmail };
|
|
2618
|
+
}
|
|
2619
|
+
return {
|
|
2620
|
+
...tax,
|
|
2621
|
+
customer_identifier: tax.customer_identifier ?? customerEmail ?? void 0
|
|
2622
|
+
};
|
|
2623
|
+
})();
|
|
2522
2624
|
const checkoutData = {
|
|
2523
2625
|
line_items,
|
|
2524
2626
|
successUrl,
|
|
@@ -2530,6 +2632,24 @@ async function createCheckoutSession(options) {
|
|
|
2530
2632
|
mode: mode === "test" ? "payment" : "payment",
|
|
2531
2633
|
metadata
|
|
2532
2634
|
};
|
|
2635
|
+
if (currency) {
|
|
2636
|
+
checkoutData.currency = currency;
|
|
2637
|
+
}
|
|
2638
|
+
if (typeof shippingAmount === "number") {
|
|
2639
|
+
checkoutData.shipping_amount = shippingAmount;
|
|
2640
|
+
checkoutData.shippingAmount = shippingAmount;
|
|
2641
|
+
}
|
|
2642
|
+
if (shippingAddress) {
|
|
2643
|
+
checkoutData.shipping_address = shippingAddress;
|
|
2644
|
+
checkoutData.shippingAddress = shippingAddress;
|
|
2645
|
+
}
|
|
2646
|
+
if (billingAddress) {
|
|
2647
|
+
checkoutData.billing_address = billingAddress;
|
|
2648
|
+
checkoutData.billingAddress = billingAddress;
|
|
2649
|
+
}
|
|
2650
|
+
if (resolvedTax) {
|
|
2651
|
+
checkoutData.tax = resolvedTax;
|
|
2652
|
+
}
|
|
2533
2653
|
log(logger, "info", "[PerspectAPI] Creating checkout session");
|
|
2534
2654
|
return client.checkout.createCheckoutSession(siteName, checkoutData);
|
|
2535
2655
|
} catch (error) {
|
package/dist/index.mjs
CHANGED
|
@@ -668,6 +668,43 @@ var AuthClient = class extends BaseClient {
|
|
|
668
668
|
}
|
|
669
669
|
};
|
|
670
670
|
|
|
671
|
+
// src/utils/validators.ts
|
|
672
|
+
var MAX_API_QUERY_LIMIT = 100;
|
|
673
|
+
var ALLOWED_CONTENT_TYPES = ["post", "page"];
|
|
674
|
+
function validateLimit(limit, context) {
|
|
675
|
+
if (typeof limit !== "number" || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
676
|
+
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
677
|
+
}
|
|
678
|
+
if (limit < 1) {
|
|
679
|
+
throw new Error(`[PerspectAPI] ${context} limit must be at least 1.`);
|
|
680
|
+
}
|
|
681
|
+
if (limit > MAX_API_QUERY_LIMIT) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
`[PerspectAPI] ${context} limit ${limit} exceeds the maximum allowed value of ${MAX_API_QUERY_LIMIT}.`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
return limit;
|
|
687
|
+
}
|
|
688
|
+
function validateOptionalLimit(limit, context) {
|
|
689
|
+
if (limit === void 0 || limit === null) {
|
|
690
|
+
return void 0;
|
|
691
|
+
}
|
|
692
|
+
return validateLimit(limit, context);
|
|
693
|
+
}
|
|
694
|
+
function validateOptionalContentType(pageType, context) {
|
|
695
|
+
if (pageType === void 0 || pageType === null) {
|
|
696
|
+
return void 0;
|
|
697
|
+
}
|
|
698
|
+
if (ALLOWED_CONTENT_TYPES.includes(pageType)) {
|
|
699
|
+
return pageType;
|
|
700
|
+
}
|
|
701
|
+
throw new Error(
|
|
702
|
+
`[PerspectAPI] ${context} received unsupported page_type "${pageType}". Allowed values are ${ALLOWED_CONTENT_TYPES.join(
|
|
703
|
+
", "
|
|
704
|
+
)}.`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
671
708
|
// src/client/content-client.ts
|
|
672
709
|
var ContentClient = class extends BaseClient {
|
|
673
710
|
constructor(http, cache) {
|
|
@@ -679,12 +716,33 @@ var ContentClient = class extends BaseClient {
|
|
|
679
716
|
async getContent(siteName, params, cachePolicy) {
|
|
680
717
|
const endpoint = this.siteScopedEndpoint(siteName);
|
|
681
718
|
const path = this.buildPath(endpoint);
|
|
719
|
+
const normalizedParams = params ? { ...params } : void 0;
|
|
720
|
+
if (normalizedParams) {
|
|
721
|
+
const validatedLimit = validateOptionalLimit(
|
|
722
|
+
normalizedParams.limit,
|
|
723
|
+
"content query"
|
|
724
|
+
);
|
|
725
|
+
if (validatedLimit !== void 0) {
|
|
726
|
+
normalizedParams.limit = validatedLimit;
|
|
727
|
+
} else {
|
|
728
|
+
delete normalizedParams.limit;
|
|
729
|
+
}
|
|
730
|
+
const validatedPageType = validateOptionalContentType(
|
|
731
|
+
normalizedParams.page_type,
|
|
732
|
+
"content query"
|
|
733
|
+
);
|
|
734
|
+
if (validatedPageType !== void 0) {
|
|
735
|
+
normalizedParams.page_type = validatedPageType;
|
|
736
|
+
} else {
|
|
737
|
+
delete normalizedParams.page_type;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
682
740
|
return this.fetchWithCache(
|
|
683
741
|
endpoint,
|
|
684
|
-
|
|
742
|
+
normalizedParams,
|
|
685
743
|
this.buildContentTags(siteName),
|
|
686
744
|
cachePolicy,
|
|
687
|
-
() => this.http.get(path,
|
|
745
|
+
() => this.http.get(path, normalizedParams)
|
|
688
746
|
);
|
|
689
747
|
}
|
|
690
748
|
/**
|
|
@@ -1046,6 +1104,15 @@ var ProductsClient = class extends BaseClient {
|
|
|
1046
1104
|
};
|
|
1047
1105
|
const normalizedParams = params ? { ...params } : void 0;
|
|
1048
1106
|
if (normalizedParams) {
|
|
1107
|
+
const validatedLimit = validateOptionalLimit(
|
|
1108
|
+
normalizedParams.limit,
|
|
1109
|
+
"products query"
|
|
1110
|
+
);
|
|
1111
|
+
if (validatedLimit !== void 0) {
|
|
1112
|
+
normalizedParams.limit = validatedLimit;
|
|
1113
|
+
} else {
|
|
1114
|
+
delete normalizedParams.limit;
|
|
1115
|
+
}
|
|
1049
1116
|
const normalizedCategories = normalizeList(normalizedParams.category);
|
|
1050
1117
|
if (normalizedCategories !== void 0) {
|
|
1051
1118
|
normalizedParams.category = normalizedCategories;
|
|
@@ -2261,12 +2328,16 @@ async function loadProducts(options) {
|
|
|
2261
2328
|
siteName,
|
|
2262
2329
|
logger = noopLogger,
|
|
2263
2330
|
fallbackProducts,
|
|
2264
|
-
limit
|
|
2331
|
+
limit: requestedLimit,
|
|
2265
2332
|
offset,
|
|
2266
2333
|
search,
|
|
2267
2334
|
category,
|
|
2268
2335
|
categoryIds
|
|
2269
2336
|
} = options;
|
|
2337
|
+
const resolvedLimit = validateLimit(
|
|
2338
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2339
|
+
"products query"
|
|
2340
|
+
);
|
|
2270
2341
|
if (!client) {
|
|
2271
2342
|
log(logger, "warn", "[PerspectAPI] No client configured, using fallback products");
|
|
2272
2343
|
return resolveFallbackProducts({ siteName, fallbackProducts });
|
|
@@ -2275,7 +2346,7 @@ async function loadProducts(options) {
|
|
|
2275
2346
|
log(logger, "info", `[PerspectAPI] Loading products for site "${siteName}"`);
|
|
2276
2347
|
const queryParams = {
|
|
2277
2348
|
isActive: true,
|
|
2278
|
-
limit,
|
|
2349
|
+
limit: resolvedLimit,
|
|
2279
2350
|
offset,
|
|
2280
2351
|
search
|
|
2281
2352
|
};
|
|
@@ -2335,13 +2406,20 @@ async function loadPages(options) {
|
|
|
2335
2406
|
}
|
|
2336
2407
|
try {
|
|
2337
2408
|
log(logger, "info", `[PerspectAPI] Loading pages for site "${siteName}"`);
|
|
2409
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2410
|
+
const resolvedLimit = validateLimit(
|
|
2411
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2412
|
+
"pages query"
|
|
2413
|
+
);
|
|
2414
|
+
const requestedPageType = params?.page_type;
|
|
2415
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "pages query") ?? "page";
|
|
2338
2416
|
const response = await client.content.getContent(
|
|
2339
2417
|
siteName,
|
|
2340
2418
|
{
|
|
2341
2419
|
...params,
|
|
2342
2420
|
page_status: params?.page_status ?? "publish",
|
|
2343
|
-
page_type:
|
|
2344
|
-
limit:
|
|
2421
|
+
page_type: validatedPageType,
|
|
2422
|
+
limit: resolvedLimit
|
|
2345
2423
|
}
|
|
2346
2424
|
);
|
|
2347
2425
|
if (!response.data) {
|
|
@@ -2361,13 +2439,20 @@ async function loadPosts(options) {
|
|
|
2361
2439
|
}
|
|
2362
2440
|
try {
|
|
2363
2441
|
log(logger, "info", `[PerspectAPI] Loading posts for site "${siteName}"`);
|
|
2442
|
+
const requestedLimit = options.limit !== void 0 ? options.limit : params?.limit;
|
|
2443
|
+
const resolvedLimit = validateLimit(
|
|
2444
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
2445
|
+
"posts query"
|
|
2446
|
+
);
|
|
2447
|
+
const requestedPageType = params?.page_type;
|
|
2448
|
+
const validatedPageType = validateOptionalContentType(requestedPageType, "posts query") ?? "post";
|
|
2364
2449
|
const response = await client.content.getContent(
|
|
2365
2450
|
siteName,
|
|
2366
2451
|
{
|
|
2367
2452
|
...params,
|
|
2368
2453
|
page_status: params?.page_status ?? "publish",
|
|
2369
|
-
page_type:
|
|
2370
|
-
limit:
|
|
2454
|
+
page_type: validatedPageType,
|
|
2455
|
+
limit: resolvedLimit
|
|
2371
2456
|
}
|
|
2372
2457
|
);
|
|
2373
2458
|
if (!response.data) {
|
|
@@ -2427,7 +2512,12 @@ async function createCheckoutSession(options) {
|
|
|
2427
2512
|
logger = noopLogger,
|
|
2428
2513
|
fallbackProducts,
|
|
2429
2514
|
metadata,
|
|
2430
|
-
|
|
2515
|
+
currency,
|
|
2516
|
+
shippingAmount,
|
|
2517
|
+
shippingAddress,
|
|
2518
|
+
billingAddress,
|
|
2519
|
+
priceIdResolver,
|
|
2520
|
+
tax
|
|
2431
2521
|
} = options;
|
|
2432
2522
|
if (!client) {
|
|
2433
2523
|
log(logger, "error", "[PerspectAPI] Cannot create checkout session without SDK client");
|
|
@@ -2438,7 +2528,7 @@ async function createCheckoutSession(options) {
|
|
|
2438
2528
|
client,
|
|
2439
2529
|
siteName,
|
|
2440
2530
|
logger,
|
|
2441
|
-
limit:
|
|
2531
|
+
limit: Math.min(Math.max(items.length, 1), MAX_API_QUERY_LIMIT),
|
|
2442
2532
|
fallbackProducts
|
|
2443
2533
|
});
|
|
2444
2534
|
const productMap = new Map(
|
|
@@ -2458,6 +2548,18 @@ async function createCheckoutSession(options) {
|
|
|
2458
2548
|
quantity: item.quantity
|
|
2459
2549
|
};
|
|
2460
2550
|
});
|
|
2551
|
+
const resolvedTax = (() => {
|
|
2552
|
+
if (!tax && !customerEmail) {
|
|
2553
|
+
return void 0;
|
|
2554
|
+
}
|
|
2555
|
+
if (!tax) {
|
|
2556
|
+
return { customer_identifier: customerEmail };
|
|
2557
|
+
}
|
|
2558
|
+
return {
|
|
2559
|
+
...tax,
|
|
2560
|
+
customer_identifier: tax.customer_identifier ?? customerEmail ?? void 0
|
|
2561
|
+
};
|
|
2562
|
+
})();
|
|
2461
2563
|
const checkoutData = {
|
|
2462
2564
|
line_items,
|
|
2463
2565
|
successUrl,
|
|
@@ -2469,6 +2571,24 @@ async function createCheckoutSession(options) {
|
|
|
2469
2571
|
mode: mode === "test" ? "payment" : "payment",
|
|
2470
2572
|
metadata
|
|
2471
2573
|
};
|
|
2574
|
+
if (currency) {
|
|
2575
|
+
checkoutData.currency = currency;
|
|
2576
|
+
}
|
|
2577
|
+
if (typeof shippingAmount === "number") {
|
|
2578
|
+
checkoutData.shipping_amount = shippingAmount;
|
|
2579
|
+
checkoutData.shippingAmount = shippingAmount;
|
|
2580
|
+
}
|
|
2581
|
+
if (shippingAddress) {
|
|
2582
|
+
checkoutData.shipping_address = shippingAddress;
|
|
2583
|
+
checkoutData.shippingAddress = shippingAddress;
|
|
2584
|
+
}
|
|
2585
|
+
if (billingAddress) {
|
|
2586
|
+
checkoutData.billing_address = billingAddress;
|
|
2587
|
+
checkoutData.billingAddress = billingAddress;
|
|
2588
|
+
}
|
|
2589
|
+
if (resolvedTax) {
|
|
2590
|
+
checkoutData.tax = resolvedTax;
|
|
2591
|
+
}
|
|
2472
2592
|
log(logger, "info", "[PerspectAPI] Creating checkout session");
|
|
2473
2593
|
return client.checkout.createCheckoutSession(siteName, checkoutData);
|
|
2474
2594
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
PaginatedResponse,
|
|
14
14
|
ApiResponse,
|
|
15
15
|
} from '../types';
|
|
16
|
+
import { validateOptionalContentType, validateOptionalLimit } from '../utils/validators';
|
|
16
17
|
|
|
17
18
|
export class ContentClient extends BaseClient {
|
|
18
19
|
constructor(http: any, cache?: CacheManager) {
|
|
@@ -29,13 +30,38 @@ export class ContentClient extends BaseClient {
|
|
|
29
30
|
): Promise<PaginatedResponse<Content>> {
|
|
30
31
|
const endpoint = this.siteScopedEndpoint(siteName);
|
|
31
32
|
const path = this.buildPath(endpoint);
|
|
33
|
+
const normalizedParams: ContentQueryParams | undefined = params
|
|
34
|
+
? { ...params }
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
if (normalizedParams) {
|
|
38
|
+
const validatedLimit = validateOptionalLimit(
|
|
39
|
+
normalizedParams.limit,
|
|
40
|
+
'content query',
|
|
41
|
+
);
|
|
42
|
+
if (validatedLimit !== undefined) {
|
|
43
|
+
normalizedParams.limit = validatedLimit;
|
|
44
|
+
} else {
|
|
45
|
+
delete normalizedParams.limit;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const validatedPageType = validateOptionalContentType(
|
|
49
|
+
normalizedParams.page_type,
|
|
50
|
+
'content query',
|
|
51
|
+
);
|
|
52
|
+
if (validatedPageType !== undefined) {
|
|
53
|
+
normalizedParams.page_type = validatedPageType;
|
|
54
|
+
} else {
|
|
55
|
+
delete normalizedParams.page_type;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
32
58
|
|
|
33
59
|
return this.fetchWithCache<PaginatedResponse<Content>>(
|
|
34
60
|
endpoint,
|
|
35
|
-
|
|
61
|
+
normalizedParams,
|
|
36
62
|
this.buildContentTags(siteName),
|
|
37
63
|
cachePolicy,
|
|
38
|
-
() => this.http.get(path,
|
|
64
|
+
() => this.http.get(path, normalizedParams) as Promise<PaginatedResponse<Content>>
|
|
39
65
|
);
|
|
40
66
|
}
|
|
41
67
|
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ApiResponse,
|
|
13
13
|
ProductQueryParams,
|
|
14
14
|
} from '../types';
|
|
15
|
+
import { validateOptionalLimit } from '../utils/validators';
|
|
15
16
|
|
|
16
17
|
export class ProductsClient extends BaseClient {
|
|
17
18
|
constructor(http: any, cache?: CacheManager) {
|
|
@@ -41,6 +42,16 @@ export class ProductsClient extends BaseClient {
|
|
|
41
42
|
const normalizedParams: ProductQueryParams | undefined = params ? { ...params } : undefined;
|
|
42
43
|
|
|
43
44
|
if (normalizedParams) {
|
|
45
|
+
const validatedLimit = validateOptionalLimit(
|
|
46
|
+
normalizedParams.limit,
|
|
47
|
+
'products query',
|
|
48
|
+
);
|
|
49
|
+
if (validatedLimit !== undefined) {
|
|
50
|
+
normalizedParams.limit = validatedLimit;
|
|
51
|
+
} else {
|
|
52
|
+
delete normalizedParams.limit;
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
const normalizedCategories = normalizeList(normalizedParams.category as any);
|
|
45
56
|
if (normalizedCategories !== undefined) {
|
|
46
57
|
normalizedParams.category = normalizedCategories;
|
package/src/loaders.ts
CHANGED
|
@@ -17,8 +17,15 @@ import type {
|
|
|
17
17
|
BlogPost,
|
|
18
18
|
CreateCheckoutSessionRequest,
|
|
19
19
|
CheckoutSession,
|
|
20
|
-
CheckoutMetadata
|
|
20
|
+
CheckoutMetadata,
|
|
21
|
+
CheckoutTaxRequest,
|
|
22
|
+
CheckoutAddress
|
|
21
23
|
} from './types';
|
|
24
|
+
import {
|
|
25
|
+
MAX_API_QUERY_LIMIT,
|
|
26
|
+
validateLimit,
|
|
27
|
+
validateOptionalContentType
|
|
28
|
+
} from './utils/validators';
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
31
|
* Logger interface so consumers can supply custom logging behaviour (or noop).
|
|
@@ -311,13 +318,18 @@ export async function loadProducts(options: LoadProductsOptions): Promise<Produc
|
|
|
311
318
|
siteName,
|
|
312
319
|
logger = noopLogger,
|
|
313
320
|
fallbackProducts,
|
|
314
|
-
limit
|
|
321
|
+
limit: requestedLimit,
|
|
315
322
|
offset,
|
|
316
323
|
search,
|
|
317
324
|
category,
|
|
318
325
|
categoryIds
|
|
319
326
|
} = options;
|
|
320
327
|
|
|
328
|
+
const resolvedLimit = validateLimit(
|
|
329
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
330
|
+
'products query',
|
|
331
|
+
);
|
|
332
|
+
|
|
321
333
|
if (!client) {
|
|
322
334
|
log(logger, 'warn', '[PerspectAPI] No client configured, using fallback products');
|
|
323
335
|
return resolveFallbackProducts({ siteName, fallbackProducts });
|
|
@@ -327,7 +339,7 @@ export async function loadProducts(options: LoadProductsOptions): Promise<Produc
|
|
|
327
339
|
log(logger, 'info', `[PerspectAPI] Loading products for site "${siteName}"`);
|
|
328
340
|
const queryParams: ProductQueryParams = {
|
|
329
341
|
isActive: true,
|
|
330
|
-
limit,
|
|
342
|
+
limit: resolvedLimit,
|
|
331
343
|
offset,
|
|
332
344
|
search
|
|
333
345
|
};
|
|
@@ -427,13 +439,23 @@ export async function loadPages(
|
|
|
427
439
|
|
|
428
440
|
try {
|
|
429
441
|
log(logger, 'info', `[PerspectAPI] Loading pages for site "${siteName}"`);
|
|
442
|
+
const requestedLimit =
|
|
443
|
+
options.limit !== undefined ? options.limit : params?.limit;
|
|
444
|
+
const resolvedLimit = validateLimit(
|
|
445
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
446
|
+
'pages query',
|
|
447
|
+
);
|
|
448
|
+
const requestedPageType = params?.page_type;
|
|
449
|
+
const validatedPageType =
|
|
450
|
+
validateOptionalContentType(requestedPageType, 'pages query') ?? 'page';
|
|
451
|
+
|
|
430
452
|
const response: PaginatedResponse<Content> = await client.content.getContent(
|
|
431
453
|
siteName,
|
|
432
454
|
{
|
|
433
455
|
...params,
|
|
434
456
|
page_status: (params?.page_status ?? 'publish') as ContentStatus,
|
|
435
|
-
page_type:
|
|
436
|
-
limit:
|
|
457
|
+
page_type: validatedPageType as ContentType,
|
|
458
|
+
limit: resolvedLimit
|
|
437
459
|
}
|
|
438
460
|
);
|
|
439
461
|
|
|
@@ -463,13 +485,23 @@ export async function loadPosts(
|
|
|
463
485
|
|
|
464
486
|
try {
|
|
465
487
|
log(logger, 'info', `[PerspectAPI] Loading posts for site "${siteName}"`);
|
|
488
|
+
const requestedLimit =
|
|
489
|
+
options.limit !== undefined ? options.limit : params?.limit;
|
|
490
|
+
const resolvedLimit = validateLimit(
|
|
491
|
+
requestedLimit ?? MAX_API_QUERY_LIMIT,
|
|
492
|
+
'posts query',
|
|
493
|
+
);
|
|
494
|
+
const requestedPageType = params?.page_type;
|
|
495
|
+
const validatedPageType =
|
|
496
|
+
validateOptionalContentType(requestedPageType, 'posts query') ?? 'post';
|
|
497
|
+
|
|
466
498
|
const response: PaginatedResponse<Content> = await client.content.getContent(
|
|
467
499
|
siteName,
|
|
468
500
|
{
|
|
469
501
|
...params,
|
|
470
502
|
page_status: (params?.page_status ?? 'publish') as ContentStatus,
|
|
471
|
-
page_type:
|
|
472
|
-
limit:
|
|
503
|
+
page_type: validatedPageType as ContentType,
|
|
504
|
+
limit: resolvedLimit
|
|
473
505
|
}
|
|
474
506
|
);
|
|
475
507
|
|
|
@@ -554,10 +586,30 @@ export interface CheckoutSessionOptions {
|
|
|
554
586
|
logger?: LoaderLogger;
|
|
555
587
|
fallbackProducts?: Product[];
|
|
556
588
|
metadata?: CheckoutMetadata;
|
|
589
|
+
/**
|
|
590
|
+
* Optional currency override; defaults to the currency defined on each Stripe price.
|
|
591
|
+
*/
|
|
592
|
+
currency?: string;
|
|
593
|
+
/**
|
|
594
|
+
* Optional shipping amount (in the smallest currency unit).
|
|
595
|
+
*/
|
|
596
|
+
shippingAmount?: number;
|
|
597
|
+
/**
|
|
598
|
+
* Shipping address forwarded to the checkout API for tax estimation.
|
|
599
|
+
*/
|
|
600
|
+
shippingAddress?: CheckoutAddress;
|
|
601
|
+
/**
|
|
602
|
+
* Billing address forwarded to the checkout API for tax estimation.
|
|
603
|
+
*/
|
|
604
|
+
billingAddress?: CheckoutAddress;
|
|
557
605
|
/**
|
|
558
606
|
* Optional resolver to convert a product record into a Stripe price ID.
|
|
559
607
|
*/
|
|
560
608
|
priceIdResolver?: (product: Product, mode: 'live' | 'test') => string | undefined;
|
|
609
|
+
/**
|
|
610
|
+
* Optional tax configuration that will be forwarded to the checkout API.
|
|
611
|
+
*/
|
|
612
|
+
tax?: CheckoutTaxRequest;
|
|
561
613
|
}
|
|
562
614
|
|
|
563
615
|
/**
|
|
@@ -578,7 +630,12 @@ export async function createCheckoutSession(
|
|
|
578
630
|
logger = noopLogger,
|
|
579
631
|
fallbackProducts,
|
|
580
632
|
metadata,
|
|
581
|
-
|
|
633
|
+
currency,
|
|
634
|
+
shippingAmount,
|
|
635
|
+
shippingAddress,
|
|
636
|
+
billingAddress,
|
|
637
|
+
priceIdResolver,
|
|
638
|
+
tax
|
|
582
639
|
} = options;
|
|
583
640
|
|
|
584
641
|
if (!client) {
|
|
@@ -592,7 +649,7 @@ export async function createCheckoutSession(
|
|
|
592
649
|
client,
|
|
593
650
|
siteName,
|
|
594
651
|
logger,
|
|
595
|
-
limit:
|
|
652
|
+
limit: Math.min(Math.max(items.length, 1), MAX_API_QUERY_LIMIT),
|
|
596
653
|
fallbackProducts
|
|
597
654
|
});
|
|
598
655
|
|
|
@@ -623,6 +680,21 @@ export async function createCheckoutSession(
|
|
|
623
680
|
};
|
|
624
681
|
});
|
|
625
682
|
|
|
683
|
+
const resolvedTax: CheckoutTaxRequest | undefined = (() => {
|
|
684
|
+
if (!tax && !customerEmail) {
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!tax) {
|
|
689
|
+
return { customer_identifier: customerEmail };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return {
|
|
693
|
+
...tax,
|
|
694
|
+
customer_identifier: tax.customer_identifier ?? customerEmail ?? undefined,
|
|
695
|
+
};
|
|
696
|
+
})();
|
|
697
|
+
|
|
626
698
|
const checkoutData: CreateCheckoutSessionRequest = {
|
|
627
699
|
line_items,
|
|
628
700
|
successUrl,
|
|
@@ -632,9 +704,32 @@ export async function createCheckoutSession(
|
|
|
632
704
|
customerEmail,
|
|
633
705
|
customer_email: customerEmail,
|
|
634
706
|
mode: mode === 'test' ? 'payment' : 'payment',
|
|
635
|
-
metadata
|
|
707
|
+
metadata,
|
|
636
708
|
};
|
|
637
709
|
|
|
710
|
+
if (currency) {
|
|
711
|
+
checkoutData.currency = currency;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (typeof shippingAmount === 'number') {
|
|
715
|
+
checkoutData.shipping_amount = shippingAmount;
|
|
716
|
+
checkoutData.shippingAmount = shippingAmount;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (shippingAddress) {
|
|
720
|
+
checkoutData.shipping_address = shippingAddress;
|
|
721
|
+
checkoutData.shippingAddress = shippingAddress;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (billingAddress) {
|
|
725
|
+
checkoutData.billing_address = billingAddress;
|
|
726
|
+
checkoutData.billingAddress = billingAddress;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (resolvedTax) {
|
|
730
|
+
checkoutData.tax = resolvedTax;
|
|
731
|
+
}
|
|
732
|
+
|
|
638
733
|
log(logger, 'info', '[PerspectAPI] Creating checkout session');
|
|
639
734
|
return client.checkout.createCheckoutSession(siteName, checkoutData);
|
|
640
735
|
} catch (error) {
|
package/src/types/index.ts
CHANGED
|
@@ -399,6 +399,54 @@ export type CheckoutMetadataValue =
|
|
|
399
399
|
|
|
400
400
|
export type CheckoutMetadata = Record<string, CheckoutMetadataValue>;
|
|
401
401
|
|
|
402
|
+
export type CheckoutTaxStrategy =
|
|
403
|
+
| 'disabled'
|
|
404
|
+
| 'gateway_auto'
|
|
405
|
+
| 'manual_rates'
|
|
406
|
+
| 'external_service';
|
|
407
|
+
|
|
408
|
+
export type CheckoutTaxExemptionStatus =
|
|
409
|
+
| 'none'
|
|
410
|
+
| 'exempt'
|
|
411
|
+
| 'reverse_charge';
|
|
412
|
+
|
|
413
|
+
export interface CheckoutAddress {
|
|
414
|
+
line1?: string;
|
|
415
|
+
line2?: string;
|
|
416
|
+
city?: string;
|
|
417
|
+
state?: string;
|
|
418
|
+
postal_code?: string;
|
|
419
|
+
country?: string;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface CheckoutTaxCustomerExemptionRequest {
|
|
423
|
+
status?: CheckoutTaxExemptionStatus;
|
|
424
|
+
reason?: string;
|
|
425
|
+
tax_id?: string;
|
|
426
|
+
tax_id_type?: string;
|
|
427
|
+
certificate_url?: string;
|
|
428
|
+
metadata?: Record<string, any>;
|
|
429
|
+
expires_at?: string;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export interface CheckoutTaxRequest {
|
|
433
|
+
strategy?: CheckoutTaxStrategy;
|
|
434
|
+
customer_identifier?: string;
|
|
435
|
+
customer_profile_id?: string;
|
|
436
|
+
customer_display_name?: string;
|
|
437
|
+
allow_exemption?: boolean;
|
|
438
|
+
require_tax_id?: boolean;
|
|
439
|
+
save_profile?: boolean;
|
|
440
|
+
customer_exemption?: CheckoutTaxCustomerExemptionRequest;
|
|
441
|
+
manual_rate_percent?: number;
|
|
442
|
+
manual_rate_map?: Record<string, number>;
|
|
443
|
+
external_service?: {
|
|
444
|
+
provider: string;
|
|
445
|
+
config?: Record<string, any>;
|
|
446
|
+
};
|
|
447
|
+
metadata?: Record<string, any>;
|
|
448
|
+
}
|
|
449
|
+
|
|
402
450
|
export interface CreateCheckoutSessionRequest {
|
|
403
451
|
// Single item checkout (legacy/simple)
|
|
404
452
|
priceId?: string;
|
|
@@ -428,6 +476,7 @@ export interface CreateCheckoutSessionRequest {
|
|
|
428
476
|
cancelUrl?: string; // Alternative naming
|
|
429
477
|
customer_email?: string;
|
|
430
478
|
customerEmail?: string; // Alternative naming
|
|
479
|
+
currency?: string;
|
|
431
480
|
metadata?: CheckoutMetadata;
|
|
432
481
|
mode?: 'payment' | 'subscription' | 'setup';
|
|
433
482
|
automatic_tax?: {
|
|
@@ -437,12 +486,38 @@ export interface CreateCheckoutSessionRequest {
|
|
|
437
486
|
allowed_countries: string[];
|
|
438
487
|
};
|
|
439
488
|
billing_address_collection?: 'auto' | 'required';
|
|
489
|
+
shipping_amount?: number;
|
|
490
|
+
shippingAmount?: number;
|
|
491
|
+
shipping_address?: CheckoutAddress;
|
|
492
|
+
shippingAddress?: CheckoutAddress;
|
|
493
|
+
billing_address?: CheckoutAddress;
|
|
494
|
+
billingAddress?: CheckoutAddress;
|
|
495
|
+
tax?: CheckoutTaxRequest;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export interface CheckoutTaxBreakdownItem {
|
|
499
|
+
jurisdiction: string;
|
|
500
|
+
rate_percent: number;
|
|
501
|
+
tax_amount: number;
|
|
502
|
+
taxable_amount: number;
|
|
503
|
+
source: 'manual_map' | 'manual_percent' | 'gateway' | 'external';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export interface CheckoutSessionTax {
|
|
507
|
+
amount: number;
|
|
508
|
+
currency: string;
|
|
509
|
+
strategy: CheckoutTaxStrategy;
|
|
510
|
+
exemption_applied: boolean;
|
|
511
|
+
exemption_status: CheckoutTaxExemptionStatus;
|
|
512
|
+
breakdown?: CheckoutTaxBreakdownItem[] | null;
|
|
440
513
|
}
|
|
441
514
|
|
|
442
515
|
export interface CheckoutSession {
|
|
443
516
|
id: string;
|
|
444
517
|
url: string;
|
|
445
518
|
status: string;
|
|
519
|
+
payment_status?: string;
|
|
520
|
+
tax?: CheckoutSessionTax | null;
|
|
446
521
|
}
|
|
447
522
|
|
|
448
523
|
// Contact Forms
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ContentType } from '../types';
|
|
2
|
+
|
|
3
|
+
export const MAX_API_QUERY_LIMIT = 100;
|
|
4
|
+
const ALLOWED_CONTENT_TYPES: ReadonlyArray<ContentType> = ['post', 'page'];
|
|
5
|
+
|
|
6
|
+
export function validateLimit(limit: number, context: string): number {
|
|
7
|
+
if (typeof limit !== 'number' || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
8
|
+
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (limit < 1) {
|
|
12
|
+
throw new Error(`[PerspectAPI] ${context} limit must be at least 1.`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (limit > MAX_API_QUERY_LIMIT) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`[PerspectAPI] ${context} limit ${limit} exceeds the maximum allowed value of ${MAX_API_QUERY_LIMIT}.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return limit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function validateOptionalLimit(
|
|
25
|
+
limit: number | undefined,
|
|
26
|
+
context: string,
|
|
27
|
+
): number | undefined {
|
|
28
|
+
if (limit === undefined || limit === null) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return validateLimit(limit, context);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateOptionalContentType(
|
|
36
|
+
pageType: string | undefined,
|
|
37
|
+
context: string,
|
|
38
|
+
): ContentType | undefined {
|
|
39
|
+
if (pageType === undefined || pageType === null) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (ALLOWED_CONTENT_TYPES.includes(pageType as ContentType)) {
|
|
44
|
+
return pageType as ContentType;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(
|
|
48
|
+
`[PerspectAPI] ${context} received unsupported page_type "${pageType}". Allowed values are ${ALLOWED_CONTENT_TYPES.join(
|
|
49
|
+
', ',
|
|
50
|
+
)}.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|