perspectapi-ts-sdk 2.8.3 → 3.0.1

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.
@@ -8,7 +8,6 @@ import type { CachePolicy } from '../cache/types';
8
8
  import type {
9
9
  Category,
10
10
  CreateCategoryRequest,
11
- PaginatedResponse,
12
11
  ApiResponse,
13
12
  } from '../types';
14
13
 
@@ -18,161 +17,181 @@ export class CategoriesClient extends BaseClient {
18
17
  }
19
18
 
20
19
  /**
21
- * Get all categories
20
+ * Get all categories for a site
22
21
  */
23
- async getCategories(params?: {
24
- page?: number;
25
- limit?: number;
26
- parentId?: number;
27
- organizationId?: number;
28
- }): Promise<PaginatedResponse<Category>> {
29
- return this.getPaginated<Category>('/categories', params);
30
- }
22
+ async getCategories(
23
+ siteName: string,
24
+ params?: {
25
+ category_type?: 'post' | 'product';
26
+ parent_id?: string;
27
+ include_subcategories?: 'true' | 'false';
28
+ },
29
+ cachePolicy?: CachePolicy
30
+ ): Promise<ApiResponse<{ categories: Category[] }>> {
31
+ const endpoint = this.siteScopedEndpoint(siteName, '/categories', { includeSitesSegment: false });
32
+ const path = this.buildPath(endpoint);
31
33
 
32
- /**
33
- * Get category by ID
34
- */
35
- async getCategoryById(id: number): Promise<ApiResponse<Category>> {
36
- return this.getSingle<Category>(`/categories/${id}`);
34
+ return this.fetchWithCache<ApiResponse<{ categories: Category[] }>>(
35
+ endpoint,
36
+ params,
37
+ this.buildCategoryTags(siteName),
38
+ cachePolicy,
39
+ () => this.http.get<{ categories: Category[] }>(path, params)
40
+ );
37
41
  }
38
42
 
39
43
  /**
40
- * Get category by slug
44
+ * Get category by ID (validates it belongs to the site)
41
45
  */
42
- async getCategoryBySlug(slug: string): Promise<ApiResponse<Category>> {
43
- return this.getSingle<Category>(`/categories/slug/${slug}`);
46
+ async getCategoryById(siteName: string, id: number, cachePolicy?: CachePolicy): Promise<ApiResponse<{ category: Category }>> {
47
+ const endpoint = this.siteScopedEndpoint(siteName, `/categories/${id}`, { includeSitesSegment: false });
48
+ const path = this.buildPath(endpoint);
49
+
50
+ return this.fetchWithCache<ApiResponse<{ category: Category }>>(
51
+ endpoint,
52
+ undefined,
53
+ this.buildCategoryTags(siteName, `categories:id:${id}`),
54
+ cachePolicy,
55
+ () => this.http.get<{ category: Category }>(path)
56
+ );
44
57
  }
45
58
 
46
59
  /**
47
- * Get product category by slug (for products)
60
+ * Get product categories for a site
48
61
  */
49
- async getProductCategoryBySlug(
62
+ async getProductCategories(
50
63
  siteName: string,
51
- slug: string,
64
+ params?: {
65
+ parent_id?: string;
66
+ include_subcategories?: 'true' | 'false';
67
+ },
52
68
  cachePolicy?: CachePolicy
53
- ): Promise<ApiResponse<Category>> {
54
- const endpoint = this.siteScopedEndpoint(
55
- siteName,
56
- `/product_category/slug/${encodeURIComponent(slug)}`,
57
- { includeSitesSegment: false }
58
- );
69
+ ): Promise<ApiResponse<{ categories: Category[] }>> {
70
+ const endpoint = this.siteScopedEndpoint(siteName, '/categories/product', { includeSitesSegment: false });
59
71
  const path = this.buildPath(endpoint);
60
72
 
61
- return this.fetchWithCache<ApiResponse<Category>>(
73
+ return this.fetchWithCache<ApiResponse<{ categories: Category[] }>>(
62
74
  endpoint,
63
- undefined,
64
- this.buildCategoryTags(siteName, slug),
75
+ params,
76
+ this.buildCategoryTags(siteName, 'categories:product'),
65
77
  cachePolicy,
66
- () => this.http.get<Category>(path)
78
+ () => this.http.get<{ categories: Category[] }>(path, params)
67
79
  );
68
80
  }
69
81
 
70
82
  /**
71
- * Create new category
83
+ * Create new category for a site
72
84
  */
73
- async createCategory(data: CreateCategoryRequest): Promise<ApiResponse<Category>> {
74
- return this.create<CreateCategoryRequest, Category>('/categories', data);
75
- }
85
+ async createCategory(
86
+ siteName: string,
87
+ data: CreateCategoryRequest,
88
+ csrfToken?: string
89
+ ): Promise<ApiResponse<{ message: string; category_id: number }>> {
90
+ const endpoint = this.siteScopedEndpoint(siteName, '/categories', { includeSitesSegment: false });
91
+ const path = this.buildPath(endpoint);
76
92
 
77
- /**
78
- * Update category
79
- */
80
- async updateCategory(id: number, data: Partial<CreateCategoryRequest>): Promise<ApiResponse<Category>> {
81
- return this.update<Partial<CreateCategoryRequest>, Category>(`/categories/${id}`, data);
82
- }
93
+ const result = await this.http.post<{ message: string; category_id: number }>(path, data, { csrfToken });
83
94
 
84
- /**
85
- * Delete category
86
- */
87
- async deleteCategory(id: number): Promise<ApiResponse<{ message: string }>> {
88
- return this.delete<{ message: string }>(`/categories/${id}`);
89
- }
95
+ // Invalidate cache after creation
96
+ if (this.cache) {
97
+ await this.cache.invalidate({ tags: this.buildCategoryTags(siteName) });
98
+ }
90
99
 
91
- /**
92
- * Get category tree (hierarchical structure)
93
- */
94
- async getCategoryTree(rootId?: number): Promise<ApiResponse<Array<Category & {
95
- children: Category[];
96
- }>>> {
97
- const endpoint = rootId ? `/categories/tree/${rootId}` : '/categories/tree';
98
- return this.getSingle(endpoint);
100
+ return result;
99
101
  }
100
102
 
101
103
  /**
102
- * Get category children
104
+ * Create new product category for a site
103
105
  */
104
- async getCategoryChildren(id: number): Promise<ApiResponse<Category[]>> {
105
- return this.getSingle<Category[]>(`/categories/${id}/children`);
106
- }
106
+ async createProductCategory(
107
+ siteName: string,
108
+ data: Omit<CreateCategoryRequest, 'category_type'>,
109
+ csrfToken?: string
110
+ ): Promise<ApiResponse<{ message: string; category_id: number }>> {
111
+ const endpoint = this.siteScopedEndpoint(siteName, '/categories/product', { includeSitesSegment: false });
112
+ const path = this.buildPath(endpoint);
107
113
 
108
- /**
109
- * Get category parent
110
- */
111
- async getCategoryParent(id: number): Promise<ApiResponse<Category | null>> {
112
- return this.getSingle<Category | null>(`/categories/${id}/parent`);
113
- }
114
+ const result = await this.http.post<{ message: string; category_id: number }>(path, data, { csrfToken });
114
115
 
115
- /**
116
- * Move category to new parent
117
- */
118
- async moveCategoryToParent(id: number, parentId: number | null): Promise<ApiResponse<Category>> {
119
- return this.patch<{ parentId: number | null }, Category>(`/categories/${id}`, { parentId });
120
- }
116
+ // Invalidate cache after creation
117
+ if (this.cache) {
118
+ await this.cache.invalidate({ tags: this.buildCategoryTags(siteName, 'categories:product') });
119
+ }
121
120
 
122
- /**
123
- * Get category breadcrumb path
124
- */
125
- async getCategoryBreadcrumb(id: number): Promise<ApiResponse<Array<{
126
- id: number;
127
- name: string;
128
- slug: string;
129
- }>>> {
130
- return this.getSingle(`/categories/${id}/breadcrumb`);
121
+ return result;
131
122
  }
132
123
 
133
124
  /**
134
- * Get category content/products
125
+ * Update category (validates it belongs to the site)
135
126
  */
136
- async getCategoryContent(id: number, params?: {
137
- page?: number;
138
- limit?: number;
139
- type?: 'content' | 'products' | 'all';
140
- }): Promise<ApiResponse<{
141
- content: any[];
142
- products: any[];
143
- total: number;
144
- }>> {
145
- return this.http.get(`/categories/${id}/content`, params);
127
+ async updateCategory(
128
+ siteName: string,
129
+ id: number,
130
+ data: Partial<CreateCategoryRequest>,
131
+ csrfToken?: string
132
+ ): Promise<ApiResponse<{ message: string }>> {
133
+ const endpoint = this.siteScopedEndpoint(siteName, `/categories/${id}`, { includeSitesSegment: false });
134
+ const path = this.buildPath(endpoint);
135
+
136
+ const result = await this.http.put<{ message: string }>(path, data, { csrfToken });
137
+
138
+ // Invalidate cache after update
139
+ if (this.cache) {
140
+ await this.cache.invalidate({ tags: this.buildCategoryTags(siteName, `categories:id:${id}`) });
141
+ }
142
+
143
+ return result;
146
144
  }
147
145
 
148
146
  /**
149
- * Bulk update category order
147
+ * Delete category (validates it belongs to the site)
150
148
  */
151
- async updateCategoryOrder(updates: Array<{
152
- id: number;
153
- order: number;
154
- parentId?: number;
155
- }>): Promise<ApiResponse<{ message: string }>> {
156
- return this.create('/categories/reorder', { updates });
149
+ async deleteCategory(siteName: string, id: number, csrfToken?: string): Promise<ApiResponse<{ message: string }>> {
150
+ const endpoint = this.siteScopedEndpoint(siteName, `/categories/${id}`, { includeSitesSegment: false });
151
+ const path = this.buildPath(endpoint);
152
+
153
+ const result = await this.http.delete<{ message: string }>(path, { csrfToken });
154
+
155
+ // Invalidate cache after deletion
156
+ if (this.cache) {
157
+ await this.cache.invalidate({ tags: this.buildCategoryTags(siteName) });
158
+ }
159
+
160
+ return result;
157
161
  }
158
162
 
159
163
  /**
160
- * Search categories
164
+ * Associate pages or products with categories
161
165
  */
162
- async searchCategories(query: string, params?: {
163
- limit?: number;
164
- organizationId?: number;
165
- }): Promise<ApiResponse<Category[]>> {
166
- return this.http.get(`/categories/search`, { q: query, ...params });
166
+ async associateCategories(
167
+ siteName: string,
168
+ data: {
169
+ entity_id: number;
170
+ entity_type: 'page' | 'product';
171
+ category_ids: number[];
172
+ },
173
+ csrfToken?: string
174
+ ): Promise<ApiResponse<{ message: string }>> {
175
+ const endpoint = this.siteScopedEndpoint(siteName, '/categories/associate', { includeSitesSegment: false });
176
+ const path = this.buildPath(endpoint);
177
+
178
+ const result = await this.http.post<{ message: string }>(path, data, { csrfToken });
179
+
180
+ // Invalidate cache after association
181
+ if (this.cache) {
182
+ await this.cache.invalidate({ tags: this.buildCategoryTags(siteName) });
183
+ }
184
+
185
+ return result;
167
186
  }
168
187
 
169
- private buildCategoryTags(siteName: string, slug: string): string[] {
188
+ private buildCategoryTags(siteName: string, extraTag?: string): string[] {
170
189
  const tags = new Set<string>(['categories']);
171
190
  if (siteName) {
172
191
  tags.add(`categories:site:${siteName}`);
173
192
  }
174
- if (slug) {
175
- tags.add(`categories:product:${siteName}:${slug}`);
193
+ if (extraTag) {
194
+ tags.add(extraTag);
176
195
  }
177
196
  return Array.from(tags.values());
178
197
  }
@@ -29,25 +29,7 @@ export interface PaginatedResponse<T> extends ApiResponse<T[]> {
29
29
  };
30
30
  }
31
31
 
32
- // Authentication
33
- export interface SignUpRequest {
34
- email: string;
35
- password: string;
36
- firstName?: string;
37
- lastName?: string;
38
- }
39
-
40
- export interface SignInRequest {
41
- email: string;
42
- password: string;
43
- }
44
-
45
- export interface AuthResponse {
46
- message: string;
47
- user?: User;
48
- token?: string;
49
- }
50
-
32
+ // Authentication (OTP-based)
51
33
  export interface User {
52
34
  id: string;
53
35
  email: string;
@@ -480,6 +462,8 @@ export interface CreateCheckoutSessionRequest {
480
462
  // Either use existing Stripe price ID
481
463
  price?: string;
482
464
  quantity: number;
465
+ // SKU-based checkout (variant products)
466
+ sku_id?: number;
483
467
  // Or define price data inline
484
468
  price_data?: {
485
469
  currency: string;
@@ -499,6 +483,7 @@ export interface CreateCheckoutSessionRequest {
499
483
  cancelUrl?: string; // Alternative naming
500
484
  customer_email?: string;
501
485
  customerEmail?: string; // Alternative naming
486
+ site_user_id?: string; // Authenticated site user — enables checkout prefill
502
487
  currency?: string;
503
488
  metadata?: CheckoutMetadata;
504
489
  mode?: 'payment' | 'subscription' | 'setup';