perspectapi-ts-sdk 5.0.3 → 5.2.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.
@@ -1,8 +1,10 @@
1
1
  /**
2
- * v2 Base Client — cursor pagination, expand support, typed errors.
2
+ * v2 Base Client — cursor pagination, expand support, caching, typed errors.
3
3
  */
4
4
 
5
5
  import { HttpClient } from '../../utils/http-client';
6
+ import type { CacheManager } from '../../cache/cache-manager';
7
+ import type { CachePolicy, CacheInvalidateOptions } from '../../cache/types';
6
8
  import type { V2List, V2Error, V2Deleted } from '../types';
7
9
 
8
10
  export class PerspectV2Error extends Error {
@@ -24,10 +26,12 @@ export class PerspectV2Error extends Error {
24
26
  export abstract class BaseV2Client {
25
27
  protected http: HttpClient;
26
28
  protected basePath: string;
29
+ protected cache?: CacheManager;
27
30
 
28
- constructor(http: HttpClient, basePath: string) {
31
+ constructor(http: HttpClient, basePath: string, cache?: CacheManager) {
29
32
  this.http = http;
30
33
  this.basePath = basePath;
34
+ this.cache = cache && cache.isEnabled() ? cache : undefined;
31
35
  }
32
36
 
33
37
  protected buildPath(endpoint: string): string {
@@ -77,19 +81,26 @@ export abstract class BaseV2Client {
77
81
  return payload as T;
78
82
  }
79
83
 
80
- /** GET a single resource. */
81
- protected async getOne<T>(path: string, params?: object): Promise<T> {
82
- const response = await this.http.get<T>(path, this.toParams(params));
83
- return this.extractData<T>(response);
84
+ /** GET a single resource, with optional caching. */
85
+ protected async getOne<T>(path: string, params?: object, cachePolicy?: CachePolicy): Promise<T> {
86
+ const fetcher = async () => {
87
+ const response = await this.http.get<T>(path, this.toParams(params));
88
+ return this.extractData<T>(response);
89
+ };
90
+ return this.fetchWithCache(path, params, cachePolicy, fetcher);
84
91
  }
85
92
 
86
- /** GET a list of resources with cursor pagination. */
93
+ /** GET a list of resources with cursor pagination, with optional caching. */
87
94
  protected async getList<T>(
88
95
  path: string,
89
96
  params?: object,
97
+ cachePolicy?: CachePolicy,
90
98
  ): Promise<V2List<T>> {
91
- const response = await this.http.get<V2List<T>>(path, this.toParams(params));
92
- return this.extractData<V2List<T>>(response);
99
+ const fetcher = async () => {
100
+ const response = await this.http.get<V2List<T>>(path, this.toParams(params));
101
+ return this.extractData<V2List<T>>(response);
102
+ };
103
+ return this.fetchWithCache(path, params, cachePolicy, fetcher);
93
104
  }
94
105
 
95
106
  /** POST to create a resource. */
@@ -110,6 +121,41 @@ export abstract class BaseV2Client {
110
121
  return this.extractData<V2Deleted>(response);
111
122
  }
112
123
 
124
+ /** Fetch with optional cache. Bypasses cache for writes or when no cache is configured. */
125
+ private async fetchWithCache<T>(
126
+ path: string,
127
+ params: object | undefined,
128
+ policy: CachePolicy | undefined,
129
+ fetcher: () => Promise<T>,
130
+ ): Promise<T> {
131
+ if (!this.cache || policy?.skipCache) {
132
+ return fetcher();
133
+ }
134
+ const key = this.buildCacheKey(path, params);
135
+ return this.cache.getOrSet<T>(key, fetcher, policy);
136
+ }
137
+
138
+ /** Invalidate cache entries by keys or tags. */
139
+ protected async invalidateCache(options: CacheInvalidateOptions): Promise<void> {
140
+ if (!this.cache) return;
141
+ await this.cache.invalidate(options);
142
+ }
143
+
144
+ private buildCacheKey(path: string, params?: object): string {
145
+ const parts: Array<string | Record<string, unknown>> = [path];
146
+ if (params && Object.keys(params).length > 0) {
147
+ const sorted: Record<string, unknown> = {};
148
+ for (const key of Object.keys(params).sort()) {
149
+ sorted[key] = (params as Record<string, unknown>)[key];
150
+ }
151
+ parts.push(sorted);
152
+ }
153
+ if (this.cache) {
154
+ return this.cache.buildKey(parts);
155
+ }
156
+ return parts.map(p => typeof p === 'string' ? p : JSON.stringify(p)).join(':');
157
+ }
158
+
113
159
  /**
114
160
  * Auto-paginating async generator.
115
161
  * Yields every item across all pages.
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { BaseV2Client } from './base-v2-client';
6
+ import type { CachePolicy } from '../../cache/types';
6
7
  import type {
7
8
  V2Category, V2CategoryCreateParams, V2CategoryUpdateParams,
8
9
  V2PaginationParams, V2List, V2Deleted,
@@ -10,12 +11,12 @@ import type {
10
11
 
11
12
  export class CategoriesV2Client extends BaseV2Client {
12
13
 
13
- async list(siteName: string, params?: V2PaginationParams & { type?: string }): Promise<V2List<V2Category>> {
14
- return this.getList<V2Category>(this.sitePath(siteName, 'categories'), params);
14
+ async list(siteName: string, params?: V2PaginationParams & { type?: string }, cachePolicy?: CachePolicy): Promise<V2List<V2Category>> {
15
+ return this.getList<V2Category>(this.sitePath(siteName, 'categories'), params, cachePolicy);
15
16
  }
16
17
 
17
- async get(siteName: string, id: string): Promise<V2Category> {
18
- return this.getOne<V2Category>(this.sitePath(siteName, 'categories', id));
18
+ async get(siteName: string, id: string, cachePolicy?: CachePolicy): Promise<V2Category> {
19
+ return this.getOne<V2Category>(this.sitePath(siteName, 'categories', id), undefined, cachePolicy);
19
20
  }
20
21
 
21
22
  async create(siteName: string, data: V2CategoryCreateParams): Promise<V2Category> {
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { BaseV2Client } from './base-v2-client';
6
+ import type { CachePolicy } from '../../cache/types';
6
7
  import type {
7
8
  V2Content, V2ContentCreateParams, V2ContentUpdateParams,
8
9
  V2ContentListParams, V2List, V2Deleted,
@@ -10,16 +11,16 @@ import type {
10
11
 
11
12
  export class ContentV2Client extends BaseV2Client {
12
13
 
13
- async list(siteName: string, params?: V2ContentListParams): Promise<V2List<V2Content>> {
14
- return this.getList<V2Content>(this.sitePath(siteName, 'content'), params);
14
+ async list(siteName: string, params?: V2ContentListParams, cachePolicy?: CachePolicy): Promise<V2List<V2Content>> {
15
+ return this.getList<V2Content>(this.sitePath(siteName, 'content'), params, cachePolicy);
15
16
  }
16
17
 
17
18
  async *listAutoPaginated(siteName: string, params?: Omit<V2ContentListParams, 'starting_after' | 'ending_before'>) {
18
19
  yield* this.listAutoPaginate<V2Content>(this.sitePath(siteName, 'content'), params);
19
20
  }
20
21
 
21
- async get(siteName: string, idOrSlug: string): Promise<V2Content> {
22
- return this.getOne<V2Content>(this.sitePath(siteName, 'content', idOrSlug));
22
+ async get(siteName: string, idOrSlug: string, cachePolicy?: CachePolicy): Promise<V2Content> {
23
+ return this.getOne<V2Content>(this.sitePath(siteName, 'content', idOrSlug), undefined, cachePolicy);
23
24
  }
24
25
 
25
26
  async create(siteName: string, data: V2ContentCreateParams): Promise<V2Content> {
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { BaseV2Client } from './base-v2-client';
6
+ import type { CachePolicy } from '../../cache/types';
6
7
  import type {
7
8
  V2NewsletterSubscription, V2NewsletterList, V2NewsletterCampaign,
8
9
  V2PaginationParams, V2List, V2NewsletterTrackingResponse,
@@ -73,24 +74,30 @@ export class NewsletterV2Client extends BaseV2Client {
73
74
  async listSubscriptions(
74
75
  siteName: string,
75
76
  params?: V2PaginationParams & { status?: string },
77
+ cachePolicy?: CachePolicy,
76
78
  ): Promise<V2List<V2NewsletterSubscription>> {
77
79
  return this.getList<V2NewsletterSubscription>(
78
80
  this.sitePath(siteName, 'newsletter', 'subscriptions'),
79
81
  params,
82
+ cachePolicy,
80
83
  );
81
84
  }
82
85
 
83
- async getSubscription(siteName: string, id: string): Promise<V2NewsletterSubscription> {
86
+ async getSubscription(siteName: string, id: string, cachePolicy?: CachePolicy): Promise<V2NewsletterSubscription> {
84
87
  return this.getOne<V2NewsletterSubscription>(
85
88
  this.sitePath(siteName, 'newsletter', `subscriptions/${id}`),
89
+ undefined,
90
+ cachePolicy,
86
91
  );
87
92
  }
88
93
 
89
94
  // --- Lists ---
90
95
 
91
- async listLists(siteName: string): Promise<V2List<V2NewsletterList>> {
96
+ async listLists(siteName: string, cachePolicy?: CachePolicy): Promise<V2List<V2NewsletterList>> {
92
97
  return this.getList<V2NewsletterList>(
93
98
  this.sitePath(siteName, 'newsletter', 'lists'),
99
+ undefined,
100
+ cachePolicy,
94
101
  );
95
102
  }
96
103
 
@@ -99,16 +106,20 @@ export class NewsletterV2Client extends BaseV2Client {
99
106
  async listCampaigns(
100
107
  siteName: string,
101
108
  params?: V2PaginationParams & { status?: string },
109
+ cachePolicy?: CachePolicy,
102
110
  ): Promise<V2List<V2NewsletterCampaign>> {
103
111
  return this.getList<V2NewsletterCampaign>(
104
112
  this.sitePath(siteName, 'newsletter', 'campaigns'),
105
113
  params,
114
+ cachePolicy,
106
115
  );
107
116
  }
108
117
 
109
- async getCampaign(siteName: string, idOrSlug: string): Promise<V2NewsletterCampaign> {
118
+ async getCampaign(siteName: string, idOrSlug: string, cachePolicy?: CachePolicy): Promise<V2NewsletterCampaign> {
110
119
  return this.getOne<V2NewsletterCampaign>(
111
120
  this.sitePath(siteName, 'newsletter', `campaigns/${idOrSlug}`),
121
+ undefined,
122
+ cachePolicy,
112
123
  );
113
124
  }
114
125
  }
@@ -3,19 +3,20 @@
3
3
  */
4
4
 
5
5
  import { BaseV2Client } from './base-v2-client';
6
+ import type { CachePolicy } from '../../cache/types';
6
7
  import type { V2Order, V2OrderListParams, V2List } from '../types';
7
8
 
8
9
  export class OrdersV2Client extends BaseV2Client {
9
10
 
10
- async list(siteName: string, params?: V2OrderListParams): Promise<V2List<V2Order>> {
11
- return this.getList<V2Order>(this.sitePath(siteName, 'orders'), params);
11
+ async list(siteName: string, params?: V2OrderListParams, cachePolicy?: CachePolicy): Promise<V2List<V2Order>> {
12
+ return this.getList<V2Order>(this.sitePath(siteName, 'orders'), params, cachePolicy);
12
13
  }
13
14
 
14
15
  async *listAutoPaginated(siteName: string, params?: Omit<V2OrderListParams, 'starting_after' | 'ending_before'>) {
15
16
  yield* this.listAutoPaginate<V2Order>(this.sitePath(siteName, 'orders'), params);
16
17
  }
17
18
 
18
- async get(siteName: string, id: string): Promise<V2Order> {
19
- return this.getOne<V2Order>(this.sitePath(siteName, 'orders', id));
19
+ async get(siteName: string, id: string, cachePolicy?: CachePolicy): Promise<V2Order> {
20
+ return this.getOne<V2Order>(this.sitePath(siteName, 'orders', id), undefined, cachePolicy);
20
21
  }
21
22
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { BaseV2Client } from './base-v2-client';
6
+ import type { CachePolicy } from '../../cache/types';
6
7
  import type {
7
8
  V2Product, V2ProductCreateParams, V2ProductUpdateParams,
8
9
  V2ProductListParams, V2List, V2Deleted,
@@ -10,16 +11,16 @@ import type {
10
11
 
11
12
  export class ProductsV2Client extends BaseV2Client {
12
13
 
13
- async list(siteName: string, params?: V2ProductListParams): Promise<V2List<V2Product>> {
14
- return this.getList<V2Product>(this.sitePath(siteName, 'products'), params);
14
+ async list(siteName: string, params?: V2ProductListParams, cachePolicy?: CachePolicy): Promise<V2List<V2Product>> {
15
+ return this.getList<V2Product>(this.sitePath(siteName, 'products'), params, cachePolicy);
15
16
  }
16
17
 
17
18
  async *listAutoPaginated(siteName: string, params?: Omit<V2ProductListParams, 'starting_after' | 'ending_before'>) {
18
19
  yield* this.listAutoPaginate<V2Product>(this.sitePath(siteName, 'products'), params);
19
20
  }
20
21
 
21
- async get(siteName: string, idOrSlug: string): Promise<V2Product> {
22
- return this.getOne<V2Product>(this.sitePath(siteName, 'products', idOrSlug));
22
+ async get(siteName: string, idOrSlug: string, cachePolicy?: CachePolicy): Promise<V2Product> {
23
+ return this.getOne<V2Product>(this.sitePath(siteName, 'products', idOrSlug), undefined, cachePolicy);
23
24
  }
24
25
 
25
26
  async create(siteName: string, data: V2ProductCreateParams): Promise<V2Product> {
package/src/v2/index.ts CHANGED
@@ -11,7 +11,9 @@
11
11
  */
12
12
 
13
13
  import { HttpClient } from '../utils/http-client';
14
+ import { CacheManager } from '../cache/cache-manager';
14
15
  import type { PerspectApiConfig } from '../types';
16
+ import type { CacheConfig } from '../cache/types';
15
17
 
16
18
  import { ContentV2Client } from './client/content-client';
17
19
  import { ProductsV2Client } from './client/products-client';
@@ -26,8 +28,13 @@ import { SitesV2Client } from './client/sites-client';
26
28
  import { ApiKeysV2Client } from './client/api-keys-client';
27
29
  import { WebhooksV2Client } from './client/webhooks-client';
28
30
 
31
+ export interface PerspectApiV2Config extends PerspectApiConfig {
32
+ cache?: CacheConfig;
33
+ }
34
+
29
35
  export class PerspectApiV2Client {
30
36
  private http: HttpClient;
37
+ readonly cache: CacheManager;
31
38
 
32
39
  readonly content: ContentV2Client;
33
40
  readonly products: ProductsV2Client;
@@ -42,7 +49,7 @@ export class PerspectApiV2Client {
42
49
  readonly apiKeys: ApiKeysV2Client;
43
50
  readonly webhooks: WebhooksV2Client;
44
51
 
45
- constructor(config: PerspectApiConfig) {
52
+ constructor(config: PerspectApiV2Config) {
46
53
  // Ensure base URL points to /api/v2
47
54
  const baseUrl = config.baseUrl.replace(/\/+$/, '');
48
55
  const v2BaseUrl = baseUrl.endsWith('/api/v2')
@@ -50,20 +57,22 @@ export class PerspectApiV2Client {
50
57
  : `${baseUrl}/api/v2`;
51
58
 
52
59
  this.http = new HttpClient({ ...config, baseUrl: v2BaseUrl });
60
+ this.cache = new CacheManager(config.cache);
53
61
 
54
62
  const basePath = '';
55
- this.content = new ContentV2Client(this.http, basePath);
56
- this.products = new ProductsV2Client(this.http, basePath);
57
- this.categories = new CategoriesV2Client(this.http, basePath);
58
- this.collections = new CollectionsV2Client(this.http, basePath);
59
- this.orders = new OrdersV2Client(this.http, basePath);
60
- this.siteUsers = new SiteUsersV2Client(this.http, basePath);
61
- this.newsletter = new NewsletterV2Client(this.http, basePath);
62
- this.contacts = new ContactsV2Client(this.http, basePath);
63
- this.organizations = new OrganizationsV2Client(this.http, basePath);
64
- this.sites = new SitesV2Client(this.http, basePath);
65
- this.apiKeys = new ApiKeysV2Client(this.http, basePath);
66
- this.webhooks = new WebhooksV2Client(this.http, basePath);
63
+ const cache = this.cache;
64
+ this.content = new ContentV2Client(this.http, basePath, cache);
65
+ this.products = new ProductsV2Client(this.http, basePath, cache);
66
+ this.categories = new CategoriesV2Client(this.http, basePath, cache);
67
+ this.collections = new CollectionsV2Client(this.http, basePath, cache);
68
+ this.orders = new OrdersV2Client(this.http, basePath, cache);
69
+ this.siteUsers = new SiteUsersV2Client(this.http, basePath, cache);
70
+ this.newsletter = new NewsletterV2Client(this.http, basePath, cache);
71
+ this.contacts = new ContactsV2Client(this.http, basePath, cache);
72
+ this.organizations = new OrganizationsV2Client(this.http, basePath, cache);
73
+ this.sites = new SitesV2Client(this.http, basePath, cache);
74
+ this.apiKeys = new ApiKeysV2Client(this.http, basePath, cache);
75
+ this.webhooks = new WebhooksV2Client(this.http, basePath, cache);
67
76
  }
68
77
 
69
78
  /** Update the JWT token for authenticated requests. */
package/src/v2/types.ts CHANGED
@@ -89,6 +89,7 @@ export interface V2ContentListParams extends V2PaginationParams {
89
89
  type?: "post" | "page" | "block";
90
90
  search?: string;
91
91
  slug_prefix?: string;
92
+ category?: string;
92
93
  }
93
94
 
94
95
  // --- Products ---