perspectapi-ts-sdk 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,6 +82,72 @@ const checkout = await createCheckoutSession({
82
82
  ```
83
83
 
84
84
  > 📚 See [docs/loaders.md](docs/loaders.md) for full walkthroughs, including fallback data, custom logging, and Stripe price resolution.
85
+
86
+ ## Configuring Caching
87
+
88
+ Pass a `cache` block when you create the client to enable caching. The SDK falls back to an in-memory store in development; in production you can supply any adapter that satisfies the `CacheAdapter` interface.
89
+
90
+ ```ts
91
+ import { PerspectApiClient } from 'perspectapi-ts-sdk';
92
+ import { myCacheAdapter } from './cache-adapter';
93
+
94
+ const perspect = new PerspectApiClient({
95
+ baseUrl: 'https://api.perspect.co',
96
+ apiKey: env.PERSPECT_API_KEY,
97
+ cache: {
98
+ adapter: myCacheAdapter,
99
+ defaultTtlSeconds: 300,
100
+ keyPrefix: 'storefront', // optional namespace
101
+ },
102
+ });
103
+ ```
104
+
105
+ ### Cloudflare Workers KV example
106
+
107
+ KV namespaces make a great globally distributed cache. Bind a namespace (e.g. `PERSPECT_CACHE`) in your Worker and wrap it with a small adapter:
108
+
109
+ ```ts
110
+ // worker.ts
111
+ import { PerspectApiClient, type CacheAdapter } from 'perspectapi-ts-sdk';
112
+
113
+ const kvAdapter = (kv: KVNamespace): CacheAdapter => ({
114
+ async get(key) {
115
+ const value = await kv.get(key);
116
+ return value ?? undefined;
117
+ },
118
+ async set(key, value, options) {
119
+ const ttl = options?.ttlSeconds;
120
+ await kv.put(key, value, ttl ? { expirationTtl: ttl } : undefined);
121
+ },
122
+ async delete(key) {
123
+ await kv.delete(key);
124
+ },
125
+ async deleteMany(keys) {
126
+ await Promise.all(keys.map(key => kv.delete(key)));
127
+ },
128
+ });
129
+
130
+ export default {
131
+ async fetch(request: Request, env: Env) {
132
+ const perspect = new PerspectApiClient({
133
+ baseUrl: env.PERSPECT_API_URL,
134
+ apiKey: env.PERSPECT_API_KEY,
135
+ cache: {
136
+ adapter: kvAdapter(env.PERSPECT_CACHE),
137
+ defaultTtlSeconds: 300,
138
+ keyPrefix: 'storefront',
139
+ },
140
+ });
141
+
142
+ const products = await perspect.products.getProducts('museum-indian-art');
143
+ return new Response(JSON.stringify(products.data), {
144
+ headers: { 'Content-Type': 'application/json' },
145
+ });
146
+ },
147
+ };
148
+ ```
149
+
150
+ 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>`, etc.) so stale entries are purged immediately.
85
151
  ```
86
152
 
87
153
  ## Image Transformations
package/dist/index.d.mts CHANGED
@@ -1,6 +1,91 @@
1
+ /**
2
+ * Cache-related types for the PerspectAPI SDK.
3
+ */
4
+ type CacheKeyPart = string | number | boolean | null | undefined | Record<string, unknown> | Array<Record<string, unknown> | string | number | boolean | null | undefined>;
5
+ interface CacheSetOptions {
6
+ ttlSeconds?: number;
7
+ tags?: string[];
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ interface CachePolicy extends CacheSetOptions {
11
+ skipCache?: boolean;
12
+ }
13
+ interface CacheAdapter {
14
+ /**
15
+ * Retrieve a raw cache payload for the given key. Implementations should return
16
+ * undefined (or null) when the key does not exist or has expired.
17
+ */
18
+ get(key: string): Promise<string | undefined | null>;
19
+ /**
20
+ * Store a raw payload for the given key. Implementations MAY honour ttlSeconds
21
+ * natively; the cache manager will also persist expiry timestamps in the payload
22
+ * for adapters that do not have native TTL support.
23
+ */
24
+ set(key: string, value: string, options?: {
25
+ ttlSeconds?: number;
26
+ }): Promise<void>;
27
+ /**
28
+ * Remove a cached entry.
29
+ */
30
+ delete(key: string): Promise<void>;
31
+ /**
32
+ * Optional bulk delete implementation.
33
+ */
34
+ deleteMany?(keys: string[]): Promise<void>;
35
+ /**
36
+ * Optional clear method to wipe all entries for adapters that manage isolated namespaces.
37
+ */
38
+ clear?(): Promise<void>;
39
+ }
40
+ interface CacheManagerOptions {
41
+ defaultTtlSeconds?: number;
42
+ keyPrefix?: string;
43
+ }
44
+ interface CacheConfig extends CacheManagerOptions {
45
+ /**
46
+ * Explicit flag to disable caching. Defaults to false when a cache configuration
47
+ * is supplied, so caching is considered enabled unless explicitly disabled.
48
+ */
49
+ enabled?: boolean;
50
+ adapter?: CacheAdapter;
51
+ }
52
+ interface CacheInvalidateOptions {
53
+ keys?: string[];
54
+ tags?: string[];
55
+ }
56
+
57
+ /**
58
+ * CacheManager orchestrates cache get/set/invalidate behaviour using a pluggable adapter.
59
+ */
60
+
61
+ declare class CacheManager {
62
+ private adapter;
63
+ private readonly defaultTtlSeconds;
64
+ private readonly keyPrefix;
65
+ private readonly enabled;
66
+ constructor(config?: CacheConfig);
67
+ isEnabled(): boolean;
68
+ getKeyPrefix(): string;
69
+ buildKey(parts: CacheKeyPart[]): string;
70
+ getOrSet<T>(key: string, resolveValue: () => Promise<T>, policy?: CachePolicy): Promise<T>;
71
+ set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>;
72
+ delete(key: string): Promise<void>;
73
+ invalidate(options: CacheInvalidateOptions): Promise<void>;
74
+ private namespacedKey;
75
+ private tagKey;
76
+ private serialize;
77
+ private deserialize;
78
+ private deserializeTagSet;
79
+ private registerKeyTags;
80
+ private removeKeyFromTags;
81
+ private normalizeKeyPart;
82
+ private normalizeObject;
83
+ }
84
+
1
85
  /**
2
86
  * Core types and interfaces for PerspectAPI SDK
3
87
  */
88
+
4
89
  interface ApiResponse<T = any> {
5
90
  data?: T;
6
91
  message?: string;
@@ -420,6 +505,7 @@ interface PerspectApiConfig {
420
505
  timeout?: number;
421
506
  retries?: number;
422
507
  headers?: Record<string, string>;
508
+ cache?: CacheConfig;
423
509
  }
424
510
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
425
511
  interface RequestOptions {
@@ -511,7 +597,8 @@ declare function createApiError(error: unknown): ApiError;
511
597
  declare abstract class BaseClient {
512
598
  protected http: HttpClient;
513
599
  protected basePath: string;
514
- constructor(http: HttpClient, basePath: string);
600
+ protected cache?: CacheManager;
601
+ constructor(http: HttpClient, basePath: string, cache?: CacheManager);
515
602
  /**
516
603
  * Build a site-scoped endpoint relative to the API base path
517
604
  */
@@ -546,6 +633,20 @@ declare abstract class BaseClient {
546
633
  * Handle delete operations
547
634
  */
548
635
  protected delete<T = any>(endpoint: string, csrfToken?: string): Promise<ApiResponse<T>>;
636
+ /**
637
+ * Fetch a GET endpoint with optional caching support.
638
+ */
639
+ protected fetchWithCache<T>(endpoint: string, params: Record<string, any> | undefined, tags: string[], policy: CachePolicy | undefined, fetcher: () => Promise<T>): Promise<T>;
640
+ /**
641
+ * Invalidate cache entries by keys or tags.
642
+ */
643
+ protected invalidateCache(options: CacheInvalidateOptions): Promise<void>;
644
+ /**
645
+ * Build a consistent cache key for an endpoint + params combination.
646
+ */
647
+ protected buildCacheKey(endpoint: string, params?: Record<string, any>): string;
648
+ private mergeTags;
649
+ private sortObject;
549
650
  }
550
651
 
551
652
  /**
@@ -553,7 +654,7 @@ declare abstract class BaseClient {
553
654
  */
554
655
 
555
656
  declare class AuthClient extends BaseClient {
556
- constructor(http: any);
657
+ constructor(http: any, cache?: CacheManager);
557
658
  /**
558
659
  * Get CSRF token
559
660
  */
@@ -606,19 +707,19 @@ declare class AuthClient extends BaseClient {
606
707
  */
607
708
 
608
709
  declare class ContentClient extends BaseClient {
609
- constructor(http: any);
710
+ constructor(http: any, cache?: CacheManager);
610
711
  /**
611
712
  * Get all content with pagination and filtering for a site
612
713
  */
613
- getContent(siteName: string, params?: ContentQueryParams): Promise<PaginatedResponse<Content>>;
714
+ getContent(siteName: string, params?: ContentQueryParams, cachePolicy?: CachePolicy): Promise<PaginatedResponse<Content>>;
614
715
  /**
615
716
  * Get content by ID
616
717
  */
617
- getContentById(id: number): Promise<ApiResponse<Content>>;
718
+ getContentById(id: number, cachePolicy?: CachePolicy): Promise<ApiResponse<Content>>;
618
719
  /**
619
720
  * Get content by slug for a site
620
721
  */
621
- getContentBySlug(siteName: string, slug: string): Promise<ApiResponse<Content>>;
722
+ getContentBySlug(siteName: string, slug: string, cachePolicy?: CachePolicy): Promise<ApiResponse<Content>>;
622
723
  /**
623
724
  * Create new content
624
725
  */
@@ -661,6 +762,7 @@ declare class ContentClient extends BaseClient {
661
762
  * Duplicate content
662
763
  */
663
764
  duplicateContent(id: number): Promise<ApiResponse<Content>>;
765
+ private buildContentTags;
664
766
  }
665
767
 
666
768
  /**
@@ -668,7 +770,7 @@ declare class ContentClient extends BaseClient {
668
770
  */
669
771
 
670
772
  declare class ApiKeysClient extends BaseClient {
671
- constructor(http: any);
773
+ constructor(http: any, cache?: CacheManager);
672
774
  /**
673
775
  * Get all API keys
674
776
  */
@@ -735,7 +837,7 @@ declare class ApiKeysClient extends BaseClient {
735
837
  */
736
838
 
737
839
  declare class OrganizationsClient extends BaseClient {
738
- constructor(http: any);
840
+ constructor(http: any, cache?: CacheManager);
739
841
  /**
740
842
  * Get all organizations
741
843
  */
@@ -810,7 +912,7 @@ declare class OrganizationsClient extends BaseClient {
810
912
  */
811
913
 
812
914
  declare class SitesClient extends BaseClient {
813
- constructor(http: any);
915
+ constructor(http: any, cache?: CacheManager);
814
916
  /**
815
917
  * Get all sites
816
918
  */
@@ -913,23 +1015,23 @@ declare class SitesClient extends BaseClient {
913
1015
  */
914
1016
 
915
1017
  declare class ProductsClient extends BaseClient {
916
- constructor(http: any);
1018
+ constructor(http: any, cache?: CacheManager);
917
1019
  /**
918
1020
  * Get all products for a site
919
1021
  */
920
- getProducts(siteName: string, params?: ProductQueryParams): Promise<PaginatedResponse<Product>>;
1022
+ getProducts(siteName: string, params?: ProductQueryParams, cachePolicy?: CachePolicy): Promise<PaginatedResponse<Product>>;
921
1023
  /**
922
1024
  * Get product by ID
923
1025
  */
924
- getProductById(id: number): Promise<ApiResponse<Product>>;
1026
+ getProductById(id: number, cachePolicy?: CachePolicy): Promise<ApiResponse<Product>>;
925
1027
  /**
926
1028
  * Get product by SKU
927
1029
  */
928
- getProductBySku(sku: string): Promise<ApiResponse<Product>>;
1030
+ getProductBySku(sku: string, cachePolicy?: CachePolicy): Promise<ApiResponse<Product>>;
929
1031
  /**
930
1032
  * Get product by slug and site name
931
1033
  */
932
- getProductBySlug(siteName: string, slug: string): Promise<ApiResponse<Product & {
1034
+ getProductBySlug(siteName: string, slug: string, cachePolicy?: CachePolicy): Promise<ApiResponse<Product & {
933
1035
  variants?: any[];
934
1036
  }>>;
935
1037
  /**
@@ -1044,7 +1146,7 @@ declare class ProductsClient extends BaseClient {
1044
1146
  limit?: number;
1045
1147
  published?: boolean;
1046
1148
  search?: string;
1047
- }): Promise<ApiResponse<{
1149
+ }, cachePolicy?: CachePolicy): Promise<ApiResponse<{
1048
1150
  data: Product[];
1049
1151
  category: {
1050
1152
  id: number;
@@ -1058,6 +1160,7 @@ declare class ProductsClient extends BaseClient {
1058
1160
  offset?: number;
1059
1161
  };
1060
1162
  }>>;
1163
+ private buildProductTags;
1061
1164
  }
1062
1165
 
1063
1166
  /**
@@ -1065,7 +1168,7 @@ declare class ProductsClient extends BaseClient {
1065
1168
  */
1066
1169
 
1067
1170
  declare class CategoriesClient extends BaseClient {
1068
- constructor(http: any);
1171
+ constructor(http: any, cache?: CacheManager);
1069
1172
  /**
1070
1173
  * Get all categories
1071
1174
  */
@@ -1086,7 +1189,7 @@ declare class CategoriesClient extends BaseClient {
1086
1189
  /**
1087
1190
  * Get product category by slug (for products)
1088
1191
  */
1089
- getProductCategoryBySlug(siteName: string, slug: string): Promise<ApiResponse<Category>>;
1192
+ getProductCategoryBySlug(siteName: string, slug: string, cachePolicy?: CachePolicy): Promise<ApiResponse<Category>>;
1090
1193
  /**
1091
1194
  * Create new category
1092
1195
  */
@@ -1156,6 +1259,7 @@ declare class CategoriesClient extends BaseClient {
1156
1259
  limit?: number;
1157
1260
  organizationId?: number;
1158
1261
  }): Promise<ApiResponse<Category[]>>;
1262
+ private buildCategoryTags;
1159
1263
  }
1160
1264
 
1161
1265
  /**
@@ -1163,7 +1267,7 @@ declare class CategoriesClient extends BaseClient {
1163
1267
  */
1164
1268
 
1165
1269
  declare class WebhooksClient extends BaseClient {
1166
- constructor(http: any);
1270
+ constructor(http: any, cache?: CacheManager);
1167
1271
  /**
1168
1272
  * Get all webhooks
1169
1273
  */
@@ -1309,7 +1413,7 @@ declare class WebhooksClient extends BaseClient {
1309
1413
  */
1310
1414
 
1311
1415
  declare class CheckoutClient extends BaseClient {
1312
- constructor(http: any);
1416
+ constructor(http: any, cache?: CacheManager);
1313
1417
  /**
1314
1418
  * Get CSRF token for a specific site
1315
1419
  * @param siteName - The site name to get CSRF token for
@@ -1482,7 +1586,7 @@ declare class CheckoutClient extends BaseClient {
1482
1586
  */
1483
1587
 
1484
1588
  declare class ContactClient extends BaseClient {
1485
- constructor(http: any);
1589
+ constructor(http: any, cache?: CacheManager);
1486
1590
  /**
1487
1591
  * Build a contact endpoint scoped to a site (without /sites prefix)
1488
1592
  */
@@ -1629,7 +1733,7 @@ declare class ContactClient extends BaseClient {
1629
1733
  */
1630
1734
 
1631
1735
  declare class NewsletterClient extends BaseClient {
1632
- constructor(http: any);
1736
+ constructor(http: any, cache?: CacheManager);
1633
1737
  /**
1634
1738
  * Build a newsletter endpoint scoped to a site (without /sites prefix)
1635
1739
  */
@@ -1831,6 +1935,7 @@ declare class NewsletterClient extends BaseClient {
1831
1935
 
1832
1936
  declare class PerspectApiClient {
1833
1937
  private http;
1938
+ readonly cache: CacheManager;
1834
1939
  readonly auth: AuthClient;
1835
1940
  readonly content: ContentClient;
1836
1941
  readonly apiKeys: ApiKeysClient;
@@ -1928,6 +2033,33 @@ declare class PerspectApiClient {
1928
2033
  */
1929
2034
  declare function createPerspectApiClient(config: PerspectApiConfig): PerspectApiClient;
1930
2035
 
2036
+ /**
2037
+ * Simple in-memory cache adapter primarily suited for development and testing.
2038
+ */
2039
+
2040
+ declare class InMemoryCacheAdapter implements CacheAdapter {
2041
+ private store;
2042
+ get(key: string): Promise<string | undefined>;
2043
+ set(key: string, value: string, options?: {
2044
+ ttlSeconds?: number;
2045
+ }): Promise<void>;
2046
+ delete(key: string): Promise<void>;
2047
+ deleteMany(keys: string[]): Promise<void>;
2048
+ clear(): Promise<void>;
2049
+ }
2050
+
2051
+ /**
2052
+ * No-op cache adapter that disables caching while preserving the cache contract.
2053
+ */
2054
+
2055
+ declare class NoopCacheAdapter implements CacheAdapter {
2056
+ get(): Promise<undefined>;
2057
+ set(): Promise<void>;
2058
+ delete(): Promise<void>;
2059
+ deleteMany(): Promise<void>;
2060
+ clear(): Promise<void>;
2061
+ }
2062
+
1931
2063
  /**
1932
2064
  * Cloudflare Image Resizing Integration
1933
2065
  * Transforms images on-the-fly using Cloudflare's Image Resizing service
@@ -2199,4 +2331,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
2199
2331
  error: string;
2200
2332
  }>;
2201
2333
 
2202
- export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, 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, 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, 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 };
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 };